From b8e148831582c423e670daf946bd224551596dc3 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:49:34 -0500 Subject: [PATCH 01/18] test(wip): add `HOLDS_MAX` asset type --- .../integration/IntegrationDeployer.t.sol | 21 ++++---- ...e_Undelegate_Withdraw_Max_Strategies.t.sol | 49 +++++++++++++++++++ src/test/utils/Logger.t.sol | 3 +- 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 0e8cd6c72..efedd94b8 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -60,6 +60,8 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser, Logger { uint64 constant GENESIS_TIME_LOCAL = 1 hours * 12; uint64 constant GENESIS_TIME_MAINNET = 1_606_824_023; + uint8 constant NUM_LST_STRATS = 32; + TimeMachine public timeMachine; // Lists of strategies used in the system @@ -288,15 +290,12 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser, Logger { cheats.prank(eigenLayerReputedMultisig); strategyManager.setStrategyWhitelister(address(strategyFactory)); - // Normal deployments - _newStrategyAndToken("Strategy1 token", "str1", 10e50, address(this), false); // initialSupply, owner - _newStrategyAndToken("Strategy2 token", "str2", 10e50, address(this), false); // initialSupply, owner - _newStrategyAndToken("Strategy3 token", "str3", 10e50, address(this), false); // initialSupply, owner - - // Factory deployments - _newStrategyAndToken("Strategy4 token", "str4", 10e50, address(this), true); // initialSupply, owner - _newStrategyAndToken("Strategy5 token", "str5", 10e50, address(this), true); // initialSupply, owner - _newStrategyAndToken("Strategy6 token", "str6", 10e50, address(this), true); // initialSupply, owner + for (uint i = 1; i < NUM_LST_STRATS + 1; ++i) { + string memory name = string.concat("LST-Strat", cheats.toString(i), " token"); + string memory symbol = string.concat("lstStrat", cheats.toString(i)); + // Deploy half of the strategies using the factory. + _newStrategyAndToken(name, symbol, 10e50, address(this), i % 2 == 0); + } ethStrats.push(BEACONCHAIN_ETH_STRAT); allStrats.push(BEACONCHAIN_ETH_STRAT); @@ -769,8 +768,8 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser, Logger { strategies[0] = BEACONCHAIN_ETH_STRAT; tokenBalances[0] = amount; - } else if (assetType == HOLDS_ALL) { - uint numLSTs = lstStrats.length; + } else if (assetType == HOLDS_ALL || assetType == HOLDS_MAX) { + uint numLSTs = assetType == HOLDS_MAX ? lstStrats.length : 5; strategies = new IStrategy[](numLSTs + 1); tokenBalances = new uint[](numLSTs + 1); diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol new file mode 100644 index 000000000..d81c30272 --- /dev/null +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/users/User.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; + +contract Integration_Deposit_Delegate_Undelegate_Withdraw_Max_Strategies is + IntegrationCheckUtils, + IDelegationManagerTypes +{ + function testFuzz_deposit_delegate_undelegate_withdraw_max_strategies( + uint24 _random + ) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); + _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local + + (User staker, IStrategy[] memory strategies, uint256[] memory balances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + (AVS avs,) = _newRandomAVS(); + + OperatorSet memory operatorSet = avs.createOperatorSet(strategies); + + assertEq(strategies.length, 33, "sanity"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, balances); + + // 2. Delegate to an operator + staker.delegateTo(operator); + + // 3. Register for operator set + operator.registerForOperatorSet(operatorSet); + + // 4. Allocate to operator set. + operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + + // 5. Slash operator + avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + + // 6. Undelegate from an operator + Withdrawal[] memory withdrawals = staker.undelegate(); + + // 7. Complete withdrawal + _rollBlocksForCompleteWithdrawals(); + staker.completeWithdrawalsAsTokens(withdrawals); + } +} diff --git a/src/test/utils/Logger.t.sol b/src/test/utils/Logger.t.sol index 41a64fbf6..8f36bc07e 100644 --- a/src/test/utils/Logger.t.sol +++ b/src/test/utils/Logger.t.sol @@ -21,7 +21,8 @@ uint256 constant FLAG = 1; uint256 constant NO_ASSETS = (FLAG << 0); // will have no assets uint256 constant HOLDS_LST = (FLAG << 1); // will hold some random amount of LSTs uint256 constant HOLDS_ETH = (FLAG << 2); // will hold some random amount of ETH -uint256 constant HOLDS_ALL = (FLAG << 3); // will hold every LST and ETH +uint256 constant HOLDS_ALL = (FLAG << 3); // will always hold ETH, and some LSTs +uint256 constant HOLDS_MAX = (FLAG << 4); // will hold every LST and ETH (used for testing max strategies) /// @dev Types representing the different types of users that can be created. uint256 constant DEFAULT = (FLAG << 0); From 238d84f5294512aae43b0afed1c18fe8d046394c Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:16:58 -0500 Subject: [PATCH 02/18] test: review changes - still failing with `WithdrawalNotQueued` when slashing added --- ...e_Undelegate_Withdraw_Max_Strategies.t.sol | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol index d81c30272..cef0ed169 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol @@ -32,12 +32,44 @@ contract Integration_Deposit_Delegate_Undelegate_Withdraw_Max_Strategies is // 3. Register for operator set operator.registerForOperatorSet(operatorSet); + // 4. Undelegate from an operator + Withdrawal[] memory withdrawals = staker.undelegate(); + + // 5. Complete withdrawal + _rollBlocksForCompleteWithdrawals(); + staker.completeWithdrawalsAsTokens(withdrawals); + } + + function testFuzz_deposit_delegate_allocate_slash_undelegate_withdraw_max_strategies( + uint24 _random + ) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); + _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local + + (User staker, IStrategy[] memory strategies, uint256[] memory balances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + (AVS avs,) = _newRandomAVS(); + + OperatorSet memory operatorSet = avs.createOperatorSet(strategies); + + assertEq(strategies.length, 33, "sanity"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, balances); + + // 2. Delegate to an operator + staker.delegateTo(operator); + + // 3. Register for operator set + operator.registerForOperatorSet(operatorSet); + // 4. Allocate to operator set. operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); // 5. Slash operator - avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + avs.slashOperator(operator, operatorSet.id, 0.5 ether); // 6. Undelegate from an operator Withdrawal[] memory withdrawals = staker.undelegate(); From a2b0c4304d998b25d700f12caa996ee31298aa1b Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:29:10 -0500 Subject: [PATCH 03/18] fix: use `_randWadToSlash` --- .../Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol index cef0ed169..3efe223ee 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol @@ -69,7 +69,7 @@ contract Integration_Deposit_Delegate_Undelegate_Withdraw_Max_Strategies is cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); // 5. Slash operator - avs.slashOperator(operator, operatorSet.id, 0.5 ether); + avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); // 6. Undelegate from an operator Withdrawal[] memory withdrawals = staker.undelegate(); From b606549e7f2f114d9b094bc562f72591e26ccf02 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:34:07 -0500 Subject: [PATCH 04/18] fix: `_getExpectedWithdrawalStructsForStaker` --- src/test/integration/users/User.t.sol | 46 ++++++++++++++++++++------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index dd772cf29..eadb2f248 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -28,9 +28,12 @@ interface IUserDeployer { } contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { + using SlashingLib for *; using SingleItemArrayLib for *; using print for *; + IStrategy constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); + AllocationManager allocationManager; DelegationManager delegationManager; StrategyManager strategyManager; @@ -577,34 +580,53 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(pod)); } + function _getSlashingFactor( + address staker, + IStrategy strategy + ) internal view returns (uint256) { + address operator = delegationManager.delegatedTo(staker); + uint64 maxMagnitude = allocationManager.getMaxMagnitudes(operator, strategy.toArray())[0]; + if (strategy == beaconChainETHStrategy) { + return maxMagnitude.mulWad(eigenPodManager.beaconChainSlashingFactor(staker)); + } + return maxMagnitude; + } + /// @notice Gets the expected withdrawals to be created when the staker is undelegated via a call to `DelegationManager.undelegate()` /// @notice Assumes staker and withdrawer are the same and that all strategies and shares are withdrawn function _getExpectedWithdrawalStructsForStaker( address staker - ) internal view returns (Withdrawal[] memory) { - (IStrategy[] memory strategies, uint256[] memory shares) = delegationManager.getDepositedShares(staker); + ) internal view returns (Withdrawal[] memory expectedWithdrawals) { + (IStrategy[] memory strategies, ) + = delegationManager.getDepositedShares(staker); + + expectedWithdrawals = new Withdrawal[](strategies.length); + + (uint256[] memory withdrawableShares,) + = delegationManager.getWithdrawableShares(staker, strategies); - Withdrawal[] memory expectedWithdrawals = new Withdrawal[](strategies.length); address delegatedTo = delegationManager.delegatedTo(staker); uint256 nonce = delegationManager.cumulativeWithdrawalsQueued(staker); - + for (uint256 i = 0; i < strategies.length; ++i) { - IStrategy[] memory singleStrategy = new IStrategy[](1); - uint256[] memory singleShares = new uint256[](1); - singleStrategy[0] = strategies[i]; - singleShares[0] = shares[i]; + uint256 scaledShares = withdrawableShares[i].scaleForQueueWithdrawal( + _getSlashingFactor(staker, strategies[i]) + ); + + if (strategies[i] == beaconChainETHStrategy) { + scaledShares -= scaledShares % 1 gwei; + } + expectedWithdrawals[i] = Withdrawal({ staker: staker, delegatedTo: delegatedTo, withdrawer: staker, nonce: (nonce + i), startBlock: uint32(block.number), - strategies: singleStrategy, - scaledShares: singleShares + strategies: strategies[i].toArray(), + scaledShares: scaledShares.toArrayU256() }); } - - return expectedWithdrawals; } function getActiveValidators() public view returns (uint40[] memory) { From 530cd6a33829df0473d1f830eef11c1f983ba88f Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:32:45 -0500 Subject: [PATCH 05/18] feat: add `print.gasUsed()` also fixes logging toggle --- src/test/utils/Logger.t.sol | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/test/utils/Logger.t.sol b/src/test/utils/Logger.t.sol index 8f36bc07e..0eae06aca 100644 --- a/src/test/utils/Logger.t.sol +++ b/src/test/utils/Logger.t.sol @@ -40,7 +40,7 @@ abstract contract Logger is Test { /// Storage /// ----------------------------------------------------------------------- - bool on = true; + bool public isLoggingOn = true; /// ----------------------------------------------------------------------- /// Modifiers @@ -69,12 +69,12 @@ abstract contract Logger is Test { /// Colored Names /// ----------------------------------------------------------------------- - /// @dev Returns `NAME` colored based on the inheriting contract's role. + /// @dev Returns `NAME` colored based isLoggingOn the inheriting contract's role. function NAME_COLORED() public view returns (string memory) { return colorByRole(NAME()); } - /// @dev Returns `name` colored based on its role. + /// @dev Returns `name` colored based isLoggingOn its role. function colorByRole( string memory name ) public view noTracing returns (string memory colored) { @@ -115,12 +115,12 @@ abstract contract Logger is Test { function _pauseLogging() internal { console.log("\n%s logging paused...", NAME_COLORED()); - on = false; + isLoggingOn = false; } function _resumeLogging() internal { console.log("\n%s logging unpaused...", NAME_COLORED()); - on = true; + isLoggingOn = true; } } @@ -136,10 +136,12 @@ library print { function method( string memory m ) internal view { + if (!_on()) return; console.log("%s.%s()", _name(), m.italic()); } function method(string memory m, string memory args) internal view { + if (!_on()) return; console.log("%s.%s(%s)", _name(), m.italic(), args); } @@ -150,6 +152,7 @@ library print { IStrategy[] memory strategies, uint256[] memory tokenBalances ) internal view { + if (!_on()) return; console.log( "\nCreated %s %s who holds %s.", userType.asUserType(), _logger().colorByRole(name), assetType.asAssetType() ); @@ -168,6 +171,12 @@ library print { } } + function gasUsed() internal { + if (!_on()) return; + uint256 used = cheats.snapshotGasLastCall("gasUsed"); + console.log("Gas used: %d", used.asGwei()); + } + /// ----------------------------------------------------------------------- /// Logging /// ----------------------------------------------------------------------- @@ -284,4 +293,8 @@ library print { function _logger() internal view returns (Logger) { return Logger(address(this)); } + + function _on() internal view returns (bool) { + return _logger().isLoggingOn(); + } } From 01061a6fc979417c0cbe768aa8165c56e1290d3f Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:38:45 -0500 Subject: [PATCH 06/18] feat: add gas logging --- src/test/integration/users/AVS.t.sol | 7 +++++++ src/test/integration/users/User.t.sol | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index 1678762e8..cc5aa6653 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -73,6 +73,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.createOperatorSets(p); allocationManager.createOperatorSets(address(this), p); + print.gasUsed(); } function createOperatorSet( @@ -86,6 +87,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.createOperatorSets(p); allocationManager.createOperatorSets(address(this), p); + print.gasUsed(); } function slashOperator(User operator, uint32 operatorSetId, uint256 wadToSlash) public createSnapshot { @@ -110,6 +112,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { }); allocationManager.slashOperator(address(this), p); + print.gasUsed(); } function deregisterFromOperatorSets(User operator, uint32[] memory operatorSetIds) public createSnapshot { @@ -120,6 +123,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.deregisterFromOperatorSets(p); allocationManager.deregisterFromOperatorSets(p); + print.gasUsed(); } function setAVSRegistrar( @@ -128,6 +132,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.method("setAVSRegistrar"); console.log("Setting AVS registrar to: %s", address(registrar)); allocationManager.setAVSRegistrar(address(this), registrar); + print.gasUsed(); } function addStrategiesToOperatorSet(uint32 operatorSetId, IStrategy[] memory strategies) public createSnapshot { @@ -140,6 +145,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { } allocationManager.addStrategiesToOperatorSet(address(this), operatorSetId, strategies); + print.gasUsed(); } function removeStrategiesFromOperatorSet( @@ -155,6 +161,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { } allocationManager.removeStrategiesFromOperatorSet(address(this), operatorSetId, strategies); + print.gasUsed(); } /// ----------------------------------------------------------------------- diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index eadb2f248..0c7aaee58 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -104,6 +104,8 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { }).toArray(); allocationManager.modifyAllocations(address(this), params); + print.gasUsed(); + (, uint32 delay) = allocationManager.getAllocationDelay(address(this)); rollForward({blocks: delay}); } @@ -112,6 +114,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { OperatorSet memory operatorSet ) public virtual { modifyAllocations(operatorSet, new uint64[](allocationManager.getStrategiesInOperatorSet(operatorSet).length)); + print.gasUsed(); } function registerForOperatorSets( @@ -140,6 +143,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { address(this), RegisterParams({avs: operatorSet.avs, operatorSetIds: operatorSet.id.toArrayU32(), data: ""}) ); + print.gasUsed(); } function deregisterFromOperatorSet( @@ -163,11 +167,13 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { operatorSetIds: operatorSet.id.toArrayU32() }) ); + print.gasUsed(); } function setAllocationDelay(uint32 delay) public virtual createSnapshot { print.method("setAllocationDelay"); allocationManager.setAllocationDelay(address(this), delay); + print.gasUsed(); rollForward({blocks: allocationManager.ALLOCATION_CONFIGURATION_DELAY()}); } @@ -179,8 +185,8 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { function registerAsOperator() public virtual createSnapshot { print.method("registerAsOperator"); - delegationManager.registerAsOperator(address(0), withdrawalDelay, "metadata"); + print.gasUsed(); } /// @dev Delegate to the operator without a signature @@ -191,6 +197,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { ISignatureUtils.SignatureWithExpiry memory emptySig; delegationManager.delegateTo(address(operator), emptySig, bytes32(0)); + print.gasUsed(); } /// @dev Undelegate from operator @@ -199,6 +206,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { Withdrawal[] memory expectedWithdrawals = _getExpectedWithdrawalStructsForStaker(address(this)); delegationManager.undelegate(address(this)); + print.gasUsed(); for (uint256 i = 0; i < expectedWithdrawals.length; i++) { emit log("expecting withdrawal:"); @@ -218,6 +226,8 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { Withdrawal[] memory expectedWithdrawals = _getExpectedWithdrawalStructsForStaker(address(staker)); delegationManager.undelegate(address(staker)); + print.gasUsed(); + return expectedWithdrawals; } @@ -250,6 +260,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { }); bytes32[] memory withdrawalRoots = delegationManager.queueWithdrawals(params); + print.gasUsed(); // Basic sanity check - we do all other checks outside this file assertEq(withdrawals.length, withdrawalRoots.length, "User.queueWithdrawals: length mismatch"); @@ -389,6 +400,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { IERC20 underlyingToken = strat.underlyingToken(); underlyingToken.approve(address(strategyManager), tokenBalance); strategyManager.depositIntoStrategy(strat, underlyingToken, tokenBalance); + print.gasUsed(); } } } @@ -411,6 +423,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { IERC20 underlyingToken = strat.underlyingToken(); underlyingToken.approve(address(strategyManager), tokens); strategyManager.depositIntoStrategy(strat, underlyingToken, tokens); + print.gasUsed(); } } } @@ -450,6 +463,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { } delegationManager.completeQueuedWithdrawal(withdrawal, tokens, receiveAsTokens); + print.gasUsed(); return tokens; } From eaf00da0e6555a43e4f7ebb67c2a907a76e347be Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:44:23 -0500 Subject: [PATCH 07/18] fix: `print.gasUsed()` --- src/test/utils/Logger.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/utils/Logger.t.sol b/src/test/utils/Logger.t.sol index 0eae06aca..45e2e48ed 100644 --- a/src/test/utils/Logger.t.sol +++ b/src/test/utils/Logger.t.sol @@ -172,9 +172,9 @@ library print { } function gasUsed() internal { - if (!_on()) return; uint256 used = cheats.snapshotGasLastCall("gasUsed"); - console.log("Gas used: %d", used.asGwei()); + if (!_on()) return; + console.log(" Gas used: %d", used); } /// ----------------------------------------------------------------------- From 39593fbfb5d63b0ee8e4f6875b5af117a8308ebe Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:03:14 -0500 Subject: [PATCH 08/18] fix: `print.gasUsed()` --- src/test/utils/Logger.t.sol | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/test/utils/Logger.t.sol b/src/test/utils/Logger.t.sol index 45e2e48ed..c3fd148c2 100644 --- a/src/test/utils/Logger.t.sol +++ b/src/test/utils/Logger.t.sol @@ -40,7 +40,7 @@ abstract contract Logger is Test { /// Storage /// ----------------------------------------------------------------------- - bool public isLoggingOn = true; + bool public logging = true; /// ----------------------------------------------------------------------- /// Modifiers @@ -53,9 +53,9 @@ abstract contract Logger is Test { } modifier noLogging() { - _pauseLogging(); + logging = false; _; - _resumeLogging(); + logging = true; } /// ----------------------------------------------------------------------- @@ -69,12 +69,12 @@ abstract contract Logger is Test { /// Colored Names /// ----------------------------------------------------------------------- - /// @dev Returns `NAME` colored based isLoggingOn the inheriting contract's role. + /// @dev Returns `NAME` colored based logging the inheriting contract's role. function NAME_COLORED() public view returns (string memory) { return colorByRole(NAME()); } - /// @dev Returns `name` colored based isLoggingOn its role. + /// @dev Returns `name` colored based logging its role. function colorByRole( string memory name ) public view noTracing returns (string memory colored) { @@ -113,14 +113,9 @@ abstract contract Logger is Test { /// Logging /// ----------------------------------------------------------------------- - function _pauseLogging() internal { - console.log("\n%s logging paused...", NAME_COLORED()); - isLoggingOn = false; - } - - function _resumeLogging() internal { - console.log("\n%s logging unpaused...", NAME_COLORED()); - isLoggingOn = true; + function _toggleLog() internal { + logging = !logging; + console.log("\n%s logging %s...", NAME_COLORED(), logging ? "enabled" : "disabled"); } } @@ -136,12 +131,12 @@ library print { function method( string memory m ) internal view { - if (!_on()) return; + if (!_logging()) return; console.log("%s.%s()", _name(), m.italic()); } function method(string memory m, string memory args) internal view { - if (!_on()) return; + if (!_logging()) return; console.log("%s.%s(%s)", _name(), m.italic(), args); } @@ -152,7 +147,7 @@ library print { IStrategy[] memory strategies, uint256[] memory tokenBalances ) internal view { - if (!_on()) return; + if (!_logging()) return; console.log( "\nCreated %s %s who holds %s.", userType.asUserType(), _logger().colorByRole(name), assetType.asAssetType() ); @@ -173,8 +168,8 @@ library print { function gasUsed() internal { uint256 used = cheats.snapshotGasLastCall("gasUsed"); - if (!_on()) return; - console.log(" Gas used: %d", used); + if (!_logging()) return; + console.log(" Gas used: %d".dim().bold(), used); } /// ----------------------------------------------------------------------- @@ -294,7 +289,7 @@ library print { return Logger(address(this)); } - function _on() internal view returns (bool) { - return _logger().isLoggingOn(); + function _logging() internal view returns (bool) { + return _logger().logging(); } } From bca115007095ef6e076050565aa3ead4fd39ea3e Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:29:48 -0500 Subject: [PATCH 09/18] refactor: reorganize + cleanup --- .../tests/Deposit_Delegate_Allocate.t.sol | 59 +++++++++++++- ...Deposit_Delegate_Undelegate_Complete.t.sol | 48 ++++++++++- ...e_Undelegate_Withdraw_Max_Strategies.t.sol | 81 ------------------- 3 files changed, 105 insertions(+), 83 deletions(-) delete mode 100644 src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol index ee3958dcf..08c9fc130 100644 --- a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.27; import "src/test/integration/IntegrationChecks.t.sol"; import "src/test/integration/users/User.t.sol"; -contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils { +contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelegationManagerTypes { function testFuzz_deposit_delegate_allocate(uint24 _random) public { // Configure the random parameters for the test _configRand({ @@ -43,4 +43,61 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils { // TODO: write checks for slashing... } + + // FIXME: Current fails with `AmountMustBeMultipleOfGwei` since slashed beaconchain deposits + // are not garanteed to be multiple of gwei after being slashed. + function testFuzz_deposit_delegate_allocate_slash_undelegate_completeAsTokens( + uint24 _random + ) public { + _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); + _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local + + (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + (AVS avs,) = _newRandomAVS(); + + assertEq(strategies.length, 33, "sanity"); + + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // Create an operator set and register an operator. + OperatorSet memory operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); + + // 3. Allocate to operator set. + operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + + // 4. Slash operator + avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + + // 3. Undelegate from an operator + IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + // check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + + // 5. Complete withdrawal + _rollBlocksForCompleteWithdrawals(); + for (uint256 i = 0; i < withdrawals.length; ++i) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); + // check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares, tokens, expectedTokens); + } + + // Check Final State + assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); + assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token tokenBalances"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } } \ No newline at end of file diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol index 50eec4ca2..c4d61f83f 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.27; import "src/test/integration/users/User.t.sol"; import "src/test/integration/IntegrationChecks.t.sol"; -contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUtils { +contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUtils, IDelegationManagerTypes { /// Randomly generates a user with different held assets. Then: /// 1. deposit into strategy /// 2. delegate to an operator @@ -240,4 +240,50 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens"); assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } + + function testFuzz_deposit_delegate_undelegate_completeAsTokens_Max_Strategies( + uint24 _random + ) public { + _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); + _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local + + (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + + assertEq(strategies.length, 33, "sanity"); + + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // 3. Undelegate from an operator + IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + + // 4. Complete withdrawal + // Fast forward to when we can complete the withdrawal + _rollBlocksForCompleteWithdrawals(); + + // Complete withdrawal + for (uint256 i = 0; i < withdrawals.length; ++i) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares, tokens, expectedTokens); + } + + // Check Final State + assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); + assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token tokenBalances"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } } diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol deleted file mode 100644 index 3efe223ee..000000000 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Withdraw_Max_Strategies.t.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "src/test/integration/users/User.t.sol"; -import "src/test/integration/IntegrationChecks.t.sol"; - -contract Integration_Deposit_Delegate_Undelegate_Withdraw_Max_Strategies is - IntegrationCheckUtils, - IDelegationManagerTypes -{ - function testFuzz_deposit_delegate_undelegate_withdraw_max_strategies( - uint24 _random - ) public { - // When new Users are created, they will choose a random configuration from these params: - _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); - _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local - - (User staker, IStrategy[] memory strategies, uint256[] memory balances) = _newRandomStaker(); - (User operator,,) = _newRandomOperator(); - (AVS avs,) = _newRandomAVS(); - - OperatorSet memory operatorSet = avs.createOperatorSet(strategies); - - assertEq(strategies.length, 33, "sanity"); - - /// 1. Deposit Into Strategies - staker.depositIntoEigenlayer(strategies, balances); - - // 2. Delegate to an operator - staker.delegateTo(operator); - - // 3. Register for operator set - operator.registerForOperatorSet(operatorSet); - - // 4. Undelegate from an operator - Withdrawal[] memory withdrawals = staker.undelegate(); - - // 5. Complete withdrawal - _rollBlocksForCompleteWithdrawals(); - staker.completeWithdrawalsAsTokens(withdrawals); - } - - function testFuzz_deposit_delegate_allocate_slash_undelegate_withdraw_max_strategies( - uint24 _random - ) public { - // When new Users are created, they will choose a random configuration from these params: - _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); - _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local - - (User staker, IStrategy[] memory strategies, uint256[] memory balances) = _newRandomStaker(); - (User operator,,) = _newRandomOperator(); - (AVS avs,) = _newRandomAVS(); - - OperatorSet memory operatorSet = avs.createOperatorSet(strategies); - - assertEq(strategies.length, 33, "sanity"); - - /// 1. Deposit Into Strategies - staker.depositIntoEigenlayer(strategies, balances); - - // 2. Delegate to an operator - staker.delegateTo(operator); - - // 3. Register for operator set - operator.registerForOperatorSet(operatorSet); - - // 4. Allocate to operator set. - operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); - cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); - - // 5. Slash operator - avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); - - // 6. Undelegate from an operator - Withdrawal[] memory withdrawals = staker.undelegate(); - - // 7. Complete withdrawal - _rollBlocksForCompleteWithdrawals(); - staker.completeWithdrawalsAsTokens(withdrawals); - } -} From 4e8eedc22eea825e3a39a920e427a9e9978d804d Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:49:02 -0500 Subject: [PATCH 10/18] feat: more `AVS` + `User` interaction return values --- src/test/integration/users/AVS.t.sol | 10 +++++++--- src/test/integration/users/User.t.sol | 11 ++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index cc5aa6653..69a1e9a98 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -89,8 +89,12 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { allocationManager.createOperatorSets(address(this), p); print.gasUsed(); } - - function slashOperator(User operator, uint32 operatorSetId, uint256 wadToSlash) public createSnapshot { + + function slashOperator( + User operator, + uint32 operatorSetId, + uint256 wadToSlash + ) public createSnapshot returns (SlashingParams memory slashingParams) { print.method( "slashOperator", string.concat( @@ -104,7 +108,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ) ); - SlashingParams memory p = SlashingParams({ + slashingParams= SlashingParams({ operator: address(operator), operatorSetId: operatorSetId, wadToSlash: wadToSlash, diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index 0c7aaee58..ea5d46d15 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -81,7 +81,10 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { /// @dev Allocates randomly accross the operator set's strategies with a sum of `magnitudeSum`. /// NOTE: Calling more than once will lead to deallocations... - function modifyAllocations(OperatorSet memory operatorSet, uint64[] memory magnitudes) public virtual createSnapshot { + function modifyAllocations( + OperatorSet memory operatorSet, + uint64[] memory magnitudes + ) public virtual createSnapshot return (AllocateParams memory) { print.method( "modifyAllocations", string.concat( @@ -97,17 +100,19 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { require(strategies.length == magnitudes.length, "User.modifyAllocations: length mismatch"); - AllocateParams[] memory params = AllocateParams({ + AllocateParams[] memory allocateParams = AllocateParams({ operatorSet: operatorSet, strategies: strategies, newMagnitudes: magnitudes }).toArray(); - allocationManager.modifyAllocations(address(this), params); + allocationManager.modifyAllocations(address(this), allocateParams); print.gasUsed(); (, uint32 delay) = allocationManager.getAllocationDelay(address(this)); rollForward({blocks: delay}); + + return allocateParams[0] } function deallocateAll( From 5b27f39174c2049d3d82273e1377aa7415a83f91 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:27:33 -0500 Subject: [PATCH 11/18] test(wip): slashing integration checks --- src/test/integration/IntegrationBase.t.sol | 81 ++++++++- src/test/integration/IntegrationChecks.t.sol | 5 + .../tests/Deposit_Delegate_Allocate.t.sol | 159 ++++++++++++------ src/test/integration/users/AVS.t.sol | 2 +- src/test/integration/users/User.t.sol | 12 +- 5 files changed, 201 insertions(+), 58 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 228af0435..6d4e5d0d8 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -14,7 +14,7 @@ import "src/test/integration/TimeMachine.t.sol"; import "src/test/integration/users/User.t.sol"; import "src/test/integration/users/User_M1.t.sol"; -abstract contract IntegrationBase is IntegrationDeployer { +abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerTypes { using StdStyle for *; using SlashingLib for *; using Strings for *; @@ -349,6 +349,50 @@ abstract contract IntegrationBase is IntegrationDeployer { TIME TRAVELERS ONLY BEYOND THIS POINT *******************************************************************************/ + /******************************************************************************* + SNAPSHOT ASSERTIONS: ALLOCATIONS + *******************************************************************************/ + + function assert_Snap_Allocations_Updated_Before_Delay( + User operator, + OperatorSet memory operatorSet, + IStrategy[] memory strategies, + uint64[] memory newMagnitudes, + string memory err + ) internal { + Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); + Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); + + for (uint i = 0; i < strategies.length; i++) { + Allocation memory curAlloc = curAllocs[i]; + Allocation memory prevAlloc = prevAllocs[i]; + + assertEq(curAlloc.currentMagnitude, prevAlloc.currentMagnitude, err); + assertEq(curAlloc.pendingDiff, prevAlloc.pendingDiff + int128(int64(newMagnitudes[i])), err); + assertEq(curAlloc.effectBlock, block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY(), err); + } + } + + function assert_Snap_Allocations_Updated_After_Delay( + User operator, + OperatorSet memory operatorSet, + IStrategy[] memory strategies, + uint64[] memory newMagnitudes, + string memory err + ) internal { + Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); + Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); + + for (uint i = 0; i < strategies.length; i++) { + Allocation memory curAlloc = curAllocs[i]; + Allocation memory prevAlloc = prevAllocs[i]; + + assertEq(curAlloc.currentMagnitude, newMagnitudes[i], err); + assertEq(curAlloc.pendingDiff, 0, err); + assertEq(curAlloc.effectBlock, 0, err); + } + } + /******************************************************************************* SNAPSHOT ASSERTIONS: OPERATOR SHARES *******************************************************************************/ @@ -1094,13 +1138,42 @@ abstract contract IntegrationBase is IntegrationDeployer { timeMachine.warpToPresent(curState); } - // TODO - /// @dev Given a list of strategies, roll the block number forward to the - /// a valid blocknumber to completeWithdrawals + /// @dev Rolls forward by the minimum withdrawal delay blocks. function _rollBlocksForCompleteWithdrawals() internal { cheats.roll(block.number + delegationManager.MIN_WITHDRAWAL_DELAY_BLOCKS()); } + /// @dev Rolls forward by the default allocation delay blocks. + function _rollBlocksForCompleteAllocation() internal { + cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + } + + /// @dev Rolls forward by the default deallocation delay blocks. + function _rollBlocksForCompleteDeallocation() internal { + cheats.roll(block.number + allocationManager.DEALLOCATION_DELAY()); + } + + /// @dev Uses timewarp modifier to get the operator set strategy allocations at the last snapshot. + function _getPrevAllocations( + User operator, + OperatorSet memory operatorSet, + IStrategy[] memory strategies + ) internal timewarp() returns (Allocation[] memory) { + return _getAllocations(operator, operatorSet, strategies); + } + + /// @dev Looks up each strategy for an operator set and returns a list of operator allocations. + function _getAllocations( + User operator, + OperatorSet memory operatorSet, + IStrategy[] memory strategies + ) internal view returns (Allocation[] memory allocations) { + allocations = new Allocation[](strategies.length); + for (uint i = 0; i < strategies.length; ++i) { + allocations[i] = allocationManager.getAllocation(address(operator), operatorSet, strategies[i]); + } + } + /// @dev Uses timewarp modifier to get operator shares at the last snapshot function _getPrevOperatorShares( User operator, diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index ffe014d78..2a2022abe 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -7,6 +7,11 @@ import "src/test/integration/users/User_M1.t.sol"; /// @notice Contract that provides utility functions to reuse common test blocks & checks contract IntegrationCheckUtils is IntegrationBase { + /******************************************************************************* + ALLOCATION MANAGER CHECKS + *******************************************************************************/ + + /******************************************************************************* EIGENPOD CHECKS diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol index 08c9fc130..7335ee42c 100644 --- a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol @@ -5,44 +5,47 @@ import "src/test/integration/IntegrationChecks.t.sol"; import "src/test/integration/users/User.t.sol"; contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelegationManagerTypes { - function testFuzz_deposit_delegate_allocate(uint24 _random) public { - // Configure the random parameters for the test - _configRand({ - _randomSeed: _random, - _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS - }); - - // Create a staker and an operator with a nonzero balance and corresponding strategies - (AVS avs, OperatorSet[] memory operatorSets) = _newRandomAVS(); - (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker(); - (User operator, ,) = _newRandomOperator(); - - // Upgrade contracts if forkType is not local - _upgradeEigenLayerContracts(); - - // 1. Delegate to operator - staker.delegateTo(operator); - check_Delegation_State(staker, operator, strategies, new uint256[](strategies.length)); // Initial shares are zero - - // 2. Deposit into strategies - staker.depositIntoEigenlayer(strategies, tokenBalances); - uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); - - // Check that the deposit increased operator shares the staker is delegated to - check_Deposit_State(staker, strategies, shares); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - - operator.registerForOperatorSets(operatorSets); - - for (uint i; i < operatorSets.length; ++i) { - uint256 len = allocationManager.getStrategiesInOperatorSet(operatorSets[i]).length; - operator.modifyAllocations(operatorSets[i], _randMagnitudes({ sum: 1 ether / uint64(operatorSets.length), len: len })); - avs.slashOperator(operator, operatorSets[i].id, _randWadToSlash()); - } - - // TODO: write checks for slashing... - } + // function testFuzz_deposit_delegate_allocate( + // uint24 _random + // ) public { + // // Configure the random parameters for the test + // _configRand({ + // _randomSeed: _random, + // _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + // _userTypes: DEFAULT | ALT_METHODS + // }); + + // // Create a staker and an operator with a nonzero balance and corresponding strategies + // (AVS avs, OperatorSet[] memory operatorSets) = _newRandomAVS(); + // (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + // (User operator,,) = _newRandomOperator(); + + // _upgradeEigenLayerContracts(); + + // // 1. Delegate to operator + // staker.delegateTo(operator); + // check_Delegation_State(staker, operator, strategies, new uint256[](strategies.length)); // Initial shares are zero + + // // 2. Deposit into strategies + // staker.depositIntoEigenlayer(strategies, tokenBalances); + // uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + // // Check that the deposit increased operator shares the staker is delegated to + // check_Deposit_State(staker, strategies, shares); + // assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); + + // operator.registerForOperatorSets(operatorSets); + + // for (uint256 i; i < operatorSets.length; ++i) { + // uint256 len = allocationManager.getStrategiesInOperatorSet(operatorSets[i]).length; + // operator.modifyAllocations( + // operatorSets[i], _randMagnitudes({sum: 1 ether / uint64(operatorSets.length), len: len}) + // ); + // allocationManager.getAllocation(operatorSets[i]); + // } + + // // TODO: slashing checks + // } // FIXME: Current fails with `AmountMustBeMultipleOfGwei` since slashed beaconchain deposits // are not garanteed to be multiple of gwei after being slashed. @@ -58,7 +61,7 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega assertEq(strategies.length, 33, "sanity"); - uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); @@ -76,28 +79,88 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega operator.registerForOperatorSet(operatorSet); // 3. Allocate to operator set. - operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + AllocateParams memory allocateParams = + operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + assert_Snap_Allocations_Updated_Before_Delay( + operator, operatorSet, strategies, allocateParams.newMagnitudes, "operator allocations should be updated before delay" + ); cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + // assert_Snap_Allocations_Updated_After_Delay( + // operator, operatorSet, strategies, allocateParams.newMagnitudes, "operator allocations should be updated after delay" + // ); + + // TODO: check_Allocation_State() // 4. Slash operator - avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + // TODO: check_Slash_State() - // 3. Undelegate from an operator + // 5. Undelegate from an operator IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - // check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + // FIXME: check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); - // 5. Complete withdrawal + // 6. Complete withdrawal _rollBlocksForCompleteWithdrawals(); for (uint256 i = 0; i < withdrawals.length; ++i) { - uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); + uint256[] memory expectedTokens = + _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); - // check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares, tokens, expectedTokens); + // FIXME: check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares, tokens, expectedTokens); } // Check Final State assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); - assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token tokenBalances"); + assert_HasUnderlyingTokenBalances( + staker, strategies, tokenBalances, "staker should once again have original token tokenBalances" + ); assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } -} \ No newline at end of file + + // function testFuzz_deposit_delegate_allocate_queue_slash_completeAsTokens( + // uint24 _random + // ) public { + // _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); + // _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local + + // (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + // (User operator,,) = _newRandomOperator(); + // (AVS avs,) = _newRandomAVS(); + + // assertEq(strategies.length, 33, "sanity"); + + // uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + // assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + // assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + // /// 1. Deposit Into Strategies + // staker.depositIntoEigenlayer(strategies, tokenBalances); + // check_Deposit_State(staker, strategies, shares); + + // // 2. Delegate to an operator + // staker.delegateTo(operator); + // check_Delegation_State(staker, operator, strategies, shares); + + // // Create an operator set and register an operator. + // OperatorSet memory operatorSet = avs.createOperatorSet(strategies); + // operator.registerForOperatorSet(operatorSet); + + // // 3. Allocate to operator set. + // operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + // cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + + // // 4. Slash operator + // SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + + // // 5. Queue withdrawal + // } + + // function testFuzz_deposit_delegate_allocate_deallocate_slash_queue_completeAsTokens( + // uint24 _random + // ) public {} + + // function testFuzz_deposit_delegate_allocate_deregister_slash( + // uint24 _random + // ) public {} +} diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index 69a1e9a98..5f2d615e7 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -115,7 +115,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { description: "bad operator" }); - allocationManager.slashOperator(address(this), p); + allocationManager.slashOperator(address(this), slashingParams); print.gasUsed(); } diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index ea5d46d15..9a6681db2 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -84,7 +84,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { function modifyAllocations( OperatorSet memory operatorSet, uint64[] memory magnitudes - ) public virtual createSnapshot return (AllocateParams memory) { + ) public virtual createSnapshot returns (AllocateParams memory) { print.method( "modifyAllocations", string.concat( @@ -112,14 +112,16 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { (, uint32 delay) = allocationManager.getAllocationDelay(address(this)); rollForward({blocks: delay}); - return allocateParams[0] + return allocateParams[0]; } function deallocateAll( OperatorSet memory operatorSet - ) public virtual { - modifyAllocations(operatorSet, new uint64[](allocationManager.getStrategiesInOperatorSet(operatorSet).length)); - print.gasUsed(); + ) public virtual returns (AllocateParams memory) { + return modifyAllocations( + operatorSet, + new uint64[](allocationManager.getStrategiesInOperatorSet(operatorSet).length) + ); } function registerForOperatorSets( From 6fccc0a4f631d60929712a88a633d0c5d57796f1 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:04:25 -0500 Subject: [PATCH 12/18] test(wip): slashing integration checks - adds time machine logging + cleanup - roll to completion removed from modify allocations to simplify check flow --- src/test/integration/IntegrationBase.t.sol | 64 ++++++++++--------- src/test/integration/TimeMachine.t.sol | 54 ++++++++++------ .../tests/Deposit_Delegate_Allocate.t.sol | 25 ++++++-- src/test/integration/users/User.t.sol | 3 - src/test/unit/EigenPodUnit.t.sol | 4 +- 5 files changed, 91 insertions(+), 59 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 6d4e5d0d8..5afba66a7 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -352,32 +352,13 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType /******************************************************************************* SNAPSHOT ASSERTIONS: ALLOCATIONS *******************************************************************************/ - - function assert_Snap_Allocations_Updated_Before_Delay( - User operator, - OperatorSet memory operatorSet, - IStrategy[] memory strategies, - uint64[] memory newMagnitudes, - string memory err - ) internal { - Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); - Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); - - for (uint i = 0; i < strategies.length; i++) { - Allocation memory curAlloc = curAllocs[i]; - Allocation memory prevAlloc = prevAllocs[i]; - - assertEq(curAlloc.currentMagnitude, prevAlloc.currentMagnitude, err); - assertEq(curAlloc.pendingDiff, prevAlloc.pendingDiff + int128(int64(newMagnitudes[i])), err); - assertEq(curAlloc.effectBlock, block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY(), err); - } - } - - function assert_Snap_Allocations_Updated_After_Delay( + + function assert_Snap_Allocations_Updated( User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies, uint64[] memory newMagnitudes, + bool completed, string memory err ) internal { Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); @@ -385,11 +366,33 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType for (uint i = 0; i < strategies.length; i++) { Allocation memory curAlloc = curAllocs[i]; - Allocation memory prevAlloc = prevAllocs[i]; - assertEq(curAlloc.currentMagnitude, newMagnitudes[i], err); - assertEq(curAlloc.pendingDiff, 0, err); - assertEq(curAlloc.effectBlock, 0, err); + if (completed) { + assertEq(curAlloc.currentMagnitude, newMagnitudes[i], string.concat(err, " (currentMagnitude)")); + assertEq(curAlloc.pendingDiff, 0, string.concat(err, " (pendingDiff)")); + assertEq(curAlloc.effectBlock, 0, string.concat(err, " (effectBlock)")); + } else { + Allocation memory prevAlloc = prevAllocs[i]; + + assertEq( + curAlloc.currentMagnitude, + prevAlloc.currentMagnitude, + string.concat(err, " (currentMagnitude)") + ); + assertEq( + curAlloc.pendingDiff, + prevAlloc.pendingDiff + int128(int64(newMagnitudes[i])), + string.concat(err, " (pendingDiff)") + ); + + (, uint32 delay) = allocationManager.getAllocationDelay(address(operator)); + + assertEq( + curAlloc.effectBlock, + block.number + delay, + string.concat(err, " (effectBlock)") + ); + } } } @@ -1133,19 +1136,20 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType } modifier timewarp() { - uint curState = timeMachine.warpToLast(); + uint curState = timeMachine.travelToLast(); _; - timeMachine.warpToPresent(curState); + timeMachine.travel(curState); } /// @dev Rolls forward by the minimum withdrawal delay blocks. function _rollBlocksForCompleteWithdrawals() internal { - cheats.roll(block.number + delegationManager.MIN_WITHDRAWAL_DELAY_BLOCKS()); + rollForward({blocks: delegationManager.MIN_WITHDRAWAL_DELAY_BLOCKS()}); } /// @dev Rolls forward by the default allocation delay blocks. function _rollBlocksForCompleteAllocation() internal { - cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + (, uint32 delay) = allocationManager.getAllocationDelay(address(this)); + rollForward({blocks: delay}); } /// @dev Rolls forward by the default deallocation delay blocks. diff --git a/src/test/integration/TimeMachine.t.sol b/src/test/integration/TimeMachine.t.sol index 616f56423..506147bfb 100644 --- a/src/test/integration/TimeMachine.t.sol +++ b/src/test/integration/TimeMachine.t.sol @@ -2,32 +2,50 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; +import "src/test/utils/Logger.t.sol"; -contract TimeMachine is Test { +contract TimeMachine is Test, Logger { + uint256[] public snapshots; - Vm cheats = Vm(VM_ADDRESS); + function NAME() public view virtual override returns (string memory) { + return "TimeMachine"; + } + + /// ----------------------------------------------------------------------- + /// Setters + /// ----------------------------------------------------------------------- - bool pastExists = false; - uint lastSnapshot; + function createSnapshot() public returns (uint256 snapshot) { + snapshots.push(snapshot = cheats.snapshotState()); + print.method("createSnapshot", cheats.toString(snapshot)); + } + + function travelToLast() public returns (uint256 currentSnapshot) { + // Safety check to make sure createSnapshot is called before attempting + // to warp so we don't accidentally prevent our own births. + assertTrue(pastExists(), "Global.warpToPast: invalid usage, past does not exist"); + uint256 last = lastSnapshot(); + print.method("travelToLast", cheats.toString(last)); + currentSnapshot = createSnapshot(); + cheats.revertToState(last); + } - function createSnapshot() public returns (uint) { - uint snapshot = cheats.snapshotState(); - lastSnapshot = snapshot; - pastExists = true; - return snapshot; + function travel( + uint256 snapshot + ) public { + print.method("travel", cheats.toString(snapshot)); + cheats.revertToState(snapshot); } - function warpToLast() public returns (uint curState) { - // Safety check to make sure createSnapshot is called before attempting to warp - // so we don't accidentally prevent our own births - assertTrue(pastExists, "Global.warpToPast: invalid usage, past does not exist"); + /// ----------------------------------------------------------------------- + /// Getters + /// ----------------------------------------------------------------------- - curState = cheats.snapshotState(); - cheats.revertToState(lastSnapshot); - return curState; + function lastSnapshot() public view returns (uint256) { + return snapshots[snapshots.length - 1]; } - function warpToPresent(uint curState) public { - cheats.revertToState(curState); + function pastExists() public view returns (bool) { + return snapshots.length != 0; } } diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol index 7335ee42c..17e247c9d 100644 --- a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol @@ -81,13 +81,26 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega // 3. Allocate to operator set. AllocateParams memory allocateParams = operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); - assert_Snap_Allocations_Updated_Before_Delay( - operator, operatorSet, strategies, allocateParams.newMagnitudes, "operator allocations should be updated before delay" + + assert_Snap_Allocations_Updated( + operator, + operatorSet, + strategies, + allocateParams.newMagnitudes, + false, + "operator allocations should be updated before delay" + ); + + _rollBlocksForCompleteAllocation(); + + assert_Snap_Allocations_Updated( + operator, + operatorSet, + strategies, + allocateParams.newMagnitudes, + true, + "operator allocations should be updated after delay" ); - cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); - // assert_Snap_Allocations_Updated_After_Delay( - // operator, operatorSet, strategies, allocateParams.newMagnitudes, "operator allocations should be updated after delay" - // ); // TODO: check_Allocation_State() diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index 9a6681db2..8ba2a8d07 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -109,9 +109,6 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { allocationManager.modifyAllocations(address(this), allocateParams); print.gasUsed(); - (, uint32 delay) = allocationManager.getAllocationDelay(address(this)); - rollForward({blocks: delay}); - return allocateParams[0]; } diff --git a/src/test/unit/EigenPodUnit.t.sol b/src/test/unit/EigenPodUnit.t.sol index 15798afac..0aafde362 100644 --- a/src/test/unit/EigenPodUnit.t.sol +++ b/src/test/unit/EigenPodUnit.t.sol @@ -93,9 +93,9 @@ contract EigenPodUnitTests is EigenLayerUnitTestSetup, EigenPodPausingConstants, *******************************************************************************/ modifier timewarp() { - uint curState = timeMachine.warpToLast(); + uint curState = timeMachine.travelToLast(); _; - timeMachine.warpToPresent(curState); + timeMachine.travel(curState); } function _seedPodWithETH(uint256 ethAmount) internal { From 88a15450d002092191c186026e5f937f96b6e1a0 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:43:29 -0500 Subject: [PATCH 13/18] refactor: `assertApproxEqAbs` -> `assertEq` --- src/test/integration/IntegrationBase.t.sol | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 5afba66a7..341d4f218 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -225,8 +225,7 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType tokenBalance = strat.underlyingToken().balanceOf(address(user)); } - assertApproxEqAbs(expectedBalance, tokenBalance, 1, err); - // assertEq(expectedBalance, tokenBalance, err); + assertEq(expectedBalance, tokenBalance, err); } } @@ -258,7 +257,7 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType actualShares = strategyManager.stakerDepositShares(address(user), strat); } - assertApproxEqAbs(expectedShares[i], actualShares, 1, err); + assertEq(expectedShares[i], actualShares, err); } } @@ -269,11 +268,8 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType string memory err ) internal view { for (uint i = 0; i < strategies.length; i++) { - IStrategy strat = strategies[i]; - - uint actualShares = delegationManager.operatorShares(address(user), strat); - - assertApproxEqAbs(expectedShares[i], actualShares, 1, err); + uint actualShares = delegationManager.operatorShares(address(user), strategies[i]); + assertEq(expectedShares[i], actualShares, err); } } @@ -414,7 +410,7 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType // For each strategy, check (prev + added == cur) for (uint i = 0; i < strategies.length; i++) { - assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1, err); + assertEq(prevShares[i] + addedShares[i], curShares[i], err); } } @@ -493,8 +489,8 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType uint[] memory prevShares = _getPrevStakerDepositShares(staker, strategies); // For each strategy, check (prev + added == cur) - for (uint i = 0; i < strategies.length; i++) { - assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1, err); + for (uint i = 0; i < strategies.length; i++) { + assertEq(prevShares[i] + addedShares[i], curShares[i], err); } } From 94107688b0cf135c4f43ef70e5caf3f8be881185 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:48:35 -0500 Subject: [PATCH 14/18] feat: `PermissionController` user support --- src/test/integration/users/User.t.sol | 62 ++++++++++++++++----------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index 8ba2a8d07..e9b6a5d8f 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import "src/contracts/core/AllocationManager.sol"; import "src/contracts/core/DelegationManager.sol"; +import "src/contracts/permissions/PermissionController.sol"; import "src/contracts/core/StrategyManager.sol"; import "src/contracts/pods/EigenPodManager.sol"; import "src/contracts/pods/EigenPod.sol"; @@ -21,6 +22,7 @@ struct Validator { interface IUserDeployer { function allocationManager() external view returns (AllocationManager); function delegationManager() external view returns (DelegationManager); + function permissionController() external view returns (PermissionController); function strategyManager() external view returns (StrategyManager); function eigenPodManager() external view returns (EigenPodManager); function timeMachine() external view returns (TimeMachine); @@ -36,6 +38,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { AllocationManager allocationManager; DelegationManager delegationManager; + PermissionController permissionController; StrategyManager strategyManager; EigenPodManager eigenPodManager; TimeMachine timeMachine; @@ -54,13 +57,14 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { allocationManager = deployer.allocationManager(); delegationManager = deployer.delegationManager(); + permissionController = deployer.permissionController(); strategyManager = deployer.strategyManager(); eigenPodManager = deployer.eigenPodManager(); + timeMachine = deployer.timeMachine(); - beaconChain = deployer.beaconChain(); - _createPod(); + _createPod(); _NAME = name; } @@ -78,7 +82,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { /// ----------------------------------------------------------------------- /// Allocation Manager Methods /// ----------------------------------------------------------------------- - + /// @dev Allocates randomly accross the operator set's strategies with a sum of `magnitudeSum`. /// NOTE: Calling more than once will lead to deallocations... function modifyAllocations( @@ -106,6 +110,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { newMagnitudes: magnitudes }).toArray(); + _tryPrankAppointee_AllocationManager(IAllocationManager.modifyAllocations.selector); allocationManager.modifyAllocations(address(this), allocateParams); print.gasUsed(); @@ -142,7 +147,8 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { "}" ) ); - + + _tryPrankAppointee_AllocationManager(IAllocationManager.registerForOperatorSets.selector); allocationManager.registerForOperatorSets( address(this), RegisterParams({avs: operatorSet.avs, operatorSetIds: operatorSet.id.toArrayU32(), data: ""}) @@ -163,7 +169,8 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { "}" ) ); - + + _tryPrankAppointee_AllocationManager(IAllocationManager.deregisterFromOperatorSets.selector); allocationManager.deregisterFromOperatorSets( DeregisterParams({ operator: address(this), @@ -176,6 +183,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { function setAllocationDelay(uint32 delay) public virtual createSnapshot { print.method("setAllocationDelay"); + _tryPrankAppointee_AllocationManager(IAllocationManager.setAllocationDelay.selector); allocationManager.setAllocationDelay(address(this), delay); print.gasUsed(); rollForward({blocks: allocationManager.ALLOCATION_CONFIGURATION_DELAY()}); @@ -209,6 +217,7 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { print.method("undelegate"); Withdrawal[] memory expectedWithdrawals = _getExpectedWithdrawalStructsForStaker(address(this)); + _tryPrankAppointee_DelegationManager(IDelegationManager.undelegate.selector); delegationManager.undelegate(address(this)); print.gasUsed(); @@ -274,45 +283,35 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { function completeWithdrawalsAsTokens( Withdrawal[] memory withdrawals - ) public virtual createSnapshot returns (IERC20[][] memory) { + ) public virtual createSnapshot returns (IERC20[][] memory tokens) { print.method("completeWithdrawalsAsTokens"); - - IERC20[][] memory tokens = new IERC20[][](withdrawals.length); - + tokens = new IERC20[][](withdrawals.length); for (uint256 i = 0; i < withdrawals.length; i++) { tokens[i] = _completeQueuedWithdrawal(withdrawals[i], true); } - - return tokens; } function completeWithdrawalAsTokens( Withdrawal memory withdrawal ) public virtual createSnapshot returns (IERC20[] memory) { print.method("completeWithdrawalsAsTokens"); - return _completeQueuedWithdrawal(withdrawal, true); } function completeWithdrawalsAsShares( Withdrawal[] memory withdrawals - ) public virtual createSnapshot returns (IERC20[][] memory) { + ) public virtual createSnapshot returns (IERC20[][] memory tokens) { print.method("completeWithdrawalAsShares"); - - IERC20[][] memory tokens = new IERC20[][](withdrawals.length); - + tokens = new IERC20[][](withdrawals.length); for (uint256 i = 0; i < withdrawals.length; i++) { tokens[i] = _completeQueuedWithdrawal(withdrawals[i], false); } - - return tokens; } function completeWithdrawalAsShares( Withdrawal memory withdrawal ) public virtual createSnapshot returns (IERC20[] memory) { print.method("completeWithdrawalAsShares"); - return _completeQueuedWithdrawal(withdrawal, false); } @@ -328,7 +327,6 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { /// withdrawal credential proofs are generated for each validator. function startValidators() public virtual createSnapshot returns (uint40[] memory, uint64) { print.method("startValidators"); - return _startValidators(); } @@ -336,7 +334,6 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { uint40[] memory _validators ) public virtual createSnapshot returns (uint64 exitedBalanceGwei) { print.method("exitValidators"); - return _exitValidators(_validators); } @@ -348,19 +345,16 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { uint40[] memory _validators ) public virtual createSnapshot { print.method("verifyWithdrawalCredentials"); - _verifyWithdrawalCredentials(_validators); } function startCheckpoint() public virtual createSnapshot { print.method("startCheckpoint"); - _startCheckpoint(); } function completeCheckpoint() public virtual createSnapshot { print.method("completeCheckpoint"); - _completeCheckpoint(); } @@ -667,6 +661,26 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { return activeValidators; } + + function _tryPrankAppointee( + address target, + bytes4 selector + ) internal { + address[] memory appointees = permissionController.getAppointees(address(this), target, selector); + if (appointees.length != 0) cheats.prank(appointees[0]); + } + + function _tryPrankAppointee_AllocationManager( + bytes4 selector + ) internal { + return _tryPrankAppointee(address(allocationManager), selector); + } + + function _tryPrankAppointee_DelegationManager( + bytes4 selector + ) internal { + return _tryPrankAppointee(address(delegationManager), selector); + } } /// @notice A user contract that calls nonstandard methods (like xBySignature methods) From 3e5d3199f958dc222c751509f42e1007666e88d1 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:21:43 -0500 Subject: [PATCH 15/18] feat: `PermissionController` avs support --- src/test/integration/users/AVS.t.sol | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index 5f2d615e7..73a89ee42 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.27; import "forge-std/Test.sol"; import "src/contracts/core/AllocationManager.sol"; +import "src/contracts/permissions/PermissionController.sol"; import "src/contracts/strategies/StrategyFactory.sol"; import "src/test/mocks/ERC20Mock.sol"; @@ -17,6 +18,7 @@ import "src/contracts/interfaces/IAVSRegistrar.sol"; interface IAVSDeployer { function allocationManager() external view returns (AllocationManager); function strategyFactory() external view returns (StrategyFactory); + function permissionController() external view returns (PermissionController); function timeMachine() external view returns (TimeMachine); } @@ -25,6 +27,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { using SingleItemArrayLib for *; AllocationManager immutable allocationManager; + PermissionController immutable permissionController; StrategyFactory immutable strategyFactory; TimeMachine immutable timeMachine; string _NAME; @@ -115,6 +118,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { description: "bad operator" }); + _tryPrankAppointee_AllocationManager(IAllocationManager.slashOperator.selector); allocationManager.slashOperator(address(this), slashingParams); print.gasUsed(); } @@ -126,6 +130,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { DeregisterParams({operator: address(operator), avs: address(this), operatorSetIds: operatorSetIds}); print.deregisterFromOperatorSets(p); + _tryPrankAppointee_AllocationManager(IAllocationManager.deregisterFromOperatorSets.selector); allocationManager.deregisterFromOperatorSets(p); print.gasUsed(); } @@ -135,6 +140,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ) public createSnapshot { print.method("setAVSRegistrar"); console.log("Setting AVS registrar to: %s", address(registrar)); + _tryPrankAppointee_AllocationManager(IAllocationManager.setAVSRegistrar.selector); allocationManager.setAVSRegistrar(address(this), registrar); print.gasUsed(); } @@ -147,7 +153,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { for (uint256 i; i < strategies.length; ++i) { console.log(" strategy: %s", address(strategies[i])); } - + _tryPrankAppointee_AllocationManager(IAllocationManager.addStrategiesToOperatorSet.selector); allocationManager.addStrategiesToOperatorSet(address(this), operatorSetId, strategies); print.gasUsed(); } @@ -163,7 +169,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { for (uint256 i; i < strategies.length; ++i) { console.log(" strategy: %s", address(strategies[i])); } - + _tryPrankAppointee_AllocationManager(IAllocationManager.removeStrategiesFromOperatorSet.selector); allocationManager.removeStrategiesFromOperatorSet(address(this), operatorSetId, strategies); print.gasUsed(); } @@ -179,4 +185,22 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ) external override {} function deregisterOperator(address operator, uint32[] calldata operatorSetIds) external override {} + + /// ----------------------------------------------------------------------- + /// Internal Helpers + /// ----------------------------------------------------------------------- + + function _tryPrankAppointee( + address target, + bytes4 selector + ) internal { + address[] memory appointees = permissionController.getAppointees(address(this), target, selector); + if (appointees.length != 0) cheats.prank(appointees[0]); + } + + function _tryPrankAppointee_AllocationManager( + bytes4 selector + ) internal { + return _tryPrankAppointee(address(allocationManager), selector); + } } From 60442a02fcf01c77154a4e5ec684c5d49acfaf34 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:13:11 -0500 Subject: [PATCH 16/18] fix: add missing var construction --- src/test/integration/users/AVS.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index 73a89ee42..067ec2762 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -39,6 +39,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { ) { IAVSDeployer deployer = IAVSDeployer(msg.sender); allocationManager = deployer.allocationManager(); + permissionController = deployer.permissionController(); strategyFactory = deployer.strategyFactory(); timeMachine = deployer.timeMachine(); _NAME = name; From 56aef55e9a83bdfb703b82ac6ab36bfd9d65e19c Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:28:42 -0500 Subject: [PATCH 17/18] test(wip): add `assert_Snap_Allocations_Slashed` --- src/test/integration/IntegrationBase.t.sol | 63 +++++- .../tests/Deposit_Delegate_Allocate.t.sol | 185 ++++++++++-------- 2 files changed, 166 insertions(+), 82 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 341d4f218..d7ddce1bf 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -348,8 +348,8 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType /******************************************************************************* SNAPSHOT ASSERTIONS: ALLOCATIONS *******************************************************************************/ - - function assert_Snap_Allocations_Updated( + + function assert_Snap_Allocations_Modified( User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies, @@ -357,6 +357,8 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType bool completed, string memory err ) internal { + // TODO: magnitudes (max, encumbered) + Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); @@ -392,6 +394,63 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType } } + function assert_Snap_Allocations_Slashed( + User operator, + OperatorSet memory operatorSet, + IStrategy[] memory strategies, + uint256[] memory wadsToSlash, + bool completed, + string memory err + ) internal { + // TODO: slashable stake + + Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); + Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); + + for (uint i = 0; i < strategies.length; i++) { + Allocation memory curAlloc = curAllocs[i]; + Allocation memory prevAlloc = prevAllocs[i]; + + uint64 slashedMagnitude = uint64(uint256(prevAlloc.currentMagnitude).mulWadRoundUp(wadsToSlash[i])); + + assertEq( + curAlloc.currentMagnitude, + prevAlloc.currentMagnitude - slashedMagnitude, + string.concat(err, " (currentMagnitude)") + ); + + if (completed) { + assertEq(curAlloc.pendingDiff, 0, string.concat(err, " (pendingDiff)")); + assertEq(curAlloc.effectBlock, 0, string.concat(err, " (effectBlock)")); + } else { + assertEq( + curAlloc.currentMagnitude, + prevAlloc.currentMagnitude - slashedMagnitude, + string.concat(err, " (currentMagnitude)") + ); + + if (prevAlloc.pendingDiff < 0) { + uint64 slashedPending = + uint64(uint256(uint128(-prevAlloc.pendingDiff)).mulWadRoundUp(wadsToSlash[i])); + + assertEq( + curAlloc.pendingDiff, + prevAlloc.pendingDiff + int128(uint128(slashedPending)), + string.concat(err, " (pendingDiff)") + ); + } + + (, uint32 delay) = allocationManager.getAllocationDelay(address(operator)); + + assertEq( + curAlloc.effectBlock, + block.number + delay, + string.concat(err, " (effectBlock)") + ); + } + } + } + /******************************************************************************* SNAPSHOT ASSERTIONS: OPERATOR SHARES *******************************************************************************/ diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol index 17e247c9d..13b4d807b 100644 --- a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol @@ -5,47 +5,67 @@ import "src/test/integration/IntegrationChecks.t.sol"; import "src/test/integration/users/User.t.sol"; contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelegationManagerTypes { - // function testFuzz_deposit_delegate_allocate( - // uint24 _random - // ) public { - // // Configure the random parameters for the test - // _configRand({ - // _randomSeed: _random, - // _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - // _userTypes: DEFAULT | ALT_METHODS - // }); - - // // Create a staker and an operator with a nonzero balance and corresponding strategies - // (AVS avs, OperatorSet[] memory operatorSets) = _newRandomAVS(); - // (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); - // (User operator,,) = _newRandomOperator(); - - // _upgradeEigenLayerContracts(); - - // // 1. Delegate to operator - // staker.delegateTo(operator); - // check_Delegation_State(staker, operator, strategies, new uint256[](strategies.length)); // Initial shares are zero - - // // 2. Deposit into strategies - // staker.depositIntoEigenlayer(strategies, tokenBalances); - // uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); - - // // Check that the deposit increased operator shares the staker is delegated to - // check_Deposit_State(staker, strategies, shares); - // assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - - // operator.registerForOperatorSets(operatorSets); - - // for (uint256 i; i < operatorSets.length; ++i) { - // uint256 len = allocationManager.getStrategiesInOperatorSet(operatorSets[i]).length; - // operator.modifyAllocations( - // operatorSets[i], _randMagnitudes({sum: 1 ether / uint64(operatorSets.length), len: len}) - // ); - // allocationManager.getAllocation(operatorSets[i]); - // } - - // // TODO: slashing checks - // } + function testFuzz_deposit_delegate_allocate( + uint24 _random + ) public { + // Configure the random parameters for the test + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS + }); + + // Create a staker and an operator with a nonzero balance and corresponding strategies + (AVS avs, OperatorSet[] memory operatorSets) = _newRandomAVS(); + (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + + _upgradeEigenLayerContracts(); + + // 1. Delegate to operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, new uint256[](strategies.length)); // Initial shares are zero + + // 2. Deposit into strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + // Check that the deposit increased operator shares the staker is delegated to + check_Deposit_State(staker, strategies, shares); + assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); + + operator.registerForOperatorSets(operatorSets); + + AllocateParams[] memory allocateParams = new AllocateParams[](operatorSets.length); + + for (uint256 i; i < operatorSets.length; ++i) { + uint256 len = allocationManager.getStrategiesInOperatorSet(operatorSets[i]).length; + allocateParams[i] = operator.modifyAllocations( + operatorSets[i], _randMagnitudes({sum: 1 ether / uint64(operatorSets.length), len: len}) + ); + assert_Snap_Allocations_Modified( + operator, + operatorSets[i], + allocateParams[i].strategies, + allocateParams[i].newMagnitudes, + false, + "operator allocations should be updated before delay" + ); + } + + _rollBlocksForCompleteAllocation(); + + for (uint256 i; i < operatorSets.length; ++i) { + assert_Snap_Allocations_Modified( + operator, + operatorSets[i], + allocateParams[i].strategies, + allocateParams[i].newMagnitudes, + true, + "operator allocations should be updated after delay" + ); + } + } // FIXME: Current fails with `AmountMustBeMultipleOfGwei` since slashed beaconchain deposits // are not garanteed to be multiple of gwei after being slashed. @@ -82,7 +102,7 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega AllocateParams memory allocateParams = operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); - assert_Snap_Allocations_Updated( + assert_Snap_Allocations_Modified( operator, operatorSet, strategies, @@ -93,7 +113,7 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega _rollBlocksForCompleteAllocation(); - assert_Snap_Allocations_Updated( + assert_Snap_Allocations_Modified( operator, operatorSet, strategies, @@ -102,11 +122,16 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega "operator allocations should be updated after delay" ); - // TODO: check_Allocation_State() - // 4. Slash operator SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); - // TODO: check_Slash_State() + // assert_Snap_Allocations_Slashed( + // operator, + // operatorSet, + // strategies, + // slashingParams.wadsToSlash, + // true, + // "operator allocations should be slashed" + // ); // 5. Undelegate from an operator IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); @@ -130,50 +155,50 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } - // function testFuzz_deposit_delegate_allocate_queue_slash_completeAsTokens( - // uint24 _random - // ) public { - // _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); - // _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local + function testFuzz_deposit_delegate_allocate_queue_slash_completeAsTokens( + uint24 _random + ) public { + _configRand({_randomSeed: _random, _assetTypes: HOLDS_MAX, _userTypes: DEFAULT}); + _upgradeEigenLayerContracts(); // Upgrade contracts if forkType is not local - // (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); - // (User operator,,) = _newRandomOperator(); - // (AVS avs,) = _newRandomAVS(); + (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + (AVS avs,) = _newRandomAVS(); - // assertEq(strategies.length, 33, "sanity"); + assertEq(strategies.length, 33, "sanity"); - // uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); - // assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); - // assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - // /// 1. Deposit Into Strategies - // staker.depositIntoEigenlayer(strategies, tokenBalances); - // check_Deposit_State(staker, strategies, shares); + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); - // // 2. Delegate to an operator - // staker.delegateTo(operator); - // check_Delegation_State(staker, operator, strategies, shares); + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); - // // Create an operator set and register an operator. - // OperatorSet memory operatorSet = avs.createOperatorSet(strategies); - // operator.registerForOperatorSet(operatorSet); + // Create an operator set and register an operator. + OperatorSet memory operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); - // // 3. Allocate to operator set. - // operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); - // cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + // 3. Allocate to operator set. + operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); - // // 4. Slash operator - // SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + // 4. Queue withdrawal - // // 5. Queue withdrawal - // } + // 5. Slash operator + SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + } - // function testFuzz_deposit_delegate_allocate_deallocate_slash_queue_completeAsTokens( - // uint24 _random - // ) public {} + function testFuzz_deposit_delegate_allocate_deallocate_slash_queue_completeAsTokens( + uint24 _random + ) public {} - // function testFuzz_deposit_delegate_allocate_deregister_slash( - // uint24 _random - // ) public {} + function testFuzz_deposit_delegate_allocate_deregister_slash( + uint24 _random + ) public {} } From 78ddf564d195ea4c131e6f7b84040a57b9abe134 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:12:20 -0500 Subject: [PATCH 18/18] test: slashing integration todos refactor: review changes refactor: final commit fix: `assert_Snap_Allocations_Modified` bandaid will improve in second pr fix: `assert_Snap_Allocations_Modified` made need further improvement... test(wip): add `assert_HasUnderlyingTokenBalances_AfterSlash` refactor: snap checks fix: `EigenPod` gwei bug test(wip): add `assert_Snap_Magnitudes_Slashed` --- src/contracts/interfaces/IEigenPod.sol | 2 - src/contracts/pods/EigenPod.sol | 2 +- src/test/integration/IntegrationBase.t.sol | 205 ++++++++++++++--- src/test/integration/IntegrationChecks.t.sol | 52 ++++- .../integration/IntegrationDeployer.t.sol | 2 + .../tests/Deposit_Delegate_Allocate.t.sol | 135 ++++++----- src/test/integration/users/AVS.t.sol | 11 +- src/test/integration/users/User.t.sol | 17 +- src/test/unit/EigenPodUnit.t.sol | 54 +++-- src/test/utils/ArrayLib.sol | 213 +++++++++++++++--- 10 files changed, 520 insertions(+), 173 deletions(-) diff --git a/src/contracts/interfaces/IEigenPod.sol b/src/contracts/interfaces/IEigenPod.sol index ec4f524b4..5f354e7cd 100644 --- a/src/contracts/interfaces/IEigenPod.sol +++ b/src/contracts/interfaces/IEigenPod.sol @@ -42,8 +42,6 @@ interface IEigenPodErrors { /// @dev Thrown when amount exceeds `restakedExecutionLayerGwei`. error InsufficientWithdrawableBalance(); - /// @dev Thrown when provided `amountGwei` is not a multiple of gwei. - error AmountMustBeMultipleOfGwei(); /// Validator Status diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index f69981cec..1066b2fbf 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -397,8 +397,8 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC * @dev Reverts if `amountWei` is not a whole Gwei amount */ function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external onlyEigenPodManager { - require(amountWei % GWEI_TO_WEI == 0, AmountMustBeMultipleOfGwei()); uint64 amountGwei = uint64(amountWei / GWEI_TO_WEI); + amountWei = amountGwei * GWEI_TO_WEI; require(amountGwei <= restakedExecutionLayerGwei, InsufficientWithdrawableBalance()); restakedExecutionLayerGwei -= amountGwei; emit RestakedBeaconChainETHWithdrawn(recipient, amountWei); diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 8bfd27ff2..34caeb4eb 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -14,7 +14,7 @@ import "src/test/integration/TimeMachine.t.sol"; import "src/test/integration/users/User.t.sol"; import "src/test/integration/users/User_M1.t.sol"; -abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerTypes { +abstract contract IntegrationBase is IntegrationDeployer { using StdStyle for *; using SlashingLib for *; using Strings for *; @@ -353,27 +353,25 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType function assert_Snap_Allocations_Modified( User operator, - OperatorSet memory operatorSet, - IStrategy[] memory strategies, - uint64[] memory newMagnitudes, + IAllocationManagerTypes.AllocateParams memory allocateParams, bool completed, string memory err ) internal { - // TODO: magnitudes (max, encumbered) - - Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); - Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); - - for (uint i = 0; i < strategies.length; i++) { - Allocation memory curAlloc = curAllocs[i]; - + IAllocationManagerTypes.Allocation[] memory curAllocs = _getAllocations(operator, allocateParams.operatorSet, allocateParams.strategies); + IAllocationManagerTypes.Allocation[] memory prevAllocs = _getPrevAllocations(operator, allocateParams.operatorSet, allocateParams.strategies); + Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, allocateParams.strategies); + Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, allocateParams.strategies); + + for (uint i = 0; i < allocateParams.strategies.length; i++) { + IAllocationManagerTypes.Allocation memory curAlloc = curAllocs[i]; + IAllocationManagerTypes.Allocation memory prevAlloc = prevAllocs[i]; + + // Check allocations if (completed) { - assertEq(curAlloc.currentMagnitude, newMagnitudes[i], string.concat(err, " (currentMagnitude)")); + assertEq(curAlloc.currentMagnitude, allocateParams.newMagnitudes[i], string.concat(err, " (currentMagnitude)")); assertEq(curAlloc.pendingDiff, 0, string.concat(err, " (pendingDiff)")); assertEq(curAlloc.effectBlock, 0, string.concat(err, " (effectBlock)")); } else { - Allocation memory prevAlloc = prevAllocs[i]; - assertEq( curAlloc.currentMagnitude, prevAlloc.currentMagnitude, @@ -381,7 +379,7 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType ); assertEq( curAlloc.pendingDiff, - prevAlloc.pendingDiff + int128(int64(newMagnitudes[i])), + prevAlloc.pendingDiff + int128(int64(allocateParams.newMagnitudes[i])), string.concat(err, " (pendingDiff)") ); @@ -393,28 +391,88 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType string.concat(err, " (effectBlock)") ); } + + // Check magnitudes + Magnitudes memory curMagnitude = curMagnitudes[i]; + Magnitudes memory prevMagnitude = prevMagnitudes[i]; + + // if (isAllocation) ... + if (prevAlloc.currentMagnitude < allocateParams.newMagnitudes[i]) { + uint256 delta = allocateParams.newMagnitudes[i] - prevAlloc.currentMagnitude; + + assertEq( + curMagnitude.encumbered, + prevMagnitude.encumbered + delta, + string.concat(err, " (encumbered magnitude)") + ); + + assertEq( + curMagnitude.allocatable, + prevMagnitude.allocatable - delta, + string.concat(err, " (allocatable magnitude)") + ); + } else { + uint256 delta = prevAlloc.currentMagnitude - allocateParams.newMagnitudes[i]; + + // if deallocated, and completed + if (completed) { + assertEq( + curMagnitude.encumbered - delta, + prevMagnitude.encumbered, + string.concat(err, " (encumbered magnitude)") + ); + + assertEq( + curMagnitude.allocatable, + prevMagnitude.allocatable + delta, + string.concat(err, " (allocatable magnitude)") + ); + } else { + assertEq( + curMagnitude.encumbered, + prevMagnitude.encumbered, + string.concat(err, " (encumbered magnitude)") + ); + + assertEq( + curMagnitude.allocatable, + prevMagnitude.allocatable, + string.concat(err, " (allocatable magnitude)") + ); + } + } + + // Max magnitude should not have mutated. + assertEq( + curMagnitude.max, + prevMagnitude.max, + string.concat(err, " (max magnitude)") + ); } } function assert_Snap_Allocations_Slashed( - User operator, + IAllocationManagerTypes.SlashingParams memory slashingParams, OperatorSet memory operatorSet, - IStrategy[] memory strategies, - uint256[] memory wadsToSlash, bool completed, string memory err ) internal { - // TODO: slashable stake + User op = User(payable(slashingParams.operator)); + + IAllocationManagerTypes.Allocation[] memory curAllocs = _getAllocations(op, operatorSet, slashingParams.strategies); + IAllocationManagerTypes.Allocation[] memory prevAllocs = _getPrevAllocations(op, operatorSet, slashingParams.strategies); + Magnitudes[] memory curMagnitudes = _getMagnitudes(op, slashingParams.strategies); + Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(op, slashingParams.strategies); - Allocation[] memory curAllocs = _getAllocations(operator, operatorSet, strategies); - Allocation[] memory prevAllocs = _getPrevAllocations(operator, operatorSet, strategies); - - for (uint i = 0; i < strategies.length; i++) { - Allocation memory curAlloc = curAllocs[i]; - Allocation memory prevAlloc = prevAllocs[i]; + (, uint32 delay) = allocationManager.getAllocationDelay(slashingParams.operator); - uint64 slashedMagnitude = uint64(uint256(prevAlloc.currentMagnitude).mulWadRoundUp(wadsToSlash[i])); + for (uint i = 0; i < slashingParams.strategies.length; i++) { + IAllocationManagerTypes.Allocation memory curAlloc = curAllocs[i]; + IAllocationManagerTypes.Allocation memory prevAlloc = prevAllocs[i]; + uint64 slashedMagnitude = uint64(uint256(prevAlloc.currentMagnitude).mulWadRoundUp(slashingParams.wadsToSlash[i])); + + // Check Allocations assertEq( curAlloc.currentMagnitude, prevAlloc.currentMagnitude - slashedMagnitude, @@ -431,18 +489,19 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType string.concat(err, " (currentMagnitude)") ); + // If (isDeallocation) ... if (prevAlloc.pendingDiff < 0) { uint64 slashedPending = - uint64(uint256(uint128(-prevAlloc.pendingDiff)).mulWadRoundUp(wadsToSlash[i])); + uint64(uint256(uint128(-prevAlloc.pendingDiff)).mulWadRoundUp(slashingParams.wadsToSlash[i])); assertEq( curAlloc.pendingDiff, prevAlloc.pendingDiff + int128(uint128(slashedPending)), string.concat(err, " (pendingDiff)") ); - } - (, uint32 delay) = allocationManager.getAllocationDelay(address(operator)); + delay = DEALLOCATION_DELAY; + } assertEq( curAlloc.effectBlock, @@ -450,9 +509,62 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType string.concat(err, " (effectBlock)") ); } + + // Check Magnitudes + Magnitudes memory curMagnitude = curMagnitudes[i]; + Magnitudes memory prevMagnitude = prevMagnitudes[i]; + + assertEq( + curMagnitude.encumbered, + prevMagnitude.encumbered - slashedMagnitude, + string.concat(err, " (encumbered magnitude)") + ); + + assertEq( + curMagnitude.allocatable, + prevMagnitude.allocatable, + string.concat(err, " (allocatable magnitude)") + ); + + assertEq( + curMagnitude.max, + prevMagnitude.max - slashedMagnitude, + string.concat(err, " (max magnitude)") + ); } } + function assert_HasUnderlyingTokenBalances_AfterSlash( + User staker, + IAllocationManagerTypes.AllocateParams memory allocateParams, + IAllocationManagerTypes.SlashingParams memory slashingParams, + uint[] memory expectedBalances, + string memory err + ) internal view { + for (uint i; i < allocateParams.strategies.length; ++i) { + IStrategy strat = allocateParams.strategies[i]; + + uint balance = strat == BEACONCHAIN_ETH_STRAT + ? address(staker).balance + : strat.underlyingToken().balanceOf(address(staker)); + + if (slashingParams.strategies.contains(strat)) { + uint256 wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; + + expectedBalances[i] -= expectedBalances[i] + .mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); + + uint256 maxDelta = strat == BEACONCHAIN_ETH_STRAT ? 1 gwei : 1; + + assertApproxEqAbs(expectedBalances[i], balance, maxDelta, err); + } else { + assertEq(expectedBalances[i], balance, err); + } + } + } + + // TODO: slashable stake + /******************************************************************************* SNAPSHOT ASSERTIONS: OPERATOR SHARES *******************************************************************************/ @@ -1243,7 +1355,7 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies - ) internal timewarp() returns (Allocation[] memory) { + ) internal timewarp() returns (IAllocationManagerTypes.Allocation[] memory) { return _getAllocations(operator, operatorSet, strategies); } @@ -1252,13 +1364,40 @@ abstract contract IntegrationBase is IntegrationDeployer, IAllocationManagerType User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies - ) internal view returns (Allocation[] memory allocations) { - allocations = new Allocation[](strategies.length); + ) internal view returns (IAllocationManagerTypes.Allocation[] memory allocations) { + allocations = new IAllocationManagerTypes.Allocation[](strategies.length); for (uint i = 0; i < strategies.length; ++i) { allocations[i] = allocationManager.getAllocation(address(operator), operatorSet, strategies[i]); } } + struct Magnitudes { + uint256 encumbered; + uint256 allocatable; + uint256 max; + } + + function _getPrevMagnitudes( + User operator, + IStrategy[] memory strategies + ) internal timewarp() returns (Magnitudes[] memory) { + return _getMagnitudes(operator, strategies); + } + + function _getMagnitudes( + User operator, + IStrategy[] memory strategies + ) internal view returns (Magnitudes[] memory magnitudes) { + magnitudes = new Magnitudes[](strategies.length); + for (uint i = 0; i < strategies.length; ++i) { + magnitudes[i] = Magnitudes({ + encumbered: allocationManager.encumberedMagnitude(address(operator), strategies[i]), + allocatable: allocationManager.getAllocatableMagnitude(address(operator), strategies[i]), + max: allocationManager.getMaxMagnitude(address(operator), strategies[i]) + }); + } + } + /// @dev Uses timewarp modifier to get operator shares at the last snapshot function _getPrevOperatorShares( User operator, diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 2a2022abe..7d88477cd 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -7,11 +7,8 @@ import "src/test/integration/users/User_M1.t.sol"; /// @notice Contract that provides utility functions to reuse common test blocks & checks contract IntegrationCheckUtils is IntegrationBase { - /******************************************************************************* - ALLOCATION MANAGER CHECKS - *******************************************************************************/ - - + using ArrayLib for IStrategy[]; + using SlashingLib for *; /******************************************************************************* EIGENPOD CHECKS @@ -274,9 +271,9 @@ contract IntegrationCheckUtils is IntegrationBase { // Checks specific to an operator that the Staker has delegated to if (operator != User(payable(0))) { if (operator != staker) { - assert_Snap_Unchanged_TokenBalances(User(operator), "operator token balances should not have changed"); + assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); } - assert_Snap_Unchanged_OperatorShares(User(operator), "operator shares should not have changed"); + assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); } } @@ -296,9 +293,9 @@ contract IntegrationCheckUtils is IntegrationBase { // Additional checks or handling for the non-user operator scenario if (operator != User(User(payable(0)))) { if (operator != staker) { - assert_Snap_Unchanged_TokenBalances(User(operator), "operator 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_OperatorShares(User(operator), withdrawal.strategies, withdrawal.scaledShares, "operator should have received shares"); + assert_Snap_Added_OperatorShares(operator, withdrawal.strategies, withdrawal.scaledShares, "operator should have received shares"); } } @@ -323,4 +320,41 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Unchanged_OperatorShares(operator, "operator should have shares unchanged"); assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); } + + /******************************************************************************* + ALLOCATION MANAGER CHECKS + *******************************************************************************/ + + function check_Withdrawal_AsTokens_State_AfterSlash( + User staker, + User operator, + IDelegationManagerTypes.Withdrawal memory withdrawal, + IAllocationManagerTypes.AllocateParams memory allocateParams, + IAllocationManagerTypes.SlashingParams memory slashingParams, + uint[] memory expectedTokens + ) internal { + IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length); + + for (uint256 i; i < withdrawal.strategies.length; i++) { + IStrategy strat = withdrawal.strategies[i]; + + bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy; + + tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken(); + + if (slashingParams.strategies.contains(strat)) { + uint256 wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; + + expectedTokens[i] -= expectedTokens[i] + .mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); + + // Round down to the nearest gwei for beaconchain ETH strategy. + if (isBeaconChainETHStrategy) { + expectedTokens[i] -= expectedTokens[i] % 1 gwei; + } + } + } + + check_Withdrawal_AsTokens_State(staker, operator, withdrawal, withdrawal.strategies, withdrawal.scaledShares, tokens, expectedTokens); + } } diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 230f091b2..e58e11263 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -44,6 +44,8 @@ uint8 constant PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE = 3; uint8 constant PAUSED_EIGENPODS_VERIFY_WITHDRAWAL = 4; uint8 constant PAUSED_NON_PROOF_WITHDRAWALS = 5; +IStrategy constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); + abstract contract IntegrationDeployer is ExistingDeploymentParser, Logger { using StdStyle for *; using ArrayLib for *; diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol index 13b4d807b..abee4b82e 100644 --- a/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Allocate.t.sol @@ -4,7 +4,9 @@ pragma solidity ^0.8.27; import "src/test/integration/IntegrationChecks.t.sol"; import "src/test/integration/users/User.t.sol"; -contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelegationManagerTypes { +// TODO: move randomness from tests + +contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils { function testFuzz_deposit_delegate_allocate( uint24 _random ) public { @@ -16,7 +18,7 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega }); // Create a staker and an operator with a nonzero balance and corresponding strategies - (AVS avs, OperatorSet[] memory operatorSets) = _newRandomAVS(); + (, OperatorSet[] memory operatorSets) = _newRandomAVS(); (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); (User operator,,) = _newRandomOperator(); @@ -36,39 +38,28 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega operator.registerForOperatorSets(operatorSets); - AllocateParams[] memory allocateParams = new AllocateParams[](operatorSets.length); + IAllocationManagerTypes.AllocateParams[] memory allocateParams = + new IAllocationManagerTypes.AllocateParams[](operatorSets.length); for (uint256 i; i < operatorSets.length; ++i) { uint256 len = allocationManager.getStrategiesInOperatorSet(operatorSets[i]).length; allocateParams[i] = operator.modifyAllocations( - operatorSets[i], _randMagnitudes({sum: 1 ether / uint64(operatorSets.length), len: len}) + operatorSets[i], _randMagnitudes({sum: WAD / uint64(operatorSets.length), len: len}) ); assert_Snap_Allocations_Modified( - operator, - operatorSets[i], - allocateParams[i].strategies, - allocateParams[i].newMagnitudes, - false, - "operator allocations should be updated before delay" - ); + operator, allocateParams[i], false, "operator allocations should be updated before delay" + ); } _rollBlocksForCompleteAllocation(); for (uint256 i; i < operatorSets.length; ++i) { assert_Snap_Allocations_Modified( - operator, - operatorSets[i], - allocateParams[i].strategies, - allocateParams[i].newMagnitudes, - true, - "operator allocations should be updated after delay" + operator, allocateParams[i], true, "operator allocations should be updated after delay" ); } } - // FIXME: Current fails with `AmountMustBeMultipleOfGwei` since slashed beaconchain deposits - // are not garanteed to be multiple of gwei after being slashed. function testFuzz_deposit_delegate_allocate_slash_undelegate_completeAsTokens( uint24 _random ) public { @@ -81,76 +72,55 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega assertEq(strategies.length, 33, "sanity"); - uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); - - assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); - assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - - /// 1. Deposit Into Strategies + // 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator staker.delegateTo(operator); - check_Delegation_State(staker, operator, strategies, shares); // Create an operator set and register an operator. OperatorSet memory operatorSet = avs.createOperatorSet(strategies); operator.registerForOperatorSet(operatorSet); // 3. Allocate to operator set. - AllocateParams memory allocateParams = + IAllocationManagerTypes.AllocateParams memory allocateParams = operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); - assert_Snap_Allocations_Modified( - operator, - operatorSet, - strategies, - allocateParams.newMagnitudes, - false, - "operator allocations should be updated before delay" + operator, allocateParams, false, "operator allocations should be updated before delay" ); - _rollBlocksForCompleteAllocation(); - assert_Snap_Allocations_Modified( - operator, - operatorSet, - strategies, - allocateParams.newMagnitudes, - true, - "operator allocations should be updated after delay" + operator, allocateParams, true, "operator allocations should be updated after delay" ); + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + // 4. Slash operator - SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); - // assert_Snap_Allocations_Slashed( - // operator, - // operatorSet, - // strategies, - // slashingParams.wadsToSlash, - // true, - // "operator allocations should be slashed" - // ); + IAllocationManagerTypes.SlashingParams memory slashingParams = + avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); // 5. Undelegate from an operator IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - // FIXME: check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); - // 6. Complete withdrawal _rollBlocksForCompleteWithdrawals(); for (uint256 i = 0; i < withdrawals.length; ++i) { uint256[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); - IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); - // FIXME: check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares, tokens, expectedTokens); + staker.completeWithdrawalAsTokens(withdrawals[i]); + // FIXME: check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); } // Check Final State assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); - assert_HasUnderlyingTokenBalances( - staker, strategies, tokenBalances, "staker should once again have original token tokenBalances" + assert_HasUnderlyingTokenBalances_AfterSlash( + staker, + allocateParams, + slashingParams, + tokenBalances, + "staker should once again have original token tokenBalances minus slashed" ); assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } @@ -167,31 +137,56 @@ contract Integration_Deposit_Delegate_Allocate is IntegrationCheckUtils, IDelega assertEq(strategies.length, 33, "sanity"); - uint256[] memory shares = _calculateExpectedShares(strategies, tokenBalances); - - assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); - assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - - /// 1. Deposit Into Strategies + // 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - check_Deposit_State(staker, strategies, shares); - // 2. Delegate to an operator staker.delegateTo(operator); - check_Delegation_State(staker, operator, strategies, shares); // Create an operator set and register an operator. OperatorSet memory operatorSet = avs.createOperatorSet(strategies); operator.registerForOperatorSet(operatorSet); // 3. Allocate to operator set. - operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); - cheats.roll(block.number + allocationManager.ALLOCATION_CONFIGURATION_DELAY()); + IAllocationManagerTypes.AllocateParams memory allocateParams = + operator.modifyAllocations(operatorSet, _randMagnitudes({sum: 1 ether, len: strategies.length})); + assert_Snap_Allocations_Modified( + operator, allocateParams, false, "operator allocations should be updated before delay" + ); + _rollBlocksForCompleteAllocation(); + assert_Snap_Allocations_Modified( + operator, allocateParams, true, "operator allocations should be updated after delay" + ); // 4. Queue withdrawal + IDelegationManagerTypes.Withdrawal[] memory withdrawals = + staker.queueWithdrawals(strategies, _calculateExpectedShares(strategies, tokenBalances)); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); // 5. Slash operator - SlashingParams memory slashingParams = avs.slashOperator(operator, operatorSet.id, _randWadToSlash()); + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + IAllocationManagerTypes.SlashingParams memory slashingParams = + avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + + // 6. Complete withdrawal + _rollBlocksForCompleteWithdrawals(); + for (uint256 i = 0; i < withdrawals.length; ++i) { + uint256[] memory expectedTokens = + _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); + staker.completeWithdrawalAsTokens(withdrawals[i]); + // FIXME: check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); + } + + // Check Final State + assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); + assert_HasUnderlyingTokenBalances_AfterSlash( + staker, + allocateParams, + slashingParams, + tokenBalances, + "staker should once again have original token tokenBalances minus slashed" + ); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } function testFuzz_deposit_delegate_allocate_deallocate_slash_queue_completeAsTokens( diff --git a/src/test/integration/users/AVS.t.sol b/src/test/integration/users/AVS.t.sol index e6ccb356b..c891867fa 100644 --- a/src/test/integration/users/AVS.t.sol +++ b/src/test/integration/users/AVS.t.sol @@ -96,8 +96,13 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { print.gasUsed(); } - function slashOperator(User operator, uint32 operatorSetId, IStrategy[] memory strategies, uint256[] memory wadsToSlash) public createSnapshot { - SlashingParams memory p = SlashingParams({ + function slashOperator( + User operator, + uint32 operatorSetId, + IStrategy[] memory strategies, + uint256[] memory wadsToSlash + ) public createSnapshot returns (SlashingParams memory p) { + p = SlashingParams({ operator: address(operator), operatorSetId: operatorSetId, strategies: strategies, @@ -127,7 +132,7 @@ contract AVS is Logger, IAllocationManagerTypes, IAVSRegistrar { } _tryPrankAppointee_AllocationManager(IAllocationManager.slashOperator.selector); - allocationManager.slashOperator(address(this), slashingParams); + allocationManager.slashOperator(address(this), p); print.gasUsed(); } diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index d06d02d29..7de434607 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -30,6 +30,7 @@ interface IUserDeployer { } contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { + using StdStyle for *; using SlashingLib for *; using ArrayLib for *; using print for *; @@ -222,10 +223,18 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { print.gasUsed(); for (uint256 i = 0; i < expectedWithdrawals.length; i++) { - emit log("expecting withdrawal:"); - emit log_named_uint("nonce: ", expectedWithdrawals[i].nonce); - emit log_named_address("strat: ", address(expectedWithdrawals[i].strategies[0])); - emit log_named_uint("shares: ", expectedWithdrawals[i].scaledShares[0]); + IStrategy strat = expectedWithdrawals[i].strategies[0]; + + string memory name = strat == beaconChainETHStrategy + ? "Native ETH" + : IERC20Metadata(address(strat.underlyingToken())).name(); + + console.log( + " Expecting withdrawal with nonce %s of %s for %s scaled shares.", + expectedWithdrawals[i].nonce, + name, + expectedWithdrawals[i].scaledShares[0] + ); } return expectedWithdrawals; diff --git a/src/test/unit/EigenPodUnit.t.sol b/src/test/unit/EigenPodUnit.t.sol index 0aafde362..002ff9419 100644 --- a/src/test/unit/EigenPodUnit.t.sol +++ b/src/test/unit/EigenPodUnit.t.sol @@ -450,12 +450,13 @@ contract EigenPodUnitTests_EPMFunctions is EigenPodUnitTests { pod.withdrawRestakedBeaconChainETH(recipient, randAmount); } - function testFuzz_withdrawRestakedBeaconChainETH_revert_notFullGweiAmount( + function testFuzz_withdrawRestakedBeaconChainETH_revert_withdrawAmountTooLarge( + uint256 rand, address recipient, - uint256 randAmount + uint256 randAmountWei ) public { // Setup EigenPod Staker - (EigenPodUser staker,) = _newEigenPodStaker({ rand: 0 }); + (EigenPodUser staker,) = _newEigenPodStaker({ rand: rand }); EigenPod pod = staker.pod(); (uint40[] memory validators,) = staker.startValidators(); staker.verifyWithdrawalCredentials(validators); @@ -463,16 +464,17 @@ contract EigenPodUnitTests_EPMFunctions is EigenPodUnitTests { staker.startCheckpoint(); staker.completeCheckpoint(); - // ensure amount is not a full gwei - randAmount = (randAmount % 1 gwei) + bound(randAmount, 1, 1 gwei - 1); - cheats.expectRevert(IEigenPodErrors.AmountMustBeMultipleOfGwei.selector); + // ensure amount is too large + uint64 withdrawableRestakedExecutionLayerGwei = pod.withdrawableRestakedExecutionLayerGwei(); + randAmountWei = randAmountWei - (randAmountWei % 1 gwei); + cheats.assume((randAmountWei / 1 gwei) > withdrawableRestakedExecutionLayerGwei); + cheats.expectRevert(IEigenPodErrors.InsufficientWithdrawableBalance.selector); cheats.prank(address(eigenPodManagerMock)); - pod.withdrawRestakedBeaconChainETH(recipient, randAmount); + pod.withdrawRestakedBeaconChainETH(recipient, randAmountWei); } - function testFuzz_withdrawRestakedBeaconChainETH_revert_withdrawAmountTooLarge( + function testFuzz_withdrawRestakedBeaconChainETH( uint256 rand, - address recipient, uint256 randAmountWei ) public { // Setup EigenPod Staker @@ -484,16 +486,32 @@ contract EigenPodUnitTests_EPMFunctions is EigenPodUnitTests { staker.startCheckpoint(); staker.completeCheckpoint(); - // ensure amount is too large + // ensure valid fuzzed wei amounts uint64 withdrawableRestakedExecutionLayerGwei = pod.withdrawableRestakedExecutionLayerGwei(); randAmountWei = randAmountWei - (randAmountWei % 1 gwei); - cheats.assume((randAmountWei / 1 gwei) > withdrawableRestakedExecutionLayerGwei); - cheats.expectRevert(IEigenPodErrors.InsufficientWithdrawableBalance.selector); + cheats.assume((randAmountWei / 1 gwei) <= withdrawableRestakedExecutionLayerGwei); + + address recipient = cheats.addr(uint256(123_456_789)); + cheats.prank(address(eigenPodManagerMock)); + cheats.expectEmit(true, true, true, true, address(pod)); + emit RestakedBeaconChainETHWithdrawn(recipient, randAmountWei); pod.withdrawRestakedBeaconChainETH(recipient, randAmountWei); + + assertEq(address(recipient).balance, randAmountWei, "recipient should have received withdrawn balance"); + assertEq( + address(pod).balance, + uint(withdrawableRestakedExecutionLayerGwei * 1 gwei) - randAmountWei, + "pod balance should have decreased by withdrawn eth" + ); + assertEq( + pod.withdrawableRestakedExecutionLayerGwei(), + withdrawableRestakedExecutionLayerGwei - uint64(randAmountWei / 1 gwei), + "withdrawableRestakedExecutionLayerGwei should have decreased by amount withdrawn" + ); } - function testFuzz_withdrawRestakedBeaconChainETH( + function testFuzz_withdrawRestakedBeaconChainETH_AmountGweiNotDivisibleByGwei( uint256 rand, uint256 randAmountWei ) public { @@ -508,25 +526,25 @@ contract EigenPodUnitTests_EPMFunctions is EigenPodUnitTests { // ensure valid fuzzed wei amounts uint64 withdrawableRestakedExecutionLayerGwei = pod.withdrawableRestakedExecutionLayerGwei(); - randAmountWei = randAmountWei - (randAmountWei % 1 gwei); + uint256 randAmountWeiAdjusted = randAmountWei - (randAmountWei % 1 gwei); cheats.assume((randAmountWei / 1 gwei) <= withdrawableRestakedExecutionLayerGwei); address recipient = cheats.addr(uint256(123_456_789)); cheats.prank(address(eigenPodManagerMock)); cheats.expectEmit(true, true, true, true, address(pod)); - emit RestakedBeaconChainETHWithdrawn(recipient, randAmountWei); + emit RestakedBeaconChainETHWithdrawn(recipient, randAmountWeiAdjusted); pod.withdrawRestakedBeaconChainETH(recipient, randAmountWei); - assertEq(address(recipient).balance, randAmountWei, "recipient should have received withdrawn balance"); + assertEq(address(recipient).balance, randAmountWeiAdjusted, "recipient should have received withdrawn balance"); assertEq( address(pod).balance, - uint(withdrawableRestakedExecutionLayerGwei * 1 gwei) - randAmountWei, + uint(withdrawableRestakedExecutionLayerGwei * 1 gwei) - randAmountWeiAdjusted, "pod balance should have decreased by withdrawn eth" ); assertEq( pod.withdrawableRestakedExecutionLayerGwei(), - withdrawableRestakedExecutionLayerGwei - uint64(randAmountWei / 1 gwei), + withdrawableRestakedExecutionLayerGwei - uint64(randAmountWeiAdjusted / 1 gwei), "withdrawableRestakedExecutionLayerGwei should have decreased by amount withdrawn" ); } diff --git a/src/test/utils/ArrayLib.sol b/src/test/utils/ArrayLib.sol index 6ae3d434d..a2f518af7 100644 --- a/src/test/utils/ArrayLib.sol +++ b/src/test/utils/ArrayLib.sol @@ -122,39 +122,6 @@ library ArrayLib { array = new IAllocationManagerTypes.AllocateParams[](1); array[0] = allocateParams; } - - /// ----------------------------------------------------------------------- - /// Sorting - /// ----------------------------------------------------------------------- - - function sort( - IStrategy[] memory array - ) internal pure returns (IStrategy[] memory) { - if (array.length <= 1) { - return array; - } - - for (uint256 i = 1; i < array.length; i++) { - IStrategy key = array[i]; - uint256 j = i - 1; - - while (j > 0 && uint256(uint160(address(array[j]))) > uint256(uint160(address(key)))) { - array[j + 1] = array[j]; - j--; - } - - // Special case for the first element - if (j == 0 && uint256(uint160(address(array[j]))) > uint256(uint160(address(key)))) { - array[j + 1] = array[j]; - array[j] = key; - } else if (j < i - 1) { - array[j + 1] = key; - } - } - - return array; - } - /// ----------------------------------------------------------------------- /// Length Updates /// ----------------------------------------------------------------------- @@ -238,4 +205,184 @@ library ArrayLib { } return array; } + + /// ----------------------------------------------------------------------- + /// Contains + /// ----------------------------------------------------------------------- + + function contains( + uint16[] memory array, + uint16 x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + function contains( + uint32[] memory array, + uint32 x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + function contains( + uint64[] memory array, + uint64 x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + function contains( + uint256[] memory array, + uint256 x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + function contains( + address[] memory array, + address x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + function contains( + IERC20[] memory array, + IERC20 x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + function contains( + IStrategy[] memory array, + IStrategy x + ) internal pure returns (bool) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return true; + } + return false; + } + + /// ----------------------------------------------------------------------- + /// indexOf + /// ----------------------------------------------------------------------- + + function indexOf( + uint16[] memory array, + uint16 x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + function indexOf( + uint32[] memory array, + uint32 x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + function indexOf( + uint64[] memory array, + uint64 x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + function indexOf( + uint256[] memory array, + uint256 x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + function indexOf( + address[] memory array, + address x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + function indexOf( + IERC20[] memory array, + IERC20 x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + function indexOf( + IStrategy[] memory array, + IStrategy x + ) internal pure returns (uint256) { + for (uint256 i; i < array.length; ++i) { + if (array[i] == x) return i; + } + return type(uint256).max; + } + + /// ----------------------------------------------------------------------- + /// Sorting + /// ----------------------------------------------------------------------- + + function sort( + IStrategy[] memory array + ) internal pure returns (IStrategy[] memory) { + if (array.length <= 1) { + return array; + } + + for (uint256 i = 1; i < array.length; i++) { + IStrategy key = array[i]; + uint256 j = i - 1; + + while (j > 0 && uint256(uint160(address(array[j]))) > uint256(uint160(address(key)))) { + array[j + 1] = array[j]; + j--; + } + + // Special case for the first element + if (j == 0 && uint256(uint160(address(array[j]))) > uint256(uint160(address(key)))) { + array[j + 1] = array[j]; + array[j] = key; + } else if (j < i - 1) { + array[j + 1] = key; + } + } + + return array; + } } \ No newline at end of file