From e945d8d37b16bef70081f9764d52eab294acd3b0 Mon Sep 17 00:00:00 2001 From: eigenmikem Date: Thu, 9 Jan 2025 13:05:52 -0500 Subject: [PATCH 1/3] Slashing integration tests (#1003) * Validate that users who are slashed fully can redeposit once undelegated * removing arraylib use * test for updating eigenpod state -> slash/undelegate * removing unnecessary logging function * fixing strategy set * beacon chain tests in progress --------- Co-authored-by: Michael --- src/test/integration/IntegrationBase.t.sol | 62 +++++- src/test/integration/IntegrationChecks.t.sol | 50 +++++ ...egate_Allocate_Slash_Queue_Redeposit.t.sol | 97 ++++++++++ .../integration/tests/Slashing_Combined.sol | 177 ++++++++++++++++++ .../tests/WithdrawalSlashing_Combined.sol | 124 ++++++++++++ 5 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol create mode 100644 src/test/integration/tests/Slashing_Combined.sol create mode 100644 src/test/integration/tests/WithdrawalSlashing_Combined.sol diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 852352e91..1df949a05 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -186,6 +186,36 @@ abstract contract IntegrationBase is IntegrationDeployer { return result; } + + /// @dev Choose a random subset of validators (selects AT LEAST ONE but NOT ALL) + function _chooseSubset(uint40[] memory validators) internal returns (uint40[] memory) { + require(validators.length >= 2, "Need at least 2 validators to choose subset"); + + uint40[] memory result = new uint40[](validators.length); + uint newLen; + + uint rand = _randUint({ min: 1, max: validators.length ** 2 }); + for (uint i = 0; i < validators.length; i++) { + if (rand >> i & 1 == 1) { + result[newLen] = validators[i]; + newLen++; + } + } + + // If we picked all, remove one random validator + if (newLen == validators.length) { + uint indexToRemove = _randUint({ min: 0, max: validators.length - 1 }); + for (uint i = indexToRemove; i < newLen - 1; i++) { + result[i] = result[i + 1]; + } + newLen--; + } + + // Update array length + assembly { mstore(result, newLen) } + + return result; + } function _getTokenName(IERC20 token) internal view returns (string memory) { if (token == NATIVE_ETH) { @@ -561,8 +591,11 @@ abstract contract IntegrationBase is IntegrationDeployer { uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); } + console.log(prevShares[i]); + console.log(slashedShares); + console.log(curShares[i]); - assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1, err); + assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1000, err); } } @@ -1133,6 +1166,21 @@ abstract contract IntegrationBase is IntegrationDeployer { return (strategies.sort(), wadsToSlash); } + + function _strategiesAndWadsForFullSlash( + OperatorSet memory operatorSet + ) internal view returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) { + // Get list of all strategies in an operator set. + strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + + wadsToSlash = new uint[](strategies.length); + + for (uint i; i < strategies.length; ++i) { + wadsToSlash[i] = 1 ether; + } + + return (strategies.sort(), wadsToSlash); + } function _randMagnitudes(uint64 sum, uint256 len) internal returns (uint64[] memory magnitudes) { magnitudes = new uint64[](len); @@ -1151,6 +1199,18 @@ abstract contract IntegrationBase is IntegrationDeployer { } } + function _maxMagnitudes(OperatorSet memory operatorSet, User operator) internal view returns (uint64[] memory magnitudes) { + IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); + uint256 len = strategies.length; + magnitudes = new uint64[](len); + + if (len == 0) return magnitudes; + + for (uint256 i; i < len; ++i) { + magnitudes[i] = allocationManager.getMaxMagnitude(address(operator), strategies[i]); + } + } + function _randWithdrawal( IStrategy[] memory strategies, uint[] memory shares diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index c01db4e72..cd0ce115d 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -390,6 +390,56 @@ contract IntegrationCheckUtils is IntegrationBase { } } + function check_Withdrawal_AsTokens_State_AfterBeaconSlash( + 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 (uint 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)) { + uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; + + expectedTokens[i] -= expectedTokens[i] + .mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); + + uint256 max = allocationManager.getMaxMagnitude(address(operator), strat); + + withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max); + + // Round down to the nearest gwei for beaconchain ETH strategy. + if (isBeaconChainETHStrategy) { + expectedTokens[i] -= expectedTokens[i] % 1 gwei; + } + } + } + + // Common checks + assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); + + // assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + assert_Snap_Unchanged_StakerDepositShares(staker, "staker shares should not have changed"); + assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.scaledShares, "strategies should have total shares decremented"); + + // Checks specific to an operator that the Staker has delegated to + if (operator != User(payable(0))) { + if (operator != staker) { + assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); + } + assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); + } + } + function check_Withdrawal_AsShares_State_AfterSlash( User staker, User operator, diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol new file mode 100644 index 000000000..fcfc65b44 --- /dev/null +++ b/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/IntegrationChecks.t.sol"; +import "src/test/integration/users/User.t.sol"; +import {console} from "forge-std/console.sol"; + +contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is IntegrationCheckUtils, IDelegationManagerTypes { + + function testFuzz_deposit_delegate_allocate_fullSlash_queue_complete_redeposit( + uint24 _random + ) public { + _configRand({_randomSeed: _random, _assetTypes: HOLDS_LST, _userTypes: DEFAULT}); + _upgradeEigenLayerContracts(); + + (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); + (User operator,,) = _newRandomOperator(); + (AVS avs,) = _newRandomAVS(); + + uint256[] memory tokensToDeposit = new uint256[](tokenBalances.length); + uint256[] memory numTokensRemaining = new uint256[](tokenBalances.length); + for (uint256 i = 0; i < tokenBalances.length; i++) { + tokensToDeposit[i] = tokenBalances[i]/2; + numTokensRemaining[i] = tokenBalances[i] - tokensToDeposit[i]; + } + + uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit); + + // 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokensToDeposit); + check_Deposit_State_PartialDeposit(staker, strategies, shares, numTokensRemaining); + + // 2. Delegate to operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // Create operator set and register operator + OperatorSet memory operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); + + // 3. Allocate to operator set + IAllocationManagerTypes.AllocateParams memory allocateParams = + operator.modifyAllocations(operatorSet, _maxMagnitudes(operatorSet, operator)); + + assert_Snap_Allocations_Modified( + operator, + allocateParams, + false, + "operator allocations should be updated before delay" + ); + + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + + assert_Snap_Allocations_Modified( + operator, + allocateParams, + true, + "operator allocations should be updated after delay" + ); + + // 4. Fully slash operator + IAllocationManagerTypes.SlashingParams memory slashingParams; + { + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _strategiesAndWadsForFullSlash(operatorSet); + + slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing"); + assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); + } + + // 5. Undelegate from an operator + IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + + // 6. Complete withdrawal + _rollBlocksForCompleteWithdrawals(withdrawals); + for (uint256 i = 0; i < withdrawals.length; ++i) { + uint256[] memory expectedTokens = + _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); + for (uint256 i = 0; i < expectedTokens.length; i++) { + } + staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); + } + + // 7. Redeposit + shares = _calculateExpectedShares(strategies, numTokensRemaining); + staker.depositIntoEigenlayer(strategies, numTokensRemaining); + check_Deposit_State(staker, strategies, shares); + + // Final state checks + assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } +} \ No newline at end of file diff --git a/src/test/integration/tests/Slashing_Combined.sol b/src/test/integration/tests/Slashing_Combined.sol new file mode 100644 index 000000000..fff647ba4 --- /dev/null +++ b/src/test/integration/tests/Slashing_Combined.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/IntegrationChecks.t.sol"; +import "src/test/integration/users/User.t.sol"; + +contract Integration_Slashing_Combined is IntegrationCheckUtils { + // Helper struct to reduce stack variables + struct TestContext { + User staker; + User operator; + AVS avs; + IStrategy[] strategies; + uint256[] tokenBalances; + uint40[] validators; + OperatorSet operatorSet; + IAllocationManagerTypes.AllocateParams allocateParams; + IAllocationManagerTypes.SlashingParams slashingParams; + } + + function testFuzz_deposit_slashBeacon_delegate_slashEigen_withdraw(uint24 _random) public { + TestContext memory ctx; + + // Initial setup and configuration + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_ETH, + _userTypes: DEFAULT + }); + + _upgradeEigenLayerContracts(); + + // Initialize actors and store in context + (ctx.staker, ctx.strategies, ctx.tokenBalances) = _newRandomStaker(); + (ctx.operator,,) = _newRandomOperator(); + (ctx.avs,) = _newRandomAVS(); + + // Handle validator setup and slashing + _handleValidatorSetupAndSlashing(ctx); + + // Handle delegation and operator setup + _handleDelegationAndOperatorSetup(ctx); + + // Handle EigenLayer slashing + _handleEigenLayerSlashing(ctx); + + // Handle withdrawal + _handleWithdrawal(ctx); + } + + function _handleValidatorSetupAndSlashing(TestContext memory ctx) internal { + // Create and verify validators + (ctx.validators,) = ctx.staker.startValidators(); + beaconChain.advanceEpoch_NoRewards(); + ctx.staker.verifyWithdrawalCredentials(ctx.validators); + + // Slash validators and record via checkpoint + uint40[] memory slashedValidators = _chooseSubset(ctx.validators); + uint64 slashedGwei = beaconChain.slashValidators(slashedValidators); + beaconChain.advanceEpoch_NoRewards(); + + ctx.staker.startCheckpoint(); + console.log("Active validator count after starting checkpoint:", ctx.staker.pod().activeValidatorCount()); + ctx.staker.completeCheckpoint(); + console.log("Active validator count after completing checkpoint:", ctx.staker.pod().activeValidatorCount()); + check_CompleteCheckpoint_WithSlashing_HandleRoundDown_State(ctx.staker, slashedValidators, slashedGwei); + } + + function _handleDelegationAndOperatorSetup(TestContext memory ctx) internal { + // Handle delegation + ctx.staker.delegateTo(ctx.operator); + check_Delegation_State( + ctx.staker, + ctx.operator, + ctx.strategies, + _getStakerDepositShares(ctx.staker, ctx.strategies) + ); + + // Setup operator set + ctx.operatorSet = ctx.avs.createOperatorSet(ctx.strategies); + ctx.operator.registerForOperatorSet(ctx.operatorSet); + + ctx.allocateParams = ctx.operator.modifyAllocations( + ctx.operatorSet, + _randMagnitudes({sum: 1 ether, len: ctx.strategies.length}) + ); + _rollBlocksForCompleteAllocation(ctx.operator, ctx.operatorSet, ctx.strategies); + } + + function _handleEigenLayerSlashing(TestContext memory ctx) internal { + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(ctx.operatorSet); + + ctx.slashingParams = ctx.avs.slashOperator( + ctx.operator, + ctx.operatorSet.id, + strategiesToSlash, + wadsToSlash + ); + + assert_Snap_Allocations_Slashed( + ctx.slashingParams, + ctx.operatorSet, + true, + "operator allocations should be slashed" + ); + assert_Snap_Unchanged_StakerDepositShares( + ctx.staker, + "staker deposit shares should be unchanged after slashing" + ); + assert_Snap_StakerWithdrawableShares_AfterSlash( + ctx.staker, + ctx.allocateParams, + ctx.slashingParams, + "staker deposit shares should be slashed" + ); + } + + function _handleWithdrawal(TestContext memory ctx) internal { + EigenPod pod = ctx.staker.pod(); + console.log("Before checkpoint:"); + console.log("Restaked execution layer gwei:", pod.withdrawableRestakedExecutionLayerGwei()); + console.log("Total shares:", eigenPodManager.stakerDepositShares(address(ctx.staker), beaconChainETHStrategy)); + + // Advance beacon chain epoch + beaconChain.advanceEpoch_NoRewards(); + + // checkpoint + //ctx.staker.startCheckpoint(); + //ctx.staker.completeCheckpoint(); + + console.log("After checkpoint:"); + console.log("Restaked execution layer gwei:", pod.withdrawableRestakedExecutionLayerGwei()); + console.log("Total shares:", eigenPodManager.stakerDepositShares(address(ctx.staker), beaconChainETHStrategy)); + + // Queue withdrawal + IDelegationManagerTypes.Withdrawal[] memory withdrawals = ctx.staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + + console.log("After undelegate:"); + console.log("withdrawals length:", withdrawals.length); + for (uint i = 0; i < withdrawals.length; i++) { + console.log("Is pending?", delegationManager.pendingWithdrawals(withdrawalRoots[i])); + } + + // Complete withdrawals after delay + _rollBlocksForCompleteWithdrawals(withdrawals); + + for (uint256 i = 0; i < withdrawals.length; i++) { + console.log("Attempting to complete withdrawal", i); + console.log("Is pending before completion?", delegationManager.pendingWithdrawals(withdrawalRoots[i])); + + uint[] memory expectedTokens = _calculateExpectedTokens( + withdrawals[i].strategies, + withdrawals[i].scaledShares + ); + + for (uint256 i = 0; i < expectedTokens.length; i++) { + console.log(expectedTokens[i]); + } + IERC20[] memory tokens = ctx.staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State_AfterSlash( + ctx.staker, + ctx.operator, + withdrawals[i], + ctx.allocateParams, + ctx.slashingParams, + expectedTokens + ); + } + + // Final state checks + assert_HasNoDelegatableShares(ctx.staker, "staker should have withdrawn all shares"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be completed"); + } + +} \ No newline at end of file diff --git a/src/test/integration/tests/WithdrawalSlashing_Combined.sol b/src/test/integration/tests/WithdrawalSlashing_Combined.sol new file mode 100644 index 000000000..b91fb550b --- /dev/null +++ b/src/test/integration/tests/WithdrawalSlashing_Combined.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/IntegrationChecks.t.sol"; +import "src/test/integration/users/User.t.sol"; + +contract Integration_WithdrawalSlashing_Combined is IntegrationCheckUtils { + // Helper struct to reduce stack variables + struct TestContext { + User staker; + User operator; + IStrategy[] strategies; + uint256[] tokenBalances; + uint40[] validators; + bytes32[] withdrawalRoots; + IDelegationManagerTypes.Withdrawal[] withdrawals; + uint64 slashedGwei; + } + function testFuzz_deposit_delegate_queueWithdrawal_slashBeacon_checkpoint(uint24 _random) public { + TestContext memory ctx; + + // Initial setup and configuration + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_ETH, + _userTypes: DEFAULT + }); + + _upgradeEigenLayerContracts(); + + // Initialize actors and store in context + (ctx.staker, ctx.strategies, ctx.tokenBalances) = _newRandomStaker(); + (ctx.operator,,) = _newRandomOperator(); + + // Handle validator setup and delegation + _handleValidatorSetupAndDelegation(ctx); + + // Queue withdrawal before slashing + _handleQueueWithdrawal(ctx); + + // Execute slashing while withdrawal is in queue + _handleBeaconChainSlashing(ctx); + + // Start a checkpoint to reflect slashing + _handlePostSlashingCheckpoint(ctx); + + // Complete the withdrawal and verify slashing was applied + _handleWithdrawalCompletion(ctx); + } + + function _handleValidatorSetupAndDelegation(TestContext memory ctx) internal { + // Create and verify validators + (ctx.validators,) = ctx.staker.startValidators(); + beaconChain.advanceEpoch_NoRewards(); + ctx.staker.verifyWithdrawalCredentials(ctx.validators); + + // Delegate to operator + ctx.staker.delegateTo(ctx.operator); + check_Delegation_State( + ctx.staker, + ctx.operator, + ctx.strategies, + _getStakerDepositShares(ctx.staker, ctx.strategies) + ); + } + + function _handleQueueWithdrawal(TestContext memory ctx) internal { + // Queue withdrawal by undelegating + ctx.withdrawals = ctx.staker.undelegate(); + ctx.withdrawalRoots = _getWithdrawalHashes(ctx.withdrawals); + + // Verify withdrawal state + assert_AllWithdrawalsPending(ctx.withdrawalRoots, "withdrawals should be pending"); + assert_ValidWithdrawalHashes(ctx.withdrawals, ctx.withdrawalRoots, "withdrawal hashes should be valid"); + } + + function _handleBeaconChainSlashing(TestContext memory ctx) internal { + // Choose subset of validators to slash + uint40[] memory slashedValidators = _chooseSubset(ctx.validators); + + // Execute slashing on beacon chain + ctx.slashedGwei = beaconChain.slashValidators(slashedValidators); + beaconChain.advanceEpoch_NoRewards(); + + console.log("Slashed amount (gwei)", ctx.slashedGwei); + } + + function _handlePostSlashingCheckpoint(TestContext memory ctx) internal { + // Start and complete checkpoint to reflect slashing + ctx.staker.startCheckpoint(); + ctx.staker.completeCheckpoint(); + console.log("Active validator count after completing checkpoint:", ctx.staker.pod().activeValidatorCount()); + + } + + function _handleWithdrawalCompletion(TestContext memory ctx) internal { + // Advance blocks to complete withdrawal + _rollBlocksForCompleteWithdrawals(ctx.withdrawals); + + // Complete each withdrawal and verify state + for (uint i = 0; i < ctx.withdrawals.length; i++) { + uint[] memory expectedTokens = _calculateExpectedTokens( + ctx.withdrawals[i].strategies, + ctx.withdrawals[i].scaledShares + ); + + IERC20[] memory tokens = ctx.staker.completeWithdrawalAsTokens(ctx.withdrawals[i]); + + check_Withdrawal_AsTokens_State( + ctx.staker, + ctx.operator, + ctx.withdrawals[i], + ctx.withdrawals[i].strategies, + ctx.withdrawals[i].scaledShares, + tokens, + expectedTokens + ); + } + + // Final checks + assert_HasNoDelegatableShares(ctx.staker, "staker should have no shares after withdrawal"); + assert_NoWithdrawalsPending(ctx.withdrawalRoots, "all withdrawals should be completed"); + } +} \ No newline at end of file From 84dbd2fc5ca3ee5c01e5ecafb0852431f1d90972 Mon Sep 17 00:00:00 2001 From: eigenmikem Date: Thu, 9 Jan 2025 13:07:17 -0500 Subject: [PATCH 2/3] Revert "Slashing integration tests (#1003)" (#1007) This reverts commit e945d8d37b16bef70081f9764d52eab294acd3b0. --- src/test/integration/IntegrationBase.t.sol | 62 +----- src/test/integration/IntegrationChecks.t.sol | 50 ----- ...egate_Allocate_Slash_Queue_Redeposit.t.sol | 97 ---------- .../integration/tests/Slashing_Combined.sol | 177 ------------------ .../tests/WithdrawalSlashing_Combined.sol | 124 ------------ 5 files changed, 1 insertion(+), 509 deletions(-) delete mode 100644 src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol delete mode 100644 src/test/integration/tests/Slashing_Combined.sol delete mode 100644 src/test/integration/tests/WithdrawalSlashing_Combined.sol diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 1df949a05..852352e91 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -186,36 +186,6 @@ abstract contract IntegrationBase is IntegrationDeployer { return result; } - - /// @dev Choose a random subset of validators (selects AT LEAST ONE but NOT ALL) - function _chooseSubset(uint40[] memory validators) internal returns (uint40[] memory) { - require(validators.length >= 2, "Need at least 2 validators to choose subset"); - - uint40[] memory result = new uint40[](validators.length); - uint newLen; - - uint rand = _randUint({ min: 1, max: validators.length ** 2 }); - for (uint i = 0; i < validators.length; i++) { - if (rand >> i & 1 == 1) { - result[newLen] = validators[i]; - newLen++; - } - } - - // If we picked all, remove one random validator - if (newLen == validators.length) { - uint indexToRemove = _randUint({ min: 0, max: validators.length - 1 }); - for (uint i = indexToRemove; i < newLen - 1; i++) { - result[i] = result[i + 1]; - } - newLen--; - } - - // Update array length - assembly { mstore(result, newLen) } - - return result; - } function _getTokenName(IERC20 token) internal view returns (string memory) { if (token == NATIVE_ETH) { @@ -591,11 +561,8 @@ abstract contract IntegrationBase is IntegrationDeployer { uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); } - console.log(prevShares[i]); - console.log(slashedShares); - console.log(curShares[i]); - assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1000, err); + assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1, err); } } @@ -1166,21 +1133,6 @@ abstract contract IntegrationBase is IntegrationDeployer { return (strategies.sort(), wadsToSlash); } - - function _strategiesAndWadsForFullSlash( - OperatorSet memory operatorSet - ) internal view returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) { - // Get list of all strategies in an operator set. - strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); - - wadsToSlash = new uint[](strategies.length); - - for (uint i; i < strategies.length; ++i) { - wadsToSlash[i] = 1 ether; - } - - return (strategies.sort(), wadsToSlash); - } function _randMagnitudes(uint64 sum, uint256 len) internal returns (uint64[] memory magnitudes) { magnitudes = new uint64[](len); @@ -1199,18 +1151,6 @@ abstract contract IntegrationBase is IntegrationDeployer { } } - function _maxMagnitudes(OperatorSet memory operatorSet, User operator) internal view returns (uint64[] memory magnitudes) { - IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); - uint256 len = strategies.length; - magnitudes = new uint64[](len); - - if (len == 0) return magnitudes; - - for (uint256 i; i < len; ++i) { - magnitudes[i] = allocationManager.getMaxMagnitude(address(operator), strategies[i]); - } - } - function _randWithdrawal( IStrategy[] memory strategies, uint[] memory shares diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index cd0ce115d..c01db4e72 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -390,56 +390,6 @@ contract IntegrationCheckUtils is IntegrationBase { } } - function check_Withdrawal_AsTokens_State_AfterBeaconSlash( - 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 (uint 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)) { - uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; - - expectedTokens[i] -= expectedTokens[i] - .mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); - - uint256 max = allocationManager.getMaxMagnitude(address(operator), strat); - - withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max); - - // Round down to the nearest gwei for beaconchain ETH strategy. - if (isBeaconChainETHStrategy) { - expectedTokens[i] -= expectedTokens[i] % 1 gwei; - } - } - } - - // Common checks - assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); - - // assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); - assert_Snap_Unchanged_StakerDepositShares(staker, "staker shares should not have changed"); - assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.scaledShares, "strategies should have total shares decremented"); - - // Checks specific to an operator that the Staker has delegated to - if (operator != User(payable(0))) { - if (operator != staker) { - assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); - } - assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); - } - } - function check_Withdrawal_AsShares_State_AfterSlash( User staker, User operator, diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol deleted file mode 100644 index fcfc65b44..000000000 --- a/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "src/test/integration/IntegrationChecks.t.sol"; -import "src/test/integration/users/User.t.sol"; -import {console} from "forge-std/console.sol"; - -contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is IntegrationCheckUtils, IDelegationManagerTypes { - - function testFuzz_deposit_delegate_allocate_fullSlash_queue_complete_redeposit( - uint24 _random - ) public { - _configRand({_randomSeed: _random, _assetTypes: HOLDS_LST, _userTypes: DEFAULT}); - _upgradeEigenLayerContracts(); - - (User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker(); - (User operator,,) = _newRandomOperator(); - (AVS avs,) = _newRandomAVS(); - - uint256[] memory tokensToDeposit = new uint256[](tokenBalances.length); - uint256[] memory numTokensRemaining = new uint256[](tokenBalances.length); - for (uint256 i = 0; i < tokenBalances.length; i++) { - tokensToDeposit[i] = tokenBalances[i]/2; - numTokensRemaining[i] = tokenBalances[i] - tokensToDeposit[i]; - } - - uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit); - - // 1. Deposit Into Strategies - staker.depositIntoEigenlayer(strategies, tokensToDeposit); - check_Deposit_State_PartialDeposit(staker, strategies, shares, numTokensRemaining); - - // 2. Delegate to operator - staker.delegateTo(operator); - check_Delegation_State(staker, operator, strategies, shares); - - // Create operator set and register operator - OperatorSet memory operatorSet = avs.createOperatorSet(strategies); - operator.registerForOperatorSet(operatorSet); - - // 3. Allocate to operator set - IAllocationManagerTypes.AllocateParams memory allocateParams = - operator.modifyAllocations(operatorSet, _maxMagnitudes(operatorSet, operator)); - - assert_Snap_Allocations_Modified( - operator, - allocateParams, - false, - "operator allocations should be updated before delay" - ); - - _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); - - assert_Snap_Allocations_Modified( - operator, - allocateParams, - true, - "operator allocations should be updated after delay" - ); - - // 4. Fully slash operator - IAllocationManagerTypes.SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _strategiesAndWadsForFullSlash(operatorSet); - - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); - } - - // 5. Undelegate from an operator - IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); - bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - - // 6. Complete withdrawal - _rollBlocksForCompleteWithdrawals(withdrawals); - for (uint256 i = 0; i < withdrawals.length; ++i) { - uint256[] memory expectedTokens = - _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); - for (uint256 i = 0; i < expectedTokens.length; i++) { - } - staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); - } - - // 7. Redeposit - shares = _calculateExpectedShares(strategies, numTokensRemaining); - staker.depositIntoEigenlayer(strategies, numTokensRemaining); - check_Deposit_State(staker, strategies, shares); - - // Final state checks - assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit"); - assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); - } -} \ No newline at end of file diff --git a/src/test/integration/tests/Slashing_Combined.sol b/src/test/integration/tests/Slashing_Combined.sol deleted file mode 100644 index fff647ba4..000000000 --- a/src/test/integration/tests/Slashing_Combined.sol +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "src/test/integration/IntegrationChecks.t.sol"; -import "src/test/integration/users/User.t.sol"; - -contract Integration_Slashing_Combined is IntegrationCheckUtils { - // Helper struct to reduce stack variables - struct TestContext { - User staker; - User operator; - AVS avs; - IStrategy[] strategies; - uint256[] tokenBalances; - uint40[] validators; - OperatorSet operatorSet; - IAllocationManagerTypes.AllocateParams allocateParams; - IAllocationManagerTypes.SlashingParams slashingParams; - } - - function testFuzz_deposit_slashBeacon_delegate_slashEigen_withdraw(uint24 _random) public { - TestContext memory ctx; - - // Initial setup and configuration - _configRand({ - _randomSeed: _random, - _assetTypes: HOLDS_ETH, - _userTypes: DEFAULT - }); - - _upgradeEigenLayerContracts(); - - // Initialize actors and store in context - (ctx.staker, ctx.strategies, ctx.tokenBalances) = _newRandomStaker(); - (ctx.operator,,) = _newRandomOperator(); - (ctx.avs,) = _newRandomAVS(); - - // Handle validator setup and slashing - _handleValidatorSetupAndSlashing(ctx); - - // Handle delegation and operator setup - _handleDelegationAndOperatorSetup(ctx); - - // Handle EigenLayer slashing - _handleEigenLayerSlashing(ctx); - - // Handle withdrawal - _handleWithdrawal(ctx); - } - - function _handleValidatorSetupAndSlashing(TestContext memory ctx) internal { - // Create and verify validators - (ctx.validators,) = ctx.staker.startValidators(); - beaconChain.advanceEpoch_NoRewards(); - ctx.staker.verifyWithdrawalCredentials(ctx.validators); - - // Slash validators and record via checkpoint - uint40[] memory slashedValidators = _chooseSubset(ctx.validators); - uint64 slashedGwei = beaconChain.slashValidators(slashedValidators); - beaconChain.advanceEpoch_NoRewards(); - - ctx.staker.startCheckpoint(); - console.log("Active validator count after starting checkpoint:", ctx.staker.pod().activeValidatorCount()); - ctx.staker.completeCheckpoint(); - console.log("Active validator count after completing checkpoint:", ctx.staker.pod().activeValidatorCount()); - check_CompleteCheckpoint_WithSlashing_HandleRoundDown_State(ctx.staker, slashedValidators, slashedGwei); - } - - function _handleDelegationAndOperatorSetup(TestContext memory ctx) internal { - // Handle delegation - ctx.staker.delegateTo(ctx.operator); - check_Delegation_State( - ctx.staker, - ctx.operator, - ctx.strategies, - _getStakerDepositShares(ctx.staker, ctx.strategies) - ); - - // Setup operator set - ctx.operatorSet = ctx.avs.createOperatorSet(ctx.strategies); - ctx.operator.registerForOperatorSet(ctx.operatorSet); - - ctx.allocateParams = ctx.operator.modifyAllocations( - ctx.operatorSet, - _randMagnitudes({sum: 1 ether, len: ctx.strategies.length}) - ); - _rollBlocksForCompleteAllocation(ctx.operator, ctx.operatorSet, ctx.strategies); - } - - function _handleEigenLayerSlashing(TestContext memory ctx) internal { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(ctx.operatorSet); - - ctx.slashingParams = ctx.avs.slashOperator( - ctx.operator, - ctx.operatorSet.id, - strategiesToSlash, - wadsToSlash - ); - - assert_Snap_Allocations_Slashed( - ctx.slashingParams, - ctx.operatorSet, - true, - "operator allocations should be slashed" - ); - assert_Snap_Unchanged_StakerDepositShares( - ctx.staker, - "staker deposit shares should be unchanged after slashing" - ); - assert_Snap_StakerWithdrawableShares_AfterSlash( - ctx.staker, - ctx.allocateParams, - ctx.slashingParams, - "staker deposit shares should be slashed" - ); - } - - function _handleWithdrawal(TestContext memory ctx) internal { - EigenPod pod = ctx.staker.pod(); - console.log("Before checkpoint:"); - console.log("Restaked execution layer gwei:", pod.withdrawableRestakedExecutionLayerGwei()); - console.log("Total shares:", eigenPodManager.stakerDepositShares(address(ctx.staker), beaconChainETHStrategy)); - - // Advance beacon chain epoch - beaconChain.advanceEpoch_NoRewards(); - - // checkpoint - //ctx.staker.startCheckpoint(); - //ctx.staker.completeCheckpoint(); - - console.log("After checkpoint:"); - console.log("Restaked execution layer gwei:", pod.withdrawableRestakedExecutionLayerGwei()); - console.log("Total shares:", eigenPodManager.stakerDepositShares(address(ctx.staker), beaconChainETHStrategy)); - - // Queue withdrawal - IDelegationManagerTypes.Withdrawal[] memory withdrawals = ctx.staker.undelegate(); - bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - - console.log("After undelegate:"); - console.log("withdrawals length:", withdrawals.length); - for (uint i = 0; i < withdrawals.length; i++) { - console.log("Is pending?", delegationManager.pendingWithdrawals(withdrawalRoots[i])); - } - - // Complete withdrawals after delay - _rollBlocksForCompleteWithdrawals(withdrawals); - - for (uint256 i = 0; i < withdrawals.length; i++) { - console.log("Attempting to complete withdrawal", i); - console.log("Is pending before completion?", delegationManager.pendingWithdrawals(withdrawalRoots[i])); - - uint[] memory expectedTokens = _calculateExpectedTokens( - withdrawals[i].strategies, - withdrawals[i].scaledShares - ); - - for (uint256 i = 0; i < expectedTokens.length; i++) { - console.log(expectedTokens[i]); - } - IERC20[] memory tokens = ctx.staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash( - ctx.staker, - ctx.operator, - withdrawals[i], - ctx.allocateParams, - ctx.slashingParams, - expectedTokens - ); - } - - // Final state checks - assert_HasNoDelegatableShares(ctx.staker, "staker should have withdrawn all shares"); - assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be completed"); - } - -} \ No newline at end of file diff --git a/src/test/integration/tests/WithdrawalSlashing_Combined.sol b/src/test/integration/tests/WithdrawalSlashing_Combined.sol deleted file mode 100644 index b91fb550b..000000000 --- a/src/test/integration/tests/WithdrawalSlashing_Combined.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "src/test/integration/IntegrationChecks.t.sol"; -import "src/test/integration/users/User.t.sol"; - -contract Integration_WithdrawalSlashing_Combined is IntegrationCheckUtils { - // Helper struct to reduce stack variables - struct TestContext { - User staker; - User operator; - IStrategy[] strategies; - uint256[] tokenBalances; - uint40[] validators; - bytes32[] withdrawalRoots; - IDelegationManagerTypes.Withdrawal[] withdrawals; - uint64 slashedGwei; - } - function testFuzz_deposit_delegate_queueWithdrawal_slashBeacon_checkpoint(uint24 _random) public { - TestContext memory ctx; - - // Initial setup and configuration - _configRand({ - _randomSeed: _random, - _assetTypes: HOLDS_ETH, - _userTypes: DEFAULT - }); - - _upgradeEigenLayerContracts(); - - // Initialize actors and store in context - (ctx.staker, ctx.strategies, ctx.tokenBalances) = _newRandomStaker(); - (ctx.operator,,) = _newRandomOperator(); - - // Handle validator setup and delegation - _handleValidatorSetupAndDelegation(ctx); - - // Queue withdrawal before slashing - _handleQueueWithdrawal(ctx); - - // Execute slashing while withdrawal is in queue - _handleBeaconChainSlashing(ctx); - - // Start a checkpoint to reflect slashing - _handlePostSlashingCheckpoint(ctx); - - // Complete the withdrawal and verify slashing was applied - _handleWithdrawalCompletion(ctx); - } - - function _handleValidatorSetupAndDelegation(TestContext memory ctx) internal { - // Create and verify validators - (ctx.validators,) = ctx.staker.startValidators(); - beaconChain.advanceEpoch_NoRewards(); - ctx.staker.verifyWithdrawalCredentials(ctx.validators); - - // Delegate to operator - ctx.staker.delegateTo(ctx.operator); - check_Delegation_State( - ctx.staker, - ctx.operator, - ctx.strategies, - _getStakerDepositShares(ctx.staker, ctx.strategies) - ); - } - - function _handleQueueWithdrawal(TestContext memory ctx) internal { - // Queue withdrawal by undelegating - ctx.withdrawals = ctx.staker.undelegate(); - ctx.withdrawalRoots = _getWithdrawalHashes(ctx.withdrawals); - - // Verify withdrawal state - assert_AllWithdrawalsPending(ctx.withdrawalRoots, "withdrawals should be pending"); - assert_ValidWithdrawalHashes(ctx.withdrawals, ctx.withdrawalRoots, "withdrawal hashes should be valid"); - } - - function _handleBeaconChainSlashing(TestContext memory ctx) internal { - // Choose subset of validators to slash - uint40[] memory slashedValidators = _chooseSubset(ctx.validators); - - // Execute slashing on beacon chain - ctx.slashedGwei = beaconChain.slashValidators(slashedValidators); - beaconChain.advanceEpoch_NoRewards(); - - console.log("Slashed amount (gwei)", ctx.slashedGwei); - } - - function _handlePostSlashingCheckpoint(TestContext memory ctx) internal { - // Start and complete checkpoint to reflect slashing - ctx.staker.startCheckpoint(); - ctx.staker.completeCheckpoint(); - console.log("Active validator count after completing checkpoint:", ctx.staker.pod().activeValidatorCount()); - - } - - function _handleWithdrawalCompletion(TestContext memory ctx) internal { - // Advance blocks to complete withdrawal - _rollBlocksForCompleteWithdrawals(ctx.withdrawals); - - // Complete each withdrawal and verify state - for (uint i = 0; i < ctx.withdrawals.length; i++) { - uint[] memory expectedTokens = _calculateExpectedTokens( - ctx.withdrawals[i].strategies, - ctx.withdrawals[i].scaledShares - ); - - IERC20[] memory tokens = ctx.staker.completeWithdrawalAsTokens(ctx.withdrawals[i]); - - check_Withdrawal_AsTokens_State( - ctx.staker, - ctx.operator, - ctx.withdrawals[i], - ctx.withdrawals[i].strategies, - ctx.withdrawals[i].scaledShares, - tokens, - expectedTokens - ); - } - - // Final checks - assert_HasNoDelegatableShares(ctx.staker, "staker should have no shares after withdrawal"); - assert_NoWithdrawalsPending(ctx.withdrawalRoots, "all withdrawals should be completed"); - } -} \ No newline at end of file From 8528bc42543ad6c944a8c3d0c91bd0e75f7e0d75 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Sat, 11 Jan 2025 19:07:24 -0500 Subject: [PATCH 3/3] refactor: optimize reentrancy guard --- src/contracts/core/AVSDirectory.sol | 8 +++-- src/contracts/core/AllocationManager.sol | 4 +-- src/contracts/core/DelegationManager.sol | 9 +++-- src/contracts/core/RewardsCoordinator.sol | 4 +-- src/contracts/core/StrategyManager.sol | 4 +-- src/contracts/mixins/ReentrancyGuardMixin.sol | 34 +++++++++++++++++++ src/contracts/pods/EigenPod.sol | 5 +-- src/contracts/pods/EigenPodManager.sol | 4 +-- 8 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 src/contracts/mixins/ReentrancyGuardMixin.sol diff --git a/src/contracts/core/AVSDirectory.sol b/src/contracts/core/AVSDirectory.sol index 0e9af8741..8e932dfd8 100644 --- a/src/contracts/core/AVSDirectory.sol +++ b/src/contracts/core/AVSDirectory.sol @@ -3,10 +3,12 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; -import "../mixins/SignatureUtils.sol"; import "../permissions/Pausable.sol"; + +import "../mixins/ReentrancyGuardMixin.sol"; +import "../mixins/SignatureUtils.sol"; + import "./AVSDirectoryStorage.sol"; contract AVSDirectory is @@ -14,7 +16,7 @@ contract AVSDirectory is OwnableUpgradeable, Pausable, AVSDirectoryStorage, - ReentrancyGuardUpgradeable, + ReentrancyGuardMixin, SignatureUtils { /** diff --git a/src/contracts/core/AllocationManager.sol b/src/contracts/core/AllocationManager.sol index 2c77cc02b..0ce9852a9 100644 --- a/src/contracts/core/AllocationManager.sol +++ b/src/contracts/core/AllocationManager.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; +import "../mixins/ReentrancyGuardMixin.sol"; import "../mixins/PermissionControllerMixin.sol"; import "../permissions/Pausable.sol"; import "../libraries/SlashingLib.sol"; @@ -16,7 +16,7 @@ contract AllocationManager is OwnableUpgradeable, Pausable, AllocationManagerStorage, - ReentrancyGuardUpgradeable, + ReentrancyGuardMixin, PermissionControllerMixin { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index 2d5350b40..092e61b3b 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -3,13 +3,16 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; +import "../permissions/Pausable.sol"; + +import "../mixins/ReentrancyGuardMixin.sol"; import "../mixins/SignatureUtils.sol"; import "../mixins/PermissionControllerMixin.sol"; -import "../permissions/Pausable.sol"; + import "../libraries/SlashingLib.sol"; import "../libraries/Snapshots.sol"; + import "./DelegationManagerStorage.sol"; /** @@ -27,7 +30,7 @@ contract DelegationManager is OwnableUpgradeable, Pausable, DelegationManagerStorage, - ReentrancyGuardUpgradeable, + ReentrancyGuardMixin, SignatureUtils, PermissionControllerMixin { diff --git a/src/contracts/core/RewardsCoordinator.sol b/src/contracts/core/RewardsCoordinator.sol index 13e2569d5..bb3c32417 100644 --- a/src/contracts/core/RewardsCoordinator.sol +++ b/src/contracts/core/RewardsCoordinator.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../mixins/ReentrancyGuardMixin.sol"; import "../libraries/Merkle.sol"; import "../permissions/Pausable.sol"; import "./RewardsCoordinatorStorage.sol"; @@ -24,7 +24,7 @@ contract RewardsCoordinator is Initializable, OwnableUpgradeable, Pausable, - ReentrancyGuardUpgradeable, + ReentrancyGuardMixin, RewardsCoordinatorStorage, PermissionControllerMixin { diff --git a/src/contracts/core/StrategyManager.sol b/src/contracts/core/StrategyManager.sol index 3f986fede..67dee9cda 100644 --- a/src/contracts/core/StrategyManager.sol +++ b/src/contracts/core/StrategyManager.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../mixins/ReentrancyGuardMixin.sol"; import "../mixins/SignatureUtils.sol"; import "../interfaces/IEigenPodManager.sol"; import "../permissions/Pausable.sol"; @@ -23,7 +23,7 @@ import "./StrategyManagerStorage.sol"; contract StrategyManager is Initializable, OwnableUpgradeable, - ReentrancyGuardUpgradeable, + ReentrancyGuardMixin, Pausable, StrategyManagerStorage, SignatureUtils diff --git a/src/contracts/mixins/ReentrancyGuardMixin.sol b/src/contracts/mixins/ReentrancyGuardMixin.sol new file mode 100644 index 000000000..f58600ae8 --- /dev/null +++ b/src/contracts/mixins/ReentrancyGuardMixin.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +/** + * @dev Variant of {ReentrancyGuard} that uses transient storage. + * + * NOTE: This variant only works on networks where EIP-1153 is available. + */ +abstract contract ReentrancyGuardMixin { + // keccak256(abi.encode(uint256(keccak256("eigenlayer.storage.ReentrancyGuardMixin")) - 1)) & ~bytes32(uint256(0xff)) + uint256 private constant _REENTRANCY_GUARD_SLOT = + 0x61bb794ad7a504b3613420bc192fca11ecb0ea36bf99527d17aa6bd66a5db500; + + /// @dev Unauthorized reentrant call. + error Reentrancy(); + + /// @dev Guards a function from reentrancy. + modifier nonReentrant() virtual { + uint256 s = _REENTRANCY_GUARD_SLOT; + /// @solidity memory-safe-assembly + assembly { + if tload(s) { + mstore(0x00, 0xab143c06) // `Reentrancy()`. + revert(0x1c, 0x04) + } + tstore(s, address()) + } + _; + /// @solidity memory-safe-assembly + assembly { + tstore(s, 0) + } + } +} \ No newline at end of file diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index 1066b2fbf..0e8884d61 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.27; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../mixins/ReentrancyGuardMixin.sol"; + import "../libraries/BeaconChainProofs.sol"; import "../libraries/BytesLib.sol"; @@ -23,7 +24,7 @@ import "./EigenPodStorage.sol"; * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose * to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts */ -contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants, EigenPodStorage { +contract EigenPod is Initializable, ReentrancyGuardMixin, EigenPodPausingConstants, EigenPodStorage { using BytesLib for bytes; using SafeERC20 for IERC20; using BeaconChainProofs for *; diff --git a/src/contracts/pods/EigenPodManager.sol b/src/contracts/pods/EigenPodManager.sol index c15b60f8f..1d454789b 100644 --- a/src/contracts/pods/EigenPodManager.sol +++ b/src/contracts/pods/EigenPodManager.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.27; import "@openzeppelin/contracts/utils/Create2.sol"; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; +import "../mixins/ReentrancyGuardMixin.sol"; import "../libraries/SlashingLib.sol"; import "../permissions/Pausable.sol"; import "./EigenPodPausingConstants.sol"; @@ -27,7 +27,7 @@ contract EigenPodManager is Pausable, EigenPodPausingConstants, EigenPodManagerStorage, - ReentrancyGuardUpgradeable + ReentrancyGuardMixin { using SlashingLib for *; using Math for *;