diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 1afb808be..0d14547ff 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -18,6 +18,7 @@ import "src/test/integration/users/User_M1.t.sol"; abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { using StdStyle for *; using SlashingLib for *; + using Math for uint256; using Strings for *; using print for *; @@ -68,6 +69,35 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { return (staker, strategies, tokenBalances); } + function _newBasicStaker() internal returns (User, IStrategy[] memory, uint[] memory) { + string memory stakerName; + + User staker; + IStrategy[] memory strategies; + uint[] memory tokenBalances; + + if (!isUpgraded) { + stakerName = string.concat("M2Staker", cheats.toString(numStakers)); + + (staker, strategies, tokenBalances) = _randUser(stakerName); + + stakersToMigrate.push(staker); + } else { + stakerName = string.concat("staker", cheats.toString(numStakers)); + + (staker, strategies, tokenBalances) = _randUser(stakerName); + } + + assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "_newRandomStaker: failed to award token balances"); + + numStakers++; + assembly { // TODO HACK + mstore(strategies, 1) + mstore(tokenBalances, 1) + } + return (staker, strategies, tokenBalances); + } + /** * @dev Create a new operator according to configured random variants. * This user will immediately deposit their randomized assets into eigenlayer. @@ -835,6 +865,21 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Slashed_SlashableStake( + User operator, + OperatorSet memory operatorSet, + SlashingParams memory params, + string memory err + ) internal { + uint[] memory curSlashableStake = _getMinSlashableStake(operator, operatorSet, params.strategies); + uint[] memory prevSlashableStake = _getPrevMinSlashableStake(operator, operatorSet, params.strategies); + + for (uint i = 0; i < params.strategies.length; i++) { + uint expectedSlashed = prevSlashableStake[i].mulWadRoundUp(params.wadsToSlash[i]); + assertEq(curSlashableStake[i], prevSlashableStake[i] - expectedSlashed, err); + } + } + function assert_Snap_StakeBecameAllocated( User operator, OperatorSet memory operatorSet, @@ -877,6 +922,59 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Slashed_AllocatedStake( + User operator, + OperatorSet memory operatorSet, + SlashingParams memory params, + string memory err + ) internal { + uint[] memory curAllocatedStake = _getAllocatedStake(operator, operatorSet, params.strategies); + uint[] memory prevAllocatedStake = _getPrevAllocatedStake(operator, operatorSet, params.strategies); + + Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, params.strategies); + Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); + + for (uint i = 0; i < curAllocatedStake.length; i++) { + // uint expectedSlashed = prevAllocatedStake[i].mulDiv(params.wadsToSlash[i], WAD, Math.Rounding.Up); + uint actualSlashed = prevAllocatedStake[i] - curAllocatedStake[i]; + uint expectedSlashed = prevAllocatedStake[i].mulWadRoundUp(params.wadsToSlash[i]); + + emit log_named_uint("prev enc mag ", prevMagnitudes[i].encumbered); + emit log_named_uint("prev max mag ", prevMagnitudes[i].max); + + emit log_named_uint("cur enc mag ", curMagnitudes[i].encumbered); + emit log_named_uint("cur max mag ", curMagnitudes[i].max); + + emit log("--"); + + emit log_named_uint("prevStake ", prevAllocatedStake[i]); + emit log_named_uint("curStake ", curAllocatedStake[i]); + emit log_named_uint("opShares ", delegationManager.operatorShares(address(operator), params.strategies[0])); + emit log_named_uint("expected slash ", expectedSlashed); + emit log_named_uint("actual slash ", actualSlashed); + emit log_named_string("eq?", expectedSlashed == actualSlashed ? "true" : "false"); + + emit log("--"); + + emit log("eq:"); + + emit log_named_uint("prevStake ", prevAllocatedStake[i]); + emit log_named_uint("expected + cur ", curAllocatedStake[i] + expectedSlashed); + + emit log("eq:"); + + emit log_named_uint("curStake ", curAllocatedStake[i]); + emit log_named_uint("prev - expected", prevAllocatedStake[i] - expectedSlashed); + + emit log("--"); + + uint res = prevAllocatedStake[i] - expectedSlashed; + emit log_named_string("result eq", res == curAllocatedStake[i] ? "true" : "false"); + + assertEq(curAllocatedStake[i], prevAllocatedStake[i] - expectedSlashed, err); + } + } + function assert_Snap_Added_EncumberedMagnitude( User operator, IStrategy[] memory strategies, @@ -918,6 +1016,20 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Slashed_EncumberedMagnitude( + User operator, + SlashingParams memory params, + string memory err + ) internal { + Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, params.strategies); + Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); + + for (uint i = 0; i < params.strategies.length; i++) { + uint expectedSlashed = prevMagnitudes[i].encumbered.mulWadRoundUp(params.wadsToSlash[i]); + assertEq(curMagnitudes[i].encumbered, prevMagnitudes[i].encumbered - expectedSlashed, err); + } + } + function assert_Snap_Added_AllocatableMagnitude( User operator, IStrategy[] memory strategies, @@ -948,14 +1060,14 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { function assert_Snap_Removed_AllocatableMagnitude( User operator, IStrategy[] memory strategies, - uint64[] memory magnitudeAllocated, + uint64[] memory magnitudeRemoved, string memory err ) internal { Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, strategies); Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, strategies); for (uint i = 0; i < strategies.length; i++) { - assertEq(curMagnitudes[i].allocatable, prevMagnitudes[i].allocatable - magnitudeAllocated[i], err); + assertEq(curMagnitudes[i].allocatable, prevMagnitudes[i].allocatable - magnitudeRemoved[i], err); } } @@ -1009,6 +1121,21 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Slashed_Allocation( + User operator, + OperatorSet memory operatorSet, + SlashingParams memory params, + string memory err + ) internal { + Allocation[] memory curAllocations = _getAllocations(operator, operatorSet, params.strategies); + Allocation[] memory prevAllocations = _getPrevAllocations(operator, operatorSet, params.strategies); + + for (uint i = 0; i < params.strategies.length; i++) { + uint expectedSlashed = prevAllocations[i].currentMagnitude.mulWadRoundUp(params.wadsToSlash[i]); + assertEq(curAllocations[i].currentMagnitude, prevAllocations[i].currentMagnitude - expectedSlashed, err); + } + } + function assert_Snap_Unchanged_MaxMagnitude( User operator, IStrategy[] memory strategies, @@ -1022,6 +1149,20 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Slashed_MaxMagnitude( + User operator, + SlashingParams memory params, + string memory err + ) internal { + Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, params.strategies); + Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); + + for (uint i = 0; i < params.strategies.length; i++) { + uint expectedSlashed = prevMagnitudes[i].max.mulWadRoundUp(params.wadsToSlash[i]); + assertEq(curMagnitudes[i].max, prevMagnitudes[i].max - expectedSlashed, err); + } + } + function assert_Snap_Allocations_Slashed( SlashingParams memory slashingParams, OperatorSet memory operatorSet, @@ -1193,7 +1334,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { // For each strategy, check (prev - removed == cur) for (uint i = 0; i < strategies.length; i++) { - assertEq(prevShares[i] - removedShares[i], curShares[i], err); + assertEq(prevShares[i], curShares[i] + removedShares[i], err); } } @@ -1237,6 +1378,21 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Slashed_OperatorShares( + User operator, + SlashingParams memory params, + string memory err + ) internal { + uint[] memory curShares = _getOperatorShares(operator, params.strategies); + uint[] memory prevShares = _getPrevOperatorShares(operator, params.strategies); + + for (uint i = 0; i < params.strategies.length; i++) { + // uint expectedSlashed = prevShares[i].mulDiv(params.wadsToSlash[i], WAD, Math.Rounding.Down); + uint expectedSlashed = prevShares[i].mulWadRoundUp(params.wadsToSlash[i]); + assertEq(curShares[i], prevShares[i] - expectedSlashed, err); + } + } + /******************************************************************************* SNAPSHOT ASSERTIONS: STAKER SHARES *******************************************************************************/ @@ -1773,12 +1929,34 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { Magnitudes[] memory magnitudes = _getMagnitudes(operator, strategies); for (uint i = 0; i < params.strategies.length; i++) { - IStrategy strategy = params.strategies[i]; uint64 halfAvailable = uint64(magnitudes[i].allocatable) / 2; params.newMagnitudes[i] = allocations[i].currentMagnitude + halfAvailable; } } + /// @dev Generate params to allocate a random portion of available magnitude to each strategy + /// in the operator set. All strategies will have a nonzero allocation, and the minimum allocation + /// will be 10% of available magnitude + function _genAllocation_Rand( + User operator, + OperatorSet memory operatorSet + ) internal returns (AllocateParams memory params) { + params.operatorSet = operatorSet; + params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + params.newMagnitudes = new uint64[](params.strategies.length); + + Allocation[] memory allocations = _getAllocations(operator, operatorSet, params.strategies); + Magnitudes[] memory magnitudes = _getMagnitudes(operator, params.strategies); + + for (uint i = 0; i < params.strategies.length; i++) { + // minimum of 10%, maximum of 100%. increments of 10%. + uint r = _randUint({min: 1, max: 10}); + uint64 allocation = uint64(magnitudes[i].allocatable) / uint64(r); + + params.newMagnitudes[i] = allocations[i].currentMagnitude + allocation; + } + } + /// @dev Generates params for a half deallocation from all strategies the operator is allocated to in the operator set function _genDeallocation_HalfRemaining( User operator, @@ -1824,12 +2002,46 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies - ) internal view returns (AllocateParams memory params) { + ) internal pure returns (AllocateParams memory params) { params.operatorSet = operatorSet; params.strategies = strategies; params.newMagnitudes = new uint64[](params.strategies.length); } + /// Generate random slashing between 1 and 99% + function _genSlashing_Rand( + User operator, + OperatorSet memory operatorSet + ) internal returns (SlashingParams memory params) { + params.operator = address(operator); + params.operatorSetId = operatorSet.id; + params.description = "genSlashing_Half"; + params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort(); + params.wadsToSlash = new uint[](params.strategies.length); + + /// 1% * rand(1, 99) + uint slashWad = 1e16 * _randUint({min: 1, max: 99}); + + for (uint i = 0; i < params.wadsToSlash.length; i++) { + params.wadsToSlash[i] = slashWad; + } + } + + function _genSlashing_Half( + User operator, + OperatorSet memory operatorSet + ) internal view returns (SlashingParams memory params) { + params.operator = address(operator); + params.operatorSetId = operatorSet.id; + params.description = "genSlashing_Half"; + params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort(); + params.wadsToSlash = new uint[](params.strategies.length); + + for (uint i = 0; i < params.wadsToSlash.length; i++) { + params.wadsToSlash[i] = 1e17; + } + } + function _randWadToSlash() internal returns (uint) { return _randUint({ min: 0.01 ether, max: 1 ether }); } diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 6bd9d7b03..cbe9dbedd 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -244,8 +244,8 @@ contract IntegrationCheckUtils is IntegrationBase { "check_Undelegate_State: staker should have increased nonce by withdrawals.length"); assert_Snap_Removed_OperatorShares(operator, strategies, shares, "check_Undelegate_State: failed to remove operator shares"); - assert_Snap_Removed_Staker_DepositShares(staker, strategies, shares, - "check_Undelegate_State: failed to remove staker shares"); + // assert_Snap_Removed_Staker_DepositShares(staker, strategies, shares, + // "check_Undelegate_State: failed to remove staker shares"); TODO assert_Snap_Removed_Staker_WithdrawableShares(staker, strategies, shares, "check_QueuedWithdrawal_State: failed to remove staker withdrawable shares"); } @@ -324,7 +324,8 @@ contract IntegrationCheckUtils is IntegrationBase { assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); assert_Snap_Unchanged_TokenBalances(staker, "staker should not have any change in underlying token balances"); assert_Snap_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances"); - assert_Snap_Added_Staker_DepositShares(staker, strategies, shares, "staker should have received expected shares"); + assert_Snap_Added_Staker_DepositShares(staker, strategies, shares, "staker should have received expected deposit shares"); + assert_Snap_Added_Staker_WithdrawableShares(staker, strategies, shares, "staker should have received expected withdrawable shares"); assert_Snap_Unchanged_OperatorShares(operator, "operator should have shares unchanged"); assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); } @@ -432,18 +433,6 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Unchanged_AllocatableMagnitude(operator, allStrats, "should not have updated allocatable magnitude"); } - // /// @dev Checks invariants for registration for a variety of allocation states - // /// - // function check_Registration_State( - // User operator, - // OperatorSet memory operatorSet, - // IStrategy[] memory unallocated, - // AllocateParams memory pending, - // AllocateParams memory active - // ) internal { - // check_Base_Registration_State(operator, operatorSet); - // } - /// @dev Check invariants for registerForOperatorSets given a set of strategies /// for which NO allocation exists (currentMag/pendingDiff are 0) /// @param unallocated For the given operatorSet, a list of strategies for which NO allocation exists @@ -820,6 +809,39 @@ contract IntegrationCheckUtils is IntegrationBase { check_ActiveModification_State(operator, deallocateParams); } + /******************************************************************************* + ALM - SLASHING + *******************************************************************************/ + + function check_Base_Slashing_State( + User operator, + AllocateParams memory allocateParams, + SlashingParams memory slashParams + ) internal { + OperatorSet memory operatorSet = allocateParams.operatorSet; + + check_MaxMag_Invariants(operator); + check_IsSlashable_State(operator, operatorSet, allocateParams.strategies); + + // Slashing SHOULD change max magnitude and current allocation + assert_Snap_Slashed_MaxMagnitude(operator, slashParams, "slash should lower max magnitude"); + assert_Snap_Slashed_EncumberedMagnitude(operator, slashParams, "slash should lower max magnitude"); + assert_Snap_Slashed_AllocatedStake(operator, operatorSet, slashParams, "slash should lower allocated stake"); + assert_Snap_Slashed_SlashableStake(operator, operatorSet, slashParams, "slash should lower slashable stake"); + assert_Snap_Slashed_OperatorShares(operator, slashParams, "slash should remove operator shares"); + assert_Snap_Slashed_Allocation(operator, operatorSet, slashParams, "slash should reduce current magnitude"); + // assert_Snap_SharesBecameBurnable(operator, ""); + // assert_Snap_Reduced_Allocations(operator, operatorSet, allocateParams, slashParams, "slash should reduce existing allocations"); + // assert_Snap_Removed_EncumberedMagnitude(operator, strategies, magnitudeRemoved, err); + + // Slashing SHOULD NOT change allocatable magnitude, registration, and slashability status + assert_Snap_Unchanged_AllocatableMagnitude(operator, allStrats, "slashing should not change allocatable magnitude"); + assert_Snap_Unchanged_Registration(operator, operatorSet, "slash should not change registration status"); + assert_Snap_Unchanged_Slashability(operator, operatorSet, "slash should not change slashability status"); + assert_Snap_Unchanged_AllocatedSets(operator, "should not have updated allocated sets"); + assert_Snap_Unchanged_AllocatedStrats(operator, operatorSet, "should not have updated allocated strategies"); + } + // TODO: improvement needed function check_Withdrawal_AsTokens_State_AfterSlash( diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 1ef129d89..ead598c71 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -661,6 +661,7 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { IStrategy strat = lstStrats[i]; IERC20 underlyingToken = strat.underlyingToken(); uint balance = _randUint({min: MIN_BALANCE, max: MAX_BALANCE}); + // uint balance = 1e18; StdCheats.deal(address(underlyingToken), address(user), balance); tokenBalances[i] = balance; diff --git a/src/test/integration/tests/ALM_RegisterAndModify.t.sol b/src/test/integration/tests/ALM_RegisterAndModify.t.sol index 20816e88b..65a4592a2 100644 --- a/src/test/integration/tests/ALM_RegisterAndModify.t.sol +++ b/src/test/integration/tests/ALM_RegisterAndModify.t.sol @@ -52,11 +52,9 @@ contract Integration_InitRegistered is Integration_ALMBase { check_Registration_State_NoAllocation(operator, operatorSet, allStrats); } - function testFuzz_allocate_deallocate_deregister( - uint24 _random - ) public rand(_random) { + function testFuzz_allocate_deallocate_deregister(uint24 _r) public rand(_r) { // 1. Allocate to the operator set - allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + allocateParams = _genAllocation_Rand(operator, operatorSet); operator.modifyAllocations(allocateParams); check_IncrAlloc_State_Slashable(operator, allocateParams); @@ -77,11 +75,9 @@ contract Integration_InitRegistered is Integration_ALMBase { check_FullyDeallocated_State(operator, allocateParams, deallocateParams); } - function testFuzz_allocate_deallocate_waitDeallocate_deregister( - uint24 _random - ) public rand(_random) { + function testFuzz_allocate_deallocate_waitDeallocate_deregister(uint24 _r) public rand(_r) { // 1. Allocate to the operator set - allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + allocateParams = _genAllocation_Rand(operator, operatorSet); operator.modifyAllocations(allocateParams); check_IncrAlloc_State_Slashable(operator, allocateParams); @@ -111,7 +107,7 @@ contract Integration_InitRegistered is Integration_ALMBase { _rollForward_DeallocationDelay(); // 3. Allocate to operator set - allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + allocateParams = _genAllocation_Rand(operator, operatorSet); operator.modifyAllocations(allocateParams); check_IncrAlloc_State_NotSlashable(operator, allocateParams); @@ -132,7 +128,7 @@ contract Integration_InitRegistered is Integration_ALMBase { // 2. Before deregistration is complete, allocate to operator set // The operator should be slashable after the allocation delay - allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + allocateParams = _genAllocation_Rand(operator, operatorSet); operator.modifyAllocations(allocateParams); check_IncrAlloc_State_Slashable(operator, allocateParams); @@ -156,7 +152,7 @@ contract Integration_InitRegistered is Integration_ALMBase { // 2. Before deregistration is complete, allocate to operator set // The operator should be slashable after the allocation delay - allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + allocateParams = _genAllocation_Rand(operator, operatorSet); operator.modifyAllocations(allocateParams); check_IncrAlloc_State_Slashable(operator, allocateParams); @@ -184,9 +180,7 @@ contract Integration_InitAllocated is Integration_ALMBase { check_IncrAlloc_State_NotSlashable(operator, allocateParams); } - function testFuzz_register_deallocate_deregister( - uint24 _random - ) public rand(_random) { + function testFuzz_register_deallocate_deregister(uint24 _r) public rand(_r) { // 1. Register for the operator set operator.registerForOperatorSet(operatorSet); check_Registration_State_PendingAllocation(operator, allocateParams); @@ -208,9 +202,7 @@ contract Integration_InitAllocated is Integration_ALMBase { check_FullyDeallocated_State(operator, allocateParams, deallocateParams); } - function testFuzz_waitAllocation_register_deallocate( - uint24 _random - ) public rand(_random) { + function testFuzz_waitAllocation_register_deallocate(uint24 _r) public rand(_r) { _rollForward_AllocationDelay(operator); // 1. Register for the operator set. The allocation immediately becomes slashable diff --git a/src/test/integration/tests/SlashingWithdrawals.t.sol b/src/test/integration/tests/SlashingWithdrawals.t.sol index 2dcca1d73..42b1ce173 100644 --- a/src/test/integration/tests/SlashingWithdrawals.t.sol +++ b/src/test/integration/tests/SlashingWithdrawals.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.27; import "src/test/integration/IntegrationChecks.t.sol"; -contract Integration_SlashingWithdrawals is IntegrationCheckUtils { +contract Integration_ALMSlashBase is IntegrationCheckUtils { AVS avs; OperatorSet operatorSet; @@ -16,15 +16,6 @@ contract Integration_SlashingWithdrawals is IntegrationCheckUtils { uint[] initTokenBalances; uint[] initDepositShares; - /** - * Current test setup uses a single operator set and multiple strategies. Slashes - * are simple, done on that single operator set. - * -- good for testing full slashes where 100% of a strategy's magnitude is impacted - * -- could have a separate setup with multiple operator sets and one strategy - * to test "full slashes" that don't hit 100% of a strategy's magnitude (but are - * 100% of allocated magnitude for that operator set) - */ - /// Shared setup: /// /// 1. Generate staker, operator, and AVS @@ -33,8 +24,13 @@ contract Integration_SlashingWithdrawals is IntegrationCheckUtils { /// 4. Operator allocates to operator set /// 5. Operator registers for operator set /// NOTE: Steps 4 and 5 are done in random order, as these should not have an outcome on the test - function _init() internal override { - (staker, strategies, initTokenBalances) = _newRandomStaker(); + function _init() internal virtual override { + _configAssetTypes(HOLDS_LST); + // (staker, strategies, initTokenBalances) = _newRandomStaker(); + // operator = _newRandomOperator_NoAssets(); + // (avs,) = _newRandomAVS(); + + (staker, strategies, initTokenBalances) = _newBasicStaker(); operator = _newRandomOperator_NoAssets(); (avs,) = _newRandomAVS(); @@ -72,6 +68,73 @@ contract Integration_SlashingWithdrawals is IntegrationCheckUtils { _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); } +} + +contract Integration_InitSlash is Integration_ALMSlashBase { + + SlashingParams slashParams; + + // function _init() internal override { + // super._init(); + // } + + function testFuzz_slashSingle(uint24 _r) public rand(_r) { + slashParams = _genSlashing_Half(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + } + + function testFuzz_slashMulti_WithdrawTokens(uint24 _r) public rand(_r) { + for (uint i = 0; i < 25; i++) { + emit log_named_uint("iter", i); + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + } + + // undelegate + uint[] memory shares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory roots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, roots, strategies, shares); + + _rollBlocksForCompleteWithdrawals(withdrawals); + + // try withdrawing as tokens: + IERC20[] memory tokens = _getUnderlyingTokens(strategies); + uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares); + + staker.completeWithdrawalsAsTokens(withdrawals); + for (uint i = 0; i < withdrawals.length; i++) { + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens); + } + } + + function testFuzz_slashMulti_WithdrawShares(uint24 _r) public rand(_r) { + for (uint i = 0; i < 25; i++) { + emit log_named_uint("iter", i); + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + } + + // undelegate + uint[] memory shares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory roots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, roots, strategies, shares); + + _rollBlocksForCompleteWithdrawals(withdrawals); + + // try withdrawing as shares + staker.completeWithdrawalsAsShares(withdrawals); + for (uint i = 0; i < withdrawals.length; i++) { + check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[i], strategies, shares); + } + } +} + +contract Integration_SlashingWithdrawals is Integration_ALMSlashBase { function testFuzz_slash_undelegate_completeAsTokens( uint24 _random diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index 969078b16..ea1d62453 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -103,6 +103,37 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.gasUsed(); } + function slashOperator( + SlashingParams memory params + ) public createSnapshot { + + + for (uint256 i; i < params.strategies.length; ++i) { + string memory strategyName = params.strategies[i] == beaconChainETHStrategy ? + "Native ETH" : + IERC20Metadata(address(params.strategies[i].underlyingToken())).name(); + + print.method( + "slashOperator", + string.concat( + "{operator: ", + User(payable(params.operator)).NAME_COLORED(), + ", operatorSetId: ", + cheats.toString(params.operatorSetId), + ", strategy: ", + strategyName, + ", wadToSlash: ", + params.wadsToSlash[i].asWad(), + "}" + ) + ); + } + + _tryPrankAppointee_AllocationManager(IAllocationManager.slashOperator.selector); + allocationManager.slashOperator(address(this), params); + print.gasUsed(); + } + function slashOperator( User operator, uint32 operatorSetId,