diff --git a/.github/workflows/testinparallel.yml b/.github/workflows/testinparallel.yml index 2d1fb3395c..a0bd90fe39 100644 --- a/.github/workflows/testinparallel.yml +++ b/.github/workflows/testinparallel.yml @@ -47,4 +47,5 @@ jobs: run: forge test --match-path src/test/${{ matrix.file }} --no-match-contract FFI env: RPC_MAINNET: ${{ secrets.RPC_MAINNET }} + RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }} CHAIN_ID: ${{ secrets.CHAIN_ID }} diff --git a/foundry.toml b/foundry.toml index 2d98e3d271..a2431046a6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -18,6 +18,7 @@ solc_version = '0.8.12' [rpc_endpoints] mainnet = "${RPC_MAINNET}" +holesky = "${RPC_HOLESKY}" [fmt] bracket_spacing = false diff --git a/script/configs/holesky/Holesky_current_deployment.config.json b/script/configs/holesky/Holesky_current_deployment.config.json new file mode 100644 index 0000000000..a173ccb316 --- /dev/null +++ b/script/configs/holesky/Holesky_current_deployment.config.json @@ -0,0 +1,55 @@ +{ + "addresses": { + "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf", + "avsDirectoryImplementation": "0xEF5BA995Bc7722fd1e163edF8Dc09375de3d3e3a", + "baseStrategyImplementation": "0xFb83e1D133D0157775eC4F19Ff81478Df1103305", + "beaconOracle": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25", + "delayedWithdrawalRouter": "0x642c646053eaf2254f088e9019ACD73d9AE0FA32", + "delayedWithdrawalRouterImplementation": "0xcE8b8D99773a718423F8040a6e52c06a4ce63407", + "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7", + "delegationManagerImplementation": "0x83f8F8f0BB125F7870F6bfCf76853f874C330D76", + "eigenLayerPauserReg": "0x85Ef7299F8311B25642679edBF02B62FA2212F06", + "eigenLayerProxyAdmin": "0xDB023566064246399b4AE851197a97729C93A6cf", + "eigenPodBeacon": "0x7261C2bd75a7ACE1762f6d7FAe8F63215581832D", + "eigenPodImplementation": "0xe98f9298344527608A1BCC23907B8145F9Cb641c", + "eigenPodManager": "0x30770d7E3e71112d7A6b7259542D1f680a70e315", + "eigenPodManagerImplementation": "0x5265C162f7d5F3fE3175a78828ab16bf5E324a7B", + "emptyContract": "0x9690d52B1Ce155DB2ec5eCbF5a262ccCc7B3A6D2", + "slasher": "0xcAe751b75833ef09627549868A04E32679386e7C", + "slasherImplementation": "0x99715D255E34a39bE9943b82F281CA734bcF345A", + "strategies": { + "WETH": "0x80528D6e9A2BAbFc766965E0E26d5aB08D9CFaF9", + "rETH": "0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0", + "stETH": "0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3", + "lsETH": "0x05037A81BD7B4C9E0F7B430f1F2A22c31a2FD943", + "frxETH": "0x15F70a41Afe34020B3B16079010D3e88c4A85daf", + "ETHx": "0x31B6F59e1627cEfC9fA174aD03859fC337666af7", + "osETH": "0x46281E3B7fDcACdBa44CADf069a94a588Fd4C6Ef", + "cbETH": "0x70EB4D3c164a6B4A5f908D4FBb5a9cAfFb66bAB6" + }, + "strategyAddresses": [ + "0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0", + "0x80528D6e9A2BAbFc766965E0E26d5aB08D9CFaF9", + "0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3", + "0x05037A81BD7B4C9E0F7B430f1F2A22c31a2FD943", + "0x15F70a41Afe34020B3B16079010D3e88c4A85daf", + "0x31B6F59e1627cEfC9fA174aD03859fC337666af7", + "0x46281E3B7fDcACdBa44CADf069a94a588Fd4C6Ef", + "0x70EB4D3c164a6B4A5f908D4FBb5a9cAfFb66bAB6" + ], + "strategyManager": "0xdfB5f6CE42aAA7830E94ECFCcAd411beF4d4D5b6", + "strategyManagerImplementation": "0x59f766A603C53f3AC8Be43bBe158c1519b193a18" + }, + "numStrategies": 8, + "chainInfo": { + "chainId": 17000, + "deploymentBlock": 1167041 + }, + "parameters": { + "communityMultisig": "0xCb8d2f9e55Bc7B1FA9d089f9aC80C583D2BDD5F7", + "executorMultisig": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", + "operationsMultisig": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d", + "pauserMultisig": "0x53410249ec7d3a3F9F1ba3912D50D6A3Df6d10A7", + "timelock": "0xcF19CE0561052a7A7Ff21156730285997B350A7D" + } +} diff --git a/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json b/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json index 8cd29ce250..6cd5bc2c39 100644 --- a/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json +++ b/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json @@ -9,23 +9,11 @@ "executorMultisig": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348", "timelock": "0xcF19CE0561052a7A7Ff21156730285997B350A7D" }, - "numStrategies": 2, "strategies": { - "numStrategies": 2, + "numStrategies": 0, "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935, "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935, - "strategiesToDeploy": [ - { - "token_address": "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", - "token_name": "Liquid staked Ether 2.0", - "token_symbol": "stETH" - }, - { - "token_address": "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1", - "token_name": "Rocket Pool ETH", - "token_symbol": "rETH" - } - ] + "strategiesToDeploy": [] }, "strategyManager": { "init_strategy_whitelister": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d", diff --git a/script/configs/mainnet/Mainnet_current_deployment.config.json b/script/configs/mainnet/Mainnet_current_deployment.config.json new file mode 100644 index 0000000000..e69821b4b0 --- /dev/null +++ b/script/configs/mainnet/Mainnet_current_deployment.config.json @@ -0,0 +1,63 @@ +{ + "addresses": { + "avsDirectory": "0x0000000000000000000000000000000000000000", + "avsDirectoryImplementation": "0x0000000000000000000000000000000000000000", + "beaconOracle": "0x343907185b71aDF0eBa9567538314396aa985442", + "baseStrategyImplementation": "0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3", + "delayedWithdrawalRouter": "0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8", + "delayedWithdrawalRouterImplementation": "0x44Bcb0E01CD0C5060D4Bb1A07b42580EF983E2AF", + "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", + "delegationManagerImplementation": "0xf97E97649Da958d290e84E6D571c32F4b7F475e4", + "eigenLayerPauserReg": "0x0c431C66F4dE941d089625E5B423D00707977060", + "eigenLayerProxyAdmin": "0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444", + "eigenPodBeacon": "0x5a2a4F2F3C18f09179B6703e63D9eDD165909073", + "eigenPodImplementation": "0x5c86e9609fbBc1B754D0FD5a4963Fdf0F5b99dA7", + "eigenPodManager": "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338", + "eigenPodManagerImplementation": "0xEB86a5c40FdE917E6feC440aBbCDc80E3862e111", + "emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9", + "slasher": "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd", + "slasherImplementation": "0xef31c292801f24f16479DD83197F1E6AeBb8d6d8", + "strategyManager": "0x858646372CC42E1A627fcE94aa7A7033e7CF075A", + "strategyManagerImplementation": "0x5d25EEf8CfEdaA47d31fE2346726dE1c21e342Fb", + "strategies": { + "stETH": "0x93c4b944D05dfe6df7645A86cd2206016c51564D", + "rETH": "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2", + "cbETH": "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc", + "ETHx": "0x9d7eD45EE2E8FC5482fa2428f15C971e6369011d", + "ankrETH": "0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff", + "oETH": "0xa4C637e0F704745D182e4D38cAb7E7485321d059", + "osETH": "0x57ba429517c3473B6d34CA9aCd56c0e735b94c02", + "swETH": "0x0Fe4F44beE93503346A3Ac9EE5A26b130a5796d6", + "wBETH": "0x7CA911E83dabf90C90dD3De5411a10F1A6112184", + "sfrxETH": "0x8CA7A5d6f3acd3A7A8bC468a8CD0FB14B6BD28b6", + "lsETH": "0xAe60d8180437b5C34bB956822ac2710972584473", + "mETH": "0x298aFB19A105D59E74658C4C334Ff360BadE6dd2" + }, + "strategyAddresses": [ + "0x93c4b944D05dfe6df7645A86cd2206016c51564D", + "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2", + "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc", + "0x9d7eD45EE2E8FC5482fa2428f15C971e6369011d", + "0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff", + "0xa4C637e0F704745D182e4D38cAb7E7485321d059", + "0x57ba429517c3473B6d34CA9aCd56c0e735b94c02", + "0x0Fe4F44beE93503346A3Ac9EE5A26b130a5796d6", + "0x7CA911E83dabf90C90dD3De5411a10F1A6112184", + "0x8CA7A5d6f3acd3A7A8bC468a8CD0FB14B6BD28b6", + "0xAe60d8180437b5C34bB956822ac2710972584473", + "0x298aFB19A105D59E74658C4C334Ff360BadE6dd2" + ] + }, + "numStrategies": 12, + "chainInfo": { + "chainId": 1, + "deploymentBlock": 17445559 + }, + "parameters": { + "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598", + "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111", + "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90", + "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390", + "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF" + } +} diff --git a/script/configs/mainnet/Mainnet_current_eigenPods.config.json b/script/configs/mainnet/Mainnet_current_eigenPods.config.json new file mode 100644 index 0000000000..73f8dfb28d --- /dev/null +++ b/script/configs/mainnet/Mainnet_current_eigenPods.config.json @@ -0,0 +1,46 @@ +{ + "chainInfo": { + "chainId": 1, + "deploymentBlock": 19285000 + }, + "eigenPods": { + "multiValidators": [ + "0x2641c2ded63a0c640629f5edf1189e0f53c06561", + "0xa345dcb63f984ed1c6d1a8901e0cdbd13b2b4d19", + "0x7fb3d3801883341a02ddd1beb2e624a278e87930", + "0x2f5cb052d397d0b628299a9d4cfc49a5019c4170", + "0xdb678e1056acd7db74507b921a6295c3d586ece9" + ], + "singleValidators": [ + "0xc149531419db43b8cc0d4f2f2ce1700ae46f4c8a", + "0x61340dcc5aef625ded27f21e5068916ad334dad0", + "0x722e2350a55e6b617e66983b4b91d47fe9e9403e", + "0x49213606dc1953eae3b733187fed9e7307edd55b", + "0xa8fc63e72288b79009f7b5952760114513efc700" + ], + "inActive": [ + "0xcbb42f0d320056453c497867119814c59615daeb", + "0xd6e50e7f6250ca26d5d5033138ad09cfe2aaeacb", + "0xe18c1447804563af9647cf8c879f35ced8172c1f", + "0x38de067514c77fed61630bb77ecc24f2adb73ff4", + "0xc3f9bbd74ded6b2bc2ff8b5c693fcbabf2e24efd" + ], + "allEigenPods": [ + "0x2641c2ded63a0c640629f5edf1189e0f53c06561", + "0xa345dcb63f984ed1c6d1a8901e0cdbd13b2b4d19", + "0x7fb3d3801883341a02ddd1beb2e624a278e87930", + "0x2f5cb052d397d0b628299a9d4cfc49a5019c4170", + "0xdb678e1056acd7db74507b921a6295c3d586ece9", + "0xc149531419db43b8cc0d4f2f2ce1700ae46f4c8a", + "0x61340dcc5aef625ded27f21e5068916ad334dad0", + "0x722e2350a55e6b617e66983b4b91d47fe9e9403e", + "0x49213606dc1953eae3b733187fed9e7307edd55b", + "0xa8fc63e72288b79009f7b5952760114513efc700", + "0xcbb42f0d320056453c497867119814c59615daeb", + "0xd6e50e7f6250ca26d5d5033138ad09cfe2aaeacb", + "0xe18c1447804563af9647cf8c879f35ced8172c1f", + "0x38de067514c77fed61630bb77ecc24f2adb73ff4", + "0xc3f9bbd74ded6b2bc2ff8b5c693fcbabf2e24efd" + ] + } +} diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol index 088c66b536..8ed9d7b964 100644 --- a/script/utils/ExistingDeploymentParser.sol +++ b/script/utils/ExistingDeploymentParser.sol @@ -30,6 +30,12 @@ struct StrategyUnderlyingTokenConfig { string tokenSymbol; } +struct DeployedEigenPods { + address[] multiValidatorPods; + address[] singleValidatorPods; + address[] inActivePods; +} + contract ExistingDeploymentParser is Script, Test { // EigenLayer Contracts ProxyAdmin public eigenLayerProxyAdmin; @@ -51,6 +57,13 @@ contract ExistingDeploymentParser is Script, Test { EigenPod public eigenPodImplementation; StrategyBase public baseStrategyImplementation; + // EigenPods deployed + address[] public multiValidatorPods; + address[] public singleValidatorPods; + address[] public inActivePods; + // All eigenpods is just single array list of above eigenPods + address[] public allEigenPods; + EmptyContract public emptyContract; address executorMultisig; @@ -60,18 +73,12 @@ contract ExistingDeploymentParser is Script, Test { address timelock; // strategies deployed + uint256 numStrategiesDeployed; StrategyBase[] public deployedStrategyArray; // Strategies to Deploy uint256 numStrategiesToDeploy; StrategyUnderlyingTokenConfig[] public strategiesToDeploy; - // the ETH2 deposit contract -- if not on mainnet, we deploy a mock as stand-in - // IETHPOSDeposit public ethPOSDeposit; - - // // IMMUTABLES TO SET - // uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; - // uint64 GOERLI_GENESIS_TIME = 1616508000; - /// @notice Initialization Params for first initial deployment scripts // StrategyManager uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS; @@ -129,9 +136,9 @@ contract ExistingDeploymentParser is Script, Test { slasherImplementation = Slasher( stdJson.readAddress(existingDeploymentData, ".addresses.slasherImplementation") ); - delegationManager = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegation")); + delegationManager = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegationManager")); delegationManagerImplementation = DelegationManager( - stdJson.readAddress(existingDeploymentData, ".addresses.delegationImplementation") + stdJson.readAddress(existingDeploymentData, ".addresses.delegationManagerImplementation") ); avsDirectory = AVSDirectory(stdJson.readAddress(existingDeploymentData, ".addresses.avsDirectory")); avsDirectoryImplementation = AVSDirectory( @@ -152,7 +159,7 @@ contract ExistingDeploymentParser is Script, Test { stdJson.readAddress(existingDeploymentData, ".addresses.delayedWithdrawalRouterImplementation") ); beaconOracle = IBeaconChainOracle( - stdJson.readAddress(existingDeploymentData, ".addresses.beaconOracleAddress") + stdJson.readAddress(existingDeploymentData, ".addresses.beaconOracle") ); eigenPodBeacon = UpgradeableBeacon(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodBeacon")); eigenPodImplementation = EigenPod( @@ -163,20 +170,37 @@ contract ExistingDeploymentParser is Script, Test { ); emptyContract = EmptyContract(stdJson.readAddress(existingDeploymentData, ".addresses.emptyContract")); - /* - commented out -- needs JSON formatting of the form: - strategies": [ - {"WETH": "0x7CA911E83dabf90C90dD3De5411a10F1A6112184"}, - {"rETH": "0x879944A8cB437a5f8061361f82A6d4EED59070b5"}, - {"tsETH": "0xcFA9da720682bC4BCb55116675f16F503093ba13"}, - {"wstETH": "0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff"}] - // load strategy list - bytes memory strategyListRaw = stdJson.parseRaw(existingDeploymentData, ".addresses.strategies"); - address[] memory strategyList = abi.decode(strategyListRaw, (address[])); - for (uint256 i = 0; i < strategyList.length; ++i) { - deployedStrategyArray.push(StrategyBase(strategyList[i])); + // Strategies Deployed, load strategy list + numStrategiesDeployed = stdJson.readUint(existingDeploymentData, ".numStrategies"); + for (uint256 i = 0; i < numStrategiesDeployed; ++i) { + // Form the key for the current element + string memory key = string.concat(".addresses.strategyAddresses[", vm.toString(i), "]"); + + // Use the key and parse the strategy address + address strategyAddress = abi.decode(stdJson.parseRaw(existingDeploymentData, key), (address)); + deployedStrategyArray.push(StrategyBase(strategyAddress)); } - */ + } + + function _parseDeployedEigenPods(string memory existingDeploymentInfoPath) internal returns (DeployedEigenPods memory) { + uint256 currentChainId = block.chainid; + + // READ JSON CONFIG DATA + string memory existingDeploymentData = vm.readFile(existingDeploymentInfoPath); + + // check that the chainID matches the one in the config + uint256 configChainId = stdJson.readUint(existingDeploymentData, ".chainInfo.chainId"); + require(configChainId == currentChainId, "You are on the wrong chain for this config"); + + multiValidatorPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.multiValidatorPods"); + singleValidatorPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.singleValidatorPods"); + inActivePods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.inActivePods"); + allEigenPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.allEigenPods"); + return DeployedEigenPods({ + multiValidatorPods: multiValidatorPods, + singleValidatorPods: singleValidatorPods, + inActivePods: inActivePods + }); } /// @notice use for deploying a new set of EigenLayer contracts @@ -650,6 +674,7 @@ contract ExistingDeploymentParser is Script, Test { vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig); vm.serializeAddress(parameters, "communityMultisig", communityMultisig); vm.serializeAddress(parameters, "pauserMultisig", pauserMultisig); + vm.serializeAddress(parameters, "timelock", timelock); string memory parameters_output = vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig); string memory chain_info = "chainInfo"; diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 3d82fad03d..0ae15ade4f 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -8,7 +8,8 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "src/test/integration/IntegrationDeployer.t.sol"; import "src/test/integration/TimeMachine.t.sol"; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; +import "src/test/integration/users/User_M1.t.sol"; abstract contract IntegrationBase is IntegrationDeployer { @@ -17,6 +18,14 @@ abstract contract IntegrationBase is IntegrationDeployer { uint numStakers = 0; uint numOperators = 0; + // Lists of stakers/operators created before the m2 upgrade + // + // When we call _upgradeEigenLayerContracts, we iterate over + // these lists and migrate perform the standard migration actions + // for each user + User[] stakersToMigrate; + User[] operatorsToMigrate; + /** * Gen/Init methods: */ @@ -26,32 +35,108 @@ abstract contract IntegrationBase is IntegrationDeployer { * This user is ready to deposit into some strategies and has some underlying token balances */ function _newRandomStaker() internal returns (User, IStrategy[] memory, uint[] memory) { - string memory stakerName = string.concat("- Staker", numStakers.toString()); - numStakers++; + string memory stakerName; + + User staker; + IStrategy[] memory strategies; + uint[] memory tokenBalances; + + if (forkType == MAINNET && !isUpgraded) { + stakerName = string.concat("- M1_Staker", numStakers.toString()); + + (staker, strategies, tokenBalances) = _randUser(stakerName); + + stakersToMigrate.push(staker); + } else { + stakerName = string.concat("- Staker", numStakers.toString()); + + (staker, strategies, tokenBalances) = _randUser(stakerName); + } - (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _randUser(stakerName); - assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "_newRandomStaker: failed to award token balances"); + numStakers++; return (staker, strategies, tokenBalances); } + /** + * @dev Create a new operator according to configured random variants. + * This user will immediately deposit their randomized assets into eigenlayer. + * @notice If forktype is mainnet and not upgraded, then the operator will only randomize LSTs assets and deposit them + * as ETH podowner shares are not available yet. + */ function _newRandomOperator() internal returns (User, IStrategy[] memory, uint[] memory) { - string memory operatorName = string.concat("- Operator", numOperators.toString()); - numOperators++; + User operator; + IStrategy[] memory strategies; + uint[] memory tokenBalances; - (User operator, IStrategy[] memory strategies, uint[] memory tokenBalances) = _randUser(operatorName); - - operator.registerAsOperator(); - operator.depositIntoEigenlayer(strategies, tokenBalances); + if (forkType == MAINNET && !isUpgraded) { + string memory operatorName = string.concat("- M1_Operator", numOperators.toString()); + + // Create an operator for M1. We omit native ETH because we want to + // check staker/operator shares, and we don't award native ETH shares in M1 + (operator, strategies, tokenBalances) = _randUser_NoETH(operatorName); + + User_M1(payable(address(operator))).depositIntoEigenlayer_M1(strategies, tokenBalances); + uint[] memory addedShares = _calculateExpectedShares(strategies, tokenBalances); + + assert_Snap_Added_StakerShares(operator, strategies, addedShares, "_newRandomOperator: failed to add delegatable shares"); + + operatorsToMigrate.push(operator); + } else { + string memory operatorName = string.concat("- Operator", numOperators.toString()); + + (operator, strategies, tokenBalances) = _randUser(operatorName); + + uint[] memory addedShares = _calculateExpectedShares(strategies, tokenBalances); - assert_Snap_Added_StakerShares(operator, strategies, tokenBalances, "_newRandomOperator: failed to add delegatable shares"); - assert_Snap_Added_OperatorShares(operator, strategies, tokenBalances, "_newRandomOperator: failed to award shares to operator"); - assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered"); + operator.registerAsOperator(); + operator.depositIntoEigenlayer(strategies, tokenBalances); + + assert_Snap_Added_StakerShares(operator, strategies, addedShares, "_newRandomOperator: failed to add delegatable shares"); + assert_Snap_Added_OperatorShares(operator, strategies, addedShares, "_newRandomOperator: failed to award shares to operator"); + assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered"); + } + numOperators++; return (operator, strategies, tokenBalances); } + /// @dev If we're on mainnet, upgrade contracts to M2 and migrate stakers/operators + function _upgradeEigenLayerContracts() internal { + if (forkType == MAINNET) { + require(!isUpgraded, "_upgradeEigenLayerContracts: already performed m2 upgrade"); + + emit log("_upgradeEigenLayerContracts: upgrading mainnet to m2"); + _upgradeMainnetContracts(); + + emit log("===Migrating Stakers/Operators==="); + + // Enable restaking for stakers' pods + for (uint i = 0; i < stakersToMigrate.length; i++) { + stakersToMigrate[i].activateRestaking(); + } + + // Register operators with DelegationManager + for (uint i = 0; i < operatorsToMigrate.length; i++) { + operatorsToMigrate[i].registerAsOperator(); + } + + emit log("======"); + + isUpgraded = true; + emit log("_upgradeEigenLayerContracts: m2 upgrade complete"); + } else if (forkType == HOLESKY) { + require(!isUpgraded, "_upgradeEigenLayerContracts: already performed m2 upgrade"); + + emit log("_upgradeEigenLayerContracts: upgrading holesky to m2"); + _upgradeHoleskyContracts(); + + isUpgraded = true; + emit log("_upgradeEigenLayerContracts: m2 upgrade complete"); + } + } + /** * Common assertions: */ @@ -81,7 +166,8 @@ abstract contract IntegrationBase is IntegrationDeployer { tokenBalance = strat.underlyingToken().balanceOf(address(user)); } - assertEq(expectedBalance, tokenBalance, err); + assertApproxEqAbs(expectedBalance, tokenBalance, 1, err); + // assertEq(expectedBalance, tokenBalance, err); } } @@ -113,7 +199,7 @@ abstract contract IntegrationBase is IntegrationDeployer { actualShares = strategyManager.stakerStrategyShares(address(user), strat); } - assertEq(expectedShares[i], actualShares, err); + assertApproxEqAbs(expectedShares[i], actualShares, 1, err); } } @@ -128,7 +214,7 @@ abstract contract IntegrationBase is IntegrationDeployer { uint actualShares = delegationManager.operatorShares(address(user), strat); - assertEq(expectedShares[i], actualShares, err); + assertApproxEqAbs(expectedShares[i], actualShares, 1, err); } } @@ -194,7 +280,7 @@ abstract contract IntegrationBase is IntegrationDeployer { // For each strategy, check (prev + added == cur) for (uint i = 0; i < strategies.length; i++) { - assertEq(prevShares[i] + addedShares[i], curShares[i], err); + assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1, err); } } @@ -272,7 +358,7 @@ abstract contract IntegrationBase is IntegrationDeployer { // For each strategy, check (prev + added == cur) for (uint i = 0; i < strategies.length; i++) { - assertEq(prevShares[i] + addedShares[i], curShares[i], err); + assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1, err); } } @@ -801,4 +887,4 @@ abstract contract IntegrationBase is IntegrationDeployer { return shares; } -} +} \ No newline at end of file diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index fef09682e6..54bb599526 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -2,7 +2,8 @@ pragma solidity =0.8.12; import "src/test/integration/IntegrationBase.t.sol"; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; +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 { diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 1be9fdf81a..f19c92b4dc 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -13,6 +13,7 @@ import "src/contracts/core/DelegationManager.sol"; import "src/contracts/core/StrategyManager.sol"; import "src/contracts/core/Slasher.sol"; import "src/contracts/strategies/StrategyBase.sol"; +import "src/contracts/strategies/StrategyBaseTVLLimits.sol"; import "src/contracts/pods/EigenPodManager.sol"; import "src/contracts/pods/EigenPod.sol"; import "src/contracts/pods/DelayedWithdrawalRouter.sol"; @@ -23,24 +24,23 @@ import "src/test/mocks/ETHDepositMock.sol"; import "src/test/integration/mocks/BeaconChainOracleMock.t.sol"; import "src/test/integration/mocks/BeaconChainMock.t.sol"; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; +import "src/test/integration/users/User_M1.t.sol"; -abstract contract IntegrationDeployer is Test, IUserDeployer { +import "script/utils/ExistingDeploymentParser.sol"; + +abstract contract IntegrationDeployer is ExistingDeploymentParser { Vm cheats = Vm(HEVM_ADDRESS); - // Core contracts to deploy - DelegationManager public delegationManager; - StrategyManager public strategyManager; - EigenPodManager public eigenPodManager; - PauserRegistry pauserRegistry; - Slasher slasher; - IBeacon eigenPodBeacon; - EigenPod pod; - DelayedWithdrawalRouter delayedWithdrawalRouter; + // Fork ids for specific fork tests + bool isUpgraded; + uint256 mainnetForkBlock = 19_280_000; + uint256 mainnetForkId; + uint256 holeskyForkBLock = 1_213_950; + uint256 holeskyForkId; + uint64 constant DENEB_FORK_TIMESTAMP = 1705473120; - // Base strategy implementation in case we want to create more strategies later - StrategyBase baseStrategyImplementation; TimeMachine public timeMachine; @@ -53,13 +53,14 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { IStrategy[] allStrats; // just a combination of the above 2 lists IERC20[] allTokens; // `allStrats`, but contains all of the underlying tokens instead + // If a token is in this mapping, then we will ignore this LST as it causes issues with reading balanceOf + mapping(address => bool) public tokensNotTested; + // Mock Contracts to deploy ETHPOSDepositMock ethPOSDeposit; - BeaconChainOracleMock beaconChainOracle; + BeaconChainOracleMock public beaconChainOracle; BeaconChainMock public beaconChain; - // ProxyAdmin - ProxyAdmin eigenLayerProxyAdmin; // Admin Addresses address eigenLayerReputedMultisig = address(this); // admin address address constant pauser = address(555); @@ -71,10 +72,12 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // be returned from `_randUser`. bytes assetTypes; bytes userTypes; + bytes forkTypes; + // Set only once in configRand + uint forkType; // Constants uint64 constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; - uint64 constant GOERLI_GENESIS_TIME = 1616508000; IStrategy constant BEACONCHAIN_ETH_STRAT = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); IERC20 constant NATIVE_ETH = IERC20(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); @@ -83,6 +86,23 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { uint constant MAX_BALANCE = 5e6; uint constant GWEI_TO_WEI = 1e9; + // Paused Constants + // DelegationManager + uint8 internal constant PAUSED_NEW_DELEGATION = 0; + uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1; + uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2; + // StrategyManager + uint8 internal constant PAUSED_DEPOSITS = 0; + // EigenpodManager + uint8 internal constant PAUSED_NEW_EIGENPODS = 0; + uint8 internal constant PAUSED_WITHDRAW_RESTAKED_ETH = 1; + uint8 internal constant PAUSED_EIGENPODS_VERIFY_CREDENTIALS = 2; + uint8 internal constant PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE = 3; + uint8 internal constant PAUSED_EIGENPODS_VERIFY_WITHDRAWAL = 4; + uint8 internal constant PAUSED_NON_PROOF_WITHDRAWALS = 5; + // DelayedWithdrawalRouter + uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0; + // Flags uint constant FLAG = 1; @@ -99,6 +119,12 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { uint constant DEFAULT = (FLAG << 0); uint constant ALT_METHODS = (FLAG << 1); + /// @dev Shadow Fork flags + /// These are used for upgrade integration testing. + uint constant LOCAL = (FLAG << 0); + uint constant MAINNET = (FLAG << 1); + uint constant HOLESKY = (FLAG << 2); + // /// @dev Withdrawal flags // /// These are used with _configRand to determine how a user conducts a withdrawal // uint constant FULL_WITHDRAW_SINGLE = (FLAG << 0); // stakers will withdraw all assets using a single queued withdrawal @@ -119,6 +145,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { mapping(uint => string) assetTypeToStr; mapping(uint => string) userTypeToStr; + mapping(uint => string) forkTypeToStr; constructor () { assetTypeToStr[NO_ASSETS] = "NO_ASSETS"; @@ -128,16 +155,49 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { userTypeToStr[DEFAULT] = "DEFAULT"; userTypeToStr[ALT_METHODS] = "ALT_METHODS"; + + forkTypeToStr[LOCAL] = "LOCAL"; + forkTypeToStr[MAINNET] = "MAINNET"; + forkTypeToStr[HOLESKY] = "HOLESKY"; + + address stETH_Holesky = 0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034; + address stETH_Mainnet = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; + address OETH_Mainnet = 0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3; + address osETH_Holesky = 0xF603c5A3F774F05d4D848A9bB139809790890864; + address osETH_Mainnet = 0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38; + address cbETH_Holesky = 0x8720095Fa5739Ab051799211B146a2EEE4Dd8B37; + tokensNotTested[stETH_Holesky] = true; + tokensNotTested[stETH_Mainnet] = true; + tokensNotTested[OETH_Mainnet] = true; + tokensNotTested[osETH_Holesky] = true; + tokensNotTested[osETH_Mainnet] = true; + tokensNotTested[cbETH_Holesky] = true; } + /** + * @dev Anyone who wants to test using this contract in a separate repo via submodules may have to + * override this function to set the correct paths for the deployment info files. + * + * This setUp function will account for specific --fork-url flags and deploy/upgrade contracts accordingly. + * Note that forkIds are also created so you can make explicit fork tests using cheats.selectFork(forkId) + */ function setUp() public virtual { + isUpgraded = false; + // create mainnet fork that can be used later + mainnetForkId = cheats.createFork(cheats.rpcUrl("mainnet"), mainnetForkBlock); + // create holesky fork that can be used later + holeskyForkId = cheats.createFork(cheats.rpcUrl("holesky"), holeskyForkBLock); + } + + function _setUpLocal() public virtual { // Deploy ProxyAdmin eigenLayerProxyAdmin = new ProxyAdmin(); + executorMultisig = address(eigenLayerProxyAdmin.owner()); // Deploy PauserRegistry address[] memory pausers = new address[](1); pausers[0] = pauser; - pauserRegistry = new PauserRegistry(pausers, unpauser); + eigenLayerPauserReg = new PauserRegistry(pausers, unpauser); // Deploy mocks EmptyContract emptyContract = new EmptyContract(); @@ -163,9 +223,12 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { delayedWithdrawalRouter = DelayedWithdrawalRouter( address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) ); + avsDirectory = AVSDirectory( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); // Deploy EigenPod Contracts - pod = new EigenPod( + eigenPodImplementation = new EigenPod( ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, @@ -173,20 +236,21 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { 0 ); - eigenPodBeacon = new UpgradeableBeacon(address(pod)); + eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation)); // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs - DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); - StrategyManager strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher); - Slasher slasherImplementation = new Slasher(strategyManager, delegationManager); - EigenPodManager eigenPodManagerImplementation = new EigenPodManager( + delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); + strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher); + slasherImplementation = new Slasher(strategyManager, delegationManager); + eigenPodManagerImplementation = new EigenPodManager( ethPOSDeposit, eigenPodBeacon, strategyManager, slasher, delegationManager ); - DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); + delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); + avsDirectoryImplementation = new AVSDirectory(delegationManager); // Third, upgrade the proxy contracts to point to the implementations uint256 withdrawalDelayBlocks = 7 days / 12 seconds; @@ -195,11 +259,11 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // DelegationManager eigenLayerProxyAdmin.upgradeAndCall( TransparentUpgradeableProxy(payable(address(delegationManager))), - address(delegationImplementation), + address(delegationManagerImplementation), abi.encodeWithSelector( DelegationManager.initialize.selector, eigenLayerReputedMultisig, // initialOwner - pauserRegistry, + eigenLayerPauserReg, 0 /* initialPausedStatus */, withdrawalDelayBlocks, initializeStrategiesToSetDelayBlocks, @@ -214,7 +278,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { StrategyManager.initialize.selector, eigenLayerReputedMultisig, //initialOwner eigenLayerReputedMultisig, //initial whitelister - pauserRegistry, + eigenLayerPauserReg, 0 // initialPausedStatus ) ); @@ -225,7 +289,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { abi.encodeWithSelector( Slasher.initialize.selector, eigenLayerReputedMultisig, - pauserRegistry, + eigenLayerPauserReg, 0 // initialPausedStatus ) ); @@ -237,7 +301,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { EigenPodManager.initialize.selector, address(beaconChainOracle), eigenLayerReputedMultisig, // initialOwner - pauserRegistry, + eigenLayerPauserReg, 0 // initialPausedStatus ) ); @@ -248,11 +312,22 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { abi.encodeWithSelector( DelayedWithdrawalRouter.initialize.selector, eigenLayerReputedMultisig, // initialOwner - pauserRegistry, + eigenLayerPauserReg, 0, // initialPausedStatus withdrawalDelayBlocks ) ); + // AVSDirectory + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(avsDirectory))), + address(avsDirectoryImplementation), + abi.encodeWithSelector( + AVSDirectory.initialize.selector, + eigenLayerReputedMultisig, // initialOwner + eigenLayerPauserReg, + 0 // initialPausedStatus + ) + ); // Create base strategy implementation and deploy a few strategies baseStrategyImplementation = new StrategyBase(strategyManager); @@ -268,16 +343,233 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // Create time machine and set block timestamp forward so we can create EigenPod proofs in the past timeMachine = new TimeMachine(); timeMachine.setProofGenStartTime(2 hours); - // Create mock beacon chain / proof gen interface - beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle); - - + beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle, eigenPodManager); //set deneb fork timestamp eigenPodManager.setDenebForkTimestamp(type(uint64).max); } + /** + * @notice deploy current implementation contracts and upgrade the existing proxy EigenLayer contracts + * on Mainnet. Setup for integration tests on mainnet fork. + * + * Note that beacon chain oracle and eth deposit contracts are mocked and pointed to different addresses for these tests. + */ + function _upgradeMainnetContracts() public virtual { + cheats.startPrank(address(executorMultisig)); + + ethPOSDeposit = new ETHPOSDepositMock(); + ETHPOSDepositAddress = address(ethPOSDeposit); // overwrite for upgrade checks later + + // Deploy EigenPod Contracts + eigenPodImplementation = new EigenPod( + ethPOSDeposit, + delayedWithdrawalRouter, + eigenPodManager, + MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, + 0 + ); + eigenPodBeacon.upgradeTo(address(eigenPodImplementation)); + // Deploy AVSDirectory, contract has not been deployed on mainnet yet + avsDirectory = AVSDirectory( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + + // First, deploy the *implementation* contracts, using the *proxy contracts* as inputs + delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); + strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher); + slasherImplementation = new Slasher(strategyManager, delegationManager); + eigenPodManagerImplementation = new EigenPodManager( + ethPOSDeposit, + eigenPodBeacon, + strategyManager, + slasher, + delegationManager + ); + delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); + avsDirectoryImplementation = new AVSDirectory(delegationManager); + + // Second, upgrade the proxy contracts to point to the implementations + // DelegationManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(delegationManager))), + address(delegationManagerImplementation) + ); + // StrategyManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(strategyManager))), + address(strategyManagerImplementation) + ); + // Slasher + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(slasher))), + address(slasherImplementation) + ); + // EigenPodManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(eigenPodManager))), + address(eigenPodManagerImplementation) + ); + // Delayed Withdrawal Router + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))), + address(delayedWithdrawalRouterImplementation) + ); + // AVSDirectory, upgrade and initalized + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(avsDirectory))), + address(avsDirectoryImplementation), + abi.encodeWithSelector( + AVSDirectory.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + 0 // initialPausedStatus + ) + ); + + // Create base strategy implementation and deploy a few strategies + baseStrategyImplementation = new StrategyBase(strategyManager); + + // Upgrade All deployed strategy contracts to new base strategy + for (uint i = 0; i < numStrategiesDeployed; i++) { + // Upgrade existing strategy + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i]))), + address(baseStrategyImplementation) + ); + } + + // Third, unpause core contracts + delegationManager.unpause(0); + eigenPodManager.unpause(0); + strategyManager.unpause(0); + + eigenPodManager.updateBeaconChainOracle(beaconChainOracle); + timeMachine.setProofGenStartTime(0); + beaconChain.setNextTimestamp(timeMachine.proofGenStartTime()); + + if (eigenPodManager.denebForkTimestamp() == type(uint64).max) { + //set deneb fork timestamp if not set + eigenPodManager.setDenebForkTimestamp(DENEB_FORK_TIMESTAMP); + } + cheats.stopPrank(); + + ethStrats.push(BEACONCHAIN_ETH_STRAT); + allStrats.push(BEACONCHAIN_ETH_STRAT); + allTokens.push(NATIVE_ETH); + } + + /** + * @notice deploy current implementation contracts and upgrade the existing proxy EigenLayer contracts + * on Holesky. Setup for integration tests on Holesky fork. + * + * Note that beacon chain oracle and eth deposit contracts are mocked and pointed to different addresses for these tests. + */ + function _upgradeHoleskyContracts() public virtual { + cheats.startPrank(address(executorMultisig)); + + ethPOSDeposit = new ETHPOSDepositMock(); + ETHPOSDepositAddress = address(ethPOSDeposit); // overwrite for upgrade checks later + + // Deploy EigenPod Contracts + eigenPodImplementation = new EigenPod( + ethPOSDeposit, + delayedWithdrawalRouter, + eigenPodManager, + MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, + 0 + ); + eigenPodBeacon.upgradeTo(address(eigenPodImplementation)); + // Deploy AVSDirectory, contract has not been deployed on mainnet yet + avsDirectory = AVSDirectory( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + + // First, deploy the *implementation* contracts, using the *proxy contracts* as inputs + delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); + strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher); + slasherImplementation = new Slasher(strategyManager, delegationManager); + eigenPodManagerImplementation = new EigenPodManager( + ethPOSDeposit, + eigenPodBeacon, + strategyManager, + slasher, + delegationManager + ); + delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); + avsDirectoryImplementation = new AVSDirectory(delegationManager); + + // Second, upgrade the proxy contracts to point to the implementations + // DelegationManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(delegationManager))), + address(delegationManagerImplementation) + ); + // StrategyManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(strategyManager))), + address(strategyManagerImplementation) + ); + // Slasher + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(slasher))), + address(slasherImplementation) + ); + // EigenPodManager + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(eigenPodManager))), + address(eigenPodManagerImplementation) + ); + // Delayed Withdrawal Router + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))), + address(delayedWithdrawalRouterImplementation) + ); + // AVSDirectory, upgrade and initalized + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(avsDirectory))), + address(avsDirectoryImplementation), + abi.encodeWithSelector( + AVSDirectory.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + 0 // initialPausedStatus + ) + ); + + // Create base strategy implementation and deploy a few strategies + baseStrategyImplementation = new StrategyBase(strategyManager); + + // Upgrade All deployed strategy contracts to new base strategy + for (uint i = 0; i < numStrategiesDeployed; i++) { + // Upgrade existing strategy + eigenLayerProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i]))), + address(baseStrategyImplementation) + ); + } + + // Third, unpause core contracts + delegationManager.unpause(0); + eigenPodManager.unpause(0); + strategyManager.unpause(0); + + eigenPodManager.updateBeaconChainOracle(beaconChainOracle); + timeMachine.setProofGenStartTime(0); + beaconChain.setNextTimestamp(timeMachine.proofGenStartTime()); + + if (eigenPodManager.denebForkTimestamp() == type(uint64).max) { + //set deneb fork timestamp if not set + eigenPodManager.setDenebForkTimestamp(DENEB_FORK_TIMESTAMP); + } + cheats.stopPrank(); + + ethStrats.push(BEACONCHAIN_ETH_STRAT); + allStrats.push(BEACONCHAIN_ETH_STRAT); + allTokens.push(NATIVE_ETH); + } + /// @dev Deploy a strategy and its underlying token, push to global lists of tokens/strategies, and whitelist /// strategy in strategyManager function _newStrategyAndToken(string memory tokenName, string memory tokenSymbol, uint initialSupply, address owner) internal { @@ -287,7 +579,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { new TransparentUpgradeableProxy( address(baseStrategyImplementation), address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, pauserRegistry) + abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) ) ) ); @@ -296,8 +588,16 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { IStrategy[] memory strategies = new IStrategy[](1); bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1); strategies[0] = strategy; - cheats.prank(strategyManager.strategyWhitelister()); - strategyManager.addStrategiesToDepositWhitelist(strategies, _thirdPartyTransfersForbiddenValues); + + if (forkType == MAINNET) { + cheats.prank(strategyManager.strategyWhitelister()); + IStrategyManager_DeprecatedM1(address(strategyManager)).addStrategiesToDepositWhitelist(strategies); + cheats.prank(eigenLayerPauserReg.unpauser()); + StrategyBaseTVLLimits(address(strategy)).setTVLLimits(type(uint256).max, type(uint256).max); + } else { + cheats.prank(strategyManager.strategyWhitelister()); + strategyManager.addStrategiesToDepositWhitelist(strategies, _thirdPartyTransfersForbiddenValues); + } // Add to lstStrats and allStrats lstStrats.push(strategy); @@ -308,7 +608,8 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { function _configRand( uint24 _randomSeed, uint _assetTypes, - uint _userTypes + uint _userTypes, + uint _forkTypes ) internal { // Using uint24 for the seed type so that if a test fails, it's easier // to manually use the seed to replay the same test. @@ -318,6 +619,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // Convert flag bitmaps to bytes of set bits for easy use with _randUint assetTypes = _bitmapToBytes(_assetTypes); userTypes = _bitmapToBytes(_userTypes); + forkTypes = _bitmapToBytes(_forkTypes); emit log("_configRand: Users will be initialized with these asset types:"); for (uint i = 0; i < assetTypes.length; i++) { @@ -329,8 +631,104 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { emit log(userTypeToStr[uint(uint8(userTypes[i]))]); } + emit log("_configRand: Tests will be shadow forked based on these fork types"); + for (uint i = 0; i < forkTypes.length; i++) { + emit log(forkTypeToStr[uint(uint8(forkTypes[i]))]); + } + assertTrue(assetTypes.length != 0, "_configRand: no asset types selected"); assertTrue(userTypes.length != 0, "_configRand: no user types selected"); + assertTrue(forkTypes.length != 0, "_configRand: no fork types selected"); + + // Use forkType to deploy contracts locally or fetch from mainnet + _deployOrFetchContracts(); + } + + /** + * Depending on the forkType, either deploy contracts locally or parse existing contracts + * from network. + * + * Note: for non-LOCAL forktypes, upgrade of contracts will be peformed after user initialization. + */ + function _deployOrFetchContracts() internal { + forkType = _randForkType(); + + emit log_named_string("_deployOrFetchContracts selected fork for test", forkTypeToStr[forkType]); + + if (forkType == LOCAL) { + _setUpLocal(); + // Set Upgraded as local setup deploys most up to date contracts + isUpgraded = true; + } else if (forkType == MAINNET) { + cheats.selectFork(mainnetForkId); + string memory deploymentInfoPath = "script/configs/mainnet/Mainnet_current_deployment.config.json"; + _parseDeployedContracts(deploymentInfoPath); + + // Unpause to enable deposits and withdrawals for initializing random user state + cheats.prank(eigenLayerPauserReg.unpauser()); + strategyManager.unpause(0); + + // Add deployed strategies to lstStrats and allStrats + for (uint i; i < deployedStrategyArray.length; i++) { + IStrategy strategy = IStrategy(deployedStrategyArray[i]); + + if (tokensNotTested[address(strategy.underlyingToken())]) { + continue; + } + + // Add to lstStrats and allStrats + lstStrats.push(strategy); + allStrats.push(strategy); + allTokens.push(strategy.underlyingToken()); + } + + // Create time machine and set block timestamp forward so we can create EigenPod proofs in the past + timeMachine = new TimeMachine(); + beaconChainOracle = new BeaconChainOracleMock(); + // Create mock beacon chain / proof gen interface + beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle, eigenPodManager); + } else if (forkType == HOLESKY) { + cheats.selectFork(holeskyForkId); + string memory deploymentInfoPath = "script/configs/holesky/Holesky_current_deployment.config.json"; + _parseDeployedContracts(deploymentInfoPath); + + // Add deployed strategies to lstStrats and allStrats + for (uint i; i < deployedStrategyArray.length; i++) { + IStrategy strategy = IStrategy(deployedStrategyArray[i]); + + if (tokensNotTested[address(strategy.underlyingToken())]) { + continue; + } + + // Add to lstStrats and allStrats + lstStrats.push(strategy); + allStrats.push(strategy); + allTokens.push(strategy.underlyingToken()); + } + + // Update deposit contract to be a mock + ethPOSDeposit = new ETHPOSDepositMock(); + eigenPodImplementation = new EigenPod( + ethPOSDeposit, + eigenPodImplementation.delayedWithdrawalRouter(), + eigenPodImplementation.eigenPodManager(), + eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(), + 0 + ); + // Create time machine and set block timestamp forward so we can create EigenPod proofs in the past + timeMachine = new TimeMachine(); + beaconChainOracle = new BeaconChainOracleMock(); + // Create mock beacon chain / proof gen interface + beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle, eigenPodManager); + + cheats.startPrank(executorMultisig); + eigenPodBeacon.upgradeTo(address(eigenPodImplementation)); + eigenPodManager.updateBeaconChainOracle(beaconChainOracle); + cheats.stopPrank(); + + } else { + revert("_configEigenlayerContracts: unimplemented forkType"); + } } /** @@ -346,27 +744,93 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { uint assetType = _randAssetType(); uint userType = _randUserType(); - // Create User contract based on deposit type: - User user; - if (userType == DEFAULT) { - user = new User(name); - } else if (userType == ALT_METHODS) { - // User will use nonstandard methods like: - // `delegateToBySignature` and `depositIntoStrategyWithSignature` - user = User(new User_AltMethods(name)); - } else { - revert("_randUser: unimplemented userType"); - } + // Deploy new User contract + User user = _genRandUser(name, userType); // For the specific asset selection we made, get a random assortment of // strategies and deal the user some corresponding underlying token balances (IStrategy[] memory strategies, uint[] memory tokenBalances) = _dealRandAssets(user, assetType); - _printUserInfo(assetType, userType, strategies, tokenBalances); + _printUserInfo(name, assetType, userType, strategies, tokenBalances); + return (user, strategies, tokenBalances); + } + /// @dev Create a new user without native ETH. See _randUser above for standard usage + function _randUser_NoETH(string memory name) internal returns (User, IStrategy[] memory, uint[] memory) { + // For the new user, select what type of assets they'll have and whether + // they'll use `xWithSignature` methods. + // + // The values selected here are in the ranges configured via `_configRand` + uint userType = _randUserType(); + + // Pick the user's asset distribution, removing "native ETH" as an option + // I'm sorry if this eventually leads to a bug that's really hard to track down + uint assetType = _randAssetType(); + if (assetType == HOLDS_ETH) { + assetType = NO_ASSETS; + } else if (assetType == HOLDS_ALL) { + assetType = HOLDS_LST; + } + + // Deploy new User contract + User user = _genRandUser(name, userType); + + // For the specific asset selection we made, get a random assortment of + // strategies and deal the user some corresponding underlying token balances + (IStrategy[] memory strategies, uint[] memory tokenBalances) = _dealRandAssets(user, assetType); + + _printUserInfo(name, assetType, userType, strategies, tokenBalances); return (user, strategies, tokenBalances); } + function _genRandUser(string memory name, uint userType) internal returns (User) { + // Create User contract based on userType: + User user; + if (forkType == LOCAL) { + user = new User(name); + + if (userType == DEFAULT) { + user = new User(name); + } else if (userType == ALT_METHODS) { + // User will use nonstandard methods like: + // `delegateToBySignature` and `depositIntoStrategyWithSignature` + user = User(new User_AltMethods(name)); + } else { + revert("_randUser: unimplemented userType"); + } + } else if (forkType == MAINNET) { + if (userType == DEFAULT) { + user = User(new User_M1(name)); + } else if (userType == ALT_METHODS) { + // User will use nonstandard methods like: + // `delegateToBySignature` and `depositIntoStrategyWithSignature` + user = User(new User_M1_AltMethods(name)); + } else { + revert("_randUser: unimplemented userType"); + } + + } else if (forkType == HOLESKY) { + // User deployment for Holesky is exact same as holesky. + // Current Holesky deployment is up to date and no deprecated interfaces have been added. + + user = new User(name); + + if (userType == DEFAULT) { + user = new User(name); + } else if (userType == ALT_METHODS) { + // User will use nonstandard methods like: + // `delegateToBySignature` and `depositIntoStrategyWithSignature` + user = User(new User_AltMethods(name)); + } else { + revert("_randUser: unimplemented userType"); + } + } else { + revert("_randUser: unimplemented forkType"); + } + + return user; + } + /// @dev For a given `assetType`, select a random assortment of strategies and assets /// NO_ASSETS - return will be empty /// HOLDS_LST - `strategies` will be a random subset of initialized strategies @@ -376,18 +840,15 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { /// HOLDS_ALL - `strategies` will contain ALL initialized strategies AND BEACONCHAIN_ETH_STRAT, and /// `tokenBalances` will contain random token/eth balances accordingly function _dealRandAssets(User user, uint assetType) internal returns (IStrategy[] memory, uint[] memory) { - IStrategy[] memory strategies; uint[] memory tokenBalances; - if (assetType == NO_ASSETS) { strategies = new IStrategy[](0); tokenBalances = new uint[](0); } else if (assetType == HOLDS_LST) { - + assetType = HOLDS_LST; // Select a random number of assets uint numAssets = _randUint({ min: 1, max: lstStrats.length }); - strategies = new IStrategy[](numAssets); tokenBalances = new uint[](numAssets); @@ -395,10 +856,9 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { for (uint i = 0; i < numAssets; i++) { IStrategy strat = lstStrats[i]; IERC20 underlyingToken = strat.underlyingToken(); - uint balance = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE }); - StdCheats.deal(address(underlyingToken), address(user), balance); + StdCheats.deal(address(underlyingToken), address(user), balance); tokenBalances[i] = balance; strategies[i] = strat; } @@ -421,10 +881,9 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { for (uint i = 0; i < numLSTs; i++) { IStrategy strat = lstStrats[i]; IERC20 underlyingToken = strat.underlyingToken(); - uint balance = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE }); - StdCheats.deal(address(underlyingToken), address(user), balance); + StdCheats.deal(address(underlyingToken), address(user), balance); tokenBalances[i] = balance; strategies[i] = strat; } @@ -443,6 +902,28 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { return (strategies, tokenBalances); } + /// @dev By default will have a assetType of HOLDS_LST + function _dealRandAssets_M1(User user) internal returns (IStrategy[] memory, uint[] memory) { + // Select a random number of assets + uint numAssets = _randUint({ min: 1, max: lstStrats.length }); + + IStrategy[] memory strategies = new IStrategy[](numAssets); + uint[] memory tokenBalances = new uint[](numAssets); + + // For each asset, award the user a random balance of the underlying token + for (uint i = 0; i < numAssets; i++) { + IStrategy strat = lstStrats[i]; + IERC20 underlyingToken = strat.underlyingToken(); + uint balance = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE }); + + StdCheats.deal(address(underlyingToken), address(user), balance); + tokenBalances[i] = balance; + strategies[i] = strat; + } + + return (strategies, tokenBalances); + } + /// @dev Uses `random` to return a random uint, with a range given by `min` and `max` (inclusive) /// @return `min` <= result <= `max` function _randUint(uint min, uint max) internal returns (uint) { @@ -487,7 +968,14 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { uint userType = uint(uint8(userTypes[idx])); return userType; - } + } + + function _randForkType() internal returns (uint) { + uint idx = _randUint({ min: 0, max: forkTypes.length - 1 }); + uint randForkType = uint(uint8(forkTypes[idx])); + + return randForkType; + } /** * @dev Converts a bitmap into an array of bytes @@ -509,15 +997,18 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { } function _printUserInfo( + string memory name, uint assetType, uint userType, IStrategy[] memory strategies, uint[] memory tokenBalances ) internal { - emit log("Creating user:"); - emit log_named_string("assetType: ", assetTypeToStr[assetType]); - emit log_named_string("userType: ", userTypeToStr[userType]); + emit log("===Created User==="); + emit log_named_string("Name", name); + emit log_named_string("assetType", assetTypeToStr[assetType]); + emit log_named_string("userType", userTypeToStr[userType]); + emit log_named_string("forkType", forkTypeToStr[forkType]); emit log_named_uint("num assets: ", strategies.length); @@ -534,5 +1025,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { emit log_named_uint("token balance: ", tokenBalances[i]); } } + + emit log("=================="); } } \ No newline at end of file diff --git a/src/test/integration/deprecatedInterfaces/mainnet/BeaconChainProofs.sol b/src/test/integration/deprecatedInterfaces/mainnet/BeaconChainProofs.sol new file mode 100644 index 0000000000..c41d75a45e --- /dev/null +++ b/src/test/integration/deprecatedInterfaces/mainnet/BeaconChainProofs.sol @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity =0.8.12; + +import "src/contracts/libraries/Merkle.sol"; +import "src/contracts/libraries/Endian.sol"; + +// DEPRECATED BeaconChainProofs at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928 +//Utility library for parsing and PHASE0 beacon chain block headers +//SSZ Spec: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization +//BeaconBlockHeader Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader +//BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate +library BeaconChainProofs_DeprecatedM1 { + // constants are the number of fields and the heights of the different merkle trees used in merkleizing beacon chain containers + uint256 internal constant NUM_BEACON_BLOCK_HEADER_FIELDS = 5; + uint256 internal constant BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT = 3; + + uint256 internal constant NUM_BEACON_BLOCK_BODY_FIELDS = 11; + uint256 internal constant BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT = 4; + + uint256 internal constant NUM_BEACON_STATE_FIELDS = 21; + uint256 internal constant BEACON_STATE_FIELD_TREE_HEIGHT = 5; + + uint256 internal constant NUM_ETH1_DATA_FIELDS = 3; + uint256 internal constant ETH1_DATA_FIELD_TREE_HEIGHT = 2; + + uint256 internal constant NUM_VALIDATOR_FIELDS = 8; + uint256 internal constant VALIDATOR_FIELD_TREE_HEIGHT = 3; + + uint256 internal constant NUM_EXECUTION_PAYLOAD_HEADER_FIELDS = 15; + uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT = 4; + + + uint256 internal constant NUM_EXECUTION_PAYLOAD_FIELDS = 15; + uint256 internal constant EXECUTION_PAYLOAD_FIELD_TREE_HEIGHT = 4; + + + // HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24 + uint256 internal constant HISTORICAL_ROOTS_TREE_HEIGHT = 24; + + // HISTORICAL_BATCH is root of state_roots and block_root, so number of leaves = 2^1 + uint256 internal constant HISTORICAL_BATCH_TREE_HEIGHT = 1; + + // SLOTS_PER_HISTORICAL_ROOT = 2**13, so tree height is 13 + uint256 internal constant STATE_ROOTS_TREE_HEIGHT = 13; + uint256 internal constant BLOCK_ROOTS_TREE_HEIGHT = 13; + + + uint256 internal constant NUM_WITHDRAWAL_FIELDS = 4; + // tree height for hash tree of an individual withdrawal container + uint256 internal constant WITHDRAWAL_FIELD_TREE_HEIGHT = 2; + + uint256 internal constant VALIDATOR_TREE_HEIGHT = 40; + //refer to the eigenlayer-cli proof library. Despite being the same dimensions as the validator tree, the balance tree is merkleized differently + uint256 internal constant BALANCE_TREE_HEIGHT = 38; + + // MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4 + uint256 internal constant WITHDRAWALS_TREE_HEIGHT = 4; + + //in beacon block body + uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9; + + // in beacon block header + uint256 internal constant STATE_ROOT_INDEX = 3; + uint256 internal constant PROPOSER_INDEX_INDEX = 1; + uint256 internal constant SLOT_INDEX = 0; + uint256 internal constant BODY_ROOT_INDEX = 4; + // in beacon state + uint256 internal constant STATE_ROOTS_INDEX = 6; + uint256 internal constant BLOCK_ROOTS_INDEX = 5; + uint256 internal constant HISTORICAL_ROOTS_INDEX = 7; + uint256 internal constant ETH_1_ROOT_INDEX = 8; + uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11; + uint256 internal constant BALANCE_INDEX = 12; + uint256 internal constant EXECUTION_PAYLOAD_HEADER_INDEX = 24; + uint256 internal constant HISTORICAL_BATCH_STATE_ROOT_INDEX = 1; + + // in validator + uint256 internal constant VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX = 1; + uint256 internal constant VALIDATOR_BALANCE_INDEX = 2; + uint256 internal constant VALIDATOR_SLASHED_INDEX = 3; + uint256 internal constant VALIDATOR_WITHDRAWABLE_EPOCH_INDEX = 7; + + // in execution payload header + uint256 internal constant BLOCK_NUMBER_INDEX = 6; + uint256 internal constant WITHDRAWALS_ROOT_INDEX = 14; + + //in execution payload + uint256 internal constant WITHDRAWALS_INDEX = 14; + + // in withdrawal + uint256 internal constant WITHDRAWAL_VALIDATOR_INDEX_INDEX = 1; + uint256 internal constant WITHDRAWAL_VALIDATOR_AMOUNT_INDEX = 3; + + //In historicalBatch + uint256 internal constant HISTORICALBATCH_STATEROOTS_INDEX = 1; + + //Misc Constants + uint256 internal constant SLOTS_PER_EPOCH = 32; + + bytes8 internal constant UINT64_MASK = 0xffffffffffffffff; + + + + struct WithdrawalProofs { + bytes blockHeaderProof; + bytes withdrawalProof; + bytes slotProof; + bytes executionPayloadProof; + bytes blockNumberProof; + uint64 blockHeaderRootIndex; + uint64 withdrawalIndex; + bytes32 blockHeaderRoot; + bytes32 blockBodyRoot; + bytes32 slotRoot; + bytes32 blockNumberRoot; + bytes32 executionPayloadRoot; + } + + struct ValidatorFieldsAndBalanceProofs { + bytes validatorFieldsProof; + bytes validatorBalanceProof; + bytes32 balanceRoot; + } + + struct ValidatorFieldsProof { + bytes validatorProof; + uint40 validatorIndex; + } + + /** + * + * @notice This function is parses the balanceRoot to get the uint64 balance of a validator. During merkleization of the + * beacon state balance tree, four uint64 values (making 32 bytes) are grouped together and treated as a single leaf in the merkle tree. Thus the + * validatorIndex mod 4 is used to determine which of the four uint64 values to extract from the balanceRoot. + * @param validatorIndex is the index of the validator being proven for. + * @param balanceRoot is the combination of 4 validator balances being proven for. + * @return The validator's balance, in Gwei + */ + function getBalanceFromBalanceRoot(uint40 validatorIndex, bytes32 balanceRoot) internal pure returns (uint64) { + uint256 bitShiftAmount = (validatorIndex % 4) * 64; + bytes32 validatorBalanceLittleEndian = bytes32((uint256(balanceRoot) << bitShiftAmount)); + uint64 validatorBalance = Endian.fromLittleEndianUint64(validatorBalanceLittleEndian); + return validatorBalance; + } + + /** + * @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root + * @param validatorIndex the index of the proven validator + * @param beaconStateRoot is the beacon chain state root to be proven against. + * @param proof is the data used in proving the validator's fields + * @param validatorFields the claimed fields of the validator + */ + function verifyValidatorFields( + uint40 validatorIndex, + bytes32 beaconStateRoot, + bytes calldata proof, + bytes32[] calldata validatorFields + ) internal view { + + require(validatorFields.length == 2**VALIDATOR_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length"); + + /** + * Note: the length of the validator merkle proof is BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1. + * There is an additional layer added by hashing the root with the length of the validator list + */ + require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length"); + uint256 index = (VALIDATOR_TREE_ROOT_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex); + // merkleize the validatorFields to get the leaf to prove + bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields); + + // verify the proof of the validatorRoot against the beaconStateRoot + require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, validatorRoot, index), "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof"); + } + + /** + * @notice This function verifies merkle proofs of the balance of a certain validator against a beacon chain state root + * @param validatorIndex the index of the proven validator + * @param beaconStateRoot is the beacon chain state root to be proven against. + * @param proof is the proof of the balance against the beacon chain state root + * @param balanceRoot is the serialized balance used to prove the balance of the validator (refer to `getBalanceFromBalanceRoot` above for detailed explanation) + */ + function verifyValidatorBalance( + uint40 validatorIndex, + bytes32 beaconStateRoot, + bytes calldata proof, + bytes32 balanceRoot + ) internal view { + require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length"); + + /** + * the beacon state's balance list is a list of uint64 values, and these are grouped together in 4s when merkleized. + * Therefore, the index of the balance of a validator is validatorIndex/4 + */ + uint256 balanceIndex = uint256(validatorIndex/4); + balanceIndex = (BALANCE_INDEX << (BALANCE_TREE_HEIGHT + 1)) | balanceIndex; + + require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, balanceRoot, balanceIndex), "BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof"); + } + + /** + * @notice This function verifies the slot and the withdrawal fields for a given withdrawal + * @param beaconStateRoot is the beacon chain state root to be proven against. + * @param proofs is the provided set of merkle proofs + * @param withdrawalFields is the serialized withdrawal container to be proven + */ + function verifyWithdrawalProofs( + bytes32 beaconStateRoot, + WithdrawalProofs calldata proofs, + bytes32[] calldata withdrawalFields + ) internal view { + require(withdrawalFields.length == 2**WITHDRAWAL_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalFields has incorrect length"); + + require(proofs.blockHeaderRootIndex < 2**BLOCK_ROOTS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: blockRootIndex is too large"); + require(proofs.withdrawalIndex < 2**WITHDRAWALS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalIndex is too large"); + + // verify the block header proof length + require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT), + "BeaconChainProofs.verifyWithdrawalProofs: blockHeaderProof has incorrect length"); + require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1), + "BeaconChainProofs.verifyWithdrawalProofs: withdrawalProof has incorrect length"); + require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT), + "BeaconChainProofs.verifyWithdrawalProofs: executionPayloadProof has incorrect length"); + require(proofs.slotProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT), + "BeaconChainProofs.verifyWithdrawalProofs: slotProof has incorrect length"); + require(proofs.blockNumberProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT), + "BeaconChainProofs.verifyWithdrawalProofs: blockNumberProof has incorrect length"); + + + /** + * Computes the block_header_index relative to the beaconStateRoot. It concatenates the indexes of all the + * intermediate root indexes from the bottom of the sub trees (the block header container) to the top of the tree + */ + uint256 blockHeaderIndex = BLOCK_ROOTS_INDEX << (BLOCK_ROOTS_TREE_HEIGHT) | uint256(proofs.blockHeaderRootIndex); + // Verify the blockHeaderRoot against the beaconStateRoot + require(Merkle.verifyInclusionSha256(proofs.blockHeaderProof, beaconStateRoot, proofs.blockHeaderRoot, blockHeaderIndex), + "BeaconChainProofs.verifyWithdrawalProofs: Invalid block header merkle proof"); + + //Next we verify the slot against the blockHeaderRoot + require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof"); + + // Next we verify the executionPayloadRoot against the blockHeaderRoot + uint256 executionPayloadIndex = BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)| EXECUTION_PAYLOAD_INDEX ; + require(Merkle.verifyInclusionSha256(proofs.executionPayloadProof, proofs.blockHeaderRoot, proofs.executionPayloadRoot, executionPayloadIndex), + "BeaconChainProofs.verifyWithdrawalProofs: Invalid executionPayload merkle proof"); + + // Next we verify the blockNumberRoot against the executionPayload root + require(Merkle.verifyInclusionSha256(proofs.blockNumberProof, proofs.executionPayloadRoot, proofs.blockNumberRoot, BLOCK_NUMBER_INDEX), + "BeaconChainProofs.verifyWithdrawalProofs: Invalid blockNumber merkle proof"); + + /** + * Next we verify the withdrawal fields against the blockHeaderRoot: + * First we compute the withdrawal_index relative to the blockHeaderRoot by concatenating the indexes of all the + * intermediate root indexes from the bottom of the sub trees (the withdrawal container) to the top, the blockHeaderRoot. + * Then we calculate merkleize the withdrawalFields container to calculate the the withdrawalRoot. + * Finally we verify the withdrawalRoot against the executionPayloadRoot. + */ + uint256 withdrawalIndex = WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1) | uint256(proofs.withdrawalIndex); + bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields); + require(Merkle.verifyInclusionSha256(proofs.withdrawalProof, proofs.executionPayloadRoot, withdrawalRoot, withdrawalIndex), + "BeaconChainProofs.verifyWithdrawalProofs: Invalid withdrawal merkle proof"); + } + +} diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IBeaconChainOracle.sol b/src/test/integration/deprecatedInterfaces/mainnet/IBeaconChainOracle.sol new file mode 100644 index 0000000000..4a9a5ac366 --- /dev/null +++ b/src/test/integration/deprecatedInterfaces/mainnet/IBeaconChainOracle.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +/** + * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928 + * @title Interface for the BeaconStateOracle contract. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ +interface IBeaconChainOracle_DeprecatedM1 { + /// @notice Largest blockNumber that has been confirmed by the oracle. + function latestConfirmedOracleBlockNumber() external view returns(uint64); + /// @notice Mapping: Beacon Chain blockNumber => the Beacon Chain state root at the specified blockNumber. + /// @dev This will return `bytes32(0)` if the state root at the specified blockNumber is not yet confirmed. + function beaconStateRootAtBlockNumber(uint64 blockNumber) external view returns(bytes32); + + /// @notice Mapping: address => whether or not the address is in the set of oracle signers. + function isOracleSigner(address _oracleSigner) external view returns(bool); + + /// @notice Mapping: Beacon Chain blockNumber => oracle signer address => whether or not the oracle signer has voted on the state root at the blockNumber. + function hasVoted(uint64 blockNumber, address oracleSigner) external view returns(bool); + + /// @notice Mapping: Beacon Chain blockNumber => state root => total number of oracle signer votes for the state root at the blockNumber. + function stateRootVotes(uint64 blockNumber, bytes32 stateRoot) external view returns(uint256); + + /// @notice Total number of members of the set of oracle signers. + function totalOracleSigners() external view returns(uint256); + + /** + * @notice Number of oracle signers that must vote for a state root in order for the state root to be confirmed. + * Adjustable by this contract's owner through use of the `setThreshold` function. + * @dev We note that there is an edge case -- when the threshold is adjusted downward, if a state root already has enough votes to meet the *new* threshold, + * the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations. + */ + function threshold() external view returns(uint256); + + /** + * @notice Owner-only function used to modify the value of the `threshold` variable. + * @param _threshold Desired new value for the `threshold` variable. Function will revert if this is set to zero. + */ + function setThreshold(uint256 _threshold) external; + + /** + * @notice Owner-only function used to add a signer to the set of oracle signers. + * @param _oracleSigners Array of address to be added to the set. + * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already in the set of oracle signers. + */ + function addOracleSigners(address[] memory _oracleSigners) external; + + /** + * @notice Owner-only function used to remove a signer from the set of oracle signers. + * @param _oracleSigners Array of address to be removed from the set. + * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already not in the set of oracle signers. + */ + function removeOracleSigners(address[] memory _oracleSigners) external; + + /** + * @notice Called by a member of the set of oracle signers to assert that the Beacon Chain state root is `stateRoot` at `blockNumber`. + * @dev The state root will be finalized once the total number of votes *for this exact state root at this exact blockNumber* meets the `threshold` value. + * @param blockNumber The Beacon Chain blockNumber of interest. + * @param stateRoot The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`. + */ + function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external; +} diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IDelayedWithdrawalRouter.sol b/src/test/integration/deprecatedInterfaces/mainnet/IDelayedWithdrawalRouter.sol new file mode 100644 index 0000000000..2ee0914d49 --- /dev/null +++ b/src/test/integration/deprecatedInterfaces/mainnet/IDelayedWithdrawalRouter.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +/// @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928 +interface IDelayedWithdrawalRouter_DeprecatedM1 { + // struct used to pack data into a single storage slot + struct DelayedWithdrawal { + uint224 amount; + uint32 blockCreated; + } + + // struct used to store a single users delayedWithdrawal data + struct UserDelayedWithdrawals { + uint256 delayedWithdrawalsCompleted; + DelayedWithdrawal[] delayedWithdrawals; + } + + /** + * @notice Creates an delayed withdrawal for `msg.value` to the `recipient`. + * @dev Only callable by the `podOwner`'s EigenPod contract. + */ + function createDelayedWithdrawal(address podOwner, address recipient) external payable; + + /** + * @notice Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period. + * @param recipient The address to claim delayedWithdrawals for. + * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming. + */ + function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfWithdrawalsToClaim) external; + + /** + * @notice Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period. + * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming. + */ + function claimDelayedWithdrawals(uint256 maxNumberOfWithdrawalsToClaim) external; + + /// @notice Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable. + function setWithdrawalDelayBlocks(uint256 newValue) external; + + /// @notice Getter function for the mapping `_userWithdrawals` + function userWithdrawals(address user) external view returns (UserDelayedWithdrawals memory); + + /// @notice Getter function to get all delayedWithdrawals of the `user` + function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory); + + /// @notice Getter function to get all delayedWithdrawals that are currently claimable by the `user` + function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory); + + /// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array + function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (DelayedWithdrawal memory); + + /// @notice Getter function for fetching the length of the delayedWithdrawals array of a specific user + function userWithdrawalsLength(address user) external view returns (uint256); + + /// @notice Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable + function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool); + + /** + * @notice Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner, + * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced). + */ + function withdrawalDelayBlocks() external view returns (uint256); +} diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol new file mode 100644 index 0000000000..8efe93ccd0 --- /dev/null +++ b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "./BeaconChainProofs.sol"; +import "./IEigenPodManager.sol"; +import "./IBeaconChainOracle.sol"; + +/** + * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928 + * @title The implementation contract used for restaking beacon chain ETH on EigenLayer + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice The main functionalities are: + * - creating new ETH validators with their withdrawal credentials pointed to this contract + * - proving from beacon chain state roots that withdrawal credentials are pointed to this contract + * - proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials + * pointed to this contract + * - updating aggregate balances in the EigenPodManager + * - withdrawing eth when withdrawals are initiated + * @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 + */ +interface IEigenPod_DeprecatedM1 { + enum VALIDATOR_STATUS { + INACTIVE, // doesnt exist + ACTIVE, // staked on ethpos and withdrawal credentials are pointed to the EigenPod + OVERCOMMITTED, // proven to be overcommitted to EigenLayer + WITHDRAWN // withdrawn from the Beacon Chain + } + + // this struct keeps track of PartialWithdrawalClaims + struct PartialWithdrawalClaim { + PARTIAL_WITHDRAWAL_CLAIM_STATUS status; + // block at which the PartialWithdrawalClaim was created + uint32 creationBlockNumber; + // last block (inclusive) in which the PartialWithdrawalClaim can be fraudproofed + uint32 fraudproofPeriodEndBlockNumber; + // amount of ETH -- in Gwei -- to be withdrawn until completion of this claim + uint64 partialWithdrawalAmountGwei; + } + + enum PARTIAL_WITHDRAWAL_CLAIM_STATUS { + REDEEMED, + PENDING, + FAILED + } + + /// @notice The amount of eth, in gwei, that is restaked per validator + function REQUIRED_BALANCE_GWEI() external view returns(uint64); + + /// @notice The amount of eth, in wei, that is restaked per validator + function REQUIRED_BALANCE_WEI() external view returns(uint256); + + /// @notice this is a mapping of validator indices to a Validator struct containing pertinent info about the validator + function validatorStatus(uint40 validatorIndex) external view returns(VALIDATOR_STATUS); + + /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer), + function restakedExecutionLayerGwei() external view returns(uint64); + + /// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager + function initialize(address owner) external; + + /// @notice Called by EigenPodManager when the owner wants to create another ETH validator. + function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable; + + /** + * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address + * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain. + * @dev Called during withdrawal or slashing. + * @dev Note that this function is marked as non-reentrant to prevent the recipient calling back into it + */ + function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external; + + /// @notice The single EigenPodManager for EigenLayer + function eigenPodManager() external view returns (IEigenPodManager_DeprecatedM1); + + /// @notice The owner of this EigenPod + function podOwner() external view returns (address); + + /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`. + function hasRestaked() external view returns (bool); + + /// @notice block number of the most recent withdrawal + function mostRecentWithdrawalBlockNumber() external view returns (uint64); + + + ///@notice mapping that tracks proven partial withdrawals + function provenPartialWithdrawal(uint40 validatorIndex, uint64 slot) external view returns (bool); + + /** + * @notice This function verifies that the withdrawal credentials of the podOwner are pointed to + * this contract. It also verifies the current (not effective) balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state + * root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer. + * @param oracleBlockNumber is the Beacon Chain blockNumber whose state root the `proof` will be proven against. + * @param validatorIndex is the index of the validator being proven, refer to consensus specs + * @param proofs is the bytes that prove the ETH validator's balance and withdrawal credentials against a beacon chain state root + * @param validatorFields are the fields of the "Validator Container", refer to consensus specs + * for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator + */ + function verifyWithdrawalCredentialsAndBalance( + uint64 oracleBlockNumber, + uint40 validatorIndex, + BeaconChainProofs_DeprecatedM1.ValidatorFieldsAndBalanceProofs memory proofs, + bytes32[] calldata validatorFields + ) external; + + /** + * @notice This function records an overcommitment of stake to EigenLayer on behalf of a certain ETH validator. + * If successful, the overcommitted balance is penalized (available for withdrawal whenever the pod's balance allows). + * The ETH validator's shares in the enshrined beaconChainETH strategy are also removed from the StrategyManager and undelegated. + * @param oracleBlockNumber The oracleBlockNumber whose state root the `proof` will be proven against. + * Must be within `VERIFY_OVERCOMMITTED_WINDOW_BLOCKS` of the current block. + * @param validatorIndex is the index of the validator being proven, refer to consensus specs + * @param proofs is the proof of the validator's balance and validatorFields in the balance tree and the balanceRoot to prove for + * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to + * the StrategyManager in case it must be removed from the list of the podOwners strategies + * @param validatorFields are the fields of the "Validator Container", refer to consensus specs + * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator + */ + function verifyOvercommittedStake( + uint40 validatorIndex, + BeaconChainProofs_DeprecatedM1.ValidatorFieldsAndBalanceProofs calldata proofs, + bytes32[] calldata validatorFields, + uint256 beaconChainETHStrategyIndex, + uint64 oracleBlockNumber + ) external; + + /** + * @notice This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod + * @param withdrawalProofs is the information needed to check the veracity of the block number and withdrawal being proven + * @param validatorFieldsProof is the proof of the validator's fields in the validator tree + * @param withdrawalFields are the fields of the withdrawal being proven + * @param validatorFields are the fields of the validator being proven + * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to + * the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies + */ + function verifyAndProcessWithdrawal( + BeaconChainProofs_DeprecatedM1.WithdrawalProofs calldata withdrawalProofs, + bytes calldata validatorFieldsProof, + bytes32[] calldata validatorFields, + bytes32[] calldata withdrawalFields, + uint256 beaconChainETHStrategyIndex, + uint64 oracleBlockNumber + ) external; + + /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false + function withdrawBeforeRestaking() external; +} diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol new file mode 100644 index 0000000000..5c791c2325 --- /dev/null +++ b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "./IStrategyManager.sol"; +import "./IEigenPod.sol"; +import "./IBeaconChainOracle.sol"; +import "src/contracts/interfaces/IPausable.sol"; + +/** + * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928 + * @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials pointed to EigenLayer. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ + +interface IEigenPodManager_DeprecatedM1 is IPausable { + /** + * @notice Creates an EigenPod for the sender. + * @dev Function will revert if the `msg.sender` already has an EigenPod. + */ + function createPod() external; + + /** + * @notice Stakes for a new beacon chain validator on the sender's EigenPod. + * Also creates an EigenPod for the sender if they don't have one already. + * @param pubkey The 48 bytes public key of the beacon chain validator. + * @param signature The validator's signature of the deposit data. + * @param depositDataRoot The root/hash of the deposit data for the validator's deposit. + */ + function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable; + + /** + * @notice Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod. + * @param podOwner The owner of the pod whose balance must be deposited. + * @param amount The amount of ETH to 'deposit' (i.e. be credited to the podOwner). + * @dev Callable only by the podOwner's EigenPod contract. + */ + function restakeBeaconChainETH(address podOwner, uint256 amount) external; + + /** + * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the + * balance of a validator is lower than how much stake they have committed to EigenLayer + * @param podOwner The owner of the pod whose balance must be removed. + * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to + * the StrategyManager in case it must be removed from the list of the podOwner's strategies + * @param amount The amount of ETH to remove. + * @dev Callable only by the podOwner's EigenPod contract. + */ + function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external; + + /** + * @notice Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain. + * @param podOwner The owner of the pod whose balance must be withdrawn. + * @param recipient The recipient of the withdrawn ETH. + * @param amount The amount of ETH to withdraw. + * @dev Callable only by the StrategyManager contract. + */ + function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount) external; + + /** + * @notice Updates the oracle contract that provides the beacon chain state root + * @param newBeaconChainOracle is the new oracle contract being pointed to + * @dev Callable only by the owner of this contract (i.e. governance) + */ + function updateBeaconChainOracle(IBeaconChainOracle_DeprecatedM1 newBeaconChainOracle) external; + + /// @notice Returns the address of the `podOwner`'s EigenPod if it has been deployed. + function ownerToPod(address podOwner) external view returns(IEigenPod_DeprecatedM1); + + /// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not). + function getPod(address podOwner) external view returns(IEigenPod_DeprecatedM1); + + /// @notice Oracle contract that provides updates to the beacon chain's state + function beaconChainOracle() external view returns(IBeaconChainOracle_DeprecatedM1); + + /// @notice Returns the Beacon Chain state root at `blockNumber`. Reverts if the Beacon Chain state root at `blockNumber` has not yet been finalized. + function getBeaconChainStateRoot(uint64 blockNumber) external view returns(bytes32); + + /// @notice EigenLayer's StrategyManager contract + function strategyManager() external view returns(IStrategyManager_DeprecatedM1); + + /// @notice EigenLayer's Slasher contract + function slasher() external view returns(ISlasher); + + function hasPod(address podOwner) external view returns (bool); +} diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol b/src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol new file mode 100644 index 0000000000..c0a45f82a2 --- /dev/null +++ b/src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "src/contracts/interfaces/IStrategy.sol"; +import "src/contracts/interfaces/ISlasher.sol"; +import "src/contracts/interfaces/IDelegationManager.sol"; + +/** + * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928 + * @title Interface for the primary entrypoint for funds into EigenLayer. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice See the `StrategyManager` contract itself for implementation details. + */ +interface IStrategyManager_DeprecatedM1 { + // packed struct for queued withdrawals; helps deal with stack-too-deep errors + struct WithdrawerAndNonce { + address withdrawer; + uint96 nonce; + } + + /** + * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored. + * In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or `completeQueuedWithdrawal`, + * the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked against the + * stored hash in order to confirm the integrity of the submitted data. + */ + struct QueuedWithdrawal { + IStrategy[] strategies; + uint256[] shares; + address depositor; + WithdrawerAndNonce withdrawerAndNonce; + uint32 withdrawalStartBlock; + address delegatedAddress; + } + + /** + * @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender` + * @param strategy is the specified strategy where deposit is to be made, + * @param token is the denomination in which the deposit is to be made, + * @param amount is the amount of token to be deposited in the strategy by the depositor + * @return shares The amount of new shares in the `strategy` created as part of the action. + * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf. + * @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen). + * + * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors + * where the token balance and corresponding strategy shares are not in sync upon reentrancy. + */ + function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount) + external + returns (uint256 shares); + + + /** + * @notice Deposits `amount` of beaconchain ETH into this contract on behalf of `staker` + * @param staker is the entity that is restaking in eigenlayer, + * @param amount is the amount of beaconchain ETH being restaked, + * @dev Only callable by EigenPodManager. + */ + function depositBeaconChainETH(address staker, uint256 amount) external; + + /** + * @notice Records an overcommitment event on behalf of a staker. The staker's beaconChainETH shares are decremented by `amount`. + * @param overcommittedPodOwner is the pod owner to be slashed + * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy in case it must be removed, + * @param amount is the amount to decrement the slashedAddress's beaconChainETHStrategy shares + * @dev Only callable by EigenPodManager. + */ + function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) + external; + + /** + * @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`, + * who must sign off on the action. + * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed + * purely to help one address deposit 'for' another. + * @param strategy is the specified strategy where deposit is to be made, + * @param token is the denomination in which the deposit is to be made, + * @param amount is the amount of token to be deposited in the strategy by the depositor + * @param staker the staker that the deposited assets will be credited to + * @param expiry the timestamp at which the signature expires + * @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward + * following EIP-1271 if the `staker` is a contract + * @return shares The amount of new shares in the `strategy` created as part of the action. + * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf. + * @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically those + * targeting stakers who may be attempting to undelegate. + * @dev Cannot be called on behalf of a staker that is 'frozen' (this function will revert if the `staker` is frozen). + * + * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors + * where the token balance and corresponding strategy shares are not in sync upon reentrancy + */ + function depositIntoStrategyWithSignature( + IStrategy strategy, + IERC20 token, + uint256 amount, + address staker, + uint256 expiry, + bytes memory signature + ) + external + returns (uint256 shares); + + /// @notice Returns the current shares of `user` in `strategy` + function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares); + + /** + * @notice Get all details on the depositor's deposits and corresponding shares + * @return (depositor's strategies, shares in these strategies) + */ + function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory); + + /// @notice Simple getter function that returns `stakerStrategyList[staker].length`. + function stakerStrategyListLength(address staker) external view returns (uint256); + + /** + * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`. + * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function. + * User shares are decreased in this function, but the total number of shares in each strategy remains the same. + * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where + * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures + * that the value per share reported by each strategy will remain consistent, and that the shares will continue + * to accrue gains during the enforced withdrawal waiting period. + * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies + * for which `msg.sender` is withdrawing 100% of their shares + * @param strategies The Strategies to withdraw from + * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array + * @param withdrawer The address that can complete the withdrawal and will receive any withdrawn funds or shares upon completing the withdrawal + * @param undelegateIfPossible If this param is marked as 'true' *and the withdrawal will result in `msg.sender` having no shares in any Strategy,* + * then this function will also make an internal call to `undelegate(msg.sender)` to undelegate the `msg.sender`. + * @return The 'withdrawalRoot' of the newly created Queued Withdrawal + * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then + * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input + * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in + * `stakerStrategyList` to lowest index + * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and + * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed + * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in + * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod). + */ + function queueWithdrawal( + uint256[] calldata strategyIndexes, + IStrategy[] calldata strategies, + uint256[] calldata shares, + address withdrawer, + bool undelegateIfPossible + ) + external returns(bytes32); + + /** + * @notice Used to complete the specified `queuedWithdrawal`. The function caller must match `queuedWithdrawal.withdrawer` + * @param queuedWithdrawal The QueuedWithdrawal to complete. + * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array + * of the `queuedWithdrawal`. This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused) + * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array + * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves + * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies + * will simply be transferred to the caller directly. + * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw` + */ + function completeQueuedWithdrawal( + QueuedWithdrawal calldata queuedWithdrawal, + IERC20[] calldata tokens, + uint256 middlewareTimesIndex, + bool receiveAsTokens + ) + external; + + /** + * @notice Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer` + * @param queuedWithdrawals The QueuedWithdrawals to complete. + * @param tokens Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array. + * @param middlewareTimesIndexes One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index. + * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves + * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies + * will simply be transferred to the caller directly. + * @dev Array-ified version of `completeQueuedWithdrawal` + * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw` + */ + function completeQueuedWithdrawals( + QueuedWithdrawal[] calldata queuedWithdrawals, + IERC20[][] calldata tokens, + uint256[] calldata middlewareTimesIndexes, + bool[] calldata receiveAsTokens + ) + external; + + /** + * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one) + * @param slashedAddress is the frozen address that is having its shares slashed + * @param recipient is the address that will receive the slashed funds, which could e.g. be a harmed party themself, + * or a MerkleDistributor-type contract that further sub-divides the slashed funds. + * @param strategies Strategies to slash + * @param shareAmounts The amount of shares to slash in each of the provided `strategies` + * @param tokens The tokens to use as input to the `withdraw` function of each of the provided `strategies` + * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies + * for which `msg.sender` is withdrawing 100% of their shares + * @param recipient The slashed funds are withdrawn as tokens to this address. + * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then + * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input + * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in + * `stakerStrategyList` to lowest index + */ + function slashShares( + address slashedAddress, + address recipient, + IStrategy[] calldata strategies, + IERC20[] calldata tokens, + uint256[] calldata strategyIndexes, + uint256[] calldata shareAmounts + ) + external; + + /** + * @notice Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one) + * @param recipient The funds in the slashed withdrawal are withdrawn as tokens to this address. + * @param queuedWithdrawal The previously queued withdrawal to be slashed + * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` + * array of the `queuedWithdrawal`. + * @param indicesToSkip Optional input parameter -- indices in the `strategies` array to skip (i.e. not call the 'withdraw' function on). This input exists + * so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function, + * then the malicious strategy can be skipped (with the shares in effect "burned"), while the non-malicious strategies are still called as normal. + */ + function slashQueuedWithdrawal(address recipient, QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256[] calldata indicesToSkip) + external; + + /// @notice Returns the keccak256 hash of `queuedWithdrawal`. + function calculateWithdrawalRoot( + QueuedWithdrawal memory queuedWithdrawal + ) + external + pure + returns (bytes32); + + /** + * @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into + * @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already) + */ + function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external; + + /** + * @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into + * @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it) + */ + function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external; + + /// @notice Returns the single, central Delegation contract of EigenLayer + function delegation() external view returns (IDelegationManager); + + /// @notice Returns the single, central Slasher contract of EigenLayer + function slasher() external view returns (ISlasher); + + /// @notice returns the enshrined, virtual 'beaconChainETH' Strategy + function beaconChainETHStrategy() external view returns (IStrategy); + + /// @notice Returns the number of blocks that must pass between the time a withdrawal is queued and the time it can be completed + function withdrawalDelayBlocks() external view returns (uint256); + + + + /// VIEW FUNCTIONS FOR PUBLIC VARIABLES, NOT IN ORIGINAL INTERFACE + function withdrawalRootPending(bytes32 withdrawalRoot) external view returns (bool); + + function numWithdrawalsQueued(address staker) external view returns (uint256); +} diff --git a/src/test/integration/mocks/BeaconChainMock.t.sol b/src/test/integration/mocks/BeaconChainMock.t.sol index c0f7b37700..2429859cea 100644 --- a/src/test/integration/mocks/BeaconChainMock.t.sol +++ b/src/test/integration/mocks/BeaconChainMock.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import "src/contracts/libraries/BeaconChainProofs.sol"; import "src/contracts/libraries/Merkle.sol"; +import "src/contracts/pods/EigenPodManager.sol"; import "src/test/integration/TimeMachine.t.sol"; import "src/test/integration/mocks/BeaconChainOracleMock.t.sol"; @@ -46,7 +47,7 @@ contract BeaconChainMock is Test { } uint40 nextValidatorIndex = 0; - uint64 nextTimestamp; + uint64 public nextTimestamp; // Sequential list of created Validators // Validator[] validators; @@ -55,14 +56,16 @@ contract BeaconChainMock is Test { mapping(uint40 => Validator) validators; BeaconChainOracleMock oracle; + EigenPodManager eigenPodManager; /// @dev All withdrawals are processed with index == 0 uint64 constant WITHDRAWAL_INDEX = 0; uint constant GWEI_TO_WEI = 1e9; - constructor(TimeMachine timeMachine, BeaconChainOracleMock beaconChainOracle) { + constructor(TimeMachine timeMachine, BeaconChainOracleMock beaconChainOracle, EigenPodManager _eigenPodManager) { nextTimestamp = timeMachine.proofGenStartTime(); oracle = beaconChainOracle; + eigenPodManager = _eigenPodManager; } /** @@ -157,6 +160,10 @@ contract BeaconChainMock is Test { return update; } + function setNextTimestamp(uint64 timestamp) public { + nextTimestamp = timestamp; + } + function balanceOfGwei(uint40 validatorIndex) public view returns (uint64) { return validators[validatorIndex].effectiveBalanceGwei; } @@ -218,9 +225,10 @@ contract BeaconChainMock is Test { // Send the block root to the oracle and increment timestamp: proof.oracleTimestamp = uint64(nextTimestamp); + oracle.setBlockRoot(nextTimestamp, blockRoot); nextTimestamp++; - + return proof; } @@ -733,11 +741,20 @@ contract BeaconChainMock is Test { uint64 withdrawalIndex, uint64 oracleTimestamp ) internal view returns (BeaconChainProofs.WithdrawalProof memory) { + uint256 withdrawalProofLength; + uint256 timestampProofLength; + if (block.timestamp > eigenPodManager.denebForkTimestamp()) { + withdrawalProofLength = WITHDRAWAL_PROOF_LEN_DENEB; + timestampProofLength = TIMESTAMP_PROOF_LEN_DENEB; + } else { + withdrawalProofLength = WITHDRAWAL_PROOF_LEN_CAPELLA; + timestampProofLength = TIMESTAMP_PROOF_LEN_CAPELLA; + } return BeaconChainProofs.WithdrawalProof({ - withdrawalProof: new bytes(WITHDRAWAL_PROOF_LEN_CAPELLA), + withdrawalProof: new bytes(withdrawalProofLength), slotProof: new bytes(SLOT_PROOF_LEN), executionPayloadProof: new bytes(EXECPAYLOAD_PROOF_LEN), - timestampProof: new bytes(TIMESTAMP_PROOF_LEN_CAPELLA), + timestampProof: new bytes(timestampProofLength), historicalSummaryBlockRootProof: new bytes(HISTSUMMARY_PROOF_LEN), blockRootIndex: 0, historicalSummaryIndex: 0, @@ -797,4 +814,4 @@ contract BeaconChainMock is Test { assembly { a := and(creds, mask) } } -} \ No newline at end of file +} diff --git a/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol index 4c7149fbb2..e9f3dd4508 100644 --- a/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol +++ b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.12; import "src/test/integration/IntegrationChecks.t.sol"; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils { @@ -11,12 +11,14 @@ contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); - // Create a staker and an operator with a nonzero balance and corresponding strategies (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); @@ -48,12 +50,15 @@ contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); // Create a staker and an operator with a nonzero balance and corresponding strategies (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); diff --git a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol index 3c6afde3e0..5d90763f80 100644 --- a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.12; import "src/test/integration/IntegrationChecks.t.sol"; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { @@ -20,7 +20,8 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -34,6 +35,9 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -79,7 +83,8 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -93,6 +98,9 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -141,7 +149,8 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -155,6 +164,9 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -203,7 +215,8 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -217,6 +230,9 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -271,7 +287,8 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: NO_ASSETS | HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create a staker and operator @@ -281,6 +298,9 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -299,4 +319,4 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated"); staker.delegateTo(operator); } -} \ No newline at end of file +} diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol index 43a303ef7f..a0e0f155e4 100644 --- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; import "src/test/integration/IntegrationChecks.t.sol"; contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUtils { @@ -18,7 +18,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -34,6 +35,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti ) = _newRandomStaker(); (User operator1, ,) = _newRandomOperator(); (User operator2, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -87,7 +91,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -103,6 +108,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti ) = _newRandomStaker(); (User operator1, ,) = _newRandomOperator(); (User operator2, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -169,7 +177,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST, // not holding ETH since we can only deposit 32 ETH multiples - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -185,6 +194,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti ) = _newRandomStaker(); (User operator1, ,) = _newRandomOperator(); (User operator2, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -229,11 +241,13 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti // 6. Deposit into Strategies uint[] memory sharesAdded = _calculateExpectedShares(strategies, numTokensRemaining); staker.depositIntoEigenlayer(strategies, numTokensRemaining); + tokenBalances = _calculateExpectedTokens(strategies, shares); check_Deposit_State(staker, strategies, sharesAdded); } { // 7. Queue Withdrawal + shares = _calculateExpectedShares(strategies, tokenBalances); IDelegationManager.Withdrawal[] memory newWithdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory newWithdrawalRoots = _getWithdrawalHashes(newWithdrawals); check_QueuedWithdrawal_State(staker, operator2, strategies, shares, newWithdrawals, newWithdrawalRoots); @@ -256,7 +270,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST, // not holding ETH since we can only deposit 32 ETH multiples - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -272,6 +287,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti ) = _newRandomStaker(); (User operator1, ,) = _newRandomOperator(); (User operator2, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -311,16 +329,18 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti // 5. Deposit into Strategies uint[] memory sharesAdded = _calculateExpectedShares(strategies, numTokensRemaining); staker.depositIntoEigenlayer(strategies, numTokensRemaining); + tokenBalances = _calculateExpectedTokens(strategies, shares); check_Deposit_State(staker, strategies, sharesAdded); // 6. Delegate to a new operator staker.delegateTo(operator2); - check_Delegation_State(staker, operator2, strategies, tokenBalances); + check_Delegation_State(staker, operator2, strategies, shares); assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); } { // 7. Queue Withdrawal + shares = _calculateExpectedShares(strategies, tokenBalances); IDelegationManager.Withdrawal[] memory newWithdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory newWithdrawalRoots = _getWithdrawalHashes(newWithdrawals); check_QueuedWithdrawal_State(staker, operator2, strategies, shares, newWithdrawals, newWithdrawalRoots); @@ -343,7 +363,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create operators and a staker @@ -354,6 +375,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti ) = _newRandomStaker(); (User operator1, ,) = _newRandomOperator(); (User operator2, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -361,6 +385,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); + uint[] memory withdrawnTokenBalances = _calculateExpectedTokens(strategies, shares); check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator @@ -382,7 +407,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti } //5. Deposit into Strategies - staker.depositIntoEigenlayer(strategies, tokenBalances); + staker.depositIntoEigenlayer(strategies, withdrawnTokenBalances); + shares = _calculateExpectedShares(strategies, withdrawnTokenBalances); check_Deposit_State(staker, strategies, shares); // 6. Delegate to a new operator @@ -412,7 +438,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create operators and a staker @@ -423,6 +450,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti ) = _newRandomStaker(); (User operator1, ,) = _newRandomOperator(); (User operator2, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -430,6 +460,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); + uint[] memory withdrawnTokenBalances = _calculateExpectedTokens(strategies, shares); check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator @@ -451,7 +482,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti } //5. Deposit into Strategies - staker.depositIntoEigenlayer(strategies, tokenBalances); + staker.depositIntoEigenlayer(strategies, withdrawnTokenBalances); check_Deposit_State(staker, strategies, shares); // 6. Delegate to a new operator @@ -460,6 +491,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); // 7. Queue Withdrawal + shares = _calculateExpectedShares(strategies, withdrawnTokenBalances); withdrawals = staker.queueWithdrawals(strategies, shares); withdrawalRoots = _getWithdrawalHashes(withdrawals); check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); @@ -474,4 +506,4 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti check_Withdrawal_AsShares_State(staker, operator2, withdrawals[i], strategies, shares); } } -} \ 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 4332935d15..32afc81cd2 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; import "src/test/integration/IntegrationChecks.t.sol"; contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUtils { @@ -16,7 +16,8 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -31,6 +32,9 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -76,7 +80,8 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -91,6 +96,9 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -129,7 +137,8 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -144,6 +153,9 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -183,7 +195,8 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and a staker with: @@ -198,6 +211,9 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -229,4 +245,4 @@ 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"); } -} \ No newline at end of file +} diff --git a/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol b/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol index ec9977223a..a782363db2 100644 --- a/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.12; import "src/test/integration/IntegrationChecks.t.sol"; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; contract Integration_Deposit_Delegate_UpdateBalance is IntegrationCheckUtils { @@ -16,7 +16,8 @@ contract Integration_Deposit_Delegate_UpdateBalance is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); /// 0. Create an operator and staker with some underlying assets @@ -26,6 +27,9 @@ contract Integration_Deposit_Delegate_UpdateBalance is IntegrationCheckUtils { uint[] memory tokenBalances ) = _newRandomStaker(); (User operator, ,) = _newRandomOperator(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); @@ -68,4 +72,4 @@ contract Integration_Deposit_Delegate_UpdateBalance is IntegrationCheckUtils { assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); } -} \ No newline at end of file +} diff --git a/src/test/integration/tests/Deposit_Queue_Complete.t.sol b/src/test/integration/tests/Deposit_Queue_Complete.t.sol index 5689b86765..4d84b8bea8 100644 --- a/src/test/integration/tests/Deposit_Queue_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Queue_Complete.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; import "src/test/integration/IntegrationChecks.t.sol"; contract Integration_Deposit_QueueWithdrawal_Complete is IntegrationCheckUtils { @@ -15,11 +15,14 @@ contract Integration_Deposit_QueueWithdrawal_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); // Create a staker with a nonzero balance and corresponding strategies (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); // 1. Deposit into strategy staker.depositIntoEigenlayer(strategies, tokenBalances); @@ -49,11 +52,14 @@ contract Integration_Deposit_QueueWithdrawal_Complete is IntegrationCheckUtils { _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); // Create a staker with a nonzero balance and corresponding strategies (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); // 1. Deposit into strategy staker.depositIntoEigenlayer(strategies, tokenBalances); diff --git a/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol index cd93188e40..1e69fd8faa 100644 --- a/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import "src/test/integration/User.t.sol"; +import "src/test/integration/users/User.t.sol"; import "src/test/integration/IntegrationChecks.t.sol"; contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationCheckUtils { @@ -10,11 +10,14 @@ contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationChe _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); // Create a staker with a nonzero balance and corresponding strategies (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); // 1. Staker deposits into strategy staker.depositIntoEigenlayer(strategies, tokenBalances); @@ -43,11 +46,14 @@ contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationChe _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, - _userTypes: DEFAULT | ALT_METHODS + _userTypes: DEFAULT | ALT_METHODS, + _forkTypes: LOCAL | MAINNET }); // Create a staker with a nonzero balance and corresponding strategies (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker(); + // Upgrade contracts if forkType is not local + _upgradeEigenLayerContracts(); // 1. Staker deposits into strategy staker.depositIntoEigenlayer(strategies, tokenBalances); diff --git a/src/test/integration/tests/Upgrade_Setup.t.sol b/src/test/integration/tests/Upgrade_Setup.t.sol new file mode 100644 index 0000000000..b1b2f2edda --- /dev/null +++ b/src/test/integration/tests/Upgrade_Setup.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "src/test/integration/IntegrationChecks.t.sol"; + +contract IntegrationMainnetFork_UpgradeSetup is IntegrationCheckUtils { + + // /// @notice Test upgrade setup is correct + // /// forge-config: default.fuzz.runs = 1 + // function test_mainnet_upgrade_setup(uint24 _random) public { + // _configRand({ + // _randomSeed: _random, + // _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + // _userTypes: DEFAULT | ALT_METHODS, + // _forkTypes: MAINNET + // }); + + // // // 1. Check proper state pre-upgrade + // // _verifyContractPointers(); + // // _verifyImplementations(); + // // _verifyContractsInitialized({isInitialDeployment: true}); + // // _verifyInitializationParams(); + + // // 2. Upgrade mainnet contracts + // _upgradeEigenLayerContracts(); + // _parseInitialDeploymentParams("script/configs/mainnet/M2_mainnet_upgrade.config.json"); + + // // 2. Verify upgrade setup + // _verifyContractPointers(); + // _verifyImplementations(); + // _verifyContractsInitialized({isInitialDeployment: true}); + // _verifyInitializationParams(); + // } + + // /// @notice Test upgrade setup is correct + // /// forge-config: default.fuzz.runs = 1 + // function test_holesky_upgrade_setup(uint24 _random) public { + // _configRand({ + // _randomSeed: _random, + // _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + // _userTypes: DEFAULT | ALT_METHODS, + // _forkTypes: HOLESKY + // }); + + // // // 1. Check proper state pre-upgrade + // // _verifyContractPointers(); + // // _verifyImplementations(); + // // _verifyContractsInitialized({isInitialDeployment: true}); + // // _verifyInitializationParams(); + + // // 2. Upgrade holesky contracts + // _upgradeEigenLayerContracts(); + // _parseInitialDeploymentParams("script/configs/holesky/M2_deploy_from_scratch.holesky.config.json"); + + // // 3. Verify upgrade setup + // _verifyContractPointers(); + // _verifyImplementations(); + // _verifyContractsInitialized({isInitialDeployment: true}); + // _verifyInitializationParams(); + // } + + /// @notice Ensure contracts point at each other correctly via constructors + /// override to remove ethPOSDeposit contract check + function _verifyContractPointers() internal virtual override view { + // AVSDirectory + require( + avsDirectory.delegation() == delegationManager, + "avsDirectory: delegationManager address not set correctly" + ); + // DelegationManager + require(delegationManager.slasher() == slasher, "delegationManager: slasher address not set correctly"); + require( + delegationManager.strategyManager() == strategyManager, + "delegationManager: strategyManager address not set correctly" + ); + require( + delegationManager.eigenPodManager() == eigenPodManager, + "delegationManager: eigenPodManager address not set correctly" + ); + // StrategyManager + require(strategyManager.slasher() == slasher, "strategyManager: slasher address not set correctly"); + require( + strategyManager.delegation() == delegationManager, + "strategyManager: delegationManager address not set correctly" + ); + require( + strategyManager.eigenPodManager() == eigenPodManager, + "strategyManager: eigenPodManager address not set correctly" + ); + // EPM + require( + eigenPodManager.eigenPodBeacon() == eigenPodBeacon, + "eigenPodManager: eigenPodBeacon contract address not set correctly" + ); + require( + eigenPodManager.strategyManager() == strategyManager, + "eigenPodManager: strategyManager contract address not set correctly" + ); + require(eigenPodManager.slasher() == slasher, "eigenPodManager: slasher contract address not set correctly"); + require( + eigenPodManager.delegationManager() == delegationManager, + "eigenPodManager: delegationManager contract address not set correctly" + ); + // DelayedWithdrawalRouter + require( + delayedWithdrawalRouter.eigenPodManager() == eigenPodManager, + "delayedWithdrawalRouterContract: eigenPodManager address not set correctly" + ); + } +} \ No newline at end of file diff --git a/src/test/integration/User.t.sol b/src/test/integration/users/User.t.sol similarity index 95% rename from src/test/integration/User.t.sol rename to src/test/integration/users/User.t.sol index 447fcb7b28..d7bf7d939c 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -13,6 +13,8 @@ import "src/contracts/interfaces/IStrategy.sol"; import "src/test/integration/TimeMachine.t.sol"; import "src/test/integration/mocks/BeaconChainMock.t.sol"; +import "src/test/integration/mocks/BeaconChainOracleMock.t.sol"; + interface IUserDeployer { function delegationManager() external view returns (DelegationManager); @@ -20,6 +22,7 @@ interface IUserDeployer { function eigenPodManager() external view returns (EigenPodManager); function timeMachine() external view returns (TimeMachine); function beaconChain() external view returns (BeaconChainMock); + function beaconChainOracle() external view returns (address); } contract User is Test { @@ -35,6 +38,7 @@ contract User is Test { /// @dev Native restaker state vars BeaconChainMock beaconChain; + BeaconChainOracleMock beaconChainOracle; // User's EigenPod and each of their validator indices within that pod EigenPod public pod; uint40[] validators; @@ -54,7 +58,8 @@ contract User is Test { timeMachine = deployer.timeMachine(); beaconChain = deployer.beaconChain(); - pod = EigenPod(payable(eigenPodManager.createPod())); + beaconChainOracle = BeaconChainOracleMock(deployer.beaconChainOracle()); + _createPod(); NAME = name; } @@ -105,9 +110,9 @@ contract User is Test { balanceWei: 32 ether, withdrawalCreds: _podWithdrawalCredentials() }); - - validators.push(newValidatorIndex); + validators.push(newValidatorIndex); + emit log_named_uint("oracle timestamp", proofs.oracleTimestamp); pod.verifyWithdrawalCredentials({ oracleTimestamp: proofs.oracleTimestamp, stateRootProof: proofs.stateRootProof, @@ -268,6 +273,18 @@ contract User is Test { return _completeQueuedWithdrawal(withdrawal, false); } + /// @notice We set the proof generation start time to be after the timestamp that pod restaking is activated + /// We do this to prevent proofIsForValidTimestamp modifier from reverting + function activateRestaking() public createSnapshot { + emit log(_name(".activateRestaking")); + + emit log_named_uint("pre-activation, most recent wd timestamp", pod.mostRecentWithdrawalTimestamp()); + + pod.activateRestaking(); + + emit log_named_uint("post-activation, most recent wd timestamp", pod.mostRecentWithdrawalTimestamp()); + } + function _completeQueuedWithdrawal( IDelegationManager.Withdrawal memory withdrawal, bool receiveAsTokens @@ -319,6 +336,10 @@ contract User is Test { return tokens; } + function _createPod() internal virtual { + pod = EigenPod(payable(eigenPodManager.createPod())); + } + function _podWithdrawalCredentials() internal view returns (bytes memory) { return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(pod)); } @@ -464,4 +485,4 @@ contract User_AltMethods is User { return 0xffffffff; } } -} \ No newline at end of file +} diff --git a/src/test/integration/users/User_M1.t.sol b/src/test/integration/users/User_M1.t.sol new file mode 100644 index 0000000000..7b9369c00a --- /dev/null +++ b/src/test/integration/users/User_M1.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol"; +import "src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol"; +import "src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol"; +import "src/test/integration/users/User.t.sol"; + +interface IUserMainnetForkDeployer { + function delegationManager() external view returns (DelegationManager); + function strategyManager() external view returns (StrategyManager); + function eigenPodManager() external view returns (EigenPodManager); + function timeMachine() external view returns (TimeMachine); + function beaconChain() external view returns (BeaconChainMock); + function strategyManager_M1() external view returns (IStrategyManager_DeprecatedM1); + function eigenPodManager_M1() external view returns (IEigenPodManager_DeprecatedM1); +} + +/** + * @dev User_M1 used for performing mainnet M1 contract methods but also inherits User + * to perform current local contract methods after a upgrade of core contracts + */ +contract User_M1 is User { + IStrategyManager_DeprecatedM1 strategyManager_M1; + IEigenPodManager_DeprecatedM1 eigenPodManager_M1; + + constructor(string memory name) User(name) { + IUserMainnetForkDeployer deployer = IUserMainnetForkDeployer(msg.sender); + + strategyManager_M1 = IStrategyManager_DeprecatedM1(address(deployer.strategyManager())); + eigenPodManager_M1 = IEigenPodManager_DeprecatedM1(address(deployer.eigenPodManager())); + } + + /** + * StrategyManager M1 mainnet methods: + */ + + /// @notice Deposit into EigenLayer with M1 mainnet methods, only concerns LSTs + /// Note that this should not be called with BeaconChainStrat + function depositIntoEigenlayer_M1( + IStrategy[] memory strategies, + uint256[] memory tokenBalances + ) public virtual createSnapshot { + emit log(_name(".depositIntoEigenlayer_M1")); + + for (uint256 i = 0; i < strategies.length; i++) { + IStrategy strat = strategies[i]; + uint256 tokenBalance = tokenBalances[i]; + + // Skip BeaconChainStrat, since BeaconChainStrat doesn't exist on M1 mainnet + if (strat == BEACONCHAIN_ETH_STRAT) { + continue; + } + + IERC20 underlyingToken = strat.underlyingToken(); + underlyingToken.approve(address(strategyManager), tokenBalance); + strategyManager.depositIntoStrategy(strat, underlyingToken, tokenBalance); + } + } + + function _createPod() internal virtual override { + IEigenPodManager_DeprecatedM1(address(eigenPodManager)).createPod(); + // get EigenPod address + pod = EigenPod( + payable(address(IEigenPodManager_DeprecatedM1(address(eigenPodManager)).ownerToPod(address(this)))) + ); + } +} + +contract User_M1_AltMethods is User_M1 { + mapping(bytes32 => bool) public signedHashes; + + constructor(string memory name) User_M1(name) {} + + function depositIntoEigenlayer_M1( + IStrategy[] memory strategies, + uint256[] memory tokenBalances + ) public override createSnapshot { + emit log(_name(".depositIntoEigenlayer_M1_ALT")); + + uint256 expiry = type(uint256).max; + for (uint256 i = 0; i < strategies.length; i++) { + IStrategy strat = strategies[i]; + uint256 tokenBalance = tokenBalances[i]; + + if (strat == BEACONCHAIN_ETH_STRAT) { + revert("Should not be depositing with BEACONCHAIN_ETH_STRAT for M1-mainnet User"); + } + + // Approve token + IERC20 underlyingToken = strat.underlyingToken(); + underlyingToken.approve(address(strategyManager), tokenBalance); + + // Get signature + uint256 nonceBefore = strategyManager.nonces(address(this)); + bytes32 structHash = keccak256( + abi.encode( + strategyManager.DEPOSIT_TYPEHASH(), + strat, + underlyingToken, + tokenBalance, + nonceBefore, + expiry + ) + ); + bytes32 domain_separator = keccak256(abi.encode(strategyManager.DOMAIN_TYPEHASH(), keccak256(bytes("EigenLayer")), block.chainid, address(strategyManager))); + bytes32 digestHash = + keccak256(abi.encodePacked("\x19\x01", domain_separator, structHash)); + bytes memory signature = bytes(abi.encodePacked(digestHash)); // dummy sig data + + // Mark hash as signed + signedHashes[digestHash] = true; + + // Deposit + strategyManager.depositIntoStrategyWithSignature( + strat, underlyingToken, tokenBalance, address(this), expiry, signature + ); + + // Mark hash as used + signedHashes[digestHash] = false; + } + } + + bytes4 internal constant MAGIC_VALUE = 0x1626ba7e; + + function isValidSignature(bytes32 hash, bytes memory) external view returns (bytes4) { + if (signedHashes[hash]) { + return MAGIC_VALUE; + } else { + return 0xffffffff; + } + } +}