Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: beacon chain and AVS fuzz tests #891

Draft
wants to merge 2 commits into
base: slashing-magnitudes
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/forge-std
184 changes: 167 additions & 17 deletions src/test/unit/DelegationUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag

// Deploy DelegationManager implmentation and upgrade proxy
delegationManagerImplementation = new DelegationManager(
IAVSDirectory(address(avsDirectoryMock)),
IStrategyManager(address(strategyManagerMock)),
IEigenPodManager(address(eigenPodManagerMock)),
IAllocationManager(address(allocationManagerMock)),
IAVSDirectory(address(avsDirectoryMock)),
IStrategyManager(address(strategyManagerMock)),
IEigenPodManager(address(eigenPodManagerMock)),
IAllocationManager(address(allocationManagerMock)),
pauserRegistry,
MIN_WITHDRAWAL_DELAY_BLOCKS
);
Expand Down Expand Up @@ -150,7 +150,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
*/

/**
* @notice internal function to deploy mock tokens and strategies and have the staker deposit into them.
* @notice internal function to deploy mock tokens and strategies and have the staker deposit into them.
* Since we are mocking the strategyManager we call strategyManagerMock.setDeposits so that when
* DelegationManager calls getDeposits, we can have these share amounts returned.
*/
Expand Down Expand Up @@ -306,7 +306,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag

/**
* @notice Using this helper function to fuzz withdrawalAmounts since fuzzing two dynamic sized arrays of equal lengths
* reject too many inputs.
* reject too many inputs.
*/
function _fuzzDepositWithdrawalAmounts(
Randomness r,
Expand Down Expand Up @@ -367,7 +367,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
scaledShares: scaledSharesArray
});
bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal);

return (queuedWithdrawalParams, withdrawal, withdrawalRoot);
}

Expand Down Expand Up @@ -406,7 +406,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
scaledShares: scaledSharesArray
});
bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal);

return (queuedWithdrawalParams, withdrawal, withdrawalRoot);
}

Expand Down Expand Up @@ -476,7 +476,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag

/**
* Deploy and deposit staker into a single strategy, then set up a queued withdrawal for the staker
* Assumptions:
* Assumptions:
* - operator is already a registered operator.
* - withdrawalAmount <= depositAmount
*/
Expand Down Expand Up @@ -574,7 +574,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag

/**
* Deploy and deposit staker into strategies, then set up a queued withdrawal for the staker
* Assumptions:
* Assumptions:
* - operator is already a registered operator.
* - for each i, withdrawalAmount[i] <= depositAmount[i] (see filterFuzzedDepositWithdrawInputs above)
*/
Expand Down Expand Up @@ -1332,7 +1332,7 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests {
cheats.expectRevert(FullySlashed.selector);
delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);

assertEq(
beaconSharesBefore,
beaconSharesAfter,
Expand Down Expand Up @@ -3033,6 +3033,156 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest
);
}
}

function testFuzz_beaconSlashAndAVSSlash(
int256 beaconShares,
uint64 initialMagnitude,
uint64 slashedMagnitude,
uint64 beaconChainSlashingFactor,
Randomness r
) public filterFuzzedAddressInputs(defaultStaker) { // remember to filter fuzz inputs
// filter inputs, since this will fail when the defaultStaker is already registered as an operator
cheats.assume(defaultStaker != defaultOperator);
// assume operator has not been fully slashed yet
cheats.assume(initialMagnitude > 0);
// make sure that operator magnitude after AVS slash is not larger than initial magnitude
cheats.assume(slashedMagnitude < initialMagnitude);

// note: beaconShares only goes negative when performing withdrawal -- and this will change post-migration
// so it's ok to make this assumption
cheats.assume(beaconShares > 0);
int256 beaconShares = int256(r.Uint256(1, MAX_ETH_SUPPLY));

// TODO: remove to enable fuzzing these parameters
initialMagnitude = WAD;
slashedMagnitude = initialMagnitude / 2;

////////////////////////////
// 0. setup operator with Beacon Chain stake
////////////////////////////
_registerOperatorWithBaseDetails(defaultOperator);

// set operator's magnitude
_setOperatorMagnitude({
operator: defaultOperator,
strategy: beaconChainETHStrategy,
magnitude: initialMagnitude
});

// set up stake for operator
eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares);

// expecting equal to 0
// TODO: rename to operatorShares
uint256 beaconSharesBefore = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);

// perform the delegation
_delegateTo_expectEmit(
DelegateToEmitStruct({
staker: defaultStaker,
operator: defaultOperator,
strategies: beaconChainETHStrategy.toArray(),
depositShares: uint256(beaconShares).toArrayU256(),
depositScalingFactors: dsf.scalingFactor().toArrayU256()
})
);
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);

// check that operator shares increase after delegation
uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);

// TODO: restore
// assertEq(
// beaconSharesBefore + uint256(beaconShares),
// beaconSharesAfter,
// "operator beaconchain shares not increased correctly"
// );

// NOTE: when to use getWithdrawableShares vs fetching operatorShares directly?
// Answer: use former when considering slashing factor, latter when just looking for raw units
uint[] memory withdrawableShares;
(withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray());

// TODO: figure out why this assertion has an off-by-one error
// assertEq(withdrawableShares, depositShares);
// withdrawableShares <= depositShares is expected behavior (rounding down)

// TODO: figure out why this isn't working
assertEq(int256(withdrawableShares[0]), beaconShares);

////////////////////////////
// 1. do beacon chain slash then AVS slash
////////////////////////////

(withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray());
uint beaconSharesBeforeSlash = withdrawableShares[0];

// do a slash on the beacon chain
cheats.prank(address(eigenPodManagerMock));
delegationManager.decreaseBeaconChainScalingFactor(defaultStaker, beaconSharesBeforeSlash, beaconChainSlashingFactor);


(withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray());
uint beaconSharesAfterSlash = withdrawableShares[0];

emit log_uint(beaconSharesAfterSlash);

uint expectedBeaconShares = beaconSharesBeforeSlash * beaconChainSlashingFactor / WAD;

assertApproxEqRel(
expectedBeaconShares,
beaconSharesAfterSlash,
0.01e18, // TODO: decrease error delta to 0.001%)
"beacon chain shares not slashed correctly"
);

// do a slash via an AVS
allocationManagerMock.setMaxMagnitude(defaultOperator, beaconChainETHStrategy, slashedMagnitude);

// save the outcome
(withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray());
uint beaconSharesAfterSecondSlash = withdrawableShares[0];

emit log_uint(beaconSharesAfterSecondSlash);

// expectedBeaconShares = beaconSharesAfterSlash * slashedMagnitude / initialMagnitude;

assertApproxEqRel(
expectedBeaconShares, // TODO: figure out how to properly calculate this factor
beaconSharesAfterSecondSlash,
0.01e18, // TODO: decrease error delta to 0.001%)
"beacon chain -> AVS shares not slashed correctly"
);

// (withdrawableShares,) = delegationManager.getWithdrawableShares(staker, strategies);
// uint beaconSharesAfterSlash = withdrawableShares[0];
// uint beaconSharesAfterSlash = uint(eigenPodManagerMock.podOwnerShares(staker));

// restore the staker and operator to their original state

// set operator's magnitude
// _setOperatorMagnitude({
// operator: defaultOperator,
// strategy: beaconChainETHStrategy,
// magnitude: initialMagnitude
// });

// set up stake for operator
// eigenPodManagerMock.setPodOwnerShares(staker, beaconShares);

////////////////////////////
// 2. do AVS slash then beacon chain slash
////////////////////////////

// 2.1 do a slash via an AVS
// 2.2 do a slash via the beacon chain
// 2.3 save the outcome

// 3. see if they're the same (i.e. that the slashes are composable)

// NOTE: deposit scaling factor not being updated correctly in beacon chain scenario -- undelegate -> deposit scenario, flow yields incorrect results.
// check out thread in group DM "question about undelegating"
}
}

contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests {
Expand Down Expand Up @@ -3205,7 +3355,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests {
// register *this contract* as an operator and delegate from the `staker` to them
_registerOperatorWithBaseDetails(defaultOperator);
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);

// Format queued withdrawal
(
,
Expand Down Expand Up @@ -3444,7 +3594,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests {

// register *this contract* as an operator
_registerOperatorWithBaseDetails(defaultOperator);

// Set the staker deposits in the strategies
strategyManagerMock.addDeposit(defaultStaker, strategy, shares);

Expand Down Expand Up @@ -3651,7 +3801,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests {
// register *this contract* as an operator & set its slashed magnitude
_registerOperatorWithBaseDetails(defaultOperator);
_setOperatorMagnitude(defaultOperator, strategyMock, operatorMagnitude);

// Set the staker deposits in the strategies
IStrategy[] memory strategies = strategyMock.toArray();
{
Expand Down Expand Up @@ -3880,7 +4030,7 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes

/**
* @notice Verifies that `DelegationManager.queueWithdrawals` properly queues a withdrawal for the `withdrawer`
* from the `strategy` for the `sharesAmount`.
* from the `strategy` for the `sharesAmount`.
* - Asserts that staker is delegated to the operator
* - Asserts that shares for delegatedTo operator are decreased by `sharesAmount`
* - Asserts that staker cumulativeWithdrawalsQueued nonce is incremented
Expand Down Expand Up @@ -4587,7 +4737,7 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
cheats.prank(defaultStaker);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, true);
}

function test_Revert_WhenWithdrawerNotCaller(address invalidCaller) filterFuzzedAddressInputs(invalidCaller) public {
cheats.assume(invalidCaller != defaultStaker);

Expand Down Expand Up @@ -4902,7 +5052,7 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
assertEq(operatorSharesAfterWithdrawalComplete, operatorSharesAfterSlash, "operator shares should be unchanged from slash to withdrawal completion");
assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be completed and marked false now");

// Checks: staker shares:
// Checks: staker shares:
uint256 stakerSharesWithdrawn = strategyManagerMock.strategySharesWithdrawn(defaultStaker, strategyMock);
{
dsf = DepositScalingFactor({
Expand Down
Loading