diff --git a/.github/workflows/certora-prover.yml b/.github/workflows/certora-prover.yml
index 868addac1..e255d8e29 100644
--- a/.github/workflows/certora-prover.yml
+++ b/.github/workflows/certora-prover.yml
@@ -46,9 +46,8 @@ jobs:
run: pip install certora-cli
- name: Install solc
run: |
- wget https://github.com/ethereum/solidity/releases/download/v0.8.12/solc-static-linux
- sudo mv solc-static-linux /usr/local/bin/solc
- chmod +x /usr/local/bin/solc
+ pip install solc-select
+ solc-select use 0.8.12 --always-install
- name: Verify rule ${{ matrix.params }}
run: |
bash ${{ matrix.params }}
diff --git a/.github/workflows/testinparallel.yml b/.github/workflows/testinparallel.yml
index 97679d277..2d1fb3395 100644
--- a/.github/workflows/testinparallel.yml
+++ b/.github/workflows/testinparallel.yml
@@ -37,8 +37,11 @@ jobs:
with:
version: nightly
- - name: Install forge dependencies
- run: forge install
+ - name: Run Forge build
+ run: |
+ forge --version
+ forge build --sizes
+ id: build
- name: Run forge test for the file
run: forge test --match-path src/test/${{ matrix.file }} --no-match-contract FFI
diff --git a/README.md b/README.md
index 85426f254..72b7e1279 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,12 @@
EigenLayer is a set of smart contracts deployed on Ethereum that enable restaking of assets to secure new services. This repo contains the EigenLayer core contracts, whose currently-supported assets include beacon chain ETH and several liquid staking tokens (LSTs). Users use these contracts to deposit and withdraw these assets, as well as delegate them to operators providing services to AVSs.
+The most up to date mainnet and testnet deployment addresses can be found on our [docs site](https://docs.eigenlayer.xyz/eigenlayer/deployed-contracts).
+
## Getting Started
* [Documentation](#documentation)
* [Building and Running Tests](#building-and-running-tests)
-* [Deployments](#deployments)
## Documentation
@@ -75,61 +76,3 @@ surya inheritance ./src/contracts/**/*.sol | dot -Tpng > InheritanceGraph.png
surya mdreport surya_report.md ./src/contracts/**/*.sol
```
-## Deployments
-
-### Current Mainnet Deployment
-
-The current mainnet deployment is our M1 release, and is from a much older version of this repo. You can view the deployed contract addresses in [Current Mainnet Deployment](#current-mainnet-deployment) below, or check out the [`init-mainnet-deployment`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/mainnet-deployment) branch in "Releases".
-
-| Name | Solidity | Proxy | Implementation | Notes |
-| -------- | -------- | -------- | -------- | -------- |
-| StrategyManager | [`StrategyManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/core/StrategyManager.sol) | [`0x8586...075A`](https://etherscan.io/address/0x858646372CC42E1A627fcE94aa7A7033e7CF075A) | [`0x5d25...42Fb`](https://etherscan.io/address/0x5d25EEf8CfEdaA47d31fE2346726dE1c21e342Fb) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: cbETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x5494...56bc`](https://etherscan.io/address/0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: stETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x93c4...564D`](https://etherscan.io/address/0x93c4b944D05dfe6df7645A86cd2206016c51564D) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: rETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x1BeE...dCD2`](https://etherscan.io/address/0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: ETHx | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x9d7e...011d`](https://etherscan.io/address/0x9d7eD45EE2E8FC5482fa2428f15C971e6369011d) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: ankrETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x1376...58ff`](https://etherscan.io/address/0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: OETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xa4C6...d059`](https://etherscan.io/address/0xa4C637e0F704745D182e4D38cAb7E7485321d059) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: osETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x57ba...4c02`](https://etherscan.io/address/0x57ba429517c3473B6d34CA9aCd56c0e735b94c02) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: swETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x0Fe4...96d6`](https://etherscan.io/address/0x0Fe4F44beE93503346A3Ac9EE5A26b130a5796d6) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: wBETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x7CA9...2184`](https://etherscan.io/address/0x7CA911E83dabf90C90dD3De5411a10F1A6112184) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: sfrxETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x8CA7...28b6`](https://etherscan.io/address/0x8CA7A5d6f3acd3A7A8bC468a8CD0FB14B6BD28b6) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: lsETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xAe60...4473`](https://etherscan.io/address/0xAe60d8180437b5C34bB956822ac2710972584473) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: mETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x298a...6dd2`](https://etherscan.io/address/0x298aFB19A105D59E74658C4C334Ff360BadE6dd2) | [`0xdfdA...46d3`](https://etherscan.io/address/0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| EigenPodManager | [`EigenPodManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/pods/EigenPodManager.sol) | [`0x91E6...A338`](https://etherscan.io/address/0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338) | [`0xEB86...e111`](https://etherscan.io/address/0xEB86a5c40FdE917E6feC440aBbCDc80E3862e111) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/pods/EigenPod.sol) | [`0x5a2a...9073`](https://etherscan.io/address/0x5a2a4F2F3C18f09179B6703e63D9eDD165909073) | [`0x5c86...9dA7`](https://etherscan.io/address/0x5c86e9609fbBc1B754D0FD5a4963Fdf0F5b99dA7) | - Beacon: [OpenZeppelin BeaconProxy@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol)
- Deployed pods use [UpgradableBeacon@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) |
-| DelayedWithdrawalRouter | [`DelayedWithdrawalRouter`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x7Fe7...23D8`](https://etherscan.io/address/0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8) | [`0x44Bc...E2AF`](https://etherscan.io/address/0x44Bcb0E01CD0C5060D4Bb1A07b42580EF983E2AF) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| DelegationManager | [`DelegationManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/core/DelegationManager.sol) | [`0x3905...f37A`](https://etherscan.io/address/0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A) | [`0xf97E...75e4`](https://etherscan.io/address/0xf97E97649Da958d290e84E6D571c32F4b7F475e4) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Slasher | [`Slasher`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/core/Slasher.sol) | [`0xD921...c3Cd`](https://etherscan.io/address/0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd) | [`0xef31...d6d8`](https://etherscan.io/address/0xef31c292801f24f16479DD83197F1E6AeBb8d6d8) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| PauserRegistry | [`PauserRegistry`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/permissions/PauserRegistry.sol) | - | [`0x0c43...7060`](https://etherscan.io/address/0x0c431C66F4dE941d089625E5B423D00707977060) | |
-| Pauser Multisig | [`GnosisSafe@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol) | [`0x5050…2390`](https://etherscan.io/address/0x5050389572f2d220ad927CcbeA0D406831012390) | [`0xd9db...9552`](https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552) | Proxy: [`GnosisSafeProxy@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/proxies/GnosisSafeProxy.sol) |
-| Community Multisig | [`GnosisSafe@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol) | [`0xFEA4...c598`](https://etherscan.io/address/0xFEA47018D632A77bA579846c840d5706705Dc598) | [`0xd9db...9552`](https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552) | Proxy: [`GnosisSafeProxy@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/proxies/GnosisSafeProxy.sol) |
-| Executor Multisig | [`GnosisSafe@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol) | [`0x369e...9111`](https://etherscan.io/address/0x369e6F597e22EaB55fFb173C6d9cD234BD699111) | [`0xd9db...9552`](https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552) | Proxy: [`GnosisSafeProxy@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/proxies/GnosisSafeProxy.sol) |
-| Operations Multisig | [`GnosisSafe@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/GnosisSafe.sol) | [`0xBE16...3e90`](https://etherscan.io/address/0xBE1685C81aA44FF9FB319dD389addd9374383e90) | [`0xd9db...9552`](https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552) | Proxy: [`GnosisSafeProxy@1.3.0`](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/proxies/GnosisSafeProxy.sol) |
-| Timelock | [Compound: `Timelock.sol`](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Timelock.sol) | - | [`0xA6Db...0EAF`](https://etherscan.io/address/0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF) | |
-| Proxy Admin | [OpenZeppelin ProxyAdmin@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/ProxyAdmin.sol) | - | [`0x8b95...2444`](https://etherscan.io/address/0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444) | |
-
-### Current Testnet Deployment
-
-The current testnet deployment is from our M2 beta release, which is a slightly older version of this repo. You can view the deployed contract addresses in [Current Testnet Deployment](#current-testnet-deployment), or check out the [`goerli-m2-deployment`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/goerli-m2-deployment) branch in "Releases".
-
-| Name | Solidity | Proxy | Implementation | Notes |
-| -------- | -------- | -------- | -------- | -------- |
-| StrategyManager | [`StrategyManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/StrategyManager.sol) | [`0x779d...E907`](https://goerli.etherscan.io/address/0x779d1b5315df083e3F9E94cB495983500bA8E907) | [`0x8676...0055`](https://goerli.etherscan.io/address/0x8676bb5f792ED407a237234Fe422aC6ed3540055) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: stETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xB613...14da`](https://goerli.etherscan.io/address/0xb613e78e2068d7489bb66419fb1cfa11275d14da) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: rETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x8799...70b5`](https://goerli.etherscan.io/address/0x879944A8cB437a5f8061361f82A6d4EED59070b5) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: ETHx | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x5d1E...7C14`](https://goerli.etherscan.io/address/0x5d1E9DC056C906CBfe06205a39B0D965A6Df7C14) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: ankrETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x98b4...dB8E`](https://goerli.etherscan.io/address/0x98b47798B68b734af53c930495595729E96cdB8E) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: lsETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xa9DC...37A9`](https://goerli.etherscan.io/address/0xa9DC3c93ae59B8d26AF17Ae63c96Be78793537A9) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: mETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x1755...9b14`](https://goerli.etherscan.io/address/0x1755d34476BB4DaEd726ee4a81E8132dF00F9b14) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: wBETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xD89d...E164`](https://goerli.etherscan.io/address/0xD89dc4C40d901D4622C203Fb8808e6e7C7fcE164) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| EigenPodManager | [`EigenPodManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/EigenPodManager.sol) | [`0xa286...df41`](https://goerli.etherscan.io/address/0xa286b84C96aF280a49Fe1F40B9627C2A2827df41) | [`0xdD09...901b`](https://goerli.etherscan.io/address/0xdD09d95bD25299EDBF4f33d76F84dBc77b0B901b) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| EigenLayerBeaconOracle | [`succinctlabs/EigenLayerBeaconOracle.sol`](https://github.com/succinctlabs/telepathy-contracts/blob/main/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol) | [`0x40B1...9f2c`](https://goerli.etherscan.io/address/0x40B10ddD29a2cfF33DBC420AE5bbDa0649049f2c) | [`0x0a6e...db01`](https://goerli.etherscan.io/address/0x0a6e235c30658dbdb53147fbb199878a4e34db01) | OpenZeppelin UUPS |
-| EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/EigenPod.sol) | [`0x3093...C9a5`](https://goerli.etherscan.io/address/0x3093F3B560352F896F0e9567019902C9Aff8C9a5) | [`0x86bf...6CcA`](https://goerli.etherscan.io/address/0x86bf376E0C0c9c6D332E13422f35Aca75C106CcA) | - Beacon: [OpenZeppelin BeaconProxy@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol)
- Deployed pods use [UpgradableBeacon@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) |
-| DelayedWithdrawalRouter | [`DelayedWithdrawalRouter`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x8958...388f`](https://goerli.etherscan.io/address/0x89581561f1F98584F88b0d57c2180fb89225388f) | [`0x6070...27fe`](https://goerli.etherscan.io/address/0x60700ade3Bf48C437a5D01b6Da8d7483cffa27fe) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| DelegationManager | [`DelegationManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/DelegationManager.sol) | [`0x1b7b...b0a8`](https://goerli.etherscan.io/address/0x1b7b8F6b258f95Cf9596EabB9aa18B62940Eb0a8) | [`0x9b79...A99d`](https://goerli.etherscan.io/address/0x9b7980a32ceCe2Aa936DD2E43AF74af62581A99d) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Slasher | [`Slasher`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/Slasher.sol) | [`0xD11d...0C22`](https://goerli.etherscan.io/address/0xD11d60b669Ecf7bE10329726043B3ac07B380C22) | [`0x3865...8Be6`](https://goerli.etherscan.io/address/0x3865B5F5297f86c5295c7f818BAD1fA5286b8Be6) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| PauserRegistry | [`PauserRegistry`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/permissions/PauserRegistry.sol) | - | [`0x2588...0010`](https://goerli.etherscan.io/address/0x2588f9299871a519883D92dcd5092B4A0Cf70010) | |
-| Timelock | [Compound: `Timelock.sol`](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Timelock.sol) | - | [`0xa7e7...796e`](https://goerli.etherscan.io/address/0xa7e72a0564ebf25fa082fc27020225edeaf1796e) | |
-| Proxy Admin | [OpenZeppelin ProxyAdmin@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/ProxyAdmin.sol) | - | [`0x28ce...02e2`](https://goerli.etherscan.io/address/0x28ceac2ff82B2E00166e46636e2A4818C29902e2) | |
-
diff --git a/certora/specs/core/StrategyManager.spec b/certora/specs/core/StrategyManager.spec
index ec7f7b9cd..43cd8e20f 100644
--- a/certora/specs/core/StrategyManager.spec
+++ b/certora/specs/core/StrategyManager.spec
@@ -20,7 +20,7 @@ methods {
// external calls to StrategyManager
function _.getDeposits(address) external => DISPATCHER(true);
function _.slasher() external => DISPATCHER(true);
- function _.addShares(address,address,uint256) external => DISPATCHER(true);
+ function _.addShares(address,address,address,uint256) external => DISPATCHER(true);
function _.removeShares(address,address,uint256) external => DISPATCHER(true);
function _.withdrawSharesAsTokens(address, address, uint256, address) external => DISPATCHER(true);
@@ -97,7 +97,7 @@ definition methodCanIncreaseShares(method f) returns bool =
f.selector == sig:depositIntoStrategy(address,address,uint256).selector
|| f.selector == sig:depositIntoStrategyWithSignature(address,address,uint256,address,uint256,bytes).selector
|| f.selector == sig:withdrawSharesAsTokens(address,address,uint256,address).selector
- || f.selector == sig:addShares(address,address,uint256).selector;
+ || f.selector == sig:addShares(address,address,address,uint256).selector;
/**
* a staker's amount of shares in a strategy (i.e. `stakerStrategyShares[staker][strategy]`) should only decrease when
@@ -129,7 +129,7 @@ rule newSharesIncreaseTotalShares(address strategy) {
uint256 stakerStrategySharesBefore = get_stakerStrategyShares(e.msg.sender, strategy);
uint256 totalSharesBefore = totalShares(strategy);
if (
- f.selector == sig:addShares(address, address, uint256).selector
+ f.selector == sig:addShares(address, address, address, uint256).selector
|| f.selector == sig:removeShares(address, address, uint256).selector
) {
uint256 totalSharesAfter = totalShares(strategy);
diff --git a/docs/README.md b/docs/README.md
index 435309b00..2c8f27fb3 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -11,6 +11,11 @@ This document provides an overview of system components, contracts, and user rol
#### Contents
* [System Components](#system-components)
+ * [`EigenPodManager`](#eigenpodmanager)
+ * [`StrategyManager`](#strategymanager)
+ * [`DelegationManager`](#delegationmanager)
+ * [`AVSDirectory`](#avsdirectory)
+ * [`Slasher`](#slasher)
* [Roles and Actors](#roles-and-actors)
### System Components
@@ -40,7 +45,7 @@ See full documentation in [`/core/EigenPodManager.md`](./core/EigenPodManager.md
| [`StrategyBaseTVLLimits.sol`](../src/contracts/strategies/StrategyBaseTVLLimits.sol) | One instance per supported LST | Transparent proxy |
These contracts work together to enable restaking for LSTs:
-* The `StrategyManager` acts as the entry and exit point for LSTs in EigenLayer. It handles deposits into each of the 3 LST-specific strategies, and manages accounting+interactions between users with restaked LSTs and the `DelegationManager`.
+* The `StrategyManager` acts as the entry and exit point for LSTs in EigenLayer. It handles deposits into LST-specific strategies, and manages accounting+interactions between users with restaked LSTs and the `DelegationManager`.
* `StrategyBaseTVLLimits` is deployed as multiple separate instances, one for each supported LST. When a user deposits into a strategy through the `StrategyManager`, this contract receives the tokens and awards the user with a proportional quantity of shares in the strategy. When a user withdraws, the strategy contract sends the LSTs back to the user.
See full documentation in [`/core/StrategyManager.md`](./core/StrategyManager.md).
@@ -55,6 +60,18 @@ The `DelegationManager` sits between the `EigenPodManager` and `StrategyManager`
See full documentation in [`/core/DelegationManager.md`](./core/DelegationManager.md).
+#### AVSDirectory
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`AVSDirectory.sol`](../src/contracts/core/AVSDirectory.sol) | Singleton | Transparent proxy |
+
+The `AVSDirectory` handles interactions between AVSs and the EigenLayer core contracts. Once registered as an Operator in EigenLayer core (via the `DelegationManager`), Operators can register with one or more AVSs (via the AVS's contracts) to begin providing services to them offchain. As a part of registering with an AVS, the AVS will record this registration in the core contracts by calling into the `AVSDirectory`.
+
+See full documentation in [`/core/AVSDirectory.md`](./core/AVSDirectory.md).
+
+For more information on AVS contracts, see the [middleware repo][middleware-repo].
+
#### Slasher
| File | Type | Proxy |
diff --git a/docs/core/AVSDirectory.md b/docs/core/AVSDirectory.md
new file mode 100644
index 000000000..e025345b5
--- /dev/null
+++ b/docs/core/AVSDirectory.md
@@ -0,0 +1,80 @@
+[middleware-repo]: https://github.com/Layr-Labs/eigenlayer-middleware/
+
+## AVSDirectory
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`AVSDirectory.sol`](../src/contracts/core/AVSDirectory.sol) | Singleton | Transparent proxy |
+
+The `AVSDirectory` handles interactions between AVSs and the EigenLayer core contracts. Once registered as an Operator in EigenLayer core (via the `DelegationManager`), Operators can register with one or more AVSs (via the AVS's contracts) to begin providing services to them offchain. As a part of registering with an AVS, the AVS will record this registration in the core contracts by calling into the `AVSDirectory`.
+
+For more information on AVS contracts, see the [middleware repo][middleware-repo].
+
+Currently, the only interactions between AVSs and the core contracts is to track whether Operators are currently registered for the AVS. This is handled by two methods:
+* [`AVSDirectory.registerOperatorToAVS`](#registeroperatortoavs)
+* [`AVSDirectory.deregisterOperatorFromAVS`](#deregisteroperatorfromavs)
+
+In a future release, this contract will implement additional interactions that relate to (i) paying Operators for the services they provide and (ii) slashing Operators that misbehave. Currently, these features are not implemented.
+
+---
+
+#### `registerOperatorToAVS`
+
+```solidity
+function registerOperatorToAVS(
+ address operator,
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
+)
+ external
+ onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS)
+```
+
+Allows the caller (an AVS) to register an `operator` with itself, given the provided signature is valid.
+
+*Effects*:
+* Sets the `operator's` status to `REGISTERED` for the AVS
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS`
+* `operator` MUST already be a registered Operator (via the `DelegationManager`)
+* `operator` MUST NOT already be registered with the AVS
+* `operatorSignature` must be a valid, unused, unexpired signature from the `operator`. The signature is an ECDSA signature by the operator over the [`OPERATOR_AVS_REGISTRATION_TYPEHASH`](../../src/contracts/core/DelegationManagerStorage.sol). Expiry is a utc timestamp in seconds. Salt is used only once per signature to prevent replay attacks.
+
+*As of M2*:
+* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
+
+#### `deregisterOperatorFromAVS`
+
+```solidity
+function deregisterOperatorFromAVS(
+ address operator
+)
+ external
+ onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS)
+```
+
+Allows the caller (an AVS) to deregister an `operator` with itself
+
+*Effects*:
+* Sets the `operator's` status to `UNREGISTERED` for the AVS
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS`
+* `operator` MUST already be registered with the AVS
+
+*As of M2*:
+* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
+
+#### `cancelSalt`
+
+```solidity
+function cancelSalt(bytes32 salt) external
+```
+
+Allows the caller (an Operator) to cancel a signature salt before it is used to register for an AVS.
+
+*Effects*:
+* Sets `operatorSaltIsSpent[msg.sender][salt]` to `true`
+
+*Requirements*:
+* Salt MUST NOT already be cancelled
\ No newline at end of file
diff --git a/docs/core/DelegationManager.md b/docs/core/DelegationManager.md
index b851f95cd..28c18547f 100644
--- a/docs/core/DelegationManager.md
+++ b/docs/core/DelegationManager.md
@@ -19,6 +19,7 @@ This document organizes methods according to the following themes (click each to
* [Delegating to an Operator](#delegating-to-an-operator)
* [Undelegating and Withdrawing](#undelegating-and-withdrawing)
* [Accounting](#accounting)
+* [System Configuration](#system-configuration)
#### Important state variables
@@ -28,9 +29,13 @@ This document organizes methods according to the following themes (click each to
* `mapping(address => mapping(IStrategy => uint256)) public operatorShares`: Tracks the current balance of shares an Operator is delegated according to each strategy. Updated by both the `StrategyManager` and `EigenPodManager` when a Staker's delegatable balance changes.
* Because Operators are delegated to themselves, an Operator's own restaked assets are reflected in these balances.
* A similar mapping exists in the `StrategyManager`, but the `DelegationManager` additionally tracks beacon chain ETH delegated via the `EigenPodManager`. The "beacon chain ETH" strategy gets its own special address for this mapping: `0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0`.
-* `uint256 public withdrawalDelayBlocks`:
+* `uint256 public minWithdrawalDelayBlocks`:
* As of M2, this is 50400 (roughly 1 week)
- * Stakers must wait this amount of time before a withdrawal can be completed
+ * For all strategies including native beacon chain ETH, Stakers at minimum must wait this amount of time before a withdrawal can be completed.
+ To withdraw a specific strategy, it may require additional time depending on the strategy's withdrawal delay. See `strategyWithdrawalDelayBlocks` below.
+* `mapping(IStrategy => uint256) public strategyWithdrawalDelayBlocks`:
+ * This mapping tracks the withdrawal delay for each strategy. This mapping value only comes into affect
+ if `strategyWithdrawalDelayBlocks[strategy] > minWithdrawalDelayBlocks`. Otherwise, `minWithdrawalDelayBlocks` is used.
* `mapping(bytes32 => bool) public pendingWithdrawals;`:
* `Withdrawals` are hashed and set to `true` in this mapping when a withdrawal is initiated. The hash is set to false again when the withdrawal is completed. A per-staker nonce provides a way to distinguish multiple otherwise-identical withdrawals.
@@ -51,10 +56,6 @@ Operators interact with the following functions to become an Operator:
* [`DelegationManager.modifyOperatorDetails`](#modifyoperatordetails)
* [`DelegationManager.updateOperatorMetadataURI`](#updateoperatormetadatauri)
-Once registered as an Operator, Operators can use an AVS's contracts to register with a specific AVS. The AVS will call these functions on the Operator's behalf:
-* [`DelegationManager.registerOperatorToAVS`](#registeroperatortoavs)
-* [`DelegationManager.deregisterOperatorFromAVS`](#deregisteroperatorfromavs)
-
#### `registerAsOperator`
```solidity
@@ -106,53 +107,6 @@ Allows an Operator to emit an `OperatorMetadataURIUpdated` event. No other state
*Requirements*:
* Caller MUST already be an Operator
-#### `registerOperatorToAVS`
-
-```solidity
-function registerOperatorToAVS(
- address operator,
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
-)
- external
- onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS)
-```
-
-Allows the caller (an AVS) to register an `operator` with itself, given the provided signature is valid.
-
-*Effects*:
-* Sets the `operator's` status to `REGISTERED` for the AVS
-
-*Requirements*:
-* Pause status MUST NOT be set: `PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS`
-* `operator` MUST already be a registered Operator
-* `operator` MUST NOT already be registered with the AVS
-* `operatorSignature` must be a valid, unused, unexpired signature from the `operator`
-
-*As of M2*:
-* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
-
-#### `deregisterOperatorFromAVS`
-
-```solidity
-function deregisterOperatorFromAVS(
- address operator
-)
- external
- onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS)
-```
-
-Allows the caller (an AVS) to deregister an `operator` with itself
-
-*Effects*:
-* Sets the `operator's` status to `UNREGISTERED` for the AVS
-
-*Requirements*:
-* Pause status MUST NOT be set: `PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS`
-* `operator` MUST already be registered with the AVS
-
-*As of M2*:
-* Operator registration/deregistration does not have any sort of consequences for the Operator or its shares. Eventually, this will tie into payments for services and slashing for misbehavior.
-
---
### Delegating to an Operator
@@ -236,7 +190,7 @@ function undelegate(
If the Staker has active shares in either the `EigenPodManager` or `StrategyManager`, they are removed while the withdrawal is in the queue - and an individual withdrawal is queued for each strategy removed.
-The withdrawals can be completed by the Staker after `withdrawalDelayBlocks`. This does not require the Staker to "fully exit" from the system -- the Staker may choose to receive their shares back in full once withdrawals are completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
+The withdrawals can be completed by the Staker after max(`minWithdrawalDelayBlocks`, `strategyWithdrawalDelayBlocks[strategy]`) where `strategy` is any of the Staker's delegated strategies. This does not require the Staker to "fully exit" from the system -- the Staker may choose to receive their shares back in full once withdrawals are completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
Note that becoming an Operator is irreversible! Although Operators can withdraw, they cannot use this method to undelegate from themselves.
@@ -272,18 +226,18 @@ function queueWithdrawals(
Allows the caller to queue one or more withdrawals of their held shares across any strategy (in either/both the `EigenPodManager` or `StrategyManager`). If the caller is delegated to an Operator, the `shares` and `strategies` being withdrawn are immediately removed from that Operator's delegated share balances. Note that if the caller is an Operator, this still applies, as Operators are essentially delegated to themselves.
-`queueWithdrawals` works very similarly to `undelegate`, except that the caller is not undelegated, and also may:
-* Choose which strategies and how many shares to withdraw (as opposed to ALL shares/strategies)
-* Specify a `withdrawer` to receive withdrawn funds once the withdrawal is completed
+`queueWithdrawals` works very similarly to `undelegate`, except that the caller is not undelegated, and also may choose which strategies and how many shares to withdraw (as opposed to ALL shares/strategies).
All shares being withdrawn (whether via the `EigenPodManager` or `StrategyManager`) are removed while the withdrawals are in the queue.
-Withdrawals can be completed by the `withdrawer` after `withdrawalDelayBlocks`, and does not require the `withdrawer` to "fully exit" from the system -- they may choose to receive their shares back in full once the withdrawal is completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
+Withdrawals can be completed by the caller after max(`minWithdrawalDelayBlocks`, `strategyWithdrawalDelayBlocks[strategy]`) such that `strategy` represents the queued strategies to be withdrawn. Withdrawals do not require the caller to "fully exit" from the system -- they may choose to receive their shares back in full once the withdrawal is completed (see [`completeQueuedWithdrawal`](#completequeuedwithdrawal) for details).
+
+Note that the `QueuedWithdrawalParams` struct has a `withdrawer` field. Originally, this was used to specify an address that the withdrawal would be credited to once completed. However, `queueWithdrawals` now requires that `withdrawer == msg.sender`. Any other input is rejected.
*Effects*:
* For each withdrawal:
* If the caller is delegated to an Operator, that Operator's delegated balances are decreased according to the `strategies` and `shares` being withdrawn.
- * A `Withdrawal` is queued for the `withdrawer`, tracking the strategies and shares being withdrawn
+ * A `Withdrawal` is queued for the caller, tracking the strategies and shares being withdrawn
* The caller's withdrawal nonce is increased
* The hash of the `Withdrawal` is marked as "pending"
* See [`EigenPodManager.removeShares`](./EigenPodManager.md#eigenpodmanagerremoveshares)
@@ -294,7 +248,7 @@ Withdrawals can be completed by the `withdrawer` after `withdrawalDelayBlocks`,
* For each withdrawal:
* `strategies.length` MUST equal `shares.length`
* `strategies.length` MUST NOT be equal to 0
- * The `withdrawer` MUST NOT be 0
+ * The `withdrawer` MUST equal `msg.sender`
* See [`EigenPodManager.removeShares`](./EigenPodManager.md#eigenpodmanagerremoveshares)
* See [`StrategyManager.removeShares`](./StrategyManager.md#removeshares)
@@ -312,7 +266,7 @@ function completeQueuedWithdrawal(
nonReentrant
```
-After waiting `withdrawalDelayBlocks`, this allows the `withdrawer` of a `Withdrawal` to finalize a withdrawal and receive either (i) the underlying tokens of the strategies being withdrawn from, or (ii) the shares being withdrawn. This choice is dependent on the passed-in parameter `receiveAsTokens`.
+After waiting max(`minWithdrawalDelayBlocks`, `strategyWithdrawalDelayBlocks[strategy]`) number of blocks, this allows the `withdrawer` of a `Withdrawal` to finalize a withdrawal and receive either (i) the underlying tokens of the strategies being withdrawn from, or (ii) the shares being withdrawn. This choice is dependent on the passed-in parameter `receiveAsTokens`.
For each strategy/share pair in the `Withdrawal`:
* If the `withdrawer` chooses to receive tokens:
@@ -345,7 +299,8 @@ For each strategy/share pair in the `Withdrawal`:
*Requirements*:
* Pause status MUST NOT be set: `PAUSED_EXIT_WITHDRAWAL_QUEUE`
* The hash of the passed-in `Withdrawal` MUST correspond to a pending withdrawal
- * At least `withdrawalDelayBlocks` MUST have passed before `completeQueuedWithdrawal` is called
+ * At least `minWithdrawalDelayBlocks` MUST have passed before `completeQueuedWithdrawal` is called
+ * For all strategies in the `Withdrawal`, at least `strategyWithdrawalDelayBlocks[strategy]` MUST have passed before `completeQueuedWithdrawal` is called
* Caller MUST be the `withdrawer` specified in the `Withdrawal`
* If `receiveAsTokens`:
* The caller MUST pass in the underlying `IERC20[] tokens` being withdrawn in the appropriate order according to the strategies in the `Withdrawal`.
@@ -432,4 +387,51 @@ Called by the `EigenPodManager` when a Staker's shares decrease. This method is
* This method is a no-op if the Staker is not delegated to an Operator.
*Requirements*:
-* Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method)
\ No newline at end of file
+* Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method)
+
+---
+
+### System Configuration
+
+* [`DelegationManager.setMinWithdrawalDelayBlocks`](#setminwithdrawaldelayblocks)
+* [`DelegationManager.setStrategyWithdrawalDelayBlocks`](#setstrategywithdrawaldelayblocks)
+
+#### `setMinWithdrawalDelayBlocks`
+
+```solidity
+function setMinWithdrawalDelayBlocks(
+ uint256 newMinWithdrawalDelayBlocks
+)
+ external
+ onlyOwner
+```
+
+Allows the Owner to set the overall minimum withdrawal delay for withdrawals concerning any strategy. The total time required for a withdrawal to be completable is at least `minWithdrawalDelayBlocks`. If any of the withdrawal's strategies have a higher per-strategy withdrawal delay, the time required is the maximum of these per-strategy delays.
+
+*Effects*:
+* Sets the global `minWithdrawalDelayBlocks`
+
+*Requirements*:
+* Caller MUST be the Owner
+* The new value MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS`
+
+#### `setStrategyWithdrawalDelayBlocks`
+
+```solidity
+function setStrategyWithdrawalDelayBlocks(
+ IStrategy[] calldata strategies,
+ uint256[] calldata withdrawalDelayBlocks
+)
+ external
+ onlyOwner
+```
+
+Allows the Owner to set a per-strategy withdrawal delay for each passed-in strategy. The total time required for a withdrawal to be completable is at least `minWithdrawalDelayBlocks`. If any of the withdrawal's strategies have a higher per-strategy withdrawal delay, the time required is the maximum of these per-strategy delays.
+
+*Effects*:
+* For each `strategy`, sets `strategyWithdrawalDelayBlocks[strategy]` to a new value
+
+*Requirements*:
+* Caller MUST be the Owner
+* `strategies.length` MUST be equal to `withdrawalDelayBlocks.length`
+* For each entry in `withdrawalDelayBlocks`, the value MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS`
\ No newline at end of file
diff --git a/docs/core/EigenPodManager.md b/docs/core/EigenPodManager.md
index d6c7ca6b4..f770782de 100644
--- a/docs/core/EigenPodManager.md
+++ b/docs/core/EigenPodManager.md
@@ -20,7 +20,7 @@ The `EigenPodManager` is the entry point for this process, allowing Stakers to d
`EigenPods` serve as the withdrawal credentials for one or more beacon chain validators controlled by a Staker. Their primary role is to validate beacon chain proofs for each of the Staker's validators. Beacon chain proofs are used to verify a validator's:
* `EigenPod.verifyWithdrawalCredentials`: withdrawal credentials and effective balance
-* `EigenPod.verifyBalanceUpdates`: current balance
+* `EigenPod.verifyBalanceUpdates`: effective balance (when it changes)
* `EigenPod.verifyAndProcessWithdrawals`: withdrawable epoch, and processed withdrawals within historical block summary
See [/proofs](./proofs/) for detailed documentation on each of the state proofs used in these methods. Additionally, proofs are checked against a beacon chain block root supplied by Succinct's Telepathy protocol ([docs link](https://docs.telepathy.xyz/)).
@@ -223,7 +223,7 @@ function verifyBalanceUpdates(
uint64 oracleTimestamp,
uint40[] calldata validatorIndices,
BeaconChainProofs.StateRootProof calldata stateRootProof,
- BeaconChainProofs.BalanceUpdateProof[] calldata balanceUpdateProofs,
+ bytes[] calldata validatorFieldsProofs,
bytes32[][] calldata validatorFields
)
external
diff --git a/docs/core/StrategyManager.md b/docs/core/StrategyManager.md
index 0ab265501..a6fdb4cbd 100644
--- a/docs/core/StrategyManager.md
+++ b/docs/core/StrategyManager.md
@@ -23,6 +23,9 @@ This document organizes methods according to the following themes (click each to
* `mapping(address => IStrategy[]) public stakerStrategyList`: Maintains a list of the strategies a Staker holds a nonzero number of shares in.
* Updated as needed when Stakers deposit and withdraw: if a Staker has a zero balance in a Strategy, it is removed from the list. Likewise, if a Staker deposits into a Strategy and did not previously have a balance, it is added to the list.
* `mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit`: The `strategyWhitelister` is (as of M2) a permissioned role that can be changed by the contract owner. The `strategyWhitelister` has currently whitelisted 3 `StrategyBaseTVLLimits` contracts in this mapping, one for each supported LST.
+* `mapping(IStrategy => bool) public thirdPartyTransfersForbidden`: The `strategyWhitelister` can disable third party transfers for a given strategy. If `thirdPartyTransfersForbidden[strategy] == true`:
+ * Users cannot deposit on behalf of someone else (see [`depositIntoStrategyWithSignature`](#depositintostrategywithsignature)).
+ * Users cannot withdraw on behalf of someone else. (see [`DelegationManager.queueWithdrawals`](./DelegationManager.md#queuewithdrawals))
#### Helpful definitions
@@ -97,6 +100,7 @@ function depositIntoStrategyWithSignature(
*Requirements*: See `depositIntoStrategy` above. Additionally:
* Caller MUST provide a valid, unexpired signature over the correct fields
+* `thirdPartyTransfersForbidden[strategy]` MUST be false
---
@@ -271,6 +275,7 @@ This method converts the withdrawal shares back into tokens using the strategy's
* [`StrategyManager.setStrategyWhitelister`](#setstrategywhitelister)
* [`StrategyManager.addStrategiesToDepositWhitelist`](#addstrategiestodepositwhitelist)
* [`StrategyManager.removeStrategiesFromDepositWhitelist`](#removestrategiesfromdepositwhitelist)
+* [`StrategyManager.setThirdPartyTransfersForbidden`](#setthirdpartytransfersforbidden)
#### `setStrategyWhitelister`
@@ -289,13 +294,19 @@ Allows the `owner` to update the Strategy Whitelister address.
#### `addStrategiesToDepositWhitelist`
```solidity
-function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external onlyStrategyWhitelister
+function addStrategiesToDepositWhitelist(
+ IStrategy[] calldata strategiesToWhitelist,
+ bool[] calldata thirdPartyTransfersForbiddenValues
+)
+ external
+ onlyStrategyWhitelister
```
-Allows the Strategy Whitelister address to add any number of strategies to the `StrategyManager` whitelist. Strategies on the whitelist are eligible for deposit via `depositIntoStrategy`.
+Allows the Strategy Whitelister to add any number of strategies to the `StrategyManager` whitelist, and configure whether third party transfers are enabled or disabled for each. Strategies on the whitelist are eligible for deposit via `depositIntoStrategy`.
*Effects*:
* Adds entries to `StrategyManager.strategyIsWhitelistedForDeposit`
+* Sets `thirdPartyTransfersForbidden` for each added strategy
*Requirements*:
* Caller MUST be the `strategyWhitelister`
@@ -303,13 +314,38 @@ Allows the Strategy Whitelister address to add any number of strategies to the `
#### `removeStrategiesFromDepositWhitelist`
```solidity
-function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external onlyStrategyWhitelister
+function removeStrategiesFromDepositWhitelist(
+ IStrategy[] calldata strategiesToRemoveFromWhitelist
+)
+ external
+ onlyStrategyWhitelister
```
-Allows the Strategy Whitelister address to remove any number of strategies from the `StrategyManager` whitelist. The removed strategies will no longer be eligible for deposit via `depositIntoStrategy`. However, withdrawals for previously-whitelisted strategies may still be initiated and completed, as long as the Staker has shares to withdraw.
+Allows the Strategy Whitelister to remove any number of strategies from the `StrategyManager` whitelist. The removed strategies will no longer be eligible for deposit via `depositIntoStrategy`. However, withdrawals for previously-whitelisted strategies may still be initiated and completed, as long as the Staker has shares to withdraw.
*Effects*:
* Removes entries from `StrategyManager.strategyIsWhitelistedForDeposit`
+*Requirements*:
+* Caller MUST be the `strategyWhitelister`
+
+#### `setThirdPartyTransfersForbidden`
+
+```solidity
+function setThirdPartyTransfersForbidden(
+ IStrategy strategy,
+ bool value
+)
+ external
+ onlyStrategyWhitelister
+```
+
+Allows the Strategy Whitelister to enable or disable third-party transfers for any `strategy`. If third-party transfers are disabled:
+* Deposits via [`depositIntoStrategyWithSiganture`](#depositintostrategywithsignature) are disabled.
+* Withdrawals to a different address via [`DelegationManager.queueWithdrawals`](./DelegationManager.md#queuewithdrawals) are disabled.
+
+*Effects*:
+* Sets `thirdPartyTransfersForbidden[strategy]`, even if that strategy is not currently whitelisted
+
*Requirements*:
* Caller MUST be the `strategyWhitelister`
\ No newline at end of file
diff --git a/docs/core/proofs/BeaconChainProofs.md b/docs/core/proofs/BeaconChainProofs.md
index bcd416d2f..c174ef942 100644
--- a/docs/core/proofs/BeaconChainProofs.md
+++ b/docs/core/proofs/BeaconChainProofs.md
@@ -15,40 +15,28 @@ Below are the explanations of each individual proof function that we use to prov
#### `BeaconChainProofs.verifyValidatorFields`
```solidity
- function verifyValidatorFields(
- bytes32 beaconStateRoot,
- bytes32[] calldata validatorFields,
- bytes calldata validatorFieldsProof,
- uint40 validatorIndex
- ) internal
+function verifyValidatorFields(
+ bytes32 beaconStateRoot,
+ bytes32[] calldata validatorFields,
+ bytes calldata validatorFieldsProof,
+ uint40 validatorIndex
+)
+ internal
```
-Verifies the proof of a provided [validator container](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator) against the beacon state root. This proof can be used to verify any field in the validator container. Below is a diagram that illustrates exactly how the proof is structured relative to the [beacon state object](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate).
+Verifies the proof of a provided [validator container](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator) against the beacon state root. This proof can be used to verify any field in the validator container. In the EigenPods system, this proof is used to prove a validator's withdrawal credentials as well as their effective balance. Below is a diagram that illustrates exactly how the proof is structured relative to the [beacon state object](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate).
![Verify Validator Fields Proof Structure](../../images/Withdrawal_Credential_Proof.png)
-#### `BeaconChainProofs.verifyValidatorBalance`
-
-```solidity
-function verifyValidatorBalance(
- bytes32 beaconStateRoot,
- bytes32 balanceRoot,
- bytes calldata validatorBalanceProof,
- uint40 validatorIndex
- ) internal
-```
-Verifies the proof of a [validator's](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator) balance against the beacon state root. Validator's balances are stored separately in "balances" field of the [beacon state object](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate), with each entry corresponding to the appropriate validator, based on index. Below is a diagram that illustrates exactly how the proof is structured relative to the [beacon state object](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate).
-
-![Verify Validator Fields Proof Structure](../../images/Balance_Proof.png)
-
#### `BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot`
```solidity
function verifyStateRootAgainstLatestBlockRoot(
- bytes32 latestBlockRoot,
- bytes32 beaconStateRoot,
- bytes calldata stateRootProof
- ) internal
+ bytes32 latestBlockRoot,
+ bytes32 beaconStateRoot,
+ bytes calldata stateRootProof
+)
+ internal
```
Verifies the proof of a beacon state root against the oracle provided block root. Every [beacon block](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblock) in the beacon state contains the state root corresponding with that block. Thus to prove anything against a state root, we must first prove the state root against the corresponding oracle block root.
@@ -59,14 +47,18 @@ Verifies the proof of a beacon state root against the oracle provided block root
```solidity
function verifyWithdrawal(
- bytes32 beaconStateRoot,
- bytes32[] calldata withdrawalFields,
- WithdrawalProof calldata withdrawalProof
- ) internal
+ bytes32 beaconStateRoot,
+ bytes32[] calldata withdrawalFields,
+ WithdrawalProof calldata withdrawalProof,
+ uint64 denebForkTimestamp
+)
+ internal
```
Verifies a withdrawal, either [full or partial](https://eth2book.info/capella/part2/deposits-withdrawals/withdrawal-processing/#partial-and-full-withdrawals), of a validator. There are a maximum of 16 withdrawals per block in the consensus layer. This proof proves the inclusion of a given [withdrawal](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#withdrawal) in the block for a given slot.
-One important note is that we use [`historical_summaries`](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historical-summaries-updates) to prove the blocks that contain withdrawals. Each new [historical summary](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historicalsummary) is added every 8192 slots, i.e., if `slot % 8192 = 0`, then `slot.state_roots` and `slot.block_roots` are merkleized and are used to create the latest `historical_summaries` entry.
+One important note is that we use [`historical_summaries`](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historical-summaries-updates) to prove the blocks that contain withdrawals. Each new [historical summary](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historicalsummary) is added every 8192 slots, i.e., if `slot % 8192 = 0`, then `slot.state_roots` and `slot.block_roots` are merkleized and are used to create the latest `historical_summaries` entry.
+
+This method also uses `denebForkTimestamp` to determine the height of the execution payload header field tree.
![Verify Withdrawal Proof Structure](../../images/Withdrawal_Proof.png)
diff --git a/docs/images/Balance_Proof.png b/docs/images/Balance_Proof.png
deleted file mode 100644
index 970e399bb..000000000
Binary files a/docs/images/Balance_Proof.png and /dev/null differ
diff --git a/docs/images/Withdrawal_Proof.png b/docs/images/Withdrawal_Proof.png
index d1bc3622b..16c94980d 100644
Binary files a/docs/images/Withdrawal_Proof.png and b/docs/images/Withdrawal_Proof.png differ
diff --git a/script/middleware/DeployOpenEigenLayer.s.sol b/script/middleware/DeployOpenEigenLayer.s.sol
index 4f62690b2..46381f8c5 100644
--- a/script/middleware/DeployOpenEigenLayer.s.sol
+++ b/script/middleware/DeployOpenEigenLayer.s.sol
@@ -12,6 +12,7 @@ import "../../src/contracts/interfaces/IBeaconChainOracle.sol";
import "../../src/contracts/core/StrategyManager.sol";
import "../../src/contracts/core/Slasher.sol";
import "../../src/contracts/core/DelegationManager.sol";
+import "../../src/contracts/core/AVSDirectory.sol";
import "../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
@@ -56,6 +57,8 @@ contract DeployOpenEigenLayer is Script, Test {
EigenPodManager public eigenPodManagerImplementation;
DelayedWithdrawalRouter public delayedWithdrawalRouter;
DelayedWithdrawalRouter public delayedWithdrawalRouterImplementation;
+ AVSDirectory public avsDirectory;
+ AVSDirectory public avsDirectoryImplementation;
UpgradeableBeacon public eigenPodBeacon;
EigenPod public eigenPodImplementation;
StrategyBase public baseStrategyImplementation;
@@ -97,6 +100,9 @@ contract DeployOpenEigenLayer is Script, Test {
delegation = DelegationManager(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
+ avsDirectory = AVSDirectory(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
strategyManager = StrategyManager(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
@@ -124,6 +130,7 @@ contract DeployOpenEigenLayer is Script, Test {
// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ avsDirectoryImplementation = new AVSDirectory(delegation);
strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
slasherImplementation = new Slasher(strategyManager, delegation);
eigenPodManagerImplementation = new EigenPodManager(
@@ -136,10 +143,17 @@ contract DeployOpenEigenLayer is Script, Test {
delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
+ IStrategy[] memory _strategies;
+ uint256[] memory _withdrawalDelayBlocks;
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delegation))),
address(delegationImplementation),
- abi.encodeWithSelector(DelegationManager.initialize.selector, executorMultisig, eigenLayerPauserReg, 0, 0 /* withdrawalDelayBlocks */)
+ abi.encodeWithSelector(DelegationManager.initialize.selector, executorMultisig, eigenLayerPauserReg, 0, 0, _strategies, _withdrawalDelayBlocks)
+ );
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ address(avsDirectoryImplementation),
+ abi.encodeWithSelector(AVSDirectory.initialize.selector, executorMultisig, eigenLayerPauserReg, 0)
);
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(strategyManager))),
diff --git a/script/milestone/M2Deploy.s.sol b/script/milestone/M2Deploy.s.sol
index cf2ca485e..fb9db6c72 100644
--- a/script/milestone/M2Deploy.s.sol
+++ b/script/milestone/M2Deploy.s.sol
@@ -325,8 +325,17 @@ contract M2Deploy is Script, Test {
0
);
+ IStrategy[] memory strategyArray = new IStrategy[](0);
+ uint256[] memory withdrawalDelayBlocksArray = new uint256[](0);
cheats.expectRevert(bytes("Initializable: contract is already initialized"));
- DelegationManager(address(delegation)).initialize(address(this), PauserRegistry(address(this)), 0, 0);
+ DelegationManager(address(delegation)).initialize(
+ address(this),
+ PauserRegistry(address(this)),
+ 0, // initialPausedStatus
+ 0, // minWithdrawalDelayBLocks
+ strategyArray,
+ withdrawalDelayBlocksArray
+ );
cheats.expectRevert(bytes("Initializable: contract is already initialized"));
EigenPodManager(address(eigenPodManager)).initialize(
diff --git a/script/output/GV2_deployment_2024_6_2.json b/script/output/GV2_deployment_2024_6_2.json
new file mode 100644
index 000000000..e68db2428
--- /dev/null
+++ b/script/output/GV2_deployment_2024_6_2.json
@@ -0,0 +1,30 @@
+{
+ "addresses": {
+ "baseStrategyImplementation": "0x81E94e16949AC397d508B5C2557a272faD2F8ebA",
+ "delayedWithdrawalRouter": "0x89581561f1F98584F88b0d57c2180fb89225388f",
+ "delayedWithdrawalRouterImplementation": "0xE576731194EC3d8Ba92E7c2B578ea74238772878",
+ "delegation": "0x1b7b8F6b258f95Cf9596EabB9aa18B62940Eb0a8",
+ "delegationImplementation": "0x56652542926444Ebce46Fd97aFd80824ed51e58C",
+ "eigenLayerPauserReg": "0x7cB9c5D6b9702f2f680e4d35cb1fC945D08208F6",
+ "eigenLayerProxyAdmin": "0x28ceac2ff82B2E00166e46636e2A4818C29902e2",
+ "eigenPodBeacon": "0x3093F3B560352F896F0e9567019902C9Aff8C9a5",
+ "eigenPodImplementation": "0x16a0d8aD2A2b12f3f47d0e8F5929F9840e29a426",
+ "eigenPodManager": "0xa286b84C96aF280a49Fe1F40B9627C2A2827df41",
+ "eigenPodManagerImplementation": "0xDA9B60D3dC7adD40C0e35c628561Ff71C13a189f",
+ "emptyContract": "0xa04bf5170D86833294b5c21c712C69C0Fb5735A4",
+ "slasher": "0xD11d60b669Ecf7bE10329726043B3ac07B380C22",
+ "slasherImplementation": "0x89C5e6e98f79be658e830Ec66b61ED3EE910D262",
+ "strategyManager": "0x779d1b5315df083e3F9E94cB495983500bA8E907",
+ "strategyManagerImplementation": "0x506C21f43e81D9d231d8A13831b42A2a2B5540E4",
+ "avsDirectory": "0x0AC9694c271eFbA6059e9783769e515E8731f935",
+ "avsDirectoryImplementation": "0x871cD8f6CFec8b2EB1ac64d58F6D9e1D36a88cb3"
+ },
+ "chainInfo": {
+ "chainId": 5,
+ "deploymentBlock": 10497389
+ },
+ "parameters": {
+ "executorMultisig": "0x3d9C2c2B40d890ad53E27947402e977155CD2808",
+ "operationsMultisig": "0x040353E9d057689b77DF275c07FFe1A46b98a4a6"
+ }
+}
\ No newline at end of file
diff --git a/script/output/GV2_preprod_deployment_2024_30_1.json b/script/output/GV2_preprod_deployment_2024_30_1.json
new file mode 100644
index 000000000..f705e6f9f
--- /dev/null
+++ b/script/output/GV2_preprod_deployment_2024_30_1.json
@@ -0,0 +1,30 @@
+{
+ "addresses": {
+ "baseStrategyImplementation": "0xA548BF0106108A0c14779F3f1d8981517b8fA9D0",
+ "delayedWithdrawalRouter": "0x9572e46797B7A07257314e587061dC46c4dfCE0E",
+ "delayedWithdrawalRouterImplementation": "0x44a40C60857b4B420Ad3D8b9646FefEBF2D0dB86",
+ "delegation": "0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332",
+ "delegationImplementation": "0x934eB3E2b6D5C2E1601B29B7180026D71438F20D",
+ "eigenLayerPauserReg": "0x94A2679B6A87ADb4e0CabA8E3E40f463C6062DeC",
+ "eigenLayerProxyAdmin": "0x555573Ff2B3b2731e69eeBAfb40a4EEA7fBaC54A",
+ "eigenPodBeacon": "0x38cBD4e08eA1840B91dA42fE02B55Abc89083bFB",
+ "eigenPodImplementation": "0x83cbB48391F428878Bc5DD97C9792a8dbCAa0729",
+ "eigenPodManager": "0x33e42d539abFe9b387B27b0e467374Bbb76cf925",
+ "eigenPodManagerImplementation": "0xEEdCC9dB001fB8429721FE21426F51f0Cdd329EC",
+ "emptyContract": "0xb23633b2240D78502fA308B817C892b2d5778469",
+ "slasher": "0xF751E8C37ACd3AD5a35D5db03E57dB6F9AD0bDd0",
+ "slasherImplementation": "0x05c235183e8b9dFb7113Cf92bbDc3f5085324158",
+ "strategyManager": "0xD309ADd2B269d522112DcEe0dCf0b0f04a09C29e",
+ "strategyManagerImplementation": "0xb9B69504f1a727E783F4B4248A115D56F4080DF8",
+ "avsDirectory": "0x47eFB8e38656a805BC6B3b13FA331d34dcDeB374",
+ "avsDirectoryImplementation": "0x728111B10227F44E5e389e5650725948d1DCcE7A"
+ },
+ "chainInfo": {
+ "chainId": 5,
+ "deploymentBlock": 10469472
+ },
+ "parameters": {
+ "executorMultisig": "0x27977e6E4426A525d055A587d2a0537b4cb376eA",
+ "operationsMultisig": "0x27977e6E4426A525d055A587d2a0537b4cb376eA"
+ }
+}
\ No newline at end of file
diff --git a/script/output/M2_preprod_deployment_from_scratch.json b/script/output/M2_preprod_deployment_from_scratch.json
new file mode 100644
index 000000000..0ac97a9e9
--- /dev/null
+++ b/script/output/M2_preprod_deployment_from_scratch.json
@@ -0,0 +1,33 @@
+{
+ "addresses": {
+ "baseStrategyImplementation": "0xA548BF0106108A0c14779F3f1d8981517b8fA9D0",
+ "blsPublicKeyCompendium": "0x663F1f6A8E4417b9dB3117821068DAD862395aF0",
+ "delayedWithdrawalRouter": "0x9572e46797B7A07257314e587061dC46c4dfCE0E",
+ "delayedWithdrawalRouterImplementation": "0xaDd6b52E063bE5CdeF6450F28D9CA038bDAB9A49",
+ "delegation": "0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332",
+ "delegationImplementation": "0x679cf51e303827c99e924bea05331101bF90B126",
+ "eigenLayerPauserReg": "0x94A2679B6A87ADb4e0CabA8E3E40f463C6062DeC",
+ "eigenLayerProxyAdmin": "0x555573Ff2B3b2731e69eeBAfb40a4EEA7fBaC54A",
+ "eigenPodBeacon": "0x38cBD4e08eA1840B91dA42fE02B55Abc89083bFB",
+ "eigenPodImplementation": "0x9CeE917f0f5d4123585A4B12906a8A65cFac1ac8",
+ "eigenPodManager": "0x33e42d539abFe9b387B27b0e467374Bbb76cf925",
+ "eigenPodManagerImplementation": "0x6A4855ab9a3924c8169f20a189272FFF3cd00b68",
+ "emptyContract": "0xb23633b2240D78502fA308B817C892b2d5778469",
+ "slasher": "0xF751E8C37ACd3AD5a35D5db03E57dB6F9AD0bDd0",
+ "slasherImplementation": "0xa02171440AfD8d5f09BaAB74Cd48b1401C47F2f9",
+ "strategies": {
+ "Liquid staked Ether 2.0": "0xed6DE3f2916d20Cb427fe7255194a05061319FFB",
+ "Rocket Pool ETH": "0xd421b2a340497545dA68AE53089d99b9Fe0493cD"
+ },
+ "strategyManager": "0xD309ADd2B269d522112DcEe0dCf0b0f04a09C29e",
+ "strategyManagerImplementation": "0xC10133A329A210f8DEbf597C8eF5907c95D673e9"
+ },
+ "chainInfo": {
+ "chainId": 5,
+ "deploymentBlock": 9729808
+ },
+ "parameters": {
+ "executorMultisig": "0x27977e6E4426A525d055A587d2a0537b4cb376eA",
+ "operationsMultisig": "0x27977e6E4426A525d055A587d2a0537b4cb376eA"
+ }
+ }
\ No newline at end of file
diff --git a/script/testing/M2_Deploy_From_Scratch.s.sol b/script/testing/M2_Deploy_From_Scratch.s.sol
index dff1919be..0d41648d1 100644
--- a/script/testing/M2_Deploy_From_Scratch.s.sol
+++ b/script/testing/M2_Deploy_From_Scratch.s.sol
@@ -378,7 +378,7 @@ contract Deployer_M2 is Script, Test {
function _verifyContractsPointAtOneAnother(
DelegationManager delegationContract,
StrategyManager strategyManagerContract,
- Slasher slasherContract,
+ Slasher /*slasherContract*/,
EigenPodManager eigenPodManagerContract,
DelayedWithdrawalRouter delayedWithdrawalRouterContract
) internal view {
diff --git a/script/upgrade/GoerliUpgrade2.s.sol b/script/upgrade/GoerliUpgrade2.s.sol
new file mode 100644
index 000000000..1e83b4522
--- /dev/null
+++ b/script/upgrade/GoerliUpgrade2.s.sol
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "../../src/contracts/interfaces/IETHPOSDeposit.sol";
+
+import "../../src/contracts/core/StrategyManager.sol";
+import "../../src/contracts/core/Slasher.sol";
+import "../../src/contracts/core/DelegationManager.sol";
+import "../../src/contracts/core/AVSDirectory.sol";
+
+import "../../src/contracts/pods/EigenPod.sol";
+import "../../src/contracts/pods/EigenPodManager.sol";
+import "../../src/contracts/pods/DelayedWithdrawalRouter.sol";
+
+import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import "../../src/test/mocks/EmptyContract.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/upgrade/GoerliUpgrade2.s.sol:GoerliUpgrade2 --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
+
+// NOTE: ONLY WORKS ON GOERLI
+// CommitHash: 6de01c6c16d6df44af15f0b06809dc160eac0ebf
+contract GoerliUpgrade2 is Script, Test {
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ string public deploymentOutputPath = string(bytes("script/output/M1_deployment_goerli_2023_3_23.json"));
+
+ IDelayedWithdrawalRouter delayedWithdrawalRouter;
+ IDelegationManager delegation;
+ IEigenPodManager eigenPodManager;
+ IStrategyManager strategyManager;
+ ISlasher slasher;
+ IBeacon eigenPodBeacon;
+ EmptyContract emptyContract;
+ ProxyAdmin eigenLayerProxyAdmin;
+
+ function run() external {
+ // read and log the chainID
+ uint256 chainId = block.chainid;
+ emit log_named_uint("You are deploying on ChainID", chainId);
+
+ string memory config_data = vm.readFile(deploymentOutputPath);
+
+ delayedWithdrawalRouter = IDelayedWithdrawalRouter(stdJson.readAddress(config_data, ".addresses.delayedWithdrawalRouter"));
+ delegation = IDelegationManager(stdJson.readAddress(config_data, ".addresses.delegation"));
+ eigenPodManager = IEigenPodManager(stdJson.readAddress(config_data, ".addresses.eigenPodManager"));
+ strategyManager = IStrategyManager(stdJson.readAddress(config_data, ".addresses.strategyManager"));
+ slasher = ISlasher(stdJson.readAddress(config_data, ".addresses.slasher"));
+ eigenPodBeacon = IBeacon(stdJson.readAddress(config_data, ".addresses.eigenPodBeacon"));
+ emptyContract = EmptyContract(stdJson.readAddress(config_data, ".addresses.emptyContract"));
+ eigenLayerProxyAdmin = ProxyAdmin(stdJson.readAddress(config_data, ".addresses.eigenLayerProxyAdmin"));
+
+ vm.startBroadcast();
+
+ address delegationImplementation = address(
+ new DelegationManager(
+ strategyManager,
+ slasher,
+ eigenPodManager
+ )
+ );
+
+ address slasherImplementation = address(
+ new Slasher(
+ strategyManager,
+ delegation
+ )
+ );
+
+ address strategyManagerImplementation = address(
+ new StrategyManager(
+ delegation,
+ eigenPodManager,
+ slasher
+ )
+ );
+
+ address delayedWithdrawalRouterImplementation = address(
+ new DelayedWithdrawalRouter(
+ eigenPodManager
+ )
+ );
+
+ address eigenPodImplementation = address(
+ new EigenPod(
+ IETHPOSDeposit(0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b),
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ 32e9,
+ 1616508000
+ )
+ );
+
+ address eigenPodManagerImplementation = address(
+ new EigenPodManager(
+ IETHPOSDeposit(0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b),
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegation
+ )
+ );
+
+ vm.stopBroadcast();
+
+ emit log_named_address("DelegationImplementation", delegationImplementation);
+ emit log_named_address("SlasherImplementation", slasherImplementation);
+ emit log_named_address("StrategyManagerImplementation", strategyManagerImplementation);
+ emit log_named_address("DelayedWithdrawalRouterImplementation", delayedWithdrawalRouterImplementation);
+ emit log_named_address("EigenPodImplementation", eigenPodImplementation);
+ emit log_named_address("EigenPodManagerImplementation", eigenPodManagerImplementation);
+
+ /*
+ == Logs ==
+ You are deploying on ChainID: 5
+ DelegationImplementation: 0x56652542926444Ebce46Fd97aFd80824ed51e58C
+ SlasherImplementation: 0x89C5e6e98f79be658e830Ec66b61ED3EE910D262
+ StrategyManagerImplementation: 0x506C21f43e81D9d231d8A13831b42A2a2B5540E4
+ DelayedWithdrawalRouterImplementation: 0xE576731194EC3d8Ba92E7c2B578ea74238772878
+ EigenPodImplementation: 0x16a0d8aD2A2b12f3f47d0e8F5929F9840e29a426
+ EigenPodManagerImplementation: 0xDA9B60D3dC7adD40C0e35c628561Ff71C13a189f
+ */
+ }
+}
\ No newline at end of file
diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol
index f11e8b5cd..8d2544b26 100644
--- a/script/utils/ExistingDeploymentParser.sol
+++ b/script/utils/ExistingDeploymentParser.sol
@@ -8,6 +8,7 @@ import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "../../src/contracts/core/StrategyManager.sol";
import "../../src/contracts/core/Slasher.sol";
import "../../src/contracts/core/DelegationManager.sol";
+import "../../src/contracts/core/AVSDirectory.sol";
import "../../src/contracts/strategies/StrategyBase.sol";
@@ -29,6 +30,8 @@ contract ExistingDeploymentParser is Script, Test {
PauserRegistry public eigenLayerPauserReg;
Slasher public slasher;
Slasher public slasherImplementation;
+ AVSDirectory public avsDirectory;
+ AVSDirectory public avsDirectoryImplementation;
DelegationManager public delegation;
DelegationManager public delegationImplementation;
StrategyManager public strategyManager;
@@ -71,6 +74,8 @@ contract ExistingDeploymentParser is Script, Test {
slasherImplementation = Slasher(stdJson.readAddress(existingDeploymentData, ".addresses.slasherImplementation"));
delegation = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegation"));
delegationImplementation = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegationImplementation"));
+ avsDirectory = AVSDirectory(stdJson.readAddress(existingDeploymentData, ".addresses.avsDirectory"));
+ avsDirectoryImplementation = AVSDirectory(stdJson.readAddress(existingDeploymentData, ".addresses.avsDirectoryImplementation"));
strategyManager = StrategyManager(stdJson.readAddress(existingDeploymentData, ".addresses.strategyManager"));
strategyManagerImplementation = StrategyManager(stdJson.readAddress(existingDeploymentData, ".addresses.strategyManagerImplementation"));
eigenPodManager = EigenPodManager(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodManager"));
diff --git a/src/contracts/core/AVSDirectory.sol b/src/contracts/core/AVSDirectory.sol
new file mode 100644
index 000000000..0391aac6b
--- /dev/null
+++ b/src/contracts/core/AVSDirectory.sol
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
+import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
+import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
+import "../permissions/Pausable.sol";
+import "../libraries/EIP1271SignatureUtils.sol";
+import "./AVSDirectoryStorage.sol";
+
+contract AVSDirectory is
+ Initializable,
+ OwnableUpgradeable,
+ Pausable,
+ AVSDirectoryStorage,
+ ReentrancyGuardUpgradeable
+{
+ // @dev Index for flag that pauses operator register/deregister to avs when set.
+ uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0;
+
+ // @dev Chain ID at the time of contract deployment
+ uint256 internal immutable ORIGINAL_CHAIN_ID;
+
+ /*******************************************************************************
+ INITIALIZING FUNCTIONS
+ *******************************************************************************/
+
+ /**
+ * @dev Initializes the immutable addresses of the strategy mananger, delegationManager, slasher,
+ * and eigenpodManager contracts
+ */
+ constructor(IDelegationManager _delegation) AVSDirectoryStorage(_delegation) {
+ _disableInitializers();
+ ORIGINAL_CHAIN_ID = block.chainid;
+ }
+
+ /**
+ * @dev Initializes the addresses of the initial owner, pauser registry, and paused status.
+ * minWithdrawalDelayBlocks is set only once here
+ */
+ function initialize(
+ address initialOwner,
+ IPauserRegistry _pauserRegistry,
+ uint256 initialPausedStatus
+ ) external initializer {
+ _initializePauser(_pauserRegistry, initialPausedStatus);
+ _DOMAIN_SEPARATOR = _calculateDomainSeparator();
+ _transferOwnership(initialOwner);
+ }
+
+ /*******************************************************************************
+ EXTERNAL FUNCTIONS
+ *******************************************************************************/
+
+
+ /**
+ * @notice Called by the AVS's service manager contract to register an operator with the avs.
+ * @param operator The address of the operator to register.
+ * @param operatorSignature The signature, salt, and expiry of the operator's signature.
+ */
+ function registerOperatorToAVS(
+ address operator,
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
+ ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
+
+ require(
+ operatorSignature.expiry >= block.timestamp,
+ "AVSDirectory.registerOperatorToAVS: operator signature expired"
+ );
+ require(
+ avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
+ "AVSDirectory.registerOperatorToAVS: operator already registered"
+ );
+ require(
+ !operatorSaltIsSpent[operator][operatorSignature.salt],
+ "AVSDirectory.registerOperatorToAVS: salt already spent"
+ );
+ require(
+ delegation.isOperator(operator),
+ "AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
+
+ // Calculate the digest hash
+ bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
+ operator: operator,
+ avs: msg.sender,
+ salt: operatorSignature.salt,
+ expiry: operatorSignature.expiry
+ });
+
+ // Check that the signature is valid
+ EIP1271SignatureUtils.checkSignature_EIP1271(
+ operator,
+ operatorRegistrationDigestHash,
+ operatorSignature.signature
+ );
+
+ // Set the operator as registered
+ avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
+
+ // Mark the salt as spent
+ operatorSaltIsSpent[operator][operatorSignature.salt] = true;
+
+ emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
+ }
+
+ /**
+ * @notice Called by an avs to deregister an operator with the avs.
+ * @param operator The address of the operator to deregister.
+ */
+ function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
+ require(
+ avsOperatorStatus[msg.sender][operator] == OperatorAVSRegistrationStatus.REGISTERED,
+ "AVSDirectory.deregisterOperatorFromAVS: operator not registered"
+ );
+
+ // Set the operator as deregistered
+ avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED;
+
+ emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED);
+ }
+
+ /**
+ * @notice Called by an avs to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
+ * @param metadataURI The URI for metadata associated with an avs
+ */
+ function updateAVSMetadataURI(string calldata metadataURI) external {
+ emit AVSMetadataURIUpdated(msg.sender, metadataURI);
+ }
+
+ /**
+ * @notice Called by an operator to cancel a salt that has been used to register with an AVS.
+ * @param salt A unique and single use value associated with the approver signature.
+ */
+ function cancelSalt(bytes32 salt) external {
+ require(!operatorSaltIsSpent[msg.sender][salt], "AVSDirectory.cancelSalt: cannot cancel spent salt");
+ operatorSaltIsSpent[msg.sender][salt] = true;
+ }
+
+ /*******************************************************************************
+ VIEW FUNCTIONS
+ *******************************************************************************/
+
+ /**
+ * @notice Calculates the digest hash to be signed by an operator to register with an AVS
+ * @param operator The account registering as an operator
+ * @param avs The address of the service manager contract for the AVS that the operator is registering to
+ * @param salt A unique and single use value associated with the approver signature.
+ * @param expiry Time after which the approver's signature becomes invalid
+ */
+ function calculateOperatorAVSRegistrationDigestHash(
+ address operator,
+ address avs,
+ bytes32 salt,
+ uint256 expiry
+ ) public view returns (bytes32) {
+ // calculate the struct hash
+ bytes32 structHash = keccak256(
+ abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
+ );
+ // calculate the digest hash
+ bytes32 digestHash = keccak256(
+ abi.encodePacked("\x19\x01", domainSeparator(), structHash)
+ );
+ return digestHash;
+ }
+
+ /**
+ * @notice Getter function for the current EIP-712 domain separator for this contract.
+ * @dev The domain separator will change in the event of a fork that changes the ChainID.
+ */
+ function domainSeparator() public view returns (bytes32) {
+ if (block.chainid == ORIGINAL_CHAIN_ID) {
+ return _DOMAIN_SEPARATOR;
+ } else {
+ return _calculateDomainSeparator();
+ }
+ }
+
+ // @notice Internal function for calculating the current domain separator of this contract
+ function _calculateDomainSeparator() internal view returns (bytes32) {
+ return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
+ }
+}
\ No newline at end of file
diff --git a/src/contracts/core/AVSDirectoryStorage.sol b/src/contracts/core/AVSDirectoryStorage.sol
new file mode 100644
index 000000000..ec536f76b
--- /dev/null
+++ b/src/contracts/core/AVSDirectoryStorage.sol
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "../interfaces/IAVSDirectory.sol";
+import "../interfaces/IStrategyManager.sol";
+import "../interfaces/IDelegationManager.sol";
+import "../interfaces/ISlasher.sol";
+import "../interfaces/IEigenPodManager.sol";
+
+abstract contract AVSDirectoryStorage is IAVSDirectory {
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH =
+ keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
+
+ /// @notice The EIP-712 typehash for the `Registration` struct used by the contract
+ bytes32 public constant OPERATOR_AVS_REGISTRATION_TYPEHASH =
+ keccak256("OperatorAVSRegistration(address operator,address avs,bytes32 salt,uint256 expiry)");
+
+ /// @notice The DelegationManager contract for EigenLayer
+ IDelegationManager public immutable delegation;
+
+ /**
+ * @notice Original EIP-712 Domain separator for this contract.
+ * @dev The domain separator may change in the event of a fork that modifies the ChainID.
+ * Use the getter function `domainSeparator` to get the current domain separator for this contract.
+ */
+ bytes32 internal _DOMAIN_SEPARATOR;
+
+ /// @notice Mapping: AVS => operator => enum of operator status to the AVS
+ mapping(address => mapping(address => OperatorAVSRegistrationStatus)) public avsOperatorStatus;
+
+ /// @notice Mapping: operator => 32-byte salt => whether or not the salt has already been used by the operator.
+ /// @dev Salt is used in the `registerOperatorToAVS` function.
+ mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpent;
+
+ constructor(IDelegationManager _delegation) {
+ delegation = _delegation;
+ }
+
+ /**
+ * @dev This empty reserved space is put in place to allow future versions to add new
+ * variables without shifting down storage in the inheritance chain.
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
+ */
+ uint256[47] private __gap;
+}
diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol
index 6cb9a9f76..6a54bb3d2 100644
--- a/src/contracts/core/DelegationManager.sol
+++ b/src/contracts/core/DelegationManager.sol
@@ -4,10 +4,9 @@ pragma solidity =0.8.12;
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
-import "../interfaces/ISlasher.sol";
-import "./DelegationManagerStorage.sol";
import "../permissions/Pausable.sol";
import "../libraries/EIP1271SignatureUtils.sol";
+import "./DelegationManagerStorage.sol";
/**
* @title DelegationManager
@@ -29,9 +28,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
// @dev Index for flag that pauses completing existing withdrawals when set.
uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
- // @dev Index for flag that pauses operator register/deregister to avs when set.
- uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 3;
-
// @dev Chain ID at the time of contract deployment
uint256 internal immutable ORIGINAL_CHAIN_ID;
@@ -68,18 +64,21 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
/**
* @dev Initializes the addresses of the initial owner, pauser registry, and paused status.
- * withdrawalDelayBlocks is set only once here
+ * minWithdrawalDelayBlocks is set only once here
*/
function initialize(
address initialOwner,
IPauserRegistry _pauserRegistry,
uint256 initialPausedStatus,
- uint256 _withdrawalDelayBlocks
+ uint256 _minWithdrawalDelayBlocks,
+ IStrategy[] calldata _strategies,
+ uint256[] calldata _withdrawalDelayBlocks
) external initializer {
_initializePauser(_pauserRegistry, initialPausedStatus);
_DOMAIN_SEPARATOR = _calculateDomainSeparator();
_transferOwnership(initialOwner);
- _initializeWithdrawalDelayBlocks(_withdrawalDelayBlocks);
+ _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
+ _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
}
/*******************************************************************************
@@ -133,14 +132,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
}
- /**
- * @notice Called by an avs to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
- * @param metadataURI The URI for metadata associated with an avs
- */
- function updateAVSMetadataURI(string calldata metadataURI) external {
- emit AVSMetadataURIUpdated(msg.sender, metadataURI);
- }
-
/**
* @notice Caller delegates their stake to an operator.
* @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on EigenLayer.
@@ -266,7 +257,7 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
return withdrawalRoots;
}
- /**
+ /**
* Allows a staker to withdraw some shares. Withdrawn shares/strategies are immediately removed
* from the staker. If the staker is delegated, withdrawn shares/strategies are also removed from
* their operator.
@@ -277,12 +268,11 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
QueuedWithdrawalParams[] calldata queuedWithdrawalParams
) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory) {
bytes32[] memory withdrawalRoots = new bytes32[](queuedWithdrawalParams.length);
+ address operator = delegatedTo[msg.sender];
for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) {
require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch");
- require(queuedWithdrawalParams[i].withdrawer != address(0), "DelegationManager.queueWithdrawal: must provide valid withdrawal address");
-
- address operator = delegatedTo[msg.sender];
+ require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker");
// Remove shares from staker's strategies and place strategies/shares in queue.
// If the staker is delegated to an operator, the operator's delegated shares are also reduced
@@ -434,69 +424,25 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
}
/**
- * @notice Called by an avs to register an operator with the avs.
- * @param operator The address of the operator to register.
- * @param operatorSignature The signature, salt, and expiry of the operator's signature.
+ * @notice Owner-only function for modifying the value of the `minWithdrawalDelayBlocks` variable.
+ * @param newMinWithdrawalDelayBlocks new value of `minWithdrawalDelayBlocks`.
*/
- function registerOperatorToAVS(
- address operator,
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
- ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
-
- require(
- operatorSignature.expiry >= block.timestamp,
- "DelegationManager.registerOperatorToAVS: operator signature expired"
- );
- require(
- avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
- "DelegationManager.registerOperatorToAVS: operator already registered"
- );
- require(
- !operatorSaltIsSpent[operator][operatorSignature.salt],
- "DelegationManager.registerOperatorToAVS: salt already spent"
- );
- require(
- isOperator(operator),
- "DelegationManager.registerOperatorToAVS: operator not registered to EigenLayer yet");
-
- // Calculate the digest hash
- bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
- operator: operator,
- avs: msg.sender,
- salt: operatorSignature.salt,
- expiry: operatorSignature.expiry
- });
-
- // Check that the signature is valid
- EIP1271SignatureUtils.checkSignature_EIP1271(
- operator,
- operatorRegistrationDigestHash,
- operatorSignature.signature
- );
-
- // Set the operator as registered
- avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
-
- // Mark the salt as spent
- operatorSaltIsSpent[operator][operatorSignature.salt] = true;
-
- emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
+ function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner {
+ _setMinWithdrawalDelayBlocks(newMinWithdrawalDelayBlocks);
}
/**
- * @notice Called by an avs to deregister an operator with the avs.
- * @param operator The address of the operator to deregister.
+ * @notice Called by owner to set the minimum withdrawal delay blocks for each passed in strategy
+ * Note that the min number of blocks to complete a withdrawal of a strategy is
+ * MAX(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy])
+ * @param strategies The strategies to set the minimum withdrawal delay blocks for
+ * @param withdrawalDelayBlocks The minimum withdrawal delay blocks to set for each strategy
*/
- function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
- require(
- avsOperatorStatus[msg.sender][operator] == OperatorAVSRegistrationStatus.REGISTERED,
- "DelegationManager.deregisterOperatorFromAVS: operator not registered"
- );
-
- // Set the operator as deregistered
- avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED;
-
- emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED);
+ function setStrategyWithdrawalDelayBlocks(
+ IStrategy[] calldata strategies,
+ uint256[] calldata withdrawalDelayBlocks
+ ) external onlyOwner {
+ _setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
}
/*******************************************************************************
@@ -617,23 +563,23 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
require(
pendingWithdrawals[withdrawalRoot],
- "DelegationManager.completeQueuedAction: action is not in queue"
+ "DelegationManager._completeQueuedWithdrawal: action is not in queue"
);
require(
- withdrawal.startBlock + withdrawalDelayBlocks <= block.number,
- "DelegationManager.completeQueuedAction: withdrawalDelayBlocks period has not yet passed"
+ withdrawal.startBlock + minWithdrawalDelayBlocks <= block.number,
+ "DelegationManager._completeQueuedWithdrawal: minWithdrawalDelayBlocks period has not yet passed"
);
require(
msg.sender == withdrawal.withdrawer,
- "DelegationManager.completeQueuedAction: only withdrawer can complete action"
+ "DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action"
);
if (receiveAsTokens) {
require(
tokens.length == withdrawal.strategies.length,
- "DelegationManager.completeQueuedAction: input length mismatch"
+ "DelegationManager._completeQueuedWithdrawal: input length mismatch"
);
}
@@ -644,6 +590,11 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
// by re-awarding shares in each strategy.
if (receiveAsTokens) {
for (uint256 i = 0; i < withdrawal.strategies.length; ) {
+ require(
+ withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number,
+ "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
+ );
+
_withdrawSharesAsTokens({
staker: withdrawal.staker,
withdrawer: msg.sender,
@@ -657,6 +608,11 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
} else {
address currentOperator = delegatedTo[msg.sender];
for (uint256 i = 0; i < withdrawal.strategies.length; ) {
+ require(
+ withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number,
+ "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
+ );
+
/** When awarding podOwnerShares in EigenPodManager, we need to be sure to only give them back to the original podOwner.
* Other strategy shares can + will be awarded to the withdrawer.
*/
@@ -682,7 +638,7 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
});
}
} else {
- strategyManager.addShares(msg.sender, withdrawal.strategies[i], withdrawal.shares[i]);
+ strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]);
// Similar to `isDelegated` logic
if (currentOperator != address(0)) {
_increaseOperatorShares({
@@ -717,6 +673,7 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
/**
* @notice Removes `shares` in `strategies` from `staker` who is currently delegated to `operator` and queues a withdrawal to the `withdrawer`.
* @dev If the `operator` is indeed an operator, then the operator's delegated shares in the `strategies` are also decreased appropriately.
+ * @dev If `withdrawer` is not the same address as `staker`, then thirdPartyTransfersForbidden[strategy] must be set to false in the StrategyManager.
*/
function _removeSharesAndQueueWithdrawal(
address staker,
@@ -751,6 +708,10 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
*/
eigenPodManager.removeShares(staker, shares[i]);
} else {
+ require(
+ staker == withdrawer || !strategyManager.thirdPartyTransfersForbidden(strategies[i]),
+ "DelegationManager._removeSharesAndQueueWithdrawal: withdrawer must be same address as staker if thirdPartyTransfersForbidden are set"
+ );
// this call will revert if `shares[i]` exceeds the Staker's current shares in `strategies[i]`
strategyManager.removeShares(staker, strategies[i], shares[i]);
}
@@ -797,13 +758,45 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
}
}
- function _initializeWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) internal {
+ function _setMinWithdrawalDelayBlocks(uint256 _minWithdrawalDelayBlocks) internal {
require(
- _withdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS,
- "DelegationManager._initializeWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
+ _minWithdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS,
+ "DelegationManager._setMinWithdrawalDelayBlocks: _minWithdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
);
- emit WithdrawalDelayBlocksSet(withdrawalDelayBlocks, _withdrawalDelayBlocks);
- withdrawalDelayBlocks = _withdrawalDelayBlocks;
+ emit MinWithdrawalDelayBlocksSet(minWithdrawalDelayBlocks, _minWithdrawalDelayBlocks);
+ minWithdrawalDelayBlocks = _minWithdrawalDelayBlocks;
+ }
+
+ /**
+ * @notice Sets the withdrawal delay blocks for each strategy in `_strategies` to `_withdrawalDelayBlocks`.
+ * gets called when initializing contract or by calling `setStrategyWithdrawalDelayBlocks`
+ */
+ function _setStrategyWithdrawalDelayBlocks(
+ IStrategy[] calldata _strategies,
+ uint256[] calldata _withdrawalDelayBlocks
+ ) internal {
+ require(
+ _strategies.length == _withdrawalDelayBlocks.length,
+ "DelegationManager._setStrategyWithdrawalDelayBlocks: input length mismatch"
+ );
+ uint256 numStrats = _strategies.length;
+ for (uint256 i = 0; i < numStrats; ++i) {
+ IStrategy strategy = _strategies[i];
+ uint256 prevStrategyWithdrawalDelayBlocks = strategyWithdrawalDelayBlocks[strategy];
+ uint256 newStrategyWithdrawalDelayBlocks = _withdrawalDelayBlocks[i];
+ require(
+ newStrategyWithdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS,
+ "DelegationManager._setStrategyWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
+ );
+
+ // set the new withdrawal delay blocks
+ strategyWithdrawalDelayBlocks[strategy] = newStrategyWithdrawalDelayBlocks;
+ emit StrategyWithdrawalDelayBlocksSet(
+ strategy,
+ prevStrategyWithdrawalDelayBlocks,
+ newStrategyWithdrawalDelayBlocks
+ );
+ }
}
/*******************************************************************************
@@ -867,6 +860,18 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
return _operatorDetails[operator].stakerOptOutWindowBlocks;
}
+ /// @notice Given array of strategies, returns array of shares for the operator
+ function getOperatorShares(
+ address operator,
+ IStrategy[] memory strategies
+ ) public view returns (uint256[] memory) {
+ uint256[] memory shares = new uint256[](strategies.length);
+ for (uint256 i = 0; i < strategies.length; ++i) {
+ shares[i] = operatorShares[operator][strategies[i]];
+ }
+ return shares;
+ }
+
/**
* @notice Returns the number of actively-delegatable shares a staker has across all strategies.
* @dev Returns two empty arrays in the case that the Staker has no actively-delegateable shares.
@@ -914,6 +919,22 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
return (strategies, shares);
}
+ /**
+ * @notice Given a list of strategies, return the minimum number of blocks that must pass to withdraw
+ * from all the inputted strategies. Return value is >= minWithdrawalDelayBlocks as this is the global min withdrawal delay.
+ * @param strategies The strategies to check withdrawal delays for
+ */
+ function getWithdrawalDelay(IStrategy[] calldata strategies) public view returns (uint256) {
+ uint256 withdrawalDelay = minWithdrawalDelayBlocks;
+ for (uint256 i = 0; i < strategies.length; ++i) {
+ uint256 currWithdrawalDelay = strategyWithdrawalDelayBlocks[strategies[i]];
+ if (currWithdrawalDelay > withdrawalDelay) {
+ withdrawalDelay = currWithdrawalDelay;
+ }
+ }
+ return withdrawalDelay;
+ }
+
/// @notice Returns the keccak256 hash of `withdrawal`.
function calculateWithdrawalRoot(Withdrawal memory withdrawal) public pure returns (bytes32) {
return keccak256(abi.encode(withdrawal));
@@ -982,30 +1003,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
return approverDigestHash;
}
- /**
- * @notice Calculates the digest hash to be signed by an operator to register with an AVS
- * @param operator The account registering as an operator
- * @param avs The AVS the operator is registering to
- * @param salt A unique and single use value associated with the approver signature.
- * @param expiry Time after which the approver's signature becomes invalid
- */
- function calculateOperatorAVSRegistrationDigestHash(
- address operator,
- address avs,
- bytes32 salt,
- uint256 expiry
- ) public view returns (bytes32) {
- // calculate the struct hash
- bytes32 structHash = keccak256(
- abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
- );
- // calculate the digest hash
- bytes32 digestHash = keccak256(
- abi.encodePacked("\x19\x01", domainSeparator(), structHash)
- );
- return digestHash;
- }
-
/**
* @dev Recalculates the domain separator when the chainid changes due to a fork.
*/
diff --git a/src/contracts/core/DelegationManagerStorage.sol b/src/contracts/core/DelegationManagerStorage.sol
index f7d37c75f..cea995b3d 100644
--- a/src/contracts/core/DelegationManagerStorage.sol
+++ b/src/contracts/core/DelegationManagerStorage.sol
@@ -23,11 +23,7 @@ abstract contract DelegationManagerStorage is IDelegationManager {
/// @notice The EIP-712 typehash for the `DelegationApproval` struct used by the contract
bytes32 public constant DELEGATION_APPROVAL_TYPEHASH =
- keccak256("DelegationApproval(address staker,address operator,bytes32 salt,uint256 expiry)");
-
- /// @notice The EIP-712 typehash for the `Registration` struct used by the contract
- bytes32 public constant OPERATOR_AVS_REGISTRATION_TYPEHASH =
- keccak256("OperatorAVSRegistration(address operator,address avs,uint256 expiry)");
+ keccak256("DelegationApproval(address delegationApprover,address staker,address operator,bytes32 salt,uint256 expiry)");
/**
* @notice Original EIP-712 Domain separator for this contract.
@@ -45,7 +41,8 @@ abstract contract DelegationManagerStorage is IDelegationManager {
/// @notice The EigenPodManager contract for EigenLayer
IEigenPodManager public immutable eigenPodManager;
- uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;
+ // the number of 12-second blocks in 30 days (60 * 60 * 24 * 30 / 12 = 216,000)
+ uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 216000;
/**
* @notice returns the total number of shares in `strategy` that are delegated to `operator`.
@@ -79,12 +76,13 @@ abstract contract DelegationManagerStorage is IDelegationManager {
mapping(address => mapping(bytes32 => bool)) public delegationApproverSaltIsSpent;
/**
- * @notice Minimum delay enforced by this contract for completing queued withdrawals. 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).
- * @dev Note that the withdrawal delay is not enforced on withdrawals of 'beaconChainETH', as the EigenPods have their own separate delay mechanic
- * and we want to avoid stacking multiple enforced delays onto a single withdrawal.
+ * @notice Global minimum withdrawal delay for all strategy withdrawals.
+ * In a prior Goerli release, we only had a global min withdrawal delay across all strategies.
+ * In addition, we now also configure withdrawal delays on a per-strategy basis.
+ * To withdraw from a strategy, max(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy]) number of blocks must have passed.
+ * See mapping strategyWithdrawalDelayBlocks below for per-strategy withdrawal delays.
*/
- uint256 public withdrawalDelayBlocks;
+ uint256 public minWithdrawalDelayBlocks;
/// @notice Mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending
mapping(bytes32 => bool) public pendingWithdrawals;
@@ -97,12 +95,11 @@ abstract contract DelegationManagerStorage is IDelegationManager {
/// See conversation here: https://github.com/Layr-Labs/eigenlayer-contracts/pull/365/files#r1417525270
address private __deprecated_stakeRegistry;
- /// @notice Mapping: AVS => operator => enum of operator status to the AVS
- mapping(address => mapping(address => OperatorAVSRegistrationStatus)) public avsOperatorStatus;
-
- /// @notice Mapping: operator => 32-byte salt => whether or not the salt has already been used by the operator.
- /// @dev Salt is used in the `registerOperatorToAVS` function.
- mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpent;
+ /**
+ * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. 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).
+ */
+ mapping(IStrategy => uint256) public strategyWithdrawalDelayBlocks;
constructor(IStrategyManager _strategyManager, ISlasher _slasher, IEigenPodManager _eigenPodManager) {
strategyManager = _strategyManager;
@@ -115,5 +112,5 @@ abstract contract DelegationManagerStorage is IDelegationManager {
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
- uint256[38] private __gap;
+ uint256[39] private __gap;
}
diff --git a/src/contracts/core/StrategyManager.sol b/src/contracts/core/StrategyManager.sol
index 998364a2c..b0ec52d25 100644
--- a/src/contracts/core/StrategyManager.sol
+++ b/src/contracts/core/StrategyManager.sol
@@ -126,6 +126,7 @@ contract StrategyManager is
* @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 if thirdPartyTransfersForbidden is set to true for this strategy
*
* 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
@@ -138,10 +139,14 @@ contract StrategyManager is
uint256 expiry,
bytes memory signature
) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
+ require(
+ !thirdPartyTransfersForbidden[strategy],
+ "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
+ );
require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
// calculate struct hash, then increment `staker`'s nonce
uint256 nonce = nonces[staker];
- bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, strategy, token, amount, nonce, expiry));
+ bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
unchecked {
nonces[staker] = nonce + 1;
}
@@ -173,10 +178,11 @@ contract StrategyManager is
/// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal queue
function addShares(
address staker,
+ IERC20 token,
IStrategy strategy,
uint256 shares
) external onlyDelegationManager {
- _addShares(staker, strategy, shares);
+ _addShares(staker, token, strategy, shares);
}
/// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient
@@ -202,6 +208,20 @@ contract StrategyManager is
return (isDeleted, existingWithdrawalRoot);
}
+ /**
+ * If true for a strategy, a user cannot depositIntoStrategyWithSignature into that strategy for another staker
+ * and also when performing DelegationManager.queueWithdrawals, a staker can only withdraw to themselves.
+ * Defaulted to false for all existing strategies.
+ * @param strategy The strategy to set `thirdPartyTransfersForbidden` value to
+ * @param value bool value to set `thirdPartyTransfersForbidden` to
+ */
+ function setThirdPartyTransfersForbidden(
+ IStrategy strategy,
+ bool value
+ ) external onlyStrategyWhitelister {
+ _setThirdPartyTransfersForbidden(strategy, value);
+ }
+
/**
* @notice Owner-only function to change the `strategyWhitelister` address.
* @param newStrategyWhitelister new address for the `strategyWhitelister`.
@@ -213,16 +233,23 @@ contract StrategyManager is
/**
* @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)
+ * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
*/
function addStrategiesToDepositWhitelist(
- IStrategy[] calldata strategiesToWhitelist
+ IStrategy[] calldata strategiesToWhitelist,
+ bool[] calldata thirdPartyTransfersForbiddenValues
) external onlyStrategyWhitelister {
+ require(
+ strategiesToWhitelist.length == thirdPartyTransfersForbiddenValues.length,
+ "StrategyManager.addStrategiesToDepositWhitelist: array lengths do not match"
+ );
uint256 strategiesToWhitelistLength = strategiesToWhitelist.length;
for (uint256 i = 0; i < strategiesToWhitelistLength; ) {
// change storage and emit event only if strategy is not already in whitelist
if (!strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]]) {
strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true;
emit StrategyAddedToDepositWhitelist(strategiesToWhitelist[i]);
+ _setThirdPartyTransfersForbidden(strategiesToWhitelist[i], thirdPartyTransfersForbiddenValues[i]);
}
unchecked {
++i;
@@ -243,6 +270,8 @@ contract StrategyManager is
if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) {
strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false;
emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]);
+ // Set mapping value to default false value
+ _setThirdPartyTransfersForbidden(strategiesToRemoveFromWhitelist[i], false);
}
unchecked {
++i;
@@ -255,13 +284,14 @@ contract StrategyManager is
/**
* @notice This function adds `shares` for a given `strategy` to the `staker` and runs through the necessary update logic.
* @param staker The address to add shares to
+ * @param token The token that is being deposited (used for indexing)
* @param strategy The Strategy in which the `staker` is receiving shares
* @param shares The amount of shares to grant to the `staker`
* @dev In particular, this function calls `delegation.increaseDelegatedShares(staker, strategy, shares)` to ensure that all
* delegated shares are tracked, increases the stored share amount in `stakerStrategyShares[staker][strategy]`, and adds `strategy`
* to the `staker`'s list of strategies, if it is not in the list already.
*/
- function _addShares(address staker, IStrategy strategy, uint256 shares) internal {
+ function _addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) internal {
// sanity checks on inputs
require(staker != address(0), "StrategyManager._addShares: staker cannot be zero address");
require(shares != 0, "StrategyManager._addShares: shares should not be zero!");
@@ -277,6 +307,8 @@ contract StrategyManager is
// add the returned shares to their existing shares for this strategy
stakerStrategyShares[staker][strategy] += shares;
+
+ emit Deposit(staker, token, strategy, shares);
}
/**
@@ -301,12 +333,11 @@ contract StrategyManager is
shares = strategy.deposit(token, amount);
// add the returned shares to the staker's existing shares for this strategy
- _addShares(staker, strategy, shares);
+ _addShares(staker, token, strategy, shares);
// Increase shares delegated to operator, if needed
delegation.increaseDelegatedShares(staker, strategy, shares);
- emit Deposit(staker, token, strategy, shares);
return shares;
}
@@ -377,6 +408,17 @@ contract StrategyManager is
stakerStrategyList[staker].pop();
}
+ /**
+ * @notice Internal function for modifying `thirdPartyTransfersForbidden`.
+ * Used inside of the `setThirdPartyTransfersForbidden` and `addStrategiesToDepositWhitelist` functions.
+ * @param strategy The strategy to set `thirdPartyTransfersForbidden` value to
+ * @param value bool value to set `thirdPartyTransfersForbidden` to
+ */
+ function _setThirdPartyTransfersForbidden(IStrategy strategy, bool value) internal {
+ emit UpdatedThirdPartyTransfersForbidden(strategy, value);
+ thirdPartyTransfersForbidden[strategy] = value;
+ }
+
/**
* @notice Internal function for modifying the `strategyWhitelister`. Used inside of the `setStrategyWhitelister` and `initialize` functions.
* @param newStrategyWhitelister The new address for the `strategyWhitelister` to take.
diff --git a/src/contracts/core/StrategyManagerStorage.sol b/src/contracts/core/StrategyManagerStorage.sol
index e16db96cd..568e6770b 100644
--- a/src/contracts/core/StrategyManagerStorage.sol
+++ b/src/contracts/core/StrategyManagerStorage.sol
@@ -19,7 +19,7 @@ abstract contract StrategyManagerStorage is IStrategyManager {
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
/// @notice The EIP-712 typehash for the deposit struct used by the contract
bytes32 public constant DEPOSIT_TYPEHASH =
- keccak256("Deposit(address strategy,address token,uint256 amount,uint256 nonce,uint256 expiry)");
+ keccak256("Deposit(address staker,address strategy,address token,uint256 amount,uint256 nonce,uint256 expiry)");
// maximum length of dynamic arrays in `stakerStrategyList` mapping, for sanity's sake
uint8 internal constant MAX_STAKER_STRATEGY_LIST_LENGTH = 32;
@@ -65,6 +65,13 @@ abstract contract StrategyManagerStorage is IStrategyManager {
*/
mapping(address => uint256) internal beaconChainETHSharesToDecrementOnWithdrawal;
+ /**
+ * @notice Mapping: strategy => whether or not stakers are allowed to transfer strategy shares to another address
+ * if true for a strategy, a user cannot depositIntoStrategyWithSignature into that strategy for another staker
+ * and also when performing queueWithdrawals, a staker can only withdraw to themselves
+ */
+ mapping(IStrategy => bool) public thirdPartyTransfersForbidden;
+
constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher) {
delegation = _delegation;
eigenPodManager = _eigenPodManager;
@@ -76,5 +83,5 @@ abstract contract StrategyManagerStorage is IStrategyManager {
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
- uint256[40] private __gap;
+ uint256[39] private __gap;
}
diff --git a/src/contracts/interfaces/IAVSDirectory.sol b/src/contracts/interfaces/IAVSDirectory.sol
new file mode 100644
index 000000000..6187439ff
--- /dev/null
+++ b/src/contracts/interfaces/IAVSDirectory.sol
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity >=0.5.0;
+
+import "./ISignatureUtils.sol";
+
+interface IAVSDirectory is ISignatureUtils {
+ /// @notice Enum representing the status of an operator's registration with an AVS
+ enum OperatorAVSRegistrationStatus {
+ UNREGISTERED, // Operator not registered to AVS
+ REGISTERED // Operator registered to AVS
+ }
+
+ /**
+ * @notice Emitted when @param avs indicates that they are updating their MetadataURI string
+ * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing
+ */
+ event AVSMetadataURIUpdated(address indexed avs, string metadataURI);
+
+ /// @notice Emitted when an operator's registration status for an AVS is updated
+ event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, OperatorAVSRegistrationStatus status);
+
+ /**
+ * @notice Called by an avs to register an operator with the avs.
+ * @param operator The address of the operator to register.
+ * @param operatorSignature The signature, salt, and expiry of the operator's signature.
+ */
+ function registerOperatorToAVS(
+ address operator,
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
+ ) external;
+
+ /**
+ * @notice Called by an avs to deregister an operator with the avs.
+ * @param operator The address of the operator to deregister.
+ */
+ function deregisterOperatorFromAVS(address operator) external;
+
+ /**
+ * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
+ * @param metadataURI The URI for metadata associated with an AVS
+ * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event
+ */
+ function updateAVSMetadataURI(string calldata metadataURI) external;
+
+ /**
+ * @notice Returns whether or not the salt has already been used by the operator.
+ * @dev Salts is used in the `registerOperatorToAVS` function.
+ */
+ function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool);
+
+ /**
+ * @notice Calculates the digest hash to be signed by an operator to register with an AVS
+ * @param operator The account registering as an operator
+ * @param avs The AVS the operator is registering to
+ * @param salt A unique and single use value associated with the approver signature.
+ * @param expiry Time after which the approver's signature becomes invalid
+ */
+ function calculateOperatorAVSRegistrationDigestHash(
+ address operator,
+ address avs,
+ bytes32 salt,
+ uint256 expiry
+ ) external view returns (bytes32);
+
+ /// @notice The EIP-712 typehash for the Registration struct used by the contract
+ function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32);
+}
diff --git a/src/contracts/interfaces/IDelegationManager.sol b/src/contracts/interfaces/IDelegationManager.sol
index b26b41e51..5b663d4e8 100644
--- a/src/contracts/interfaces/IDelegationManager.sol
+++ b/src/contracts/interfaces/IDelegationManager.sol
@@ -100,12 +100,6 @@ interface IDelegationManager is ISignatureUtils {
address withdrawer;
}
- /// @notice Enum representing the status of an operator's registration with an AVS
- enum OperatorAVSRegistrationStatus {
- UNREGISTERED, // Operator not registered to AVS
- REGISTERED // Operator registered to AVS
- }
-
// @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails);
@@ -118,15 +112,6 @@ interface IDelegationManager is ISignatureUtils {
*/
event OperatorMetadataURIUpdated(address indexed operator, string metadataURI);
- /**
- * @notice Emitted when @param avs indicates that they are updating their MetadataURI string
- * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing
- */
- event AVSMetadataURIUpdated(address indexed avs, string metadataURI);
-
- /// @notice Emitted when an operator's registration status for an AVS is updated
- event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, OperatorAVSRegistrationStatus status);
-
/// @notice Emitted whenever an operator's shares are increased for a given strategy. Note that shares is the delta in the operator's shares.
event OperatorSharesIncreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
@@ -154,9 +139,12 @@ interface IDelegationManager is ISignatureUtils {
/// @notice Emitted when a queued withdrawal is *migrated* from the StrategyManager to the DelegationManager
event WithdrawalMigrated(bytes32 oldWithdrawalRoot, bytes32 newWithdrawalRoot);
+
+ /// @notice Emitted when the `minWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
+ event MinWithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
- /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
- event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
+ /// @notice Emitted when the `strategyWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
+ event StrategyWithdrawalDelayBlocksSet(IStrategy strategy, uint256 previousValue, uint256 newValue);
/**
* @notice Registers the caller as an operator in EigenLayer.
@@ -188,13 +176,6 @@ interface IDelegationManager is ISignatureUtils {
*/
function updateOperatorMetadataURI(string calldata metadataURI) external;
- /**
- * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
- * @param metadataURI The URI for metadata associated with an AVS
- * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event
- */
- function updateAVSMetadataURI(string calldata metadataURI) external;
-
/**
* @notice Caller delegates their stake to an operator.
* @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on EigenLayer.
@@ -329,42 +310,6 @@ interface IDelegationManager is ISignatureUtils {
uint256 shares
) external;
- /**
- * @notice Called by an avs to register an operator with the avs.
- * @param operator The address of the operator to register.
- * @param operatorSignature The signature, salt, and expiry of the operator's signature.
- */
- function registerOperatorToAVS(
- address operator,
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
- ) external;
-
- /**
- * @notice Called by an avs to deregister an operator with the avs.
- * @param operator The address of the operator to deregister.
- */
- function deregisterOperatorFromAVS(address operator) external;
-
- /**
- * @notice Returns whether or not the salt has already been used by the operator.
- * @dev Salts is used in the `registerOperatorToAVS` function.
- */
- function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool);
-
- /**
- * @notice Calculates the digest hash to be signed by an operator to register with an AVS
- * @param operator The account registering as an operator
- * @param avs The AVS the operator is registering to
- * @param salt A unique and single use value associated with the approver signature.
- * @param expiry Time after which the approver's signature becomes invalid
- */
- function calculateOperatorAVSRegistrationDigestHash(
- address operator,
- address avs,
- bytes32 salt,
- uint256 expiry
- ) external view returns (bytes32);
-
/**
* @notice returns the address of the operator that `staker` is delegated to.
* @notice Mapping: staker => operator whom the staker is currently delegated to.
@@ -392,6 +337,21 @@ interface IDelegationManager is ISignatureUtils {
*/
function stakerOptOutWindowBlocks(address operator) external view returns (uint256);
+ /**
+ * @notice Given array of strategies, returns array of shares for the operator
+ */
+ function getOperatorShares(
+ address operator,
+ IStrategy[] memory strategies
+ ) external view returns (uint256[] memory);
+
+ /**
+ * @notice Given a list of strategies, return the minimum number of blocks that must pass to withdraw
+ * from all the inputted strategies. Return value is >= minWithdrawalDelayBlocks as this is the global min withdrawal delay.
+ * @param strategies The strategies to check withdrawal delays for
+ */
+ function getWithdrawalDelay(IStrategy[] calldata strategies) external view returns (uint256);
+
/**
* @notice returns the total number of shares in `strategy` that are delegated to `operator`.
* @notice Mapping: operator => strategy => total number of shares in the strategy delegated to the operator.
@@ -424,10 +384,16 @@ interface IDelegationManager is ISignatureUtils {
/**
* @notice Minimum delay enforced by this contract for completing queued withdrawals. 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).
- * @dev Note that the withdrawal delay is not enforced on withdrawals of 'beaconChainETH', as the EigenPods have their own separate delay mechanic
- * and we want to avoid stacking multiple enforced delays onto a single withdrawal.
+ * Note that strategies each have a separate withdrawal delay, which can be greater than this value. So the minimum number of blocks that must pass
+ * to withdraw a strategy is MAX(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy])
*/
- function withdrawalDelayBlocks() external view returns (uint256);
+ function minWithdrawalDelayBlocks() external view returns (uint256);
+
+ /**
+ * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. 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 strategyWithdrawalDelayBlocks(IStrategy strategy) external view returns (uint256);
/**
* @notice Calculates the digestHash for a `staker` to sign to delegate to an `operator`
@@ -480,9 +446,6 @@ interface IDelegationManager is ISignatureUtils {
/// @notice The EIP-712 typehash for the DelegationApproval struct used by the contract
function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32);
- /// @notice The EIP-712 typehash for the Registration struct used by the contract
- function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32);
-
/**
* @notice Getter function for the current EIP-712 domain separator for this contract.
*
diff --git a/src/contracts/interfaces/IEigenPodManager.sol b/src/contracts/interfaces/IEigenPodManager.sol
index 2ed9063c0..020e0d2d9 100644
--- a/src/contracts/interfaces/IEigenPodManager.sol
+++ b/src/contracts/interfaces/IEigenPodManager.sol
@@ -29,6 +29,9 @@ interface IEigenPodManager is IPausable {
/// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue`
event MaxPodsUpdated(uint256 previousValue, uint256 newValue);
+ /// @notice Emitted when the balance of an EigenPod is updated
+ event PodSharesUpdated(address indexed podOwner, int256 sharesDelta);
+
/// @notice Emitted when a withdrawal of beacon chain ETH is completed
event BeaconChainETHWithdrawalCompleted(
address indexed podOwner,
@@ -39,6 +42,8 @@ interface IEigenPodManager is IPausable {
bytes32 withdrawalRoot
);
+ event DenebForkTimestampUpdated(uint64 newValue);
+
/**
* @notice Creates an EigenPod for the sender.
* @dev Function will revert if the `msg.sender` already has an EigenPod.
@@ -143,4 +148,18 @@ interface IEigenPodManager is IPausable {
* @dev Reverts if `shares` is not a whole Gwei amount
*/
function withdrawSharesAsTokens(address podOwner, address destination, uint256 shares) external;
+
+ /**
+ * @notice the deneb hard fork timestamp used to determine which proof path to use for proving a withdrawal
+ */
+ function denebForkTimestamp() external view returns (uint64);
+
+ /**
+ * setting the deneb hard fork timestamp by the eigenPodManager owner
+ * @dev this function is designed to be called twice. Once, it is set to type(uint64).max
+ * prior to the actual deneb fork timestamp being set, and then the second time it is set
+ * to the actual deneb fork timestamp.
+ */
+ function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external;
+
}
diff --git a/src/contracts/interfaces/IStrategyManager.sol b/src/contracts/interfaces/IStrategyManager.sol
index 5c1e41597..ecdb175c8 100644
--- a/src/contracts/interfaces/IStrategyManager.sol
+++ b/src/contracts/interfaces/IStrategyManager.sol
@@ -22,6 +22,9 @@ interface IStrategyManager {
*/
event Deposit(address staker, IERC20 token, IStrategy strategy, uint256 shares);
+ /// @notice Emitted when `thirdPartyTransfersForbidden` is updated for a strategy and value by the owner
+ event UpdatedThirdPartyTransfersForbidden(IStrategy strategy, bool value);
+
/// @notice Emitted when the `strategyWhitelister` is changed
event StrategyWhitelisterChanged(address previousAddress, address newAddress);
@@ -61,7 +64,7 @@ interface IStrategyManager {
* @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).
+ * @dev Cannot be called if thirdPartyTransfersForbidden is set to true for this strategy
*
* 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
@@ -79,7 +82,7 @@ interface IStrategyManager {
function removeShares(address staker, IStrategy strategy, uint256 shares) external;
/// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal queue
- function addShares(address staker, IStrategy strategy, uint256 shares) external;
+ function addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) external;
/// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient
function withdrawSharesAsTokens(address recipient, IStrategy strategy, uint256 shares, IERC20 token) external;
@@ -99,8 +102,12 @@ interface IStrategyManager {
/**
* @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)
+ * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
*/
- function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external;
+ function addStrategiesToDepositWhitelist(
+ IStrategy[] calldata strategiesToWhitelist,
+ bool[] calldata thirdPartyTransfersForbiddenValues
+ ) external;
/**
* @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
@@ -120,6 +127,12 @@ interface IStrategyManager {
/// @notice Returns the address of the `strategyWhitelister`
function strategyWhitelister() external view returns (address);
+ /**
+ * @notice Returns bool for whether or not `strategy` enables credit transfers. i.e enabling
+ * depositIntoStrategyWithSignature calls or queueing withdrawals to a different address than the staker.
+ */
+ function thirdPartyTransfersForbidden(IStrategy strategy) external view returns (bool);
+
// LIMITED BACKWARDS-COMPATIBILITY FOR DEPRECATED FUNCTIONALITY
// packed struct for queued withdrawals; helps deal with stack-too-deep errors
struct DeprecatedStruct_WithdrawerAndNonce {
diff --git a/src/contracts/libraries/BeaconChainProofs.sol b/src/contracts/libraries/BeaconChainProofs.sol
index 6cf36a702..2c4b88662 100644
--- a/src/contracts/libraries/BeaconChainProofs.sol
+++ b/src/contracts/libraries/BeaconChainProofs.sol
@@ -11,35 +11,19 @@ import "../libraries/Endian.sol";
//BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate
library BeaconChainProofs {
// 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;
+ //Note: changed in the deneb hard fork from 4->5
+ uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB = 5;
+ uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA = 4;
// 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;
//HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24
@@ -48,7 +32,6 @@ library BeaconChainProofs {
//Index of block_summary_root in historical_summary container
uint256 internal constant BLOCK_SUMMARY_ROOT_INDEX = 0;
- 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;
@@ -62,31 +45,20 @@ library BeaconChainProofs {
// in beacon block header https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
uint256 internal constant SLOT_INDEX = 0;
- uint256 internal constant PROPOSER_INDEX_INDEX = 1;
uint256 internal constant STATE_ROOT_INDEX = 3;
uint256 internal constant BODY_ROOT_INDEX = 4;
// in beacon state https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate
- uint256 internal constant HISTORICAL_BATCH_STATE_ROOT_INDEX = 1;
- uint256 internal constant BEACON_STATE_SLOT_INDEX = 2;
- uint256 internal constant LATEST_BLOCK_HEADER_ROOT_INDEX = 4;
- uint256 internal constant BLOCK_ROOTS_INDEX = 5;
- uint256 internal constant STATE_ROOTS_INDEX = 6;
- 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 EXECUTION_PAYLOAD_HEADER_INDEX = 24;
uint256 internal constant HISTORICAL_SUMMARIES_INDEX = 27;
// in validator https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
uint256 internal constant VALIDATOR_PUBKEY_INDEX = 0;
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 TIMESTAMP_INDEX = 9;
- uint256 internal constant WITHDRAWALS_ROOT_INDEX = 14;
//in execution payload
uint256 internal constant WITHDRAWALS_INDEX = 14;
@@ -95,9 +67,6 @@ library BeaconChainProofs {
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
/// @notice The number of slots each epoch in the beacon chain
@@ -211,7 +180,8 @@ library BeaconChainProofs {
function verifyWithdrawal(
bytes32 beaconStateRoot,
bytes32[] calldata withdrawalFields,
- WithdrawalProof calldata withdrawalProof
+ WithdrawalProof calldata withdrawalProof,
+ uint64 denebForkTimestamp
) internal view {
require(
withdrawalFields.length == 2 ** WITHDRAWAL_FIELD_TREE_HEIGHT,
@@ -232,9 +202,11 @@ library BeaconChainProofs {
"BeaconChainProofs.verifyWithdrawal: historicalSummaryIndex is too large"
);
+ //Note: post deneb hard fork, the number of exection payload header fields increased from 15->17, adding an extra level to the tree height
+ uint256 executionPayloadHeaderFieldTreeHeight = (getWithdrawalTimestamp(withdrawalProof) < denebForkTimestamp) ? EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA : EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB;
require(
withdrawalProof.withdrawalProof.length ==
- 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
+ 32 * (executionPayloadHeaderFieldTreeHeight + WITHDRAWALS_TREE_HEIGHT + 1),
"BeaconChainProofs.verifyWithdrawal: withdrawalProof has incorrect length"
);
require(
@@ -247,7 +219,7 @@ library BeaconChainProofs {
"BeaconChainProofs.verifyWithdrawal: slotProof has incorrect length"
);
require(
- withdrawalProof.timestampProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT),
+ withdrawalProof.timestampProof.length == 32 * (executionPayloadHeaderFieldTreeHeight),
"BeaconChainProofs.verifyWithdrawal: timestampProof has incorrect length"
);
@@ -315,16 +287,14 @@ library BeaconChainProofs {
leaf: withdrawalProof.timestampRoot,
index: TIMESTAMP_INDEX
}),
- "BeaconChainProofs.verifyWithdrawal: Invalid blockNumber merkle proof"
+ "BeaconChainProofs.verifyWithdrawal: Invalid timestamp merkle proof"
);
{
/**
* Next we verify the withdrawal fields against the executionPayloadRoot:
- * First we compute the withdrawal_index, then we calculate merkleize the
- * withdrawalFields container to calculate the the withdrawalRoot.
- * Finally we verify the withdrawalRoot against the executionPayloadRoot.
- *
+ * First we compute the withdrawal_index, then we merkleize the
+ * withdrawalFields container to calculate the withdrawalRoot.
*
* Note: Merkleization of the withdrawals root tree uses MerkleizeWithMixin, i.e., the length of the array is hashed with the root of
* the array. Thus we shift the WITHDRAWALS_INDEX over by WITHDRAWALS_TREE_HEIGHT + 1 and not just WITHDRAWALS_TREE_HEIGHT.
diff --git a/src/contracts/libraries/Merkle.sol b/src/contracts/libraries/Merkle.sol
index 9da153a5f..ddb0bd4c5 100644
--- a/src/contracts/libraries/Merkle.sol
+++ b/src/contracts/libraries/Merkle.sol
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: BUSL-1.1
+// SPDX-License-Identifier: MIT
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
diff --git a/src/contracts/pods/DelayedWithdrawalRouter.sol b/src/contracts/pods/DelayedWithdrawalRouter.sol
index 898c956d0..9de2eb093 100644
--- a/src/contracts/pods/DelayedWithdrawalRouter.sol
+++ b/src/contracts/pods/DelayedWithdrawalRouter.sol
@@ -23,8 +23,8 @@ contract DelayedWithdrawalRouter is
* up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
*/
uint256 public withdrawalDelayBlocks;
- // the number of 12-second blocks in one week (60 * 60 * 24 * 7 / 12 = 50,400)
- uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;
+ // the number of 12-second blocks in 30 days (60 * 60 * 24 * 30 / 12 = 216,000)
+ uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 216000;
/// @notice The EigenPodManager contract of EigenLayer.
IEigenPodManager public immutable eigenPodManager;
@@ -47,6 +47,7 @@ contract DelayedWithdrawalRouter is
"DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address"
);
eigenPodManager = _eigenPodManager;
+ _disableInitializers();
}
function initialize(
diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol
index 1a2e3cdbf..49ab3b0d0 100644
--- a/src/contracts/pods/EigenPod.sol
+++ b/src/contracts/pods/EigenPod.sol
@@ -607,7 +607,8 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
BeaconChainProofs.verifyWithdrawal({
beaconStateRoot: beaconStateRoot,
withdrawalFields: withdrawalFields,
- withdrawalProof: withdrawalProof
+ withdrawalProof: withdrawalProof,
+ denebForkTimestamp: eigenPodManager.denebForkTimestamp()
});
uint40 validatorIndex = withdrawalFields.getValidatorIndex();
diff --git a/src/contracts/pods/EigenPodManager.sol b/src/contracts/pods/EigenPodManager.sol
index 545f2ce55..a90177a13 100644
--- a/src/contracts/pods/EigenPodManager.sol
+++ b/src/contracts/pods/EigenPodManager.sol
@@ -140,6 +140,7 @@ contract EigenPodManager is
});
}
}
+ emit PodSharesUpdated(podOwner, sharesDelta);
}
/**
@@ -180,6 +181,8 @@ contract EigenPodManager is
int256 updatedPodOwnerShares = currentPodOwnerShares + int256(shares);
podOwnerShares[podOwner] = updatedPodOwnerShares;
+ emit PodSharesUpdated(podOwner, int256(shares));
+
return uint256(_calculateChangeInDelegatableShares({sharesBefore: currentPodOwnerShares, sharesAfter: updatedPodOwnerShares}));
}
@@ -208,13 +211,14 @@ contract EigenPodManager is
if (shares > currentShareDeficit) {
podOwnerShares[podOwner] = 0;
shares -= currentShareDeficit;
+ emit PodSharesUpdated(podOwner, int256(currentShareDeficit));
// otherwise get rid of as much deficit as possible, and return early, since there is nothing left over to forward on
} else {
podOwnerShares[podOwner] += int256(shares);
+ emit PodSharesUpdated(podOwner, int256(shares));
return;
}
}
-
// Actually withdraw to the destination
ownerToPod[podOwner].withdrawRestakedBeaconChainETH(destination, shares);
}
@@ -237,6 +241,18 @@ contract EigenPodManager is
_updateBeaconChainOracle(newBeaconChainOracle);
}
+ /**
+ * @notice Sets the timestamp of the Deneb fork.
+ * @param newDenebForkTimestamp is the new timestamp of the Deneb fork
+ */
+ function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner {
+ require(newDenebForkTimestamp != 0, "EigenPodManager.setDenebForkTimestamp: cannot set newDenebForkTimestamp to 0");
+ require(_denebForkTimestamp == 0, "EigenPodManager.setDenebForkTimestamp: cannot set denebForkTimestamp more than once");
+
+ _denebForkTimestamp = newDenebForkTimestamp;
+ emit DenebForkTimestampUpdated(newDenebForkTimestamp);
+ }
+
// INTERNAL FUNCTIONS
function _deployPod() internal returns (IEigenPod) {
@@ -326,4 +342,17 @@ contract EigenPodManager is
);
return stateRoot;
}
-}
+
+ /**
+ * @notice Wrapper around the `_denebForkTimestamp` storage variable that returns type(uint64).max if the storage variable is unset.
+ * @dev This allows restricting the storage variable to be set once and only once.
+ */
+ function denebForkTimestamp() public view returns (uint64) {
+ uint64 timestamp = _denebForkTimestamp;
+ if (timestamp == 0) {
+ return type(uint64).max;
+ } else {
+ return timestamp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/contracts/pods/EigenPodManagerStorage.sol b/src/contracts/pods/EigenPodManagerStorage.sol
index b893a0816..76e687fbd 100644
--- a/src/contracts/pods/EigenPodManagerStorage.sol
+++ b/src/contracts/pods/EigenPodManagerStorage.sol
@@ -64,6 +64,8 @@ abstract contract EigenPodManagerStorage is IEigenPodManager {
*/
mapping(address => int256) public podOwnerShares;
+ uint64 internal _denebForkTimestamp;
+
constructor(
IETHPOSDeposit _ethPOS,
IBeacon _eigenPodBeacon,
@@ -83,5 +85,5 @@ abstract contract EigenPodManagerStorage is IEigenPodManager {
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
- uint256[45] private __gap;
+ uint256[44] private __gap;
}
diff --git a/src/contracts/strategies/StrategyBase.sol b/src/contracts/strategies/StrategyBase.sol
index 713ec23ce..cf31a30f8 100644
--- a/src/contracts/strategies/StrategyBase.sol
+++ b/src/contracts/strategies/StrategyBase.sol
@@ -90,6 +90,7 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
* (as performed in the StrategyManager's deposit functions). In particular, setting the `underlyingToken` of this contract
* to be a fee-on-transfer token will break the assumption that the amount this contract *received* of the token is equal to
* the amount that was input when the transfer was performed (i.e. the amount transferred 'out' of the depositor's balance).
+ * @dev Note that any validation of `token` is done inside `_beforeDeposit`. This can be overridden if needed.
* @return newShares is the number of new shares issued at the current exchange ratio.
*/
function deposit(
@@ -99,8 +100,6 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
// call hook to allow for any pre-deposit logic
_beforeDeposit(token, amount);
- require(token == underlyingToken, "StrategyBase.deposit: Can only deposit underlyingToken");
-
// copy `totalShares` value to memory, prior to any change
uint256 priorTotalShares = totalShares;
@@ -130,6 +129,7 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
* @param amountShares is the amount of shares being withdrawn
* @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
* other functions, and individual share balances are recorded in the strategyManager as well.
+ * @dev Note that any validation of `token` is done inside `_beforeWithdrawal`. This can be overridden if needed.
*/
function withdraw(
address recipient,
@@ -139,8 +139,6 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
// call hook to allow for any pre-withdrawal logic
_beforeWithdrawal(recipient, token, amountShares);
- require(token == underlyingToken, "StrategyBase.withdraw: Can only withdraw the strategy token");
-
// copy `totalShares` value to memory, prior to any change
uint256 priorTotalShares = totalShares;
@@ -162,7 +160,7 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
// Decrease the `totalShares` value to reflect the withdrawal
totalShares = priorTotalShares - amountShares;
- underlyingToken.safeTransfer(recipient, amountToSend);
+ _afterWithdrawal(recipient, token, amountToSend);
}
/**
@@ -170,8 +168,9 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
* @param token The token being deposited
* @param amount The amount of `token` being deposited
*/
- // solhint-disable-next-line no-empty-blocks
- function _beforeDeposit(IERC20 token, uint256 amount) internal virtual {}
+ function _beforeDeposit(IERC20 token, uint256 amount) internal virtual {
+ require(token == underlyingToken, "StrategyBase.deposit: Can only deposit underlyingToken");
+ }
/**
* @notice Called in the external `withdraw` function, before any logic is executed. Expected to be overridden if strategies want such logic.
@@ -179,8 +178,20 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
* @param token The token being withdrawn
* @param amountShares The amount of shares being withdrawn
*/
- // solhint-disable-next-line no-empty-blocks
- function _beforeWithdrawal(address recipient, IERC20 token, uint256 amountShares) internal virtual {}
+ function _beforeWithdrawal(address recipient, IERC20 token, uint256 amountShares) internal virtual {
+ require(token == underlyingToken, "StrategyBase.withdraw: Can only withdraw the strategy token");
+ }
+
+ /**
+ * @notice Transfers tokens to the recipient after a withdrawal is processed
+ * @dev Called in the external `withdraw` function after all logic is executed
+ * @param recipient The destination of the tokens
+ * @param token The ERC20 being transferred
+ * @param amountToSend The amount of `token` to transfer
+ */
+ function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual {
+ token.safeTransfer(recipient, amountToSend);
+ }
/**
* @notice Currently returns a brief string explaining the strategy's goal & purpose, but for more complex
diff --git a/src/contracts/strategies/StrategyBaseTVLLimits.sol b/src/contracts/strategies/StrategyBaseTVLLimits.sol
index fd1541568..a395bf081 100644
--- a/src/contracts/strategies/StrategyBaseTVLLimits.sol
+++ b/src/contracts/strategies/StrategyBaseTVLLimits.sol
@@ -76,9 +76,11 @@ contract StrategyBaseTVLLimits is StrategyBase {
* c) increases in the token balance of this contract through other effects – including token rebasing – may cause similar issues to (a) and (b).
* @param amount The amount of `token` being deposited
*/
- function _beforeDeposit(IERC20 /*token*/, uint256 amount) internal virtual override {
+ function _beforeDeposit(IERC20 token, uint256 amount) internal virtual override {
require(amount <= maxPerDeposit, "StrategyBaseTVLLimits: max per deposit exceeded");
require(_tokenBalance() <= maxTotalDeposits, "StrategyBaseTVLLimits: max deposits exceeded");
+
+ super._beforeDeposit(token, amount);
}
/**
diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol
index 8f6964d92..cb3f51960 100644
--- a/src/test/Delegation.t.sol
+++ b/src/test/Delegation.t.sol
@@ -326,7 +326,14 @@ contract DelegationTests is EigenLayerTestHelper {
/// cannot be intitialized multiple times
function testCannotInitMultipleTimesDelegation() public cannotReinit {
//delegation has already been initialized in the Deployer test contract
- delegation.initialize(address(this), eigenLayerPauserReg, 0, initializedWithdrawalDelayBlocks);
+ delegation.initialize(
+ address(this),
+ eigenLayerPauserReg,
+ 0,
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ );
}
/// @notice This function tests to ensure that a you can't register as a delegate multiple times
@@ -363,7 +370,14 @@ contract DelegationTests is EigenLayerTestHelper {
//delegation has already been initialized in the Deployer test contract
vm.prank(_attacker);
cheats.expectRevert(bytes("Initializable: contract is already initialized"));
- delegation.initialize(_attacker, eigenLayerPauserReg, 0, initializedWithdrawalDelayBlocks);
+ delegation.initialize(
+ _attacker,
+ eigenLayerPauserReg,
+ 0,
+ 0, // minWithdrawalDelayBLocks
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ );
}
/// @notice This function tests that the earningsReceiver cannot be set to address(0)
diff --git a/src/test/DelegationFaucet.t.sol b/src/test/DelegationFaucet.t.sol
index 53aa47abd..3bd26ef80 100644
--- a/src/test/DelegationFaucet.t.sol
+++ b/src/test/DelegationFaucet.t.sol
@@ -37,8 +37,9 @@ contract DelegationFaucetTests is EigenLayerTestHelper {
);
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = stakeTokenStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
// Deploy DelegationFaucet, grant it admin/mint/pauser roles, etc.
diff --git a/src/test/DepositWithdraw.t.sol b/src/test/DepositWithdraw.t.sol
index 28c3b5d04..accb90382 100644
--- a/src/test/DepositWithdraw.t.sol
+++ b/src/test/DepositWithdraw.t.sol
@@ -51,8 +51,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = wethStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
cheats.expectRevert(bytes("StrategyBase.deposit: Can only deposit underlyingToken"));
@@ -87,8 +88,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = IStrategy(nonexistentStrategy);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
cheats.expectRevert();
@@ -100,8 +102,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = wethStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
cheats.expectRevert(bytes("StrategyBase.deposit: newShares cannot be zero"));
@@ -278,8 +281,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
{
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = oneWeiFeeOnTransferTokenStrategy;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
}
@@ -381,7 +385,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0 /*initialPausedStatus*/,
- initializedWithdrawalDelayBlocks
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -446,8 +452,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
{
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = stethStrategy;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
}
@@ -490,8 +497,9 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = IStrategy(_strategyBase);
- _strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ _strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
return _strategyManager;
diff --git a/src/test/EigenLayerDeployer.t.sol b/src/test/EigenLayerDeployer.t.sol
index 3283be6db..05998820f 100644
--- a/src/test/EigenLayerDeployer.t.sol
+++ b/src/test/EigenLayerDeployer.t.sol
@@ -73,7 +73,9 @@ contract EigenLayerDeployer is Operators {
uint256 public constant eigenTotalSupply = 1000e18;
uint256 nonce = 69;
uint256 public gasLimit = 750000;
- uint256 initializedWithdrawalDelayBlocks = 0;
+ IStrategy[] public initializeStrategiesToSetDelayBlocks;
+ uint256[] public initializeWithdrawalDelayBlocks;
+ uint256 minWithdrawalDelayBlocks = 0;
uint32 PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS = 7 days / 12 seconds;
uint256 REQUIRED_BALANCE_WEI = 32 ether;
uint64 MAX_PARTIAL_WTIHDRAWAL_AMOUNT_GWEI = 1 ether / 1e9;
@@ -270,7 +272,9 @@ contract EigenLayerDeployer is Operators {
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0 /*initialPausedStatus*/,
- initializedWithdrawalDelayBlocks
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
eigenLayerProxyAdmin.upgradeAndCall(
diff --git a/src/test/EigenLayerTestHelper.t.sol b/src/test/EigenLayerTestHelper.t.sol
index 0b2387e60..6f99dc6bb 100644
--- a/src/test/EigenLayerTestHelper.t.sol
+++ b/src/test/EigenLayerTestHelper.t.sol
@@ -127,8 +127,9 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
{
cheats.startPrank(strategyManager.strategyWhitelister());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = stratToDepositTo;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
}
diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol
index caec6be5e..56cf277cf 100644
--- a/src/test/EigenPod.t.sol
+++ b/src/test/EigenPod.t.sol
@@ -15,6 +15,8 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
uint256 internal constant GWEI_TO_WEI = 1e9;
+ uint64 public constant DENEB_FORK_TIMESTAMP_GOERLI = 1705473120;
+
bytes pubkey =
hex"88347ed1c492eedc97fc8c506a35d44d81f27a0c7a1c661b35913cfd15256c0cccbd34a83341f505c7de2983292f2cab";
@@ -24,6 +26,8 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
address podOwner = address(42000094993494);
+ bool public IS_DENEB;
+
Vm cheats = Vm(HEVM_ADDRESS);
DelegationManager public delegation;
IStrategyManager public strategyManager;
@@ -53,6 +57,8 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
bytes32[] validatorFields;
uint32 WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds;
+ IStrategy[] public initializeStrategiesToSetDelayBlocks;
+ uint256[] public initializeWithdrawalDelayBlocks;
uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9;
uint64 RESTAKED_BALANCE_OFFSET_GWEI = 75e7;
uint64 internal constant GOERLI_GENESIS_TIME = 1616508000;
@@ -205,7 +211,9 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
initialOwner,
pauserReg,
0 /*initialPausedStatus*/,
- WITHDRAWAL_DELAY_BLOCKS
+ WITHDRAWAL_DELAY_BLOCKS,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -498,6 +506,37 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
}
/// @notice This test is to ensure the full withdrawal flow works
+ function testFullWithdrawalFlowDeneb() public returns (IEigenPod) {
+ eigenPodManager.setDenebForkTimestamp(DENEB_FORK_TIMESTAMP_GOERLI);
+ IS_DENEB = true;
+ //this call is to ensure that validator 302913 has proven their withdrawalcreds
+ // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json"
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ IEigenPod newPod = eigenPodManager.getPod(podOwner);
+
+ //Deneb: ./solidityProofGen/solidityProofGen "WithdrawalFieldsProof" 302913 271 8191 true false "data/deneb_goerli_block_header_7431952.json" "data/deneb_goerli_slot_7431952.json" "data/deneb_goerli_slot_7421952.json" "data/deneb_goerli_block_header_7421951.json" "data/deneb_goerli_block_7421951.json" "fullWithdrawalProof_Latest.json" false false
+ // To get block header: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v1/beacon/headers/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_header_6399000.json
+ // To get block: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v2/beacon/blocks/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_6399000.json
+ setJSON("./src/test/test-data/fullWithdrawalDeneb.json");
+ return _proveWithdrawalForPod(newPod);
+ }
+
+ function testFullWithdrawalFlowCapellaWithdrawalAgainstDenebRoot() public returns (IEigenPod) {
+ IS_DENEB = false;
+ //this call is to ensure that validator 302913 has proven their withdrawalcreds
+ // ./solidityProofGen/solidityProofGen "WithdrawalFieldsProof" 302913 146 8092 true false "data/deneb_goerli_block_header_7431952.json" "data/deneb_goerli_slot_7431952.json" "data/goerli_slot_6397952.json" "data/goerli_block_header_6397852.json" "data/goerli_block_6397852.json" "fullWithdrawalProof_CapellaAgainstDeneb.json" false true
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ IEigenPod newPod = eigenPodManager.getPod(podOwner);
+
+ //Deneb: ./solidityProofGen/solidityProofGen "WithdrawalFieldsProof" 302913 271 8191 true false "data/deneb_goerli_block_header_7431952.json" "data/deneb_goerli_slot_7431952.json" "data/deneb_goerli_slot_7421952.json" "data/deneb_goerli_block_header_7421951.json" "data/deneb_goerli_block_7421951.json" "fullWithdrawalProof_Latest.json" false
+ // To get block header: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v1/beacon/headers/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_header_6399000.json
+ // To get block: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v2/beacon/blocks/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_6399000.json
+ setJSON("./src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json");
+ return _proveWithdrawalForPod(newPod);
+ }
+
function testFullWithdrawalFlow() public returns (IEigenPod) {
//this call is to ensure that validator 302913 has proven their withdrawalcreds
// ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json"
@@ -831,7 +870,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
emit log("hello");
IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
- emit log("hello");
//./solidityProofGen "WithdrawalFieldsProof" 302913 146 8092 true false "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6397852.json" "data/withdrawal_proof_goerli/goerli_block_header_6397852.json" "data/withdrawal_proof_goerli/goerli_block_6397852.json" "fullWithdrawalProof_Latest.json" false
// To get block header: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v1/beacon/headers/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_header_6399000.json
// To get block: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v2/beacon/blocks/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_6399000.json
@@ -1471,7 +1509,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
);
-
+ emit log_named_uint("withdrawalAmountGwei", withdrawalAmountGwei);
uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) *
uint64(GWEI_TO_WEI);
cheats.deal(address(newPod), leftOverBalanceWEI);
@@ -1723,18 +1761,22 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();
+ if(!IS_DENEB){
+ emit log("NOT DENEB");
+ }
+ bytes memory withdrawalProof = IS_DENEB ? abi.encodePacked(getWithdrawalProofDeneb()) : abi.encodePacked(getWithdrawalProofCapella());
+ bytes memory timestampProof = IS_DENEB ? abi.encodePacked(getTimestampProofDeneb()) : abi.encodePacked(getTimestampProofCapella());
{
bytes32 blockRoot = getBlockRoot();
bytes32 slotRoot = getSlotRoot();
bytes32 timestampRoot = getTimestampRoot();
bytes32 executionPayloadRoot = getExecutionPayloadRoot();
-
return
BeaconChainProofs.WithdrawalProof(
- abi.encodePacked(getWithdrawalProof()),
+ abi.encodePacked(withdrawalProof),
abi.encodePacked(getSlotProof()),
abi.encodePacked(getExecutionPayloadProof()),
- abi.encodePacked(getTimestampProof()),
+ abi.encodePacked(timestampProof),
abi.encodePacked(getHistoricalSummaryProof()),
uint64(getBlockRootIndex()),
uint64(getHistoricalSummaryIndex()),
@@ -1746,6 +1788,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
);
}
}
+
function _setOracleBlockRoot() internal {
bytes32 latestBlockRoot = getLatestBlockRoot();
@@ -1774,7 +1817,7 @@ contract Relayer is Test {
bytes32[] calldata withdrawalFields,
BeaconChainProofs.WithdrawalProof calldata proofs
) public view {
- BeaconChainProofs.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs);
+ BeaconChainProofs.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs, type(uint64).max);
}
}
diff --git a/src/test/Withdrawals.t.sol b/src/test/Withdrawals.t.sol
index 2f07da4e1..567822f70 100644
--- a/src/test/Withdrawals.t.sol
+++ b/src/test/Withdrawals.t.sol
@@ -22,16 +22,17 @@ contract WithdrawalTests is EigenLayerTestHelper {
function testWithdrawalWrapper(
address operator,
address depositor,
- address withdrawer,
uint96 ethAmount,
uint96 eigenAmount,
bool withdrawAsTokens,
bool RANDAO
- ) public fuzzedAddress(operator) fuzzedAddress(depositor) fuzzedAddress(withdrawer) {
+ ) public fuzzedAddress(operator) fuzzedAddress(depositor) {
cheats.assume(depositor != operator);
cheats.assume(ethAmount >= 1 && ethAmount <= 1e18);
cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18);
+ address withdrawer = depositor;
+
if (RANDAO) {
_testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens);
} else {
@@ -244,14 +245,13 @@ contract WithdrawalTests is EigenLayerTestHelper {
function testRedelegateAfterWithdrawal(
address operator,
address depositor,
- address withdrawer,
uint96 ethAmount,
uint96 eigenAmount,
bool withdrawAsShares
- ) public fuzzedAddress(operator) fuzzedAddress(depositor) fuzzedAddress(withdrawer) {
+ ) public fuzzedAddress(operator) fuzzedAddress(depositor) {
cheats.assume(depositor != operator);
//this function performs delegation and subsequent withdrawal
- testWithdrawalWrapper(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsShares, true);
+ testWithdrawalWrapper(operator, depositor, ethAmount, eigenAmount, withdrawAsShares, true);
cheats.prank(depositor);
delegation.undelegate(depositor);
@@ -261,50 +261,6 @@ contract WithdrawalTests is EigenLayerTestHelper {
_initiateDelegation(operator, depositor, ethAmount, eigenAmount);
}
- // onlyNotFrozen modifier is not used in current DelegationManager implementation.
- // commented out test case for now
- // /// @notice test to see if an operator who is slashed/frozen
- // /// cannot be undelegated from by their stakers.
- // /// @param operator is the operator being delegated to.
- // /// @param staker is the staker delegating stake to the operator.
- // function testSlashedOperatorWithdrawal(address operator, address staker, uint96 ethAmount, uint96 eigenAmount)
- // public
- // fuzzedAddress(operator)
- // fuzzedAddress(staker)
- // {
- // cheats.assume(staker != operator);
- // testDelegation(operator, staker, ethAmount, eigenAmount);
-
- // {
- // address slashingContract = slasher.owner();
-
- // cheats.startPrank(operator);
- // slasher.optIntoSlashing(address(slashingContract));
- // cheats.stopPrank();
-
- // cheats.startPrank(slashingContract);
- // slasher.freezeOperator(operator);
- // cheats.stopPrank();
- // }
-
- // (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) =
- // strategyManager.getDeposits(staker);
-
- // uint256[] memory strategyIndexes = new uint256[](2);
- // strategyIndexes[0] = 0;
- // strategyIndexes[1] = 1;
-
- // IERC20[] memory tokensArray = new IERC20[](2);
- // tokensArray[0] = weth;
- // tokensArray[0] = eigenToken;
-
- // //initiating queued withdrawal
- // cheats.expectRevert(
- // bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing")
- // );
- // _testQueueWithdrawal(staker, strategyIndexes, updatedStrategies, updatedShares, staker);
- // }
-
// Helper function to begin a delegation
function _initiateDelegation(
address operator,
diff --git a/src/test/events/IAVSDirectoryEvents.sol b/src/test/events/IAVSDirectoryEvents.sol
new file mode 100644
index 000000000..ff344d994
--- /dev/null
+++ b/src/test/events/IAVSDirectoryEvents.sol
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "src/contracts/interfaces/IAVSDirectory.sol";
+
+interface IAVSDirectoryEvents {
+ /**
+ * @notice Emitted when @param avs indicates that they are updating their MetadataURI string
+ * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing
+ */
+ event AVSMetadataURIUpdated(address indexed avs, string metadataURI);
+
+ /// @notice Emitted when an operator's registration status for an AVS is updated
+ event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, IAVSDirectory.OperatorAVSRegistrationStatus status);
+}
\ No newline at end of file
diff --git a/src/test/events/IDelegationManagerEvents.sol b/src/test/events/IDelegationManagerEvents.sol
index 793d1e729..4654d722f 100644
--- a/src/test/events/IDelegationManagerEvents.sol
+++ b/src/test/events/IDelegationManagerEvents.sol
@@ -59,7 +59,6 @@ interface IDelegationManagerEvents {
/// @notice Emitted when a queued withdrawal is *migrated* from the StrategyManager to the DelegationManager
event WithdrawalMigrated(bytes32 oldWithdrawalRoot, bytes32 newWithdrawalRoot);
- /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
- event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
-
+ /// @notice Emitted when the `strategyWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
+ event StrategyWithdrawalDelayBlocksSet(IStrategy strategy, uint256 previousValue, uint256 newValue);
}
\ No newline at end of file
diff --git a/src/test/events/IEigenPodManagerEvents.sol b/src/test/events/IEigenPodManagerEvents.sol
index 9b2eb7386..2f9796684 100644
--- a/src/test/events/IEigenPodManagerEvents.sol
+++ b/src/test/events/IEigenPodManagerEvents.sol
@@ -5,9 +5,16 @@ interface IEigenPodManagerEvents {
/// @notice Emitted to notify the update of the beaconChainOracle address
event BeaconOracleUpdated(address indexed newOracleAddress);
+ /// @notice Emitted to notify that the denebForkTimestamp has been set
+ event DenebForkTimestampUpdated(uint64 denebForkTimestamp);
+
+
/// @notice Emitted to notify the deployment of an EigenPod
event PodDeployed(address indexed eigenPod, address indexed podOwner);
/// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue`
event MaxPodsUpdated(uint256 previousValue, uint256 newValue);
+
+ /// @notice Emitted when the balance of an EigenPod is updated
+ event PodSharesUpdated(address indexed podOwner, int256 sharesDelta);
}
\ No newline at end of file
diff --git a/src/test/events/IStrategyManagerEvents.sol b/src/test/events/IStrategyManagerEvents.sol
new file mode 100644
index 000000000..e5a83effb
--- /dev/null
+++ b/src/test/events/IStrategyManagerEvents.sol
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "src/contracts/interfaces/IStrategyManager.sol";
+
+interface IStrategyManagerEvents {
+ /**
+ * @notice Emitted when a new deposit occurs on behalf of `depositor`.
+ * @param depositor Is the staker who is depositing funds into EigenLayer.
+ * @param strategy Is the strategy that `depositor` has deposited into.
+ * @param token Is the token that `depositor` deposited.
+ * @param shares Is the number of new shares `depositor` has been granted in `strategy`.
+ */
+ event Deposit(address depositor, IERC20 token, IStrategy strategy, uint256 shares);
+
+ /**
+ * @notice Emitted when a new withdrawal occurs on behalf of `depositor`.
+ * @param depositor Is the staker who is queuing a withdrawal from EigenLayer.
+ * @param nonce Is the withdrawal's unique identifier (to the depositor).
+ * @param strategy Is the strategy that `depositor` has queued to withdraw from.
+ * @param shares Is the number of shares `depositor` has queued to withdraw.
+ */
+ event ShareWithdrawalQueued(address depositor, uint96 nonce, IStrategy strategy, uint256 shares);
+
+ /**
+ * @notice Emitted when a new withdrawal is queued by `depositor`.
+ * @param depositor Is the staker who is withdrawing funds from EigenLayer.
+ * @param nonce Is the withdrawal's unique identifier (to the depositor).
+ * @param withdrawer Is the party specified by `staker` who will be able to complete the queued withdrawal and receive the withdrawn funds.
+ * @param delegatedAddress Is the party who the `staker` was delegated to at the time of creating the queued withdrawal
+ * @param withdrawalRoot Is a hash of the input data for the withdrawal.
+ */
+ event WithdrawalQueued(
+ address depositor,
+ uint96 nonce,
+ address withdrawer,
+ address delegatedAddress,
+ bytes32 withdrawalRoot
+ );
+
+ /// @notice Emitted when a queued withdrawal is completed
+ event WithdrawalCompleted(
+ address indexed depositor,
+ uint96 nonce,
+ address indexed withdrawer,
+ bytes32 withdrawalRoot
+ );
+
+ /// @notice Emitted when `thirdPartyTransfersForbidden` is updated for a strategy and value by the owner
+ event UpdatedThirdPartyTransfersForbidden(IStrategy strategy, bool value);
+
+ /// @notice Emitted when the `strategyWhitelister` is changed
+ event StrategyWhitelisterChanged(address previousAddress, address newAddress);
+
+ /// @notice Emitted when a strategy is added to the approved list of strategies for deposit
+ event StrategyAddedToDepositWhitelist(IStrategy strategy);
+
+ /// @notice Emitted when a strategy is removed from the approved list of strategies for deposit
+ event StrategyRemovedFromDepositWhitelist(IStrategy strategy);
+
+ /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
+ event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
+}
\ No newline at end of file
diff --git a/src/test/harnesses/EigenPodHarness.sol b/src/test/harnesses/EigenPodHarness.sol
index 1f9546d76..3cdd6409c 100644
--- a/src/test/harnesses/EigenPodHarness.sol
+++ b/src/test/harnesses/EigenPodHarness.sol
@@ -2,8 +2,9 @@
pragma solidity =0.8.12;
import "../../contracts/pods/EigenPod.sol";
+import "forge-std/Test.sol";
-contract EPInternalFunctions is EigenPod {
+contract EPInternalFunctions is EigenPod, Test {
constructor(
IETHPOSDeposit _ethPOS,
diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol
index 5b55e851c..3d82fad03 100644
--- a/src/test/integration/IntegrationBase.t.sol
+++ b/src/test/integration/IntegrationBase.t.sol
@@ -444,7 +444,7 @@ abstract contract IntegrationBase is IntegrationDeployer {
function assert_Snap_Added_QueuedWithdrawal(
User staker,
- IDelegationManager.Withdrawal memory withdrawal,
+ IDelegationManager.Withdrawal memory /*withdrawal*/,
string memory err
) internal {
uint curQueuedWithdrawal = _getCumulativeWithdrawals(staker);
@@ -669,6 +669,19 @@ abstract contract IntegrationBase is IntegrationDeployer {
timeMachine.warpToPresent(curState);
}
+ /// @dev Given a list of strategies, roll the block number forward to the
+ /// a valid blocknumber to completeWithdrawals
+ function _rollBlocksForCompleteWithdrawals(IStrategy[] memory strategies) internal {
+ // uint256 blocksToRoll = delegationManager.minWithdrawalDelayBlocks();
+ // for (uint i = 0; i < strategies.length; i++) {
+ // uint256 withdrawalDelayBlocks = delegationManager.strategyWithdrawalDelayBlocks(strategies[i]);
+ // if (withdrawalDelayBlocks > blocksToRoll) {
+ // blocksToRoll = withdrawalDelayBlocks;
+ // }
+ // }
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(strategies));
+ }
+
/// @dev Uses timewarp modifier to get operator shares at the last snapshot
function _getPrevOperatorShares(
User operator,
diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol
index cd90b633b..0687a7089 100644
--- a/src/test/integration/IntegrationDeployer.t.sol
+++ b/src/test/integration/IntegrationDeployer.t.sol
@@ -190,6 +190,8 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {
// Third, upgrade the proxy contracts to point to the implementations
uint256 withdrawalDelayBlocks = 7 days / 12 seconds;
+ IStrategy[] memory initializeStrategiesToSetDelayBlocks = new IStrategy[](0);
+ uint256[] memory initializeWithdrawalDelayBlocks = new uint256[](0);
// DelegationManager
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delegationManager))),
@@ -199,7 +201,9 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {
eigenLayerReputedMultisig, // initialOwner
pauserRegistry,
0 /* initialPausedStatus */,
- withdrawalDelayBlocks
+ withdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
// StrategyManager
@@ -268,6 +272,11 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {
// Create mock beacon chain / proof gen interface
beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle);
+
+
+
+ //set deneb fork timestamp
+ eigenPodManager.setDenebForkTimestamp(type(uint64).max);
}
/// @dev Deploy a strategy and its underlying token, push to global lists of tokens/strategies, and whitelist
@@ -286,9 +295,10 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {
// Whitelist strategy
IStrategy[] memory strategies = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
strategies[0] = strategy;
cheats.prank(strategyManager.strategyWhitelister());
- strategyManager.addStrategiesToDepositWhitelist(strategies);
+ strategyManager.addStrategiesToDepositWhitelist(strategies, _thirdPartyTransfersForbiddenValues);
// Add to lstStrats and allStrats
lstStrats.push(strategy);
diff --git a/src/test/integration/User.t.sol b/src/test/integration/User.t.sol
index c8f0bb880..447fcb7b2 100644
--- a/src/test/integration/User.t.sol
+++ b/src/test/integration/User.t.sol
@@ -325,7 +325,7 @@ contract User is Test {
/// @notice Gets the expected withdrawals to be created when the staker is undelegated via a call to `DelegationManager.undelegate()`
/// @notice Assumes staker and withdrawer are the same and that all strategies and shares are withdrawn
- function _getExpectedWithdrawalStructsForStaker(address staker) internal returns (IDelegationManager.Withdrawal[] memory) {
+ function _getExpectedWithdrawalStructsForStaker(address staker) internal view returns (IDelegationManager.Withdrawal[] memory) {
(IStrategy[] memory strategies, uint256[] memory shares)
= delegationManager.getDelegatableShares(staker);
@@ -432,7 +432,7 @@ contract User_AltMethods is User {
// Get signature
uint256 nonceBefore = strategyManager.nonces(address(this));
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strat, underlyingToken, tokenBalance, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), address(this), strat, underlyingToken, tokenBalance, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
bytes memory signature = bytes(abi.encodePacked(digestHash)); // dummy sig data
diff --git a/src/test/integration/mocks/BeaconChainMock.t.sol b/src/test/integration/mocks/BeaconChainMock.t.sol
index b7a121a2f..c0f7b3770 100644
--- a/src/test/integration/mocks/BeaconChainMock.t.sol
+++ b/src/test/integration/mocks/BeaconChainMock.t.sol
@@ -694,10 +694,16 @@ contract BeaconChainMock is Test {
(BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1) + BeaconChainProofs.BEACON_STATE_FIELD_TREE_HEIGHT
);
- uint immutable WITHDRAWAL_PROOF_LEN = 32 * (
- BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT +
+ uint immutable WITHDRAWAL_PROOF_LEN_CAPELLA = 32 * (
+ BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA +
BeaconChainProofs.WITHDRAWALS_TREE_HEIGHT + 1
);
+
+ uint immutable WITHDRAWAL_PROOF_LEN_DENEB= 32 * (
+ BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB +
+ BeaconChainProofs.WITHDRAWALS_TREE_HEIGHT + 1
+ );
+
uint immutable EXECPAYLOAD_PROOF_LEN = 32 * (
BeaconChainProofs.BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT +
BeaconChainProofs.BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT
@@ -705,8 +711,11 @@ contract BeaconChainMock is Test {
uint immutable SLOT_PROOF_LEN = 32 * (
BeaconChainProofs.BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT
);
- uint immutable TIMESTAMP_PROOF_LEN = 32 * (
- BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT
+ uint immutable TIMESTAMP_PROOF_LEN_CAPELLA = 32 * (
+ BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA
+ );
+ uint immutable TIMESTAMP_PROOF_LEN_DENEB = 32 * (
+ BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB
);
uint immutable HISTSUMMARY_PROOF_LEN = 32 * (
BeaconChainProofs.BEACON_STATE_FIELD_TREE_HEIGHT +
@@ -725,10 +734,10 @@ contract BeaconChainMock is Test {
uint64 oracleTimestamp
) internal view returns (BeaconChainProofs.WithdrawalProof memory) {
return BeaconChainProofs.WithdrawalProof({
- withdrawalProof: new bytes(WITHDRAWAL_PROOF_LEN),
+ withdrawalProof: new bytes(WITHDRAWAL_PROOF_LEN_CAPELLA),
slotProof: new bytes(SLOT_PROOF_LEN),
executionPayloadProof: new bytes(EXECPAYLOAD_PROOF_LEN),
- timestampProof: new bytes(TIMESTAMP_PROOF_LEN),
+ timestampProof: new bytes(TIMESTAMP_PROOF_LEN_CAPELLA),
historicalSummaryBlockRootProof: new bytes(HISTSUMMARY_PROOF_LEN),
blockRootIndex: 0,
historicalSummaryIndex: 0,
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 5b0070d04..4c7149fbb 100644
--- a/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol
+++ b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol
@@ -36,7 +36,7 @@ contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils {
check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
// 4. Complete Queued Withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, shares);
@@ -73,7 +73,7 @@ contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils {
check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
// 4. Complete Queued Withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
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 ff19137d5..3c6afde3e 100644
--- a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol
+++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol
@@ -54,7 +54,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils {
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; i++) {
uint256[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
@@ -113,7 +113,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils {
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; i++) {
staker.completeWithdrawalAsShares(withdrawals[i]);
@@ -181,7 +181,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils {
// 4. Complete withdrawals
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; i++) {
uint256[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
@@ -243,7 +243,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils {
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
staker.completeWithdrawalAsShares(withdrawals[i]);
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 75a57af75..43a303ef7 100644
--- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol
+++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol
@@ -54,7 +54,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal as shares
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares);
@@ -72,7 +72,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 7. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete withdrawals
for (uint i = 0; i < withdrawals.length; i++) {
@@ -123,7 +123,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal as shares
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares);
@@ -141,7 +141,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 7. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete all but last withdrawal as tokens
for (uint i = 0; i < withdrawals.length - 1; i++) {
@@ -151,9 +151,17 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
}
// Complete last withdrawal as shares
- IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[withdrawals.length - 1]);
- uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
- check_Withdrawal_AsTokens_State(staker, operator2, withdrawals[withdrawals.length - 1], strategies, shares, tokens, expectedTokens);
+ IERC20[] memory finalWithdrawaltokens = staker.completeWithdrawalAsTokens(withdrawals[withdrawals.length - 1]);
+ uint[] memory finalExpectedTokens = _calculateExpectedTokens(strategies, shares);
+ check_Withdrawal_AsTokens_State(
+ staker,
+ operator2,
+ withdrawals[withdrawals.length - 1],
+ strategies,
+ shares,
+ finalWithdrawaltokens,
+ finalExpectedTokens
+ );
}
function testFuzz_deposit_delegate_reDelegate_depositAfterRedelegate(uint24 _random) public {
@@ -207,7 +215,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal as shares
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares);
@@ -232,7 +240,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 8. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete withdrawals
for (uint i = 0; i < newWithdrawals.length; i++) {
@@ -294,7 +302,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal as shares
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares);
@@ -319,7 +327,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 8. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete withdrawals
for (uint i = 0; i < newWithdrawals.length; i++) {
@@ -366,7 +374,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal as tokens
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
@@ -389,7 +397,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 8. Complete withdrawal as shares
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete withdrawals as tokens
for (uint i = 0; i < withdrawals.length; i++) {
@@ -435,7 +443,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal as Tokens
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
@@ -458,7 +466,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti
// 8. Complete withdrawal as shares
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete withdrawals as shares
for (uint i = 0; i < withdrawals.length; i++) {
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 b2f0dc1fb..4332935d1 100644
--- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol
+++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol
@@ -51,7 +51,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// Complete withdrawal
for (uint256 i = 0; i < withdrawals.length; ++i) {
@@ -111,7 +111,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
staker.completeWithdrawalAsShares(withdrawals[i]);
@@ -164,7 +164,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
@@ -218,7 +218,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUti
// 4. Complete withdrawal
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint256 i = 0; i < withdrawals.length; ++i) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares);
diff --git a/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol b/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol
index a781ca6f4..ec9977223 100644
--- a/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol
+++ b/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol
@@ -59,7 +59,7 @@ contract Integration_Deposit_Delegate_UpdateBalance is IntegrationCheckUtils {
assert_Snap_Delta_OperatorShares(operator, strategies, operatorShareDeltas, "operator should have applied deltas correctly");
// Fast forward to when we can complete the withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
// 5. Complete queued withdrawals as tokens
staker.completeWithdrawalsAsTokens(withdrawals);
diff --git a/src/test/integration/tests/Deposit_Queue_Complete.t.sol b/src/test/integration/tests/Deposit_Queue_Complete.t.sol
index 4de9ea7ad..5689b8676 100644
--- a/src/test/integration/tests/Deposit_Queue_Complete.t.sol
+++ b/src/test/integration/tests/Deposit_Queue_Complete.t.sol
@@ -33,7 +33,7 @@ contract Integration_Deposit_QueueWithdrawal_Complete is IntegrationCheckUtils {
IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
// 3. Complete Queued Withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
@@ -67,7 +67,7 @@ contract Integration_Deposit_QueueWithdrawal_Complete is IntegrationCheckUtils {
IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
// 3. Complete Queued Withdrawal
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_State(staker, User(payable(0)), withdrawals[i], strategies, shares);
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 b82e2af92..cd93188e4 100644
--- a/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol
+++ b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol
@@ -31,7 +31,7 @@ contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationChe
check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawals, withdrawalRoots);
// 4. Complete Queued Withdrawal as Shares
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
staker.completeWithdrawalAsShares(withdrawals[i]);
check_Withdrawal_AsShares_State(staker, staker, withdrawals[i], strategies, shares);
@@ -64,7 +64,7 @@ contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationChe
check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawals, withdrawalRoots);
// 4. Complete Queued Withdrawal as Tokens
- cheats.roll(block.number + delegationManager.withdrawalDelayBlocks());
+ _rollBlocksForCompleteWithdrawals(strategies);
for (uint i = 0; i < withdrawals.length; i++) {
IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
diff --git a/src/test/mocks/DelegationManagerMock.sol b/src/test/mocks/DelegationManagerMock.sol
index 8f9b9ec2d..a55dbd50b 100644
--- a/src/test/mocks/DelegationManagerMock.sol
+++ b/src/test/mocks/DelegationManagerMock.sol
@@ -75,7 +75,24 @@ contract DelegationManagerMock is IDelegationManager, Test {
return 0;
}
- function withdrawalDelayBlocks() external pure returns (uint256) {
+ function minWithdrawalDelayBlocks() external pure returns (uint256) {
+ return 0;
+ }
+
+ /**
+ * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. 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 strategyWithdrawalDelayBlocks(IStrategy /*strategy*/) external pure returns (uint256) {
+ return 0;
+ }
+
+ function getOperatorShares(
+ address operator,
+ IStrategy[] memory strategies
+ ) external view returns (uint256[] memory) {}
+
+ function getWithdrawalDelay(IStrategy[] calldata /*strategies*/) public pure returns (uint256) {
return 0;
}
@@ -156,10 +173,11 @@ contract DelegationManagerMock is IDelegationManager, Test {
function addShares(
IStrategyManager strategyManager,
address staker,
+ IERC20 token,
IStrategy strategy,
uint256 shares
) external {
- strategyManager.addShares(staker, strategy, shares);
+ strategyManager.addShares(staker, token, strategy, shares);
}
function removeShares(
diff --git a/src/test/mocks/ERC20Mock.sol b/src/test/mocks/ERC20Mock.sol
index d1e119846..ac55a0173 100644
--- a/src/test/mocks/ERC20Mock.sol
+++ b/src/test/mocks/ERC20Mock.sol
@@ -82,6 +82,11 @@ contract ERC20Mock is Context, IERC20 {
return _allowances[owner][spender];
}
+ function mint(address /*to*/, uint256 amount) public virtual {
+ address owner = _msgSender();
+ _mint(owner, amount);
+ }
+
/**
* @dev See {IERC20-approve}.
*
diff --git a/src/test/mocks/EigenPodManagerMock.sol b/src/test/mocks/EigenPodManagerMock.sol
index f06730112..e3c2f0e17 100644
--- a/src/test/mocks/EigenPodManagerMock.sol
+++ b/src/test/mocks/EigenPodManagerMock.sol
@@ -85,4 +85,11 @@ contract EigenPodManagerMock is IEigenPodManager, Test {
function numPods() external view returns (uint256) {}
function maxPods() external view returns (uint256) {}
+
+
+ function denebForkTimestamp() external pure returns (uint64) {
+ return type(uint64).max;
+ }
+
+ function setDenebForkTimestamp(uint64 timestamp) external{}
}
\ No newline at end of file
diff --git a/src/test/mocks/StrategyManagerMock.sol b/src/test/mocks/StrategyManagerMock.sol
index 6782a044d..989512b98 100644
--- a/src/test/mocks/StrategyManagerMock.sol
+++ b/src/test/mocks/StrategyManagerMock.sol
@@ -32,6 +32,8 @@ contract StrategyManagerMock is
/// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated. only increments (doesn't decrement)
mapping(address => uint256) public cumulativeWithdrawalsQueued;
+
+ mapping(IStrategy => bool) public thirdPartyTransfersForbidden;
function setAddresses(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher) external
{
@@ -77,6 +79,11 @@ contract StrategyManagerMock is
sharesToReturn[staker] = _sharesToReturn;
}
+ function setThirdPartyTransfersForbidden(IStrategy strategy, bool value) external {
+ emit UpdatedThirdPartyTransfersForbidden(strategy, value);
+ thirdPartyTransfersForbidden[strategy] = value;
+ }
+
/**
* @notice Get all details on the staker's deposits and corresponding shares
* @return (staker's strategies, shares in these strategies)
@@ -100,7 +107,7 @@ contract StrategyManagerMock is
function removeShares(address staker, IStrategy strategy, uint256 shares) external {}
- function addShares(address staker, IStrategy strategy, uint256 shares) external {}
+ function addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) external {}
function withdrawSharesAsTokens(address recipient, IStrategy strategy, uint256 shares, IERC20 token) external {}
@@ -109,7 +116,10 @@ contract StrategyManagerMock is
// function withdrawalDelayBlocks() external view returns (uint256) {}
- function addStrategiesToDepositWhitelist(IStrategy[] calldata /*strategiesToWhitelist*/) external pure {}
+ function addStrategiesToDepositWhitelist(
+ IStrategy[] calldata /*strategiesToWhitelist*/,
+ bool[] calldata /*thirdPartyTransfersForbiddenValues*/
+ ) external pure {}
function removeStrategiesFromDepositWhitelist(IStrategy[] calldata /*strategiesToRemoveFromWhitelist*/) external pure {}
diff --git a/src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json b/src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json
new file mode 100644
index 000000000..6bd773af9
--- /dev/null
+++ b/src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json
@@ -0,0 +1,160 @@
+{
+ "slot": 6397852,
+ "validatorIndex": 302913,
+ "historicalSummaryIndex": 146,
+ "withdrawalIndex": 0,
+ "blockHeaderRootIndex": 8092,
+ "beaconStateRoot": "0x426cc7e4b6a9be3a44e671c99eb62f27dd956d5db33aa8f05d07c3e8b05cb38f",
+ "slotRoot": "0x9c9f610000000000000000000000000000000000000000000000000000000000",
+ "timestampRoot": "0xb06fed6400000000000000000000000000000000000000000000000000000000",
+ "blockHeaderRoot": "0x8b036996f94e940c80c5c692fd0e25467a5d55f1cf92b7808f92090fc7be1d17",
+ "blockBodyRoot": "0x542df356d51eb305cff8282abe6909504b8c6d7bd4598b43aa7d54be13e44e9c",
+ "executionPayloadRoot": "0xe628472355543b53917635e60c1f924f111f7a3cd58f2d947e8631b9d9924cb1",
+ "latestBlockHeaderRoot": "0xb00368eaa2de6ca1e83d610be190a397668215a337837e1ad23241373d1c2dd0",
+ "SlotProof": [
+ "0x89c5010000000000000000000000000000000000000000000000000000000000",
+ "0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2",
+ "0xf4e65df697eb85f3ab176ac93b6ad4d96bd6b04bdddcc4f6c98f0fa94effc553"
+ ],
+ "WithdrawalProof": [
+ "0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f",
+ "0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e",
+ "0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6",
+ "0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8",
+ "0x1000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92",
+ "0xac5e32ea973e990d191039e91c7d9fd9830599b8208675f159c3df128228e729",
+ "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"
+ ],
+ "ValidatorProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0xdfe7aa4a49359b80e66fbdc8cbbf5d2014868aaf8dee25848f1b6494cb56231f",
+ "0xeec50554b6994496920a265643520c78ff05beec8015c6432f72b0cac5af510c",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x9c797e45839da75fea2190bf7a2735e68d8e4bd3cdbc82498491debb3acce128",
+ "0x2c693b02fbff3f4f659bfb8414a9ba27bbfdc1baf65317f05a84a85552628bd6",
+ "0x6f853a43c11ab3ac27f5ea8dd9e6dc825216114dee8e5db2306b97723048daaa",
+ "0x620fe1c260fcea9fb7ca0b7cd64f88aa906c88a8efc136317d88483f8a99d5ae",
+ "0xccfc09edfaa6bb8f9a1db781f1104b3aeb2a45799c898df57f3610b9ffc255de",
+ "0x8c5a00d96b9eb727a5a4aec2fd6b9771cb0c5be3a8b5588aff54b2ee36792af2",
+ "0x48fc48699053f4bd8f986f4216e2728f11e0d53ebeaf13bc9d287d2e099e7196",
+ "0xac88ce300b12047d9131a651417b624f206b742080c28c347873a7706897b379",
+ "0xe373b48074ce47d30b57509e85e905e42e8dbc869bb7c436553326a7a65e22ec",
+ "0x358c6950cfb1fb035e1e2506bddf5a1dc1f87d699a464a7eb05b87ce699942ce",
+ "0x040e77e06c4d45802b2093e445e56a1ed5d5afbd1c3393806b006b7f40a17148",
+ "0xcfb3f924f2e8810a349041676060bbf55113cbc00270f778723dbac055c8ba2b",
+ "0xa4b4bda96e8ae5a441b128b339ed3776f6105d24fcaa288cad2a3153e430a9ea",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x0b430a0000000000000000000000000000000000000000000000000000000000",
+ "0x3b4f070000000000000000000000000000000000000000000000000000000000",
+ "0x7e71eb9ab70a9ac86179e04cc156a3a6efd5d49e864b1def60d045fe88ae53db",
+ "0x042f04ac64635d44bd43c48c5504689a119901947ed7cfca6ce6f7171d29b696",
+ "0x560c8c92a1425b1c928f582f64de643e17290760d4ecb242afb53b62d51ea918",
+ "0xf426ceebf070d088972f46060f72b9fa35ebb90e8d18af341b698d359ab8366f"
+ ],
+ "TimestampProof": [
+ "0x28a2c80000000000000000000000000000000000000000000000000000000000",
+ "0xa749df3368741198702798435eea361b1b1946aa9456587a2be377c8472ea2df",
+ "0xb1c03097a5a24f46cdb684df37e3ac0536a76428be1a6c6d6450378701ab1f3d",
+ "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"
+ ],
+ "ExecutionPayloadProof": [
+ "0xb6a435ffd17014d1dad214ba466aaa7fba5aa247945d2c29fd53e90d554f4474",
+ "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0x5ec9aaf0a3571602f4704d4471b9af564caf17e4d22a7c31017293cb95949053",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xc1f7cb289e44e9f711d7d7c05b67b84b8c6dd0394b688922a490cfd3fe216db1"
+ ],
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x0100000000000000000000008e35f095545c56b07c942a4f3b055ef1ec4cb148",
+ "0x0040597307000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "WithdrawalFields": [
+ "0x45cee50000000000000000000000000000000000000000000000000000000000",
+ "0x419f040000000000000000000000000000000000000000000000000000000000",
+ "0x59b0d71688da01057c08e4c1baa8faa629819c2a000000000000000000000000",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0xc055061b6674f3bac542b141b441b476130d17dacc3e61d6ce01a6b8528f7de7",
+ "0xfd8af94401badce5dd4588a22c605e197a51b17e696f761aed06819a98645f03",
+ "0xdeb9a60cdd0908457ff962da3895ffcf1a1e479b633e3427ac9b1166e006c8f7"
+ ],
+ "HistoricalSummaryProof": [
+ "0x96d85b451bb1df5314fd3fd3fa4caa3285796905bc8bba6eee5e3e1539cf2695",
+ "0x354f5aaff1cde08c6b8a7ef8610abf62a0b50339d0dd26b0152699ebc2bdf785",
+ "0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529",
+ "0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a",
+ "0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2",
+ "0x9d95eefbaff153d6ad9050327e21a254750d80ff89315ab4056574275ce2a751",
+ "0x20cdc9ec1006e8bc27ab3e0a9c88a8e14c8a9da2470f8eaf673ee67abcfd0342",
+ "0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e",
+ "0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f",
+ "0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15",
+ "0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3",
+ "0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf",
+ "0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8",
+ "0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be",
+ "0x4ee09be5d00612fc8807752842f0839656107b50699330ecf09825466576a8e5",
+ "0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8",
+ "0xc9394eeb778485920ae0bc45a6f0e5f1b43f3aeadc24b24e7a655582aa87aede",
+ "0xfc3f3efb849bf652aa632502c004af0f78dfe1a75902f33bbce3ca3d1cc3bfea",
+ "0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f",
+ "0x4644f74cc7fedf973ef015906df9ba97729b04395a70ff170bee8c40ffed8f0f",
+ "0xb5202e0c2d48a246c5b45e30d3bf0a89f3d3ea5e4591db5ec39efc1ed1e0a2a2",
+ "0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7",
+ "0x72cf6a5869e83ea39846cc892946c8a5e6bf3191df19ae922664504e4cf38c6b",
+ "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
+ "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
+ "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
+ "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
+ "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
+ "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
+ "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x1101000000000000000000000000000000000000000000000000000000000000",
+ "0xcbe0080000000000000000000000000000000000000000000000000000000000",
+ "0x8ff2572846d80ce4be83e1638164331c30cd7eadb3488f00ba2507c072929e3a",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0xa6c60794743172c4a55a9fcee4a7832e5ef7b31aac1b9489b5baa1db78df5c60",
+ "0x1c8017e95f68e52210618f3802699b49a2a13a40257e97b74ee864824a73a280"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/fullWithdrawalDeneb.json b/src/test/test-data/fullWithdrawalDeneb.json
new file mode 100644
index 000000000..b7d6dc53f
--- /dev/null
+++ b/src/test/test-data/fullWithdrawalDeneb.json
@@ -0,0 +1,162 @@
+{
+ "slot": 7421951,
+ "validatorIndex": 0,
+ "historicalSummaryIndex": 271,
+ "withdrawalIndex": 0,
+ "blockHeaderRootIndex": 8191,
+ "beaconStateRoot": "0xaf91e832d495c6d0e877bdf61b2bb6614621addb789ab492ceff9eef1696a64b",
+ "slotRoot": "0xff3f710000000000000000000000000000000000000000000000000000000000",
+ "timestampRoot": "0x54f4a86500000000000000000000000000000000000000000000000000000000",
+ "blockHeaderRoot": "0x68d267d7566c4829a1560fea6cce668f8fbf2e126e59e2556288bcbff92b64f2",
+ "blockBodyRoot": "0x88a28082491645022ce8d6af49dca10325fc979d179d0135f0f7d0937fbbbfeb",
+ "executionPayloadRoot": "0xc1f6b92b0e40c5cf43de8fe08f68434736dd9d42f7620436df40320f4eb65286",
+ "latestBlockHeaderRoot": "0x5a35a89568f3323481764c70b1bad0880d4d0114f185e43a42c96a8e45fa2a0f",
+ "SlotProof": [
+ "0x8d6a010000000000000000000000000000000000000000000000000000000000",
+ "0x0d4c303f43d35612a043d17114edde94bdc94ee369b761067bb85bd347c94c4c",
+ "0x8dbd7ba3acb83e5e9a00d908e8d05e0dc99569d2135d24affc44e325b0f7911d"
+ ],
+ "WithdrawalProof": [
+ "0x3effc719333b3a5052a34bd1ed124dce49445905dbe18af5aa947bfe25a94dd8",
+ "0xf8470ba001831654956a6f12a8ffd6a1f1004e08557268d86477945cd3989531",
+ "0x55f96eba696026f9d8389019bf3c2f61ab741f968c01744540b170a4fb0f25a4",
+ "0x47ab534e81180bcf81d1ef541132b84826f1f34e2a0fde736a313a6ed5557459",
+ "0x1000000000000000000000000000000000000000000000000000000000000000",
+ "0x00000a0000000000000000000000000000000000000000000000000000000000",
+ "0x5c1bb5d0e2afe397c9fb9e275aa97209ba4c01e13d181e66311b42aed62559f7",
+ "0x058ad237cbc009d8b6f426aaa40709e508753fa90c6858e147e1c1066127dc69",
+ "0x4750fb0e389da83ea89697969498c02f840a2b21c7ece905e0f284f7e5b179c4",
+ "0xd2252e6aa60b6dbca15826f459db1e89ec584c2a2aa89bccd1715b9942633e00"
+ ],
+ "ValidatorProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0xdfe7aa4a49359b80e66fbdc8cbbf5d2014868aaf8dee25848f1b6494cb56231f",
+ "0xeec50554b6994496920a265643520c78ff05beec8015c6432f72b0cac5af510c",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x9c797e45839da75fea2190bf7a2735e68d8e4bd3cdbc82498491debb3acce128",
+ "0x2c693b02fbff3f4f659bfb8414a9ba27bbfdc1baf65317f05a84a85552628bd6",
+ "0x6f853a43c11ab3ac27f5ea8dd9e6dc825216114dee8e5db2306b97723048daaa",
+ "0x620fe1c260fcea9fb7ca0b7cd64f88aa906c88a8efc136317d88483f8a99d5ae",
+ "0xccfc09edfaa6bb8f9a1db781f1104b3aeb2a45799c898df57f3610b9ffc255de",
+ "0x8c5a00d96b9eb727a5a4aec2fd6b9771cb0c5be3a8b5588aff54b2ee36792af2",
+ "0x48fc48699053f4bd8f986f4216e2728f11e0d53ebeaf13bc9d287d2e099e7196",
+ "0xac88ce300b12047d9131a651417b624f206b742080c28c347873a7706897b379",
+ "0xe373b48074ce47d30b57509e85e905e42e8dbc869bb7c436553326a7a65e22ec",
+ "0x358c6950cfb1fb035e1e2506bddf5a1dc1f87d699a464a7eb05b87ce699942ce",
+ "0x040e77e06c4d45802b2093e445e56a1ed5d5afbd1c3393806b006b7f40a17148",
+ "0xcfb3f924f2e8810a349041676060bbf55113cbc00270f778723dbac055c8ba2b",
+ "0xa4b4bda96e8ae5a441b128b339ed3776f6105d24fcaa288cad2a3153e430a9ea",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x0b430a0000000000000000000000000000000000000000000000000000000000",
+ "0x3b4f070000000000000000000000000000000000000000000000000000000000",
+ "0x7e71eb9ab70a9ac86179e04cc156a3a6efd5d49e864b1def60d045fe88ae53db",
+ "0x042f04ac64635d44bd43c48c5504689a119901947ed7cfca6ce6f7171d29b696",
+ "0x560c8c92a1425b1c928f582f64de643e17290760d4ecb242afb53b62d51ea918",
+ "0xfe7b7941293bb1a833e6be99db3175a8ab0af7e44f42d0c9dcdf34ae916db490"
+ ],
+ "TimestampProof": [
+ "0x79958c0000000000000000000000000000000000000000000000000000000000",
+ "0xb0949007c306f2de2257c598d935ca16be928532e866698c2561bf4cf1e08b6f",
+ "0x11b7c6b7b01e2a21a682cf18e431dc78efa32300bfb5eba5374420f11cbcb751",
+ "0x4750fb0e389da83ea89697969498c02f840a2b21c7ece905e0f284f7e5b179c4",
+ "0xd2252e6aa60b6dbca15826f459db1e89ec584c2a2aa89bccd1715b9942633e00"
+ ],
+ "ExecutionPayloadProof": [
+ "0xe5e633a5ba845ad1ede8be35fed9ea6d37e39d09061398190eac885703ff5cbd",
+ "0x260336bbff9ef0540c4497ed3e946ba0ca2080b668a1bdcb033496e56c40d451",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0xd2dc492d263b7c106c7a5012f6f2c138c28e1cd37962d49a30031c16964f6bb8",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0x5c2aa56042580b615d81c829f006de5e7b2a21fc330119ddc7600a5a28692069"
+ ],
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x0100000000000000000000008e35f095545c56b07c942a4f3b055ef1ec4cb148",
+ "0x0040597307000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "WithdrawalFields": [
+ "0xe599a70100000000000000000000000000000000000000000000000000000000",
+ "0x419f040000000000000000000000000000000000000000000000000000000000",
+ "0xd982a5927741bfd9b8cf16234061d7a592ca2b1c000000000000000000000000",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0xc055061b6674f3bac542b141b441b476130d17dacc3e61d6ce01a6b8528f7de7",
+ "0xfd8af94401badce5dd4588a22c605e197a51b17e696f761aed06819a98645f03",
+ "0xdeb9a60cdd0908457ff962da3895ffcf1a1e479b633e3427ac9b1166e006c8f7"
+ ],
+ "HistoricalSummaryProof": [
+ "0x273d5cec9b06327f6b83f079e34df0678d905713326e4ac2fe3afe8b12d4af22",
+ "0xf39c43458894e29168c729f714c66b3aef24d43ea37c46ece64f736fbcbcb1d1",
+ "0xeca6748ed11dc62dbecc0eaca339af8929bfa9b962149257af7c30cb947ec642",
+ "0x7cd5fc51dbcc6c6666c00e0ddda4f095f848c70fdc6fa378c5eb9b9108e59efd",
+ "0xe6215943dc342defe9fb5c1474eb6f3ab3db8d1e04dd11707394cf026f0bf780",
+ "0x650c78d820aad1d0b31b94e1d15c2e4998aeffd5e3399646809064042e4f923e",
+ "0xefe753d3d111fa3603f090f5f08c2f12ae9324e8fbe2862162fc6c1587a5ab4b",
+ "0x3e62b06efce16432b6495d9d7fb51f0c3c745036e0c77dc5fc78a70e54d64d93",
+ "0x0aae9c557ef9a8c3d7b1dd2788f484b94a9cf04312cf274353e3c19d5beb8541",
+ "0x6d716c0e4864c59df7bc3319eb4ccac269242e9a1634cf04d4c8df8f9b53f4da",
+ "0x1eecd8c195eb8c158d0dd3e6d7303aee05cc9d9fdfe7c9135ac19453ee8d7bed",
+ "0x93b6c13c69ea8e5c921ac4b895db6f8ebc66d3b01daff16a1658f2526eb79ed9",
+ "0x4e0b3c661d827f71fca9f2fedc3a72d9e4907599577b7149123d5328afe091c9",
+ "0xae456e2a1b0f20ebda87d9e3e7f87f7dcc0860aae65c53657a51f876b862f9a9",
+ "0x3f8e2a5e35171009027df8b882d861e09d70a334a0db14b0f3c920fc6e4c4e23",
+ "0x0cad2edea11734fc388afd6bc9b9125be12edd7e4df6f05e2fdc5a622c0138fb",
+ "0x735f927c57108d1de8547c9d49ecdbf8661a481d6374ca6e25a103ea728b1916",
+ "0xf9513c49e7d50b6311372f787ab3ec7a112e384115d340b0d9f74bccb3562c33",
+ "0xd3573d59f23ed8018d754c166d987e60ac4018ed6a0c187e01439c10e449511f",
+ "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
+ "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
+ "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c",
+ "0x1ef5d0b4795711d6aaf89b6eb2e5ca1c8c729ad9acb5b58c2b700a857c3512a0",
+ "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
+ "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
+ "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
+ "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
+ "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
+ "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
+ "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x1101000000000000000000000000000000000000000000000000000000000000",
+ "0xcbe0080000000000000000000000000000000000000000000000000000000000",
+ "0x8ff2572846d80ce4be83e1638164331c30cd7eadb3488f00ba2507c072929e3a",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0xa6c60794743172c4a55a9fcee4a7832e5ef7b31aac1b9489b5baa1db78df5c60",
+ "0x1c8017e95f68e52210618f3802699b49a2a13a40257e97b74ee864824a73a280"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/unit/AVSDirectoryUnit.t.sol b/src/test/unit/AVSDirectoryUnit.t.sol
new file mode 100644
index 000000000..d18b98fe9
--- /dev/null
+++ b/src/test/unit/AVSDirectoryUnit.t.sol
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
+
+import "src/contracts/core/DelegationManager.sol";
+import "src/contracts/core/AVSDirectory.sol";
+
+import "src/test/events/IAVSDirectoryEvents.sol";
+import "src/test/utils/EigenLayerUnitTestSetup.sol";
+
+/**
+ * @notice Unit testing of the AVSDirectory contract. An AVSs' service manager contract will
+ * call this to register an operator with the AVS.
+ * Contracts tested: AVSDirectory
+ * Contracts not mocked: DelegationManager
+ */
+contract AVSDirectoryUnitTests is EigenLayerUnitTestSetup, IAVSDirectoryEvents {
+ // Contract under test
+ AVSDirectory avsDirectory;
+ AVSDirectory avsDirectoryImplementation;
+
+ // Contract dependencies
+ DelegationManager delegationManager;
+ DelegationManager delegationManagerImplementation;
+
+ // Delegation signer
+ uint256 delegationSignerPrivateKey = uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80);
+ uint256 stakerPrivateKey = uint256(123_456_789);
+
+ // empty string reused across many tests
+ string emptyStringForMetadataURI;
+
+ // reused in various tests. in storage to help handle stack-too-deep errors
+ address defaultAVS = address(this);
+
+ uint256 minWithdrawalDelayBlocks = 216_000;
+ IStrategy[] public initializeStrategiesToSetDelayBlocks;
+ uint256[] public initializeWithdrawalDelayBlocks;
+
+ // Index for flag that pauses registering/deregistering for AVSs
+ uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0;
+
+ function setUp() public virtual override {
+ // Setup
+ EigenLayerUnitTestSetup.setUp();
+
+ // Deploy DelegationManager implmentation and proxy
+ initializeStrategiesToSetDelayBlocks = new IStrategy[](0);
+ initializeWithdrawalDelayBlocks = new uint256[](0);
+ delegationManagerImplementation = new DelegationManager(strategyManagerMock, slasherMock, eigenPodManagerMock);
+ delegationManager = DelegationManager(
+ address(
+ new TransparentUpgradeableProxy(
+ address(delegationManagerImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ DelegationManager.initialize.selector,
+ address(this),
+ pauserRegistry,
+ 0, // 0 is initialPausedStatus
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ )
+ )
+ )
+ );
+
+ // Deploy AVSDirectory implmentation and proxy
+ avsDirectoryImplementation = new AVSDirectory(delegationManager);
+ avsDirectory = AVSDirectory(
+ address(
+ new TransparentUpgradeableProxy(
+ address(avsDirectoryImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ AVSDirectory.initialize.selector,
+ address(this),
+ pauserRegistry,
+ 0 // 0 is initialPausedStatus
+ )
+ )
+ )
+ );
+
+ // Exclude delegation manager from fuzzed tests
+ addressIsExcludedFromFuzzedInputs[address(avsDirectory)] = true;
+ }
+
+ /**
+ * INTERNAL / HELPER FUNCTIONS
+ */
+
+ /**
+ * @notice internal function for calculating a signature from the operator corresponding to `_operatorPrivateKey`, delegating them to
+ * the `operator`, and expiring at `expiry`.
+ */
+ function _getOperatorSignature(
+ uint256 _operatorPrivateKey,
+ address operator,
+ address avs,
+ bytes32 salt,
+ uint256 expiry
+ ) internal view returns (ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) {
+ operatorSignature.expiry = expiry;
+ operatorSignature.salt = salt;
+ {
+ bytes32 digestHash = avsDirectory.calculateOperatorAVSRegistrationDigestHash(operator, avs, salt, expiry);
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(_operatorPrivateKey, digestHash);
+ operatorSignature.signature = abi.encodePacked(r, s, v);
+ }
+ return operatorSignature;
+ }
+
+ function _registerOperatorWithBaseDetails(address operator) internal {
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(operator, operatorDetails, emptyStringForMetadataURI);
+ }
+
+ function _registerOperatorWithDelegationApprover(address operator) internal {
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: cheats.addr(delegationSignerPrivateKey),
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(operator, operatorDetails, emptyStringForMetadataURI);
+ }
+
+ function _registerOperatorWith1271DelegationApprover(address operator) internal returns (ERC1271WalletMock) {
+ address delegationSigner = cheats.addr(delegationSignerPrivateKey);
+ /**
+ * deploy a ERC1271WalletMock contract with the `delegationSigner` address as the owner,
+ * so that we can create valid signatures from the `delegationSigner` for the contract to check when called
+ */
+ ERC1271WalletMock wallet = new ERC1271WalletMock(delegationSigner);
+
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(wallet),
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(operator, operatorDetails, emptyStringForMetadataURI);
+
+ return wallet;
+ }
+
+ function _registerOperator(
+ address operator,
+ IDelegationManager.OperatorDetails memory operatorDetails,
+ string memory metadataURI
+ ) internal filterFuzzedAddressInputs(operator) {
+ _filterOperatorDetails(operator, operatorDetails);
+ cheats.prank(operator);
+ delegationManager.registerAsOperator(operatorDetails, metadataURI);
+ }
+
+ function _filterOperatorDetails(
+ address operator,
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) internal view {
+ // filter out zero address since people can't delegate to the zero address and operators are delegated to themselves
+ cheats.assume(operator != address(0));
+ // filter out zero address since people can't set their earningsReceiver address to the zero address (special test case to verify)
+ cheats.assume(operatorDetails.earningsReceiver != address(0));
+ // filter out disallowed stakerOptOutWindowBlocks values
+ cheats.assume(operatorDetails.stakerOptOutWindowBlocks <= delegationManager.MAX_STAKER_OPT_OUT_WINDOW_BLOCKS());
+ }
+}
+
+contract AVSDirectoryUnitTests_operatorAVSRegisterationStatus is AVSDirectoryUnitTests {
+ function test_revert_whenRegisterDeregisterToAVSPaused() public {
+ // set the pausing flag
+ cheats.prank(pauser);
+ avsDirectory.pause(2 ** PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS);
+
+ cheats.expectRevert("Pausable: index is paused");
+ avsDirectory.registerOperatorToAVS(address(0), ISignatureUtils.SignatureWithSaltAndExpiry(abi.encodePacked(""), 0, 0));
+
+ cheats.expectRevert("Pausable: index is paused");
+ avsDirectory.deregisterOperatorFromAVS(address(0));
+ }
+
+ // @notice Tests that an avs who calls `updateAVSMetadataURI` will correctly see an `AVSMetadataURIUpdated` event emitted with their input
+ function testFuzz_UpdateAVSMetadataURI(string memory metadataURI) public {
+ // call `updateAVSMetadataURI` and check for event
+ cheats.expectEmit(true, true, true, true, address(avsDirectory));
+ cheats.prank(defaultAVS);
+ emit AVSMetadataURIUpdated(defaultAVS, metadataURI);
+ avsDirectory.updateAVSMetadataURI(metadataURI);
+ }
+
+ // @notice Verifies an operator registers successfull to avs and see an `OperatorAVSRegistrationStatusUpdated` event emitted
+ function testFuzz_registerOperatorToAVS(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ cheats.expectEmit(true, true, true, true, address(avsDirectory));
+ emit OperatorAVSRegistrationStatusUpdated(
+ operator, defaultAVS, IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED
+ );
+
+ uint256 expiry = type(uint256).max;
+
+ cheats.prank(defaultAVS);
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+ }
+
+ // @notice Verifies an operator registers successfull to avs and see an `OperatorAVSRegistrationStatusUpdated` event emitted
+ function testFuzz_revert_whenOperatorNotRegisteredToEigenLayerYet(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+
+ cheats.prank(defaultAVS);
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ cheats.expectRevert("AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+ }
+
+ // @notice Verifies an operator registers fails when the signature is not from the operator
+ function testFuzz_revert_whenSignatureAddressIsNotOperator(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer");
+ cheats.prank(operator);
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+ }
+
+ // @notice Verifies an operator registers fails when the signature expiry already expires
+ function testFuzz_revert_whenExpiryHasExpired(
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
+ ) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ operatorSignature.expiry = bound(operatorSignature.expiry, 0, block.timestamp - 1);
+
+ cheats.expectRevert("AVSDirectory.registerOperatorToAVS: operator signature expired");
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+ }
+
+ // @notice Verifies an operator registers fails when it's already registered to the avs
+ function testFuzz_revert_whenOperatorAlreadyRegisteredToAVS(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ cheats.startPrank(defaultAVS);
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+
+ cheats.expectRevert("AVSDirectory.registerOperatorToAVS: operator already registered");
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+ cheats.stopPrank();
+ }
+
+ /// @notice Checks that cancelSalt updates the operatorSaltIsSpent mapping correctly
+ function testFuzz_cancelSalt(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ assertFalse(avsDirectory.operatorSaltIsSpent(operator, salt), "bad test setup");
+ assertFalse(avsDirectory.operatorSaltIsSpent(defaultAVS, salt), "bad test setup");
+
+ cheats.prank(operator);
+ avsDirectory.cancelSalt(salt);
+
+ assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt), "salt was not successfully cancelled");
+ assertFalse(avsDirectory.operatorSaltIsSpent(defaultAVS, salt), "salt should only be cancelled for the operator");
+
+ bytes32 newSalt;
+ unchecked { newSalt = bytes32(uint(salt) + 1); }
+
+ assertFalse(salt == newSalt, "bad test setup");
+
+ cheats.prank(operator);
+ avsDirectory.cancelSalt(newSalt);
+
+ assertTrue(avsDirectory.operatorSaltIsSpent(operator, salt), "original salt should still be cancelled");
+ assertTrue(avsDirectory.operatorSaltIsSpent(operator, newSalt), "new salt should be cancelled");
+ }
+
+ /// @notice Verifies that registration fails when the salt has been cancelled via cancelSalt
+ function testFuzz_revert_whenRegisteringWithCancelledSalt(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ cheats.prank(operator);
+ avsDirectory.cancelSalt(salt);
+
+ cheats.expectRevert("AVSDirectory.registerOperatorToAVS: salt already spent");
+ cheats.prank(defaultAVS);
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+ }
+
+ /// @notice Verifies that an operator cannot cancel the same salt twice
+ function testFuzz_revert_whenSaltCancelledTwice(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ cheats.startPrank(operator);
+ avsDirectory.cancelSalt(salt);
+
+ cheats.expectRevert("AVSDirectory.cancelSalt: cannot cancel spent salt");
+ avsDirectory.cancelSalt(salt);
+ cheats.stopPrank();
+ }
+
+ /// @notice Verifies that an operator cannot cancel the same salt twice
+ function testFuzz_revert_whenCancellingSaltUsedToRegister(bytes32 salt) public {
+ address operator = cheats.addr(delegationSignerPrivateKey);
+ assertFalse(delegationManager.isOperator(operator), "bad test setup");
+ _registerOperatorWithBaseDetails(operator);
+
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature =
+ _getOperatorSignature(delegationSignerPrivateKey, operator, defaultAVS, salt, expiry);
+
+ cheats.prank(defaultAVS);
+ avsDirectory.registerOperatorToAVS(operator, operatorSignature);
+
+ cheats.prank(operator);
+ cheats.expectRevert("AVSDirectory.cancelSalt: cannot cancel spent salt");
+ avsDirectory.cancelSalt(salt);
+ }
+}
diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol
index 6cc1c3d73..dffbf2154 100644
--- a/src/test/unit/DelegationUnit.t.sol
+++ b/src/test/unit/DelegationUnit.t.sol
@@ -42,7 +42,10 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
address defaultOperator = address(this);
address defaultAVS = address(this);
- uint256 initializedWithdrawalDelayBlocks = 50400;
+ // 604800 seconds in week / 12 = 50,400 blocks
+ uint256 minWithdrawalDelayBlocks = 50400;
+ IStrategy[] public initializeStrategiesToSetDelayBlocks;
+ uint256[] public initializeWithdrawalDelayBlocks;
IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
@@ -55,7 +58,8 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
// Index for flag that pauses completing existing withdrawals when set.
uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
- uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 50400;
+ // the number of 12-second blocks in 30 days (60 * 60 * 24 * 30 / 12 = 216,000)
+ uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 216000;
/// @notice mappings used to handle duplicate entries in fuzzed address array input
mapping(address => uint256) public totalSharesForStrategyInArray;
@@ -66,6 +70,8 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
EigenLayerUnitTestSetup.setUp();
// Deploy DelegationManager implmentation and proxy
+ initializeStrategiesToSetDelayBlocks = new IStrategy[](0);
+ initializeWithdrawalDelayBlocks = new uint256[](0);
delegationManagerImplementation = new DelegationManager(strategyManagerMock, slasherMock, eigenPodManagerMock);
delegationManager = DelegationManager(
address(
@@ -77,7 +83,9 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
address(this),
pauserRegistry,
0, // 0 is initialPausedStatus
- initializedWithdrawalDelayBlocks
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
)
)
@@ -115,7 +123,9 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
) internal returns (IStrategy[] memory) {
uint256 numStrats = sharesAmounts.length;
IStrategy[] memory strategies = new IStrategy[](numStrats);
+ uint256[] memory withdrawalDelayBlocks = new uint256[](strategies.length);
for (uint8 i = 0; i < numStrats; i++) {
+ withdrawalDelayBlocks[i] = bound(uint256(keccak256(abi.encode(staker, i))), 0, MAX_WITHDRAWAL_DELAY_BLOCKS);
ERC20PresetFixedSupply token = new ERC20PresetFixedSupply(
string(abi.encodePacked("Mock Token ", i)),
string(abi.encodePacked("MOCK", i)),
@@ -132,6 +142,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
)
);
}
+ delegationManager.setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
strategyManagerMock.setDeposits(staker, strategies, sharesAmounts);
return strategies;
}
@@ -181,28 +192,6 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
return stakerSignatureAndExpiry;
}
- /**
- * @notice internal function for calculating a signature from the operator corresponding to `_operatorPrivateKey`, delegating them to
- * the `operator`, and expiring at `expiry`.
- */
- function _getOperatorSignature(
- uint256 _operatorPrivateKey,
- address operator,
- address avs,
- bytes32 salt,
- uint256 expiry
- ) internal view returns (ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) {
- operatorSignature.expiry = expiry;
- operatorSignature.salt = salt;
- {
- bytes32 digestHash = delegationManager.calculateOperatorAVSRegistrationDigestHash(operator, avs, salt, expiry);
- (uint8 v, bytes32 r, bytes32 s) = cheats.sign(_operatorPrivateKey, digestHash);
- operatorSignature.signature = abi.encodePacked(r, s, v);
- }
- return operatorSignature;
- }
-
-
// @notice Assumes operator does not have a delegation approver & staker != approver
function _delegateToOperatorWhoAcceptsAllStakers(address staker, address operator) internal {
ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
@@ -421,7 +410,6 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
*/
function _setUpCompleteQueuedWithdrawalSingleStrat(
address staker,
- address operator,
address withdrawer,
uint256 depositAmount,
uint256 withdrawalAmount
@@ -452,6 +440,45 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
return (withdrawal, tokens, withdrawalRoot);
}
+ /**
+ * Deploy and deposit staker into a single strategy, then set up a queued withdrawal for the staker
+ * Assumptions:
+ * - operator is already a registered operator.
+ * - withdrawalAmount <= depositAmount
+ */
+ function _setUpCompleteQueuedWithdrawalBeaconStrat(
+ address staker,
+ address withdrawer,
+ uint256 depositAmount,
+ uint256 withdrawalAmount
+ ) internal returns (IDelegationManager.Withdrawal memory, IERC20[] memory, bytes32) {
+ uint256[] memory depositAmounts = new uint256[](1);
+ depositAmounts[0] = depositAmount;
+ IStrategy[] memory strategies = new IStrategy[](1);
+ strategies[0] = beaconChainETHStrategy;
+ (
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams,
+ IDelegationManager.Withdrawal memory withdrawal,
+ bytes32 withdrawalRoot
+ ) = _setUpQueueWithdrawalsSingleStrat({
+ staker: staker,
+ withdrawer: withdrawer,
+ strategy: strategies[0],
+ withdrawalAmount: withdrawalAmount
+ });
+
+ cheats.prank(staker);
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+ // Set the current deposits to be the depositAmount - withdrawalAmount
+ uint256[] memory currentAmounts = new uint256[](1);
+ currentAmounts[0] = depositAmount - withdrawalAmount;
+ strategyManagerMock.setDeposits(staker, strategies, currentAmounts);
+
+ IERC20[] memory tokens;
+ // tokens[0] = strategies[0].underlyingToken();
+ return (withdrawal, tokens, withdrawalRoot);
+ }
+
/**
* Deploy and deposit staker into strategies, then set up a queued withdrawal for the staker
* Assumptions:
@@ -460,12 +487,17 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
*/
function _setUpCompleteQueuedWithdrawal(
address staker,
- address operator,
address withdrawer,
uint256[] memory depositAmounts,
uint256[] memory withdrawalAmounts
- ) internal returns (IDelegationManager.Withdrawal memory, bytes32) {
+ ) internal returns (IDelegationManager.Withdrawal memory, IERC20[] memory, bytes32) {
IStrategy[] memory strategies = _deployAndDepositIntoStrategies(staker, depositAmounts);
+
+ IERC20[] memory tokens = new IERC20[](strategies.length);
+ for (uint256 i = 0; i < strategies.length; i++) {
+ tokens[i] = strategies[i].underlyingToken();
+ }
+
(
IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams,
IDelegationManager.Withdrawal memory withdrawal,
@@ -480,7 +512,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
cheats.prank(staker);
delegationManager.queueWithdrawals(queuedWithdrawalParams);
- return (withdrawal, withdrawalRoot);
+ return (withdrawal, tokens, withdrawalRoot);
}
}
@@ -508,15 +540,53 @@ contract DelegationManagerUnitTests_Initialization_Setters is DelegationManagerU
/// @notice Verifies that the DelegationManager cannot be iniitalized multiple times
function test_initialize_revert_reinitialization() public {
cheats.expectRevert("Initializable: contract is already initialized");
- delegationManager.initialize(address(this), pauserRegistry, 0, initializedWithdrawalDelayBlocks);
+ delegationManager.initialize(
+ address(this),
+ pauserRegistry,
+ 0,
+ 0, // minWithdrawalDelayBlocks
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ );
+ }
+
+ function testFuzz_setMinWithdrawalDelayBlocks_revert_notOwner(
+ address invalidCaller
+ ) public filterFuzzedAddressInputs(invalidCaller) {
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("Ownable: caller is not the owner");
+ delegationManager.setMinWithdrawalDelayBlocks(0);
+ }
+
+ function testFuzz_setMinWithdrawalDelayBlocks_revert_tooLarge(uint256 newMinWithdrawalDelayBlocks) external {
+ // filter fuzzed inputs to disallowed amounts
+ cheats.assume(newMinWithdrawalDelayBlocks > delegationManager.MAX_WITHDRAWAL_DELAY_BLOCKS());
+
+ // attempt to set the `minWithdrawalDelayBlocks` variable
+ cheats.expectRevert("DelegationManager._setMinWithdrawalDelayBlocks: _minWithdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS");
+ delegationManager.setMinWithdrawalDelayBlocks(newMinWithdrawalDelayBlocks);
}
- function testFuzz_initialize_Revert_WhenWithdrawalDelayBlocksTooLarge(uint256 withdrawalDelayBlocks) public {
- cheats.assume(withdrawalDelayBlocks > MAX_WITHDRAWAL_DELAY_BLOCKS);
+ function testFuzz_initialize_Revert_WhenWithdrawalDelayBlocksTooLarge(
+ uint256[] memory withdrawalDelayBlocks,
+ uint256 invalidStrategyIndex
+ ) public {
+ // set withdrawalDelayBlocks to be too large
+ cheats.assume(withdrawalDelayBlocks.length > 0);
+ uint256 numStrats = withdrawalDelayBlocks.length;
+ IStrategy[] memory strategiesToSetDelayBlocks = new IStrategy[](numStrats);
+ for (uint256 i = 0; i < numStrats; i++) {
+ strategiesToSetDelayBlocks[i] = IStrategy(address(uint160(uint256(keccak256(abi.encode(strategyMock, i))))));
+ }
+
+ // set at least one index to be too large for withdrawalDelayBlocks
+ invalidStrategyIndex = invalidStrategyIndex % numStrats;
+ withdrawalDelayBlocks[invalidStrategyIndex] = MAX_WITHDRAWAL_DELAY_BLOCKS + 1;
+
// Deploy DelegationManager implmentation and proxy
delegationManagerImplementation = new DelegationManager(strategyManagerMock, slasherMock, eigenPodManagerMock);
cheats.expectRevert(
- "DelegationManager._initializeWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
+ "DelegationManager._setStrategyWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
);
delegationManager = DelegationManager(
address(
@@ -528,6 +598,8 @@ contract DelegationManagerUnitTests_Initialization_Setters is DelegationManagerU
address(this),
pauserRegistry,
0, // 0 is initialPausedStatus
+ minWithdrawalDelayBlocks,
+ strategiesToSetDelayBlocks,
withdrawalDelayBlocks
)
)
@@ -780,111 +852,6 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU
}
}
-contract DelegationManagerUnitTests_operatorAVSRegisterationStatus is DelegationManagerUnitTests {
- // @notice Tests that an avs who calls `updateAVSMetadataURI` will correctly see an `AVSMetadataURIUpdated` event emitted with their input
- function testFuzz_UpdateAVSMetadataURI(string memory metadataURI) public {
- // call `updateAVSMetadataURI` and check for event
- cheats.expectEmit(true, true, true, true, address(delegationManager));
- cheats.prank(defaultAVS);
- emit AVSMetadataURIUpdated(defaultAVS, metadataURI);
- delegationManager.updateAVSMetadataURI(metadataURI);
- }
-
- // @notice Verifies an operator registers successfull to avs and see an `OperatorAVSRegistrationStatusUpdated` event emitted
- function testFuzz_registerOperatorToAVS(bytes32 salt) public {
- address operator = cheats.addr(delegationSignerPrivateKey);
- assertFalse(delegationManager.isOperator(operator), "bad test setup");
- _registerOperatorWithBaseDetails(operator);
-
- cheats.expectEmit(true, true, true, true, address(delegationManager));
- emit OperatorAVSRegistrationStatusUpdated(operator, defaultAVS, OperatorAVSRegistrationStatus.REGISTERED);
-
- uint256 expiry = type(uint256).max;
-
- cheats.prank(defaultAVS);
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature(
- delegationSignerPrivateKey,
- operator,
- defaultAVS,
- salt,
- expiry
- );
-
- delegationManager.registerOperatorToAVS(operator, operatorSignature);
- }
-
- // @notice Verifies an operator registers successfull to avs and see an `OperatorAVSRegistrationStatusUpdated` event emitted
- function testFuzz_revert_whenOperatorNotRegisteredToEigenLayerYet(bytes32 salt) public {
- address operator = cheats.addr(delegationSignerPrivateKey);
- assertFalse(delegationManager.isOperator(operator), "bad test setup");
-
- cheats.prank(defaultAVS);
- uint256 expiry = type(uint256).max;
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature(
- delegationSignerPrivateKey,
- operator,
- defaultAVS,
- salt,
- expiry
- );
-
- cheats.expectRevert("DelegationManager.registerOperatorToAVS: operator not registered to EigenLayer yet");
- delegationManager.registerOperatorToAVS(operator, operatorSignature);
- }
-
- // @notice Verifies an operator registers fails when the signature is not from the operator
- function testFuzz_revert_whenSignatureAddressIsNotOperator(bytes32 salt) public {
- address operator = cheats.addr(delegationSignerPrivateKey);
- assertFalse(delegationManager.isOperator(operator), "bad test setup");
- _registerOperatorWithBaseDetails(operator);
-
- uint256 expiry = type(uint256).max;
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature(
- delegationSignerPrivateKey,
- operator,
- defaultAVS,
- salt,
- expiry
- );
-
- cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer");
- cheats.prank(operator);
- delegationManager.registerOperatorToAVS(operator, operatorSignature);
- }
-
- // @notice Verifies an operator registers fails when the signature expiry already expires
- function testFuzz_revert_whenExpiryHasExpired(bytes32 salt, uint256 expiry, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) public {
- address operator = cheats.addr(delegationSignerPrivateKey);
- cheats.assume(operatorSignature.expiry < block.timestamp);
-
- cheats.expectRevert("DelegationManager.registerOperatorToAVS: operator signature expired");
- delegationManager.registerOperatorToAVS(operator, operatorSignature);
- }
-
- // @notice Verifies an operator registers fails when it's already registered to the avs
- function testFuzz_revert_whenOperatorAlreadyRegisteredToAVS(bytes32 salt) public {
- address operator = cheats.addr(delegationSignerPrivateKey);
- assertFalse(delegationManager.isOperator(operator), "bad test setup");
- _registerOperatorWithBaseDetails(operator);
-
- uint256 expiry = type(uint256).max;
- ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature(
- delegationSignerPrivateKey,
- operator,
- defaultAVS,
- salt,
- expiry
- );
-
- cheats.startPrank(defaultAVS);
- delegationManager.registerOperatorToAVS(operator, operatorSignature);
-
- cheats.expectRevert("DelegationManager.registerOperatorToAVS: operator already registered");
- delegationManager.registerOperatorToAVS(operator, operatorSignature);
- cheats.stopPrank();
- }
-}
-
contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests {
function test_Revert_WhenPaused() public {
// set the pausing flag
@@ -1194,9 +1161,9 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests {
uint256 expiry
) public filterFuzzedAddressInputs(staker) {
// roll to a very late timestamp
- cheats.roll(type(uint256).max / 2);
+ skip(type(uint256).max / 2);
// filter to only *invalid* `expiry` values
- cheats.assume(expiry < block.timestamp);
+ expiry = bound(expiry, 0, block.timestamp - 1);
// filter inputs, since this will fail when the staker is already registered as an operator
cheats.assume(staker != defaultOperator);
@@ -1280,9 +1247,10 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests {
uint256 expiry
) public filterFuzzedAddressInputs(staker) {
// filter to only valid `expiry` values
- cheats.assume(expiry >= block.timestamp);
+ expiry = bound(expiry, block.timestamp + 1, type(uint256).max);
// filter inputs, since this will fail when the staker is already registered as an operator
- cheats.assume(staker != defaultOperator);
+ address delegationApprover = cheats.addr(delegationSignerPrivateKey);
+ cheats.assume(staker != defaultOperator && staker != delegationApprover);
_registerOperatorWithDelegationApprover(defaultOperator);
@@ -1667,10 +1635,9 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests {
uint256 expiry
) public filterFuzzedAddressInputs(staker) {
// roll to a very late timestamp
- cheats.roll(type(uint256).max / 2);
+ skip(type(uint256).max / 2);
// filter to only *invalid* `expiry` values
- cheats.assume(expiry < block.timestamp);
-
+ expiry = bound(expiry, 0, block.timestamp - 1);
// filter inputs, since this will fail when the staker is already registered as an operator
cheats.assume(staker != defaultOperator);
@@ -1921,7 +1888,7 @@ contract DelegationManagerUnitTests_delegateToBySignature is DelegationManagerUn
uint256 expiry,
bytes memory signature
) public filterFuzzedAddressInputs(staker) filterFuzzedAddressInputs(operator) {
- cheats.assume(expiry < block.timestamp);
+ expiry = bound(expiry, 0, block.timestamp - 1);
cheats.expectRevert("DelegationManager.delegateToBySignature: staker signature expired");
ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
signature: signature,
@@ -2058,12 +2025,18 @@ contract DelegationManagerUnitTests_delegateToBySignature is DelegationManagerUn
uint256 stakerExpiry,
uint256 delegationApproverExpiry
) public filterFuzzedAddressInputs(caller) {
- // filter to only valid `stakerExpiry` values
- cheats.assume(stakerExpiry >= block.timestamp);
// roll to a very late timestamp
- cheats.roll(type(uint256).max / 2);
+ skip(type(uint256).max / 2);
+
+ // filter to only valid `stakerExpiry` values
+ stakerExpiry = bound(stakerExpiry, block.timestamp + 1, type(uint256).max);
// filter to only *invalid* `delegationApproverExpiry` values
- cheats.assume(delegationApproverExpiry < block.timestamp);
+ delegationApproverExpiry = bound(delegationApproverExpiry, 0, block.timestamp - 1);
+
+ console.log("timestamp: %s", block.timestamp);
+ console.log(stakerExpiry);
+ console.log(delegationApproverExpiry);
+
_registerOperatorWithDelegationApprover(defaultOperator);
@@ -2512,8 +2485,8 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest
) public filterFuzzedAddressInputs(invalidCaller) {
cheats.assume(invalidCaller != address(strategyManagerMock));
cheats.assume(invalidCaller != address(eigenPodManagerMock));
+ cheats.assume(invalidCaller != address(eigenLayerProxyAdmin));
- cheats.prank(invalidCaller);
cheats.expectRevert("DelegationManager: onlyStrategyManagerOrEigenPodManager");
delegationManager.increaseDelegatedShares(invalidCaller, strategyMock, shares);
}
@@ -2538,7 +2511,7 @@ contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTest
address staker,
uint256 shares,
bool delegateFromStakerToOperator
- ) public {
+ ) public filterFuzzedAddressInputs(staker) {
// filter inputs, since delegating to the operator will fail when the staker is already registered as an operator
cheats.assume(staker != defaultOperator);
@@ -2727,7 +2700,7 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests {
function testFuzz_undelegate_operatorCannotForceUndelegateThemself(
address delegationApprover,
bool callFromOperatorOrApprover
- ) public {
+ ) public filterFuzzedAddressInputs(delegationApprover) {
// register *this contract* as an operator with the default `delegationApprover`
_registerOperatorWithDelegationApprover(defaultOperator);
@@ -2872,14 +2845,16 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes
delegationManager.queueWithdrawals(queuedWithdrawalParams);
}
- function test_Revert_WhenZeroAddressWithdrawer() public {
+ function test_Revert_WhenNotStakerWithdrawer(address withdrawer) public {
+ cheats.assume(withdrawer != defaultStaker);
+
(IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams, , ) = _setUpQueueWithdrawalsSingleStrat({
staker: defaultStaker,
- withdrawer: address(0),
+ withdrawer: withdrawer,
strategy: strategyMock,
withdrawalAmount: 100
});
- cheats.expectRevert("DelegationManager.queueWithdrawal: must provide valid withdrawal address");
+ cheats.expectRevert("DelegationManager.queueWithdrawal: withdrawer must be staker");
delegationManager.queueWithdrawals(queuedWithdrawalParams);
}
@@ -3001,6 +2976,111 @@ contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTes
uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(staker);
assertEq(nonceBefore + 1, nonceAfter, "staker nonce should have incremented");
}
+
+ /**
+ * @notice Verifies that `DelegationManager.queueWithdrawals` properly queues a withdrawal for the `withdrawer`
+ * with multiple strategies and sharesAmounts and with thirdPartyTransfersForbidden for one of the strategies.
+ * Queuing a withdrawal should pass as the `withdrawer` address is the same as the staker.
+ *
+ * Depending on length sharesAmounts, deploys corresponding number of strategies
+ * and deposits sharesAmounts into each strategy for the staker and delegates to operator.
+ * For each strategy, withdrawAmount <= depositAmount
+ * - Asserts that staker is delegated to the operator
+ * - Asserts that shares for delegatedTo operator are decreased by `sharesAmount`
+ * - Asserts that staker cumulativeWithdrawalsQueued nonce is incremented
+ * - Checks that event was emitted with correct withdrawalRoot and withdrawal
+ */
+ function testFuzz_queueWithdrawal_ThirdPartyTransfersForbidden(
+ address staker,
+ uint256[] memory depositAmounts,
+ uint256 randSalt
+ ) public filterFuzzedAddressInputs(staker){
+ cheats.assume(depositAmounts.length > 0 && depositAmounts.length <= 32);
+ cheats.assume(staker != defaultOperator);
+ uint256[] memory withdrawalAmounts = _fuzzWithdrawalAmounts(depositAmounts);
+
+ IStrategy[] memory strategies = _deployAndDepositIntoStrategies(staker, depositAmounts);
+ // Randomly set strategy true for thirdPartyTransfersForbidden
+ uint256 randStrategyIndex = randSalt % strategies.length;
+ strategyManagerMock.setThirdPartyTransfersForbidden(strategies[randStrategyIndex], true);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ (
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams,
+ IDelegationManager.Withdrawal memory withdrawal,
+ bytes32 withdrawalRoot
+ ) = _setUpQueueWithdrawals({
+ staker: staker,
+ withdrawer: staker,
+ strategies: strategies,
+ withdrawalAmounts: withdrawalAmounts
+ });
+ // Before queueWithdrawal state values
+ uint256 nonceBefore = delegationManager.cumulativeWithdrawalsQueued(staker);
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker should be delegated to operator");
+ uint256[] memory delegatedSharesBefore = new uint256[](strategies.length);
+ for (uint256 i = 0; i < strategies.length; i++) {
+ delegatedSharesBefore[i] = delegationManager.operatorShares(defaultOperator, strategies[i]);
+ }
+
+ // queueWithdrawals
+ cheats.prank(staker);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit WithdrawalQueued(withdrawalRoot, withdrawal);
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+
+ // Post queueWithdrawal state values
+ for (uint256 i = 0; i < strategies.length; i++) {
+ assertEq(
+ delegatedSharesBefore[i] - withdrawalAmounts[i], // Shares before - withdrawal amount
+ delegationManager.operatorShares(defaultOperator, strategies[i]), // Shares after
+ "delegated shares not decreased correctly"
+ );
+ }
+ uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(staker);
+ assertEq(nonceBefore + 1, nonceAfter, "staker nonce should have incremented");
+ }
+
+ /**
+ * @notice Randomly selects one of the strategies to set thirdPartyTransfersForbidden to true.
+ * Verifies that `DelegationManager.queueWithdrawals` properly reverts a queuedWithdrawal since the `withdrawer`
+ * is not the same as the `staker`.
+ */
+ function testFuzz_queueWithdrawal_Revert_WhenThirdPartyTransfersForbidden(
+ address staker,
+ address withdrawer,
+ uint256[] memory depositAmounts,
+ uint256 randSalt
+ ) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != withdrawer);
+ cheats.assume(depositAmounts.length > 0 && depositAmounts.length <= 32);
+ uint256[] memory withdrawalAmounts = _fuzzWithdrawalAmounts(depositAmounts);
+
+ IStrategy[] memory strategies = _deployAndDepositIntoStrategies(staker, depositAmounts);
+ // Randomly set strategy true for thirdPartyTransfersForbidden
+ uint256 randStrategyIndex = randSalt % strategies.length;
+ strategyManagerMock.setThirdPartyTransfersForbidden(strategies[randStrategyIndex], true);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ (IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams, , ) = _setUpQueueWithdrawals({
+ staker: staker,
+ withdrawer: withdrawer,
+ strategies: strategies,
+ withdrawalAmounts: withdrawalAmounts
+ });
+
+ // queueWithdrawals
+ // NOTE: Originally, you could queue a withdrawal to a different address, which would fail with a specific error
+ // if third party transfers were forbidden. Now, withdrawing to a different address is forbidden regardless
+ // of third party transfer status.
+ cheats.expectRevert(
+ "DelegationManager.queueWithdrawal: withdrawer must be staker"
+ );
+ cheats.prank(staker);
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+ }
}
contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManagerUnitTests {
@@ -3014,7 +3094,6 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
/* bytes32 withdrawalRoot */
) = _setUpCompleteQueuedWithdrawalSingleStrat({
staker: defaultStaker,
- operator: defaultOperator,
withdrawer: defaultStaker,
depositAmount: 100,
withdrawalAmount: 100
@@ -3033,7 +3112,6 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
bytes32 withdrawalRoot
) = _setUpCompleteQueuedWithdrawalSingleStrat({
staker: defaultStaker,
- operator: defaultOperator,
withdrawer: defaultStaker,
depositAmount: 100,
withdrawalAmount: 100
@@ -3041,34 +3119,110 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending");
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
cheats.prank(defaultStaker);
- cheats.roll(block.number + initializedWithdrawalDelayBlocks);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be completed and marked false now");
- cheats.expectRevert("DelegationManager.completeQueuedAction: action is not in queue");
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.expectRevert("DelegationManager._completeQueuedWithdrawal: action is not in queue");
cheats.prank(defaultStaker);
- cheats.roll(block.number + initializedWithdrawalDelayBlocks);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
}
- function test_Revert_WhenWithdrawalDelayBlocksNotPassed() public {
+ /**
+ * @notice should revert if minWithdrawalDelayBlocks has not passed, and if
+ * delegationManager.getWithdrawalDelay returns a value greater than minWithdrawalDelayBlocks
+ * then it should revert if the validBlockNumber has not passed either.
+ */
+ function test_Revert_WhenWithdrawalDelayBlocksNotPassed(
+ uint256[] memory depositAmounts,
+ uint256 randSalt,
+ bool receiveAsTokens
+ ) public {
+ cheats.assume(depositAmounts.length > 0 && depositAmounts.length <= 32);
+ uint256[] memory withdrawalAmounts = _fuzzWithdrawalAmounts(depositAmounts);
+
_registerOperatorWithBaseDetails(defaultOperator);
(
IDelegationManager.Withdrawal memory withdrawal,
IERC20[] memory tokens,
/* bytes32 withdrawalRoot */
- ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ ) = _setUpCompleteQueuedWithdrawal({
staker: defaultStaker,
- operator: defaultOperator,
withdrawer: defaultStaker,
- depositAmount: 100,
- withdrawalAmount: 100
+ depositAmounts: depositAmounts,
+ withdrawalAmounts: withdrawalAmounts
});
- _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
- cheats.expectRevert("DelegationManager.completeQueuedAction: withdrawalDelayBlocks period has not yet passed");
+ // prank as withdrawer address
+ cheats.startPrank(defaultStaker);
+
+ cheats.expectRevert(
+ "DelegationManager._completeQueuedWithdrawal: minWithdrawalDelayBlocks period has not yet passed"
+ );
+ cheats.roll(block.number + minWithdrawalDelayBlocks - 1);
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, receiveAsTokens);
+
+ uint256 validBlockNumber = delegationManager.getWithdrawalDelay(withdrawal.strategies);
+ if (validBlockNumber > minWithdrawalDelayBlocks) {
+ cheats.expectRevert(
+ "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
+ );
+ cheats.roll(validBlockNumber - 1);
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, receiveAsTokens);
+ }
+
+ cheats.stopPrank();
+ }
+
+ /**
+ * @notice should revert when the withdrawalDelayBlocks period has not yet passed for the
+ * beacon chain strategy
+ */
+ function test_Revert_WhenWithdrawalDelayBlocksNotPassed_BeaconStrat(
+ uint256 depositAmount,
+ uint256 withdrawalAmount,
+ uint256 beaconWithdrawalDelay
+ ) public {
+ cheats.assume(depositAmount > 1 && withdrawalAmount <= depositAmount);
+ beaconWithdrawalDelay = bound(beaconWithdrawalDelay, minWithdrawalDelayBlocks, MAX_WITHDRAWAL_DELAY_BLOCKS);
+ _registerOperatorWithBaseDetails(defaultOperator);
+ (
+ IDelegationManager.Withdrawal memory withdrawal,
+ IERC20[] memory tokens,
+ bytes32 withdrawalRoot
+ ) = _setUpCompleteQueuedWithdrawalBeaconStrat({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ depositAmount: depositAmount,
+ withdrawalAmount: withdrawalAmount
+ });
+
+ IStrategy[] memory strategies = new IStrategy[](1);
+ strategies[0] = beaconChainETHStrategy;
+ uint256[] memory withdrawalDelayBlocks = new uint256[](1);
+ delegationManager.setStrategyWithdrawalDelayBlocks(withdrawal.strategies, withdrawalDelayBlocks);
+
+ // prank as withdrawer address
+ cheats.startPrank(defaultStaker);
+
+ cheats.expectRevert(
+ "DelegationManager._completeQueuedWithdrawal: minWithdrawalDelayBlocks period has not yet passed"
+ );
+ cheats.roll(block.number + minWithdrawalDelayBlocks - 1);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
+
+ uint256 validBlockNumber = delegationManager.getWithdrawalDelay(withdrawal.strategies);
+ if (validBlockNumber > minWithdrawalDelayBlocks) {
+ cheats.expectRevert(
+ "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
+ );
+ cheats.roll(validBlockNumber - 1);
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
+ }
+
+ cheats.stopPrank();
}
function test_Revert_WhenNotCalledByWithdrawer() public {
@@ -3076,18 +3230,17 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
(
IDelegationManager.Withdrawal memory withdrawal,
IERC20[] memory tokens,
- bytes32 withdrawalRoot
+ /*bytes32 withdrawalRoot*/
) = _setUpCompleteQueuedWithdrawalSingleStrat({
staker: defaultStaker,
- operator: defaultOperator,
withdrawer: defaultStaker,
depositAmount: 100,
withdrawalAmount: 100
});
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
- cheats.expectRevert("DelegationManager.completeQueuedAction: only withdrawer can complete action");
- cheats.roll(block.number + initializedWithdrawalDelayBlocks);
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.expectRevert("DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action");
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
}
@@ -3095,7 +3248,6 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
_registerOperatorWithBaseDetails(defaultOperator);
(IDelegationManager.Withdrawal memory withdrawal, , ) = _setUpCompleteQueuedWithdrawalSingleStrat({
staker: defaultStaker,
- operator: defaultOperator,
withdrawer: defaultStaker,
depositAmount: 100,
withdrawalAmount: 100
@@ -3103,9 +3255,9 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
IERC20[] memory tokens = new IERC20[](0);
- cheats.expectRevert("DelegationManager.completeQueuedAction: input length mismatch");
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.expectRevert("DelegationManager._completeQueuedWithdrawal: input length mismatch");
cheats.prank(defaultStaker);
- cheats.roll(block.number + initializedWithdrawalDelayBlocks);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, true);
}
@@ -3118,7 +3270,6 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
*/
function test_completeQueuedWithdrawal_SingleStratWithdrawAsTokens(
address staker,
- address withdrawer,
uint256 depositAmount,
uint256 withdrawalAmount
) public filterFuzzedAddressInputs(staker) {
@@ -3131,8 +3282,7 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
bytes32 withdrawalRoot
) = _setUpCompleteQueuedWithdrawalSingleStrat({
staker: staker,
- operator: defaultOperator,
- withdrawer: withdrawer,
+ withdrawer: staker,
depositAmount: depositAmount,
withdrawalAmount: withdrawalAmount
});
@@ -3141,8 +3291,8 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending");
// completeQueuedWithdrawal
- cheats.prank(withdrawer);
- cheats.roll(block.number + initializedWithdrawalDelayBlocks);
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.prank(staker);
cheats.expectEmit(true, true, true, true, address(delegationManager));
emit WithdrawalCompleted(withdrawalRoot);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, true);
@@ -3162,22 +3312,20 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
*/
function test_completeQueuedWithdrawal_SingleStratWithdrawAsShares(
address staker,
- address withdrawer,
uint256 depositAmount,
uint256 withdrawalAmount
) public filterFuzzedAddressInputs(staker) {
cheats.assume(staker != defaultOperator);
- cheats.assume(withdrawer != defaultOperator);
cheats.assume(withdrawalAmount > 0 && withdrawalAmount <= depositAmount);
_registerOperatorWithBaseDetails(defaultOperator);
+
(
IDelegationManager.Withdrawal memory withdrawal,
IERC20[] memory tokens,
bytes32 withdrawalRoot
) = _setUpCompleteQueuedWithdrawalSingleStrat({
staker: staker,
- operator: defaultOperator,
- withdrawer: withdrawer,
+ withdrawer: staker,
depositAmount: depositAmount,
withdrawalAmount: withdrawalAmount
});
@@ -3186,20 +3334,15 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending");
// completeQueuedWithdrawal
- cheats.prank(withdrawer);
- cheats.roll(block.number + initializedWithdrawalDelayBlocks);
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.prank(staker);
cheats.expectEmit(true, true, true, true, address(delegationManager));
emit WithdrawalCompleted(withdrawalRoot);
delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, withdrawal.strategies[0]);
- if (staker == withdrawer) {
- // Since staker is delegated, operatorShares get incremented
- assertEq(operatorSharesAfter, operatorSharesBefore + withdrawalAmount, "operator shares not increased correctly");
- } else {
- // Since withdrawer is not the staker and isn't delegated, staker's oeprator shares are unchanged
- assertEq(operatorSharesAfter, operatorSharesBefore, "operator shares should be unchanged");
- }
+ // Since staker is delegated, operatorShares get incremented
+ assertEq(operatorSharesAfter, operatorSharesBefore + withdrawalAmount, "operator shares not increased correctly");
assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be completed and marked false now");
}
}
\ No newline at end of file
diff --git a/src/test/unit/EigenPod-PodManagerUnit.t.sol b/src/test/unit/EigenPod-PodManagerUnit.t.sol
index 511b7d9be..7aae852b6 100644
--- a/src/test/unit/EigenPod-PodManagerUnit.t.sol
+++ b/src/test/unit/EigenPod-PodManagerUnit.t.sol
@@ -118,6 +118,8 @@ contract EigenPod_PodManager_UnitTests is EigenLayerUnitTestSetup {
// Set storage in EPM
EigenPodManagerWrapper(address(eigenPodManager)).setPodAddress(podOwner, eigenPod);
+
+ eigenPodManager.setDenebForkTimestamp(type(uint64).max);
}
}
@@ -370,6 +372,7 @@ contract EigenPod_PodManager_UnitTests_EigenPodManager is EigenPod_PodManager_Un
assertEq(validatorInfo.restakedBalanceGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Restaked balance gwei should be max");
assertGt(updatedShares - initialShares, 0, "Shares delta should be positive");
assertEq(updatedShares, 32e18, "Shares should be 32ETH");
+ assertEq(newValidatorBalance, 32e9, "validator balance should be 32e9 Gwei");
}
function test_fullWithdrawal_excess32ETH() public {
@@ -535,9 +538,6 @@ contract EigenPod_PodManager_UnitTests_EigenPodManager is EigenPod_PodManager_Un
_setOracleBlockRoot();
cheats.warp(oracleTimestamp+=1);
- // Save state for checks
- int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
-
// Act: Verify withdrawal credentials and record the balance update
cheats.prank(podOwner);
eigenPod.verifyWithdrawalCredentials(
@@ -632,10 +632,10 @@ contract EigenPod_PodManager_UnitTests_EigenPodManager is EigenPod_PodManager_Un
return
BeaconChainProofs.WithdrawalProof(
- abi.encodePacked(getWithdrawalProof()),
+ abi.encodePacked(getWithdrawalProofCapella()),
abi.encodePacked(getSlotProof()),
abi.encodePacked(getExecutionPayloadProof()),
- abi.encodePacked(getTimestampProof()),
+ abi.encodePacked(getTimestampProofCapella()),
abi.encodePacked(getHistoricalSummaryProof()),
uint64(getBlockRootIndex()),
uint64(getHistoricalSummaryIndex()),
diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol
index 8d797923f..9e0e3fc3f 100644
--- a/src/test/unit/EigenPodManagerUnit.t.sol
+++ b/src/test/unit/EigenPodManagerUnit.t.sol
@@ -175,6 +175,29 @@ contract EigenPodManagerUnitTests_Initialization_Setters is EigenPodManagerUnitT
// Check storage update
assertEq(address(eigenPodManager.beaconChainOracle()), address(newBeaconChainOracle), "Beacon chain oracle not updated");
}
+
+ function test_setDenebForkTimestamp(uint64 denebForkTimestamp) public {
+ cheats.assume(denebForkTimestamp != 0);
+ cheats.assume(denebForkTimestamp != type(uint64).max);
+ cheats.prank(initialOwner);
+
+ cheats.expectEmit(true, true, true, true);
+ emit DenebForkTimestampUpdated(denebForkTimestamp);
+ eigenPodManager.setDenebForkTimestamp(denebForkTimestamp);
+ assertEq(eigenPodManager.denebForkTimestamp(), denebForkTimestamp, "fork timestamp not set correctly");
+ }
+
+ function test_setDenebForkTimestamp_Twice(uint64 timestamp1, uint64 timestamp2) public {
+ cheats.assume(timestamp1 != 0);
+ cheats.assume(timestamp2 != 0);
+ cheats.assume(timestamp1 != type(uint64).max);
+ cheats.assume(timestamp2 != type(uint64).max);
+ cheats.prank(initialOwner);
+
+ eigenPodManager.setDenebForkTimestamp(timestamp1);
+ cheats.expectRevert(bytes("EigenPodManager.setDenebForkTimestamp: cannot set denebForkTimestamp more than once"));
+ eigenPodManager.setDenebForkTimestamp(timestamp2);
+ }
}
contract EigenPodManagerUnitTests_CreationTests is EigenPodManagerUnitTests, IEigenPodManagerEvents {
@@ -289,7 +312,7 @@ contract EigenPodManagerUnitTests_ShareUpdateTests is EigenPodManagerUnitTests {
function testFuzz_addShares(uint256 shares) public {
// Fuzz inputs
cheats.assume(defaultStaker != address(0));
- cheats.assume(shares % GWEI_TO_WEI == 0);
+ shares = shares - (shares % GWEI_TO_WEI); // Round down to nearest Gwei
cheats.assume(int256(shares) >= 0);
// Add shares
@@ -361,7 +384,9 @@ contract EigenPodManagerUnitTests_ShareUpdateTests is EigenPodManagerUnitTests {
function testFuzz_removeShares_zeroShares(address podOwner, uint256 shares) public filterFuzzedAddressInputs(podOwner) {
// Constrain inputs
cheats.assume(podOwner != address(0));
- cheats.assume(shares % GWEI_TO_WEI == 0);
+ cheats.assume(shares < type(uint256).max / 2);
+ shares = shares - (shares % GWEI_TO_WEI); // Round down to nearest Gwei
+ assertTrue(int256(shares) % int256(GWEI_TO_WEI) == 0, "Shares must be a whole Gwei amount");
// Initialize pod with shares
_initializePodWithShares(podOwner, int256(shares));
@@ -460,7 +485,7 @@ contract EigenPodManagerUnitTests_ShareUpdateTests is EigenPodManagerUnitTests {
}
}
-contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodManagerUnitTests {
+contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodManagerUnitTests, IEigenPodManagerEvents {
function testFuzz_recordBalanceUpdate_revert_notPod(address invalidCaller) public filterFuzzedAddressInputs(invalidCaller) deployPodForStaker(defaultStaker) {
cheats.assume(invalidCaller != address(defaultPod));
@@ -492,6 +517,8 @@ contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodMa
_initializePodWithShares(defaultStaker, scaledSharesBefore);
// Update balance
+ cheats.expectEmit(true, true, true, true);
+ emit PodSharesUpdated(defaultStaker, scaledSharesDelta);
cheats.prank(address(defaultPod));
eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, scaledSharesDelta);
diff --git a/src/test/unit/EigenPodUnit.t.sol b/src/test/unit/EigenPodUnit.t.sol
index 182562e04..7b907dfc4 100644
--- a/src/test/unit/EigenPodUnit.t.sol
+++ b/src/test/unit/EigenPodUnit.t.sol
@@ -19,13 +19,16 @@ contract EigenPodUnitTests is EigenLayerUnitTestSetup {
EigenPod public eigenPod;
EigenPod public podImplementation;
IBeacon public eigenPodBeacon;
-
+
// Mocks
IETHPOSDeposit public ethPOSDepositMock;
IDelayedWithdrawalRouter public delayedWithdrawalRouterMock;
// Address of pod for which proofs were generated
address podAddress = address(0x49c486E3f4303bc11C02F952Fe5b08D0AB22D443);
+
+
+ bool IS_DENEB = false;
// Constants
// uint32 public constant WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds;
@@ -146,6 +149,7 @@ contract EigenPodUnitTests_Stake is EigenPodUnitTests, IEigenPodEvents {
eigenPod.stake{value: 32 ether}(pubkey, signature, depositDataRoot);
}
+
function testFuzz_stake_revert_invalidValue(uint256 value) public {
cheats.assume(value != 32 ether);
cheats.deal(address(eigenPodManagerMock), value);
@@ -419,6 +423,7 @@ contract EigenPodUnitTests_VerifyWithdrawalCredentialsTests is EigenPodHarnessSe
}
function testFuzz_revert_invalidValidatorFields(address wrongWithdrawalAddress) public {
+ cheats.assume(wrongWithdrawalAddress != address(eigenPodHarness));
// Set JSON and params
setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
_setWithdrawalCredentialParams();
@@ -839,7 +844,6 @@ contract EigenPodUnitTests_WithdrawalTests is EigenPodHarnessSetup, ProofParsing
// Get params to check against
uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
- uint40 validatorIndex = uint40(getValidatorIndex());
uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
assertLt(withdrawalAmountGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Withdrawal amount should be greater than max restaked balance for this test");
@@ -854,7 +858,7 @@ contract EigenPodUnitTests_WithdrawalTests is EigenPodHarnessSetup, ProofParsing
// Storage checks in _verifyAndProcessWithdrawal
bytes32 validatorPubKeyHash = validatorFields.getPubkeyHash();
- // assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
+ assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
// Checks from _processFullWithdrawal
assertEq(eigenPod.withdrawableRestakedExecutionLayerGwei(), withdrawalAmountGwei, "Incorrect withdrawable restaked execution layer gwei");
@@ -983,13 +987,14 @@ contract EigenPodUnitTests_WithdrawalTests is EigenPodHarnessSetup, ProofParsing
bytes32 slotRoot = getSlotRoot();
bytes32 timestampRoot = getTimestampRoot();
bytes32 executionPayloadRoot = getExecutionPayloadRoot();
-
+ bytes memory withdrawalProof = IS_DENEB ? abi.encodePacked(getWithdrawalProofDeneb()) : abi.encodePacked(getWithdrawalProofCapella());
+ bytes memory timestampProof = IS_DENEB ? abi.encodePacked(getTimestampProofDeneb()) : abi.encodePacked(getTimestampProofCapella());
return
BeaconChainProofs.WithdrawalProof(
- abi.encodePacked(getWithdrawalProof()),
+ abi.encodePacked(withdrawalProof),
abi.encodePacked(getSlotProof()),
abi.encodePacked(getExecutionPayloadProof()),
- abi.encodePacked(getTimestampProof()),
+ abi.encodePacked(timestampProof),
abi.encodePacked(getHistoricalSummaryProof()),
uint64(getBlockRootIndex()),
uint64(getHistoricalSummaryIndex()),
diff --git a/src/test/unit/StrategyManagerUnit.t.sol b/src/test/unit/StrategyManagerUnit.t.sol
index 8b0066a8c..578b5f3a0 100644
--- a/src/test/unit/StrategyManagerUnit.t.sol
+++ b/src/test/unit/StrategyManagerUnit.t.sol
@@ -9,6 +9,7 @@ import "src/test/mocks/ERC20Mock.sol";
import "src/test/mocks/ERC20_SetTransferReverting_Mock.sol";
import "src/test/mocks/Reverter.sol";
import "src/test/mocks/Reenterer.sol";
+import "src/test/events/IStrategyManagerEvents.sol";
import "src/test/utils/EigenLayerUnitTestSetup.sol";
/**
@@ -17,7 +18,7 @@ import "src/test/utils/EigenLayerUnitTestSetup.sol";
* Contracts tested: StrategyManager.sol
* Contracts not mocked: StrategyBase, PauserRegistry
*/
-contract StrategyManagerUnitTests is EigenLayerUnitTestSetup {
+contract StrategyManagerUnitTests is EigenLayerUnitTestSetup, IStrategyManagerEvents {
StrategyManager public strategyManagerImplementation;
StrategyManager public strategyManager;
@@ -33,60 +34,6 @@ contract StrategyManagerUnitTests is EigenLayerUnitTestSetup {
uint256 public privateKey = 111111;
address constant dummyAdmin = address(uint160(uint256(keccak256("DummyAdmin"))));
- /**
- * @notice Emitted when a new deposit occurs on behalf of `depositor`.
- * @param depositor Is the staker who is depositing funds into EigenLayer.
- * @param strategy Is the strategy that `depositor` has deposited into.
- * @param token Is the token that `depositor` deposited.
- * @param shares Is the number of new shares `depositor` has been granted in `strategy`.
- */
- event Deposit(address depositor, IERC20 token, IStrategy strategy, uint256 shares);
-
- /**
- * @notice Emitted when a new withdrawal occurs on behalf of `depositor`.
- * @param depositor Is the staker who is queuing a withdrawal from EigenLayer.
- * @param nonce Is the withdrawal's unique identifier (to the depositor).
- * @param strategy Is the strategy that `depositor` has queued to withdraw from.
- * @param shares Is the number of shares `depositor` has queued to withdraw.
- */
- event ShareWithdrawalQueued(address depositor, uint96 nonce, IStrategy strategy, uint256 shares);
-
- /**
- * @notice Emitted when a new withdrawal is queued by `depositor`.
- * @param depositor Is the staker who is withdrawing funds from EigenLayer.
- * @param nonce Is the withdrawal's unique identifier (to the depositor).
- * @param withdrawer Is the party specified by `staker` who will be able to complete the queued withdrawal and receive the withdrawn funds.
- * @param delegatedAddress Is the party who the `staker` was delegated to at the time of creating the queued withdrawal
- * @param withdrawalRoot Is a hash of the input data for the withdrawal.
- */
- event WithdrawalQueued(
- address depositor,
- uint96 nonce,
- address withdrawer,
- address delegatedAddress,
- bytes32 withdrawalRoot
- );
-
- /// @notice Emitted when a queued withdrawal is completed
- event WithdrawalCompleted(
- address indexed depositor,
- uint96 nonce,
- address indexed withdrawer,
- bytes32 withdrawalRoot
- );
-
- /// @notice Emitted when the `strategyWhitelister` is changed
- event StrategyWhitelisterChanged(address previousAddress, address newAddress);
-
- /// @notice Emitted when a strategy is added to the approved list of strategies for deposit
- event StrategyAddedToDepositWhitelist(IStrategy strategy);
-
- /// @notice Emitted when a strategy is removed from the approved list of strategies for deposit
- event StrategyRemovedFromDepositWhitelist(IStrategy strategy);
-
- /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
- event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
-
function setUp() public override {
EigenLayerUnitTestSetup.setUp();
strategyManagerImplementation = new StrategyManager(delegationManagerMock, eigenPodManagerMock, slasherMock);
@@ -118,11 +65,17 @@ contract StrategyManagerUnitTests is EigenLayerUnitTestSetup {
_strategies[0] = dummyStrat;
_strategies[1] = dummyStrat2;
_strategies[2] = dummyStrat3;
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](3);
+ _thirdPartyTransfersForbiddenValues[0] = false;
+ _thirdPartyTransfersForbiddenValues[1] = false;
+ _thirdPartyTransfersForbiddenValues[2] = false;
for (uint256 i = 0; i < _strategies.length; ++i) {
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(_strategies[i]);
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit UpdatedThirdPartyTransfersForbidden(_strategies[i], _thirdPartyTransfersForbiddenValues[i]);
}
- strategyManager.addStrategiesToDepositWhitelist(_strategies);
+ strategyManager.addStrategiesToDepositWhitelist(_strategies, _thirdPartyTransfersForbiddenValues);
addressIsExcludedFromFuzzedInputs[address(reenterer)] = true;
}
@@ -200,7 +153,7 @@ contract StrategyManagerUnitTests is EigenLayerUnitTestSetup {
{
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), dummyStrat, dummyToken, amount, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, dummyStrat, dummyToken, amount, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
@@ -265,6 +218,7 @@ contract StrategyManagerUnitTests is EigenLayerUnitTestSetup {
*/
function _addStrategiesToWhitelist(uint8 numberOfStrategiesToAdd) internal returns (IStrategy[] memory) {
IStrategy[] memory strategyArray = new IStrategy[](numberOfStrategiesToAdd);
+ bool[] memory thirdPartyTransfersForbiddenValues = new bool[](numberOfStrategiesToAdd);
// loop that deploys a new strategy and adds it to the array
for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
IStrategy _strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
@@ -277,7 +231,7 @@ contract StrategyManagerUnitTests is EigenLayerUnitTestSetup {
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(strategyArray[i]);
}
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategyArray[i]), "strategy not whitelisted");
@@ -387,12 +341,13 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = IStrategy(address(reenterer));
for (uint256 i = 0; i < _strategy.length; ++i) {
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(_strategy[i]);
}
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
reenterer.prepareReturnData(abi.encode(amount));
@@ -418,8 +373,10 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+
_strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
address staker = address(this);
@@ -440,8 +397,9 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
address staker = address(this);
@@ -461,8 +419,9 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
_strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
address staker = address(this);
@@ -482,8 +441,10 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+
_strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
address staker = address(this);
@@ -518,8 +479,10 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+
_strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
address staker = address(this);
@@ -548,7 +511,7 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
{
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
@@ -619,7 +582,7 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
{
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
@@ -675,7 +638,7 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
{
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), dummyStrat, revertToken, amount, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, dummyStrat, revertToken, amount, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
@@ -728,12 +691,15 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+
+
_strategy[0] = IStrategy(address(reenterer));
for (uint256 i = 0; i < _strategy.length; ++i) {
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(_strategy[i]);
}
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
address staker = cheats.addr(privateKey);
@@ -747,7 +713,7 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
{
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
@@ -787,7 +753,7 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
{
bytes32 structHash = keccak256(
- abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry)
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
);
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
@@ -819,6 +785,19 @@ contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyMa
memory expectedRevertMessage = "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted";
_depositIntoStrategyWithSignature(staker, amount, type(uint256).max, expectedRevertMessage);
}
+
+ function testFuzz_Revert_WhenThirdPartyTransfersForbidden(uint256 amount, uint256 expiry) public {
+ // min shares must be minted on strategy
+ cheats.assume(amount >= 1);
+
+ cheats.prank(strategyManager.strategyWhitelister());
+ strategyManager.setThirdPartyTransfersForbidden(dummyStrat, true);
+
+ address staker = cheats.addr(privateKey);
+ // not expecting a revert, so input an empty string
+ string memory expectedRevertMessage = "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled";
+ _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage);
+ }
}
contract StrategyManagerUnitTests_removeShares is StrategyManagerUnitTests {
@@ -936,7 +915,7 @@ contract StrategyManagerUnitTests_removeShares is StrategyManagerUnitTests {
strategies[1] = dummyStrat2;
strategies[2] = dummyStrat3;
for (uint256 i = 0; i < 3; ++i) {
- cheats.assume(amounts[i] > 0 && amounts[i] < dummyToken.totalSupply());
+ amounts[i] = bound(amounts[i], 1, dummyToken.totalSupply() - 1);
_depositIntoStrategySuccessfully(strategies[i], staker, amounts[i]);
}
IStrategy removeStrategy = strategies[randStrategy % 3];
@@ -978,7 +957,8 @@ contract StrategyManagerUnitTests_removeShares is StrategyManagerUnitTests {
strategies[2] = dummyStrat3;
uint256[] memory sharesBefore = new uint256[](3);
for (uint256 i = 0; i < 3; ++i) {
- cheats.assume(sharesAmounts[i] > 0 && sharesAmounts[i] <= depositAmounts[i]);
+ depositAmounts[i] = bound(depositAmounts[i], 1, strategies[i].underlyingToken().totalSupply());
+ sharesAmounts[i] = bound(sharesAmounts[i], 1, depositAmounts[i]);
_depositIntoStrategySuccessfully(strategies[i], staker, depositAmounts[i]);
sharesBefore[i] = strategyManager.stakerStrategyShares(staker, strategies[i]);
assertEq(sharesBefore[i], depositAmounts[i], "Staker has not deposited amount into strategy");
@@ -1025,18 +1005,18 @@ contract StrategyManagerUnitTests_addShares is StrategyManagerUnitTests {
function test_Revert_DelegationManagerModifier() external {
DelegationManagerMock invalidDelegationManager = new DelegationManagerMock();
cheats.expectRevert("StrategyManager.onlyDelegationManager: not the DelegationManager");
- invalidDelegationManager.addShares(strategyManager, address(this), dummyStrat, 1);
+ invalidDelegationManager.addShares(strategyManager, address(this), dummyToken, dummyStrat, 1);
}
function testFuzz_Revert_StakerZeroAddress(uint256 amount) external {
cheats.expectRevert("StrategyManager._addShares: staker cannot be zero address");
- delegationManagerMock.addShares(strategyManager, address(0), dummyStrat, amount);
+ delegationManagerMock.addShares(strategyManager, address(0), dummyToken, dummyStrat, amount);
}
function testFuzz_Revert_ZeroShares(address staker) external filterFuzzedAddressInputs(staker) {
cheats.assume(staker != address(0));
cheats.expectRevert("StrategyManager._addShares: shares should not be zero!");
- delegationManagerMock.addShares(strategyManager, staker, dummyStrat, 0);
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, dummyStrat, 0);
}
function testFuzz_AppendsStakerStrategyList(
@@ -1049,7 +1029,7 @@ contract StrategyManagerUnitTests_addShares is StrategyManagerUnitTests {
assertEq(sharesBefore, 0, "Staker has already deposited into this strategy");
assertFalse(_isDepositedStrategy(staker, dummyStrat), "strategy should not be deposited");
- delegationManagerMock.addShares(strategyManager, staker, dummyStrat, amount);
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, dummyStrat, amount);
uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat);
assertEq(
@@ -1074,7 +1054,7 @@ contract StrategyManagerUnitTests_addShares is StrategyManagerUnitTests {
assertEq(sharesBefore, initialAmount, "Staker has not deposited amount into strategy");
assertTrue(_isDepositedStrategy(staker, strategy), "strategy should be deposited");
- delegationManagerMock.addShares(strategyManager, staker, dummyStrat, sharesAmount);
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, dummyStrat, sharesAmount);
uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat);
assertEq(
@@ -1109,8 +1089,10 @@ contract StrategyManagerUnitTests_addShares is StrategyManagerUnitTests {
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+
_strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
}
@@ -1122,7 +1104,7 @@ contract StrategyManagerUnitTests_addShares is StrategyManagerUnitTests {
cheats.prank(staker);
cheats.expectRevert("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH");
- delegationManagerMock.addShares(strategyManager, staker, strategy, amount);
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, strategy, amount);
cheats.expectRevert("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH");
strategyManager.depositIntoStrategy(strategy, token, amount);
@@ -1201,38 +1183,43 @@ contract StrategyManagerUnitTests_addStrategiesToDepositWhitelist is StrategyMan
) external filterFuzzedAddressInputs(notStrategyWhitelister) {
cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister());
IStrategy[] memory strategyArray = new IStrategy[](1);
+ bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1);
IStrategy _strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
strategyArray[0] = _strategy;
cheats.prank(notStrategyWhitelister);
cheats.expectRevert("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister");
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
}
function test_AddSingleStrategyToWhitelist() external {
IStrategy[] memory strategyArray = new IStrategy[](1);
+ bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1);
IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
strategyArray[0] = strategy;
assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted");
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(strategy);
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted");
}
function test_AddAlreadyWhitelistedStrategy() external {
IStrategy[] memory strategyArray = new IStrategy[](1);
+ bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1);
IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
strategyArray[0] = strategy;
assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted");
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(strategy);
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit UpdatedThirdPartyTransfersForbidden(strategy, false);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted");
// Make sure event not emitted by checking logs length
cheats.recordLogs();
uint256 numLogsBefore = cheats.getRecordedLogs().length;
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
uint256 numLogsAfter = cheats.getRecordedLogs().length;
assertEq(numLogsBefore, numLogsAfter, "event emitted when strategy already whitelisted");
assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should still be whitelisted");
@@ -1284,11 +1271,12 @@ contract StrategyManagerUnitTests_removeStrategiesFromDepositWhitelist is Strate
IStrategy[] memory strategyArray = new IStrategy[](1);
IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
strategyArray[0] = strategy;
+ bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1);
assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted");
// Add strategy to whitelist first
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyAddedToDepositWhitelist(strategy);
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted");
// Now remove strategy from whitelist
diff --git a/src/test/utils/ProofParsing.sol b/src/test/utils/ProofParsing.sol
index 44b7de2de..5baf260b7 100644
--- a/src/test/utils/ProofParsing.sol
+++ b/src/test/utils/ProofParsing.sol
@@ -10,12 +10,14 @@ contract ProofParsing is Test {
bytes32[18] blockHeaderProof;
bytes32[3] slotProof;
- bytes32[9] withdrawalProof;
+ bytes32[10] withdrawalProofDeneb;
+ bytes32[9] withdrawalProofCapella;
bytes32[46] validatorProof;
bytes32[44] historicalSummaryProof;
bytes32[7] executionPayloadProof;
- bytes32[4] timestampProofs;
+ bytes32[5] timestampProofsCapella;
+ bytes32[4] timestampProofsDeneb;
bytes32 slotRoot;
bytes32 executionPayloadRoot;
@@ -79,14 +81,24 @@ contract ProofParsing is Test {
return executionPayloadProof;
}
- function getTimestampProof() public returns(bytes32[4] memory) {
+ function getTimestampProofDeneb() public returns(bytes32[5] memory) {
+ for (uint i = 0; i < 5; i++) {
+ prefix = string.concat(".TimestampProof[", string.concat(vm.toString(i), "]"));
+ timestampProofsCapella[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ }
+ return timestampProofsCapella;
+ }
+
+ function getTimestampProofCapella() public returns(bytes32[4] memory) {
for (uint i = 0; i < 4; i++) {
prefix = string.concat(".TimestampProof[", string.concat(vm.toString(i), "]"));
- timestampProofs[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ timestampProofsDeneb[i] = (stdJson.readBytes32(proofConfigJson, prefix));
}
- return timestampProofs;
+ return timestampProofsDeneb;
}
+
+
function getBlockHeaderProof() public returns(bytes32[18] memory) {
for (uint i = 0; i < 18; i++) {
prefix = string.concat(".BlockHeaderProof[", string.concat(vm.toString(i), "]"));
@@ -112,12 +124,20 @@ contract ProofParsing is Test {
return stateRootProof;
}
- function getWithdrawalProof() public returns(bytes32[9] memory) {
+ function getWithdrawalProofDeneb() public returns(bytes32[10] memory) {
+ for (uint i = 0; i < 10; i++) {
+ prefix = string.concat(".WithdrawalProof[", string.concat(vm.toString(i), "]"));
+ withdrawalProofDeneb[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ }
+ return withdrawalProofDeneb;
+ }
+
+ function getWithdrawalProofCapella() public returns(bytes32[9] memory) {
for (uint i = 0; i < 9; i++) {
prefix = string.concat(".WithdrawalProof[", string.concat(vm.toString(i), "]"));
- withdrawalProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ withdrawalProofCapella[i] = (stdJson.readBytes32(proofConfigJson, prefix));
}
- return withdrawalProof;
+ return withdrawalProofCapella;
}
function getValidatorProof() public returns(bytes32[46] memory) {