diff --git a/docs/release/slashing/AllocationManager.md b/docs/release/slashing/AllocationManager.md index 663d78e0b..977c95742 100644 --- a/docs/release/slashing/AllocationManager.md +++ b/docs/release/slashing/AllocationManager.md @@ -12,9 +12,11 @@ The AllocationManager contract manages the allocation and reallocation of operat - `ALLOCATION_CONFIGURATION_DELAY`: The delay in seconds before allocations take effect. - Mainnet: `21 days`. Very TBD - Testnet: `1 hour`. Very TBD + - Public Devnet: `10 minutes` - `DEALLOCATION_DELAY`: The delay in seconds before deallocations take effect. - Mainnet: `17.5 days`. Slightly TBD - Testnet: `3 days`. Very TBD + - Public Devnet: `1 days` ## `setAllocationDelay` @@ -81,29 +83,27 @@ Any _allocations_ (i.e. increases in the proportion of slashable stake allocated Any _deallocations_ (i.e. decreases in the proportion of slashable stake allocated to an AVS) take after `DEALLOCATION_DELAY` seconds. This enables AVSs enough time to update their view of stakes to the new proportions and have any tasks created against previous stakes to expire. -## `clearPendingModifications` +## `clearDeallocationQueue` ```solidity /** - * @notice This function takes a list of strategies and adds all completable modifications for each strategy, + * @notice This function takes a list of strategies and adds all completable deallocations for each strategy, * updating the encumberedMagnitude of the operator as needed. * - * @param operator address to complete modifications for - * @param strategies a list of strategies to complete modifications for - * @param numToClear a list of number of pending modifications to complete for each strategy + * @param operator address to complete deallocations for + * @param strategies a list of strategies to complete deallocations for + * @param numToComplete a list of number of pending deallocations to complete for each strategy * * @dev can be called permissionlessly by anyone */ -function clearModificationQueue( +function clearDeallocationQueue( address operator, IStrategy[] calldata strategies, - uint16[] calldata numToClear + uint16[] calldata numToComplete ) external; ``` -This function is used to complete pending modifications for a list of strategies for an operator. The function takes a list of strategies and the number of pending modifications to complete for each strategy. For each strategy, the function completes a modification if its effect timestamp has passed. - -Completing an allocation doesn't have any material change to the protocol other than cleaning up some state and increasing code readability. +This function is used to complete pending deallocations for a list of strategies for an operator. The function takes a list of strategies and the number of pending deallocations to complete for each strategy. For each strategy, the function completes a modification if its effect timestamp has passed. Completing a deallocation decreases the encumbered magnitude for the strategy, allowing them to make allocations with that magnitude. Encumbered magnitude must be decreased only upon completion because pending deallocations can be slashed before they are completable. @@ -128,7 +128,7 @@ struct SlashingParams { } /** - * @notice Called by an AVS to slash an operator for given operatorSetId, list of strategies, and bipsToSlash. + * @notice Called by an AVS to slash an operator for given operatorSetId, list of strategies, and wadToSlash. * For each given (operator, operatorSetId, strategy) tuple, bipsToSlash * bips of the operatorSet's slashable stake allocation will be slashed * @@ -136,8 +136,8 @@ struct SlashingParams { * @param operatorSetId the ID of the operatorSet the operator is being slashed on behalf of * @param strategies the set of strategies to slash * @param wadToSlash the parts in 1e18 to slash, this will be proportional to the - * @param description the description of the slashing provided by the AVS for legibility * operator's slashable stake allocation for the operatorSet + * @param description the description of the slashing provided by the AVS for legibility */ function slashOperator( SlashingParams calldata params diff --git a/script/configs/devnet/deploy_from_scratch.holesky.slashing.config.json b/script/configs/devnet/deploy_from_scratch.holesky.slashing.config.json new file mode 100644 index 000000000..bcd808f71 --- /dev/null +++ b/script/configs/devnet/deploy_from_scratch.holesky.slashing.config.json @@ -0,0 +1,47 @@ +{ + "multisig_addresses": { + "operationsMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "communityMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "pauserMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "executorMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "timelock": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07" + }, + "strategyManager": { + "init_paused_status": 0, + "init_withdrawal_delay_blocks": 1 + }, + "eigenPod": { + "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 1, + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000" + }, + "eigenPodManager": { + "init_paused_status": 115792089237316195423570985008687907853269984665640564039457584007913129639935 + }, + "slasher": { + "init_paused_status": 0 + }, + "delegation": { + "init_paused_status": 0, + "init_withdrawal_delay_blocks": 1 + }, + "rewardsCoordinator": { + "init_paused_status": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "CALCULATION_INTERVAL_SECONDS": 604800, + "MAX_REWARDS_DURATION": 6048000, + "MAX_RETROACTIVE_LENGTH": 7776000, + "MAX_FUTURE_LENGTH": 2592000, + "GENESIS_REWARDS_TIMESTAMP": 1710979200, + "rewards_updater_address": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "activation_delay": 7200, + "calculation_interval_seconds": 604800, + "global_operator_commission_bips": 1000, + "OPERATOR_SET_GENESIS_REWARDS_TIMESTAMP": 1720656000, + "OPERATOR_SET_MAX_RETROACTIVE_LENGTH": 2592000 + }, + "allocationManager": { + "init_paused_status": 0, + "DEALLOCATION_DELAY": 86400, + "ALLOCATION_CONFIGURATION_DELAY": 600 + }, + "ethPOSDepositAddress": "0x4242424242424242424242424242424242424242" + } \ No newline at end of file diff --git a/script/deploy/devnet/deploy_from_scratch.s.sol b/script/deploy/devnet/deploy_from_scratch.s.sol new file mode 100644 index 000000000..5d70b49e3 --- /dev/null +++ b/script/deploy/devnet/deploy_from_scratch.s.sol @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; + +import "../../../src/contracts/interfaces/IETHPOSDeposit.sol"; + +import "../../../src/contracts/core/StrategyManager.sol"; +import "../../../src/contracts/core/DelegationManager.sol"; +import "../../../src/contracts/core/AVSDirectory.sol"; +import "../../../src/contracts/core/RewardsCoordinator.sol"; +import "../../../src/contracts/core/AllocationManager.sol"; + +import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol"; +import "../../../src/contracts/strategies/StrategyFactory.sol"; +import "../../../src/contracts/strategies/StrategyBase.sol"; + +import "../../../src/contracts/pods/EigenPod.sol"; +import "../../../src/contracts/pods/EigenPodManager.sol"; + +import "../../../src/contracts/permissions/PauserRegistry.sol"; + +import "../../../src/test/mocks/EmptyContract.sol"; +import "../../../src/test/mocks/ETHDepositMock.sol"; + +import "forge-std/Script.sol"; +import "forge-std/Test.sol"; + +// # To load the variables in the .env file +// source .env + +// # To deploy and verify our contract +// forge script script/deploy/devnet/deploy_from_scratch.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --sig "run(string memory configFile)" -- local/deploy_from_scratch.anvil.config.json +contract DeployFromScratch is Script, Test { + Vm cheats = Vm(VM_ADDRESS); + + string public deployConfigPath; + + // EigenLayer Contracts + ProxyAdmin public eigenLayerProxyAdmin; + PauserRegistry public eigenLayerPauserReg; + DelegationManager public delegation; + DelegationManager public delegationImplementation; + StrategyManager public strategyManager; + StrategyManager public strategyManagerImplementation; + RewardsCoordinator public rewardsCoordinator; + RewardsCoordinator public rewardsCoordinatorImplementation; + AVSDirectory public avsDirectory; + AVSDirectory public avsDirectoryImplementation; + EigenPodManager public eigenPodManager; + EigenPodManager public eigenPodManagerImplementation; + UpgradeableBeacon public eigenPodBeacon; + EigenPod public eigenPodImplementation; + StrategyFactory public strategyFactory; + StrategyFactory public strategyFactoryImplementation; + UpgradeableBeacon public strategyBeacon; + StrategyBase public baseStrategyImplementation; + AllocationManager public allocationManagerImplementation; + AllocationManager public allocationManager; + + EmptyContract public emptyContract; + + address executorMultisig; + address operationsMultisig; + address pauserMultisig; + + // the ETH2 deposit contract -- if not on mainnet, we deploy a mock as stand-in + IETHPOSDeposit public ethPOSDeposit; + + // strategies deployed + StrategyBaseTVLLimits[] public deployedStrategyArray; + + // IMMUTABLES TO SET + uint64 GOERLI_GENESIS_TIME = 1616508000; + + // OTHER DEPLOYMENT PARAMETERS + uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS; + uint256 DELEGATION_INIT_PAUSED_STATUS; + uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS; + uint256 REWARDS_COORDINATOR_INIT_PAUSED_STATUS; + + // DelegationManager + uint32 MIN_WITHDRAWAL_DELAY = 86400; + + // AllocationManager + uint32 DEALLOCATION_DELAY; + uint32 ALLOCATION_CONFIGURATION_DELAY; + + // RewardsCoordinator + uint32 REWARDS_COORDINATOR_MAX_REWARDS_DURATION; + uint32 REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH; + uint32 REWARDS_COORDINATOR_MAX_FUTURE_LENGTH; + uint32 REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP; + address REWARDS_COORDINATOR_UPDATER; + uint32 REWARDS_COORDINATOR_ACTIVATION_DELAY; + uint32 REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS; + uint32 REWARDS_COORDINATOR_GLOBAL_OPERATOR_COMMISSION_BIPS; + uint32 REWARDS_COORDINATOR_OPERATOR_SET_GENESIS_REWARDS_TIMESTAMP; + uint32 REWARDS_COORDINATOR_OPERATOR_SET_MAX_RETROACTIVE_LENGTH; + + // AllocationManager + uint256 ALLOCATION_MANAGER_INIT_PAUSED_STATUS; + + // one week in blocks -- 50400 + uint32 STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS; + uint256 DELEGATION_WITHDRAWAL_DELAY_BLOCKS; + + function run(string memory configFileName) public { + // read and log the chainID + uint256 chainId = block.chainid; + emit log_named_uint("You are deploying on ChainID", chainId); + + // READ JSON CONFIG DATA + deployConfigPath = string(bytes(string.concat("script/configs/", configFileName))); + string memory config_data = vm.readFile(deployConfigPath); + // bytes memory parsedData = vm.parseJson(config_data); + + STRATEGY_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".strategyManager.init_paused_status"); + DELEGATION_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".delegation.init_paused_status"); + DELEGATION_WITHDRAWAL_DELAY_BLOCKS = stdJson.readUint(config_data, ".delegation.init_withdrawal_delay_blocks"); + EIGENPOD_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".eigenPodManager.init_paused_status"); + REWARDS_COORDINATOR_INIT_PAUSED_STATUS = stdJson.readUint( + config_data, + ".rewardsCoordinator.init_paused_status" + ); + REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS = uint32( + stdJson.readUint(config_data, ".rewardsCoordinator.CALCULATION_INTERVAL_SECONDS") + ); + REWARDS_COORDINATOR_MAX_REWARDS_DURATION = uint32(stdJson.readUint(config_data, ".rewardsCoordinator.MAX_REWARDS_DURATION")); + REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH = uint32(stdJson.readUint(config_data, ".rewardsCoordinator.MAX_RETROACTIVE_LENGTH")); + REWARDS_COORDINATOR_MAX_FUTURE_LENGTH = uint32(stdJson.readUint(config_data, ".rewardsCoordinator.MAX_FUTURE_LENGTH")); + REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP = uint32(stdJson.readUint(config_data, ".rewardsCoordinator.GENESIS_REWARDS_TIMESTAMP")); + REWARDS_COORDINATOR_UPDATER = stdJson.readAddress(config_data, ".rewardsCoordinator.rewards_updater_address"); + REWARDS_COORDINATOR_ACTIVATION_DELAY = uint32(stdJson.readUint(config_data, ".rewardsCoordinator.activation_delay")); + REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS = uint32( + stdJson.readUint(config_data, ".rewardsCoordinator.calculation_interval_seconds") + ); + REWARDS_COORDINATOR_GLOBAL_OPERATOR_COMMISSION_BIPS = uint32( + stdJson.readUint(config_data, ".rewardsCoordinator.global_operator_commission_bips") + ); + REWARDS_COORDINATOR_OPERATOR_SET_GENESIS_REWARDS_TIMESTAMP = uint32( + stdJson.readUint(config_data, ".rewardsCoordinator.OPERATOR_SET_GENESIS_REWARDS_TIMESTAMP") + ); + REWARDS_COORDINATOR_OPERATOR_SET_MAX_RETROACTIVE_LENGTH = uint32( + stdJson.readUint(config_data, ".rewardsCoordinator.OPERATOR_SET_MAX_RETROACTIVE_LENGTH") + ); + + STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32( + stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks") + ); + + ALLOCATION_MANAGER_INIT_PAUSED_STATUS = uint32( + stdJson.readUint(config_data, ".allocationManager.init_paused_status") + ); + DEALLOCATION_DELAY = uint32( + stdJson.readUint(config_data, ".allocationManager.DEALLOCATION_DELAY") + ); + ALLOCATION_CONFIGURATION_DELAY = uint32( + stdJson.readUint(config_data, ".allocationManager.ALLOCATION_CONFIGURATION_DELAY") + ); + + executorMultisig = stdJson.readAddress(config_data, ".multisig_addresses.executorMultisig"); + operationsMultisig = stdJson.readAddress(config_data, ".multisig_addresses.operationsMultisig"); + pauserMultisig = stdJson.readAddress(config_data, ".multisig_addresses.pauserMultisig"); + + require(executorMultisig != address(0), "executorMultisig address not configured correctly!"); + require(operationsMultisig != address(0), "operationsMultisig address not configured correctly!"); + + // START RECORDING TRANSACTIONS FOR DEPLOYMENT + vm.startBroadcast(); + + // deploy proxy admin for ability to upgrade proxy contracts + eigenLayerProxyAdmin = new ProxyAdmin(); + + //deploy pauser registry + { + address[] memory pausers = new address[](3); + pausers[0] = executorMultisig; + pausers[1] = operationsMultisig; + pausers[2] = pauserMultisig; + eigenLayerPauserReg = new PauserRegistry(pausers, executorMultisig); + } + + /** + * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are + * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. + */ + emptyContract = new EmptyContract(); + delegation = DelegationManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + strategyManager = StrategyManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + avsDirectory = AVSDirectory( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + eigenPodManager = EigenPodManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + rewardsCoordinator = RewardsCoordinator( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + allocationManager = AllocationManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + strategyFactory = StrategyFactory( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + + // if on mainnet, use the ETH2 deposit contract address + if (chainId == 1) { + ethPOSDeposit = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa); + // if not on mainnet, deploy a mock + } else { + ethPOSDeposit = IETHPOSDeposit(stdJson.readAddress(config_data, ".ethPOSDepositAddress")); + } + eigenPodImplementation = new EigenPod( + ethPOSDeposit, + eigenPodManager, + GOERLI_GENESIS_TIME + ); + + eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation)); + + // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs + + delegationImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, MIN_WITHDRAWAL_DELAY); + strategyManagerImplementation = new StrategyManager(delegation); + avsDirectoryImplementation = new AVSDirectory(delegation, DEALLOCATION_DELAY); + eigenPodManagerImplementation = new EigenPodManager( + ethPOSDeposit, + eigenPodBeacon, + strategyManager, + delegation + ); + rewardsCoordinatorImplementation = new RewardsCoordinator( + delegation, + strategyManager, + REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS, + REWARDS_COORDINATOR_MAX_REWARDS_DURATION, + REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH, + REWARDS_COORDINATOR_MAX_FUTURE_LENGTH, + REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP + ); + allocationManagerImplementation = new AllocationManager(delegation, avsDirectory, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); + strategyFactoryImplementation = new StrategyFactory(strategyManager); + + // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. + { + IStrategy[] memory _strategies; + uint256[] memory _withdrawalDelayBlocks; + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(delegation))), + address(delegationImplementation), + abi.encodeWithSelector( + DelegationManager.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + DELEGATION_INIT_PAUSED_STATUS + ) + ); + } + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(strategyManager))), + address(strategyManagerImplementation), + abi.encodeWithSelector( + StrategyManager.initialize.selector, + executorMultisig, + operationsMultisig, + eigenLayerPauserReg, + STRATEGY_MANAGER_INIT_PAUSED_STATUS + ) + ); + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(avsDirectory))), + address(avsDirectoryImplementation), + abi.encodeWithSelector(AVSDirectory.initialize.selector, executorMultisig, eigenLayerPauserReg, 0) + ); + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(eigenPodManager))), + address(eigenPodManagerImplementation), + abi.encodeWithSelector( + EigenPodManager.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + EIGENPOD_MANAGER_INIT_PAUSED_STATUS + ) + ); + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(rewardsCoordinator))), + address(rewardsCoordinatorImplementation), + abi.encodeWithSelector( + RewardsCoordinator.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + REWARDS_COORDINATOR_INIT_PAUSED_STATUS, + REWARDS_COORDINATOR_UPDATER, + REWARDS_COORDINATOR_ACTIVATION_DELAY, + REWARDS_COORDINATOR_GLOBAL_OPERATOR_COMMISSION_BIPS + ) + ); + + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(allocationManager))), + address(allocationManagerImplementation), + abi.encodeWithSelector( + AllocationManager.initialize.selector, + executorMultisig, + eigenLayerPauserReg, + ALLOCATION_MANAGER_INIT_PAUSED_STATUS + ) + ); + + // Deploy strategyFactory & base + // Create base strategy implementation + baseStrategyImplementation = new StrategyBase(strategyManager); + + // Create a proxy beacon for base strategy implementation + strategyBeacon = new UpgradeableBeacon(address(baseStrategyImplementation)); + + // Strategy Factory, upgrade and initalized + eigenLayerProxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(payable(address(strategyFactory))), + address(strategyFactoryImplementation), + abi.encodeWithSelector( + StrategyFactory.initialize.selector, + executorMultisig, + IPauserRegistry(address(eigenLayerPauserReg)), + 0, // initial paused status + IBeacon(strategyBeacon) + ) + ); + + // Set the strategyWhitelister to the factory + strategyManager.setStrategyWhitelister(address(strategyFactory)); + + // Deploy a WETH strategy + strategyFactory.deployNewStrategy(IERC20(address(0x94373a4919B3240D86eA41593D5eBa789FEF3848))); + + // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT + vm.stopBroadcast(); + + // CHECK CORRECTNESS OF DEPLOYMENT + _verifyContractsPointAtOneAnother( + delegationImplementation, + strategyManagerImplementation, + eigenPodManagerImplementation, + rewardsCoordinatorImplementation, + allocationManagerImplementation + ); + _verifyContractsPointAtOneAnother( + delegation, + strategyManager, + eigenPodManager, + rewardsCoordinator, + allocationManager + ); + _verifyImplementationsSetCorrectly(); + _verifyInitialOwners(); + _checkPauserInitializations(); + _verifyInitializationParams(); + + // Check DM and AM have same withdrawa/deallocation delay + require( + delegation.MIN_WITHDRAWAL_DELAY() == allocationManager.DEALLOCATION_DELAY(), + "DelegationManager and AllocationManager have different withdrawal/deallocation delays" + ); + require( + allocationManager.DEALLOCATION_DELAY() == 1 days + ); + require( + allocationManager.ALLOCATION_CONFIGURATION_DELAY() == 10 minutes + ); + + // WRITE JSON DATA + string memory parent_object = "parent object"; + + string memory deployed_strategies_output = ""; + + string memory deployed_addresses = "addresses"; + vm.serializeUint(deployed_addresses, "numStrategiesDeployed", 0); // for compatibility with other scripts + vm.serializeAddress(deployed_addresses, "eigenLayerProxyAdmin", address(eigenLayerProxyAdmin)); + vm.serializeAddress(deployed_addresses, "eigenLayerPauserReg", address(eigenLayerPauserReg)); + vm.serializeAddress(deployed_addresses, "delegationManager", address(delegation)); + vm.serializeAddress(deployed_addresses, "delegationManagerImplementation", address(delegationImplementation)); + vm.serializeAddress(deployed_addresses, "avsDirectory", address(avsDirectory)); + vm.serializeAddress(deployed_addresses, "avsDirectoryImplementation", address(avsDirectoryImplementation)); + vm.serializeAddress(deployed_addresses, "allocationManager", address(allocationManager)); + vm.serializeAddress(deployed_addresses, "allocationManagerImplementation", address(allocationManagerImplementation)); + vm.serializeAddress(deployed_addresses, "strategyManager", address(strategyManager)); + vm.serializeAddress( + deployed_addresses, + "strategyManagerImplementation", + address(strategyManagerImplementation) + ); + vm.serializeAddress( + deployed_addresses, "strategyFactory", address(strategyFactory) + ); + vm.serializeAddress( + deployed_addresses, "strategyFactoryImplementation", address(strategyFactoryImplementation) + ); + vm.serializeAddress(deployed_addresses, "strategyBeacon", address(strategyBeacon)); + vm.serializeAddress(deployed_addresses, "baseStrategyImplementation", address(baseStrategyImplementation)); + vm.serializeAddress(deployed_addresses, "eigenPodManager", address(eigenPodManager)); + vm.serializeAddress( + deployed_addresses, + "eigenPodManagerImplementation", + address(eigenPodManagerImplementation) + ); + vm.serializeAddress(deployed_addresses, "rewardsCoordinator", address(rewardsCoordinator)); + vm.serializeAddress( + deployed_addresses, + "rewardsCoordinatorImplementation", + address(rewardsCoordinatorImplementation) + ); + vm.serializeAddress(deployed_addresses, "eigenPodBeacon", address(eigenPodBeacon)); + vm.serializeAddress(deployed_addresses, "eigenPodImplementation", address(eigenPodImplementation)); + vm.serializeAddress(deployed_addresses, "emptyContract", address(emptyContract)); + + string memory deployed_addresses_output = vm.serializeString( + deployed_addresses, + "strategies", + deployed_strategies_output + ); + + { + // dummy token data + string memory token = '{"tokenProxyAdmin": "0x0000000000000000000000000000000000000000", "EIGEN": "0x0000000000000000000000000000000000000000","bEIGEN": "0x0000000000000000000000000000000000000000","EIGENImpl": "0x0000000000000000000000000000000000000000","bEIGENImpl": "0x0000000000000000000000000000000000000000","eigenStrategy": "0x0000000000000000000000000000000000000000","eigenStrategyImpl": "0x0000000000000000000000000000000000000000"}'; + deployed_addresses_output = vm.serializeString(deployed_addresses, "token", token); + } + + string memory parameters = "parameters"; + vm.serializeAddress(parameters, "executorMultisig", executorMultisig); + vm.serializeAddress(parameters, "communityMultisig", operationsMultisig); + vm.serializeAddress(parameters, "pauserMultisig", pauserMultisig); + vm.serializeAddress(parameters, "timelock", address(0)); + string memory parameters_output = vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig); + + string memory chain_info = "chainInfo"; + vm.serializeUint(chain_info, "deploymentBlock", block.number); + string memory chain_info_output = vm.serializeUint(chain_info, "chainId", chainId); + + // serialize all the data + vm.serializeString(parent_object, deployed_addresses, deployed_addresses_output); + vm.serializeString(parent_object, chain_info, chain_info_output); + string memory finalJson = vm.serializeString(parent_object, parameters, parameters_output); + // TODO: should output to different file depending on configFile passed to run() + // so that we don't override mainnet output by deploying to goerli for eg. + vm.writeJson(finalJson, "script/output/devnet/slashing_output.json"); + } + + function _verifyContractsPointAtOneAnother( + DelegationManager delegationContract, + StrategyManager strategyManagerContract, + EigenPodManager eigenPodManagerContract, + RewardsCoordinator rewardsCoordinatorContract, + AllocationManager allocationManagerContract + ) internal view { + require( + delegationContract.strategyManager() == strategyManager, + "delegation: strategyManager address not set correctly" + ); + + require( + strategyManagerContract.delegation() == delegation, + "strategyManager: delegation address not set correctly" + ); + require( + eigenPodManagerContract.ethPOS() == ethPOSDeposit, + " eigenPodManager: ethPOSDeposit contract address not set correctly" + ); + require( + eigenPodManagerContract.eigenPodBeacon() == eigenPodBeacon, + "eigenPodManager: eigenPodBeacon contract address not set correctly" + ); + require( + eigenPodManagerContract.strategyManager() == strategyManager, + "eigenPodManager: strategyManager contract address not set correctly" + ); + + require( + rewardsCoordinatorContract.delegationManager() == delegation, + "rewardsCoordinator: delegation address not set correctly" + ); + require( + rewardsCoordinatorContract.strategyManager() == strategyManager, + "rewardsCoordinator: strategyManager address not set correctly" + ); + require( + delegationContract.allocationManager() == allocationManager, + "delegationManager: allocationManager address not set correctly" + ); + require( + allocationManagerContract.delegation() == delegation, + "allocationManager: delegation address not set correctly" + ); + require( + allocationManagerContract.avsDirectory() == avsDirectory, + "allocationManager: avsDirectory address not set correctly" + ); + } + + function _verifyImplementationsSetCorrectly() internal view { + require( + eigenLayerProxyAdmin.getProxyImplementation(ITransparentUpgradeableProxy(payable(address(delegation)))) == + address(delegationImplementation), + "delegation: implementation set incorrectly" + ); + require( + eigenLayerProxyAdmin.getProxyImplementation( + ITransparentUpgradeableProxy(payable(address(strategyManager))) + ) == address(strategyManagerImplementation), + "strategyManager: implementation set incorrectly" + ); + require( + eigenLayerProxyAdmin.getProxyImplementation( + ITransparentUpgradeableProxy(payable(address(eigenPodManager))) + ) == address(eigenPodManagerImplementation), + "eigenPodManager: implementation set incorrectly" + ); + require( + eigenLayerProxyAdmin.getProxyImplementation( + ITransparentUpgradeableProxy(payable(address(rewardsCoordinator))) + ) == address(rewardsCoordinatorImplementation), + "rewardsCoordinator: implementation set incorrectly" + ); + + require( + eigenLayerProxyAdmin.getProxyImplementation( + ITransparentUpgradeableProxy(payable(address(allocationManager))) + ) == address(allocationManagerImplementation), + "allocationManager: implementation set incorrectly" + ); + + require( + eigenLayerProxyAdmin.getProxyImplementation( + ITransparentUpgradeableProxy(payable(address(strategyFactory))) + ) == address(strategyFactoryImplementation), + "strategyFactory: implementation set incorrectly" + ); + + require( + eigenPodBeacon.implementation() == address(eigenPodImplementation), + "eigenPodBeacon: implementation set incorrectly" + ); + + require( + strategyBeacon.implementation() == address(baseStrategyImplementation), + "strategyBeacon: implementation set incorrectly" + ); + } + + function _verifyInitialOwners() internal view { + require(strategyManager.owner() == executorMultisig, "strategyManager: owner not set correctly"); + require(delegation.owner() == executorMultisig, "delegation: owner not set correctly"); + require(eigenPodManager.owner() == executorMultisig, "eigenPodManager: owner not set correctly"); + require(allocationManager.owner() == executorMultisig, "allocationManager: owner not set correctly"); + require(eigenLayerProxyAdmin.owner() == executorMultisig, "eigenLayerProxyAdmin: owner not set correctly"); + require(eigenPodBeacon.owner() == executorMultisig, "eigenPodBeacon: owner not set correctly"); + require(strategyBeacon.owner() == executorMultisig, "strategyBeacon: owner not set correctly"); + } + + function _checkPauserInitializations() internal view { + require(delegation.pauserRegistry() == eigenLayerPauserReg, "delegation: pauser registry not set correctly"); + require( + strategyManager.pauserRegistry() == eigenLayerPauserReg, + "strategyManager: pauser registry not set correctly" + ); + require( + eigenPodManager.pauserRegistry() == eigenLayerPauserReg, + "eigenPodManager: pauser registry not set correctly" + ); + require( + rewardsCoordinator.pauserRegistry() == eigenLayerPauserReg, + "rewardsCoordinator: pauser registry not set correctly" + ); + require( + allocationManager.pauserRegistry() == eigenLayerPauserReg, + "allocationManager: pauser registry not set correctly" + ); + + require(eigenLayerPauserReg.isPauser(operationsMultisig), "pauserRegistry: operationsMultisig is not pauser"); + require(eigenLayerPauserReg.isPauser(executorMultisig), "pauserRegistry: executorMultisig is not pauser"); + require(eigenLayerPauserReg.isPauser(pauserMultisig), "pauserRegistry: pauserMultisig is not pauser"); + require(eigenLayerPauserReg.unpauser() == executorMultisig, "pauserRegistry: unpauser not set correctly"); + + for (uint256 i = 0; i < deployedStrategyArray.length; ++i) { + require( + deployedStrategyArray[i].pauserRegistry() == eigenLayerPauserReg, + "StrategyBaseTVLLimits: pauser registry not set correctly" + ); + require( + deployedStrategyArray[i].paused() == 0, + "StrategyBaseTVLLimits: init paused status set incorrectly" + ); + } + + // // pause *nothing* + // uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS = 0; + // // pause *everything* + // // pause *everything* + // uint256 DELEGATION_INIT_PAUSED_STATUS = type(uint256).max; + // // pause *all of the proof-related functionality* (everything that can be paused other than creation of EigenPods) + // uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS = (2**1) + (2**2) + (2**3) + (2**4); /* = 30 */ + // // pause *nothing* + // require(strategyManager.paused() == 0, "strategyManager: init paused status set incorrectly"); + // require(delegation.paused() == type(uint256).max, "delegation: init paused status set incorrectly"); + // require(eigenPodManager.paused() == 30, "eigenPodManager: init paused status set incorrectly"); + } + + function _verifyInitializationParams() internal { + // // one week in blocks -- 50400 + // uint32 STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds; + // require(strategyManager.withdrawalDelayBlocks() == 7 days / 12 seconds, + // "strategyManager: withdrawalDelayBlocks initialized incorrectly"); + // uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32 ether; + + require( + address(strategyManager.strategyWhitelister()) == address(strategyFactory), + "strategyManager: strategyWhitelister address not set correctly" + ); + + require( + baseStrategyImplementation.strategyManager() == strategyManager, + "baseStrategyImplementation: strategyManager set incorrectly" + ); + + require( + eigenPodImplementation.ethPOS() == ethPOSDeposit, + "eigenPodImplementation: ethPOSDeposit contract address not set correctly" + ); + require( + eigenPodImplementation.eigenPodManager() == eigenPodManager, + " eigenPodImplementation: eigenPodManager contract address not set correctly" + ); + } +} diff --git a/script/output/devnet/SLASHING_deploy_from_scratch_deployment_data.json b/script/output/devnet/SLASHING_deploy_from_scratch_deployment_data.json new file mode 100644 index 000000000..b83fcb7d8 --- /dev/null +++ b/script/output/devnet/SLASHING_deploy_from_scratch_deployment_data.json @@ -0,0 +1,52 @@ +{ + "addresses": { + "allocationManager": "0xAbD5Dd30CaEF8598d4EadFE7D45Fd582EDEade15", + "allocationManagerImplementation": "0xBFF7154bAa41e702E78Fb082a8Ce257Ce13E6f55", + "avsDirectory": "0xCa839541648D3e23137457b1Fd4A06bccEADD33a", + "avsDirectoryImplementation": "0x1362e9Cb37831C433095f1f1568215B7FDeD37Ef", + "baseStrategyImplementation": "0x61C6A250AEcAbf6b5e4611725b4f99C4DC85DB34", + "delegationManager": "0x3391eBafDD4b2e84Eeecf1711Ff9FC06EF9Ed182", + "delegationManagerImplementation": "0x4073a9B0fb0f31420C2A2263fB6E9adD33ea6F2A", + "eigenLayerPauserReg": "0xBb02ACE793e921D6a454062D2933064F31Fae0B2", + "eigenLayerProxyAdmin": "0xBf0c97a7df334BD83e0912c1218E44FD7953d122", + "eigenPodBeacon": "0x8ad244c2a986e48862c5bE1FdCA27cef0aaa6E15", + "eigenPodImplementation": "0x93cecf40F05389E99e163539F8d1CCbd4267f9A7", + "eigenPodManager": "0x8C9781FD55c67CE4DC08e3035ECbdB2B67a07307", + "eigenPodManagerImplementation": "0x3013B13BF3a464ff9078EFa40b7dbfF8fA67138d", + "emptyContract": "0x689CEE9134e4234caEF6c15Bf1D82779415daFAe", + "rewardsCoordinator": "0xa7DB7B0E63B5B75e080924F9C842758711177c07", + "rewardsCoordinatorImplementation": "0x0e93df1A21CA53F93160AbDee19A92A20f8b397B", + "strategies": [ + { + "strategy_address": "0x4f812633943022fA97cb0881683aAf9f318D5Caa", + "token_address": "0x94373a4919B3240D86eA41593D5eBa789FEF3848", + "token_symbol": "WETH" + } + ], + "strategyBeacon": "0x957c04A5666079255fD75220a15918ecBA6039c6", + "strategyFactory": "0x09F8f1c1ca1815083a8a05E1b4A0c65EFB509141", + "strategyFactoryImplementation": "0x8b1F09f8292fd658Da35b9b3b1d4F7d1C0F3F592", + "strategyManager": "0x70f8bC2Da145b434de66114ac539c9756eF64fb3", + "strategyManagerImplementation": "0x1562BfE7Cb4644ff030C1dE4aA5A9aBb88a61aeC", + "token": { + "tokenProxyAdmin": "0x0000000000000000000000000000000000000000", + "EIGEN": "0x0000000000000000000000000000000000000000", + "bEIGEN": "0x0000000000000000000000000000000000000000", + "EIGENImpl": "0x0000000000000000000000000000000000000000", + "bEIGENImpl": "0x0000000000000000000000000000000000000000", + "eigenStrategy": "0x0000000000000000000000000000000000000000", + "eigenStrategyImpl": "0x0000000000000000000000000000000000000000" + } + }, + "chainInfo": { + "chainId": 17000, + "deploymentBlock": 2548240 + }, + "parameters": { + "communityMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "executorMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "operationsMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "pauserMultisig": "0xBB37b72F67A410B76Ce9b9aF9e37aa561B1C5B07", + "timelock": "0x0000000000000000000000000000000000000000" + } +} \ No newline at end of file diff --git a/src/test/DevnetLifecycle.t.sol b/src/test/DevnetLifecycle.t.sol new file mode 100644 index 000000000..9f51c7476 --- /dev/null +++ b/src/test/DevnetLifecycle.t.sol @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +// Contracts +import "../../src/contracts/core/DelegationManager.sol"; +import "../../src/contracts/core/StrategyManager.sol"; +import "../../src/contracts/core/AVSDirectory.sol"; +import "../../src/contracts/core/AllocationManager.sol"; +import "../../src/contracts/strategies/StrategyBase.sol"; + +// Test +import "forge-std/Test.sol"; + +/// @notice Tests deployed contracts as part of the public devnet +/// Run with: forge test --mc Devnet_Lifecycle_Test --rpc-url $RPC_HOLESKY +contract Devnet_Lifecycle_Test is Test { + + // Contracts + DelegationManager public delegationManager; + StrategyManager public strategyManager; + AVSDirectory public avsDirectory; + AllocationManager public allocationManager; + StrategyBase public wethStrategy; + IERC20 public weth; + + Vm cheats = Vm(VM_ADDRESS); + + // Addresses + address public staker = address(0x1); + address public operator; + uint256 operatorPk = 420; + address public avs = address(0x3); + uint32 public operatorSet = 1; + uint256 public wethAmount = 100 ether; + uint256 public wethShares = 100 ether; + + // Values + uint64 public magnitudeToSet = 1e18; + + function setUp() public { + // Set contracts + delegationManager = DelegationManager(0x3391eBafDD4b2e84Eeecf1711Ff9FC06EF9Ed182); + strategyManager = StrategyManager(0x70f8bC2Da145b434de66114ac539c9756eF64fb3); + avsDirectory = AVSDirectory(0xCa839541648D3e23137457b1Fd4A06bccEADD33a); + allocationManager = AllocationManager(0xAbD5Dd30CaEF8598d4EadFE7D45Fd582EDEade15); + wethStrategy = StrategyBase(0x4f812633943022fA97cb0881683aAf9f318D5Caa); + weth = IERC20(0x94373a4919B3240D86eA41593D5eBa789FEF3848); + + // Set operator + operator = cheats.addr(operatorPk); + } + + function _getOperatorSetArray() internal view returns (uint32[] memory) { + uint32[] memory operatorSets = new uint32[](1); + operatorSets[0] = operatorSet; + return operatorSets; + } + + function _getOperatorSetsArray() internal view returns (OperatorSet[] memory) { + OperatorSet[] memory operatorSets = new OperatorSet[](1); + operatorSets[0] = OperatorSet({avs: avs, operatorSetId: operatorSet}); + return operatorSets; + } + + function test() public { + if (block.chainid == 17000) { + // Seed staker with WETH + StdCheats.deal(address(weth), address(staker), wethAmount); + _run_lifecycle(); + } + } + + function _run_lifecycle() internal { + // Staker <> Operator Relationship + _depositIntoStrategy(); + _registerOperator(); + _delegateToOperator(); + + // Operator <> AVS Relationship + _registerAVS(); + _registerOperatorToAVS(); + _setMagnitude(); + + // Slash operator + _slashOperator(); + + // Withdraw staker + _withdrawStaker(); + } + + function _depositIntoStrategy() internal { + // Approve WETH + cheats.startPrank(staker); + weth.approve(address(strategyManager), wethAmount); + + // Deposit WETH into strategy + strategyManager.depositIntoStrategy(wethStrategy, weth, wethAmount); + cheats.stopPrank(); + + // Check staker balance + assertEq(weth.balanceOf(staker), 0); + + // Check staker shares + assertEq(strategyManager.stakerDepositShares(staker, wethStrategy), wethAmount); + } + + function _registerOperator() internal { + // Register operator + IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ + __deprecated_earningsReceiver: msg.sender, + delegationApprover: address(0), + __deprecated_stakerOptOutWindowBlocks: 0 + }); + string memory emptyStringForMetadataURI; + cheats.prank(operator); + delegationManager.registerAsOperator(operatorDetails, 1, emptyStringForMetadataURI); + // Warp passed configuration delay + cheats.warp(block.timestamp + delegationManager.MIN_WITHDRAWAL_DELAY()); + + // Validate storage + assertTrue(delegationManager.isOperator(operator)); + } + + function _delegateToOperator() internal { + // Delegate to operator + ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry; + cheats.prank(staker); + delegationManager.delegateTo(operator, signatureWithExpiry, bytes32(0)); + + // Validate storage + assertTrue(delegationManager.isDelegated(staker)); + assertEq(delegationManager.delegatedTo(staker), operator); + + // Validate operator shares + assertEq(delegationManager.operatorShares(operator, wethStrategy), wethShares); + } + + function _registerAVS() internal { + cheats.startPrank(avs); + avsDirectory.createOperatorSets(_getOperatorSetArray()); + avsDirectory.becomeOperatorSetAVS(); + cheats.stopPrank(); + + // Assert storage + assertTrue(avsDirectory.isOperatorSetAVS(avs)); + } + + function _registerOperatorToAVS() public { + bytes32 salt = bytes32(0); + uint256 expiry = type(uint256).max; + (uint8 v, bytes32 r, bytes32 s) = cheats.sign( + operatorPk, + avsDirectory.calculateOperatorSetRegistrationDigestHash(avs, _getOperatorSetArray(), salt, expiry) + ); + + cheats.prank(avs); + avsDirectory.registerOperatorToOperatorSets( + operator, + _getOperatorSetArray(), + ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(r, s, v), salt, expiry) + ); + + // Assert registration + assertTrue(avsDirectory.isMember( + operator, + OperatorSet({ + avs: avs, + operatorSetId: operatorSet + }) + )); + + // Assert operator is slashable + assertTrue(avsDirectory.isOperatorSlashable( + operator, + OperatorSet({ + avs: avs, + operatorSetId: operatorSet + }) + )); + } + + function _setMagnitude() public { + OperatorSet[] memory operatorSets = new OperatorSet[](1); + operatorSets[0] = OperatorSet({avs: avs, operatorSetId: operatorSet}); + + uint64[] memory magnitudes = new uint64[](1); + magnitudes[0] = magnitudeToSet; + + IAllocationManagerTypes.MagnitudeAllocation[] memory allocations = + new IAllocationManagerTypes.MagnitudeAllocation[](1); + allocations[0] = IAllocationManagerTypes.MagnitudeAllocation({ + strategy: wethStrategy, + expectedMaxMagnitude: 1e18, + operatorSets: operatorSets, + magnitudes: magnitudes + }); + + cheats.prank(operator); + allocationManager.modifyAllocations(allocations); + + // Assert storage + IAllocationManagerTypes.MagnitudeInfo[] memory infos = allocationManager.getAllocationInfo(operator, wethStrategy, _getOperatorSetsArray()); + assertEq(infos[0].currentMagnitude, 0); + assertEq(infos[0].pendingDiff, int128(uint128(magnitudeToSet))); + assertEq(infos[0].effectTimestamp, block.timestamp + 1); + + // Warp to effect timestamp + cheats.warp(block.timestamp + 1); + + // Check allocation + infos = allocationManager.getAllocationInfo(operator, wethStrategy, _getOperatorSetsArray()); + assertEq(infos[0].currentMagnitude, magnitudeToSet); + } + + function _slashOperator() public { + // Get slashing params + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = wethStrategy; + IAllocationManagerTypes.SlashingParams memory slashingParams = IAllocationManagerTypes.SlashingParams({ + operator: operator, + operatorSetId: 1, + strategies: strategies, + wadToSlash: 5e17, + description: "test" + }); + + // Slash operator + cheats.prank(avs); + allocationManager.slashOperator(slashingParams); + + // Assert storage + IAllocationManagerTypes.MagnitudeInfo[] memory infos = allocationManager.getAllocationInfo(operator, wethStrategy, _getOperatorSetsArray()); + assertEq(infos[0].currentMagnitude, magnitudeToSet - 5e17); + } + + function _withdrawStaker() public { + // Generate queued withdrawal params + IStrategy[] memory strategies = new IStrategy[](1); + strategies[0] = wethStrategy; + uint256[] memory withdrawableShares = delegationManager.getWithdrawableShares(staker, strategies); + IDelegationManagerTypes.QueuedWithdrawalParams[] memory queuedWithdrawals = new IDelegationManagerTypes.QueuedWithdrawalParams[](1); + queuedWithdrawals[0] = IDelegationManagerTypes.QueuedWithdrawalParams({ + strategies: strategies, + shares: withdrawableShares, + withdrawer: staker + }); + + // Generate withdrawal params + uint256[] memory scaledShares = new uint256[](1); + scaledShares[0] = 100e18; + IDelegationManagerTypes.Withdrawal memory withdrawal = IDelegationManagerTypes.Withdrawal({ + staker: staker, + delegatedTo: operator, + withdrawer: staker, + nonce: delegationManager.cumulativeWithdrawalsQueued(staker), + startTimestamp: uint32(block.timestamp), + strategies: strategies, + scaledSharesToWithdraw: scaledShares + }); + bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal); + // Generate complete withdrawal params + + cheats.startPrank(staker); + delegationManager.queueWithdrawals(queuedWithdrawals); + + // Roll passed withdrawal delay + cheats.warp(block.timestamp + delegationManager.MIN_WITHDRAWAL_DELAY()); + + // Complete withdrawal + IERC20[] memory tokens = new IERC20[](1); + tokens[0] = weth; + delegationManager.completeQueuedWithdrawal(withdrawal, tokens, true); + + // Assert tokens + assertEq(weth.balanceOf(staker), wethAmount / 2); + } +} \ No newline at end of file