diff --git a/.env.example b/.env.example
index 086785858f..948f166c8b 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,5 @@
RPC_MAINNET="https://eth.llamarpc.com"
-# RPC_MAINNET="https://mainnet.infura.io/v3/API-KEY"
\ No newline at end of file
+# RPC_MAINNET="https://mainnet.infura.io/v3/API-KEY"
+RPC_GOERLI="https://ethereum-goerli.publicnode.com"
+RPC_HOLESKY=""
+ETHERSCAN_API_KEY="API-KEY"
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 8941d34b97..2d7342cf67 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -11,7 +11,7 @@ User stories are often expressed in a simple sentence, structured as follows: 'A
**Actions**
- [ ] An action item list describing the work to be done
-- [ ] Link to all of the related tasks needed to complete the feature. See the [tasks](https://github.com/Layr-Labs/docs/blob/949bd6b4ddd0ef08880c6775c2d9a6222e2e7eb3/.github/ISSUE_TEMPLATE/task.md) template.
+- [ ] Link to all of the related tasks needed to complete the feature. See the [tasks](https://github.com/Layr-Labs/eigenlayer-contracts/tree/master/.github/ISSUE_TEMPLATE/task.md) template.
- [ ] Include everything in the definition of done e.g. unit tests and documentation
**Acceptance criteria**
diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md
index e722873189..9d367028c2 100644
--- a/.github/ISSUE_TEMPLATE/task.md
+++ b/.github/ISSUE_TEMPLATE/task.md
@@ -7,7 +7,7 @@ labels: task
---
## Description
-Add a summary and description. Link to any parent [feature requests](https://github.com/Layr-Labs/docs/blob/c78dbcd9a4b229e367f11725ee6758271a65bad3/.github/ISSUE_TEMPLATE/feature_request.md) or [bug reports](https://github.com/Layr-Labs/docs/blob/c78dbcd9a4b229e367f11725ee6758271a65bad3/.github/ISSUE_TEMPLATE/bug_report.md).
+Add a summary and description. Link to any parent [feature requests](https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) or [bug reports](https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/.github/ISSUE_TEMPLATE/bug_report.md).
### Action Items
- [ ] An action item list describing the work to be done
diff --git a/.github/workflows/certora-prover.yml b/.github/workflows/certora-prover.yml
index 154d00950c..e255d8e29e 100644
--- a/.github/workflows/certora-prover.yml
+++ b/.github/workflows/certora-prover.yml
@@ -6,7 +6,7 @@ on:
- master
- release-v*
- formal-verification
- - fix-ci-errors
+ - m2-mainnet
pull_request: {}
workflow_dispatch: {}
@@ -38,21 +38,18 @@ jobs:
python-version: '3.10'
cache: 'pip'
- name: Install java
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v2
with:
- java-version: '11'
- java-package: 'jre'
+ distribution: temurin
+ java-version: '17'
- name: Install certora
- run: pip install certora-cli==3.6.8.post3
+ 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: |
- touch certora/applyHarness.patch
- make -C certora munged
bash ${{ matrix.params }}
env:
CERTORAKEY: ${{ secrets.CERTORAKEY }}
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
new file mode 100644
index 0000000000..fffa80d703
--- /dev/null
+++ b/.github/workflows/commitlint.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ commitlint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Install node dependencies
+ run: |
+ npm install conventional-changelog-conventionalcommits
+ npm install commitlint@18.2.0
+
+ - name: Validate current commit (last commit) with commitlint
+ if: github.event_name == 'push'
+ run: npx commitlint --from HEAD~1 --to HEAD --verbose
+
+ - name: Validate PR commits with commitlint
+ if: github.event_name == 'pull_request'
+ run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose
diff --git a/.github/workflows/run-deploy-scripts.yml b/.github/workflows/run-deploy-scripts.yml
new file mode 100644
index 0000000000..c43dc1b730
--- /dev/null
+++ b/.github/workflows/run-deploy-scripts.yml
@@ -0,0 +1,38 @@
+name: Run Deploy Scripts
+# We run the deploy scripts just to make sure they work
+
+on:
+ push:
+ pull_request:
+ types: [opened, reopened]
+
+jobs:
+ prepare:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ with:
+ submodules: true
+
+ # install foundry to run forge script. Should we run forge script in a container instead?
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly
+
+ - name: Start Anvil chain
+ # need to start Anvil chain with -d to let the container run in the background
+ # if we start with 'anvil &' instead, the process stops when the step ends
+ run: docker run -d --rm -p 8545:8545 --entrypoint anvil ghcr.io/foundry-rs/foundry:nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a --host 0.0.0.0
+
+ - name: Wait for Anvil chain to start
+ run: sleep 3
+
+ # Run Forge script against the Anvil chain
+ - name: Run M2_Deploy_From_Scratch
+ run: |
+ forge script script/deploy/devnet/M2_Deploy_From_Scratch.s.sol --rpc-url http://localhost:8545 \
+ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast \
+ --sig "run(string memory configFileName)" -- M2_deploy_from_scratch.anvil.config.json
diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml
new file mode 100644
index 0000000000..9275ed832f
--- /dev/null
+++ b/.github/workflows/slither.yml
@@ -0,0 +1,24 @@
+name: Slither Analysis
+
+on:
+ push:
+ pull_request:
+ types: [opened, reopened]
+
+jobs:
+ analyze:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Run Slither
+ uses: crytic/slither-action@v0.3.0
+ id: slither
+ with:
+ sarif: results.sarif
+ fail-on: none
+
+ - name: Upload SARIF file
+ uses: github/codeql-action/upload-sarif@v2
+ with:
+ sarif_file: ${{ steps.slither.outputs.sarif }}
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index d0a7aef300..0000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Automatic Testing
-
-on:
- push:
- pull_request:
- types: [opened, reopened]
-
-concurrency:
- group: ${{github.workflow}}-${{github.ref}}
- cancel-in-progress: true
-
-jobs:
- check:
- name: Foundry Project
- runs-on: ubuntu-22.04
- steps:
- - uses: actions/checkout@v3
- with:
- submodules: recursive
-
- - name: Install Foundry
- uses: foundry-rs/foundry-toolchain@v1
- with:
- version: nightly
-
- - name: Install forge dependencies
- run: forge install
-
- - name: Run tests
- run: forge test -vvv
- env:
- RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
- CHAIN_ID: ${{ secrets.CHAIN_ID }}
-
- - name: Run snapshot
- run: forge snapshot
- env:
- RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
- CHAIN_ID: ${{ secrets.CHAIN_ID }}
diff --git a/.github/workflows/testinparallel.yml b/.github/workflows/testinparallel.yml
new file mode 100644
index 0000000000..8ae97b9a48
--- /dev/null
+++ b/.github/workflows/testinparallel.yml
@@ -0,0 +1,65 @@
+name: Run Parallel
+
+on:
+ push:
+ pull_request:
+ types: [opened, reopened]
+
+jobs:
+ prepare:
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.set-matrix.outputs.matrix }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Get list of .t.sol files in src/test
+ run: |
+ FILES=$(find src/test -type f -name '*.t.sol' | sed 's#src/test/##' | jq -R -s -c 'split("\n")[:-1]')
+ echo "::set-output name=matrix::$FILES"
+ id: set-matrix
+
+ run-tests:
+ needs: prepare
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly
+
+ - name: Run Forge build
+ run: |
+ forge --version
+ forge build --sizes
+ id: build
+
+ - name: Run unit tests
+ run: forge test --no-match-contract Integration
+ env:
+ RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
+ RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }}
+ CHAIN_ID: ${{ secrets.CHAIN_ID }}
+
+ - name: Run integration tests
+ run: forge test --match-contract Integration
+ env:
+ RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
+ RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }}
+ CHAIN_ID: ${{ secrets.CHAIN_ID }}
+
+ - name: Run integration mainnet fork tests
+ run: forge test --match-contract Integration
+ env:
+ FOUNDRY_PROFILE: "forktest"
+ RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
+ RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }}
+ CHAIN_ID: ${{ secrets.CHAIN_ID }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e1019104ea..555235f94a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ broadcast
# Deployment tools
/data
+.idea/
# Certora Outputs
.certora_internal/
@@ -29,4 +30,16 @@ broadcast
#script config file
# script/M1_deploy.config.json
-script/output/M1_deployment_data.json
\ No newline at end of file
+script/output/M1_deployment_data.json
+/script/output/M2_deployment_data.json
+
+# autogenerated docs (you can generate these locally)
+/docs/docgen/
+
+script/misc
+
+test.sh
+
+# Surya outputs
+InheritanceGraph.png
+surya_report.md
\ No newline at end of file
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100755
index 0000000000..c160a77123
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx --no -- commitlint --edit ${1}
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000000..1fc21ba239
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,16 @@
+{
+ "plugins": ["prettier-plugin-solidity"],
+ "overrides": [
+ {
+ "files": "*.sol",
+ "options": {
+ "parser": "solidity-parse",
+ "printWidth": 120,
+ "tabWidth": 4,
+ "useTabs": false,
+ "singleQuote": false,
+ "bracketSpacing": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.solhint.json b/.solhint.json
index 20216cba0f..8c451d3fd5 100644
--- a/.solhint.json
+++ b/.solhint.json
@@ -1,16 +1,21 @@
{
"extends": "solhint:recommended",
"rules": {
- "max-line-length": ["warn",200],
+ "max-line-length": "off",
"no-inline-assembly": "off",
"reason-string": ["warn",{"maxLength":160}],
"func-visibility": ["warn",{"ignoreConstructors":true}],
+ "explicit-types": ["warn","explicit"],
+ "quotes": ["warn","double"],
"const-name-snakecase": "off",
"not-rely-on-time": "off",
"avoid-low-level-calls": "off",
"contract-name-camelcase": "off",
"func-name-mixedcase": "off",
"var-name-mixedcase": "off",
- "compiler-version": "off"
+ "compiler-version": "off",
+ "custom-errors": "off",
+ "no-global-import": "off",
+ "immutable-vars-naming": "off"
}
}
diff --git a/.solhintignore b/.solhintignore
new file mode 100644
index 0000000000..497fd271cf
--- /dev/null
+++ b/.solhintignore
@@ -0,0 +1 @@
+Slasher.sol
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..8717c11abf
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,36 @@
+
+## Requirements
+
+Foundry
+Git
+Node.js
+
+## Setup Repo
+
+```bash
+git clone git@github.com:Layr-Labs/eigenlayer-contracts.git
+```
+
+### Install Dependencies
+
+```bash
+npm install
+
+npx husky install
+
+forge install
+```
+
+### Pull Requests
+
+All tests must pass
+
+Commits must be linted
+
+A descriptive summary of the PR has been provided.
+
+### Environment Variables
+
+Some of the tests and features of this repository require environment variables to be set up.
+
+Copy the .env.example file to create a .env and populate it with the appropriate environment values that you have control over
diff --git a/LICENSE b/LICENSE
index 642db295af..8c7af6bab8 100644
--- a/LICENSE
+++ b/LICENSE
@@ -9,12 +9,19 @@ Parameters
Licensor: Layr Labs, Inc.
-Licensed Work: EigenLayer Contracts
+Licensed Work: EigenLayer Core Contracts
The Licensed Work is (c) 2023 Layr Labs, Inc.
Additional Use Grant: None.
-Change Date: 2025-05-01 (May 1st, 2025)
+Change Dates:
+
+- All commits at or prior to commit 6de01c6c16d6df44af15f0b06809dc160eac0ebf
+(i.e. committed to this repository on or before February 6, 2024, the date of
+the [v0.2.1-goerli-m2](https://github.com/Layr-Labs/eigenlayer-contracts/releases/tag/v0.2.1-goerli-m2) release)
+have a change date of 2025-05-01 (May 1st, 2025)
+- All commits after 6de01c6c16d6df44af15f0b06809dc160eac0ebf (i.e. committed to this
+repository after February 6, 2024) have a change date of 2027-02-06 (February 6th, 2027)
Change License: MIT
diff --git a/README.md b/README.md
index d9e2f458ab..5c1b46a36f 100644
--- a/README.md
+++ b/README.md
@@ -1,78 +1,148 @@
+
# EigenLayer
-EigenLayer (formerly 'EigenLayr') is a set of smart contracts deployed on Ethereum that enable restaking of assets to secure new services.
-At present, this repository contains *both* the contracts for EigenLayer *and* a set of general "middleware" contracts, designed to be reuseable across different applications built on top of EigenLayer.
-Note that the interactions between middleware and EigenLayer are not yet "set in stone", and may change somewhat prior to the platform being fully live on mainnet; in particular, payment architecture is likely to evolve. As such, the "middleware" contracts should not be treated as definitive, but merely as a helpful reference, at least until the architecture is more settled.
+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.
+
+## Getting Started
-Click the links in the Table of Contents below to access more specific documentation. We recommend starting with the [EigenLayer Technical Specification](docs/EigenLayer-tech-spec.md) to get a better overview before diving into any of the other docs. For contracts addresses deployed on Goerli, click [here](https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/script/output/M1_deployment_goerli_2023_3_23.json).
-## Table of Contents
-* [Introduction](#introduction)
-* [Installation and Running Tests / Analyzers](#installation)
-* [EigenLayer Technical Specification](docs/EigenLayer-tech-spec.md)
+* [Branching](#branching)
+* [Documentation](#documentation)
+* [Building and Running Tests](#building-and-running-tests)
+* [Deployments](#deployments)
-Design Docs
-* [Withdrawals Design Doc](docs/Guaranteed-stake-updates.md)
-* [EigenPods Design Doc](docs/EigenPods.md)
+## Branching
-Flow Docs
-* [EigenLayer Withdrawal Flow](docs/EigenLayer-withdrawal-flow.md)
-* [EigenLayer Deposit Flow](docs/EigenLayer-deposit-flow.md)
-* [EigenLayer Delegation Flow](docs/EigenLayer-delegation-flow.md)
-* [Middleware Registration Flow for Operators](docs/Middleware-registration-operator-flow.md)
+The main branches we use are:
+* [`dev (default)`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/dev): The most up-to-date branch, containing the work-in-progress code for upcoming releases
+* [`testnet-holesky`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/testnet-holesky): Our current testnet deployment
+* [`mainnet`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/mainnet): Our current mainnet deloyment
-
-## Installation and Running Tests / Analyzers
+## Documentation
-### Installation
+### Basics
-`foundryup`
+To get a basic understanding of EigenLayer, check out [You Could've Invented EigenLayer](https://www.blog.eigenlayer.xyz/ycie/). Note that some of the document's content describes features that do not exist yet (like the Slasher). To understand more about how restakers and operators interact with EigenLayer, check out these guides:
+* [Restaking User Guide](https://docs.eigenlayer.xyz/restaking-guides/restaking-user-guide)
+* [Operator Guide](https://docs.eigenlayer.xyz/operator-guides/operator-introduction)
-This repository uses Foundry as a smart contract development toolchain.
+### Deep Dive
-See the [Foundry Docs](https://book.getfoundry.sh/) for more info on installation and usage.
+The most up-to-date and technical documentation can be found in [/docs](/docs). If you're a shadowy super coder, this is a great place to get an overview of the contracts before diving into the code.
-### Natspec Documentation
+To get an idea of how users interact with these contracts, check out our integration tests: [/src/test/integration](./src/test/integration/).
-You will notice that we also have hardhat installed in this repo. This is only used to generate natspec [docgen](https://github.com/OpenZeppelin/solidity-docgen). This is our workaround until foundry [finishes implementing](https://github.com/foundry-rs/foundry/issues/1675) the `forge doc` command.
+## Building and Running Tests
-To generate the docs, run `npx hardhat docgen` (you may need to run `npm install` first). The output is located in `docs/docgen`
+This repository uses Foundry. See the [Foundry docs](https://book.getfoundry.sh/) for more info on installation and usage. If you already have foundry, you can build this project and run tests with these commands:
-### Run Tests
+```
+foundryup
-Prior to running tests, you should set up your environment. At present this repository contains fork tests against ETH mainnet; your environment will need an `RPC_MAINNET` key to run these tests. See the `.env.example` file for an example -- two simple options are to copy the LlamaNodes RPC url to your `env` or use your own infura API key in the provided format.
+forge build
+forge test
+```
-The main command to run tests is:
+### Running Fork Tests
-`forge test -vv`
+We have a few fork tests against ETH mainnet. Passing these requires the environment variable `RPC_MAINNET` to be set. See `.env.example` for an example. Once you've set up your environment, `forge test` should show these fork tests passing.
-### Run Tests on a Fork
-Environment config is contained in config.yml. Before running the following commands, [install yq](https://mikefarah.gitbook.io/yq/v/v3.x/). Then set up the environment with this script:
+Additionally, to run all tests in a forked environment, [install yq](https://mikefarah.gitbook.io/yq/v/v3.x/). Then, set up your environment using this script to read from `config.yml`:
-`source source-env.sh [CHAIN]`
+`source source-env.sh [goerli|local]`
-for example, on goerli: `source source-env.sh goerli`. Currently options for `[CHAIN]` are `goerli`, `local`. Then to run the actual tests:
+Then run the tests:
`forge test --fork-url [RPC_URL]`
-### Run Static Analysis
+### Running Static Analysis
+
+1. Install [solhint](https://github.com/protofire/solhint), then run:
`solhint 'src/contracts/**/*.sol'`
+2. Install [slither](https://github.com/crytic/slither), then run:
+
`slither .`
### Generate Inheritance and Control-Flow Graphs
-first [install surya](https://github.com/ConsenSys/surya/)
-
-then run
-
-`surya inheritance ./src/contracts/**/*.sol | dot -Tpng > InheritanceGraph.png`
-
-and/or
-
-`surya graph ./src/contracts/middleware/*.sol | dot -Tpng > MiddlewareControlFlowGraph.png`
-
-and/or
+1. Install [surya](https://github.com/ConsenSys/surya/) and graphviz:
+
+```
+npm i -g surya
+
+apt install graphviz
+```
+
+2. Then, run:
+
+```
+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 below, or check out the code itself on the [`mainnet`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/mainnet) branch.
+
+| Name | Solidity | Proxy | Implementation | Notes |
+| -------- | -------- | -------- | -------- | -------- |
+| StrategyManager | [`StrategyManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/core/StrategyManager.sol) | [`0x8586...075A`](https://etherscan.io/address/0x858646372CC42E1A627fcE94aa7A7033e7CF075A) | [`0x70f4...619b`](https://etherscan.io/address/0x70f44c13944d49a236e3cd7a94f48f5dab6c619b) | 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/mainnet/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/mainnet/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/mainnet/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/mainnet/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/mainnet/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/mainnet/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/mainnet/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/mainnet/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/mainnet/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/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/pods/EigenPodManager.sol) | [`0x91E6...A338`](https://etherscan.io/address/0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338) | [`0xe429...5762`](https://etherscan.io/address/0xe4297e3dadbc7d99e26a2954820f514cb50c5762) | 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/eigenlayer-beacon-oracle/blob/main/contracts/src/EigenLayerBeaconOracle.sol) | - | [`0x3439...5442`](https://etherscan.io/address/0x343907185b71adf0eba9567538314396aa985442) | |
+| EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/pods/EigenPod.sol) | [`0x5a2a...9073`](https://etherscan.io/address/0x5a2a4F2F3C18f09179B6703e63D9eDD165909073) | [`0x8ba4...a255`](https://etherscan.io/address/0x8ba40da60f0827d027f029acee62609f0527a255) | - 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/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x7Fe7...23D8`](https://etherscan.io/address/0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8) | [`0x4bb6...4226`](https://etherscan.io/address/0x4bb6731b02314d40abbffbc4540f508874014226) | 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/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/core/DelegationManager.sol) | [`0x3905...f37A`](https://etherscan.io/address/0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A) | [`0x1784...9dda`](https://etherscan.io/address/0x1784be6401339fc0fedf7e9379409f5c1bfe9dda) | 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/4b15d68b7e16b5965bad398496bfce57f5a47e1b/src/contracts/core/Slasher.sol) | [`0xD921...c3Cd`](https://etherscan.io/address/0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd) | [`0xf323...6614`](https://etherscan.io/address/0xf3234220163a757edf1e11a8a085638d9b236614) | 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 on holesky, and is from our M2 beta release. You can view the deployed contract addresses below, or check out the code itself on the [`testnet-holesky`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/testnet-holesky) branch.
+
+| Name | Solidity | Proxy | Implementation | Notes |
+| -------- | -------- | -------- | -------- | -------- |
+| StrategyManager | [`StrategyManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/testnet-holesky/src/contracts/core/StrategyManager.sol) | [`0xdfB5...D5b6`](https://holesky.etherscan.io/address/0xdfB5f6CE42aAA7830E94ECFCcAd411beF4d4D5b6) | [`0x59f7...3a18`](https://holesky.etherscan.io/address/0x59f766A603C53f3AC8Be43bBe158c1519b193a18) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x7D70...63d3`](https://holesky.etherscan.io/address/0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x3A8f...c4E0`](https://holesky.etherscan.io/address/0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| Strategy: WETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x8052...FaF9`](https://holesky.etherscan.io/address/0x80528D6e9A2BAbFc766965E0E26d5aB08D9CFaF9) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x5037...D943`](https://holesky.etherscan.io/address/0x05037A81BD7B4C9E0F7B430f1F2A22c31a2FD943) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x9281...631c`](https://holesky.etherscan.io/address/0x9281ff96637710Cd9A5CAcce9c6FAD8C9F54631c) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x31B6...6af7`](https://holesky.etherscan.io/address/0x31B6F59e1627cEfC9fA174aD03859fC337666af7) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x4628...C6Ef`](https://holesky.etherscan.io/address/0x46281E3B7fDcACdBa44CADf069a94a588Fd4C6Ef) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x70EB...bAB6`](https://holesky.etherscan.io/address/0x70EB4D3c164a6B4A5f908D4FBb5a9cAfFb66bAB6) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xaccc...F8e5`](https://holesky.etherscan.io/address/0xaccc5A86732BE85b5012e8614AF237801636F8e5) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x7673...d0ac`](https://holesky.etherscan.io/address/0x7673a47463F80c6a3553Db9E54c8cDcd5313d0ac) | [`0xFb83...3305`](https://holesky.etherscan.io/address/0xFb83e1D133D0157775eC4F19Ff81478Df1103305) | 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/testnet-holesky/src/contracts/pods/EigenPodManager.sol) | [`0x3077...e315`](https://holesky.etherscan.io/address/0x30770d7E3e71112d7A6b7259542D1f680a70e315) | [`0x5265...4a7B`](https://holesky.etherscan.io/address/0x5265C162f7d5F3fE3175a78828ab16bf5E324a7B) | 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/eigenlayer-beacon-oracle/blob/main/contracts/src/EigenLayerBeaconOracle.sol) | - | [`0x4C11...8f25`](https://holesky.etherscan.io/address/0x4C116BB629bff7A8373c2378bBd919f8349B8f25) | |
+| EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/testnet-holesky/src/contracts/pods/EigenPod.sol) | [`0x7261...832D`](https://holesky.etherscan.io/address/0x7261C2bd75a7ACE1762f6d7FAe8F63215581832D) | [`0xe98f...641c`](https://holesky.etherscan.io/address/0xe98f9298344527608A1BCC23907B8145F9Cb641c) | - 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/testnet-holesky/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x642c...FA32`](https://holesky.etherscan.io/address/0x642c646053eaf2254f088e9019ACD73d9AE0FA32) | [`0xcE8b...3407`](https://holesky.etherscan.io/address/0xcE8b8D99773a718423F8040a6e52c06a4ce63407) | 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/testnet-holesky/src/contracts/core/DelegationManager.sol) | [`0xA441...48e7`](https://holesky.etherscan.io/address/0xA44151489861Fe9e3055d95adC98FbD462B948e7) | [`0x83f8...0D76`](https://holesky.etherscan.io/address/0x83f8F8f0BB125F7870F6bfCf76853f874C330D76) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| AVSDirectory | [`AVSDirectory`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/testnet-holesky/src/contracts/core/AVSDirectory.sol) | [`0x0557...FeBf`](https://holesky.etherscan.io/address/0x055733000064333CaDDbC92763c58BF0192fFeBf) | [`0xEF5B...3e3a`](https://holesky.etherscan.io/address/0xEF5BA995Bc7722fd1e163edF8Dc09375de3d3e3a) | 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/testnet-holesky/src/contracts/core/Slasher.sol) | [`0xcAe7...6e7C`](https://holesky.etherscan.io/address/0xcAe751b75833ef09627549868A04E32679386e7C) | [`0x9971...345A`](https://holesky.etherscan.io/address/0x99715D255E34a39bE9943b82F281CA734bcF345A) | 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/testnet-holesky/src/contracts/permissions/PauserRegistry.sol) | - | [`0x85Ef...2F06`](https://holesky.etherscan.io/address/0x85Ef7299F8311B25642679edBF02B62FA2212F06) | |
+| Timelock | [Compound: `Timelock.sol`](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Timelock.sol) | - | [`0xcF19...0A7D`](https://holesky.etherscan.io/address/0xcF19CE0561052a7A7Ff21156730285997B350A7D) | |
+| Proxy Admin | [OpenZeppelin ProxyAdmin@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/ProxyAdmin.sol) | - | [`0xDB02...A6cf`](https://holesky.etherscan.io/address/0xDB023566064246399b4AE851197a97729C93A6cf) | |
-`surya mdreport surya_report.md ./src/contracts/**/*.sol`
diff --git a/audits/Sigma Prime - Core Audit - v2.0 FINAL - Feb'24.pdf b/audits/Sigma Prime - Core Audit - v2.0 FINAL - Feb'24.pdf
new file mode 100644
index 0000000000..adf03cc3d9
Binary files /dev/null and b/audits/Sigma Prime - Core Audit - v2.0 FINAL - Feb'24.pdf differ
diff --git a/audits/Sigma_Prime_Layr_Labs_Eigen_Layer_2_Security_Assessment_v1.pdf b/audits/Sigma_Prime_Eigen_Layer_Phase_2_Security_Assessment_Report_v2_1.pdf
similarity index 64%
rename from audits/Sigma_Prime_Layr_Labs_Eigen_Layer_2_Security_Assessment_v1.pdf
rename to audits/Sigma_Prime_Eigen_Layer_Phase_2_Security_Assessment_Report_v2_1.pdf
index 61c72f8c91..cbfd2f87f1 100644
Binary files a/audits/Sigma_Prime_Layr_Labs_Eigen_Layer_2_Security_Assessment_v1.pdf and b/audits/Sigma_Prime_Eigen_Layer_Phase_2_Security_Assessment_Report_v2_1.pdf differ
diff --git a/certora/.gitignore b/certora/.gitignore
deleted file mode 100644
index 2843792238..0000000000
--- a/certora/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-munged
\ No newline at end of file
diff --git a/certora/Makefile b/certora/Makefile
deleted file mode 100644
index 2e1af83cb0..0000000000
--- a/certora/Makefile
+++ /dev/null
@@ -1,25 +0,0 @@
-default: help
-
-PATCH = applyHarness.patch
-CONTRACTS_DIR = ../src/contracts
-MUNGED_DIR = munged
-
-help:
- @echo "usage:"
- @echo " make clean: remove all generated files (those ignored by git)"
- @echo " make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)"
- @echo " make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)"
-
-munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH)
- rm -rf $@
- cp -r $(CONTRACTS_DIR) $@
- patch -p0 -d $@ < $(PATCH)
-
-record:
- diff -druN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+../contracts/++g' | sed 's+munged/++g' > $(PATCH)
-
-refresh: munged record
-
-clean:
- git clean -fdX
- touch $(PATCH)
diff --git a/certora/applyHarness.patch b/certora/applyHarness.patch
deleted file mode 100644
index e215a8f113..0000000000
--- a/certora/applyHarness.patch
+++ /dev/null
@@ -1,17 +0,0 @@
-diff -druN ../score/DelegationManager.sol core/DelegationManager.sol
---- ../score/DelegationManager.sol 2023-01-13 14:12:34
-+++ core/DelegationManager.sol 2023-01-13 14:24:43
-@@ -160,10 +160,10 @@
- */
- function decreaseDelegatedShares(
- address staker,
-- IStrategy[] calldata strategies,
-- uint256[] calldata shares
-+ IStrategy[] memory strategies, // MUNGED calldata => memory
-+ uint256[] memory shares // MUNGED calldata => memory
- )
-- external
-+ public // MUNGED external => public
- onlyStrategyManager
- {
- if (isDelegated(staker)) {
\ No newline at end of file
diff --git a/certora/harnesses/DelegationManagerHarness.sol b/certora/harnesses/DelegationManagerHarness.sol
index 988ca44040..9f2366ee6e 100644
--- a/certora/harnesses/DelegationManagerHarness.sol
+++ b/certora/harnesses/DelegationManagerHarness.sol
@@ -1,31 +1,28 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "../munged/core/DelegationManager.sol";
+import "../../src/contracts/core/DelegationManager.sol";
contract DelegationManagerHarness is DelegationManager {
- constructor(IStrategyManager _strategyManager, ISlasher _slasher) DelegationManager(_strategyManager, _slasher) {}
+ constructor(IStrategyManager _strategyManager, ISlasher _slasher, IEigenPodManager _eigenPodManager)
+ DelegationManager(_strategyManager, _slasher, _eigenPodManager) {}
-
- /// Harnessed functions
- function decreaseDelegatedShares(
- address staker,
- IStrategy strategy1,
- IStrategy strategy2,
- uint256 share1,
- uint256 share2
- ) external {
- IStrategy[] memory strategies = new IStrategy[](2);
- uint256[] memory shares = new uint256[](2);
- strategies[0] = strategy1;
- strategies[1] = strategy2;
- shares[0] = share1;
- shares[1] = share2;
- super.decreaseDelegatedShares(staker,strategies,shares);
+ function get_operatorShares(address operator, IStrategy strategy) public view returns (uint256) {
+ return operatorShares[operator][strategy];
}
- function get_operatorShares(address operator, IStrategy strategy) public view returns(uint256) {
- return operatorShares[operator][strategy];
+ function get_stakerDelegateableShares(address staker, IStrategy strategy) public view returns (uint256) {
+ // this is the address of the virtual 'beaconChainETH' strategy
+ if (address(strategy) == 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0) {
+ int256 beaconChainETHShares = eigenPodManager.podOwnerShares(staker);
+ if (beaconChainETHShares <= 0) {
+ return 0;
+ } else {
+ return uint256(beaconChainETHShares);
+ }
+ } else {
+ return strategyManager.stakerStrategyShares(staker, strategy);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/certora/harnesses/EigenPodHarness.sol b/certora/harnesses/EigenPodHarness.sol
new file mode 100644
index 0000000000..0462b9e1af
--- /dev/null
+++ b/certora/harnesses/EigenPodHarness.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../../src/contracts/pods/EigenPod.sol";
+
+contract EigenPodHarness is EigenPod {
+
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IDelayedWithdrawalRouter _delayedWithdrawalRouter,
+ IEigenPodManager _eigenPodManager,
+ uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ uint64 _GENESIS_TIME
+ )
+ EigenPod(_ethPOS, _delayedWithdrawalRouter, _eigenPodManager, _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, _GENESIS_TIME) {}
+
+ function get_validatorIndex(bytes32 pubkeyHash) public view returns (uint64) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].validatorIndex;
+ }
+
+ function get_restakedBalanceGwei(bytes32 pubkeyHash) public view returns (uint64) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].restakedBalanceGwei;
+ }
+
+ function get_mostRecentBalanceUpdateTimestamp(bytes32 pubkeyHash) public view returns (uint64) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].mostRecentBalanceUpdateTimestamp;
+ }
+
+ function get_podOwnerShares() public view returns (int256) {
+ return eigenPodManager.podOwnerShares(podOwner);
+ }
+
+ function get_withdrawableRestakedExecutionLayerGwei() public view returns (uint256) {
+ return withdrawableRestakedExecutionLayerGwei;
+ }
+
+ function get_ETH_Balance() public view returns (uint256) {
+ return address(this).balance;
+ }
+}
diff --git a/certora/harnesses/EigenPodManagerHarness.sol b/certora/harnesses/EigenPodManagerHarness.sol
new file mode 100644
index 0000000000..84576ec9fe
--- /dev/null
+++ b/certora/harnesses/EigenPodManagerHarness.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../../src/contracts/pods/EigenPodManager.sol";
+
+contract EigenPodManagerHarness is EigenPodManager {
+
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IBeacon _eigenPodBeacon,
+ IStrategyManager _strategyManager,
+ ISlasher _slasher,
+ IDelegationManager _delegationManager
+ )
+ EigenPodManager(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {}
+
+ function get_podOwnerShares(address podOwner) public view returns (int256) {
+ return podOwnerShares[podOwner];
+ }
+
+ function get_podByOwner(address podOwner) public view returns (IEigenPod) {
+ return ownerToPod[podOwner];
+ }
+}
diff --git a/certora/harnesses/PausableHarness.sol b/certora/harnesses/PausableHarness.sol
index c767c977cb..fc0095ebe1 100644
--- a/certora/harnesses/PausableHarness.sol
+++ b/certora/harnesses/PausableHarness.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "../munged/permissions/Pausable.sol";
+import "../../src/contracts/permissions/Pausable.sol";
contract PausableHarness is Pausable {
// getters
@@ -21,4 +21,4 @@ contract PausableHarness is Pausable {
function bitwise_and(uint256 input_1, uint256 input_2) external pure returns (uint256) {
return (input_1 & input_2);
}
-}
\ No newline at end of file
+}
diff --git a/certora/harnesses/SlasherHarness.sol b/certora/harnesses/SlasherHarness.sol
index 897393e3e9..5198957807 100644
--- a/certora/harnesses/SlasherHarness.sol
+++ b/certora/harnesses/SlasherHarness.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "../munged/core/Slasher.sol";
+import "../../src/contracts/core/Slasher.sol";
contract SlasherHarness is Slasher {
@@ -58,28 +58,28 @@ contract SlasherHarness is Slasher {
return (_operatorToWhitelistedContractsByUpdate[operator].list[node][direction]);
}
- // uses that _HEAD = 0. Similar to StructuredLinkedList.nodeExists but slightly better defined
- function nodeDoesExist(address operator, uint256 node) public returns (bool) {
- if (get_next_node(operator, node) == 0 && get_previous_node(operator, node) == 0) {
- // slightly stricter check than that defined in StructuredLinkedList.nodeExists
- if (get_next_node(operator, 0) == node && get_previous_node(operator, 0) == node) {
- return true;
- } else {
- return false;
- }
- } else {
- return true;
- }
- }
-
- // uses that _PREV = false, _NEXT = true
- function nodeIsWellLinked(address operator, uint256 node) public returns (bool) {
- return (
- // node is not linked to itself
- get_previous_node(operator, node) != node && get_next_node(operator, node) != node
- &&
- // node is the previous node's next node and the next node's previous node
- get_linked_list_entry(operator, get_previous_node(operator, node), true) == node && get_linked_list_entry(operator, get_next_node(operator, node), false) == node
- );
- }
-}
\ No newline at end of file
+ // // uses that _HEAD = 0. Similar to StructuredLinkedList.nodeExists but slightly better defined
+ // function nodeDoesExist(address operator, uint256 node) public returns (bool) {
+ // if (get_next_node(operator, node) == 0 && get_previous_node(operator, node) == 0) {
+ // // slightly stricter check than that defined in StructuredLinkedList.nodeExists
+ // if (get_next_node(operator, 0) == node && get_previous_node(operator, 0) == node) {
+ // return true;
+ // } else {
+ // return false;
+ // }
+ // } else {
+ // return true;
+ // }
+ // }
+
+ // // uses that _PREV = false, _NEXT = true
+ // function nodeIsWellLinked(address operator, uint256 node) public returns (bool) {
+ // return (
+ // // node is not linked to itself
+ // get_previous_node(operator, node) != node && get_next_node(operator, node) != node
+ // &&
+ // // node is the previous node's next node and the next node's previous node
+ // get_linked_list_entry(operator, get_previous_node(operator, node), true) == node && get_linked_list_entry(operator, get_next_node(operator, node), false) == node
+ // );
+ // }
+}
diff --git a/certora/harnesses/StrategyManagerHarness.sol b/certora/harnesses/StrategyManagerHarness.sol
index c8b426202d..441037032d 100644
--- a/certora/harnesses/StrategyManagerHarness.sol
+++ b/certora/harnesses/StrategyManagerHarness.sol
@@ -1,65 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "../munged/core/StrategyManager.sol";
+import "../../src/contracts/core/StrategyManager.sol";
contract StrategyManagerHarness is StrategyManager {
constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher)
StrategyManager(_delegation, _eigenPodManager, _slasher)
{}
- function slashSharesSinglet(
- address slashedAddress,
- address recipient,
- IStrategy strategy,
- IERC20 token,
- uint256 strategyIndex,
- uint256 shareAmount
- )
- external
- onlyOwner
- onlyFrozen(slashedAddress)
- nonReentrant
- {
- IStrategy[] memory strategies = new IStrategy[](1);
- strategies[0] = strategy;
- IERC20[] memory tokens = new IERC20[](1);
- tokens[0] = token;
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = strategyIndex;
- uint256[] memory shareAmounts = new uint256[](1);
- shareAmounts[0] = shareAmount;
- require(tokens.length == strategies.length, "StrategyManager.slashShares: input length mismatch");
- uint256 strategyIndexIndex;
- uint256 strategiesLength = strategies.length;
- for (uint256 i = 0; i < strategiesLength;) {
- // the internal function will return 'true' in the event the strategy was
- // removed from the slashedAddress's array of strategies -- i.e. stakerStrategyList[slashedAddress]
- if (_removeShares(slashedAddress, strategyIndexes[strategyIndexIndex], strategies[i], shareAmounts[i])) {
- unchecked {
- ++strategyIndexIndex;
- }
- }
-
- if (strategies[i] == beaconChainETHStrategy) {
- //withdraw the beaconChainETH to the recipient
- _withdrawBeaconChainETH(slashedAddress, recipient, shareAmounts[i]);
- }
- else {
- // withdraw the shares and send funds to the recipient
- strategies[i].withdraw(recipient, tokens[i], shareAmounts[i]);
- }
-
- // increment the loop
- unchecked {
- ++i;
- }
- }
-
- // modify delegated shares accordingly, if applicable
- delegation.decreaseDelegatedShares(slashedAddress, strategies, shareAmounts);
- }
-
function strategy_is_in_stakers_array(address staker, IStrategy strategy) public view returns (bool) {
uint256 length = stakerStrategyList[staker].length;
for (uint256 i = 0; i < length; ++i) {
@@ -101,6 +49,10 @@ contract StrategyManagerHarness is StrategyManager {
}
function totalShares(address strategy) public view returns (uint256) {
- return IStrategy(strategy).totalShares();
+ return IStrategy(strategy).totalShares();
+ }
+
+ function get_stakerStrategyShares(address staker, IStrategy strategy) public view returns (uint256) {
+ return stakerStrategyShares[staker][strategy];
}
-}
\ No newline at end of file
+}
diff --git a/certora/harnesses/StructuredLinkedListHarness.sol b/certora/harnesses/StructuredLinkedListHarness.sol
index 48c26dd599..c653abde49 100644
--- a/certora/harnesses/StructuredLinkedListHarness.sol
+++ b/certora/harnesses/StructuredLinkedListHarness.sol
@@ -2,7 +2,7 @@
pragma solidity ^0.8.0;
-import "../munged/libraries/StructuredLinkedList.sol";
+import "../../src/contracts/libraries/StructuredLinkedList.sol";
/**
* @title StructuredLinkedList
diff --git a/certora/harnesses/properties.md b/certora/harnesses/properties.md
deleted file mode 100644
index 66e24f8448..0000000000
--- a/certora/harnesses/properties.md
+++ /dev/null
@@ -1,10 +0,0 @@
-nonhead node can't point to itself
-
-no dead ends ( can't point to 0 if 0 doesn't point back )
-
-head points to itself in both directions or not at all
-
-
-if node x points at node y, node y must point back at node x
-
-size == # of nodes with nonzero next == # of nodes with nonzero prev
\ No newline at end of file
diff --git a/certora/scripts/core/verifyDelegationManager.sh b/certora/scripts/core/verifyDelegationManager.sh
index 2906d589af..23c3c124af 100644
--- a/certora/scripts/core/verifyDelegationManager.sh
+++ b/certora/scripts/core/verifyDelegationManager.sh
@@ -3,16 +3,18 @@ then
RULE="--rule $2"
fi
+solc-select use 0.8.12
certoraRun certora/harnesses/DelegationManagerHarness.sol \
lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol \
- certora/munged/pods/EigenPodManager.sol certora/munged/pods/EigenPod.sol certora/munged/strategies/StrategyBase.sol certora/munged/core/StrategyManager.sol \
- certora/munged/core/Slasher.sol certora/munged/permissions/PauserRegistry.sol \
+ src/contracts/pods/EigenPodManager.sol src/contracts/pods/EigenPod.sol src/contracts/strategies/StrategyBase.sol src/contracts/core/StrategyManager.sol \
+ src/contracts/core/Slasher.sol src/contracts/permissions/PauserRegistry.sol \
--verify DelegationManagerHarness:certora/specs/core/DelegationManager.spec \
--optimistic_loop \
- --send_only \
- --settings -optimisticFallback=true \
+ --optimistic_fallback \
+ --optimistic_hashing \
+ --parametric_contracts DelegationManagerHarness \
$RULE \
- --loop_iter 3 \
+ --loop_iter 2 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
- --msg "DelegationManager $1 $2" \
\ No newline at end of file
+ --msg "DelegationManager $1 $2" \
diff --git a/certora/scripts/core/verifySlasher.sh b/certora/scripts/core/verifySlasher.sh
deleted file mode 100644
index c1c53f2b41..0000000000
--- a/certora/scripts/core/verifySlasher.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-if [[ "$2" ]]
-then
- RULE="--rule $2"
-fi
-
-solc-select use 0.8.12
-
-certoraRun certora/harnesses/SlasherHarness.sol \
- lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol \
- certora/munged/pods/EigenPodManager.sol certora/munged/pods/EigenPod.sol certora/munged/strategies/StrategyBase.sol certora/munged/core/DelegationManager.sol \
- certora/munged/core/StrategyManager.sol certora/munged/permissions/PauserRegistry.sol \
- --verify SlasherHarness:certora/specs/core/Slasher.spec \
- --optimistic_loop \
- --send_only \
- --settings -optimisticFallback=true,-recursionErrorAsAssert=false,-recursionEntryLimit=3 \
- --loop_iter 3 \
- --link SlasherHarness:delegation=DelegationManager \
- $RULE \
- --packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
- --msg "Slasher $1 $2" \
\ No newline at end of file
diff --git a/certora/scripts/core/verifyStrategyManager.sh b/certora/scripts/core/verifyStrategyManager.sh
index 964ac6b774..52a1aa25d0 100644
--- a/certora/scripts/core/verifyStrategyManager.sh
+++ b/certora/scripts/core/verifyStrategyManager.sh
@@ -3,19 +3,19 @@ then
RULE="--rule $2"
fi
-solc-select use 0.8.12
+solc-select use 0.8.12
certoraRun certora/harnesses/StrategyManagerHarness.sol \
lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol \
- certora/munged/pods/EigenPodManager.sol certora/munged/pods/EigenPod.sol certora/munged/pods/DelayedWithdrawalRouter.sol \
- certora/munged/strategies/StrategyBase.sol certora/munged/core/DelegationManager.sol \
- certora/munged/core/Slasher.sol certora/munged/permissions/PauserRegistry.sol \
+ src/contracts/pods/EigenPodManager.sol src/contracts/pods/EigenPod.sol src/contracts/pods/DelayedWithdrawalRouter.sol \
+ src/contracts/strategies/StrategyBase.sol src/contracts/core/DelegationManager.sol \
+ src/contracts/core/Slasher.sol src/contracts/permissions/PauserRegistry.sol \
--verify StrategyManagerHarness:certora/specs/core/StrategyManager.spec \
--optimistic_loop \
- --send_only \
- --settings -optimisticFallback=true \
- --settings -optimisticUnboundedHashing=true \
+ --optimistic_fallback \
+ --optimistic_hashing \
+ --parametric_contracts StrategyManagerHarness \
$RULE \
- --loop_iter 3 \
+ --loop_iter 2 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
- --msg "StrategyManager $1 $2" \
\ No newline at end of file
+ --msg "StrategyManager $1 $2" \
diff --git a/certora/scripts/libraries/verifyStructuredLinkedList.sh b/certora/scripts/libraries/verifyStructuredLinkedList.sh
index 1527d1d87c..0097243f36 100644
--- a/certora/scripts/libraries/verifyStructuredLinkedList.sh
+++ b/certora/scripts/libraries/verifyStructuredLinkedList.sh
@@ -3,12 +3,13 @@ then
RULE="--rule $2"
fi
+solc-select use 0.8.12
certoraRun certora/harnesses/StructuredLinkedListHarness.sol \
--verify StructuredLinkedListHarness:certora/specs/libraries/StructuredLinkedList.spec \
--optimistic_loop \
- --send_only \
- --settings -optimisticFallback=true \
+ --optimistic_fallback \
+ --parametric_contracts StructuredLinkedListHarness \
$RULE \
--rule_sanity \
--loop_iter 3 \
diff --git a/certora/scripts/permissions/verifyPausable.sh b/certora/scripts/permissions/verifyPausable.sh
index f40cc00be0..2eeb1de181 100644
--- a/certora/scripts/permissions/verifyPausable.sh
+++ b/certora/scripts/permissions/verifyPausable.sh
@@ -3,13 +3,14 @@ then
RULE="--rule $2"
fi
+solc-select use 0.8.12
certoraRun certora/harnesses/PausableHarness.sol \
- certora/munged/permissions/PauserRegistry.sol \
+ src/contracts/permissions/PauserRegistry.sol \
--verify PausableHarness:certora/specs/permissions/Pausable.spec \
--optimistic_loop \
- --send_only \
- --settings -optimisticFallback=true,-recursionErrorAsAssert=false,-recursionEntryLimit=3 \
+ --optimistic_fallback \
+ --prover_args '-recursionErrorAsAssert false -recursionEntryLimit 3' \
--loop_iter 3 \
--link PausableHarness:pauserRegistry=PauserRegistry \
$RULE \
diff --git a/certora/scripts/pods/verifyEigenPod.sh b/certora/scripts/pods/verifyEigenPod.sh
new file mode 100644
index 0000000000..ddfcb81814
--- /dev/null
+++ b/certora/scripts/pods/verifyEigenPod.sh
@@ -0,0 +1,23 @@
+if [[ "$2" ]]
+then
+ RULE="--rule $2"
+fi
+
+solc-select use 0.8.12
+
+certoraRun certora/harnesses/EigenPodHarness.sol \
+ src/contracts/core/DelegationManager.sol src/contracts/pods/EigenPodManager.sol \
+ src/contracts/core/Slasher.sol src/contracts/permissions/PauserRegistry.sol \
+ src/contracts/core/StrategyManager.sol \
+ src/contracts/strategies/StrategyBase.sol \
+ lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol \
+ lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol \
+ --verify EigenPodHarness:certora/specs/pods/EigenPod.spec \
+ --optimistic_loop \
+ --prover_args '-recursionEntryLimit 3' \
+ --optimistic_hashing \
+ --parametric_contracts EigenPodHarness \
+ $RULE \
+ --loop_iter 1 \
+ --packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
+ --msg "EigenPod $1 $2" \
diff --git a/certora/scripts/pods/verifyEigenPodManager.sh b/certora/scripts/pods/verifyEigenPodManager.sh
new file mode 100644
index 0000000000..be315b558f
--- /dev/null
+++ b/certora/scripts/pods/verifyEigenPodManager.sh
@@ -0,0 +1,19 @@
+if [[ "$2" ]]
+then
+ RULE="--rule $2"
+fi
+
+solc-select use 0.8.12
+
+certoraRun certora/harnesses/EigenPodManagerHarness.sol \
+ src/contracts/core/DelegationManager.sol src/contracts/pods/EigenPod.sol src/contracts/strategies/StrategyBase.sol src/contracts/core/StrategyManager.sol \
+ src/contracts/core/Slasher.sol src/contracts/permissions/PauserRegistry.sol \
+ --verify EigenPodManagerHarness:certora/specs/pods/EigenPodManager.spec \
+ --optimistic_loop \
+ --optimistic_fallback \
+ --optimistic_hashing \
+ --parametric_contracts EigenPodManagerHarness \
+ $RULE \
+ --loop_iter 3 \
+ --packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
+ --msg "EigenPodManager $1 $2" \
diff --git a/certora/scripts/strategies/verifyStrategyBase.sh b/certora/scripts/strategies/verifyStrategyBase.sh
index 822d08a0fc..c490041bca 100644
--- a/certora/scripts/strategies/verifyStrategyBase.sh
+++ b/certora/scripts/strategies/verifyStrategyBase.sh
@@ -3,18 +3,20 @@ then
RULE="--rule $2"
fi
+solc-select use 0.8.12
-certoraRun certora/munged/strategies/StrategyBase.sol \
+certoraRun src/contracts/strategies/StrategyBase.sol \
lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol \
- certora/munged/core/StrategyManager.sol \
- certora/munged/permissions/PauserRegistry.sol \
- certora/munged/core/Slasher.sol \
+ src/contracts/core/StrategyManager.sol \
+ src/contracts/permissions/PauserRegistry.sol \
+ src/contracts/core/Slasher.sol \
--verify StrategyBase:certora/specs/strategies/StrategyBase.spec \
--optimistic_loop \
- --send_only \
- --settings -optimisticFallback=true,-recursionErrorAsAssert=false,-recursionEntryLimit=3 \
+ --optimistic_fallback \
+ --prover_args '-recursionErrorAsAssert false -recursionEntryLimit 3' \
--loop_iter 3 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
--link StrategyBase:strategyManager=StrategyManager \
+ --parametric_contracts StrategyBase \
$RULE \
- --msg "Pausable $1 $2" \
\ No newline at end of file
+ --msg "StrategyBase $1 $2" \
\ No newline at end of file
diff --git a/certora/specs/core/DelegationManager.spec b/certora/specs/core/DelegationManager.spec
index 033a534a60..276e1a7261 100644
--- a/certora/specs/core/DelegationManager.spec
+++ b/certora/specs/core/DelegationManager.spec
@@ -2,59 +2,77 @@
methods {
//// External Calls
// external calls to DelegationManager
- undelegate(address)
- decreaseDelegatedShares(address,address[],uint256[])
- increaseDelegatedShares(address,address,uint256)
+ function undelegate(address) external;
+ function decreaseDelegatedShares(address,address,uint256) external;
+ function increaseDelegatedShares(address,address,uint256) external;
+
+ // external calls from DelegationManager to ServiceManager
+ function _.updateStakes(address[]) external => NONDET;
// external calls to Slasher
- isFrozen(address) returns (bool) => DISPATCHER(true)
- canWithdraw(address,uint32,uint256) returns (bool) => DISPATCHER(true)
+ function _.isFrozen(address) external => DISPATCHER(true);
+ function _.canWithdraw(address,uint32,uint256) external => DISPATCHER(true);
// external calls to StrategyManager
- getDeposits(address) returns (address[],uint256[]) => DISPATCHER(true)
- slasher() returns (address) => DISPATCHER(true)
- deposit(address,uint256) returns (uint256) => DISPATCHER(true)
- withdraw(address,address,uint256) => DISPATCHER(true)
+ function _.getDeposits(address) external => DISPATCHER(true);
+ function _.slasher() external => DISPATCHER(true);
+ function _.addShares(address,address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256, address) external => DISPATCHER(true);
// external calls to EigenPodManager
- withdrawBeaconChainETH(address,address,uint256) => DISPATCHER(true)
-
+ function _.addShares(address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256) external => DISPATCHER(true);
+
// external calls to EigenPod
- withdrawBeaconChainETH(address,uint256) => DISPATCHER(true)
+ function _.withdrawRestakedBeaconChainETH(address,uint256) external => DISPATCHER(true);
+ // external calls to DelayedWithdrawalRouter (from EigenPod)
+ function _.createDelayedWithdrawal(address, address) external => DISPATCHER(true);
+
// external calls to PauserRegistry
- pauser() returns (address) => DISPATCHER(true)
- unpauser() returns (address) => DISPATCHER(true)
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
+
+ // external calls to Strategy contracts
+ function _.withdraw(address,address,uint256) external => DISPATCHER(true);
+ function _.deposit(address,uint256) external => DISPATCHER(true);
+
+ // external calls to ERC20
+ function _.balanceOf(address) external => DISPATCHER(true);
+ function _.transfer(address, uint256) external => DISPATCHER(true);
+ function _.transferFrom(address, address, uint256) external => DISPATCHER(true);
// external calls to ERC1271 (can import OpenZeppelin mock implementation)
// isValidSignature(bytes32 hash, bytes memory signature) returns (bytes4 magicValue) => DISPATCHER(true)
- isValidSignature(bytes32, bytes) returns (bytes4) => DISPATCHER(true)
+ function _.isValidSignature(bytes32, bytes) external => DISPATCHER(true);
//// Harnessed Functions
- // Harnessed calls
- decreaseDelegatedShares(address,address,address,uint256,uint256)
- // Harmessed getters
- get_operatorShares(address,address) returns(uint256) envfree
-
- //// Summarized Functions
- _delegationReceivedHook(address,address,address[],uint256[]) => NONDET
- _delegationWithdrawnHook(address,address,address[],uint256[]) => NONDET
+ // Harnessed getters
+ function get_operatorShares(address,address) external returns (uint256) envfree;
+ function get_stakerDelegateableShares(address,address) external returns (uint256) envfree;
//envfree functions
- isDelegated(address staker) returns (bool) envfree
- isNotDelegated(address staker) returns (bool) envfree
- isOperator(address operator) returns (bool) envfree
- delegatedTo(address staker) returns (address) envfree
- delegationTerms(address operator) returns (address) envfree
- operatorShares(address operator, address strategy) returns (uint256) envfree
- owner() returns (address) envfree
- strategyManager() returns (address) envfree
+ function delegatedTo(address) external returns (address) envfree;
+ function operatorDetails(address) external returns (IDelegationManager.OperatorDetails memory) envfree;
+ function earningsReceiver(address) external returns (address) envfree;
+ function delegationApprover(address operator) external returns (address) envfree;
+ function stakerOptOutWindowBlocks(address operator) external returns (uint256) envfree;
+ function operatorShares(address operator, address strategy) external returns (uint256) envfree;
+ function isDelegated(address staker) external returns (bool) envfree;
+ function isOperator(address operator) external returns (bool) envfree;
+ function stakerNonce(address staker) external returns (uint256) envfree;
+ function delegationApproverSaltIsSpent(address delegationApprover, bytes32 salt) external returns (bool) envfree;
+ function owner() external returns (address) envfree;
+ function strategyManager() external returns (address) envfree;
+ function eigenPodManager() external returns (address) envfree;
}
/*
LEGAL STATE TRANSITIONS:
1)
-FROM not delegated -- defined as delegatedTo(staker) == address(0), likewise returned by isNotDelegated(staker)--
+FROM not delegated -- defined as delegatedTo(staker) == address(0), likewise returned by !isDelegated(staker)--
AND not registered as an operator -- defined as isOperator(operator) == false, or equivalently, delegationTerms(operator) == 0,
TO delegated but not an operator
in this case, the end state is that:
@@ -100,14 +118,12 @@ FORBIDDEN STATES:
Combining the above, an address can be (classified as an operator) *iff* they are (delegated to themselves).
The exception is the zero address, since by default an address is 'delegated to the zero address' when they are not delegated at all
*/
-//definition notDelegated -- defined as delegatedTo(staker) == address(0), likewise returned by isNotDelegated(staker)--
+//definition notDelegated -- defined as delegatedTo(staker) == address(0), likewise returned by !isDelegated(staker)--
// verify that anyone who is registered as an operator is also always delegated to themselves
+// the zero address is an exception to this rule, since it is always "delegated to itself" but not an operator
invariant operatorsAlwaysDelegatedToSelf(address operator)
- isOperator(operator) <=> delegatedTo(operator) == operator
- { preserved {
- require operator != 0;
- } }
+ operator != 0 => (isOperator(operator) <=> delegatedTo(operator) == operator);
// verify that once registered as an operator, a person cannot 'unregister' from being an operator
// proving this rule in concert with 'operatorsAlwaysDelegatedToSelf' proves that an operator can never change their delegation
@@ -126,24 +142,29 @@ rule operatorCannotUnregister(address operator) {
// verifies that in order for an address to change who they are delegated to, `undelegate` must be called
rule cannotChangeDelegationWithoutUndelegating(address staker) {
+ requireInvariant operatorsAlwaysDelegatedToSelf(staker);
// assume the staker is delegated to begin with
require(isDelegated(staker));
address delegatedToBefore = delegatedTo(staker);
// perform arbitrary function call
method f;
env e;
- // the only way the staker can become undelegated is if `undelegate` is called
- if (f.selector == undelegate(address).selector) {
+ // the only way the staker can become undelegated is an appropriate function is called
+ if (f.selector == sig:undelegate(address).selector) {
address toUndelegate;
undelegate(e, toUndelegate);
- // either the `strategyManager` called `undelegate` with the argument `staker` (in which can the staker is now undelegated)
- if (e.msg.sender == strategyManager() && toUndelegate == staker) {
- assert (delegatedTo(staker) == 0, "undelegation did not result in delegation to zero address");
+ // either the `staker` address was an input to `undelegate` AND the caller was allowed to call the function
+ if (
+ (toUndelegate == staker && (delegatedToBefore != staker)) &&
+ (e.msg.sender == staker || e.msg.sender == delegatedToBefore || e.msg.sender == delegationApprover(delegatedToBefore))
+ ){
+ assert (delegatedTo(staker) == 0, "undelegation did not result in delegation to zero address");
// or the staker's delegation should have remained the same
} else {
address delegatedToAfter = delegatedTo(staker);
assert (delegatedToAfter == delegatedToBefore, "delegation changed without undelegating -- problem in undelegate permissions?");
}
+ assert(true);
} else {
calldataarg arg;
f(e,arg);
@@ -156,42 +177,87 @@ rule cannotChangeDelegationWithoutUndelegating(address staker) {
rule canOnlyDelegateWithSpecificFunctions(address staker) {
requireInvariant operatorsAlwaysDelegatedToSelf(staker);
// assume the staker begins as undelegated
- require(isNotDelegated(staker));
+ require(!isDelegated(staker));
// perform arbitrary function call
method f;
env e;
- if (f.selector == delegateTo(address).selector) {
+ if (f.selector == sig:delegateTo(address, ISignatureUtils.SignatureWithExpiry, bytes32).selector) {
address operator;
- delegateTo(e, operator);
+ ISignatureUtils.SignatureWithExpiry approverSignatureAndExpiry;
+ bytes32 salt;
+ delegateTo(e, operator, approverSignatureAndExpiry, salt);
// we check against operator being the zero address here, since we view being delegated to the zero address as *not* being delegated
if (e.msg.sender == staker && isOperator(operator) && operator != 0) {
assert (isDelegated(staker) && delegatedTo(staker) == operator, "failure in delegateTo");
} else {
- assert (isNotDelegated(staker), "staker delegated to inappropriate address?");
+ assert (!isDelegated(staker), "staker delegated to inappropriate address?");
}
- } else if (f.selector == delegateToBySignature(address, address, uint256, bytes).selector) {
+ } else if (f.selector == sig:delegateToBySignature(address, address, ISignatureUtils.SignatureWithExpiry, ISignatureUtils.SignatureWithExpiry, bytes32).selector) {
address toDelegateFrom;
address operator;
- uint256 expiry;
- bytes signature;
- delegateToBySignature(e, toDelegateFrom, operator, expiry, signature);
+ ISignatureUtils.SignatureWithExpiry stakerSignatureAndExpiry;
+ ISignatureUtils.SignatureWithExpiry approverSignatureAndExpiry;
+ bytes32 salt;
+ delegateToBySignature(e, toDelegateFrom, operator, stakerSignatureAndExpiry, approverSignatureAndExpiry, salt);
// TODO: this check could be stricter! need to filter when the block timestamp is appropriate for expiry and signature is valid
- assert (isNotDelegated(staker) || delegatedTo(staker) == operator, "delegateToBySignature bug?");
- } else if (f.selector == registerAsOperator(address).selector) {
- address delegationTerms;
- registerAsOperator(e, delegationTerms);
- if (e.msg.sender == staker && delegationTerms != 0) {
+ assert (!isDelegated(staker) || delegatedTo(staker) == operator, "delegateToBySignature bug?");
+ } else if (f.selector == sig:registerAsOperator(IDelegationManager.OperatorDetails, string).selector) {
+ IDelegationManager.OperatorDetails operatorDetails;
+ string metadataURI;
+ registerAsOperator(e, operatorDetails, metadataURI);
+ if (e.msg.sender == staker) {
assert (isOperator(staker));
} else {
- assert(isNotDelegated(staker));
+ assert(!isDelegated(staker));
}
} else {
calldataarg arg;
f(e,arg);
- assert (isNotDelegated(staker), "staker became delegated through inappropriate function call");
+ assert (!isDelegated(staker), "staker became delegated through inappropriate function call");
+ }
+}
+
+rule sharesBecomeDelegatedWhenStakerDelegates(address operator, address staker, address strategy) {
+ requireInvariant operatorsAlwaysDelegatedToSelf(operator);
+ // filter out zero address (not a valid operator)
+ require(operator != 0);
+ // assume the staker begins as undelegated
+ require(!isDelegated(staker));
+ mathint stakerDelegateableSharesInStrategy = get_stakerDelegateableShares(staker, strategy);
+ mathint operatorSharesBefore = get_operatorShares(operator, strategy);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg arg;
+ mathint operatorSharesAfter = get_operatorShares(operator, strategy);
+ if (delegatedTo(staker) == operator) {
+ assert(operatorSharesAfter == operatorSharesBefore + stakerDelegateableSharesInStrategy, "operator shares did not increase appropriately");
+ } else {
+ assert(operatorSharesAfter == operatorSharesBefore, "operator shares changed inappropriately");
+ }
+}
+
+rule sharesBecomeUndelegatedWhenStakerUndelegates(address operator, address staker, address strategy) {
+ requireInvariant operatorsAlwaysDelegatedToSelf(operator);
+ // filter out zero address (not a valid operator)
+ require(operator != 0);
+ // assume the staker begins as delegated to the operator
+ require(delegatedTo(staker) == operator);
+ mathint stakerDelegateableSharesInStrategy = get_stakerDelegateableShares(staker, strategy);
+ mathint operatorSharesBefore = get_operatorShares(operator, strategy);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg arg;
+ mathint operatorSharesAfter = get_operatorShares(operator, strategy);
+ if (!isDelegated(staker)) {
+ assert(operatorSharesAfter == operatorSharesBefore - stakerDelegateableSharesInStrategy, "operator shares did not decrease appropriately");
+ } else {
+ assert(operatorSharesAfter == operatorSharesBefore, "operator shares changed inappropriately");
}
}
+
/*
rule batchEquivalence {
env e;
diff --git a/certora/specs/core/Slasher.spec b/certora/specs/core/Slasher.spec
index 6e7ce1423d..1ecdbd2dea 100644
--- a/certora/specs/core/Slasher.spec
+++ b/certora/specs/core/Slasher.spec
@@ -2,61 +2,60 @@
methods {
//// External Calls
// external calls to DelegationManager
- undelegate(address) => DISPATCHER(true)
- isDelegated(address) returns (bool) => DISPATCHER(true)
- delegatedTo(address) returns (address) => DISPATCHER(true)
- decreaseDelegatedShares(address,address[],uint256[]) => DISPATCHER(true)
- increaseDelegatedShares(address,address,uint256) => DISPATCHER(true)
- _delegationReceivedHook(address,address,address[],uint256[]) => NONDET
- _delegationWithdrawnHook(address,address,address[],uint256[]) => NONDET
+ function _.undelegate(address) external => DISPATCHER(true);
+ function _.isDelegated(address) external => DISPATCHER(true);
+ function _.delegatedTo(address) external => DISPATCHER(true);
+ function _.decreaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+ function _.increaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
// external calls to Slasher
- isFrozen(address) returns (bool) envfree
- canWithdraw(address,uint32,uint256) returns (bool)
+ function isFrozen(address) external returns (bool) envfree;
+ function canWithdraw(address,uint32,uint256) external returns (bool);
// external calls to StrategyManager
- getDeposits(address) returns (address[],uint256[]) => DISPATCHER(true)
- slasher() returns (address) => DISPATCHER(true)
- deposit(address,uint256) returns (uint256) => DISPATCHER(true)
- withdraw(address,address,uint256) => DISPATCHER(true)
+ function _.getDeposits(address) external => DISPATCHER(true);
+ function _.slasher() external => DISPATCHER(true);
+ function _.addShares(address,address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256, address) external => DISPATCHER(true);
// external calls to EigenPodManager
- withdrawBeaconChainETH(address,address,uint256) => DISPATCHER(true)
-
+ function _.addShares(address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256) external => DISPATCHER(true);
+
// external calls to EigenPod
- withdrawBeaconChainETH(address,uint256) => DISPATCHER(true)
-
- // external calls to IDelegationTerms
- onDelegationWithdrawn(address,address[],uint256[]) => CONSTANT
- onDelegationReceived(address,address[],uint256[]) => CONSTANT
-
+ function _.withdrawRestakedBeaconChainETH(address,uint256) external => DISPATCHER(true);
+
+ // external calls to DelayedWithdrawalRouter (from EigenPod)
+ function _.createDelayedWithdrawal(address, address) external => DISPATCHER(true);
+
// external calls to PauserRegistry
- pauser() returns (address) => DISPATCHER(true)
- unpauser() returns (address) => DISPATCHER(true)
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
//// Harnessed Functions
// Harnessed calls
// Harnessed getters
- get_is_operator(address) returns (bool) envfree
- get_is_delegated(address) returns (bool) envfree
- get_list_exists(address) returns (bool) envfree
- get_next_node_exists(address, uint256) returns (bool) envfree
- get_next_node(address, uint256) returns (uint256) envfree
- get_previous_node_exists(address, uint256) returns (bool) envfree
- get_previous_node(address, uint256) returns (uint256) envfree
- get_node_exists(address, address) returns (bool) envfree
- get_list_head(address) returns (uint256) envfree
- get_lastest_update_block_at_node(address, uint256) returns (uint256) envfree
- get_lastest_update_block_at_head(address) returns (uint256) envfree
- get_linked_list_entry(address operator, uint256 node, bool direction) returns (uint256) envfree
+ function get_is_operator(address) external returns (bool) envfree;
+ function get_is_delegated(address) external returns (bool) envfree;
+ function get_list_exists(address) external returns (bool) envfree;
+ function get_next_node_exists(address, uint256) external returns (bool) envfree;
+ function get_next_node(address, uint256) external returns (uint256) envfree;
+ function get_previous_node_exists(address, uint256) external returns (bool) envfree;
+ function get_previous_node(address, uint256) external returns (uint256) envfree;
+ function get_list_head(address) external returns (uint256) envfree;
+ function get_lastest_update_block_at_node(address, uint256) external returns (uint256) envfree;
+ function get_lastest_update_block_at_head(address) external returns (uint256) envfree;
+ function get_linked_list_entry(address operator, uint256 node, bool direction) external returns (uint256) envfree;
// nodeDoesExist(address operator, uint256 node) returns (bool) envfree
- nodeIsWellLinked(address operator, uint256 node) returns (bool) envfree
+ //function nodeIsWellLinked(address operator, uint256 node) external returns (bool) envfree;
//// Normal Functions
- owner() returns(address) envfree
- contractCanSlashOperatorUntil(address, address) returns (uint32) envfree
- paused(uint8) returns (bool) envfree
+ function owner() external returns(address) envfree;
+ function contractCanSlashOperatorUntilBlock(address, address) external returns (uint32) envfree;
+ function paused(uint8) external returns (bool) envfree;
}
// uses that _HEAD = 0. Similar to StructuredLinkedList.nodeExists but slightly better defined
@@ -92,29 +91,29 @@ rule cantBeUnfrozen(method f) {
/*
verifies that `contractCanSlashOperatorUntil[operator][contractAddress]` only changes when either:
the `operator` themselves calls `allowToSlash`
-or
+rule or
the `contractAddress` calls `recordLastStakeUpdateAndRevokeSlashingAbility`
*/
rule canOnlyChangecontractCanSlashOperatorUntilWithSpecificFunctions(address operator, address contractAddress) {
- uint256 valueBefore = contractCanSlashOperatorUntil(operator, contractAddress);
+ uint256 valueBefore = contractCanSlashOperatorUntilBlock(operator, contractAddress);
// perform arbitrary function call
method f;
env e;
- if (f.selector == recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32).selector) {
+ if (f.selector == sig:recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32).selector) {
address operator2;
uint32 serveUntil;
recordLastStakeUpdateAndRevokeSlashingAbility(e, operator2, serveUntil);
- uint256 valueAfter = contractCanSlashOperatorUntil(operator, contractAddress);
+ uint256 valueAfter = contractCanSlashOperatorUntilBlock(operator, contractAddress);
if (e.msg.sender == contractAddress && operator2 == operator/* TODO: proper check */) {
/* TODO: proper check */
assert (true, "failure in recordLastStakeUpdateAndRevokeSlashingAbility");
} else {
assert (valueBefore == valueAfter, "bad permissions on recordLastStakeUpdateAndRevokeSlashingAbility?");
}
- } else if (f.selector == optIntoSlashing(address).selector) {
+ } else if (f.selector == sig:optIntoSlashing(address).selector) {
address arbitraryContract;
optIntoSlashing(e, arbitraryContract);
- uint256 valueAfter = contractCanSlashOperatorUntil(operator, contractAddress);
+ uint256 valueAfter = contractCanSlashOperatorUntilBlock(operator, contractAddress);
// uses that the `PAUSED_OPT_INTO_SLASHING` index is 0, as an input to the `paused` function
if (e.msg.sender == operator && arbitraryContract == contractAddress && get_is_operator(operator) && !paused(0)) {
// uses that `MAX_CAN_SLASH_UNTIL` is equal to max_uint32
@@ -125,7 +124,7 @@ rule canOnlyChangecontractCanSlashOperatorUntilWithSpecificFunctions(address ope
} else {
calldataarg arg;
f(e, arg);
- uint256 valueAfter = contractCanSlashOperatorUntil(operator, contractAddress);
+ uint256 valueAfter = contractCanSlashOperatorUntilBlock(operator, contractAddress);
assert(valueBefore == valueAfter, "bondedAfter value changed when it shouldn't have!");
}
}
diff --git a/certora/specs/core/StrategyManager.spec b/certora/specs/core/StrategyManager.spec
index 73ace3bc4e..43cd8e20f4 100644
--- a/certora/specs/core/StrategyManager.spec
+++ b/certora/specs/core/StrategyManager.spec
@@ -1,79 +1,78 @@
// to allow calling ERC20 token within this spec
-using ERC20 as token
+using ERC20 as token;
methods {
//// External Calls
// external calls to DelegationManager
- undelegate(address) => DISPATCHER(true)
- isDelegated(address) returns (bool) => DISPATCHER(true)
- delegatedTo(address) returns (address) => DISPATCHER(true)
- decreaseDelegatedShares(address,address[],uint256[]) => DISPATCHER(true)
- increaseDelegatedShares(address,address,uint256) => DISPATCHER(true)
- _delegationReceivedHook(address,address,address[],uint256[]) => NONDET
- _delegationWithdrawnHook(address,address,address[],uint256[]) => NONDET
+ function _.undelegate(address) external => DISPATCHER(true);
+ function _.isDelegated(address) external => DISPATCHER(true);
+ function _.delegatedTo(address) external => DISPATCHER(true);
+ function _.decreaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+ function _.increaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+
+ // external calls from DelegationManager to ServiceManager
+ function _.updateStakes(address[]) external => NONDET;
// external calls to Slasher
- isFrozen(address) returns (bool) => DISPATCHER(true)
- canWithdraw(address,uint32,uint256) returns (bool) => DISPATCHER(true)
+ function _.isFrozen(address) external => DISPATCHER(true);
+ function _.canWithdraw(address,uint32,uint256) external => DISPATCHER(true);
// external calls to StrategyManager
- getDeposits(address) returns (address[],uint256[])
- slasher() returns (address)
- deposit(address,uint256) returns (uint256)
- withdraw(address,address,uint256)
-
- // external calls to Strategy
- deposit(address, uint256) returns (uint256) => DISPATCHER(true)
- withdraw(address, address, uint256) => DISPATCHER(true)
- totalShares() => DISPATCHER(true)
+ function _.getDeposits(address) external => DISPATCHER(true);
+ function _.slasher() 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);
// external calls to EigenPodManager
- withdrawRestakedBeaconChainETH(address,address,uint256) => DISPATCHER(true)
- // call made to EigenPodManager by DelayedWithdrawalRouter
- getPod(address) => DISPATCHER(true)
+ function _.addShares(address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256) external => DISPATCHER(true);
- // external calls to EigenPod (from EigenPodManager)
- withdrawRestakedBeaconChainETH(address, uint256) => DISPATCHER(true)
+ // external calls to EigenPod
+ function _.withdrawRestakedBeaconChainETH(address,uint256) external => DISPATCHER(true);
// external calls to DelayedWithdrawalRouter (from EigenPod)
- createDelayedWithdrawal(address, address) => DISPATCHER(true)
+ function _.createDelayedWithdrawal(address, address) external => DISPATCHER(true);
- // external calls to IDelegationTerms
- onDelegationWithdrawn(address,address[],uint256[]) => CONSTANT
- onDelegationReceived(address,address[],uint256[]) => CONSTANT
-
// external calls to PauserRegistry
- pauser() returns (address) => DISPATCHER(true)
- unpauser() returns (address) => DISPATCHER(true)
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
+
+ // external calls to Strategy contracts
+ function _.withdraw(address,address,uint256) external => DISPATCHER(true);
+ function _.deposit(address,uint256) external => DISPATCHER(true);
// external calls to ERC20
- balanceOf(address) returns (uint256) => DISPATCHER(true)
- transfer(address, uint256) returns (bool) => DISPATCHER(true)
- transferFrom(address, address, uint256) returns (bool) => DISPATCHER(true)
+ function _.balanceOf(address) external => DISPATCHER(true);
+ function _.transfer(address, uint256) external => DISPATCHER(true);
+ function _.transferFrom(address, address, uint256) external => DISPATCHER(true);
// calls to ERC20 in this spec
- token.balanceOf(address) returns(uint256) envfree
+ function token.balanceOf(address) external returns(uint256) envfree;
// external calls to ERC1271 (can import OpenZeppelin mock implementation)
// isValidSignature(bytes32 hash, bytes memory signature) returns (bytes4 magicValue) => DISPATCHER(true)
- isValidSignature(bytes32, bytes) returns (bytes4) => DISPATCHER(true)
+ function _.isValidSignature(bytes32, bytes) external => DISPATCHER(true);
//// Harnessed Functions
// Harnessed calls
+ function _.totalShares() external => DISPATCHER(true);
// Harnessed getters
- strategy_is_in_stakers_array(address, address) returns (bool) envfree
- num_times_strategy_is_in_stakers_array(address, address) returns (uint256) envfree
- totalShares(address) returns (uint256) envfree
+ function strategy_is_in_stakers_array(address, address) external returns (bool) envfree;
+ function num_times_strategy_is_in_stakers_array(address, address) external returns (uint256) envfree;
+ function totalShares(address) external returns (uint256) envfree;
+ function get_stakerStrategyShares(address, address) external returns (uint256) envfree;
//// Normal Functions
- stakerStrategyListLength(address) returns (uint256) envfree
- stakerStrategyList(address, uint256) returns (address) envfree
- stakerStrategyShares(address, address) returns (uint256) envfree
- array_exhibits_properties(address) returns (bool) envfree
+ function stakerStrategyListLength(address) external returns (uint256) envfree;
+ function stakerStrategyList(address, uint256) external returns (address) envfree;
+ function stakerStrategyShares(address, address) external returns (uint256) envfree;
+ function array_exhibits_properties(address) external returns (bool) envfree;
}
invariant stakerStrategyListLengthLessThanOrEqualToMax(address staker)
- stakerStrategyListLength(staker) <= 32
+ stakerStrategyListLength(staker) <= 32;
// verifies that strategies in the staker's array of strategies are not duplicated, and that the staker has nonzero shares in each one
invariant arrayExhibitsProperties(address staker)
@@ -87,7 +86,7 @@ invariant arrayExhibitsProperties(address staker)
// if a strategy is *not* in staker's array of strategies, then the staker should have precisely zero shares in that strategy
invariant strategiesNotInArrayHaveZeroShares(address staker, uint256 index)
- (index >= stakerStrategyListLength(staker)) => (stakerStrategyShares(staker, stakerStrategyList(staker, index)) == 0)
+ (index >= stakerStrategyListLength(staker)) => (stakerStrategyShares(staker, stakerStrategyList(staker, index)) == 0);
/**
* a staker's amount of shares in a strategy (i.e. `stakerStrategyShares[staker][strategy]`) should only increase when
@@ -95,22 +94,17 @@ invariant strategiesNotInArrayHaveZeroShares(address staker, uint256 index)
* *OR* when completing a withdrawal
*/
definition methodCanIncreaseShares(method f) returns bool =
- f.selector == depositIntoStrategy(address,address,uint256).selector
- || f.selector == depositIntoStrategyWithSignature(address,address,uint256,address,uint256,bytes).selector
- || f.selector == depositBeaconChainETH(address,uint256).selector
- || f.selector == completeQueuedWithdrawal((address[],uint256[],address,(address,uint96),uint32,address),address[],uint256,bool).selector;
- // || f.selector == slashQueuedWithdrawal(address,bytes,address[],uint256[]).selector
- // || f.selector == slashShares(address,address,address[],address[],uint256[],uint256[]).selector;
+ 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,address,uint256).selector;
/**
* a staker's amount of shares in a strategy (i.e. `stakerStrategyShares[staker][strategy]`) should only decrease when
-* `queueWithdrawal`, `slashShares`, or `recordOvercommittedBeaconChainETH` has been called
+* `queueWithdrawal`, `slashShares`, or `recordBeaconChainETHBalanceUpdate` has been called
*/
definition methodCanDecreaseShares(method f) returns bool =
- f.selector == queueWithdrawal(uint256[],address[],uint256[],address,bool).selector
- || f.selector == slashShares(address,address,address[],address[],uint256[],uint256[]).selector
- || f.selector == slashSharesSinglet(address,address,address,address,uint256,uint256).selector
- || f.selector == recordOvercommittedBeaconChainETH(address,uint256,uint256).selector;
+ f.selector == sig:removeShares(address,address,uint256).selector;
rule sharesAmountsChangeOnlyWhenAppropriateFunctionsCalled(address staker, address strategy) {
uint256 sharesBefore = stakerStrategyShares(staker, strategy);
@@ -123,55 +117,50 @@ rule sharesAmountsChangeOnlyWhenAppropriateFunctionsCalled(address staker, addre
assert(sharesAfter < sharesBefore => methodCanDecreaseShares(f));
}
-// based on Certora's example here https://github.com/Certora/Tutorials/blob/michael/ethcc/EthCC/Ghosts/ghostTest.spec
-ghost mapping(address => mathint) sumOfSharesInStrategy {
- init_state axiom forall address strategy. sumOfSharesInStrategy[strategy] == 0;
-}
-
-hook Sstore stakerStrategyShares[KEY address staker][KEY address strategy] uint256 newValue (uint256 oldValue) STORAGE {
- sumOfSharesInStrategy[strategy] = sumOfSharesInStrategy[strategy] + newValue - oldValue;
-}
-
/**
-* Verifies that the `totalShares` returned by an Strategy is always greater than or equal to the sum of shares in the `stakerStrategyShares`
-* mapping -- specifically, that `strategy.totalShares() >= sum_over_all_stakers(stakerStrategyShares[staker][strategy])`
-* We cannot show strict equality here, since the withdrawal process first decreases a staker's shares (when `queueWithdrawal` is called) and
-* only later is `totalShares` decremented (when `completeQueuedWithdrawal` is called).
+* Verifies that the `totalShares` returned by an Strategy increases appropriately when new Strategy shares are issued by the StrategyManager
+* contract (specifically as a result of a call to `StrategyManager.depositIntoStrategy` or `StrategyManager.depositIntoStrategyWithSignature`).
+* This rule excludes the `addShares` and `removeShares` functions, since these are called only by the DelegationManager, and do not
+* "create new shares", but rather represent existing shares being "moved into a withdrawal".
*/
-invariant totalSharesGeqSumOfShares(address strategy)
- totalShares(strategy) >= sumOfSharesInStrategy[strategy]
- // preserved block since does not apply for 'beaconChainETH'
- { preserved {
- // 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0 converted to decimal (this is the address of the virtual 'beaconChainETH' strategy)
- // require strategy != beaconChainETHStrategy();
- require strategy != 1088545275507480024404324736574744392984337050304;
- } }
+rule newSharesIncreaseTotalShares(address strategy) {
+ method f;
+ env e;
+ uint256 stakerStrategySharesBefore = get_stakerStrategyShares(e.msg.sender, strategy);
+ uint256 totalSharesBefore = totalShares(strategy);
+ if (
+ f.selector == sig:addShares(address, address, address, uint256).selector
+ || f.selector == sig:removeShares(address, address, uint256).selector
+ ) {
+ uint256 totalSharesAfter = totalShares(strategy);
+ assert(totalSharesAfter == totalSharesBefore, "total shares changed unexpectedly");
+ } else {
+ uint256 stakerStrategySharesAfter = get_stakerStrategyShares(e.msg.sender, strategy);
+ uint256 totalSharesAfter = totalShares(strategy);
+ assert(stakerStrategySharesAfter - stakerStrategySharesBefore == totalSharesAfter - totalSharesBefore, "diffs don't match");
+ }
+}
/**
* Verifies that ERC20 tokens are transferred out of the account only of the msg.sender.
- * Called 'safeApprovalUse' since approval-related vulnerabilites in general allow a caller to transfer tokens out of a different account.
+ * Called 'safeApprovalUse' since approval-related vulnerabilities in general allow a caller to transfer tokens out of a different account.
* This behavior is not always unsafe, but since we don't ever use it (at present) we can do a blanket-check against it.
*/
rule safeApprovalUse(address user) {
uint256 tokenBalanceBefore = token.balanceOf(user);
method f;
env e;
- calldataarg args;
- // need special case for `slashShares` function since otherwise this rule fails by making the user address one of the slashed strategy(s)
- if (
- f.selector == slashShares(address,address,address[],address[],uint256[],uint256[]).selector
- || f.selector == slashSharesSinglet(address,address,address,address,uint256,uint256).selector
- ) {
- address slashedAddress;
+ // special case logic, to handle an edge case
+ if (f.selector == sig:withdrawSharesAsTokens(address,address,uint256,address).selector) {
address recipient;
address strategy;
- address desiredToken;
- uint256 strategyIndex;
- uint256 shareAmount;
- // need this filtering here
- require(strategy != user);
- slashSharesSinglet(e, slashedAddress, recipient, strategy, desiredToken, strategyIndex, shareAmount);
+ uint256 shares;
+ // filter out case where the 'user' is the strategy itself
+ require(user != strategy);
+ withdrawSharesAsTokens(e, recipient, strategy, shares, token);
+ // otherwise just perform an arbitrary function call
} else {
+ calldataarg args;
f(e,args);
}
uint256 tokenBalanceAfter = token.balanceOf(user);
diff --git a/certora/specs/libraries/StructuredLinkedList.spec b/certora/specs/libraries/StructuredLinkedList.spec
index 12a58f4433..8b6ae0bd9f 100644
--- a/certora/specs/libraries/StructuredLinkedList.spec
+++ b/certora/specs/libraries/StructuredLinkedList.spec
@@ -1,22 +1,22 @@
methods {
- listExists() returns (bool) envfree
- nodeExists(uint256) returns (bool) envfree
- sizeOf() returns (uint256) envfree
- getHead() returns (uint256) envfree
- getNode(uint256) returns (bool, uint256, uint256) envfree
- getAdjacent(uint256,bool) returns (bool, uint256) envfree
- getAdjacentStrict(uint256,bool) returns (uint256) envfree
- getNextNode(uint256) returns (bool, uint256) envfree
- getPreviousNode(uint256) returns (bool, uint256) envfree
- insert(uint256,uint256,bool) envfree
- remove(uint256) envfree
+ function listExists() external returns (bool) envfree;
+ function nodeExists(uint256) external returns (bool) envfree;
+ function sizeOf() external returns (uint256) envfree;
+ function getHead() external returns (uint256) envfree;
+ function getNode(uint256) external returns (bool, uint256, uint256) envfree;
+ function getAdjacent(uint256,bool) external returns (bool, uint256) envfree;
+ function getAdjacentStrict(uint256,bool) external returns (uint256) envfree;
+ function getNextNode(uint256) external returns (bool, uint256) envfree;
+ function getPreviousNode(uint256) external returns (bool, uint256) envfree;
+ function insert(uint256,uint256,bool) external envfree;
+ function remove(uint256) external envfree;
}
ghost mapping(uint256 => bool) connectsToHead {
init_state axiom connectsToHead[0] == true;
}
-hook Sstore currentContract.listStorage.list[KEY uint256 node][KEY bool direction] uint256 link (uint256 old_link) STORAGE {
+hook Sstore currentContract.listStorage.list[KEY uint256 node][KEY bool direction] uint256 link (uint256 old_link) {
connectsToHead[link] = connectsToHead[node];
connectsToHead[old_link] = old_link == 0;
}
@@ -107,7 +107,7 @@ invariant zeroRequiredInCircle(uint256 node1, uint256 node2)
}
}
-
+/* commented out while failing (can reintroduce in a PR)
// in progress
invariant headInList(uint256 node)
nodeExists(node) => connectsToHead[node]
@@ -129,7 +129,7 @@ invariant headInList(uint256 node)
// size == # of nodes with nonzero next == # of nodes with nonzero prev
-
+*/
/*
@@ -168,7 +168,7 @@ function safeAssumptions() {
invariant zeroEmpty()
isEmpty(0)
- filtered { f -> f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:insertSorted(address, uint256, uint256).selector }
rule zeroEmptyPreservedInsertSorted(address _id, uint256 _value) {
address prev;
@@ -198,7 +198,7 @@ invariant headWellFormed()
invariant tailWellFormed()
isTailWellFormed()
- filtered { f -> f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:insertSorted(address, uint256, uint256).selector }
{ preserved remove(address _id) {
requireInvariant zeroEmpty();
requireInvariant twoWayLinked(getPrev(_id), _id);
@@ -235,7 +235,7 @@ invariant tipsZero()
invariant noPrevIsHead(address addr)
hasNoPrevIsHead(addr)
- filtered { f -> f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:insertSorted(address, uint256, uint256).selector }
{ preserved remove(address _id) {
safeAssumptions();
requireInvariant twoWayLinked(_id, getNext(_id));
@@ -264,7 +264,7 @@ rule noPrevIsHeadPreservedInsertSorted(address _id, uint256 _value) {
invariant noNextIsTail(address addr)
hasNoNextIsTail(addr)
- filtered { f -> f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:insertSorted(address, uint256, uint256).selector }
{ preserved remove(address _id) {
safeAssumptions();
requireInvariant twoWayLinked(_id, getNext(_id));
@@ -293,7 +293,7 @@ rule noNextisTailPreservedInsertSorted(address _id, uint256 _value) {
invariant linkedIsInDLL(address addr)
isLinked(addr) => isInDLL(addr)
- filtered { f -> f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:insertSorted(address, uint256, uint256).selector }
{ preserved remove(address _id) {
safeAssumptions();
requireInvariant twoWayLinked(_id, getNext(_id));
@@ -323,7 +323,7 @@ rule linkedIsInDllPreservedInsertSorted(address _id, uint256 _value) {
invariant twoWayLinked(address first, address second)
isTwoWayLinked(first, second)
- filtered { f -> f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:insertSorted(address, uint256, uint256).selector }
{ preserved remove(address _id) {
safeAssumptions();
requireInvariant twoWayLinked(getPrev(_id), _id);
@@ -349,8 +349,8 @@ rule twoWayLinkedPreservedInsertSorted(address _id, uint256 _value) {
invariant forwardLinked(address addr)
isInDLL(addr) => isForwardLinkedBetween(getHead(), addr)
- filtered { f -> f.selector != remove(address).selector &&
- f.selector != insertSorted(address, uint256, uint256).selector }
+ filtered { f -> f.selector != sig:remove(address).selector &&
+ f.selector != sig:insertSorted(address, uint256, uint256).selector }
rule forwardLinkedPreservedInsertSorted(address _id, uint256 _value) {
address addr; address prev;
diff --git a/certora/specs/permissions/Pausable.spec b/certora/specs/permissions/Pausable.spec
index 9d57d4ffb8..aaeb90fe51 100644
--- a/certora/specs/permissions/Pausable.spec
+++ b/certora/specs/permissions/Pausable.spec
@@ -1,29 +1,27 @@
methods {
// external calls to PauserRegistry
- isPauser(address) returns (bool) => DISPATCHER(true)
- unpauser() returns (address) => DISPATCHER(true)
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
// envfree functions
- paused() returns (uint256) envfree
- paused(uint8 index) returns (bool) envfree
- pauserRegistry() returns (address) envfree
+ function paused() external returns (uint256) envfree;
+ function paused(uint8 index) external returns (bool) envfree;
+ function pauserRegistry() external returns (address) envfree;
// harnessed functions
- isPauser(address) returns (bool) envfree
- unpauser() returns (address) envfree
- bitwise_not(uint256) returns (uint256) envfree
- bitwise_and(uint256, uint256) returns (uint256) envfree
+ function isPauser(address) external returns (bool) envfree;
+ function unpauser() external returns (address) envfree;
+ function bitwise_not(uint256) external returns (uint256) envfree;
+ function bitwise_and(uint256, uint256) external returns (uint256) envfree;
}
rule onlyPauserCanPauseAndOnlyUnpauserCanUnpause() {
method f;
env e;
uint256 pausedStatusBefore = paused();
- address pauser;
- require(isPauser(pauser));
address unpauser = unpauser();
- if (f.selector == pause(uint256).selector) {
+ if (f.selector == sig:pause(uint256).selector) {
uint256 newPausedStatus;
pause(e, newPausedStatus);
uint256 pausedStatusAfter = paused();
@@ -32,7 +30,7 @@ rule onlyPauserCanPauseAndOnlyUnpauserCanUnpause() {
} else {
assert(pausedStatusAfter == pausedStatusBefore, "pausedStatusAfter != pausedStatusBefore");
}
- } else if (f.selector == pauseAll().selector) {
+ } else if (f.selector == sig:pauseAll().selector) {
pauseAll(e);
uint256 pausedStatusAfter = paused();
if (isPauser(e.msg.sender)) {
@@ -42,7 +40,7 @@ rule onlyPauserCanPauseAndOnlyUnpauserCanUnpause() {
} else {
assert(pausedStatusAfter == pausedStatusBefore, "pausedStatusAfter != pausedStatusBefore");
}
- } else if (f.selector == unpause(uint256).selector) {
+ } else if (f.selector == sig:unpause(uint256).selector) {
uint256 newPausedStatus;
unpause(e, newPausedStatus);
uint256 pausedStatusAfter = paused();
diff --git a/certora/specs/pods/EigenPod.spec b/certora/specs/pods/EigenPod.spec
new file mode 100644
index 0000000000..17296dcfd4
--- /dev/null
+++ b/certora/specs/pods/EigenPod.spec
@@ -0,0 +1,245 @@
+
+methods {
+ // Internal, NONDET-summarized EigenPod library functions
+ function _.verifyValidatorFields(bytes32, bytes32[] calldata, bytes calldata, uint40) internal => NONDET;
+ function _.verifyValidatorBalance(bytes32, bytes32, bytes calldata, uint40) internal => NONDET;
+ function _.verifyStateRootAgainstLatestBlockRoot(bytes32, bytes32, bytes calldata) internal => NONDET;
+ function _.verifyWithdrawal(bytes32, bytes32[] calldata, BeaconChainProofs.WithdrawalProof calldata) internal => NONDET;
+
+ // Internal, NONDET-summarized "send ETH" function -- unsound summary used to avoid HAVOC behavior
+ // when sending ETH using `Address.sendValue()`
+ function _._sendETH(address recipient, uint256 amountWei) internal => NONDET;
+
+ // summarize the deployment of EigenPods to avoid default, HAVOC behavior
+ function _.deploy(uint256, bytes32, bytes memory bytecode) internal => NONDET;
+
+ //// External Calls
+ // external calls to DelegationManager
+ function _.undelegate(address) external => DISPATCHER(true);
+ function _.decreaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+ function _.increaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+
+ // external calls from DelegationManager to ServiceManager
+ function _.updateStakes(address[]) external => NONDET;
+
+ // external calls to Slasher
+ function _.isFrozen(address) external => DISPATCHER(true);
+ function _.canWithdraw(address,uint32,uint256) external => DISPATCHER(true);
+ function _.recordStakeUpdate(address,uint32,uint32,uint256) external => NONDET;
+
+ // external calls to StrategyManager
+ function _.getDeposits(address) external => DISPATCHER(true);
+ function _.slasher() external => DISPATCHER(true);
+ function _.addShares(address,address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256, address) external => DISPATCHER(true);
+ function _.migrateQueuedWithdrawal(IStrategyManager.DeprecatedStruct_QueuedWithdrawal) external => NONDET;
+
+ // external calls to Strategy contracts
+ function _.deposit(address, uint256) external => NONDET;
+ function _.withdraw(address, address, uint256) external => NONDET;
+
+ // external calls to EigenPodManager
+ function _.addShares(address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256) external => DISPATCHER(true);
+ function _.podOwnerShares(address) external => DISPATCHER(true);
+
+ // external calls to EigenPod
+ function _.withdrawRestakedBeaconChainETH(address,uint256) external => DISPATCHER(true);
+ function _.stake(bytes, bytes, bytes32) external => DISPATCHER(true);
+ function _.initialize(address) external => DISPATCHER(true);
+
+ // external calls to ETH2Deposit contract
+ function _.deposit(bytes, bytes, bytes, bytes32) external => NONDET;
+
+ // external calls to DelayedWithdrawalRouter (from EigenPod)
+ function _.createDelayedWithdrawal(address, address) external => DISPATCHER(true);
+
+ // external calls to PauserRegistry
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
+
+ // external calls to ERC20 token
+ function _.transfer(address, uint256) external => DISPATCHER(true);
+ function _.transferFrom(address, address, uint256) external => DISPATCHER(true);
+ function _.approve(address, uint256) external => DISPATCHER(true);
+
+ // envfree functions
+ function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external returns (uint64) envfree;
+ function withdrawableRestakedExecutionLayerGwei() external returns (uint64) envfree;
+ function nonBeaconChainETHBalanceWei() external returns (uint256) envfree;
+ function eigenPodManager() external returns (address) envfree;
+ function podOwner() external returns (address) envfree;
+ function hasRestaked() external returns (bool) envfree;
+ function mostRecentWithdrawalTimestamp() external returns (uint64) envfree;
+ function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external returns (IEigenPod.ValidatorInfo) envfree;
+ function provenWithdrawal(bytes32 validatorPubkeyHash, uint64 slot) external returns (bool) envfree;
+ function validatorStatus(bytes32 pubkeyHash) external returns (IEigenPod.VALIDATOR_STATUS) envfree;
+ function delayedWithdrawalRouter() external returns (address) envfree;
+ function nonBeaconChainETHBalanceWei() external returns (uint256) envfree;
+
+ // harnessed functions
+ function get_validatorIndex(bytes32 pubkeyHash) external returns (uint64) envfree;
+ function get_restakedBalanceGwei(bytes32 pubkeyHash) external returns (uint64) envfree;
+ function get_mostRecentBalanceUpdateTimestamp(bytes32 pubkeyHash) external returns (uint64) envfree;
+ function get_podOwnerShares() external returns (int256) envfree;
+ function get_withdrawableRestakedExecutionLayerGwei() external returns (uint256) envfree;
+ function get_ETH_Balance() external returns (uint256) envfree;
+}
+
+// defines the allowed validator status transitions
+definition validatorStatusTransitionAllowed(IEigenPod.VALIDATOR_STATUS statusBefore, IEigenPod.VALIDATOR_STATUS statusAfter) returns bool =
+ (statusBefore == IEigenPod.VALIDATOR_STATUS.INACTIVE && statusAfter == IEigenPod.VALIDATOR_STATUS.ACTIVE)
+ || (statusBefore == IEigenPod.VALIDATOR_STATUS.ACTIVE && statusAfter == IEigenPod.VALIDATOR_STATUS.WITHDRAWN);
+
+// verifies that only the 2 allowed transitions of validator status occur
+rule validatorStatusTransitionsCorrect(bytes32 pubkeyHash) {
+ IEigenPod.VALIDATOR_STATUS statusBefore = validatorStatus(pubkeyHash);
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ IEigenPod.VALIDATOR_STATUS statusAfter = validatorStatus(pubkeyHash);
+ assert(
+ (statusBefore == statusAfter)
+ || validatorStatusTransitionAllowed(statusBefore, statusAfter),
+ "disallowed validator status transition occurred"
+ );
+}
+
+// verifies that _validatorPubkeyHashToInfo[validatorPubkeyHash].mostRecentBalanceUpdateTimestamp can ONLY increase (or remain the same)
+rule mostRecentBalanceUpdateTimestampOnlyIncreases(bytes32 validatorPubkeyHash) {
+ IEigenPod.ValidatorInfo validatorInfoBefore = validatorPubkeyHashToInfo(validatorPubkeyHash);
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ IEigenPod.ValidatorInfo validatorInfoAfter = validatorPubkeyHashToInfo(validatorPubkeyHash);
+ assert(validatorInfoAfter.mostRecentBalanceUpdateTimestamp >= validatorInfoBefore.mostRecentBalanceUpdateTimestamp,
+ "mostRecentBalanceUpdateTimestamp decreased");
+}
+
+// verifies that if a validator is marked as 'INACTIVE', then it has no other entries set in its ValidatorInfo
+invariant inactiveValidatorsHaveEmptyInfo(bytes32 pubkeyHash)
+ (validatorStatus(pubkeyHash) == IEigenPod.VALIDATOR_STATUS.INACTIVE) => (
+ get_validatorIndex(pubkeyHash) == 0
+ && get_restakedBalanceGwei(pubkeyHash) == 0
+ && get_mostRecentBalanceUpdateTimestamp(pubkeyHash) == 0);
+
+// verifies that _validatorPubkeyHashToInfo[validatorPubkeyHash].validatorIndex can be set initially but otherwise can't change
+// this can be understood as the only allowed transitions of index being of the form: 0 => anything (otherwise the index must stay the same)
+rule validatorIndexSetOnlyOnce(bytes32 pubkeyHash) {
+ requireInvariant inactiveValidatorsHaveEmptyInfo(pubkeyHash);
+ uint64 validatorIndexBefore = get_validatorIndex(pubkeyHash);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ uint64 validatorIndexAfter = get_validatorIndex(pubkeyHash);
+ assert(validatorIndexBefore == 0 || validatorIndexAfter == validatorIndexBefore,
+ "validator index modified from nonzero value");
+}
+
+// verifies that once a validator has its status set to WITHDRAWN, its ‘restakedBalanceGwei’ is *and always remains* zero
+invariant withdrawnValidatorsHaveZeroRestakedGwei(bytes32 pubkeyHash)
+ (validatorStatus(pubkeyHash) == IEigenPod.VALIDATOR_STATUS.INACTIVE) =>
+ (get_restakedBalanceGwei(pubkeyHash) == 0);
+
+
+// // TODO: see if this draft rule can be salvaged
+// // draft rule to capture the following behavior (or at least most of it):
+// // The core invariant that ought to be maintained across the EPM and the EPs is that
+// // podOwnerShares[podOwner] + sum(sharesInQueuedWithdrawals) =
+// // sum(_validatorPubkeyHashToInfo[validatorPubkeyHash].restakedBalanceGwei) + withdrawableRestakedExecutionLayerGwei
+
+// // idea: if we ignore shares in queued withdrawals and rearrange, then we have:
+// // sum(_validatorPubkeyHashToInfo[validatorPubkeyHash].restakedBalanceGwei) =
+// // EigenPodManager.podOwnerShares(podOwner) - withdrawableRestakedExecutionLayerGwei
+// // we can track changes to the '_validatorPubkeyHashToInfo' mapping and check this with ghost variables
+
+// based on Certora's example here https://github.com/Certora/Tutorials/blob/michael/ethcc/EthCC/Ghosts/ghostTest.spec
+ghost mathint sumOfValidatorRestakedbalancesWei {
+ // NOTE: this commented out line is broken, as calling functions in axioms is currently disallowed, but this is what we'd run ideally.
+ // init_state axiom sumOfValidatorRestakedbalancesWei == to_mathint(get_podOwnerShares()) - to_mathint(get_withdrawableRestakedExecutionLayerGwei() * 1000000000);
+
+ // since both of these variables are zero at construction, just set the ghost to zero in the axiom
+ init_state axiom sumOfValidatorRestakedbalancesWei == 0;
+}
+
+hook Sstore _validatorPubkeyHashToInfo[KEY bytes32 validatorPubkeyHash].restakedBalanceGwei uint64 newValue (uint64 oldValue) {
+ sumOfValidatorRestakedbalancesWei = (
+ sumOfValidatorRestakedbalancesWei +
+ to_mathint(newValue) * 1000000000 -
+ to_mathint(oldValue) * 1000000000
+ );
+}
+
+rule consistentAccounting() {
+ // fetch info before call
+ int256 podOwnerSharesBefore = get_podOwnerShares();
+ uint256 withdrawableRestakedExecutionLayerGweiBefore = get_withdrawableRestakedExecutionLayerGwei();
+ uint256 eigenPodBalanceBefore = get_ETH_Balance();
+ // filter down to valid pre-states
+ require(sumOfValidatorRestakedbalancesWei ==
+ to_mathint(podOwnerSharesBefore) - to_mathint(withdrawableRestakedExecutionLayerGweiBefore));
+
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+
+ // fetch info after call
+ int256 podOwnerSharesAfter = get_podOwnerShares();
+ uint256 withdrawableRestakedExecutionLayerGweiAfter = get_withdrawableRestakedExecutionLayerGwei();
+ uint256 eigenPodBalanceAfter = get_ETH_Balance();
+ /**
+ * handling for weird, unrealistic edge case where calling `initialize` causes the pod owner to change, so the
+ * call to `get_podOwnerShares` queries the shares for a different address.
+ * calling `initialize` should *not* change user shares, so it is unrealistic to simulate it doing so.
+ */
+ if (f.selector == sig:initialize(address).selector) {
+ podOwnerSharesAfter = podOwnerSharesBefore;
+ }
+ // check post-state
+ // TODO: this check is still broken for `withdrawRestakedBeaconChainETH` since it does a low-level call to transfer the ETH, which triggers optimistic fallback dispatching
+ // special handling for one function
+ if (f.selector == sig:withdrawRestakedBeaconChainETH(address,uint256).selector) {
+ /* TODO: un-comment this once the dispatching is handled correctly
+ assert(sumOfValidatorRestakedbalancesWei ==
+ to_mathint(podOwnerSharesAfter) - to_mathint(withdrawableRestakedExecutionLayerGweiAfter)
+ // adjustment term for the ETH balance of the contract changing
+ + to_mathint(eigenPodBalanceBefore) - to_mathint(eigenPodBalanceAfter),
+ "invalid post-state");
+ */
+ // TODO: delete this once the above is salvaged (was added since CVL forbids empty blocks)
+ assert(true);
+ // outside of special case, we don't need the adjustment term
+ } else {
+ assert(sumOfValidatorRestakedbalancesWei ==
+ to_mathint(podOwnerSharesAfter) - to_mathint(withdrawableRestakedExecutionLayerGweiAfter),
+ "invalid post-state");
+ }
+}
+
+/*
+rule baseInvariant() {
+ int256 podOwnerSharesBefore = get_podOwnerShares();
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ int256 podOwnerSharesAfter = get_podOwnerShares();
+ mathint podOwnerSharesDelta = podOwnerSharesAfter - podOwnerSharesBefore;
+ assert(sumOfValidatorRestakedbalancesWei == podOwnerSharesDelta - to_mathint(get_withdrawableRestakedExecutionLayerGwei()),
+ "base invariant violated");
+}
+
+invariant consistentAccounting() {
+ sumOfValidatorRestakedbalancesWei ==
+ to_mathint(get_withdrawableRestakedExecutionLayerGwei()) - to_mathint(get_withdrawableRestakedExecutionLayerGwei());
+}
+*/
\ No newline at end of file
diff --git a/certora/specs/pods/EigenPodManager.spec b/certora/specs/pods/EigenPodManager.spec
new file mode 100644
index 0000000000..37bdc41827
--- /dev/null
+++ b/certora/specs/pods/EigenPodManager.spec
@@ -0,0 +1,98 @@
+
+methods {
+ //// External Calls
+ // external calls to DelegationManager
+ function _.undelegate(address) external;
+ function _.decreaseDelegatedShares(address,address,uint256) external;
+ function _.increaseDelegatedShares(address,address,uint256) external;
+
+ // external calls from DelegationManager to ServiceManager
+ function _.updateStakes(address[]) external => NONDET;
+
+ // external calls to Slasher
+ function _.isFrozen(address) external => DISPATCHER(true);
+ function _.canWithdraw(address,uint32,uint256) external => DISPATCHER(true);
+
+ // external calls to StrategyManager
+ function _.getDeposits(address) external => DISPATCHER(true);
+ function _.slasher() external => DISPATCHER(true);
+ function _.addShares(address,address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256, address) external => DISPATCHER(true);
+
+ // external calls to EigenPodManager
+ function _.addShares(address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256) external => DISPATCHER(true);
+
+ // external calls to EigenPod
+ function _.withdrawRestakedBeaconChainETH(address,uint256) external => DISPATCHER(true);
+
+ // external calls to DelayedWithdrawalRouter (from EigenPod)
+ function _.createDelayedWithdrawal(address, address) external => DISPATCHER(true);
+
+ // external calls to PauserRegistry
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
+
+ // envfree functions
+ function ownerToPod(address podOwner) external returns (address) envfree;
+ function getPod(address podOwner) external returns (address) envfree;
+ function ethPOS() external returns (address) envfree;
+ function eigenPodBeacon() external returns (address) envfree;
+ function beaconChainOracle() external returns (address) envfree;
+ function getBlockRootAtTimestamp(uint64 timestamp) external returns (bytes32) envfree;
+ function strategyManager() external returns (address) envfree;
+ function slasher() external returns (address) envfree;
+ function hasPod(address podOwner) external returns (bool) envfree;
+ function numPods() external returns (uint256) envfree;
+ function podOwnerShares(address podOwner) external returns (int256) envfree;
+ function beaconChainETHStrategy() external returns (address) envfree;
+
+ // harnessed functions
+ function get_podOwnerShares(address) external returns (int256) envfree;
+ function get_podByOwner(address) external returns (address) envfree;
+}
+
+// verifies that podOwnerShares[podOwner] is never a non-whole Gwei amount
+invariant podOwnerSharesAlwaysWholeGweiAmount(address podOwner)
+ get_podOwnerShares(podOwner) % 1000000000 == 0;
+
+// verifies that ownerToPod[podOwner] is set once (when podOwner deploys a pod), and can otherwise never be updated
+rule podAddressNeverChanges(address podOwner) {
+ address podAddressBefore = get_podByOwner(podOwner);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ address podAddressAfter = get_podByOwner(podOwner);
+ assert(podAddressBefore == 0 || podAddressBefore == podAddressAfter,
+ "pod address changed after being set!");
+}
+
+// verifies that podOwnerShares[podOwner] can become negative (i.e. go from zero/positive to negative)
+// ONLY as a result of a call to `recordBeaconChainETHBalanceUpdate`
+rule limitationOnNegativeShares(address podOwner) {
+ int256 podOwnerSharesBefore = get_podOwnerShares(podOwner);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ int256 podOwnerSharesAfter = get_podOwnerShares(podOwner);
+ if (podOwnerSharesAfter < 0) {
+ if (podOwnerSharesBefore >= 0) {
+ assert(f.selector == sig:recordBeaconChainETHBalanceUpdate(address, int256).selector,
+ "pod owner shares became negative from calling an unqualified function!");
+ } else {
+ assert(
+ (podOwnerSharesAfter >= podOwnerSharesBefore) ||
+ (f.selector == sig:recordBeaconChainETHBalanceUpdate(address, int256).selector),
+ "pod owner had negative shares decrease inappropriately"
+ );
+ }
+ }
+ // need this line to keep the prover happy :upside_down_face:
+ assert(true);
+}
diff --git a/certora/specs/properties.md b/certora/specs/properties.md
deleted file mode 100644
index 8032c7e27b..0000000000
--- a/certora/specs/properties.md
+++ /dev/null
@@ -1,48 +0,0 @@
-Author: Yura Sherman
-
-
-
-## Withdrawal
-
-- Cannot withdraw full funds if slashed
-- Cannot withdraw funds without appropriate delay
-- Cannot withdraw funds which are at risk of slashing
-- Cannot withdraw funds if middlewares haven't been updated (recording the incoming decrease in funds)
-- A queued withdrawal can be completed if it's pending and no longer slashable
-- A queued withdrawal can still be slashed
-
-## Slashing
-
-- slashing happens if and only if a provably malicious action by an operator took place
-- operator may be slashed only if allowToSlash() for that particular contract was called
-- slashing cannot happen after contractCanSlashOperatorUntil[operator][contractAddress] timestamp
-- contractCanSlashOperatorUntil[operator][contractAddress] changed => allowToSlash() or recordLastStakeUpdateAndRevokeSlashingAbility() was called
-- recordLastStakeUpdateAndRevokeSlashingAbility() should only be callable when contractCanSlashOperatorUntil[operator][contractAddress] == MAX_CAN_SLASH_UNTIL, and only by the contractAddress
-- Any contractAddress for which contractCanSlashOperatorUntil[operator][contractAddress] > current time can call freezeOperator(operator).
-- frozen operator cannot make deposits/withdrawals, cannot complete queued withdrawals
-- slashing and unfreezing is performed by the StrategyManager contract owner (is it permanent or configurable?)
-- frozenStatus[operator] changed => freezeOperator() or resetFrozenStatus() were called
-
-
-## StrategyManager
-
-- totalShares per strategy == Σ stakerStrategyShares[staker][strategy] for all stakers *plus* any shares in pending (queued) withdrawals
-- stakerStrategyShares[staker][strategy] increase => depositIntoStrategy() or depositIntoStrategyWithSignature() have been invoked
-- stakerStrategyShares[staker][strategy] decrease => queueWithdrawal() or slashShares() have been invoked
-- stakerStrategyList[staker] should contain all strategies for which stakerStrategyShares[staker][strategy] is nonzero
-- stakerStrategyList[staker] should contain no strategies for which stakerStrategyShares[staker][strategy] is zero
-
-## Strategy
-
-- balance of underlyingToken >= total supply of shares ( depends on how slashing works ?)
-
-## Delegation
-
-- a staker must be either registered as an operator or delegate to an operator
-- after registerAsOperator() is called, delegationTerms[operator] != 0
-- for an operator, delegatedTo[operator] == operator (operators are delegated to themselves)
-- operatorShares[operator][strategy] should increase only when delegateTo() delegateToBySignature(), or increaseDelegatedShares() is called
-- operatorShares[operator][strategy] should decrease only when either of the two decreaseDelegatedShares() is called
-- sum of operatorShares[operator][strategy] for all operators <= sum of StrategyManager.stakerStrategyShares[staker][strategy]
-
-- undelegate is only possible by queueing withdrawals for all of their deposited assets.
diff --git a/certora/specs/strategies/StrategyBase.spec b/certora/specs/strategies/StrategyBase.spec
index fc01106876..0f7ab5e556 100644
--- a/certora/specs/strategies/StrategyBase.spec
+++ b/certora/specs/strategies/StrategyBase.spec
@@ -1,29 +1,29 @@
-using StrategyManager as strategyManager
+using StrategyManager as strategyManager;
methods {
// external calls to StrategyManager
- stakerStrategyShares(address, address) returns (uint256) => DISPATCHER(true)
+ function _.stakerStrategyShares(address, address) external => DISPATCHER(true);
// external calls to PauserRegistry
- pauser() returns (address) => DISPATCHER(true)
- unpauser() returns (address) => DISPATCHER(true)
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
// external calls to ERC20
- balanceOf(address) returns (uint256) => DISPATCHER(true)
- transfer(address, uint256) returns (bool) => DISPATCHER(true)
- transferFrom(address, address, uint256) returns (bool) => DISPATCHER(true)
+ function _.balanceOf(address) external => DISPATCHER(true);
+ function _.transfer(address, uint256) external => DISPATCHER(true);
+ function _.transferFrom(address, address, uint256) external => DISPATCHER(true);
// external calls from StrategyManager to Slasher
- isFrozen(address) returns (bool) => DISPATCHER(true)
- canWithdraw(address,uint32,uint256) returns (bool) => DISPATCHER(true)
+ function _.isFrozen(address) external => DISPATCHER(true);
+ function _.canWithdraw(address,uint32,uint256) external => DISPATCHER(true);
// envfree functions
- totalShares() returns (uint256) envfree
- underlyingToken() returns (address) envfree
- sharesToUnderlyingView(uint256) returns (uint256) envfree
- sharesToUnderlying(uint256) returns (uint256) envfree
- underlyingToSharesView(uint256) returns (uint256) envfree
- underlyingToShares(uint256) returns (uint256) envfree
- shares(address) returns (uint256) envfree
+ function totalShares() external returns (uint256) envfree;
+ function underlyingToken() external returns (address) envfree;
+ function sharesToUnderlyingView(uint256) external returns (uint256) envfree;
+ function sharesToUnderlying(uint256) external returns (uint256) envfree;
+ function underlyingToSharesView(uint256) external returns (uint256) envfree;
+ function underlyingToShares(uint256) external returns (uint256) envfree;
+ function shares(address) external returns (uint256) envfree;
}
// // idea based on OpenZeppelin invariant -- see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/formal-verification/certora/specs/ERC20.spec#L8-L22
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000000..422b19445b
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1 @@
+module.exports = { extends: ['@commitlint/config-conventional'] };
diff --git a/docs/EigenLayer-delegation-flow.md b/docs/EigenLayer-delegation-flow.md
deleted file mode 100644
index 13c7758675..0000000000
--- a/docs/EigenLayer-delegation-flow.md
+++ /dev/null
@@ -1,31 +0,0 @@
-
-# Delegation Flow
-
-While delegating to an operator is designed to be a simple process from the staker's perspective, a lot happens "under the hood".
-
-## Operator Registration
-
-In order to be delegated *to*, an operator must have first called `DelegationManager.registerAsOperator`. If a staker tries to delegate to someone who has not previously registered as an operator, their transaction will fail.
-
-When an operator registers in EigenLayer, the following flow of calls between contracts occurs:
-
-![Registering as an Operator in EigenLayer](images/EL_operator_registration.png?raw=true "Registering as an Operator in EigenLayer")
-
-1. The would-be operator calls `DelegationManager.registerAsOperator`, providing either a `DelegationTerms`-type contract or an EOA as input. The DelegationManager contract stores the `DelegationTerms`-type contract provided by the operator, which may act as an intermediary to help facilitate the relationship between the operator and any stakers who delegate to them.
-All of the remaining steps (2-4) proceed as outlined in the delegation process below; the DelegationManager contract treats things as if the operator has delegated *to themselves*.
-
-## Staker Delegation
-
-For a staker to delegate to an operator, the staker must either:
-1. Call `DelegationManager.delegateTo` directly
-OR
-2. Supply an appropriate ECDSA signature, which can then be submitted by the operator (or a third party) as part of a call to `DelegationManager.delegateToBySignature`
-
-In either case, the end result is the same, and the flow of calls between contracts looks identical:
-
-![Delegating in EigenLayer](images/EL_delegating.png?raw=true "Delegating in EigenLayer")
-
-1. As outlined above, either the staker themselves calls `DelegationManager.delegateTo`, or the operator (or a third party) calls `DelegationManager.delegateToBySignature`, in which case the DelegationManager contract verifies the provided ECDSA signature
-2. The DelegationManager contract calls `Slasher.isFrozen` to verify that the operator being delegated to is not frozen
-3. The DelegationManager contract calls `StrategyManager.getDeposits` to get the full list of the staker (who is delegating)'s deposits. It then increases the delegated share amounts of operator (who is being delegated to) appropriately
-4. The DelegationManager contract makes a call into the operator's stored `DelegationTerms`-type contract, calling the `onDelegationReceived` function to inform it of the new delegation
\ No newline at end of file
diff --git a/docs/EigenLayer-deposit-flow.md b/docs/EigenLayer-deposit-flow.md
deleted file mode 100644
index bc79e15a23..0000000000
--- a/docs/EigenLayer-deposit-flow.md
+++ /dev/null
@@ -1,41 +0,0 @@
-
-# Deposit Flow
-
-There are 2 main ways in which a staker can deposit new funds into EigenLayer -- depositing into an Strategy through the StrategyManager, and depositing "Beacon Chain ETH" (or proof thereof) through the EigenPodManager.
-
-## Depositing Into an Strategy Through the StrategyManager
-The StrategyManager has two functions for depositing funds into Strategy contracts -- `depositIntoStrategy` and `depositIntoStrategyWithSignature`. In both cases, a specified `amount` of an ERC20 `token` is transferred from the caller to a specified Strategy-type contract `strategy`. New shares in the strategy are created according to the return value of `strategy.deposit`; when calling `depositIntoStrategy` these shares are credited to the caller, whereas when calling `depositIntoStrategyWithSignature` the new shares are credited to a specified `staker`, who must have also signed off on the deposit (this enables more complex, contract-mediated deposits, while a signature is required to mitigate the possibility of griefing or dusting-type attacks).
-We note as well that deposits cannot be made to a 'frozen' address, i.e. to the address of an operator who has been slashed or to a staker who is actively delegated to a slashed operator.
-When performing a deposit through the StrategyManager, the flow of calls between contracts looks like the following:
-
-![Depositing Into EigenLayer Through the StrategyManager -- Contract Flow](images/EL_depositing.png?raw=true "Title")
-
-1. The depositor makes the initial call to either `StrategyManager.depositIntoStrategy` or `StrategyManager.depositIntoStrategyWithSignature`
-2. The StrategyManager calls `Slasher.isFrozen` to verify that the recipient (either the caller or the specified `staker` input) is not 'frozen' on EigenLayer
-3. The StrategyManager calls the specified `token` contract, transferring specified `amount` of tokens from the caller to the specified `strategy`
-4. The StrategyManager calls `strategy.deposit`, and then credits the returned `shares` value to the recipient
-5. The StrategyManager calls `DelegationManager.increaseDelegatedShares` to ensure that -- if the recipient has delegated to an operator -- the operator's delegated share amounts are updated appropriately
-
-## Depositing Beacon Chain ETH Through the EigenPodManager
-This section covers depositing *new ETH* into the Beacon Chain, with withdrawal credentials pointed to an EigenLayer-controlled contract (an EigenPod) and proving your deposit so it is credited in EigenLayer; this is a multi-step process. For more details on the EigenPods' design in general, see the [EigenPods doc](./EigenPods.md).
-
-The initial deposit of ETH into the Beacon Chain is performed through the EigenPodManager:
-
-![Depositing ETH Into the Beacon Chain Through the EigenPodManager](images/EL_depositing_BeaconChainETH.png?raw=true "Title")
-
-1. The depositor calls `EigenPodManager.stake`
-2. The EigenPodManager deploys a new EigenPod for the caller – if they do not already have one – and then calls `EigenPod.stake`
-3. The EigenPod deposits ETH into the Beacon Chain through the "ETH2 Deposit Contract". The deposited ETH was supplied as part of the initial call (1), which was passed along to the EigenPod by the EigenPodManager in its own call (2)
-
-After depositing ETH, the depositor waits for the Beacon Chain state root to be updated through EigenLayer's BeaconChainOracle. After an update has been posted that reflects the EigenPod's increased Beacon Chain balance (resulting from the deposit above), then the depositor can call `EigenPod.verifyWithdrawalCredentials` to initiate the following flow:
-
-![Depositing ETH Into the Beacon Chain Through the EigenPodManager Part 2](images/EL_depositing_BeaconChainETH_2.png?raw=true "Title")
-
-1. The depositor calls EigenPod.verifyWithdrawalCredentials on the EigenPod deployed for them above
-2. The EigenPod gets the most recent Beacon Chain state root from the EigenPodManager by calling `EigenPodManager.getBeaconChainStateRoot` (the EigenPodManager further passes this query along to the BeaconChainOracle, prior to returning the most recently-posted state root).
-3. The EigenPod calls `EigenPodManager.updateBeaconChainBalance` to update the EigenPodManager's accounting of EigenPod balances
-4. The EigenPodManager fetches the Slasher's address from the StrategyManager
-4. *If the operator has been slashed on the Beacon Chain* (and this is reflected in the latest BeaconChainOracle update), then the EigenPodManager calls `Slasher.freezeOperator` to freeze the staker
-5. The EigenPod calls `EigenPodManager.depositBeaconChainETH` to trigger an update in EigenLayer which will reflect the staker's new beacon chain balance
-6. The EigenPodManager forwards the information through a call to `StrategyManager.depositBeaconChainETH`, which updates the staker's balance in the enshrined 'beaconChainETHStrategy' after...
-7. The StrategyManager makes a call to `Slasher.isFrozen` to verify that the depositor is not 'frozen' in EigenLayer
diff --git a/docs/EigenLayer-tech-spec.md b/docs/EigenLayer-tech-spec.md
deleted file mode 100644
index 74ad2fbbd2..0000000000
--- a/docs/EigenLayer-tech-spec.md
+++ /dev/null
@@ -1,127 +0,0 @@
-
-# EigenLayer Technical Specification
-
-## Overview
-EigenLayer is a set of smart contracts deployed on Ethereum that enable restaking of assets to secure new services.
-**Restaking** is the process of staking an asset that has already been staked in another protocol into EigenLayer. The canonical example of restaking is ETH restaking, in which an existing Ethereum validator restakes the ETH they have staked to secure Ethereum Proof-of-Stake consensus, but restaking also encompasses actions such as depositing Liquid Staked Tokens into EigenLayer.
-**Restaked assets** are placed under the control of EigenLayer’s smart contracts, enabling them to act as stake securing additional services, such as rollups, bridges, and data availability networks.
-EigenLayer connects stakers who are willing to provide these additional services to consumers – typically protocols or companies – who want secure services with decentralized validator networks. These consumers pay for the services delivered to them, enabling stakers to earn returns on their staked assets, *in addition* to their existing staking rewards. Thus with restaking, stakers can augment their rewards in exchange for the services they opt-in to providing.
-
-These returns provide an economic incentive for stakers to opt-in and act as “operators” for services. In order to disincentivize malicious actions and deliver *cryptoeconomic security* to services, services built on EigenLayer also impose **slashing conditions** in which provably bad behavior is punished, through the 'slashing' of malicious operators' deposited funds.
-
-EigenLayer is built to be permissionless – anyone can join as a staker, consume a service, or even create their own service – with no external approval acquired. We term this **open innovation**. Being an operator for any service on EigenLayer is strictly **opt-in**. Stakers can choose to serve a single service, many (compatible) services, or simply delegate their stake to an operator *whom they trust to not get slashed*, that can earn rewards using the staker's restaked assets (and presumably somehow share the rewards).
-
-New services built on EigenLayer can define their own, arbitrary *slashing conditions*, which allows services to potentially slash their operators for any action that is **on-chain checkable**. In particular, this is compatible with the permissionless ability to launch a new service on EigenLayer only because all services are opt-in; if a staker believes a service has an unsafe slashing mechanism, then they can simply not opt-in to serving that application.
-
-## Actors in the System
-
-### Stakers
-A **staker** is any party who has assets deposited into EigenLayer. In general, these could be any mix of ERC20 tokens and/or staked ETH itself (deposited by transferring withdrawal credentials to EigenLayer or depositing to the Beacon Chain through EigenLayer). Stakers can delegate their stake to an operator, or act as an operator themselves.
-
-### Operators
-**Operators** in EigenLayer are those users who actually run the software built on top of EigenLayer. Operators register in EigenLayer, allowing stakers to delegate to them, and then opt-in to any mix of services built on top of EigenLayer; each service that an operator chooses to serve may impose its own slashing conditions on the operator.
-
-### Watchers
-**NOTE: at present, EigenLayer does not feature any optimistically rolled up claims. This paragraph reflects a potential future state of the system.**
-
-Some operations in EigenLayer are "**optimisitically rolled up**". This is a design pattern used where it is either impossible or infeasible to prove that some claim is true, but *easy to check a counterexample that proves the claim is false*. The general pattern is:
-1. A "rolled-up" claim is made, asserting that some condition is true.
-2. There is a "fraudproof period", during which anyone can *disprove* the claim with a single counterexample. If a claim is disproven, then the original claimant is punished in some way (e.g. by forfeiting some amount or being slashed).
-3. If the claim is *not* disproved during the fraudproof period, then it is assumed to be true, and the system proceeds from this assumption.
-
-**Watchers** are parties who passively observe these "rolled up" claims, and step in only in the case of an invalid or false claim. In such a case, an honest watcher will perform the fraudproof, disproving the claim.
-
-### Services / Middleware
-We refer to software built on top of EigenLayer as either **services** or **middleware**. Since we anticipate a wide variety of services built on top of EigenLayer, the EigenLayer team has endeavored to make a minimal amount of assumptions about the struture of services.
-
-## Key Assumptions
-### Discretization of Services ("Tasks")
-We assume that services manage **tasks**. In other words, we assume that services discretize commitments undertaken by operators, with each task defining the time period for which the service's operators' stakes are placed "at stake", i.e. potentially subject to slashing.
-
-### Delegation "Trust Network" Structure
-It is assumed that any staker who delegates their stake to an operator is in the same "trust network" as their chosen operator. In other words, the Staker-DelegatedOperator relationship is assumed to have a significant *trust component*. Operators may have the ability to steal the rewards that they earn from the deposited funds of stakers who delegate to them, as well as imposing other negative externalities on those delegated to them.
-
-### Non-Compromise of Trusted Roles
-We assume that all trusted roles (multisigs, etc) remain solely in the hands of honest parties.
-
-### Honest Watcher Assumption
-**NOTE: at present, EigenLayer does not feature any optimistically rolled up claims. This paragraph reflects a potential future state of the system.**
-
-For any "optimistically-rolled-up" process that relies on fraudproofs (i.e. in which someone makes an "optimistic claim" that can then be *disproven* within some window, and is otherwise treated as true), we **assume there is at least one honest watcher** who will step in to fraudproof false claims when they are made.
-We assume that such an honest watcher will fraudproof *all false claims*, regardless of the size and independent of any financial incentive that may or may not be present for the watcher.
-Efforts have been made to relax this assumption, but work is still ongoing.
-
-## Overview of Contracts
-The `StrategyManager` contract is the primary coordinator for inflows and outflows of tokens to/from EigenLayer itself. The StrategyManager hands restaked assets over to `Strategy` contracts, which may perform targeted management of restaked assets in order to earn returns outside of EigenLayer (e.g. by lending the assets out on a lending protocol) -- more details on `Strategies` to follow.
-
-Any staker in EigenLayer can choose *either* to register as an operator *or* to delegate their restaked assets to an existing operator. These actions are performed on the `DelegationManager` contract.
-
-Withdrawals and undelegation are handled through the `StrategyManager`. Both *necessitate delays*, since it is infeasible to immediately know whether or not specific restaked funds are "at stake" on any existing tasks created by services. Instead, stakers who wish to withdraw and/or undelegate must go through a *queued withdrawal* process, in which they:
-1. Begin the withdrawal, signaling that the funds they are withdrawing should no longer be placed "at stake" on new tasks.
-2. Push any necessary updates to middlewares (or wait for someone else to do so), recording the decrease in funds to be placed at stake on new tasks.
-3. Complete their withdrawal after an appropriate delay, i.e. once all tasks have been completed upon which the to-be-withdrawn funds were placed at stake.
-
-## Contract-Specific Overview
-
-### StrategyManager
-The StrategyManager contract keeps track of all stakers’ deposits, in the form of “shares” in the Strategy contracts. Stakers who wish to deposit ERC20 tokens can do so by calling the StrategyManager, which will transfer the depositor’s tokens to a user-specified Strategy contract, which in turn manages the tokens to generate rewards in the deposited token (or just passively holds them, if the depositor is risk-averse or if the token lacks good reward-generating opportunities).
-
-As the arbiter of share amounts, the StrategyManager is also the main interaction point for withdrawals from EigenLayer. In general, withdrawals from EigenLayer must ensure that restaked assets cannot be withdrawn until they are no longer placed at risk of slashing by securing some service on EigenLayer. To accomplish this, EigenLayer enforces "guaranteed stake updates on withdrawals". The full withdrawal process is outlined in [the withdrawal flow doc](./EigenLayer-withdrawal-flow.md).
-
-Lastly, the StrategyManager processes slashing actions, in which some (or all) of a user's shares are transferred to a specified address. Slashing of this kind should only ever occur as the result of an operator taking a provably malicious action.
-
-## Strategy(s)
-Each `Strategy` contract is expected to manage a single, underlying ERC20 token, known as the `underlyingToken`. Each user's holdings in the strategy is expected to be reflected in a number of `shares`, and the strategy is expected to define methods for converting between an amount of underlying tokens and an amount of shares (and vice versa), somewhat similar to an [ERC4626 Vault](https://eips.ethereum.org/EIPS/eip-4626) but without most of the tokenizing aspects of EIP-4626 (e.g. no `transfer` or `transferFrom` functions are expected).
-Assets *may* be depositable or withdrawable to a single `Strategy` contract in multiple forms, and the strategy *may* either actively or passively manage the funds.
-Since individual users' share amounts are stored in the `StrategyManager` itself, it is generally expected that each strategy's `deposit` and `withdraw` functions are restricted to only be callable by the `StrategyManager` itself.
-
-### DelegationManager
-The DelegationManager contract handles delegation of stakers’ deposited funds to “operators”, who actually serve the applications built on EigenLayer. While delegation to someone else is entirely optional, any operator on EigenLayer must also "register as an operator" by calling the `registerAsOperator` function of this contract.
-
-Any staker in EigenLayer may choose to become *either*:
-1. an **operator**, allowing other stakers to delegate to them, and potentially earning a share of the funds generated from using the restaked assets of stakers who delegate to them
-
-OR
-
-2. a **delegator**, choosing to allow an operator to use their restaked assets in securing applications built on EigenLayer
-
-Stakers can choose which path they’d like to take by interacting with the DelegationManager contract. Stakers who wish to delegate select an operator whom they trust to use their restaked assets to serve applications, while operators register to allow others to delegate to them, specifying a `DelegationTerms`-type contract (or EOA) which receives the funds they earn and can potentially help to mediate their relationship with any stakers who delegate to them.
-
-#### Storage in DelegationManager
-
-The `DelegationManager` contract relies heavily upon the `StrategyManager` contract. It keeps track of all active operators -- specifically by storing the `Delegation Terms` for each operator -- as well as storing what operator each staker is delegated to.
-A **staker** becomes an **operator** by calling `registerAsOperator`. Once registered as an operator, the mapping entry `delegationTerms[operator]` is set **irrevocably** -- in fact we define someone as an operator if `delegationTerms[operator]` returns a nonzero address. Querying `delegationTerms(operator)` returns a `DelegationTerms`-type contract; however, the returned address may be an EOA, in which case the operator is assumed to handle payments through more "trusted" means, such as by doing off-chain computations and separate distributions.
-The mapping `delegatedTo` stores which operator each staker is delegated to. Querying `delegatedTo(staker)` will return the *address* of the operator that `staker` is delegated to. Note that operators are *always considered to be delegated to themselves*.
-
-DelegationManager defines when an operator is delegated or not, as well as defining what makes someone an operator:
-* someone who has registered as an operator *once* is *always* considered to be an operator
-* an **operator** is considered to be 'delegated' to themself upon registering as an operator
-
-Similar to withdrawals, **undelegation** in EigenLayer necessitates a delay or clawback mechanism. To elaborate: if a staker is delegated to an operator, and that operator places the staker's assets 'at stake' on some task in which the operator *misbehaves* (i.e. acts in a slashable manner), it is critical that the staker's funds can still be slashed
-* stakers can only undelegate by queuing withdrawal(s) for *all of their assets currently deposited in EigenLayer*, ensuring that all existing tasks for which the staker's currently deposited assets are actively at stake are resolved prior to allowing a different operator to place those same assets at stake on other tasks
-
-### Slasher
-The `Slasher` contract is the central point for slashing in EigenLayer.
-Operators can opt-in to slashing by arbitrary contracts by calling the function `allowToSlash`. A contract with slashing permission can itself revoke its slashing ability *after a specified time* -- named `serveUntil` in the function input -- by calling `recordLastStakeUpdateAndRevokeSlashingAbility`. The time until which `contractAddress` can slash `operator` is stored in `contractCanSlashOperatorUntil[operator][contractAddress]` as a uint32-encoded UTC timestamp, and is set to the `MAX_CAN_SLASH_UNTIL` (i.e. max value of a uint32) when `allowToSlash` is initially called.
-
-At present, slashing in EigenLayer is a multi-step process. When a contract wants to slash an operator, it will call the `freezeOperator` function. Any `contractAddress` for which `contractCanSlashOperatorUntil[operator][contractAddress]` is *strictly greater than the current time* can call `freezeOperator(operator)` and trigger **freezing** of the operator. An operator who is frozen -- *and any staker delegated to them* cannot make new deposits or withdrawals, and cannot complete queued withdrawals, as being frozen signals detection of malicious action and they may be subject to slashing. At present, slashing itself is performed by the owner of the `StrategyManager` contract, who can also 'unfreeze' accounts.
-
-### EigenPodManager
-The `EigenPodManager` contract is designed to handle Beacon Chain ETH being staked on EigenLayer. Specifically, it is designed around withdrawal credentials pointed directly to the EigenLayer contracts, i.e. primarily those of "solo stakers". The EigenPodManager creates new EigenPod contracts, and coordinates virtual deposits and withdrawals of shares in an enshrined `beaconChainETH` strategy to and from the StrategyManager. More details on the EigenPodManager and EigenPod contracts can be found in the dedicated [EigenPod Doc](./EigenPods.md).
-
-### EigenPods
-Each staker can deploy a single `EigenPod` contract through the EigenPodManager that allows them to stake ETH into the Beacon Chain and restake their deposits on EigenLayer. A watcher can also prove that an Ethereum validator that is restaked on an EigenPod has a lower balance on the Beacon Chain than its stake in EigenLayer. Finally, EigenPods also facilitate the execution of withdrawals of partially withdrawn rewards from the Beacon Chain on behalf of validators (a major upgrade in the upcoming Capella consensus layer hardfork). Calls are -- in general -- passed from the EigenPod to the EigenPodManager to the StrategyManager, to trigger additional accounting logic within EigenLayer.
-EigenPods are deployed using a beacon proxy pattern, allowing simultaneous upgrades of all EigenPods. This upgradeability will likely be necessary in order to more fully integrate Beacon Chain withdrawals through the EigenPods, e.g. if Ethereum upgrades to smart contract-triggered withdrawals.
-
-### BeaconChainOracle
-This contract will post periodic Beacon Chain state root updates, for consumption by the EigenPod contracts.
-Details TBD.
-
-## High-Level Goals (And How They Affect Design Decisions)
-1. Anyone can launch a new service on EigenLayer, permissionlessly
- * all services are opt-in by design, so operators can simply choose to not serve a malicious application
- * operators must signal *specific contracts* that can slash them, potentially limiting the damage that can be done, e.g. by a malicious or poorly-written upgrade to a service's smart contracts
-2. Stakers should *not* be able to withdraw any stake that is "active" on a service
- * assuming that services use a "task-denominated" model helps to enable this paradigm
- * the queued withdrawal mechanism is designed to first stop the withdrawn funds from being placed at stake on new tasks, and then to verify when the funds are indeed no longer at stake
- * the undelegation process enforces similar delays -- it is only possible for a staker to undelegate by queuing a withdrawal for all of their assets currently deposited in EigenLayer
diff --git a/docs/EigenLayer-withdrawal-flow.md b/docs/EigenLayer-withdrawal-flow.md
deleted file mode 100644
index cd95d08209..0000000000
--- a/docs/EigenLayer-withdrawal-flow.md
+++ /dev/null
@@ -1,37 +0,0 @@
-
-# Withdrawal Flow
-
-Withdrawals from EigenLayer are a multi-step process. This is necessary in order to ensure that funds can only be withdrawn once they are no longer placed 'at stake' on an active task of a service built on top of EigenLayer. For more details on the design of withdrawals and how they guarantee this, see the [Withdrawals Design Doc](./Guaranteed-stake-updates.md).
-
-The first step of any withdrawal involves "queuing" the withdrawal itself. The staker who is withdrawing their assets can specify the Strategy(s) they would like to withdraw from, as well as the respective amount of shares to withdraw from each of these strategies. Additionally, the staker can specify the address that will ultimately be able to withdraw the funds. Being able to specify an address different from their own allows stakers to "point their withdrawal" to a smart contract, which can potentially facilitate faster/instant withdrawals in the future.
-
-## Queueing a Withdrawal
-
-![Queuing a Withdrawal](images/EL_queuing_a_withdrawal.png?raw=true "Queuing a Withdrawal")
-
-1. The staker starts a queued withdrawal by calling the `StrategyManager.queueWithdrawal` function. They set the receiver of the withdrawn funds as `withdrawer` address. Calling `queueWithdrawal` also removes the user's shares in staker-specific storage and the corresponding shares delegated to the operator. Shares in the strategies being withdrawn from, however, technically remain (i.e. the total number of shares in each strategy does not change). This ensures that the value per share reported by each strategy will remain consistent, and that the shares will continue to accrue gains (or losses!) from any strategy management until the withdrawal is completed.
-2. Prior to actually performing the above processing, the StrategyManager calls `Slasher.isFrozen` to ensure that the staker is not 'frozen' in EigenLayer (due to them or the operator they delegate to being slashed).
-3. The StrategyManager calls `DelegationManager.decreaseDelegatedShares` to account for any necessary decrease in delegated shares (the DelegationManager contract will not modify its storage if the staker is not an operator and not actively delegated to one).
-4. The StrategyManager queries `DelegationManager.delegatedTo` to get the account that the caller is *currently delegated to*. A hash of the withdrawal's details – including the account that the caller is currently delegated to – is stored in the StrategyManager, to record that the queued withdrawal has been created and to store details which can be checked against when the withdrawal is completed.
-5. If the the staker is withdrawing *all of their shares currently in EigenLayer, and they set the `undelegateIfPossible` input to 'true'*, then the staker will be immediately 'undelegated' from the operator who they are currently delegated to; this is accomplished through the StrategyManager making a call to `DelegationManager.undelegate`. This allows the staker to immediately change their delegation to a different operator if desired; in such a case, any *new* deposits by the staker will immediately be delegated to the new operator, while the withdrawn funds will be 'in limbo' until the withdrawal is completed.
-
-## Completing a Queued Withdrawal
-
-![Completing a Queued Withdrawal](images/EL_completing_queued_withdrawal.png?raw=true "Completing a Queued Withdrawal")
-
-1. The withdrawer completes the queued withdrawal after the stake is inactive, by calling `StrategyManager.completeQueuedWithdrawal`. They specify whether they would like the withdrawal in shares (to be redelegated in the future) or in tokens (to be removed from the EigenLayer platform), through the `withdrawAsTokens` input flag. The withdrawer must also specify an appropriate `middlewareTimesIndex` which proves that the withdrawn funds are no longer at stake on any active task. The appropriate index can be calculated off-chain and checked using the `Slasher.canWithdraw` function. For more details on this design, see the [Withdrawals Design Doc](./Guaranteed-stake-updates.md).
-2. The StrategyManager calls `Slasher.isFrozen` to ensure that the staker who initiated the withdrawal is not 'frozen' in EigenLayer (due to them or the operator they delegate to being slashed). In the event that they are frozen, this indicates that the to-be-withdrawn funds are likely subject to slashing.
-3. Depending on the value of the supplied `withdrawAsTokens` input flag:
-* If `withdrawAsTokens` is set to 'true', then StrategyManager calls `Strategy.withdraw` on each of the strategies being withdrawn from, causing the withdrawn funds to be transferred from each of the strategies to the withdrawer.
-OR
-* If `withdrawAsTokens` is set to 'false', then StrategyManager increases the stored share amounts that the withdrawer has in the strategies in question (effectively completing the transfer of shares from the initiator of the withdrawal to the withdrawer), and then calls `DelegationManager.increaseDelegatedShares` to trigger any appropriate updates to delegated share amounts.
-
-## Special Case -- Beacon Chain Full Withdrawals
-
-If a withdrawal includes withdrawing 'Beacon Chain ETH' from EigenLayer, then, it must be limited to *only* Beacon Chain ETH. In addition, before *completing* the withdrawal, the staker must trigger a full withdrawal from the Beacon Chain (as of now this must be originated from the validating keys, but details could change with Beacon Chain withdrawals) on behalf of enough of their validators to provide sufficient liquidity for their withdrawal.
-The staker's EigenPod's balance will eventually increase by the amount withdrawn, and the withdrawals will be reflected in a BeaconChainOracle state root update.
-At that point, the staker will prove their full withdrawals (differentiated from partial withdrawals by comparing the amount withdrawn against a hardcoded threshold) credited to the EigenPod against the beacon chain state root via the `verifyAndProcessWithdrawal` function. If the withdrawal's amount is greater than or equal to how much the corresponding Ethereum validator had restaked on EigenLayer, then the excess amount gets instantly withdrawn. If the withdrawal amount is less than the amount restaked on behalf of the validator in EigenLayer, the EigenPod will remove virtual 'beaconChainETH' shares accordingly, by calling the `StrategyManager.recordOvercommittedBeaconChainETH` function.
-
-Once the above is done, then when the withdrawal is completed through calling `StrategyManager.completeQueuedWithdrawal` function (as above), the StrategyManager will pass a call to `EigenPodManager.withdrawRestakedBeaconChainETH`, which will in turn pass a call onto the staker's EigenPod itself, invoking the `EigenPod.withdrawRestakedBeaconChainETH` function and triggering the actual transfer of ETH from the EigenPod to the withdrawer. This final call will only fail if the full withdrawals made and proven to the EigenPod do not provide sufficient liquidity for the EigenLayer withdrawal to occur.
-
-There exists an edge case in which a staker queues a withdrawal for all (or almost all) of their virtual beaconChainETH shares prior to a call to `StrategyManager.recordOvercommittedBeaconChainETH` -- in this case, once the staker's virtual beaconChainETH shares are decreased to zero, a special `beaconChainETHSharesToDecrementOnWithdrawal` variable is incremented, and in turn when the staker completes their queued withdrawal, the amount will be subtracted from their withdrawal amount. In other words, if the staker incurs a nonzero `beaconChainETHSharesToDecrementOnWithdrawal` amount, then withdrawals of the staker's beaconChainETH shares will prioritize decrementing this amount, prior to sending the staker themselves any funds.
\ No newline at end of file
diff --git a/docs/EigenPods.md b/docs/EigenPods.md
deleted file mode 100644
index 4b2a12fd7e..0000000000
--- a/docs/EigenPods.md
+++ /dev/null
@@ -1,74 +0,0 @@
-
-# EigenPods: Handling Beacon Chain ETH
-
-## Overview
-
-This document explains *EigenPods*, the mechanism by which EigenLayer facilitates the restaking of native beacon chain ether.
-
-It is important to contrast this with the restaking of liquid staking derivatives (LSDs) on EigenLayer. EigenLayer will integrate with liquid staking protocols "above the hood", meaning that withdrawal credentials will be pointed to EigenLayer at the smart contract layer rather than the consensus layer. This is because liquid staking protocols need their contracts to be in possession of the withdrawal credentials in order to not have platform risk on EigenLayer. As always, this means that value of liquid staking derivatives carries a discount due to additional smart contract risk.
-
-The architectural design of the EigenPods system is inspired by various liquid staking protocols, particularly Rocket Pool 🚀.
-
-## The EigenPodManager
-
-The EigenPodManager facilitates the higher level functionality of EigenPods and their interactions with the rest of the EigenLayer smart contracts (the StrategyManager and the StrategyManager's owner). Stakers can call the EigenPodManager to create pods (whose addresses are deterministically calculated via the Create2 OZ library) and stake on the Beacon Chain through them. The EigenPodManager also handles the 'overcommitements' of all EigenPods and coordinates processing of overcommitments with the StrategyManager.
-
-## The EigenPod
-
-The EigenPod is the contract that a staker must set their Ethereum validators' withdrawal credentials to. EigenPods can be created by stakers through a call to the EigenPodManager. EigenPods are deployed using the beacon proxy pattern to have flexible global upgradability for future changes to the Ethereum specification. Stakers can stake for an Etherum validator when they create their EigenPod, through further calls to their EigenPod, and through parallel deposits to the Beacon Chain deposit contract.
-
-### Beacon State Root Oracle
-
-EigenPods extensively use a Beacon State Root Oracle that will bring beacon state roots into Ethereum for every [`SLOTS_PER_HISTORICAL_ROOT`](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#time-parameters) slots (currently 8192 slots or ~27 hours) so that all intermediate state roots can be proven against the ones posted on the execution layer.
-
-The following sections are all related to managing Consensus Layer (CL) and Execution Layer (EL) balances via proofs against the beacon state root brought to the EL by the oracle. The below diagram will be of great help to understanding their functioning.
-
-![EigenPods_Architecture drawio](./images/EL_eigenpods_architecture.png)
-
-
-## EigenPods before Restaking
-When EigenPod contracts are initially deployed, the "restaking" functionality is turned off - the withdrawal credential proof has not been initiated yet. In this "non-restaking" mode, the contract may be used by its owner freely to withdraw validator balances from the beacon chain via the `withdrawBeforeRestaking` function. This function routes the withdrawn balance directly to the `DelayedWithdrawalRouter` contract. Once the EigenPod's owner verifies that their withdrawal credentials are pointed to the EigenPod via `verifyWithdrawalCredentialsAndBalance`, the `hasRestaked` flag will be set to true and any withdrawals must now be proven for via the `verifyAndProcessWithdrawal` function.
-
-### Merkle Proof of Correctly Pointed Withdrawal Credentials
-After staking an Etherum validator with its withdrawal credentials pointed to their EigenPod, a staker must show that the new validator exists and has its withdrawal credentials pointed to the EigenPod, by proving it against a beacon state root with a call to `verifyWithdrawalCredentialsAndBalance`. The EigenPod will verify the proof (along with checking for replays and other conditions) and, if the ETH validator's current (not effective) balance is proven to be greater than `REQUIRED_BALANCE_WEI`, then the EigenPod will call the EigenPodManager to forward a call to the StrategyManager, crediting the staker with `REQUIRED_BALANCE_WEI` shares of the virtual beacon chain ETH strategy. `REQUIRED_BALANCE_WEI` will be set to an amount of ether that a validator could get slashed down to only due to malice or negligence.
-
-### Merkle Proofs for Overcommitted Balances
-If a Ethereum validator restaked on an EigenPod has a balance that falls below `REQUIRED_BALANCE_WEI`, then they are overcommitted to EigenLayer, meaning they have less stake on the beacon chain than they the amount they have recorded as being restaked in Eigenlayer. Any watcher can prove to EigenPods that the EigenPod has a validator that is in such a state, by submitting a proof of the overcomitted validator's balance via the `verifyOvercommittedStake` function. If proof verification and other checks succeed, then `REQUIRED_BALANCE_WEI` will be immediately decremented from the EigenPod owner's (i.e. the staker's) shares in the StrategyManager. The existence of an overcommitted validator imposes a negative externality on middlewares that the staker is securing, since these middlewares will effectively overestimate their security -- proving overcommitment provides a mechanism to "eject" these validators from EigenLayer, to help minimize the amount of time this overestimation lasts. Note that a validator with a balance of 0 ETH may be either withdrawn or, in the rare case, slashed down to 0 ETH. In the case of the latter, we verify the status of the validator in addition to their balance. In the case of the former, the status of the validator should be verified through use of the verifyAndProcessWithdrawal function.
-
-
-### Merkle Proofs of Full/Partial Withdrawals
-
-Whenever a staker withdraws one of their validators from the beacon chain to provide liquidity, they have a few options. Stakers could keep the ETH in the EigenPod and continue staking on EigenLayer, in which case their ETH, when withdrawn to the EigenPod, will not earn any additional Ethereum staking rewards, but may continue to earn rewards on EigenLayer. Stakers could also queue withdrawals on EigenLayer for their virtual beacon chain ETH strategy shares, which will be fullfilled once their obligations to EigenLayer have ended and their EigenPod has enough balance to complete the withdrawal.
-
-In this second case, in order to withdraw their balance from the EigenPod, stakers must provide a valid proof of their full withdrawal or partial withdrawal (withdrawals of beacon chain rewards). In the case of the former, withdrawals are processed via the queued withdrawal system while in the latter, the balance is instantly withdrawable (as it is technically not being restaked). We distinguish between partial and full withdrawals by checking the `validator.withdrawableEpoch`. If the `validator.withdrawableEpoch <= executionPayload.slot/SLOTS_PER_EPOCH` then it is classified as a full withdrawal (here `executionPayload` contains the withdrawal being proven). This is because the `validator.withdrawableEpoch` is set when a validator submits a signed exit transaction. It is only after this that their withdrawal can be picked up by a sweep and be processed. In the case of a partial withdrawal, `validator.withdrawableEpoch` is set to FFE (far future epoch).
-
-We also must prove the `executionPayload.blockNumber > mostRecentWithdrawalBlockNumber`, which is stored in the contract. `mostRecentWithdrawalBlockNumber` is set when a validator makes a withdrawal in the pre-restaking phase of the EigenPod deployment. Without this check, a validator can make a partial withdrawal in the EigenPod's pre-restaking mode, withdraw it and then try to prove the same partial withdrawal once withdrawal credentials have been repointed and proven, thus double withdrawing (assuming that they have restaked balance in the EigenPod during the second withdrawal).
-
-In this second case, in order to withdraw their balance from the EigenPod, stakers must provide a valid proof of their full withdrawal against a beacon chain state root. Full withdrawals are differentiated from partial withdrawals by checking against the validator in question's 'withdrawable epoch'; if the validator's withdrawable epoch is less than or equal to the slot's epoch, then the validator has fully withdrawn because a full withdrawal is only processable at or after the withdrawable epoch has passed. Once the full withdrawal is successfully verified, there are 4 cases, each handled slightly differently:
-
-1. If the withdrawn amount is greater than `REQUIRED_BALANCE_GWEI` and the validator was *not* previously proven to be "overcommitted", then `REQUIRED_BALANCE_WEI` is held for processing through EigenLayer's normal withdrawal path, while the excess amount above `REQUIRED_BALANCE_GWEI` is marked as instantly withdrawable.
-
-2. If the withdrawn amount is greater than `REQUIRED_BALANCE_GWEI` and the validator *was* previously proven to be "overcommitted", then `REQUIRED_BALANCE_WEI` is held for processing through EigenLayer's normal withdrawal path, while the excess amount above `REQUIRED_BALANCE_GWEI` is marked as instantly withdrawable, identical to (1) above. Additionally, the podOwner's beaconChainShares in EigenLayer are increased by `REQUIRED_BALANCE_WEI` to counter-balance the decrease that occurred during the [overcommittment fraudproof process](#fraud-proofs-for-overcommitted-balances).
-
-3. If the amount withdrawn is less than `REQUIRED_BALANCE_GWEI` and the validator was *not* previously proven to be "overcommitted", then the full withdrawal amount is held for processing through EigenLayer's normal withdrawal path, and any excess 'beaconChainETH' shares in EigenLayer are immediately removed, somewhat similar to the process outlined in [fraud proofs for overcommitted balances].
-
-4. If the amount withdrawn is less than `REQUIRED_BALANCE_GWEI` and the validator *was* previously proven to be "overcommitted", then the full withdrawal amount is held for processing through EigenLayer's normal withdrawal path, and the podOwner is credited with enough beaconChainETH shares in EigenLayer to complete the normal withdrawal process; this last step is necessary since the validator's virtual beaconChainETH shares were previously removed from EigenLayer as part of the overcommittment fraudproof process.
-
-
-![Beacon Chain Withdrawal Proofs drawio](./images/Withdrawal_Proof_Diagram.png)
-
-
-## Merkle Proof Specifics
-
-### verifyValidatorFields
-This function is used to verify any of the fields, such as withdrawal credentials or slashed status, in the `Validator` container of the Beacon State. The user provides the validatorFields and the index of the validator they're proving for, and the function verifies this against the Beacon State Root.
-
-### verifyWithdrawalProofs
-This function verifies several proofs related to a withdrawal:
-1. It verifies the slot of the withdrawal
-2. It verifies the block number of the withdrawal
-3. It verifies that the withdrawal fields provided are correct.
-
-### verifyValidatorBalance
-The `Validator` container in the Beacon State only contains the effective balance of the validator. The effective balance is used to determine the size of a reward or penalty a validator receives (refer [here](https://kb.beaconcha.in/glossary#current-balance-and-effective-balance) for more information). The actual balance of the validator is stored in a separate array of `Balance` containers. Thus we require a separate proof to verify the validator's actual balance, which is verified in `verifyValidatorBalance`.
-
diff --git a/docs/Guaranteed-stake-updates.md b/docs/Guaranteed-stake-updates.md
deleted file mode 100644
index d4c6f2e6b4..0000000000
--- a/docs/Guaranteed-stake-updates.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# Design: Withdrawals From EigenLayer -- Guaranteed Stake Updates on Withdrawal
-Withdrawals are one of the critical flows in the EigenLayer system. Guaranteed stake updates ensure that all middlewares that an operator has opted-into (i.e. allowed to slash them) are notified at the appropriate time regarding any withdrawals initiated by an operator. To put it simply, an operator can "queue" a withdrawal at any point in time. In order to complete the withdrawal, the operator must first serve all existing obligations related to keeping their stake slashable. The contract `Slasher.sol` keeps track of a historic record of each operator's `latestServeUntil` time at various blocks, which is the timestamp after which their stake will have served its obligations which were created at or before the block in question. To complete a withdrawal, an operator (or a staker delegated to them) can point to a relevant point in the record which proves that the funds they are withdrawing are no longer "at stake" on any middleware tasks.
-EigenLayer uses a 'push' model for it's own core contracts -- when a staker queues a withdrawal from EigenLayer (or deposits new funds into EigenLayer), their withdrawn shares are immediately decremented, both in the StrategyManager itself and in the DelegationManager contract. Middlewares, however, must 'pull' this data. Their worldview is stale until a call is made that triggers a 'stake update', updating the middleware's view on how much the operator has staked. The middleware then informs EigenLayer (either immediately or eventually) that the stake update has occurred.
-
-## Storage Model
-
-Below, a whitelisted contract refers to a contract that is a part of a middleware that is allowed to freeze the opted-in operators.
-
-For each operator, the Slasher contract stores:
-
-1. A `mapping(address => mapping(address => MiddlewareDetails))`, from operator address to contract whitelisted by the operator to slash them, to [details](../src/contracts/contracts/interfaces/ISlasher.sol) about that contract formatted as
-```solidity
- struct MiddlewareDetails {
- // the UTC timestamp before which the contract is allowed to slash the user
- uint32 contractCanSlashOperatorUntil;
- // the block at which the middleware's view of the operator's stake was most recently updated
- uint32 latestUpdateBlock;
- }
-```
-2. A `mapping(address => LinkedList
) operatorToWhitelistedContractsByUpdate`, from operator address to a [linked list](../src/contracts/libraries/StructuredLinkedList.sol) of addresses of all whitelisted contracts, ordered by when their stakes were last updated by each middleware, from 'stalest'/earliest (at the 'HEAD' of the list) to most recent (at the 'TAIL' of the list)
-3. A `mapping(address => MiddlewareTimes[]) middlewareTimes` from operators to a historic list of
-```solidity
- struct MiddlewareTimes {
- // The update block for the middleware whose most recent update was earliest, i.e. the 'stalest' update out of all middlewares the operator is serving
- uint32 stalestUpdateBlock;
- // The latest 'serve until' time from all of the middleware that the operator is serving
- uint32 latestServeUntil;
- }
-```
-
-The reason we store an array of updates as opposed to one `MiddlewareTimes` struct with the most up-to-date values is that this would require pushing updates carefully to not disrupt existing withdrawals. This way, operators can select the `MiddlewareTimes` entry that is appropriate for their withdrawal. Thus, the operator provides an entry from their `operatorMiddlewareTimes` based on which a withdrawal can be completed. The withdrawability is checked by `slasher.canWithdraw()`, which checks that for the queued withdrawal in question, `withdrawalStartBlock` is less than the provided `operatorMiddlewareTimes` entry's 'stalestUpdateBlock', i.e. the specified stake update occurred *strictly after* the withdrawal was queued. It also checks that the current block.timestamp is greater than the `operatorMiddlewareTimes` entry's 'latestServeUntil' time, i.e. that the current time is *strictly after* the end of all obligations that the operator had, at the time of the specified stake update. If these criteria are both met, then the withdrawal can be completed.
-
-Note:
-`remove`, `nodeExists`,`getHead`, `getNextNode`, and `pushBack` are all constant time operations on linked lists. This is gained at the sacrifice of getting any elements by specifying their *indices* in the list. Searching the linked list for the correct entry is linear-time with respect to the length of the list; this should only ever happen in a "worst-case" scenario, after the provided index input is determined to be incorrect.
-
-## An Instructive Example
-
-Let us say an operator has opted into serving a middleware, `Middleware A`. As a result of the operator's actions, `Middleware A` calls `recordFirstStakeUpdate`, adding `Middleware A` to their linked list of middlewares, recording the `block.number` as the `updateBlock` and the middleware's specified `serveUntil` time as the `latestServeUntil` in a `MiddlewareTimes` struct that gets pushed to `operatorMiddlewareTimes`. At later times, the operator registers with a second and third middleware, `Middleware B` and `Middleware C`, respectively. At this point, the current state is as follows:
-
-![Three Middlewares Timeline](images/three_middlewares.png?raw=true "Three Middlewares Timeline")
-
-Based on this, the *current* latest serveUntil time is `serveUntil_B`, and the 'stalest' stake update from a middleware occurred at `updateBlock_A`. So the most recent entry in the `operatorMiddlewareTimes` array for the operator will have `serveUntil = serveUntil_B` and `stalestUpdateBlock = updateBlock_A`.
-
-In the meantime, let us say that the operator had also queued a withdrawal between opting-in to serve `Middleware A` and opting-in to serve `Middleware B`:
-
-![Three Middlewares Timeline With Queued Withdrawal](images/three_middlewares_withdrawal_queued.png?raw=true "Three Middlewares Timeline With Queued Withdrawal")
-
-Now that a withdrawal has been queued, the operator must wait till their obligations have been met before they can withdraw their stake. At this point, in our example, the `operatorMiddlewareTimes` array looks like this:
-
-```solidity
-{
- {
- stalestUpdateBlock: updateBlock_A
- latestServeUntil: serveUntil_A
- },
- {
- stalestUpdateBlock: updateBlock_A
- latestServeUntil: serveUntil_B
- },
- {
- stalestUpdateBlock: updateBlock_A
- latestServeUntil: serveUntil_B
- }
-}
-```
- In order to complete a withdrawal in this example, the operator would have to record a stake update in `Middleware A`, signalling readiness for withdrawal. Assuming this update was performed at roughly the time that the operator signed up to serve `Middleware B`, the state would now look like this:
-
-![Updated Three Middlewares Timeline With Queued Withdrawal](images/withdrawal.png?raw=true "Updated Three Middlewares Timeline With Queued Withdrawal")
-
-By recording a stake update in `Middleware A`, a new entry would be pushed to the operator's `operatorMiddlewareTimes` array, with `serveUntil = serveUntil_A` and `stalestUpdateBlock = updateBlock_B`. The queued withdrawal will then become completable after the current value of `serveUntil_A`, by referencing this entry in the array.
-
-## Deep Dive, aka "The Function-by-Function Explanation"
-
-### Internal Functions
-
-#### `_recordUpdateAndAddToMiddlewareTimes`
-```solidity
- function _recordUpdateAndAddToMiddlewareTimes(address operator, uint32 updateBlock, uint32 serveUntil) internal {
-```
-
-This function is called each time a middleware posts a stake update, through a call to `recordFirstStakeUpdate`, `recordStakeUpdate`, or `recordLastStakeUpdateAndRevokeSlashingAbility`. It records that the middleware has had a stake update and pushes a new entry to the operator's list of 'MiddlewareTimes', i.e. `operatorToMiddlewareTimes[operator]`, if *either* the `operator`'s stalestUpdateBlock' has decreased, *or* their latestServeUntil' has increased. An entry is also pushed in the special case of this being the first update of the first middleware that the operator has opted-in to serving.
-
-### External Functions
-
-#### `recordFirstStakeUpdate`
-```solidity
- function recordFirstStakeUpdate(address operator, uint32 serveUntil) external onlyCanSlash(operator) {
-
-```
-
-This function is called by a whitelisted slashing contract during registration of a new operator. The middleware posts an initial update, passing in the time until which the `operator`'s stake is bonded -- `serveUntil`. The middleware is pushed to the end ('TAIL') of the linked list since in `operatorToWhitelistedContractsByUpdate[operator]`, since the new middleware must have been updated the most recently, i.e. at the present moment.
-
-
-#### `recordStakeUpdate`
-```solidity
-recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntil, uint256 insertAfter)
-```
-
-This function is called by a whitelisted slashing contract, passing in the time until which the operator's stake is bonded -- `serveUntil`, the block for which the stake update to the middleware is being recorded (which may be the current block or a past block) -- `updateBlock`, and an index specifying the element of the `operator`'s linked list that the currently updating middleware should be inserted after -- `insertAfter`. It makes a call to the internal function `_updateMiddlewareList` to actually update the linked list.
-
-#### `recordLastStakeUpdateAndRevokeSlashingAbility`
-```solidity
-function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntil) external onlyCanSlash(operator) {
-```
-
-This function is called by a whitelisted slashing contract on deregistration of an operator, passing in the time until which the operator's stake is bonded -- `serveUntil`. It assumes that the update is posted for the *current* block, rather than a past block, in contrast to `recordStakeUpdate`.
-
-
-### View / "Helper" Functions
-
-#### `canWithdraw`
-```solidity
-canWithdraw(address operator, uint32 withdrawalStartBlock, uint256 middlewareTimesIndex) external returns (bool) {
-```
-
-The biggest thing guaranteed stake updates do is to make sure that withdrawals only happen once the stake being withdrawn is no longer slashable in a non-optimistic way. This is done by calling the `canWithdraw` function on the Slasher contract, which returns 'true' if the `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `operatorToMiddlewareTimes[operator]`). The specified struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/Middleware-registration-operator-flow.md b/docs/Middleware-registration-operator-flow.md
deleted file mode 100644
index 4faf78bf13..0000000000
--- a/docs/Middleware-registration-operator-flow.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Middleware Registration Operator Flow
-
-EigenLayer is a three-sided platform, bringing together middlewares/services, operators, and stakers. Middlewares have an attached risk-reward profile, based on the risk of getting slashed while running their service, and the rewards that they offer operators taking on those risks. Operators have full control over the middlewares that they decide to register with and run, and have an aggregated risk-reward profile of their own, based on these choices and their personal reputation. All of a users' assets that are deposited into EigenLayer are delegated to a single operator.
-
-Operators must thus make an informed decision as to which middlewares to run. This document is meant to give a high-level overview of the registration process that they must then follow in order to register with the middlewares that they have decided to operate.
-
-## Registration Process
-
-Any operator must go through the following sequence of steps:
-- [Register as an operator](./EigenLayer-delegation-flow.md#operator-registration) in EigenLayer
-- Get stakers to [delegate to them](./EigenLayer-delegation-flow.md#staker-delegation)
-- For each middleware:
- - Opt into [slashing](./EigenLayer-tech-spec.md#slasher) by the middleware's [ServiceManager](../src/contracts/interfaces/IServiceManager.sol) contract
- - Make sure to respect any other preconditions set by the middleware — for [EigenDA](https://docs.eigenda.xyz/), these are registering a BLS key in the [BLSPublicKeyCompendium](](../src/contracts/interfaces/IBLSPublicKeyCompendium.sol) and having sufficient (delegated) stake in EigenLayer to meet the minimum stake requirements set by EigenDA
- - Call a function on one of the middleware's contracts to complete the registration — for EigenDA, this is registering on its [BLSRegistry](../src/contracts/interfaces/IBLSRegistry.sol)
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000000..5854dbcf97
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,173 @@
+[middleware-repo]: https://github.com/Layr-Labs/eigenlayer-middleware/
+
+## EigenLayer M2 Docs
+
+**EigenLayer M2** extends the functionality of EigenLayer M1 (which is live on mainnet). M2 is currently on the Goerli testnet, and will eventually be released on mainnet.
+
+This repo contains the EigenLayer core contracts, which enable restaking of liquid staking tokens (LSTs) and beacon chain ETH to secure new services, called AVSs (actively validated services). For more info on AVSs, check out the EigenLayer middleware contracts [here][middleware-repo].
+
+This document provides an overview of system components, contracts, and user roles. Further documentation on the major system contracts can be found in [/core](./core/).
+
+#### Contents
+
+* [System Components](#system-components)
+ * [`EigenPodManager`](#eigenpodmanager)
+ * [`StrategyManager`](#strategymanager)
+ * [`DelegationManager`](#delegationmanager)
+ * [`AVSDirectory`](#avsdirectory)
+ * [`Slasher`](#slasher)
+* [Roles and Actors](#roles-and-actors)
+* [Common User Flows](#common-user-flows)
+ * [Depositing Into EigenLayer](#depositing-into-eigenlayer)
+ * [Delegating to an Operator](#delegating-to-an-operator)
+ * [Undelegating or Queueing a Withdrawal](#undelegating-or-queueing-a-withdrawal)
+ * [Completing a Withdrawal as Shares](#completing-a-withdrawal-as-shares)
+ * [Completing a Withdrawal as Tokens](#completing-a-withdrawal-as-tokens)
+ * [Withdrawal Processing: Validator Exits](#withdrawal-processing-validator-exits)
+ * [Withdrawal Processing: Partial Beacon Chain Withdrawals](#withdrawal-processing-partial-beacon-chain-withdrawals)
+
+### System Components
+
+#### EigenPodManager
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`EigenPodManager.sol`](../src/contracts/pods/EigenPodManager.sol) | Singleton | Transparent proxy |
+| [`EigenPod.sol`](../src/contracts/pods/EigenPod.sol) | Instanced, deployed per-user | Beacon proxy |
+| [`DelayedWithdrawalRouter.sol`](../src/contracts/pods/DelayedWithdrawalRouter.sol) | Singleton | Transparent proxy |
+| [`succinctlabs/EigenLayerBeaconOracle.sol`](https://github.com/succinctlabs/telepathy-contracts/blob/main/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol) | Singleton | UUPS proxy | [`0x40B1...9f2c`](https://goerli.etherscan.io/address/0x40B10ddD29a2cfF33DBC420AE5bbDa0649049f2c) |
+
+These contracts work together to enable native ETH restaking:
+* Users deploy `EigenPods` via the `EigenPodManager`, which contain beacon chain state proof logic used to verify a validator's withdrawal credentials, balance, and exit. An `EigenPod's` main role is to serve as the withdrawal address for one or more of a user's validators.
+* The `EigenPodManager` handles `EigenPod` creation and accounting+interactions between users with restaked native ETH and the `DelegationManager`.
+* The `DelayedWithdrawalRouter` imposes a 7-day delay on completing partial beacon chain withdrawals from an `EigenPod`. This is primarily to add a stopgap against a hack being able to instantly withdraw funds (note that all withdrawals from EigenLayer -- other than partial withdrawals earned by validators -- are initiated via the `DelegationManager`).
+* The `EigenLayerBeaconOracle` provides beacon chain block roots for use in various proofs. The oracle is supplied by Succinct's Telepathy protocol ([docs link](https://docs.telepathy.xyz/)).
+
+See full documentation in [`/core/EigenPodManager.md`](./core/EigenPodManager.md).
+
+#### StrategyManager
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`StrategyManager.sol`](../src/contracts/core/StrategyManager.sol) | Singleton | Transparent proxy |
+| [`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 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).
+
+#### DelegationManager
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`DelegationManager.sol`](../src/contracts/core/DelegationManager.sol) | Singleton | Transparent proxy |
+
+The `DelegationManager` sits between the `EigenPodManager` and `StrategyManager` to manage delegation and undelegation of Stakers to Operators. Its primary features are to allow Operators to register as Operators (`registerAsOperator`), to keep track of shares being delegated to Operators across different strategies, and to manage withdrawals on behalf of 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 |
+| -------- | -------- | -------- |
+| [`Slasher.sol`](../src/contracts/core/Slasher.sol) | - | - |
+
+
+🚧 The Slasher contract is under active development and its interface expected to change. We recommend writing slashing logic without integrating with the Slasher at this point in time. Although the Slasher is deployed, it will remain completely paused/unusable during M2. No contracts interact with it, and its design is not finalized. 🚧
+
+
+---
+
+#### Roles and Actors
+
+To see an example of the user flows described in this section, check out our integration tests: [/src/test/integration](../src/test/integration/).
+
+##### Staker
+
+A Staker is any party who has assets deposited (or "restaked") into EigenLayer. Currently, these assets can be:
+* Native beacon chain ETH (via the EigenPodManager)
+* Liquid staking tokens (via the StrategyManager): cbETH, rETH, stETH, ankrETH, OETH, osETH, swETH, wBETH
+
+Stakers can restake any combination of these: a Staker may hold ALL of these assets, or only one of them.
+
+*Flows:*
+* Stakers **deposit** assets into EigenLayer via either the StrategyManager (for LSTs) or EigenPodManager (for beacon chain ETH)
+* Stakers **withdraw** assets via the DelegationManager, *no matter what assets they're withdrawing*
+* Stakers **delegate** to an Operator via the DelegationManager
+
+Unimplemented as of M2:
+* Stakers earn yield by delegating to an Operator as the Operator provides services to an AVS
+* Stakers are at risk of being slashed if the Operator misbehaves
+
+##### Operator
+
+An Operator is a user who helps run the software built on top of EigenLayer (AVSs). Operators register in EigenLayer and allow Stakers to delegate to them, then opt in to provide various services built on top of EigenLayer. Operators may themselves be Stakers; these are not mutually exclusive.
+
+*Flows:*
+* User can **register** as an Operator via the DelegationManager
+* Operators can **deposit** and **withdraw** assets just like Stakers can
+* Operators can opt in to providing services for an AVS using that AVS's middleware contracts. See the [EigenLayer middleware][middleware-repo] repo for more details.
+
+*Unimplemented as of M2:*
+* Operators earn fees as part of the services they provide
+* Operators may be slashed by the services they register with (if they misbehave)
+
+---
+
+#### Common User Flows
+
+##### Depositing Into EigenLayer
+
+Depositing into EigenLayer varies depending on whether the Staker is depositing Native ETH or LSTs:
+
+![.](./images/Staker%20Flow%20Diagrams/Depositing.png)
+
+##### Delegating to an Operator
+
+![.](./images/Staker%20Flow%20Diagrams/Delegating.png)
+
+##### Undelegating or Queueing a Withdrawal
+
+Undelegating from an Operator automatically queues a withdrawal that needs to go through the `DelegationManager's` withdrawal delay. Stakers that want to withdraw can choose to `undelegate`, or can simply call `queueWithdrawals` directly.
+
+![.](./images/Staker%20Flow%20Diagrams/Queue%20Withdrawal.png)
+
+##### Completing a Withdrawal as Shares
+
+This flow is mostly useful if a Staker wants to change which Operator they are delegated to. The Staker first needs to undelegate (see above). At this point, they can delegate to a different Operator. However, the new Operator will only be awarded shares once the Staker completes their queued withdrawal "as shares":
+
+![.](./images/Staker%20Flow%20Diagrams/Complete%20Withdrawal%20as%20Shares.png)
+
+##### Completing a Withdrawal as Tokens
+
+Completing a queued withdrawal as tokens is roughly the same for both native ETH and LSTs.
+
+However, note that *before* a withdrawal can be completed, native ETH stakers will need to perform additional steps, detailed in the "Withdrawal Processing" diagrams below.
+
+![.](./images/Staker%20Flow%20Diagrams/Complete%20Withdrawal%20as%20Tokens.png)
+
+##### Withdrawal Processing: Validator Exits
+
+If a Staker wants to fully withdraw from the beacon chain, they need to perform these additional steps before their withdrawal is completable:
+
+![.](./images/Staker%20Flow%20Diagrams/Validator%20Exits.png)
+
+##### Withdrawal Processing: Partial Beacon Chain Withdrawals
+
+If a Staker wants to withdraw consensus rewards from the beacon chain, they do NOT go through the `DelegationManager`. This is the only withdrawal type that is not initiated in the `DelegationManager`:
+
+![.](./images/Staker%20Flow%20Diagrams/Partial%20Withdrawals.png)
\ No newline at end of file
diff --git a/docs/core/AVSDirectory.md b/docs/core/AVSDirectory.md
new file mode 100644
index 0000000000..c135d26530
--- /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
new file mode 100644
index 0000000000..28c18547f5
--- /dev/null
+++ b/docs/core/DelegationManager.md
@@ -0,0 +1,437 @@
+## DelegationManager
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`DelegationManager.sol`](../../src/contracts/core/DelegationManager.sol) | Singleton | Transparent proxy |
+
+The primary functions of the `DelegationManager` are (i) to allow Stakers to delegate to Operators, (ii) allow Stakers to be undelegated from Operators, and (iii) handle withdrawals and withdrawal processing for shares in both the `StrategyManager` and `EigenPodManager`.
+
+Whereas the `EigenPodManager` and `StrategyManager` perform accounting for individual Stakers according to their native ETH or LST holdings respectively, the `DelegationManager` sits between these two contracts and tracks these accounting changes according to the Operators each Staker has delegated to.
+
+This means that each time a Staker's balance changes in either the `EigenPodManager` or `StrategyManager`, the `DelegationManager` is called to record this update to the Staker's delegated Operator (if they have one). For example, if a Staker is delegated to an Operator and deposits into a strategy, the `StrategyManager` will call the `DelegationManager` to update the Operator's delegated shares for that strategy.
+
+Additionally, whether a Staker is delegated to an Operator or not, the `DelegationManager` is how a Staker queues (and later completes) a withdrawal.
+
+#### High-level Concepts
+
+This document organizes methods according to the following themes (click each to be taken to the relevant section):
+* [Becoming an Operator](#becoming-an-operator)
+* [Delegating to an Operator](#delegating-to-an-operator)
+* [Undelegating and Withdrawing](#undelegating-and-withdrawing)
+* [Accounting](#accounting)
+* [System Configuration](#system-configuration)
+
+#### Important state variables
+
+* `mapping(address => address) public delegatedTo`: Staker => Operator.
+ * If a Staker is not delegated to anyone, `delegatedTo` is unset.
+ * Operators are delegated to themselves - `delegatedTo[operator] == operator`
+* `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 minWithdrawalDelayBlocks`:
+ * As of M2, this is 50400 (roughly 1 week)
+ * 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.
+
+#### Helpful definitions
+
+* `isDelegated(address staker) -> (bool)`
+ * True if `delegatedTo[staker] != address(0)`
+* `isOperator(address operator) -> (bool)`
+ * True if `_operatorDetails[operator].earningsReceiver != address(0)`
+
+---
+
+### Becoming an Operator
+
+Operators interact with the following functions to become an Operator:
+
+* [`DelegationManager.registerAsOperator`](#registerasoperator)
+* [`DelegationManager.modifyOperatorDetails`](#modifyoperatordetails)
+* [`DelegationManager.updateOperatorMetadataURI`](#updateoperatormetadatauri)
+
+#### `registerAsOperator`
+
+```solidity
+function registerAsOperator(OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI) external
+```
+
+Registers the caller as an Operator in EigenLayer. The new Operator provides the `OperatorDetails`, a struct containing:
+* `address earningsReceiver`: the address that will receive earnings as the Operator provides services to AVSs *(currently unused)*
+* `address delegationApprover`: if set, this address must sign and approve new delegation from Stakers to this Operator *(optional)*
+* `uint32 stakerOptOutWindowBlocks`: the minimum delay (in blocks) between beginning and completing registration for an AVS. *(currently unused)*
+
+`registerAsOperator` cements the Operator's `OperatorDetails`, and self-delegates the Operator to themselves - permanently marking the caller as an Operator. They cannot "deregister" as an Operator - however, they can exit the system by withdrawing their funds via `queueWithdrawals`.
+
+*Effects*:
+* Sets `OperatorDetails` for the Operator in question
+* Delegates the Operator to itself
+* If the Operator has shares in the `EigenPodManager`, the `DelegationManager` adds these shares to the Operator's shares for the beacon chain ETH strategy.
+* For each of the three strategies in the `StrategyManager`, if the Operator holds shares in that strategy they are added to the Operator's shares under the corresponding strategy.
+
+*Requirements*:
+* Caller MUST NOT already be an Operator
+* Caller MUST NOT already be delegated to an Operator
+* `earningsReceiver != address(0)`
+* `stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS`: (~180 days)
+* Pause status MUST NOT be set: `PAUSED_NEW_DELEGATION`
+
+#### `modifyOperatorDetails`
+
+```solidity
+function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external
+```
+
+Allows an Operator to update their stored `OperatorDetails`.
+
+*Requirements*:
+* Caller MUST already be an Operator
+* `new earningsReceiver != address(0)`
+* `new stakerOptOutWindowBlocks >= old stakerOptOutWindowBlocks`
+* `new stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS`
+
+#### `updateOperatorMetadataURI`
+
+```solidity
+function updateOperatorMetadataURI(string calldata metadataURI) external
+```
+
+Allows an Operator to emit an `OperatorMetadataURIUpdated` event. No other state changes occur.
+
+*Requirements*:
+* Caller MUST already be an Operator
+
+---
+
+### Delegating to an Operator
+
+Stakers interact with the following functions to delegate their shares to an Operator:
+
+* [`DelegationManager.delegateTo`](#delegateto)
+* [`DelegationManager.delegateToBySignature`](#delegatetobysignature)
+
+#### `delegateTo`
+
+```solidity
+function delegateTo(
+ address operator,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+)
+ external
+```
+
+Allows the caller (a Staker) to delegate their shares to an Operator. Delegation is all-or-nothing: when a Staker delegates to an Operator, they delegate ALL their shares. For each strategy the Staker has shares in, the `DelegationManager` will update the Operator's corresponding delegated share amounts.
+
+*Effects*:
+* Records the Staker as being delegated to the Operator
+* If the Staker has shares in the `EigenPodManager`, the `DelegationManager` adds these shares to the Operator's shares for the beacon chain ETH strategy.
+* For each of the three strategies in the `StrategyManager`, if the Staker holds shares in that strategy they are added to the Operator's shares under the corresponding strategy.
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_NEW_DELEGATION`
+* The caller MUST NOT already be delegated to an Operator
+* The `operator` MUST already be an Operator
+* If the `operator` has a `delegationApprover`, the caller MUST provide a valid `approverSignatureAndExpiry` and `approverSalt`
+
+#### `delegateToBySignature`
+
+```solidity
+function delegateToBySignature(
+ address staker,
+ address operator,
+ SignatureWithExpiry memory stakerSignatureAndExpiry,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+)
+ external
+```
+
+Allows a Staker to delegate to an Operator by way of signature. This function can be called by three different parties:
+* If the Operator calls this method, they need to submit only the `stakerSignatureAndExpiry`
+* If the Operator's `delegationApprover` calls this method, they need to submit only the `stakerSignatureAndExpiry`
+* If the anyone else calls this method, they need to submit both the `stakerSignatureAndExpiry` AND `approverSignatureAndExpiry`
+
+*Effects*: See `delegateTo` above.
+
+*Requirements*: See `delegateTo` above. Additionally:
+* If caller is either the Operator's `delegationApprover` or the Operator, the `approverSignatureAndExpiry` and `approverSalt` can be empty
+* `stakerSignatureAndExpiry` MUST be a valid, unexpired signature over the correct hash and nonce
+
+---
+
+### Undelegating and Withdrawing
+
+These methods can be called by both Stakers AND Operators, and are used to (i) undelegate a Staker from an Operator, (ii) queue a withdrawal of a Staker/Operator's shares, or (iii) complete a queued withdrawal:
+
+* [`DelegationManager.undelegate`](#undelegate)
+* [`DelegationManager.queueWithdrawals`](#queuewithdrawals)
+* [`DelegationManager.completeQueuedWithdrawal`](#completequeuedwithdrawal)
+* [`DelegationManager.completeQueuedWithdrawals`](#completequeuedwithdrawals)
+
+#### `undelegate`
+
+```solidity
+function undelegate(
+ address staker
+)
+ external
+ onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE)
+ returns (bytes32[] memory withdrawalRoots)
+```
+
+`undelegate` can be called by a Staker to undelegate themselves, or by a Staker's delegated Operator (or that Operator's `delegationApprover`). Undelegation (i) queues withdrawals on behalf of the Staker for all their delegated shares, and (ii) decreases the Operator's delegated shares according to the amounts and strategies being withdrawn.
+
+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 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.
+
+*Effects*:
+* Any shares held by the Staker in the `EigenPodManager` and `StrategyManager` are removed from the Operator's delegated shares.
+* The Staker is undelegated from the Operator
+* If the Staker has no delegatable shares, there is no withdrawal queued or further effects
+* For each strategy being withdrawn, a `Withdrawal` is queued for the Staker:
+ * The Staker's withdrawal nonce is increased by 1 for each `Withdrawal`
+ * The hash of each `Withdrawal` is marked as "pending"
+* See [`EigenPodManager.removeShares`](./EigenPodManager.md#eigenpodmanagerremoveshares)
+* See [`StrategyManager.removeShares`](./StrategyManager.md#removeshares)
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_ENTER_WITHDRAWAL_QUEUE`
+* Staker MUST exist and be delegated to someone
+* Staker MUST NOT be an Operator
+* `staker` parameter MUST NOT be zero
+* Caller must be either the Staker, their Operator, or their Operator's `delegationApprover`
+* See [`EigenPodManager.removeShares`](./EigenPodManager.md#eigenpodmanagerremoveshares)
+* See [`StrategyManager.removeShares`](./StrategyManager.md#removeshares)
+
+#### `queueWithdrawals`
+
+```solidity
+function queueWithdrawals(
+ QueuedWithdrawalParams[] calldata queuedWithdrawalParams
+)
+ external
+ onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE)
+ returns (bytes32[] memory)
+```
+
+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).
+
+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 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 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)
+ * See [`StrategyManager.removeShares`](./StrategyManager.md#removeshares)
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_ENTER_WITHDRAWAL_QUEUE`
+* For each withdrawal:
+ * `strategies.length` MUST equal `shares.length`
+ * `strategies.length` MUST NOT be equal to 0
+ * The `withdrawer` MUST equal `msg.sender`
+ * See [`EigenPodManager.removeShares`](./EigenPodManager.md#eigenpodmanagerremoveshares)
+ * See [`StrategyManager.removeShares`](./StrategyManager.md#removeshares)
+
+#### `completeQueuedWithdrawal`
+
+```solidity
+function completeQueuedWithdrawal(
+ Withdrawal calldata withdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+)
+ external
+ onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
+ nonReentrant
+```
+
+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:
+ * The shares are converted to their underlying tokens via either the `EigenPodManager` or `StrategyManager` and sent to the `withdrawer`.
+* If the `withdrawer` chooses to receive shares (and the strategy belongs to the `StrategyManager`):
+ * The shares are awarded to the `withdrawer` via the `StrategyManager`
+ * If the `withdrawer` is delegated to an Operator, that Operator's delegated shares are increased by the added shares (according to the strategy being added to).
+
+`Withdrawals` concerning `EigenPodManager` shares have some additional nuance depending on whether a withdrawal is specified to be received as tokens vs shares (read more about "why" in [`EigenPodManager.md`](./EigenPodManager.md)):
+* `EigenPodManager` withdrawals received as shares:
+ * Shares ALWAYS go back to the originator of the withdrawal (rather than the `withdrawer` address).
+ * Shares are also delegated to the originator's Operator, rather than the `withdrawer's` Operator.
+ * Shares received by the originator may be lower than the shares originally withdrawn if the originator has debt.
+* `EigenPodManager` withdrawals received as tokens:
+ * Before the withdrawal can be completed, the originator needs to prove that a withdrawal occurred on the beacon chain (see [`EigenPod.verifyAndProcessWithdrawals`](./EigenPodManager.md#eigenpodverifyandprocesswithdrawals)).
+
+*Effects*:
+* The hash of the `Withdrawal` is removed from the pending withdrawals
+* If `receiveAsTokens`:
+ * See [`StrategyManager.withdrawSharesAsTokens`](./StrategyManager.md#withdrawsharesastokens)
+ * See [`EigenPodManager.withdrawSharesAsTokens`](./EigenPodManager.md#eigenpodmanagerwithdrawsharesastokens)
+* If `!receiveAsTokens`:
+ * For `StrategyManager` strategies:
+ * Shares are awarded to the `withdrawer` and delegated to the `withdrawer's` Operator
+ * See [`StrategyManager.addShares`](./StrategyManager.md#addshares)
+ * For the native beacon chain ETH strategy (`EigenPodManager`):
+ * Shares are awarded to `withdrawal.staker`, and delegated to the Staker's Operator
+ * See [`EigenPodManager.addShares`](./EigenPodManager.md#eigenpodmanageraddshares)
+
+*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 `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`.
+ * See [`StrategyManager.withdrawSharesAsTokens`](./StrategyManager.md#withdrawsharesastokens)
+ * See [`EigenPodManager.withdrawSharesAsTokens`](./EigenPodManager.md#eigenpodmanagerwithdrawsharesastokens)
+* If `!receiveAsTokens`:
+ * See [`StrategyManager.addShares`](./StrategyManager.md#addshares)
+ * See [`EigenPodManager.addShares`](./EigenPodManager.md#eigenpodmanageraddshares)
+
+*As of M2*:
+* The `middlewareTimesIndex` parameter has to do with the Slasher, which currently does nothing. As of M2, this parameter has no bearing on anything and can be ignored.
+
+#### `completeQueuedWithdrawals`
+
+```solidity
+function completeQueuedWithdrawals(
+ Withdrawal[] calldata withdrawals,
+ IERC20[][] calldata tokens,
+ uint256[] calldata middlewareTimesIndexes,
+ bool[] calldata receiveAsTokens
+)
+ external
+ onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
+ nonReentrant
+```
+
+This method is the plural version of [`completeQueuedWithdrawal`](#completequeuedwithdrawal).
+
+---
+
+### Accounting
+
+These methods are called by the `StrategyManager` and `EigenPodManager` to update delegated share accounting when a Staker's balance changes (e.g. due to a deposit):
+
+* [`DelegationManager.increaseDelegatedShares`](#increasedelegatedshares)
+* [`DelegationManager.decreaseDelegatedShares`](#decreasedelegatedshares)
+
+#### `increaseDelegatedShares`
+
+```solidity
+function increaseDelegatedShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+)
+ external
+ onlyStrategyManagerOrEigenPodManager
+```
+
+Called by either the `StrategyManager` or `EigenPodManager` when a Staker's shares for one or more strategies increase. This method is called to ensure that if the Staker is delegated to an Operator, that Operator's share count reflects the increase.
+
+*Entry Points*:
+* `StrategyManager.depositIntoStrategy`
+* `StrategyManager.depositIntoStrategyWithSignature`
+* `EigenPod.verifyWithdrawalCredentials`
+* `EigenPod.verifyBalanceUpdates`
+* `EigenPod.verifyAndProcessWithdrawals`
+
+*Effects*: If the Staker in question is delegated to an Operator, the Operator's shares for the `strategy` are increased.
+* This method is a no-op if the Staker is not delegated to an Operator.
+
+*Requirements*:
+* Caller MUST be either the `StrategyManager` or `EigenPodManager`
+
+#### `decreaseDelegatedShares`
+
+```solidity
+function decreaseDelegatedShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+)
+ external
+ onlyStrategyManagerOrEigenPodManager
+```
+
+Called by the `EigenPodManager` when a Staker's shares decrease. This method is called to ensure that if the Staker is delegated to an Operator, that Operator's share count reflects the decrease.
+
+*Entry Points*: This method may be called as a result of the following top-level function calls:
+* `EigenPod.verifyBalanceUpdates`
+* `EigenPod.verifyAndProcessWithdrawals`
+
+*Effects*: If the Staker in question is delegated to an Operator, the Operator's delegated balance for the `strategy` is decreased by `shares`
+* 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)
+
+---
+
+### 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
new file mode 100644
index 0000000000..24dabe1f5f
--- /dev/null
+++ b/docs/core/EigenPodManager.md
@@ -0,0 +1,719 @@
+## EigenPodManager
+
+
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`EigenPodManager.sol`](../../src/contracts/pods/EigenPodManager.sol) | Singleton | Transparent proxy |
+| [`EigenPod.sol`](../../src/contracts/pods/EigenPod.sol) | Instanced, deployed per-user | Beacon proxy |
+| [`DelayedWithdrawalRouter.sol`](../../src/contracts/pods/DelayedWithdrawalRouter.sol) | Singleton | Transparent proxy |
+| [`succinctlabs/EigenLayerBeaconOracle.sol`](https://github.com/succinctlabs/telepathy-contracts/blob/main/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol) | Singleton | UUPS Proxy |
+
+The `EigenPodManager` and `EigenPod` contracts allow Stakers to restake beacon chain ETH which can then be delegated to Operators via the `DelegationManager`.
+
+The `EigenPodManager` is the entry point for this process, allowing Stakers to deploy an `EigenPod` and begin restaking. While actively restaking, a Staker uses their `EigenPod` to validate various beacon chain state proofs of validator balance and withdrawal status. When exiting, a Staker uses the `DelegationManager` to undelegate or queue a withdrawal from EigenLayer.
+
+`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`: 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/)).
+
+#### High-level Concepts
+
+The functions of the `EigenPodManager` and `EigenPod` contracts are tightly linked. Rather than writing two separate docs pages, documentation for both contracts is included in this file. This document organizes methods according to the following themes (click each to be taken to the relevant section):
+* [Depositing Into EigenLayer](#depositing-into-eigenlayer)
+* [Restaking Beacon Chain ETH](#restaking-beacon-chain-eth)
+* [Withdrawal Processing](#withdrawal-processing)
+* [System Configuration](#system-configuration)
+* [Other Methods](#other-methods)
+
+#### Important State Variables
+
+* `EigenPodManager`:
+ * `mapping(address => IEigenPod) public ownerToPod`: Tracks the deployed `EigenPod` for each Staker
+ * `mapping(address => int256) public podOwnerShares`: Keeps track of the actively restaked beacon chain ETH for each Staker.
+ * In some cases, a beacon chain balance update may cause a Staker's balance to drop below zero. This is because when queueing for a withdrawal in the `DelegationManager`, the Staker's current shares are fully removed. If the Staker's beacon chain balance drops after this occurs, their `podOwnerShares` may go negative. This is a temporary change to account for the drop in balance, and is ultimately corrected when the withdrawal is finally processed.
+ * Since balances on the consensus layer are stored only in Gwei amounts, the EigenPodManager enforces the invariant that `podOwnerShares` is always a whole Gwei amount for every staker, i.e. `podOwnerShares[staker] % 1e9 == 0` always.
+* `EigenPod`:
+ * `_validatorPubkeyHashToInfo[bytes32] -> (ValidatorInfo)`: individual validators are identified within an `EigenPod` according to their public key hash. This mapping keeps track of the following for each validator:
+ * `validatorStatus`: (`INACTIVE`, `ACTIVE`, `WITHDRAWN`)
+ * `validatorIndex`: A `uint40` that is unique for each validator making a successful deposit via the deposit contract
+ * `mostRecentBalanceUpdateTimestamp`: A timestamp that represents the most recent successful proof of the validator's effective balance
+ * `restakedBalanceGwei`: set to the validator's balance.
+ * `withdrawableRestakedExecutionLayerGwei`: When a Staker proves that a validator has exited from the beacon chain, the withdrawal amount is added to this variable. When completing a withdrawal of beacon chain ETH, the withdrawal amount is subtracted from this variable. See also:
+ * [`DelegationManager`: "Undelegating and Withdrawing"](./DelegationManager.md#undelegating-and-withdrawing)
+ * [`EigenPodManager`: "Withdrawal Processing"](#withdrawal-processing)
+
+#### Important Definitions
+
+* "Pod Owner": A Staker who has deployed an `EigenPod` is a Pod Owner. The terms are used interchangeably in this document.
+ * Pod Owners can only deploy a single `EigenPod`, but can restake any number of beacon chain validators from the same `EigenPod`.
+ * Pod Owners can delegate their `EigenPodManager` shares to Operators (via `DelegationManager`).
+ * These shares correspond to the amount of provably-restaked beacon chain ETH held by the Pod Owner via their `EigenPod`.
+* `EigenPod`:
+ * `_podWithdrawalCredentials() -> (bytes memory)`:
+ * Gives `abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(EigenPod))`
+ * These are the `0x01` withdrawal credentials of the `EigenPod`, used as a validator's withdrawal credentials on the beacon chain.
+
+---
+
+### Depositing Into EigenLayer
+
+Before a Staker begins restaking beacon chain ETH, they need to deploy an `EigenPod`, stake, and start a beacon chain validator:
+* [`EigenPodManager.createPod`](#eigenpodmanagercreatepod)
+* [`EigenPodManager.stake`](#eigenpodmanagerstake)
+ * [`EigenPod.stake`](#eigenpodstake)
+
+To complete the deposit process, the Staker needs to prove that the validator's withdrawal credentials are pointed at the `EigenPod`:
+* [`EigenPod.verifyWithdrawalCredentials`](#eigenpodverifywithdrawalcredentials)
+
+#### `EigenPodManager.createPod`
+
+```solidity
+function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address)
+```
+
+Allows a Staker to deploy an `EigenPod` instance, if they have not done so already.
+
+Each Staker can only deploy a single `EigenPod` instance, but a single `EigenPod` can serve as the withdrawal credentials for any number of beacon chain validators. Each `EigenPod` is created using Create2 and the beacon proxy pattern, using the Staker's address as the Create2 salt.
+
+As part of the `EigenPod` deployment process, the Staker is made the Pod Owner, a permissioned role within the `EigenPod`.
+
+*Effects*:
+* Create2 deploys `EigenPodManager.beaconProxyBytecode`, appending the `eigenPodBeacon` address as a constructor argument. `bytes32(msg.sender)` is used as the salt.
+ * `address eigenPodBeacon` is an [OpenZeppelin v4.7.1 `UpgradableBeacon`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol), whose implementation address points to the current `EigenPod` implementation
+ * `beaconProxyBytecode` is the constructor code for an [OpenZeppelin v4.7.1 `BeaconProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol)
+* `EigenPod.initialize(msg.sender)`: initializes the pod, setting the caller as the Pod Owner and activating restaking for any validators pointed at the pod.
+* Maps the new pod to the Pod Owner (each address can only deploy a single `EigenPod`)
+
+*Requirements*:
+* Caller MUST NOT have deployed an `EigenPod` already
+* Pause status MUST NOT be set: `PAUSED_NEW_EIGENPODS`
+
+*As of M2*:
+* `EigenPods` are initialized with restaking enabled by default (`hasRestaked = true`). Pods deployed before M2 may not have this enabled, and will need to call `EigenPod.activateRestaking()`.
+
+#### `EigenPodManager.stake`
+
+```solidity
+function stake(
+ bytes calldata pubkey,
+ bytes calldata signature,
+ bytes32 depositDataRoot
+)
+ external
+ payable
+ onlyWhenNotPaused(PAUSED_NEW_EIGENPODS)
+```
+
+Allows a Staker to deposit 32 ETH into the beacon chain deposit contract, providing the credentials for the Staker's beacon chain validator. The `EigenPod.stake` method is called, which automatically calculates the correct withdrawal credentials for the pod and passes these to the deposit contract along with the 32 ETH.
+
+*Effects*:
+* Deploys and initializes an `EigenPod` on behalf of Staker, if this has not been done already
+* See [`EigenPod.stake`](#eigenpodstake)
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_NEW_EIGENPODS`
+* See [`EigenPod.stake`](#eigenpodstake)
+
+##### `EigenPod.stake`
+
+```solidity
+function stake(
+ bytes calldata pubkey,
+ bytes calldata signature,
+ bytes32 depositDataRoot
+)
+ external
+ payable
+ onlyEigenPodManager
+```
+
+Handles the call to the beacon chain deposit contract. Only called via `EigenPodManager.stake`.
+
+*Effects*:
+* Deposits 32 ETH into the beacon chain deposit contract, and provides the pod's address as the deposit's withdrawal credentials
+
+*Requirements*:
+* Caller MUST be the `EigenPodManager`
+* Call value MUST be 32 ETH
+* Deposit contract `deposit` method MUST succeed given the provided `pubkey`, `signature`, and `depositDataRoot`
+
+#### `EigenPod.verifyWithdrawalCredentials`
+
+```solidity
+function verifyWithdrawalCredentials(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ uint40[] calldata validatorIndices,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields
+)
+ external
+ onlyEigenPodOwner
+ onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
+ proofIsForValidTimestamp(oracleTimestamp)
+ hasEnabledRestaking
+```
+
+Once a Pod Owner has deposited ETH into the beacon chain deposit contract, they can call this method to "fully restake" one or more validators by proving the validator's withdrawal credentials are pointed at the `EigenPod`. This activation will mean that the ETH in those validators:
+* is awarded to the Staker/Pod Owner in `EigenPodManager.podOwnerShares`
+* is delegatable to an Operator (via the `DelegationManager`)
+
+For each successfully proven validator, that validator's status becomes `VALIDATOR_STATUS.ACTIVE`, and the sum of restakable ether across all newly-proven validators is provided to [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate), where it is added to the Pod Owner's shares. If the Pod Owner is delegated to an Operator via the `DelegationManager`, this sum is also added to the Operator's delegated shares for the beacon chain ETH strategy.
+
+For each validator the Pod Owner wants to verify, the Pod Owner must supply:
+* `validatorIndices`: their validator's `ValidatorIndex` (see [consensus specs](https://eth2book.info/capella/part3/config/types/#validatorindex))
+* `validatorFields`: the fields of the `Validator` container associated with the validator (see [consensus specs](https://eth2book.info/capella/part3/containers/dependencies/#validator))
+* `stateRootProof`: a proof that will verify `stateRootProof.beaconStateRoot` against an oracle-provided beacon block root
+* `validatorFieldsProofs`: a proof that the `Validator` container belongs to the associated validator at the given `ValidatorIndex` within `stateRootProof.beaconStateRoot`
+* `oracleTimestamp`: a timestamp used to fetch a beacon block root from `EigenPodManager.beaconChainOracle`
+
+*Beacon chain proofs used*:
+* [`verifyStateRootAgainstLatestBlockRoot`](./proofs/BeaconChainProofs.md#beaconchainproofsverifystaterootagainstlatestblockroot)
+* [`verifyValidatorFields`](./proofs/BeaconChainProofs.md#beaconchainproofsverifyvalidatorfields)
+
+*Effects*:
+* For each validator (`_validatorPubkeyHashToInfo[pubkeyHash]`) the validator's info is set for the first time:
+ * `VALIDATOR_STATUS` moves from `INACTIVE` to `ACTIVE`
+ * `validatorIndex` is recorded
+ * `mostRecentBalanceUpdateTimestamp` is set to the `oracleTimestamp` used to fetch the beacon block root
+ * `restakedBalanceGwei` is set to the validator's effective balance
+* See [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)
+
+*Requirements*:
+* Caller MUST be the Pod Owner
+* Pause status MUST NOT be set: `PAUSED_EIGENPODS_VERIFY_CREDENTIALS`
+* Pod MUST have enabled restaking
+* All input array lengths MUST be equal
+* `oracleTimestamp`:
+ * MUST be greater than the `mostRecentWithdrawalTimestamp`
+ * MUST be no more than `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` (~4.5 hrs) old
+ * MUST be queryable via `EigenPodManager.getBlockRootAtTimestamp` (fails if `stateRoot == 0`)
+* `BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot` MUST verify the provided `beaconStateRoot` against the oracle-provided `latestBlockRoot`
+* For each validator:
+ * The validator's status MUST initially be `VALIDATOR_STATUS.INACTIVE`
+ * `BeaconChainProofs.verifyValidatorFields` MUST verify the provided `validatorFields` against the `beaconStateRoot`
+ * The aforementioned proofs MUST show that the validator's withdrawal credentials are set to the `EigenPod`
+* See [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)
+
+*As of M2*:
+* Restaking is enabled by default for pods deployed after M2. See `activateRestaking` for more info.
+
+---
+
+### Restaking Beacon Chain ETH
+
+At this point, a Staker/Pod Owner has deployed their `EigenPod`, started their beacon chain validator, and proven that its withdrawal credentials are pointed to their `EigenPod`. They are now free to delegate to an Operator (if they have not already), or start up + verify additional beacon chain validators that also withdraw to the same `EigenPod`.
+
+The primary method concerning actively restaked validators is:
+* [`EigenPod.verifyBalanceUpdates`](#eigenpodverifybalanceupdates)
+
+#### `EigenPod.verifyBalanceUpdates`
+
+```solidity
+function verifyBalanceUpdates(
+ uint64 oracleTimestamp,
+ uint40[] calldata validatorIndices,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields
+)
+ external
+ onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE)
+```
+
+Anyone (not just the Pod Owner) may call this method with one or more valid balance update proofs to record beacon chain balance updates in one or more of the `EigenPod's` validators.
+
+A successful balance update proof updates the `EigenPod's` view of a validator's [effective balance](https://eth2book.info/capella/part2/incentives/balances/). If the validator's effective balance has changed, the difference is sent to `EigenPodManager.recordBeaconChainETHBalanceUpdate`, which updates the Pod Owner's shares. If the Pod Owner is delegated to an Operator, this delta is also sent to the `DelegationManager` to update the Operator's delegated beacon chain ETH shares.
+
+Note that if a validator's effective balance has decreased, this method will result in shares being removed from the Pod Owner in `EigenPodManager.recordBeaconChainETHBalanceUpdate`. This may cause the Pod Owner's balance to go negative in some cases, representing a "deficit" that must be repaid before any withdrawals can be processed. One example flow where this might occur is:
+* Pod Owner calls `DelegationManager.undelegate`, which queues a withdrawal in the `DelegationManager`. The Pod Owner's shares are set to 0 while the withdrawal is in the queue.
+* Pod Owner's beacon chain ETH balance decreases (maybe due to slashing), and someone provides a proof of this to `EigenPod.verifyBalanceUpdates`. In this case, the Pod Owner will have negative shares in the `EigenPodManager`.
+* After a delay, the Pod Owner calls `DelegationManager.completeQueuedWithdrawal`. The negative shares are then repaid out of the withdrawn assets.
+
+For the validator whose balance should be updated, the caller must supply:
+* `validatorIndex`: the validator's `ValidatorIndex` (see [consensus specs](https://eth2book.info/capella/part3/config/types/#validatorindex))
+* `stateRootProof`: a proof that will verify `stateRootProof.beaconStateRoot` against an oracle-provided beacon block root
+* `validatorFieldsProofs`: a proof that the `Validator` container belongs to the associated validator at the given `ValidatorIndex` within `stateRootProof.beaconStateRoot`
+* `validatorFields`: the fields of the `Validator` container associated with the validator (see [consensus specs](https://eth2book.info/capella/part3/containers/dependencies/#validator))
+* `oracleTimestamp`: a timestamp used to fetch a beacon block root from `EigenPodManager.beaconChainOracle`
+
+*Beacon chain proofs used*:
+* [`verifyStateRootAgainstLatestBlockRoot`](./proofs/BeaconChainProofs.md#beaconchainproofsverifystaterootagainstlatestblockroot)
+* [`verifyValidatorFields`](./proofs/BeaconChainProofs.md#beaconchainproofsverifyvalidatorfields)
+
+*Effects*:
+* Updates the validator's stored info:
+ * `restakedBalanceGwei` is updated to the newly-proven effective balance
+ * `mostRecentBalanceUpdateTimestamp` is set to the `oracleTimestamp` used to fetch the beacon block root
+* See [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE`
+* Balance updates should only be made before a validator has fully exited. If the validator has exited, any further proofs should follow the `verifyAndProcessWithdrawals` path.
+ * This is to prevent someone from providing a "balance update" on an exited validator that "proves" a balance of 0, when we want to process that update as a withdrawal instead.
+* `oracleTimestamp`:
+ * MUST be no more than `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` (~4.5 hrs) old
+ * MUST be newer than the validator's `mostRecentBalanceUpdateTimestamp`
+ * MUST be queryable via `EigenPodManager.getBlockRootAtTimestamp` (fails if `stateRoot == 0`)
+* `validatorFields[0]` MUST be a pubkey hash corresponding to a validator whose withdrawal credentials have been proven, and is not yet withdrawn (`VALIDATOR_STATUS.ACTIVE`)
+* `BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot` MUST verify the provided `beaconStateRoot` against the oracle-provided `latestBlockRoot`
+* `BeaconChainProofs.verifyValidatorFields` MUST verify the provided `validatorFields` against the `beaconStateRoot`
+* See [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)
+
+---
+
+### Withdrawal Processing
+
+The `DelegationManager` is the entry point for all undelegation and withdrawals, which must be queued for a time before being completed. When a withdrawal is initiated, the following method is used:
+* [`EigenPodManager.removeShares`](#eigenpodmanagerremoveshares)
+
+When completing a queued undelegation or withdrawal, the `DelegationManager` calls one of these two methods:
+* [`EigenPodManager.addShares`](#eigenpodmanageraddshares)
+* [`EigenPodManager.withdrawSharesAsTokens`](#eigenpodmanagerwithdrawsharesastokens)
+ * [`EigenPod.withdrawRestakedBeaconChainETH`](#eigenpodwithdrawrestakedbeaconchaineth)
+
+If a Staker wishes to fully withdraw their beacon chain ETH (via `withdrawSharesAsTokens`), they need to exit their validator and prove the withdrawal *prior to* completing the queued withdrawal. They do so using this method:
+* [`EigenPod.verifyAndProcessWithdrawals`](#eigenpodverifyandprocesswithdrawals)
+
+Some withdrawals are sent to their destination via the `DelayedWithdrawalRouter`:
+* [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal)
+* [`DelayedWithdrawalRouter.claimDelayedWithdrawals`](#delayedwithdrawalrouterclaimdelayedwithdrawals)
+
+#### `EigenPodManager.removeShares`
+
+```solidity
+function removeShares(
+ address podOwner,
+ uint256 shares
+)
+ external
+ onlyDelegationManager
+```
+
+The `DelegationManager` calls this method when a Staker queues a withdrawal (or undelegates, which also queues a withdrawal). The shares are removed while the withdrawal is in the queue, and when the queue completes, the shares will either be re-awarded or withdrawn as tokens (`addShares` and `withdrawSharesAsTokens`, respectively).
+
+The Staker's share balance is decreased by the removed `shares`.
+
+This method is not allowed to cause the `Staker's` balance to go negative. This prevents a Staker from queueing a withdrawal for more shares than they have (or more shares than they delegated to an Operator).
+
+*Entry Points*:
+* `DelegationManager.undelegate`
+* `DelegationManager.queueWithdrawals`
+
+*Effects*:
+* Removes `shares` from `podOwner's` share balance
+
+*Requirements*:
+* `podOwner` MUST NOT be zero
+* `shares` MUST NOT be negative when converted to `int256`
+* `shares` MUST NOT be greater than `podOwner's` share balance
+* `shares` MUST be a whole Gwei amount
+
+#### `EigenPodManager.addShares`
+
+```solidity
+function addShares(
+ address podOwner,
+ uint256 shares
+)
+ external
+ onlyDelegationManager
+ returns (uint256)
+```
+
+The `DelegationManager` calls this method when a queued withdrawal is completed and the withdrawer specifies that they want to receive the withdrawal as "shares" (rather than as the underlying tokens). A Pod Owner might want to do this in order to change their delegated Operator without needing to fully exit their validators.
+
+Note that typically, shares from completed withdrawals are awarded to a `withdrawer` specified when the withdrawal is initiated in `DelegationManager.queueWithdrawals`. However, because beacon chain ETH shares are linked to proofs provided to a Pod Owner's `EigenPod`, this method is used to award shares to the original Pod Owner.
+
+If the Pod Owner has a share deficit (negative shares), the deficit is repaid out of the added `shares`. If the Pod Owner's positive share count increases, this change is returned to the `DelegationManager` to be delegated to the Pod Owner's Operator (if they have one).
+
+*Entry Points*:
+* `DelegationManager.completeQueuedWithdrawal`
+* `DelegationManager.completeQueuedWithdrawals`
+
+*Effects*:
+* The `podOwner's` share balance is increased by `shares`
+
+*Requirements*:
+* `podOwner` MUST NOT be zero
+* `shares` MUST NOT be negative when converted to an `int256`
+* `shares` MUST be a whole Gwei amount
+
+#### `EigenPodManager.withdrawSharesAsTokens`
+
+```solidity
+function withdrawSharesAsTokens(
+ address podOwner,
+ address destination,
+ uint256 shares
+)
+ external
+ onlyDelegationManager
+```
+
+The `DelegationManager` calls this method when a queued withdrawal is completed and the withdrawer specifies that they want to receive the withdrawal as tokens (rather than shares). This can be used to "fully exit" some amount of beacon chain ETH and send it to a recipient (via `EigenPod.withdrawRestakedBeaconChainETH`).
+
+Note that because this method entails withdrawing and sending beacon chain ETH, two conditions must be met for this method to succeed: (i) the ETH being withdrawn should already be in the `EigenPod`, and (ii) the beacon chain withdrawals responsible for the ETH should already be proven.
+
+This means that before completing their queued withdrawal, a Pod Owner needs to prove their beacon chain withdrawals via `EigenPod.verifyAndProcessWithdrawals`.
+
+Also note that, like `addShares`, if the original Pod Owner has a share deficit (negative shares), the deficit is repaid out of the withdrawn `shares` before any native ETH is withdrawn.
+
+*Entry Points*:
+* `DelegationManager.completeQueuedWithdrawal`
+* `DelegationManager.completeQueuedWithdrawals`
+
+*Effects*:
+* If `podOwner's` share balance is negative, `shares` are added until the balance hits 0
+ * Any remaining shares are withdrawn and sent to `destination` (see [`EigenPod.withdrawRestakedBeaconChainETH`](#eigenpodwithdrawrestakedbeaconchaineth))
+
+*Requirements*:
+* `podOwner` MUST NOT be zero
+* `destination` MUST NOT be zero
+* `shares` MUST NOT be negative when converted to an `int256`
+* `shares` MUST be a whole Gwei amount
+* See [`EigenPod.withdrawRestakedBeaconChainETH`](#eigenpodwithdrawrestakedbeaconchaineth)
+
+##### `EigenPod.withdrawRestakedBeaconChainETH`
+
+```solidity
+function withdrawRestakedBeaconChainETH(
+ address recipient,
+ uint256 amountWei
+)
+ external
+ onlyEigenPodManager
+```
+
+The `EigenPodManager` calls this method when withdrawing a Pod Owner's shares as tokens (native ETH). The input `amountWei` is converted to Gwei and subtracted from `withdrawableRestakedExecutionLayerGwei`, which tracks Gwei that has been provably withdrawn (via `EigenPod.verifyAndProcessWithdrawals`).
+
+As such:
+* If a withdrawal has not been proven that sufficiently raises `withdrawableRestakedExecutionLayerGwei`, this method will revert.
+* If the `EigenPod` does not have `amountWei` available to transfer, this method will revert
+
+*Effects*:
+* Decreases the pod's `withdrawableRestakedExecutionLayerGwei` by `amountWei / GWEI_TO_WEI`
+* Sends `amountWei` ETH to `recipient`
+
+*Requirements*:
+* `amountWei / GWEI_TO_WEI` MUST NOT be greater than the proven `withdrawableRestakedExecutionLayerGwei`
+* Pod MUST have at least `amountWei` ETH balance
+* `recipient` MUST NOT revert when transferred `amountWei`
+* `amountWei` MUST be a whole Gwei amount
+
+#### `EigenPod.verifyAndProcessWithdrawals`
+
+```solidity
+function verifyAndProcessWithdrawals(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields,
+ bytes32[][] calldata withdrawalFields
+)
+ external
+ onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL)
+ onlyNotFrozen
+```
+
+Anyone (not just the Pod Owner) can call this method to prove that one or more validators associated with an `EigenPod` have performed a full or partial withdrawal from the beacon chain.
+
+Whether each withdrawal is a full or partial withdrawal is determined by the validator's "withdrawable epoch" in the `Validator` container given by `validatorFields` (see [consensus specs](https://eth2book.info/capella/part3/containers/dependencies/#validator)). If the withdrawal proof timestamp is after this epoch, the withdrawal is a full withdrawal.
+* Partial withdrawals are performed automatically by the beacon chain when a validator has an effective balance over 32 ETH. This method can be used to prove that these withdrawals occurred, allowing the Pod Owner to withdraw the excess ETH (via [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal)).
+* Full withdrawals are performed when a Pod Owner decides to fully exit a validator from the beacon chain. To do this, the Pod Owner should follow these steps:
+ 1. Undelegate or queue a withdrawal (via the `DelegationManager`: ["Undelegating and Withdrawing"](./DelegationManager.md#undelegating-and-withdrawing))
+ 2. Exit their validator from the beacon chain and provide a proof to this method
+ 3. Complete their withdrawal (via [`DelegationManager.completeQueuedWithdrawal`](./DelegationManager.md#completequeuedwithdrawal)).
+
+If the Pod Owner only exits their validator, the ETH of the pod owner is still staked through EigenLayer and can be used to service AVSs, even though their ETH has been withdrawn from the beacon chain. The protocol allows for this edge case.
+
+*Beacon chain proofs used*:
+* [`verifyStateRootAgainstLatestBlockRoot`](./proofs/BeaconChainProofs.md#beaconchainproofsverifystaterootagainstlatestblockroot)
+* [`verifyWithdrawal`](./proofs/BeaconChainProofs.md#beaconchainproofsverifywithdrawal)
+* [`verifyValidatorFields`](./proofs/BeaconChainProofs.md#beaconchainproofsverifyvalidatorfields)
+
+*Effects*:
+* For each proven withdrawal:
+ * The validator in question is recorded as having a proven withdrawal at the timestamp given by `withdrawalProof.timestampRoot`
+ * This is to prevent the same withdrawal from being proven twice
+ * If this is a full withdrawal:
+ * Any withdrawal amount in excess of `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR` is immediately withdrawn (see [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal))
+ * The remainder (`MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`) must be withdrawn through the `DelegationManager's` withdrawal flow, but in the meantime is added to `EigenPod.withdrawableRestakedExecutionLayerGwei`
+ * If the amount being withdrawn is not equal to the current accounted-for validator balance, a `shareDelta` is calculated to be sent to ([`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)).
+ * The validator's info is updated to reflect its `WITHDRAWN` status, and `restakedBalanceGwei` is set to 0
+ * If this is a partial withdrawal:
+ * The withdrawal amount is added to `sumOfPartialWithdrawalsClaimedGwei`
+ * The withdrawal amount is withdrawn (via [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal))
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_EIGENPODS_VERIFY_WITHDRAWAL`
+* All input array lengths MUST be equal
+* `oracleTimestamp` MUST be queryable via `EigenPodManager.getBlockRootAtTimestamp` (fails if `stateRoot == 0`)
+* `BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot` MUST verify the provided `beaconStateRoot` against the oracle-provided `latestBlockRoot`
+* For each withdrawal being proven:
+ * The time of the withdrawal (`withdrawalProof.timestampRoot`) must be AFTER the `EigenPod's` `mostRecentWithdrawalTimestamp`
+ * The validator MUST be in either status: `ACTIVE` or `WITHDRAWN`
+ * `WITHDRAWN` is permitted because technically, it's possible to deposit additional ETH into an exited validator and have the ETH be auto-withdrawn.
+ * If the withdrawal is a full withdrawal, only `ACTIVE` is permitted
+ * The validator MUST NOT have already proven a withdrawal at the `withdrawalProof.timestampRoot`
+ * `BeaconChainProofs.verifyWithdrawal` MUST verify the provided `withdrawalFields` against the provided `beaconStateRoot`
+ * `BeaconChainProofs.verifyValidatorFields` MUST verify the provided `validatorFields` against the `beaconStateRoot`
+
+#### `DelayedWithdrawalRouter.createDelayedWithdrawal`
+
+```solidity
+function createDelayedWithdrawal(
+ address podOwner,
+ address recipient
+)
+ external
+ payable
+ onlyEigenPod(podOwner)
+ onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
+```
+
+Used by `EigenPods` to queue a withdrawal of beacon chain ETH that can be claimed by a `recipient` after `withdrawalDelayBlocks` have passed.
+
+*Effects*:
+* Creates a `DelayedWithdrawal` for the `recipient` in the amount of `msg.value`, starting at the current block
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_DELAYED_WITHDRAWAL_CLAIMS`
+* Caller MUST be the `EigenPod` associated with the `podOwner`
+* `recipient` MUST NOT be zero
+
+#### `DelayedWithdrawalRouter.claimDelayedWithdrawals`
+
+```solidity
+function claimDelayedWithdrawals(
+ address recipient,
+ uint256 maxNumberOfDelayedWithdrawalsToClaim
+)
+ external
+ nonReentrant
+ onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
+
+// (Uses `msg.sender` as `recipient`)
+function claimDelayedWithdrawals(
+ uint256 maxNumberOfDelayedWithdrawalsToClaim
+)
+ external
+ nonReentrant
+ onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
+```
+
+After `withdrawalDelayBlocks`, withdrawals can be claimed using these methods. Claims may be processed on behalf of someone else by passing their address in as the `recipient`. Otherwise, claims are processed on behalf of `msg.sender`.
+
+This method loops over up to `maxNumberOfDelayedWithdrawalsToClaim` withdrawals, tallys each withdrawal amount, and sends the total to the `recipient`.
+
+*Effects*:
+* Updates the `recipient's` `delayedWithdrawalsCompleted`
+* Sends ETH from completed withdrawals to the `recipient`
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_DELAYED_WITHDRAWAL_CLAIMS`
+
+---
+
+### System Configuration
+
+* [`EigenPodManager.updateBeaconChainOracle`](#eigenpodmanagerupdatebeaconchainoracle)
+* [`DelayedWithdrawalRouter.setWithdrawalDelayBlocks`](#delayedwithdrawalroutersetwithdrawaldelayblocks)
+
+#### `EigenPodManager.updateBeaconChainOracle`
+
+```solidity
+function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner
+```
+
+Allows the owner to update the address of the oracle used by `EigenPods` to retrieve beacon chain state roots (used when verifying beacon chain state proofs).
+
+*Effects*:
+* Updates `EigenPodManager.beaconChainOracle`
+
+*Requirements*:
+* Caller MUST be the owner
+
+#### `DelayedWithdrawalRouter.setWithdrawalDelayBlocks`
+
+```solidity
+function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner
+```
+
+Allows the `DelayedWithdrawalRouter` to update the delay between withdrawal creation and claimability.
+
+The new delay can't exceed `MAX_WITHDRAWAL_DELAY_BLOCKS`.
+
+*Effects*:
+* Updates `DelayedWithdrawalRouter.withdrawalDelayBlocks`
+
+*Requirements*:
+* Caller MUST be the owner
+* `newValue` MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS`
+
+---
+
+### Other Methods
+
+This section details various methods that don't fit well into other sections.
+
+Stakers' balance updates are accounted for when the Staker's `EigenPod` calls this method:
+* [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate)
+
+*For pods deployed prior to M2*, the following methods are callable:
+* [`EigenPod.activateRestaking`](#eigenpodactivaterestaking)
+* [`EigenPod.withdrawBeforeRestaking`](#eigenpodwithdrawbeforerestaking)
+
+The `EigenPod` also includes two token recovery mechanisms:
+* [`EigenPod.withdrawNonBeaconChainETHBalanceWei`](#eigenpodwithdrawnonbeaconchainethbalancewei)
+* [`EigenPod.recoverTokens`](#eigenpodrecovertokens)
+
+#### `EigenPodManager.recordBeaconChainETHBalanceUpdate`
+
+```solidity
+function recordBeaconChainETHBalanceUpdate(
+ address podOwner,
+ int256 sharesDelta
+)
+ external
+ onlyEigenPod(podOwner)
+ nonReentrant
+```
+
+This method is called by an `EigenPod` during a balance update or withdrawal. It accepts a positive or negative `sharesDelta`, which is added/subtracted against the Pod Owner's shares.
+
+If the Pod Owner is not in undelegation limbo and is delegated to an Operator, the `sharesDelta` is also sent to the `DelegationManager` to either increase or decrease the Operator's delegated shares.
+
+*Entry Points*:
+* `EigenPod.verifyWithdrawalCredentials`
+* `EigenPod.verifyBalanceUpdates`
+* `EigenPod.verifyAndProcessWithdrawals`
+
+*Effects*:
+* Adds or removes `sharesDelta` from the Pod Owner's shares
+* If the Pod Owner is NOT in undelegation limbo:
+ * If `sharesDelta` is negative: see [`DelegationManager.decreaseDelegatedShares`](./DelegationManager.md#decreasedelegatedshares)
+ * If `sharesDelta` is positive: see [`DelegationManager.increaseDelegatedShares`](./DelegationManager.md#increasedelegatedshares)
+
+*Requirements*:
+* Caller MUST be the `EigenPod` associated with the passed-in `podOwner`
+* `sharesDelta`:
+ * MUST NOT be 0
+ * If negative, `sharesDelta` MUST NOT remove more shares than the Pod Owner has
+ * MUST be a whole Gwei amount
+
+#### `EigenPod.activateRestaking`
+
+```solidity
+function activateRestaking()
+ external
+ onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
+ onlyEigenPodOwner
+ hasNeverRestaked
+```
+
+Note: This method is only callable on pods deployed before M2. After M2, restaking is enabled by default.
+
+`activateRestaking` allows a Pod Owner to designate their pod (and any future ETH sent to it) as being restaked. Calling this method first withdraws any ETH in the `EigenPod` via the `DelayedWithdrawalRouter`, and then prevents further calls to `withdrawBeforeRestaking`.
+
+Withdrawing any future ETH sent via beacon chain withdrawal to the `EigenPod` requires providing beacon chain state proofs. However, ETH sent to the pod's `receive` function should be withdrawable without proofs (see [`withdrawNonBeaconChainETHBalanceWei`](#eigenpodwithdrawnonbeaconchainethbalancewei)).
+
+*Effects*:
+* Sets `hasRestaked = true`
+* Sets the pod's `nonBeaconChainETHBalanceWei` to 0 (only incremented in the fallback function)
+* Updates the pod's most recent withdrawal timestamp to the current time
+* See [DelayedWithdrawalRouter.createDelayedWithdrawal](#delayedwithdrawalroutercreatedelayedwithdrawal)
+
+*Requirements*:
+* Caller MUST be the Pod Owner
+* Pause status MUST NOT be set: `PAUSED_NEW_EIGENPODS`
+* Pod MUST NOT have already activated restaking
+* See [DelayedWithdrawalRouter.createDelayedWithdrawal](#delayedwithdrawalroutercreatedelayedwithdrawal)
+
+*As of M2*: restaking is automatically activated for newly-deployed `EigenPods` (`hasRestaked = true`). However, for `EigenPods` deployed *before* M2, restaking may not be active (unless the Pod Owner has called this method).
+
+#### `EigenPod.withdrawBeforeRestaking`
+
+```solidity
+function withdrawBeforeRestaking()
+ external
+ onlyEigenPodOwner
+ hasNeverRestaked
+```
+
+Note: This method is only callable on pods deployed before M2. After M2, restaking is enabled by default.
+
+Allows the Pod Owner to withdraw any ETH in the `EigenPod` via the `DelayedWithdrawalRouter`, assuming restaking has not yet been activated. See [`EigenPod.activateRestaking`](#eigenpodactivaterestaking) for more details.
+
+*Effects*:
+* Sets the pod's `nonBeaconChainETHBalanceWei` to 0 (only incremented in the fallback function)
+* Updates the pod's most recent withdrawal timestamp to the current time
+* See [DelayedWithdrawalRouter.createDelayedWithdrawal](#delayedwithdrawalroutercreatedelayedwithdrawal)
+
+*Requirements*:
+* Caller MUST be the Pod Owner
+* Pod MUST NOT have already activated restaking
+* See [DelayedWithdrawalRouter.createDelayedWithdrawal](#delayedwithdrawalroutercreatedelayedwithdrawal)
+
+*As of M2*: restaking is automatically activated for newly-deployed `EigenPods`, making this method uncallable for pods deployed after M2. However, for `EigenPods` deployed *before* M2, restaking may not be active, and this method may be callable.
+
+#### `EigenPod.withdrawNonBeaconChainETHBalanceWei`
+
+```solidity
+function withdrawNonBeaconChainETHBalanceWei(
+ address recipient,
+ uint256 amountToWithdraw
+)
+ external
+ onlyEigenPodOwner
+ onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)
+```
+
+Allows the Pod Owner to withdraw ETH accidentally sent to the contract's `receive` function.
+
+The `receive` function updates `nonBeaconChainETHBalanceWei`, which this function uses to calculate how much can be withdrawn.
+
+Withdrawals from this function are sent via the `DelayedWithdrawalRouter`, and can be claimed by the passed-in `recipient`.
+
+*Effects:*
+* Decrements `nonBeaconChainETHBalanceWei`
+* Sends `amountToWithdraw` wei to [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal)
+
+*Requirements:*
+* Pause status MUST NOT be set: `PAUSED_NON_PROOF_WITHDRAWALS`
+* Caller MUST be the Pod Owner
+* `amountToWithdraw` MUST NOT be greater than the amount sent to the contract's `receive` function
+* See [`DelayedWithdrawalRouter.createDelayedWithdrawal`](#delayedwithdrawalroutercreatedelayedwithdrawal)
+
+#### `EigenPod.recoverTokens`
+
+```solidity
+function recoverTokens(
+ IERC20[] memory tokenList,
+ uint256[] memory amountsToWithdraw,
+ address recipient
+)
+ external
+ onlyEigenPodOwner
+ onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)
+```
+
+Allows the Pod Owner to rescue ERC20 tokens accidentally sent to the `EigenPod`.
+
+*Effects:*
+* Calls `transfer` on each of the ERC20's in `tokenList`, sending the corresponding `amountsToWithdraw` to the `recipient`
+
+*Requirements:*
+* Pause status MUST NOT be set: `PAUSED_NON_PROOF_WITHDRAWALS`
+* `tokenList` and `amountsToWithdraw` MUST have equal lengths
+* Caller MUST be the Pod Owner
diff --git a/docs/core/StrategyManager.md b/docs/core/StrategyManager.md
new file mode 100644
index 0000000000..a6fdb4cbdd
--- /dev/null
+++ b/docs/core/StrategyManager.md
@@ -0,0 +1,351 @@
+## StrategyManager
+
+| File | Type | Proxy |
+| -------- | -------- | -------- |
+| [`StrategyManager.sol`](../../src/contracts/core/StrategyManager.sol) | Singleton | Transparent proxy |
+| [`StrategyBaseTVLLimits.sol`](../../src/contracts/strategies/StrategyBaseTVLLimits.sol) | 3 instances (for cbETH, rETH, stETH) | Transparent proxy |
+
+The primary function of the `StrategyManager` is to handle accounting for individual Stakers as they deposit and withdraw LSTs from their corresponding strategies. It is responsible for (i) allowing Stakers to deposit LSTs into the corresponding strategy, (ii) allowing the `DelegationManager` to remove shares when a Staker queues a withdrawal, and (iii) allowing the `DelegationManager` to complete a withdrawal by either adding shares back to the Staker or withdrawing the shares as tokens via the corresponding strategy.
+
+As of M2, three LSTs are supported and each has its own instance of `StrategyBaseTVLLimits`: cbETH, rETH, and stETH. Each `StrategyBaseTVLLimits` has two main functions (`deposit` and `withdraw`), both of which can only be called by the `StrategyManager`. These `StrategyBaseTVLLimits` contracts are fairly simple deposit/withdraw contracts that hold tokens deposited by Stakers. Because these strategies are essentially extensions of the `StrategyManager`, their functions are documented in this file (see below).
+
+#### High-level Concepts
+
+This document organizes methods according to the following themes (click each to be taken to the relevant section):
+* [Depositing Into Strategies](#depositing-into-strategies)
+* [Withdrawal Processing](#withdrawal-processing)
+* [Strategies](#strategies)
+* [System Configuration](#system-configuration)
+
+#### Important state variables
+
+* `mapping(address => mapping(IStrategy => uint256)) public stakerStrategyShares`: Tracks the current balance a Staker holds in a given strategy. Updated on deposit/withdraw.
+* `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
+
+* `stakerStrategyListLength(address staker) -> (uint)`:
+ * Gives `stakerStrategyList[staker].length`
+ * Used (especially by the `DelegationManager`) to determine whether a Staker has shares in any strategy in the `StrategyManager` (will be 0 if not)
+
+---
+
+### Depositing Into Strategies
+
+The following methods are called by Stakers as they (i) deposit LSTs into strategies to receive shares:
+
+* [`StrategyManager.depositIntoStrategy`](#depositintostrategy)
+* [`StrategyManager.depositIntoStrategyWithSignature`](#depositintostrategywithsignature)
+
+Withdrawals are performed through the `DelegationManager` (see [`DelegationManager.md`](./DelegationManager.md)).
+
+#### `depositIntoStrategy`
+
+```solidity
+function depositIntoStrategy(
+ IStrategy strategy,
+ IERC20 token,
+ uint256 amount
+)
+ external
+ onlyWhenNotPaused(PAUSED_DEPOSITS)
+ onlyNotFrozen(msg.sender)
+ nonReentrant
+ returns (uint256 shares)
+```
+
+Allows a Staker to deposit some `amount` of `token` into the specified `strategy` in exchange for shares of that strategy. The underlying `strategy` must be one of the three whitelisted `StrategyBaseTVLLimits` instances, and the `token` being deposited must correspond to that `strategy's` underlying token (cbETH, rETH, or stETH).
+
+The number of shares received is calculated by the `strategy` using an internal exchange rate that depends on the previous number of tokens deposited.
+
+If the Staker is delegated to an Operator, the Operator's delegated shares are increased in the `DelegationManager`.
+
+*Effects*:
+* `token.safeTransferFrom`: Transfers `amount` of `token` to `strategy` on behalf of the caller.
+* See [`StrategyBaseTVLLimits.deposit`](#strategybasetvllimitsdeposit)
+* `StrategyManager` awards the Staker with the newly-created shares
+* See [`DelegationManager.increaseDelegatedShares`](./DelegationManager.md#increasedelegatedshares)
+
+*Requirements*:
+* Pause status MUST NOT be set: `PAUSED_DEPOSITS`
+* Caller MUST allow at least `amount` of `token` to be transferred by `StrategyManager` to the strategy
+* `strategy` in question MUST be whitelisted for deposits.
+* See [`StrategyBaseTVLLimits.deposit`](#strategybasetvllimitsdeposit)
+
+#### `depositIntoStrategyWithSignature`
+
+```solidity
+function depositIntoStrategyWithSignature(
+ IStrategy strategy,
+ IERC20 token,
+ uint256 amount,
+ address staker,
+ uint256 expiry,
+ bytes memory signature
+)
+ external
+ onlyWhenNotPaused(PAUSED_DEPOSITS)
+ onlyNotFrozen(staker)
+ nonReentrant
+ returns (uint256 shares)
+```
+
+*Effects*: See `depositIntoStrategy` above. Additionally:
+* The Staker's nonce is incremented
+
+*Requirements*: See `depositIntoStrategy` above. Additionally:
+* Caller MUST provide a valid, unexpired signature over the correct fields
+* `thirdPartyTransfersForbidden[strategy]` MUST be false
+
+---
+
+### Withdrawal Processing
+
+These methods are callable ONLY by the `DelegationManager`, and are used when processing undelegations and withdrawals:
+* [`StrategyManager.removeShares`](#removeshares)
+* [`StrategyManager.addShares`](#addshares)
+* [`StrategyManager.withdrawSharesAsTokens`](#withdrawsharesastokens)
+
+See [`DelegationManager.md`](./DelegationManager.md) for more context on how these methods are used.
+
+#### `removeShares`
+
+```solidity
+function removeShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+)
+ external
+ onlyDelegationManager
+```
+
+The `DelegationManager` calls this method when a Staker queues a withdrawal (or undelegates, which also queues a withdrawal). The shares are removed while the withdrawal is in the queue, and when the queue completes, the shares will either be re-awarded or withdrawn as tokens (`addShares` and `withdrawSharesAsTokens`, respectively).
+
+The Staker's share balance for the `strategy` is decreased by the removed `shares`. If this causes the Staker's share balance to hit zero, the `strategy` is removed from the Staker's strategy list.
+
+*Entry Points*:
+* `DelegationManager.undelegate`
+* `DelegationManager.queueWithdrawals`
+
+*Effects*:
+* The Staker's share balance for the given `strategy` is decreased by the given `shares`
+ * If this causes the balance to hit zero, the `strategy` is removed from the Staker's strategy list
+
+*Requirements*:
+* Caller MUST be the `DelegationManager`
+* `staker` parameter MUST NOT be zero
+* `shares` parameter MUST NOT be zero
+* `staker` MUST have at least `shares` balance for the given `strategy`
+
+#### `addShares`
+
+```solidity
+function addShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+)
+ external
+ onlyDelegationManager
+```
+
+The `DelegationManager` calls this method when a queued withdrawal is completed and the withdrawer specifies that they want to receive the withdrawal as "shares" (rather than as the underlying tokens). In this case, the `shares` originally removed (via `removeShares`) are awarded to the `staker` passed in by the `DelegationManager`.
+
+*Entry Points*:
+* `DelegationManager.completeQueuedWithdrawal`
+* `DelegationManager.completeQueuedWithdrawals`
+
+*Effects*:
+* The `staker's` share balance for the given `strategy` is increased by `shares`
+ * If the prior balance was zero, the `strategy` is added to the `staker's` strategy list
+
+*Requirements*:
+* Caller MUST be the `DelegationManager`
+* `staker` parameter MUST NOT be zero
+* `shares` parameter MUST NOT be zero
+
+#### `withdrawSharesAsTokens`
+
+```solidity
+function withdrawSharesAsTokens(
+ address recipient,
+ IStrategy strategy,
+ uint shares,
+ IERC20 token
+)
+ external
+ onlyDelegationManager
+```
+
+The `DelegationManager` calls this method when a queued withdrawal is completed and the withdrawer specifies that they want to receive the withdrawal as the tokens underlying the shares. In this case, the `shares` originally removed (via `removeShares`) are converted to tokens within the `strategy` and sent to the `recipient`.
+
+*Entry Points*:
+* `DelegationManager.completeQueuedWithdrawal`
+* `DelegationManager.completeQueuedWithdrawals`
+
+*Effects*:
+* Calls [`StrategyBaseTVLLimits.withdraw`](#strategybasetvllimitswithdraw)
+
+*Requirements*:
+* Caller MUST be the `DelegationManager`
+* See [`StrategyBaseTVLLimits.withdraw`](#strategybasetvllimitswithdraw)
+
+---
+
+### Strategies
+
+`StrategyBaseTVLLimits` only has two methods of note, and both can only be called by the `StrategyManager`. Documentation for these methods are included below, rather than in a separate file:
+* [`StrategyBaseTVLLimits.deposit`](#strategybasetvllimitsdeposit)
+* [`StrategyBaseTVLLimits.withdraw`](#strategybasetvllimitswithdraw)
+
+#### `StrategyBaseTVLLimits.deposit`
+
+```solidity
+function deposit(
+ IERC20 token,
+ uint256 amount
+)
+ external
+ onlyWhenNotPaused(PAUSED_DEPOSITS)
+ onlyStrategyManager
+ returns (uint256 newShares)
+```
+
+The `StrategyManager` calls this method when Stakers deposit LSTs into a strategy. At the time this method is called, the tokens have already been transferred to the strategy. The role of this method is to (i) calculate the number of shares the deposited tokens represent according to the exchange rate, and (ii) add the new shares to the strategy's recorded total shares.
+
+The new shares created are returned to the `StrategyManager` to be added to the Staker's strategy share balance.
+
+*Entry Points*:
+* `StrategyManager.depositIntoStrategy`
+* `StrategyManager.depositIntoStrategyWithSignature`
+
+*Effects*:
+* `StrategyBaseTVLLimits.totalShares` is increased to account for the new shares created by the deposit
+
+*Requirements*:
+* Caller MUST be the `StrategyManager`
+* Pause status MUST NOT be set: `PAUSED_DEPOSITS`
+* The passed-in `token` MUST match the strategy's `underlyingToken`
+* The token amount being deposited MUST NOT exceed the per-deposit cap
+* After deposit, the strategy's current token balance MUST NOT exceed the total-deposit cap
+* When converted to shares via the strategy's exchange rate, the `amount` of `token` deposited MUST represent at least 1 new share for the depositor
+
+#### `StrategyBaseTVLLimits.withdraw`
+
+```solidity
+function withdraw(
+ address recipient,
+ IERC20 token,
+ uint256 amountShares
+)
+ external
+ onlyWhenNotPaused(PAUSED_WITHDRAWALS)
+ onlyStrategyManager
+```
+
+The `StrategyManager` calls this method when a queued withdrawal is completed and the withdrawer has specified they would like to convert their withdrawn shares to tokens.
+
+This method converts the withdrawal shares back into tokens using the strategy's exchange rate. The strategy's total shares are decreased to reflect the withdrawal before transferring the tokens to the `recipient`.
+
+*Entry Points*:
+* `DelegationManager.completeQueuedWithdrawal`
+* `DelegationManager.completeQueuedWithdrawals`
+
+*Effects*:
+* `StrategyBaseTVLLimits.totalShares` is decreased to account for the shares being withdrawn
+* `underlyingToken.safeTransfer` is called to transfer the tokens to the `recipient`
+
+*Requirements*:
+* Caller MUST be the `StrategyManager`
+* Pause status MUST NOT be set: `PAUSED_WITHDRAWALS`
+* The passed-in `token` MUST match the strategy's `underlyingToken`
+* The `amountShares` being withdrawn MUST NOT exceed the `totalShares` in the strategy
+* The tokens represented by `amountShares` MUST NOT exceed the strategy's token balance
+
+---
+
+### System Configuration
+
+* [`StrategyManager.setStrategyWhitelister`](#setstrategywhitelister)
+* [`StrategyManager.addStrategiesToDepositWhitelist`](#addstrategiestodepositwhitelist)
+* [`StrategyManager.removeStrategiesFromDepositWhitelist`](#removestrategiesfromdepositwhitelist)
+* [`StrategyManager.setThirdPartyTransfersForbidden`](#setthirdpartytransfersforbidden)
+
+#### `setStrategyWhitelister`
+
+```solidity
+function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner
+```
+
+Allows the `owner` to update the Strategy Whitelister address.
+
+*Effects*:
+* Updates `StrategyManager.strategyWhitelister`
+
+*Requirements*:
+* Caller MUST be the `owner`
+
+#### `addStrategiesToDepositWhitelist`
+
+```solidity
+function addStrategiesToDepositWhitelist(
+ IStrategy[] calldata strategiesToWhitelist,
+ bool[] calldata thirdPartyTransfersForbiddenValues
+)
+ external
+ onlyStrategyWhitelister
+```
+
+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`
+
+#### `removeStrategiesFromDepositWhitelist`
+
+```solidity
+function removeStrategiesFromDepositWhitelist(
+ IStrategy[] calldata strategiesToRemoveFromWhitelist
+)
+ external
+ onlyStrategyWhitelister
+```
+
+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
new file mode 100644
index 0000000000..c174ef942b
--- /dev/null
+++ b/docs/core/proofs/BeaconChainProofs.md
@@ -0,0 +1,66 @@
+### Important Details About Proofs
+#### How Indices Are Calculated
+To prove a leaf in a merkle tree, you need several things - A proof, the leaf, the index of that leaf in a list of leaves and the root you are proving against. The beacon state can be represented as several merkle trees stacked on top of each other, i.e., each leaf in the topmost tree is a root of another tree and so on. This means that theoretically, proving most things about the beacon state involves making multiple proofs about each of the merkle trees that are stacked on top of each other.
+
+However there is a way we can combine these proofs into a single proof. This is by concatenating each of the individual proofs into one large proof and proving that against the topmost root. However, how do we calculate the "index" for this mega-proof?
+
+The idea is simple, in a Merkle tree, every node has two children: left (or 0) and right (or 1). Starting from the root and moving down to a specific leaf, you can interpret each bit in the binary representation of the leaf's index as an instruction to traverse left (for 0) or right (for 1). The length of a binary representation of an index is just `log(num_leaves) = height_of_the tree`.
+
+Taking an example, let's say I had one merkle tree A whose Nth leaf was the root of merkle tree B. So to calculate the index for the Mth leaf in B against the root of A, the index would be:
+`index_B_against_A = N << height_of_merkle_tree_B | M`. In the image below, the blue nodes indicate the path we are trying to prove, the pink nodes are nodes in merkle tree B, which is a subtree of merkle tree A.
+
+![Sample Merkle Tree](../../images/samplemerkle.png)
+
+Below are the explanations of each individual proof function that we use to prove various attributes about the state of the beacon chain and validators who are restaking via the EigenPods subprotocol.
+#### `BeaconChainProofs.verifyValidatorFields`
+
+```solidity
+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. 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.verifyStateRootAgainstLatestBlockRoot`
+
+```solidity
+function verifyStateRootAgainstLatestBlockRoot(
+ 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.
+
+![Verify State Root Proof Structure](../../images/staterootproof.png)
+
+
+#### `BeaconChainProofs.verifyWithdrawal`
+
+```solidity
+function verifyWithdrawal(
+ 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.
+
+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/docgen/core/DelegationManager.md b/docs/docgen/core/DelegationManager.md
deleted file mode 100644
index a397526f8f..0000000000
--- a/docs/docgen/core/DelegationManager.md
+++ /dev/null
@@ -1,208 +0,0 @@
-# Solidity API
-
-## DelegationManager
-
-This is the contract for delegation in EigenLayer. The main functionalities of this contract are
-- enabling anyone to register as an operator in EigenLayer
-- allowing new operators to provide a DelegationTerms-type contract, which may mediate their interactions with stakers who delegate to them
-- enabling any staker to delegate its stake to the operator of its choice
-- enabling a staker to undelegate its assets from an operator (performed as part of the withdrawal process, initiated through the StrategyManager)
-
-### PAUSED_NEW_DELEGATION
-
-```solidity
-uint8 PAUSED_NEW_DELEGATION
-```
-
-### ERC1271_MAGICVALUE
-
-```solidity
-bytes4 ERC1271_MAGICVALUE
-```
-
-### ORIGINAL_CHAIN_ID
-
-```solidity
-uint256 ORIGINAL_CHAIN_ID
-```
-
-### onlyStrategyManager
-
-```solidity
-modifier onlyStrategyManager()
-```
-
-Simple permission for functions that are only callable by the StrategyManager contract.
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract ISlasher _slasher) public
-```
-
-### OnDelegationReceivedCallFailure
-
-```solidity
-event OnDelegationReceivedCallFailure(contract IDelegationTerms delegationTerms, bytes32 returnData)
-```
-
-_Emitted when a low-level call to `delegationTerms.onDelegationReceived` fails, returning `returnData`_
-
-### OnDelegationWithdrawnCallFailure
-
-```solidity
-event OnDelegationWithdrawnCallFailure(contract IDelegationTerms delegationTerms, bytes32 returnData)
-```
-
-_Emitted when a low-level call to `delegationTerms.onDelegationWithdrawn` fails, returning `returnData`_
-
-### RegisterAsOperator
-
-```solidity
-event RegisterAsOperator(address operator, contract IDelegationTerms delegationTerms)
-```
-
-_Emitted when an entity registers itself as an operator in the DelegationManager_
-
-### initialize
-
-```solidity
-function initialize(address initialOwner, contract IPauserRegistry _pauserRegistry, uint256 initialPausedStatus) external
-```
-
-### registerAsOperator
-
-```solidity
-function registerAsOperator(contract IDelegationTerms dt) external
-```
-
-This will be called by an operator to register itself as an operator that stakers can choose to delegate to.
-
-_An operator can set `dt` equal to their own address (or another EOA address), in the event that they want to split payments
-in a more 'trustful' manner.
-In the present design, once set, there is no way for an operator to ever modify the address of their DelegationTerms contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| dt | contract IDelegationTerms | is the `DelegationTerms` contract that the operator has for those who delegate to them. |
-
-### delegateTo
-
-```solidity
-function delegateTo(address operator) external
-```
-
-@notice This will be called by a staker to delegate its assets to some operator.
- @param operator is the operator to whom staker (msg.sender) is delegating its assets
-
-### delegateToBySignature
-
-```solidity
-function delegateToBySignature(address staker, address operator, uint256 expiry, bytes signature) external
-```
-
-Delegates from `staker` to `operator`.
-
-_requires that:
-1) if `staker` is an EOA, then `signature` is valid ECDSA signature from `staker`, indicating their intention for this action
-2) if `staker` is a contract, then `signature` must will be checked according to EIP-1271_
-
-### undelegate
-
-```solidity
-function undelegate(address staker) external
-```
-
-Undelegates `staker` from the operator who they are delegated to.
-Callable only by the StrategyManager
-
-_Should only ever be called in the event that the `staker` has no active deposits in EigenLayer._
-
-### increaseDelegatedShares
-
-```solidity
-function increaseDelegatedShares(address staker, contract IStrategy strategy, uint256 shares) external
-```
-
-Increases the `staker`'s delegated shares in `strategy` by `shares, typically called when the staker has further deposits into EigenLayer
-
-_Callable only by the StrategyManager_
-
-### decreaseDelegatedShares
-
-```solidity
-function decreaseDelegatedShares(address staker, contract IStrategy[] strategies, uint256[] shares) external
-```
-
-Decreases the `staker`'s delegated shares in each entry of `strategies` by its respective `shares[i]`, typically called when the staker withdraws from EigenLayer
-
-_Callable only by the StrategyManager_
-
-### _delegationReceivedHook
-
-```solidity
-function _delegationReceivedHook(contract IDelegationTerms dt, address staker, contract IStrategy[] strategies, uint256[] shares) internal
-```
-
-Makes a low-level call to `dt.onDelegationReceived(staker, strategies, shares)`, ignoring reverts and with a gas budget
-equal to `LOW_LEVEL_GAS_BUDGET` (a constant defined in this contract).
-
-_*If* the low-level call fails, then this function emits the event `OnDelegationReceivedCallFailure(dt, returnData)`, where
-`returnData` is *only the first 32 bytes* returned by the call to `dt`._
-
-### _delegationWithdrawnHook
-
-```solidity
-function _delegationWithdrawnHook(contract IDelegationTerms dt, address staker, contract IStrategy[] strategies, uint256[] shares) internal
-```
-
-Makes a low-level call to `dt.onDelegationWithdrawn(staker, strategies, shares)`, ignoring reverts and with a gas budget
-equal to `LOW_LEVEL_GAS_BUDGET` (a constant defined in this contract).
-
-_*If* the low-level call fails, then this function emits the event `OnDelegationReceivedCallFailure(dt, returnData)`, where
-`returnData` is *only the first 32 bytes* returned by the call to `dt`._
-
-### _delegate
-
-```solidity
-function _delegate(address staker, address operator) internal
-```
-
-Internal function implementing the delegation *from* `staker` *to* `operator`.
-
-_Ensures that the operator has registered as a delegate (`address(dt) != address(0)`), verifies that `staker` is not already
-delegated, and records the new delegation._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| staker | address | The address to delegate *from* -- this address is delegating control of its own assets. |
-| operator | address | The address to delegate *to* -- this address is being given power to place the `staker`'s assets at risk on services |
-
-### isDelegated
-
-```solidity
-function isDelegated(address staker) public view returns (bool)
-```
-
-Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
-
-### isNotDelegated
-
-```solidity
-function isNotDelegated(address staker) public view returns (bool)
-```
-
-Returns 'true' if `staker` is *not* actively delegated, and 'false' otherwise.
-
-### isOperator
-
-```solidity
-function isOperator(address operator) public view returns (bool)
-```
-
-Returns if an operator can be delegated to, i.e. it has called `registerAsOperator`.
-
diff --git a/docs/docgen/core/DelegationManagerStorage.md b/docs/docgen/core/DelegationManagerStorage.md
deleted file mode 100644
index 58b0fe0f99..0000000000
--- a/docs/docgen/core/DelegationManagerStorage.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Solidity API
-
-## DelegationManagerStorage
-
-This storage contract is separate from the logic to simplify the upgrade process.
-
-### LOW_LEVEL_GAS_BUDGET
-
-```solidity
-uint256 LOW_LEVEL_GAS_BUDGET
-```
-
-Gas budget provided in calls to DelegationTerms contracts
-
-### DOMAIN_TYPEHASH
-
-```solidity
-bytes32 DOMAIN_TYPEHASH
-```
-
-The EIP-712 typehash for the contract's domain
-
-### DELEGATION_TYPEHASH
-
-```solidity
-bytes32 DELEGATION_TYPEHASH
-```
-
-The EIP-712 typehash for the delegation struct used by the contract
-
-### DOMAIN_SEPARATOR
-
-```solidity
-bytes32 DOMAIN_SEPARATOR
-```
-
-EIP-712 Domain separator
-
-### strategyManager
-
-```solidity
-contract IStrategyManager strategyManager
-```
-
-The StrategyManager contract for EigenLayer
-
-### slasher
-
-```solidity
-contract ISlasher slasher
-```
-
-The Slasher contract for EigenLayer
-
-### operatorShares
-
-```solidity
-mapping(address => mapping(contract IStrategy => uint256)) operatorShares
-```
-
-Mapping: operator => strategy => total number of shares in the strategy delegated to the operator
-
-### delegationTerms
-
-```solidity
-mapping(address => contract IDelegationTerms) delegationTerms
-```
-
-Mapping: operator => delegation terms contract
-
-### delegatedTo
-
-```solidity
-mapping(address => address) delegatedTo
-```
-
-Mapping: staker => operator whom the staker has delegated to
-
-### nonces
-
-```solidity
-mapping(address => uint256) nonces
-```
-
-Mapping: delegator => number of signed delegation nonce (used in delegateToBySignature)
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract ISlasher _slasher) internal
-```
-
-### __gap
-
-```solidity
-uint256[46] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/core/Slasher.md b/docs/docgen/core/Slasher.md
deleted file mode 100644
index 8fa9dd3ae9..0000000000
--- a/docs/docgen/core/Slasher.md
+++ /dev/null
@@ -1,470 +0,0 @@
-# Solidity API
-
-## Slasher
-
-This contract specifies details on slashing. The functionalities are:
-- adding contracts who have permission to perform slashing,
-- revoking permission for slashing from specified contracts,
-- tracking historic stake updates to ensure that withdrawals can only be completed once no middlewares have slashing rights
-over the funds being withdrawn
-
-### HEAD
-
-```solidity
-uint256 HEAD
-```
-
-### PAUSED_OPT_INTO_SLASHING
-
-```solidity
-uint8 PAUSED_OPT_INTO_SLASHING
-```
-
-### PAUSED_FIRST_STAKE_UPDATE
-
-```solidity
-uint8 PAUSED_FIRST_STAKE_UPDATE
-```
-
-### PAUSED_NEW_FREEZING
-
-```solidity
-uint8 PAUSED_NEW_FREEZING
-```
-
-### strategyManager
-
-```solidity
-contract IStrategyManager strategyManager
-```
-
-The central StrategyManager contract of EigenLayer
-
-### delegation
-
-```solidity
-contract IDelegationManager delegation
-```
-
-The DelegationManager contract of EigenLayer
-
-### _whitelistedContractDetails
-
-```solidity
-mapping(address => mapping(address => struct ISlasher.MiddlewareDetails)) _whitelistedContractDetails
-```
-
-### frozenStatus
-
-```solidity
-mapping(address => bool) frozenStatus
-```
-
-### MAX_CAN_SLASH_UNTIL
-
-```solidity
-uint32 MAX_CAN_SLASH_UNTIL
-```
-
-### _operatorToWhitelistedContractsByUpdate
-
-```solidity
-mapping(address => struct StructuredLinkedList.List) _operatorToWhitelistedContractsByUpdate
-```
-
-operator => a linked list of the addresses of the whitelisted middleware with permission to slash the operator, i.e. which
-the operator is serving. Sorted by the block at which they were last updated (content of updates below) in ascending order.
-This means the 'HEAD' (i.e. start) of the linked list will have the stalest 'updateBlock' value.
-
-### _operatorToMiddlewareTimes
-
-```solidity
-mapping(address => struct ISlasher.MiddlewareTimes[]) _operatorToMiddlewareTimes
-```
-
-operator =>
- [
- (
- the least recent update block of all of the middlewares it's serving/served,
- latest time that the stake bonded at that update needed to serve until
- )
- ]
-
-### MiddlewareTimesAdded
-
-```solidity
-event MiddlewareTimesAdded(address operator, uint256 index, uint32 stalestUpdateBlock, uint32 latestServeUntilBlock)
-```
-
-Emitted when a middleware times is added to `operator`'s array.
-
-### OptedIntoSlashing
-
-```solidity
-event OptedIntoSlashing(address operator, address contractAddress)
-```
-
-Emitted when `operator` begins to allow `contractAddress` to slash them.
-
-### SlashingAbilityRevoked
-
-```solidity
-event SlashingAbilityRevoked(address operator, address contractAddress, uint32 contractCanSlashOperatorUntilBlock)
-```
-
-Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilBlock`.
-
-### OperatorFrozen
-
-```solidity
-event OperatorFrozen(address slashedOperator, address slashingContract)
-```
-
-Emitted when `slashingContract` 'freezes' the `slashedOperator`.
-
-_The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'._
-
-### FrozenStatusReset
-
-```solidity
-event FrozenStatusReset(address previouslySlashedAddress)
-```
-
-Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within EigenLayer.
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IDelegationManager _delegation) public
-```
-
-### onlyRegisteredForService
-
-```solidity
-modifier onlyRegisteredForService(address operator)
-```
-
-Ensures that the operator has opted into slashing by the caller, and that the caller has never revoked its slashing ability.
-
-### initialize
-
-```solidity
-function initialize(address initialOwner, contract IPauserRegistry _pauserRegistry, uint256 initialPausedStatus) external
-```
-
-### optIntoSlashing
-
-```solidity
-function optIntoSlashing(address contractAddress) external
-```
-
-Gives the `contractAddress` permission to slash the funds of the caller.
-
-_Typically, this function must be called prior to registering for a middleware._
-
-### freezeOperator
-
-```solidity
-function freezeOperator(address toBeFrozen) external
-```
-
-Used for 'slashing' a certain operator.
-
-_Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop.
-The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| toBeFrozen | address | The operator to be frozen. |
-
-### resetFrozenStatus
-
-```solidity
-function resetFrozenStatus(address[] frozenAddresses) external
-```
-
-Removes the 'frozen' status from each of the `frozenAddresses`
-
-_Callable only by the contract owner (i.e. governance)._
-
-### recordFirstStakeUpdate
-
-```solidity
-function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external
-```
-
-this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
- is slashable until serveUntilBlock
-
-_adds the middleware's slashing contract to the operator's linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the operator whose stake update is being recorded |
-| serveUntilBlock | uint32 | the block until which the operator's stake at the current block is slashable |
-
-### recordStakeUpdate
-
-```solidity
-function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 insertAfter) external
-```
-
-this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals)
- to make sure the operator's stake at updateBlock is slashable until serveUntilBlock
-
-_insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
- but it is anticipated to be rare and not detrimental._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the operator whose stake update is being recorded |
-| updateBlock | uint32 | the block for which the stake update is being recorded |
-| serveUntilBlock | uint32 | the block until which the operator's stake at updateBlock is slashable |
-| insertAfter | uint256 | the element of the operators linked list that the currently updating middleware should be inserted after |
-
-### recordLastStakeUpdateAndRevokeSlashingAbility
-
-```solidity
-function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external
-```
-
-this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
- is slashable until serveUntilBlock
-
-_removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to
-slash `operator` once `serveUntilBlock` is reached_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the operator whose stake update is being recorded |
-| serveUntilBlock | uint32 | the block until which the operator's stake at the current block is slashable |
-
-### contractCanSlashOperatorUntilBlock
-
-```solidity
-function contractCanSlashOperatorUntilBlock(address operator, address serviceContract) external view returns (uint32)
-```
-
-Returns the block until which `serviceContract` is allowed to slash the `operator`.
-
-### latestUpdateBlock
-
-```solidity
-function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32)
-```
-
-Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
-
-### whitelistedContractDetails
-
-```solidity
-function whitelistedContractDetails(address operator, address serviceContract) external view returns (struct ISlasher.MiddlewareDetails)
-```
-
-### isFrozen
-
-```solidity
-function isFrozen(address staker) external view returns (bool)
-```
-
-Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
-slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
-and the staker's status is reset (to 'unfrozen').
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| staker | address | The staker of interest. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated to an operator who has their status set to frozen. Otherwise returns 'false'. |
-
-### canSlash
-
-```solidity
-function canSlash(address toBeSlashed, address slashingContract) public view returns (bool)
-```
-
-Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`.
-
-### canWithdraw
-
-```solidity
-function canWithdraw(address operator, uint32 withdrawalStartBlock, uint256 middlewareTimesIndex) external view returns (bool)
-```
-
-Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used
-to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `_operatorToMiddlewareTimes[operator]`). The specified
-struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
-This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event
-that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist.
-
-_The correct `middlewareTimesIndex` input should be computable off-chain._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator, this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartBlock`. |
-| withdrawalStartBlock | uint32 | The block number at which the withdrawal was initiated. |
-| middlewareTimesIndex | uint256 | Indicates an index in `_operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw |
-
-### operatorToMiddlewareTimes
-
-```solidity
-function operatorToMiddlewareTimes(address operator, uint256 arrayIndex) external view returns (struct ISlasher.MiddlewareTimes)
-```
-
-Getter function for fetching `_operatorToMiddlewareTimes[operator][arrayIndex]`.
-
-### middlewareTimesLength
-
-```solidity
-function middlewareTimesLength(address operator) external view returns (uint256)
-```
-
-Getter function for fetching `_operatorToMiddlewareTimes[operator].length`.
-
-### getMiddlewareTimesIndexBlock
-
-```solidity
-function getMiddlewareTimesIndexBlock(address operator, uint32 index) external view returns (uint32)
-```
-
-Getter function for fetching `_operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
-
-### getMiddlewareTimesIndexServeUntilBlock
-
-```solidity
-function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32)
-```
-
-Getter function for fetching `_operatorToMiddlewareTimes[operator][index].latestServeUntilBlock`.
-
-### operatorWhitelistedContractsLinkedListSize
-
-```solidity
-function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256)
-```
-
-Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
-
-### operatorWhitelistedContractsLinkedListEntry
-
-```solidity
-function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256)
-```
-
-Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
-
-### getCorrectValueForInsertAfter
-
-```solidity
-function getCorrectValueForInsertAfter(address operator, uint32 updateBlock) public view returns (uint256)
-```
-
-A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`.
-
-_Used within this contract only as a fallback in the case when an incorrect value of `insertAfter` is supplied as an input to `_updateMiddlewareList`.
-The return value should *either* be 'HEAD' (i.e. zero) in the event that the node being inserted in the linked list has an `updateBlock`
-that is less than the HEAD of the list, *or* the return value should specify the last `node` in the linked list for which
-`_whitelistedContractDetails[operator][node].latestUpdateBlock <= updateBlock`,
-i.e. the node such that the *next* node either doesn't exist,
-OR
-`_whitelistedContractDetails[operator][nextNode].latestUpdateBlock > updateBlock`._
-
-### getPreviousWhitelistedContractByUpdate
-
-```solidity
-function getPreviousWhitelistedContractByUpdate(address operator, uint256 node) external view returns (bool, uint256)
-```
-
-gets the node previous to the given node in the operators middleware update linked list
-
-_used in offchain libs for updating stakes_
-
-### _optIntoSlashing
-
-```solidity
-function _optIntoSlashing(address operator, address contractAddress) internal
-```
-
-### _revokeSlashingAbility
-
-```solidity
-function _revokeSlashingAbility(address operator, address contractAddress, uint32 serveUntilBlock) internal
-```
-
-### _freezeOperator
-
-```solidity
-function _freezeOperator(address toBeFrozen, address slashingContract) internal
-```
-
-### _resetFrozenStatus
-
-```solidity
-function _resetFrozenStatus(address previouslySlashedAddress) internal
-```
-
-### _recordUpdateAndAddToMiddlewareTimes
-
-```solidity
-function _recordUpdateAndAddToMiddlewareTimes(address operator, uint32 updateBlock, uint32 serveUntilBlock) internal
-```
-
-records the most recent updateBlock for the currently updating middleware and appends an entry to the operator's list of
- MiddlewareTimes if relavent information has updated
-
-_this function is only called during externally called stake updates by middleware contracts that can slash operator_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the entity whose stake update is being recorded |
-| updateBlock | uint32 | the block number for which the currently updating middleware is updating the serveUntilBlock for |
-| serveUntilBlock | uint32 | the block until which the operator's stake at updateBlock is slashable |
-
-### _updateMiddlewareList
-
-```solidity
-function _updateMiddlewareList(address operator, uint32 updateBlock, uint256 insertAfter) internal
-```
-
-A routine for updating the `operator`'s linked list of middlewares, inside `recordStakeUpdate`.
-
-### _addressToUint
-
-```solidity
-function _addressToUint(address addr) internal pure returns (uint256)
-```
-
-### _uintToAddress
-
-```solidity
-function _uintToAddress(uint256 x) internal pure returns (address)
-```
-
-### __gap
-
-```solidity
-uint256[46] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/core/StrategyManager.md b/docs/docgen/core/StrategyManager.md
deleted file mode 100644
index 4ddd4764c8..0000000000
--- a/docs/docgen/core/StrategyManager.md
+++ /dev/null
@@ -1,666 +0,0 @@
-# Solidity API
-
-## StrategyManager
-
-This contract is for managing deposits in different strategies. The main
-functionalities are:
-- adding and removing strategies that any delegator can deposit into
-- enabling deposit of assets into specified strategy(s)
-- enabling withdrawal of assets from specified strategy(s)
-- recording deposit of ETH into settlement layer
-- slashing of assets for permissioned strategies
-
-### GWEI_TO_WEI
-
-```solidity
-uint256 GWEI_TO_WEI
-```
-
-### PAUSED_DEPOSITS
-
-```solidity
-uint8 PAUSED_DEPOSITS
-```
-
-### PAUSED_WITHDRAWALS
-
-```solidity
-uint8 PAUSED_WITHDRAWALS
-```
-
-### ORIGINAL_CHAIN_ID
-
-```solidity
-uint256 ORIGINAL_CHAIN_ID
-```
-
-### ERC1271_MAGICVALUE
-
-```solidity
-bytes4 ERC1271_MAGICVALUE
-```
-
-### Deposit
-
-```solidity
-event Deposit(address depositor, contract IERC20 token, contract IStrategy strategy, uint256 shares)
-```
-
-Emitted when a new deposit occurs on behalf of `depositor`.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | Is the staker who is depositing funds into EigenLayer. |
-| token | contract IERC20 | Is the token that `depositor` deposited. |
-| strategy | contract IStrategy | Is the strategy that `depositor` has deposited into. |
-| shares | uint256 | Is the number of new shares `depositor` has been granted in `strategy`. |
-
-### ShareWithdrawalQueued
-
-```solidity
-event ShareWithdrawalQueued(address depositor, uint96 nonce, contract IStrategy strategy, uint256 shares)
-```
-
-Emitted when a new withdrawal occurs on behalf of `depositor`.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | Is the staker who is queuing a withdrawal from EigenLayer. |
-| nonce | uint96 | Is the withdrawal's unique identifier (to the depositor). |
-| strategy | contract IStrategy | Is the strategy that `depositor` has queued to withdraw from. |
-| shares | uint256 | Is the number of shares `depositor` has queued to withdraw. |
-
-### WithdrawalQueued
-
-```solidity
-event WithdrawalQueued(address depositor, uint96 nonce, address withdrawer, address delegatedAddress, bytes32 withdrawalRoot)
-```
-
-Emitted when a new withdrawal is queued by `depositor`.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | Is the staker who is withdrawing funds from EigenLayer. |
-| nonce | uint96 | Is the withdrawal's unique identifier (to the depositor). |
-| withdrawer | address | Is the party specified by `staker` who will be able to complete the queued withdrawal and receive the withdrawn funds. |
-| delegatedAddress | address | Is the party who the `staker` was delegated to at the time of creating the queued withdrawal |
-| withdrawalRoot | bytes32 | Is a hash of the input data for the withdrawal. |
-
-### WithdrawalCompleted
-
-```solidity
-event WithdrawalCompleted(address depositor, uint96 nonce, address withdrawer, bytes32 withdrawalRoot)
-```
-
-Emitted when a queued withdrawal is completed
-
-### StrategyWhitelisterChanged
-
-```solidity
-event StrategyWhitelisterChanged(address previousAddress, address newAddress)
-```
-
-Emitted when the `strategyWhitelister` is changed
-
-### StrategyAddedToDepositWhitelist
-
-```solidity
-event StrategyAddedToDepositWhitelist(contract IStrategy strategy)
-```
-
-Emitted when a strategy is added to the approved list of strategies for deposit
-
-### StrategyRemovedFromDepositWhitelist
-
-```solidity
-event StrategyRemovedFromDepositWhitelist(contract IStrategy strategy)
-```
-
-Emitted when a strategy is removed from the approved list of strategies for deposit
-
-### WithdrawalDelayBlocksSet
-
-```solidity
-event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue)
-```
-
-Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
-
-### onlyNotFrozen
-
-```solidity
-modifier onlyNotFrozen(address staker)
-```
-
-### onlyFrozen
-
-```solidity
-modifier onlyFrozen(address staker)
-```
-
-### onlyEigenPodManager
-
-```solidity
-modifier onlyEigenPodManager()
-```
-
-### onlyStrategyWhitelister
-
-```solidity
-modifier onlyStrategyWhitelister()
-```
-
-### onlyStrategiesWhitelistedForDeposit
-
-```solidity
-modifier onlyStrategiesWhitelistedForDeposit(contract IStrategy strategy)
-```
-
-### constructor
-
-```solidity
-constructor(contract IDelegationManager _delegation, contract IEigenPodManager _eigenPodManager, contract ISlasher _slasher) public
-```
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _delegation | contract IDelegationManager | The delegation contract of EigenLayer. |
-| _eigenPodManager | contract IEigenPodManager | The contract that keeps track of EigenPod stakes for restaking beacon chain ether. |
-| _slasher | contract ISlasher | The primary slashing contract of EigenLayer. |
-
-### initialize
-
-```solidity
-function initialize(address initialOwner, address initialStrategyWhitelister, contract IPauserRegistry _pauserRegistry, uint256 initialPausedStatus, uint256 _withdrawalDelayBlocks) external
-```
-
-Initializes the strategy manager contract. Sets the `pauserRegistry` (currently **not** modifiable after being set),
-and transfers contract ownership to the specified `initialOwner`.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| initialOwner | address | Ownership of this contract is transferred to this address. |
-| initialStrategyWhitelister | address | The initial value of `strategyWhitelister` to set. |
-| _pauserRegistry | contract IPauserRegistry | Used for access control of pausing. |
-| initialPausedStatus | uint256 | The initial value of `_paused` to set. |
-| _withdrawalDelayBlocks | uint256 | The initial value of `withdrawalDelayBlocks` to set. |
-
-### depositBeaconChainETH
-
-```solidity
-function depositBeaconChainETH(address staker, uint256 amount) external
-```
-
-Deposits `amount` of beaconchain ETH into this contract on behalf of `staker`
-
-_Only callable by EigenPodManager._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| staker | address | is the entity that is restaking in eigenlayer, |
-| amount | uint256 | is the amount of beaconchain ETH being restaked, |
-
-### recordOvercommittedBeaconChainETH
-
-```solidity
-function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external
-```
-
-Records an overcommitment event on behalf of a staker. The staker's beaconChainETH shares are decremented by `amount`.
-
-_Only callable by EigenPodManager._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| overcommittedPodOwner | address | is the pod owner to be slashed |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy in case it must be removed, |
-| amount | uint256 | is the amount to decrement the slashedAddress's beaconChainETHStrategy shares |
-
-### depositIntoStrategy
-
-```solidity
-function depositIntoStrategy(contract IStrategy strategy, contract IERC20 token, uint256 amount) external returns (uint256 shares)
-```
-
-Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
-
-_The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
-Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
-
-WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
- where the token balance and corresponding strategy shares are not in sync upon reentrancy._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategy | contract IStrategy | is the specified strategy where deposit is to be made, |
-| token | contract IERC20 | is the denomination in which the deposit is to be made, |
-| amount | uint256 | is the amount of token to be deposited in the strategy by the depositor |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| shares | uint256 | The amount of new shares in the `strategy` created as part of the action. |
-
-### depositIntoStrategyWithSignature
-
-```solidity
-function depositIntoStrategyWithSignature(contract IStrategy strategy, contract IERC20 token, uint256 amount, address staker, uint256 expiry, bytes signature) external returns (uint256 shares)
-```
-
-Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
-who must sign off on the action.
-Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
-purely to help one address deposit 'for' another.
-
-_The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
-A signature is required for this function to eliminate the possibility of griefing attacks, specifically those
-targeting stakers who may be attempting to undelegate.
-Cannot be called on behalf of a staker that is 'frozen' (this function will revert if the `staker` is frozen).
-
- WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
- where the token balance and corresponding strategy shares are not in sync upon reentrancy_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategy | contract IStrategy | is the specified strategy where deposit is to be made, |
-| token | contract IERC20 | is the denomination in which the deposit is to be made, |
-| amount | uint256 | is the amount of token to be deposited in the strategy by the depositor |
-| staker | address | the staker that the deposited assets will be credited to |
-| expiry | uint256 | the timestamp at which the signature expires |
-| signature | bytes | is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward following EIP-1271 if the `staker` is a contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| shares | uint256 | The amount of new shares in the `strategy` created as part of the action. |
-
-### undelegate
-
-```solidity
-function undelegate() external
-```
-
-Called by a staker to undelegate entirely from EigenLayer. The staker must first withdraw all of their existing deposits
-(through use of the `queueWithdrawal` function), or else otherwise have never deposited in EigenLayer prior to delegating.
-
-### queueWithdrawal
-
-```solidity
-function queueWithdrawal(uint256[] strategyIndexes, contract IStrategy[] strategies, uint256[] shares, address withdrawer, bool undelegateIfPossible) external returns (bytes32)
-```
-
-Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
-
-_Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
-User shares are decreased in this function, but the total number of shares in each strategy remains the same.
-The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
-the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
-that the value per share reported by each strategy will remain consistent, and that the shares will continue
-to accrue gains during the enforced withdrawal waiting period.
-Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
-popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
-is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
-`stakerStrategyList` to lowest index
-Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
-`withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed
-for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
-the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod)._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategyIndexes | uint256[] | is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies for which `msg.sender` is withdrawing 100% of their shares |
-| strategies | contract IStrategy[] | The Strategies to withdraw from |
-| shares | uint256[] | The amount of shares to withdraw from each of the respective Strategies in the `strategies` array |
-| withdrawer | address | The address that can complete the withdrawal and will receive any withdrawn funds or shares upon completing the withdrawal |
-| undelegateIfPossible | bool | If this param is marked as 'true' *and the withdrawal will result in `msg.sender` having no shares in any Strategy,* then this function will also make an internal call to `undelegate(msg.sender)` to undelegate the `msg.sender`. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | The 'withdrawalRoot' of the newly created Queued Withdrawal |
-
-### completeQueuedWithdrawal
-
-```solidity
-function completeQueuedWithdrawal(struct IStrategyManager.QueuedWithdrawal queuedWithdrawal, contract IERC20[] tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) external
-```
-
-Used to complete the specified `queuedWithdrawal`. The function caller must match `queuedWithdrawal.withdrawer`
-
-_middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| queuedWithdrawal | struct IStrategyManager.QueuedWithdrawal | The QueuedWithdrawal to complete. |
-| tokens | contract IERC20[] | Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array of the `queuedWithdrawal`. This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused) |
-| middlewareTimesIndex | uint256 | is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array |
-| receiveAsTokens | bool | If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies will simply be transferred to the caller directly. |
-
-### completeQueuedWithdrawals
-
-```solidity
-function completeQueuedWithdrawals(struct IStrategyManager.QueuedWithdrawal[] queuedWithdrawals, contract IERC20[][] tokens, uint256[] middlewareTimesIndexes, bool[] receiveAsTokens) external
-```
-
-Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer`
-
-_Array-ified version of `completeQueuedWithdrawal`
-middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| queuedWithdrawals | struct IStrategyManager.QueuedWithdrawal[] | The QueuedWithdrawals to complete. |
-| tokens | contract IERC20[][] | Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array. |
-| middlewareTimesIndexes | uint256[] | One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index. |
-| receiveAsTokens | bool[] | If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies will simply be transferred to the caller directly. |
-
-### slashShares
-
-```solidity
-function slashShares(address slashedAddress, address recipient, contract IStrategy[] strategies, contract IERC20[] tokens, uint256[] strategyIndexes, uint256[] shareAmounts) external
-```
-
-Slashes the shares of a 'frozen' operator (or a staker delegated to one)
-
-_strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
-popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
-is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
-`stakerStrategyList` to lowest index_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| slashedAddress | address | is the frozen address that is having its shares slashed |
-| recipient | address | is the address that will receive the slashed funds, which could e.g. be a harmed party themself, or a MerkleDistributor-type contract that further sub-divides the slashed funds. |
-| strategies | contract IStrategy[] | Strategies to slash |
-| tokens | contract IERC20[] | The tokens to use as input to the `withdraw` function of each of the provided `strategies` |
-| strategyIndexes | uint256[] | is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies for which `msg.sender` is withdrawing 100% of their shares |
-| shareAmounts | uint256[] | The amount of shares to slash in each of the provided `strategies` |
-
-### slashQueuedWithdrawal
-
-```solidity
-function slashQueuedWithdrawal(address recipient, struct IStrategyManager.QueuedWithdrawal queuedWithdrawal, contract IERC20[] tokens, uint256[] indicesToSkip) external
-```
-
-Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one)
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| recipient | address | The funds in the slashed withdrawal are withdrawn as tokens to this address. |
-| queuedWithdrawal | struct IStrategyManager.QueuedWithdrawal | The previously queued withdrawal to be slashed |
-| tokens | contract IERC20[] | Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array of the `queuedWithdrawal`. |
-| indicesToSkip | uint256[] | Optional input parameter -- indices in the `strategies` array to skip (i.e. not call the 'withdraw' function on). This input exists so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function, then the malicious strategy can be skipped (with the shares in effect "burned"), while the non-malicious strategies are still called as normal. |
-
-### setWithdrawalDelayBlocks
-
-```solidity
-function setWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) external
-```
-
-Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _withdrawalDelayBlocks | uint256 | new value of `withdrawalDelayBlocks`. |
-
-### setStrategyWhitelister
-
-```solidity
-function setStrategyWhitelister(address newStrategyWhitelister) external
-```
-
-Owner-only function to change the `strategyWhitelister` address.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newStrategyWhitelister | address | new address for the `strategyWhitelister`. |
-
-### addStrategiesToDepositWhitelist
-
-```solidity
-function addStrategiesToDepositWhitelist(contract IStrategy[] strategiesToWhitelist) external
-```
-
-Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategiesToWhitelist | contract IStrategy[] | Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already) |
-
-### removeStrategiesFromDepositWhitelist
-
-```solidity
-function removeStrategiesFromDepositWhitelist(contract IStrategy[] strategiesToRemoveFromWhitelist) external
-```
-
-Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategiesToRemoveFromWhitelist | contract IStrategy[] | Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it) |
-
-### _addShares
-
-```solidity
-function _addShares(address depositor, contract IStrategy strategy, uint256 shares) internal
-```
-
-This function adds `shares` for a given `strategy` to the `depositor` and runs through the necessary update logic.
-
-_In particular, this function calls `delegation.increaseDelegatedShares(depositor, strategy, shares)` to ensure that all
-delegated shares are tracked, increases the stored share amount in `stakerStrategyShares[depositor][strategy]`, and adds `strategy`
-to the `depositor`'s list of strategies, if it is not in the list already._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | The address to add shares to |
-| strategy | contract IStrategy | The Strategy in which the `depositor` is receiving shares |
-| shares | uint256 | The amount of shares to grant to the `depositor` |
-
-### _depositIntoStrategy
-
-```solidity
-function _depositIntoStrategy(address depositor, contract IStrategy strategy, contract IERC20 token, uint256 amount) internal returns (uint256 shares)
-```
-
-Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
-`strategy`, with the resulting shares credited to `depositor`.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | The address that will be credited with the new shares. |
-| strategy | contract IStrategy | The Strategy contract to deposit into. |
-| token | contract IERC20 | The ERC20 token to deposit. |
-| amount | uint256 | The amount of `token` to deposit. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| shares | uint256 | The amount of *new* shares in `strategy` that have been credited to the `depositor`. |
-
-### _removeShares
-
-```solidity
-function _removeShares(address depositor, uint256 strategyIndex, contract IStrategy strategy, uint256 shareAmount) internal returns (bool)
-```
-
-Decreases the shares that `depositor` holds in `strategy` by `shareAmount`.
-
-_If the amount of shares represents all of the depositor`s shares in said strategy,
-then the strategy is removed from stakerStrategyList[depositor] and 'true' is returned. Otherwise 'false' is returned._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | The address to decrement shares from |
-| strategyIndex | uint256 | The `strategyIndex` input for the internal `_removeStrategyFromStakerStrategyList`. Used only in the case that the removal of the depositor's shares results in them having zero remaining shares in the `strategy` |
-| strategy | contract IStrategy | The strategy for which the `depositor`'s shares are being decremented |
-| shareAmount | uint256 | The amount of shares to decrement |
-
-### _removeStrategyFromStakerStrategyList
-
-```solidity
-function _removeStrategyFromStakerStrategyList(address depositor, uint256 strategyIndex, contract IStrategy strategy) internal
-```
-
-Removes `strategy` from `depositor`'s dynamic array of strategies, i.e. from `stakerStrategyList[depositor]`
-
-_the provided `strategyIndex` input is optimistically used to find the strategy quickly in the list. If the specified
-index is incorrect, then we revert to a brute-force search._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | The user whose array will have an entry removed |
-| strategyIndex | uint256 | Preferably the index of `strategy` in `stakerStrategyList[depositor]`. If the input is incorrect, then a brute-force fallback routine will be used to find the correct input |
-| strategy | contract IStrategy | The Strategy to remove from `stakerStrategyList[depositor]` |
-
-### _completeQueuedWithdrawal
-
-```solidity
-function _completeQueuedWithdrawal(struct IStrategyManager.QueuedWithdrawal queuedWithdrawal, contract IERC20[] tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) internal
-```
-
-Internal function for completing the given `queuedWithdrawal`.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| queuedWithdrawal | struct IStrategyManager.QueuedWithdrawal | The QueuedWithdrawal to complete |
-| tokens | contract IERC20[] | The ERC20 tokens to provide as inputs to `Strategy.withdraw`. Only relevant if `receiveAsTokens = true` |
-| middlewareTimesIndex | uint256 | Passed on as an input to the `slasher.canWithdraw` function, to ensure the withdrawal is completable. |
-| receiveAsTokens | bool | If marked 'true', then calls will be passed on to the `Strategy.withdraw` function for each strategy. If marked 'false', then the shares will simply be internally transferred to the `msg.sender`. |
-
-### _undelegate
-
-```solidity
-function _undelegate(address depositor) internal
-```
-
-If the `depositor` has no existing shares, then they can `undelegate` themselves.
-This allows people a "hard reset" in their relationship with EigenLayer after withdrawing all of their stake.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | The address to undelegate. Passed on as an input to the `delegation.undelegate` function. |
-
-### _withdrawBeaconChainETH
-
-```solidity
-function _withdrawBeaconChainETH(address staker, address recipient, uint256 amount) internal
-```
-
-### _setWithdrawalDelayBlocks
-
-```solidity
-function _setWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) internal
-```
-
-internal function for changing the value of `withdrawalDelayBlocks`. Also performs sanity check and emits an event.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _withdrawalDelayBlocks | uint256 | The new value for `withdrawalDelayBlocks` to take. |
-
-### _setStrategyWhitelister
-
-```solidity
-function _setStrategyWhitelister(address newStrategyWhitelister) internal
-```
-
-Internal function for modifying the `strategyWhitelister`. Used inside of the `setStrategyWhitelister` and `initialize` functions.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newStrategyWhitelister | address | The new address for the `strategyWhitelister` to take. |
-
-### getDeposits
-
-```solidity
-function getDeposits(address depositor) external view returns (contract IStrategy[], uint256[])
-```
-
-Get all details on the depositor's deposits and corresponding shares
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | The staker of interest, whose deposits this function will fetch |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | contract IStrategy[] | (depositor's strategies, shares in these strategies) |
-| [1] | uint256[] | |
-
-### stakerStrategyListLength
-
-```solidity
-function stakerStrategyListLength(address staker) external view returns (uint256)
-```
-
-Simple getter function that returns `stakerStrategyList[staker].length`.
-
-### calculateWithdrawalRoot
-
-```solidity
-function calculateWithdrawalRoot(struct IStrategyManager.QueuedWithdrawal queuedWithdrawal) public pure returns (bytes32)
-```
-
-Returns the keccak256 hash of `queuedWithdrawal`.
-
diff --git a/docs/docgen/core/StrategyManagerStorage.md b/docs/docgen/core/StrategyManagerStorage.md
deleted file mode 100644
index 10b7dfc9df..0000000000
--- a/docs/docgen/core/StrategyManagerStorage.md
+++ /dev/null
@@ -1,160 +0,0 @@
-# Solidity API
-
-## StrategyManagerStorage
-
-This storage contract is separate from the logic to simplify the upgrade process.
-
-### DOMAIN_TYPEHASH
-
-```solidity
-bytes32 DOMAIN_TYPEHASH
-```
-
-The EIP-712 typehash for the contract's domain
-
-### DEPOSIT_TYPEHASH
-
-```solidity
-bytes32 DEPOSIT_TYPEHASH
-```
-
-The EIP-712 typehash for the deposit struct used by the contract
-
-### DOMAIN_SEPARATOR
-
-```solidity
-bytes32 DOMAIN_SEPARATOR
-```
-
-EIP-712 Domain separator
-
-### nonces
-
-```solidity
-mapping(address => uint256) nonces
-```
-
-### MAX_STAKER_STRATEGY_LIST_LENGTH
-
-```solidity
-uint8 MAX_STAKER_STRATEGY_LIST_LENGTH
-```
-
-### delegation
-
-```solidity
-contract IDelegationManager delegation
-```
-
-Returns the single, central Delegation contract of EigenLayer
-
-### eigenPodManager
-
-```solidity
-contract IEigenPodManager eigenPodManager
-```
-
-### slasher
-
-```solidity
-contract ISlasher slasher
-```
-
-Returns the single, central Slasher contract of EigenLayer
-
-### strategyWhitelister
-
-```solidity
-address strategyWhitelister
-```
-
-Permissioned role, which can be changed by the contract owner. Has the ability to edit the strategy whitelist
-
-### withdrawalDelayBlocks
-
-```solidity
-uint256 withdrawalDelayBlocks
-```
-
-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).
-
-_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._
-
-### MAX_WITHDRAWAL_DELAY_BLOCKS
-
-```solidity
-uint256 MAX_WITHDRAWAL_DELAY_BLOCKS
-```
-
-### stakerStrategyShares
-
-```solidity
-mapping(address => mapping(contract IStrategy => uint256)) stakerStrategyShares
-```
-
-Mapping: staker => Strategy => number of shares which they currently hold
-
-### stakerStrategyList
-
-```solidity
-mapping(address => contract IStrategy[]) stakerStrategyList
-```
-
-Mapping: staker => array of strategies in which they have nonzero shares
-
-### withdrawalRootPending
-
-```solidity
-mapping(bytes32 => bool) withdrawalRootPending
-```
-
-Mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending
-
-### numWithdrawalsQueued
-
-```solidity
-mapping(address => uint256) numWithdrawalsQueued
-```
-
-Mapping: staker => cumulative number of queued withdrawals they have ever initiated. only increments (doesn't decrement)
-
-### strategyIsWhitelistedForDeposit
-
-```solidity
-mapping(contract IStrategy => bool) strategyIsWhitelistedForDeposit
-```
-
-Mapping: strategy => whether or not stakers are allowed to deposit into it
-
-### beaconChainETHSharesToDecrementOnWithdrawal
-
-```solidity
-mapping(address => uint256) beaconChainETHSharesToDecrementOnWithdrawal
-```
-
-### beaconChainETHStrategy
-
-```solidity
-contract IStrategy beaconChainETHStrategy
-```
-
-returns the enshrined, virtual 'beaconChainETH' Strategy
-
-### constructor
-
-```solidity
-constructor(contract IDelegationManager _delegation, contract IEigenPodManager _eigenPodManager, contract ISlasher _slasher) internal
-```
-
-### __gap
-
-```solidity
-uint256[40] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/interfaces/IBLSPublicKeyCompendium.md b/docs/docgen/interfaces/IBLSPublicKeyCompendium.md
deleted file mode 100644
index 2c365a74d2..0000000000
--- a/docs/docgen/interfaces/IBLSPublicKeyCompendium.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Solidity API
-
-## IBLSPublicKeyCompendium
-
-### operatorToPubkeyHash
-
-```solidity
-function operatorToPubkeyHash(address operator) external view returns (bytes32)
-```
-
-mapping from operator address to pubkey hash.
-Returns *zero* if the `operator` has never registered, and otherwise returns the hash of the public key of the operator.
-
-### pubkeyHashToOperator
-
-```solidity
-function pubkeyHashToOperator(bytes32 pubkeyHash) external view returns (address)
-```
-
-mapping from pubkey hash to operator address.
-Returns *zero* if no operator has ever registered the public key corresponding to `pubkeyHash`,
-and otherwise returns the (unique) registered operator who owns the BLS public key that is the preimage of `pubkeyHash`.
-
-### registerBLSPublicKey
-
-```solidity
-function registerBLSPublicKey(uint256 s, struct BN254.G1Point rPoint, struct BN254.G1Point pubkeyG1, struct BN254.G2Point pubkeyG2) external
-```
-
-Called by an operator to register themselves as the owner of a BLS public key and reveal their G1 and G2 public key.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| s | uint256 | is the field element of the operator's Schnorr signature |
-| rPoint | struct BN254.G1Point | is the group element of the operator's Schnorr signature |
-| pubkeyG1 | struct BN254.G1Point | is the the G1 pubkey of the operator |
-| pubkeyG2 | struct BN254.G2Point | is the G2 with the same private key as the pubkeyG1 |
-
diff --git a/docs/docgen/interfaces/IBLSRegistry.md b/docs/docgen/interfaces/IBLSRegistry.md
deleted file mode 100644
index 8c78a3731d..0000000000
--- a/docs/docgen/interfaces/IBLSRegistry.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Solidity API
-
-## IBLSRegistry
-
-Adds BLS-specific functions to the base interface.
-
-### ApkUpdate
-
-```solidity
-struct ApkUpdate {
- bytes32 apkHash;
- uint32 blockNumber;
-}
-```
-
-### getCorrectApkHash
-
-```solidity
-function getCorrectApkHash(uint256 index, uint32 blockNumber) external returns (bytes32)
-```
-
-get hash of a historical aggregated public key corresponding to a given index;
-called by checkSignatures in BLSSignatureChecker.sol.
-
-### apkUpdates
-
-```solidity
-function apkUpdates(uint256 index) external view returns (struct IBLSRegistry.ApkUpdate)
-```
-
-returns the `ApkUpdate` struct at `index` in the list of APK updates
-
-### apkHashes
-
-```solidity
-function apkHashes(uint256 index) external view returns (bytes32)
-```
-
-returns the APK hash that resulted from the `index`th APK update
-
-### apkUpdateBlockNumbers
-
-```solidity
-function apkUpdateBlockNumbers(uint256 index) external view returns (uint32)
-```
-
-returns the block number at which the `index`th APK update occurred
-
-### operatorWhitelister
-
-```solidity
-function operatorWhitelister() external view returns (address)
-```
-
-### operatorWhitelistEnabled
-
-```solidity
-function operatorWhitelistEnabled() external view returns (bool)
-```
-
-### whitelisted
-
-```solidity
-function whitelisted(address) external view returns (bool)
-```
-
-### setOperatorWhitelistStatus
-
-```solidity
-function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external
-```
-
-### addToOperatorWhitelist
-
-```solidity
-function addToOperatorWhitelist(address[]) external
-```
-
-### removeFromWhitelist
-
-```solidity
-function removeFromWhitelist(address[] operators) external
-```
-
diff --git a/docs/docgen/interfaces/IBeaconChainOracle.md b/docs/docgen/interfaces/IBeaconChainOracle.md
deleted file mode 100644
index 5c5aa2aac7..0000000000
--- a/docs/docgen/interfaces/IBeaconChainOracle.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# Solidity API
-
-## IBeaconChainOracle
-
-### latestConfirmedOracleBlockNumber
-
-```solidity
-function latestConfirmedOracleBlockNumber() external view returns (uint64)
-```
-
-Largest blockNumber that has been confirmed by the oracle.
-
-### beaconStateRootAtBlockNumber
-
-```solidity
-function beaconStateRootAtBlockNumber(uint64 blockNumber) external view returns (bytes32)
-```
-
-Mapping: Beacon Chain blockNumber => the Beacon Chain state root at the specified blockNumber.
-
-_This will return `bytes32(0)` if the state root at the specified blockNumber is not yet confirmed._
-
-### isOracleSigner
-
-```solidity
-function isOracleSigner(address _oracleSigner) external view returns (bool)
-```
-
-Mapping: address => whether or not the address is in the set of oracle signers.
-
-### hasVoted
-
-```solidity
-function hasVoted(uint64 blockNumber, address oracleSigner) external view returns (bool)
-```
-
-Mapping: Beacon Chain blockNumber => oracle signer address => whether or not the oracle signer has voted on the state root at the blockNumber.
-
-### stateRootVotes
-
-```solidity
-function stateRootVotes(uint64 blockNumber, bytes32 stateRoot) external view returns (uint256)
-```
-
-Mapping: Beacon Chain blockNumber => state root => total number of oracle signer votes for the state root at the blockNumber.
-
-### totalOracleSigners
-
-```solidity
-function totalOracleSigners() external view returns (uint256)
-```
-
-Total number of members of the set of oracle signers.
-
-### threshold
-
-```solidity
-function threshold() external view returns (uint256)
-```
-
-Number of oracle signers that must vote for a state root in order for the state root to be confirmed.
-Adjustable by this contract's owner through use of the `setThreshold` function.
-
-_We note that there is an edge case -- when the threshold is adjusted downward, if a state root already has enough votes to meet the *new* threshold,
-the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations._
-
-### setThreshold
-
-```solidity
-function setThreshold(uint256 _threshold) external
-```
-
-Owner-only function used to modify the value of the `threshold` variable.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _threshold | uint256 | Desired new value for the `threshold` variable. Function will revert if this is set to zero. |
-
-### addOracleSigners
-
-```solidity
-function addOracleSigners(address[] _oracleSigners) external
-```
-
-Owner-only function used to add a signer to the set of oracle signers.
-
-_Function will have no effect on the i-th input address if `_oracleSigners[i]`is already in the set of oracle signers._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _oracleSigners | address[] | Array of address to be added to the set. |
-
-### removeOracleSigners
-
-```solidity
-function removeOracleSigners(address[] _oracleSigners) external
-```
-
-Owner-only function used to remove a signer from the set of oracle signers.
-
-_Function will have no effect on the i-th input address if `_oracleSigners[i]`is already not in the set of oracle signers._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _oracleSigners | address[] | Array of address to be removed from the set. |
-
-### voteForBeaconChainStateRoot
-
-```solidity
-function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external
-```
-
-Called by a member of the set of oracle signers to assert that the Beacon Chain state root is `stateRoot` at `blockNumber`.
-
-_The state root will be finalized once the total number of votes *for this exact state root at this exact blockNumber* meets the `threshold` value._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| blockNumber | uint64 | The Beacon Chain blockNumber of interest. |
-| stateRoot | bytes32 | The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`. |
-
diff --git a/docs/docgen/interfaces/IDelayedService.md b/docs/docgen/interfaces/IDelayedService.md
deleted file mode 100644
index 07bbe55527..0000000000
--- a/docs/docgen/interfaces/IDelayedService.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Solidity API
-
-## IDelayedService
-
-Specifically, this interface is designed for services that consult stake amounts up to `BLOCK_STALE_MEASURE`
-blocks in the past. This may be necessary due to, e.g., network processing & communication delays, or to avoid race conditions
-that could be present with coordinating aggregate operator signatures while service operators are registering & de-registering.
-
-_To clarify edge cases, the middleware can look `BLOCK_STALE_MEASURE` blocks into the past, i.e. it may trust stakes from the interval
-[block.number - BLOCK_STALE_MEASURE, block.number] (specifically, *inclusive* of the block that is `BLOCK_STALE_MEASURE` before the current one)_
-
-### BLOCK_STALE_MEASURE
-
-```solidity
-function BLOCK_STALE_MEASURE() external view returns (uint32)
-```
-
-The maximum amount of blocks in the past that the service will consider stake amounts to still be 'valid'.
-
diff --git a/docs/docgen/interfaces/IDelayedWithdrawalRouter.md b/docs/docgen/interfaces/IDelayedWithdrawalRouter.md
deleted file mode 100644
index 31708eb160..0000000000
--- a/docs/docgen/interfaces/IDelayedWithdrawalRouter.md
+++ /dev/null
@@ -1,118 +0,0 @@
-# Solidity API
-
-## IDelayedWithdrawalRouter
-
-### DelayedWithdrawal
-
-```solidity
-struct DelayedWithdrawal {
- uint224 amount;
- uint32 blockCreated;
-}
-```
-
-### UserDelayedWithdrawals
-
-```solidity
-struct UserDelayedWithdrawals {
- uint256 delayedWithdrawalsCompleted;
- struct IDelayedWithdrawalRouter.DelayedWithdrawal[] delayedWithdrawals;
-}
-```
-
-### createDelayedWithdrawal
-
-```solidity
-function createDelayedWithdrawal(address podOwner, address recipient) external payable
-```
-
-Creates an delayed withdrawal for `msg.value` to the `recipient`.
-
-_Only callable by the `podOwner`'s EigenPod contract._
-
-### claimDelayedWithdrawals
-
-```solidity
-function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfWithdrawalsToClaim) external
-```
-
-Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| recipient | address | The address to claim delayedWithdrawals for. |
-| maxNumberOfWithdrawalsToClaim | uint256 | Used to limit the maximum number of withdrawals to loop through claiming. |
-
-### claimDelayedWithdrawals
-
-```solidity
-function claimDelayedWithdrawals(uint256 maxNumberOfWithdrawalsToClaim) external
-```
-
-Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| maxNumberOfWithdrawalsToClaim | uint256 | Used to limit the maximum number of withdrawals to loop through claiming. |
-
-### setWithdrawalDelayBlocks
-
-```solidity
-function setWithdrawalDelayBlocks(uint256 newValue) external
-```
-
-Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
-
-### userWithdrawals
-
-```solidity
-function userWithdrawals(address user) external view returns (struct IDelayedWithdrawalRouter.UserDelayedWithdrawals)
-```
-
-Getter function for the mapping `_userWithdrawals`
-
-### claimableUserDelayedWithdrawals
-
-```solidity
-function claimableUserDelayedWithdrawals(address user) external view returns (struct IDelayedWithdrawalRouter.DelayedWithdrawal[])
-```
-
-Getter function to get all delayedWithdrawals that are currently claimable by the `user`
-
-### userDelayedWithdrawalByIndex
-
-```solidity
-function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (struct IDelayedWithdrawalRouter.DelayedWithdrawal)
-```
-
-Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
-
-### userWithdrawalsLength
-
-```solidity
-function userWithdrawalsLength(address user) external view returns (uint256)
-```
-
-Getter function for fetching the length of the delayedWithdrawals array of a specific user
-
-### canClaimDelayedWithdrawal
-
-```solidity
-function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool)
-```
-
-Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable
-
-### withdrawalDelayBlocks
-
-```solidity
-function withdrawalDelayBlocks() external view returns (uint256)
-```
-
-Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner,
-up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
-
diff --git a/docs/docgen/interfaces/IDelegationManager.md b/docs/docgen/interfaces/IDelegationManager.md
deleted file mode 100644
index 4c031f801d..0000000000
--- a/docs/docgen/interfaces/IDelegationManager.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# Solidity API
-
-## IDelegationManager
-
-This is the contract for delegation in EigenLayer. The main functionalities of this contract are
-- enabling anyone to register as an operator in EigenLayer
-- allowing new operators to provide a DelegationTerms-type contract, which may mediate their interactions with stakers who delegate to them
-- enabling any staker to delegate its stake to the operator of its choice
-- enabling a staker to undelegate its assets from an operator (performed as part of the withdrawal process, initiated through the StrategyManager)
-
-### registerAsOperator
-
-```solidity
-function registerAsOperator(contract IDelegationTerms dt) external
-```
-
-This will be called by an operator to register itself as an operator that stakers can choose to delegate to.
-
-_An operator can set `dt` equal to their own address (or another EOA address), in the event that they want to split payments
-in a more 'trustful' manner.
-In the present design, once set, there is no way for an operator to ever modify the address of their DelegationTerms contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| dt | contract IDelegationTerms | is the `DelegationTerms` contract that the operator has for those who delegate to them. |
-
-### delegateTo
-
-```solidity
-function delegateTo(address operator) external
-```
-
-@notice This will be called by a staker to delegate its assets to some operator.
- @param operator is the operator to whom staker (msg.sender) is delegating its assets
-
-### delegateToBySignature
-
-```solidity
-function delegateToBySignature(address staker, address operator, uint256 expiry, bytes signature) external
-```
-
-Delegates from `staker` to `operator`.
-
-_requires that:
-1) if `staker` is an EOA, then `signature` is valid ECDSA signature from `staker`, indicating their intention for this action
-2) if `staker` is a contract, then `signature` must will be checked according to EIP-1271_
-
-### undelegate
-
-```solidity
-function undelegate(address staker) external
-```
-
-Undelegates `staker` from the operator who they are delegated to.
-Callable only by the StrategyManager
-
-_Should only ever be called in the event that the `staker` has no active deposits in EigenLayer._
-
-### delegatedTo
-
-```solidity
-function delegatedTo(address staker) external view returns (address)
-```
-
-returns the address of the operator that `staker` is delegated to.
-
-### delegationTerms
-
-```solidity
-function delegationTerms(address operator) external view returns (contract IDelegationTerms)
-```
-
-returns the DelegationTerms of the `operator`, which may mediate their interactions with stakers who delegate to them.
-
-### operatorShares
-
-```solidity
-function operatorShares(address operator, contract IStrategy strategy) external view returns (uint256)
-```
-
-returns the total number of shares in `strategy` that are delegated to `operator`.
-
-### increaseDelegatedShares
-
-```solidity
-function increaseDelegatedShares(address staker, contract IStrategy strategy, uint256 shares) external
-```
-
-Increases the `staker`'s delegated shares in `strategy` by `shares, typically called when the staker has further deposits into EigenLayer
-
-_Callable only by the StrategyManager_
-
-### decreaseDelegatedShares
-
-```solidity
-function decreaseDelegatedShares(address staker, contract IStrategy[] strategies, uint256[] shares) external
-```
-
-Decreases the `staker`'s delegated shares in each entry of `strategies` by its respective `shares[i]`, typically called when the staker withdraws from EigenLayer
-
-_Callable only by the StrategyManager_
-
-### isDelegated
-
-```solidity
-function isDelegated(address staker) external view returns (bool)
-```
-
-Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
-
-### isNotDelegated
-
-```solidity
-function isNotDelegated(address staker) external view returns (bool)
-```
-
-Returns 'true' if `staker` is *not* actively delegated, and 'false' otherwise.
-
-### isOperator
-
-```solidity
-function isOperator(address operator) external view returns (bool)
-```
-
-Returns if an operator can be delegated to, i.e. it has called `registerAsOperator`.
-
diff --git a/docs/docgen/interfaces/IDelegationTerms.md b/docs/docgen/interfaces/IDelegationTerms.md
deleted file mode 100644
index b0f207b3ec..0000000000
--- a/docs/docgen/interfaces/IDelegationTerms.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Solidity API
-
-## IDelegationTerms
-
-The gas budget provided to this contract in calls from EigenLayer contracts is limited.
-
-### payForService
-
-```solidity
-function payForService(contract IERC20 token, uint256 amount) external payable
-```
-
-### onDelegationWithdrawn
-
-```solidity
-function onDelegationWithdrawn(address delegator, contract IStrategy[] stakerStrategyList, uint256[] stakerShares) external returns (bytes)
-```
-
-### onDelegationReceived
-
-```solidity
-function onDelegationReceived(address delegator, contract IStrategy[] stakerStrategyList, uint256[] stakerShares) external returns (bytes)
-```
-
diff --git a/docs/docgen/interfaces/IETHPOSDeposit.md b/docs/docgen/interfaces/IETHPOSDeposit.md
deleted file mode 100644
index b7fbbcfdea..0000000000
--- a/docs/docgen/interfaces/IETHPOSDeposit.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Solidity API
-
-## IETHPOSDeposit
-
-This is the Ethereum 2.0 deposit contract interface.
-For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
-
-### DepositEvent
-
-```solidity
-event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index)
-```
-
-A processed deposit event.
-
-### deposit
-
-```solidity
-function deposit(bytes pubkey, bytes withdrawal_credentials, bytes signature, bytes32 deposit_data_root) external payable
-```
-
-Submit a Phase 0 DepositData object.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| pubkey | bytes | A BLS12-381 public key. |
-| withdrawal_credentials | bytes | Commitment to a public key for withdrawals. |
-| signature | bytes | A BLS12-381 signature. |
-| deposit_data_root | bytes32 | The SHA-256 hash of the SSZ-encoded DepositData object. Used as a protection against malformed input. |
-
-### get_deposit_root
-
-```solidity
-function get_deposit_root() external view returns (bytes32)
-```
-
-Query the current deposit root hash.
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | The deposit root hash. |
-
-### get_deposit_count
-
-```solidity
-function get_deposit_count() external view returns (bytes)
-```
-
-Query the current deposit count.
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes | The deposit count encoded as a little endian 64-bit number. |
-
diff --git a/docs/docgen/interfaces/IEigenPod.md b/docs/docgen/interfaces/IEigenPod.md
deleted file mode 100644
index a1d497b0f8..0000000000
--- a/docs/docgen/interfaces/IEigenPod.md
+++ /dev/null
@@ -1,215 +0,0 @@
-# Solidity API
-
-## IEigenPod
-
-The main functionalities are:
-- creating new ETH validators with their withdrawal credentials pointed to this contract
-- proving from beacon chain state roots that withdrawal credentials are pointed to this contract
-- proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials
- pointed to this contract
-- updating aggregate balances in the EigenPodManager
-- withdrawing eth when withdrawals are initiated
-
-_Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
- to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts_
-
-### VALIDATOR_STATUS
-
-```solidity
-enum VALIDATOR_STATUS {
- INACTIVE,
- ACTIVE,
- OVERCOMMITTED,
- WITHDRAWN
-}
-```
-
-### PartialWithdrawalClaim
-
-```solidity
-struct PartialWithdrawalClaim {
- enum IEigenPod.PARTIAL_WITHDRAWAL_CLAIM_STATUS status;
- uint32 creationBlockNumber;
- uint32 fraudproofPeriodEndBlockNumber;
- uint64 partialWithdrawalAmountGwei;
-}
-```
-
-### PARTIAL_WITHDRAWAL_CLAIM_STATUS
-
-```solidity
-enum PARTIAL_WITHDRAWAL_CLAIM_STATUS {
- REDEEMED,
- PENDING,
- FAILED
-}
-```
-
-### REQUIRED_BALANCE_GWEI
-
-```solidity
-function REQUIRED_BALANCE_GWEI() external view returns (uint64)
-```
-
-The amount of eth, in gwei, that is restaked per validator
-
-### REQUIRED_BALANCE_WEI
-
-```solidity
-function REQUIRED_BALANCE_WEI() external view returns (uint256)
-```
-
-The amount of eth, in wei, that is restaked per validator
-
-### validatorStatus
-
-```solidity
-function validatorStatus(uint40 validatorIndex) external view returns (enum IEigenPod.VALIDATOR_STATUS)
-```
-
-this is a mapping of validator indices to a Validator struct containing pertinent info about the validator
-
-### restakedExecutionLayerGwei
-
-```solidity
-function restakedExecutionLayerGwei() external view returns (uint64)
-```
-
-the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
-
-### initialize
-
-```solidity
-function initialize(address owner) external
-```
-
-Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
-
-### stake
-
-```solidity
-function stake(bytes pubkey, bytes signature, bytes32 depositDataRoot) external payable
-```
-
-Called by EigenPodManager when the owner wants to create another ETH validator.
-
-### withdrawRestakedBeaconChainETH
-
-```solidity
-function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external
-```
-
-Transfers `amountWei` in ether from this contract to the specified `recipient` address
-Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
-
-_Called during withdrawal or slashing.
-Note that this function is marked as non-reentrant to prevent the recipient calling back into it_
-
-### eigenPodManager
-
-```solidity
-function eigenPodManager() external view returns (contract IEigenPodManager)
-```
-
-The single EigenPodManager for EigenLayer
-
-### podOwner
-
-```solidity
-function podOwner() external view returns (address)
-```
-
-The owner of this EigenPod
-
-### hasRestaked
-
-```solidity
-function hasRestaked() external view returns (bool)
-```
-
-an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
-
-### mostRecentWithdrawalBlockNumber
-
-```solidity
-function mostRecentWithdrawalBlockNumber() external view returns (uint64)
-```
-
-block number of the most recent withdrawal
-
-### provenPartialWithdrawal
-
-```solidity
-function provenPartialWithdrawal(uint40 validatorIndex, uint64 slot) external view returns (bool)
-```
-
-mapping that tracks proven partial withdrawals
-
-### verifyWithdrawalCredentialsAndBalance
-
-```solidity
-function verifyWithdrawalCredentialsAndBalance(uint64 oracleBlockNumber, uint40 validatorIndex, struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs proofs, bytes32[] validatorFields) external
-```
-
-This function verifies that the withdrawal credentials of the podOwner are pointed to
-this contract. It also verifies the current (not effective) balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
-root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| oracleBlockNumber | uint64 | is the Beacon Chain blockNumber whose state root the `proof` will be proven against. |
-| validatorIndex | uint40 | is the index of the validator being proven, refer to consensus specs |
-| proofs | struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs | is the bytes that prove the ETH validator's balance and withdrawal credentials against a beacon chain state root |
-| validatorFields | bytes32[] | are the fields of the "Validator Container", refer to consensus specs for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator |
-
-### verifyOvercommittedStake
-
-```solidity
-function verifyOvercommittedStake(uint40 validatorIndex, struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs proofs, bytes32[] validatorFields, uint256 beaconChainETHStrategyIndex, uint64 oracleBlockNumber) external
-```
-
-This function records an overcommitment of stake to EigenLayer on behalf of a certain ETH validator.
- If successful, the overcommitted balance is penalized (available for withdrawal whenever the pod's balance allows).
- The ETH validator's shares in the enshrined beaconChainETH strategy are also removed from the StrategyManager and undelegated.
-
-_For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| validatorIndex | uint40 | is the index of the validator being proven, refer to consensus specs |
-| proofs | struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs | is the proof of the validator's balance and validatorFields in the balance tree and the balanceRoot to prove for |
-| validatorFields | bytes32[] | are the fields of the "Validator Container", refer to consensus specs |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy for the pod owner for the callback to the StrategyManager in case it must be removed from the list of the podOwners strategies |
-| oracleBlockNumber | uint64 | The oracleBlockNumber whose state root the `proof` will be proven against. Must be within `VERIFY_OVERCOMMITTED_WINDOW_BLOCKS` of the current block. |
-
-### verifyAndProcessWithdrawal
-
-```solidity
-function verifyAndProcessWithdrawal(struct BeaconChainProofs.WithdrawalProofs withdrawalProofs, bytes validatorFieldsProof, bytes32[] validatorFields, bytes32[] withdrawalFields, uint256 beaconChainETHStrategyIndex, uint64 oracleBlockNumber) external
-```
-
-This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| withdrawalProofs | struct BeaconChainProofs.WithdrawalProofs | is the information needed to check the veracity of the block number and withdrawal being proven |
-| validatorFieldsProof | bytes | is the proof of the validator's fields in the validator tree |
-| validatorFields | bytes32[] | are the fields of the validator being proven |
-| withdrawalFields | bytes32[] | are the fields of the withdrawal being proven |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy for the pod owner for the callback to the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies |
-| oracleBlockNumber | uint64 | |
-
-### withdrawBeforeRestaking
-
-```solidity
-function withdrawBeforeRestaking() external
-```
-
-Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
-
diff --git a/docs/docgen/interfaces/IEigenPodManager.md b/docs/docgen/interfaces/IEigenPodManager.md
deleted file mode 100644
index d20647bdf9..0000000000
--- a/docs/docgen/interfaces/IEigenPodManager.md
+++ /dev/null
@@ -1,155 +0,0 @@
-# Solidity API
-
-## IEigenPodManager
-
-### createPod
-
-```solidity
-function createPod() external
-```
-
-Creates an EigenPod for the sender.
-
-_Function will revert if the `msg.sender` already has an EigenPod._
-
-### stake
-
-```solidity
-function stake(bytes pubkey, bytes signature, bytes32 depositDataRoot) external payable
-```
-
-Stakes for a new beacon chain validator on the sender's EigenPod.
-Also creates an EigenPod for the sender if they don't have one already.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| pubkey | bytes | The 48 bytes public key of the beacon chain validator. |
-| signature | bytes | The validator's signature of the deposit data. |
-| depositDataRoot | bytes32 | The root/hash of the deposit data for the validator's deposit. |
-
-### restakeBeaconChainETH
-
-```solidity
-function restakeBeaconChainETH(address podOwner, uint256 amount) external
-```
-
-Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod.
-
-_Callable only by the podOwner's EigenPod contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| podOwner | address | The owner of the pod whose balance must be deposited. |
-| amount | uint256 | The amount of ETH to 'deposit' (i.e. be credited to the podOwner). |
-
-### recordOvercommittedBeaconChainETH
-
-```solidity
-function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external
-```
-
-Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
- balance of a validator is lower than how much stake they have committed to EigenLayer
-
-_Callable only by the podOwner's EigenPod contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| podOwner | address | The owner of the pod whose balance must be removed. |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy for the pod owner for the callback to the StrategyManager in case it must be removed from the list of the podOwner's strategies |
-| amount | uint256 | The amount of ETH to remove. |
-
-### withdrawRestakedBeaconChainETH
-
-```solidity
-function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount) external
-```
-
-Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain.
-
-_Callable only by the StrategyManager contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| podOwner | address | The owner of the pod whose balance must be withdrawn. |
-| recipient | address | The recipient of the withdrawn ETH. |
-| amount | uint256 | The amount of ETH to withdraw. |
-
-### updateBeaconChainOracle
-
-```solidity
-function updateBeaconChainOracle(contract IBeaconChainOracle newBeaconChainOracle) external
-```
-
-Updates the oracle contract that provides the beacon chain state root
-
-_Callable only by the owner of this contract (i.e. governance)_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newBeaconChainOracle | contract IBeaconChainOracle | is the new oracle contract being pointed to |
-
-### ownerToPod
-
-```solidity
-function ownerToPod(address podOwner) external view returns (contract IEigenPod)
-```
-
-Returns the address of the `podOwner`'s EigenPod if it has been deployed.
-
-### getPod
-
-```solidity
-function getPod(address podOwner) external view returns (contract IEigenPod)
-```
-
-Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
-
-### beaconChainOracle
-
-```solidity
-function beaconChainOracle() external view returns (contract IBeaconChainOracle)
-```
-
-Oracle contract that provides updates to the beacon chain's state
-
-### getBeaconChainStateRoot
-
-```solidity
-function getBeaconChainStateRoot(uint64 blockNumber) external view returns (bytes32)
-```
-
-Returns the Beacon Chain state root at `blockNumber`. Reverts if the Beacon Chain state root at `blockNumber` has not yet been finalized.
-
-### strategyManager
-
-```solidity
-function strategyManager() external view returns (contract IStrategyManager)
-```
-
-EigenLayer's StrategyManager contract
-
-### slasher
-
-```solidity
-function slasher() external view returns (contract ISlasher)
-```
-
-EigenLayer's Slasher contract
-
-### hasPod
-
-```solidity
-function hasPod(address podOwner) external view returns (bool)
-```
-
diff --git a/docs/docgen/interfaces/IPausable.md b/docs/docgen/interfaces/IPausable.md
deleted file mode 100644
index 69619507fc..0000000000
--- a/docs/docgen/interfaces/IPausable.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Solidity API
-
-## IPausable
-
-Contracts that inherit from this contract may define their own `pause` and `unpause` (and/or related) functions.
-These functions should be permissioned as "onlyPauser" which defers to a `PauserRegistry` for determining access control.
-
-_Pausability is implemented using a uint256, which allows up to 256 different single bit-flags; each bit can potentially pause different functionality.
-Inspiration for this was taken from the NearBridge design here https://etherscan.io/address/0x3FEFc5A4B1c02f21cBc8D3613643ba0635b9a873#code.
-For the `pause` and `unpause` functions we've implemented, if you pause, you can only flip (any number of) switches to on/1 (aka "paused"), and if you unpause,
-you can only flip (any number of) switches to off/0 (aka "paused").
-If you want a pauseXYZ function that just flips a single bit / "pausing flag", it will:
-1) 'bit-wise and' (aka `&`) a flag with the current paused state (as a uint256)
-2) update the paused state to this new value
-We note as well that we have chosen to identify flags by their *bit index* as opposed to their numerical value, so, e.g. defining `DEPOSITS_PAUSED = 3`
-indicates specifically that if the *third bit* of `_paused` is flipped -- i.e. it is a '1' -- then deposits should be paused_
-
-### pauserRegistry
-
-```solidity
-function pauserRegistry() external view returns (contract IPauserRegistry)
-```
-
-Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
-
-### pause
-
-```solidity
-function pause(uint256 newPausedStatus) external
-```
-
-This function is used to pause an EigenLayer contract's functionality.
-It is permissioned to the `pauser` address, which is expected to be a low threshold multisig.
-
-_This function can only pause functionality, and thus cannot 'unflip' any bit in `_paused` from 1 to 0._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newPausedStatus | uint256 | represents the new value for `_paused` to take, which means it may flip several bits at once. |
-
-### pauseAll
-
-```solidity
-function pauseAll() external
-```
-
-Alias for `pause(type(uint256).max)`.
-
-### unpause
-
-```solidity
-function unpause(uint256 newPausedStatus) external
-```
-
-This function is used to unpause an EigenLayer contract's functionality.
-It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or governance contract.
-
-_This function can only unpause functionality, and thus cannot 'flip' any bit in `_paused` from 0 to 1._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newPausedStatus | uint256 | represents the new value for `_paused` to take, which means it may flip several bits at once. |
-
-### paused
-
-```solidity
-function paused() external view returns (uint256)
-```
-
-Returns the current paused status as a uint256.
-
-### paused
-
-```solidity
-function paused(uint8 index) external view returns (bool)
-```
-
-Returns 'true' if the `indexed`th bit of `_paused` is 1, and 'false' otherwise
-
diff --git a/docs/docgen/interfaces/IPauserRegistry.md b/docs/docgen/interfaces/IPauserRegistry.md
deleted file mode 100644
index 5a7002e32c..0000000000
--- a/docs/docgen/interfaces/IPauserRegistry.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Solidity API
-
-## IPauserRegistry
-
-### pauser
-
-```solidity
-function pauser() external view returns (address)
-```
-
-Unique address that holds the pauser role.
-
-### unpauser
-
-```solidity
-function unpauser() external view returns (address)
-```
-
-Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
-
diff --git a/docs/docgen/interfaces/IPaymentManager.md b/docs/docgen/interfaces/IPaymentManager.md
deleted file mode 100644
index df975704ed..0000000000
--- a/docs/docgen/interfaces/IPaymentManager.md
+++ /dev/null
@@ -1,268 +0,0 @@
-# Solidity API
-
-## IPaymentManager
-
-### DissectionType
-
-```solidity
-enum DissectionType {
- INVALID,
- FIRST_HALF,
- SECOND_HALF
-}
-```
-
-### PaymentStatus
-
-```solidity
-enum PaymentStatus {
- REDEEMED,
- COMMITTED,
- CHALLENGED
-}
-```
-
-### ChallengeStatus
-
-```solidity
-enum ChallengeStatus {
- RESOLVED,
- OPERATOR_TURN,
- CHALLENGER_TURN,
- OPERATOR_TURN_ONE_STEP,
- CHALLENGER_TURN_ONE_STEP
-}
-```
-
-### Payment
-
-```solidity
-struct Payment {
- uint32 fromTaskNumber;
- uint32 toTaskNumber;
- uint32 confirmAt;
- uint96 amount;
- enum IPaymentManager.PaymentStatus status;
- uint256 challengeAmount;
-}
-```
-
-### PaymentChallenge
-
-```solidity
-struct PaymentChallenge {
- address operator;
- address challenger;
- address serviceManager;
- uint32 fromTaskNumber;
- uint32 toTaskNumber;
- uint96 amount1;
- uint96 amount2;
- uint32 settleAt;
- enum IPaymentManager.ChallengeStatus status;
-}
-```
-
-### TotalStakes
-
-```solidity
-struct TotalStakes {
- uint256 signedStakeFirstQuorum;
- uint256 signedStakeSecondQuorum;
-}
-```
-
-### depositFutureFees
-
-```solidity
-function depositFutureFees(address depositFor, uint256 amount) external
-```
-
-deposit one-time fees by the `msg.sender` with this contract to pay for future tasks of this middleware
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositFor | address | could be the `msg.sender` themselves, or a different address for whom `msg.sender` is depositing these future fees |
-| amount | uint256 | is amount of futures fees being deposited |
-
-### setAllowance
-
-```solidity
-function setAllowance(address allowed, uint256 amount) external
-```
-
-Allows the `allowed` address to spend up to `amount` of the `msg.sender`'s funds that have been deposited in this contract
-
-### takeFee
-
-```solidity
-function takeFee(address initiator, address payer, uint256 feeAmount) external
-```
-
-Used for deducting the fees from the payer to the middleware
-
-### setPaymentChallengeAmount
-
-```solidity
-function setPaymentChallengeAmount(uint256 _paymentChallengeAmount) external
-```
-
-Modifies the `paymentChallengeAmount` amount.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _paymentChallengeAmount | uint256 | The new value for `paymentChallengeAmount` to take. |
-
-### commitPayment
-
-```solidity
-function commitPayment(uint32 toTaskNumber, uint96 amount) external
-```
-
-This is used by an operator to make a claim on the amount that they deserve for their service from their last payment until `toTaskNumber`
-
-_Once this payment is recorded, a fraud proof period commences during which a challenger can dispute the proposed payment._
-
-### redeemPayment
-
-```solidity
-function redeemPayment() external
-```
-
-Called by an operator to redeem a payment that they previously 'committed' to by calling `commitPayment`.
-
-_This function can only be called after the challenge window for the payment claim has completed._
-
-### initPaymentChallenge
-
-```solidity
-function initPaymentChallenge(address operator, uint96 amount1, uint96 amount2) external
-```
-
-This function is called by a fraud prover to challenge a payment, initiating an interactive-type fraudproof.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator against whose payment claim the fraudproof is being made |
-| amount1 | uint96 | is the reward amount the challenger in that round claims is for the first half of tasks |
-| amount2 | uint96 | is the reward amount the challenger in that round claims is for the second half of tasks |
-
-### performChallengeBisectionStep
-
-```solidity
-function performChallengeBisectionStep(address operator, bool secondHalf, uint96 amount1, uint96 amount2) external
-```
-
-Perform a single bisection step in an existing interactive payment challenge.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | The middleware operator who was challenged (used to look up challenge details) |
-| secondHalf | bool | If true, then the caller wishes to challenge the amount claimed as payment in the *second half* of the previous bisection step. If false then the *first half* is indicated instead. |
-| amount1 | uint96 | The amount that the caller asserts the operator is entitled to, for the first half *of the challenged half* of the previous bisection. |
-| amount2 | uint96 | The amount that the caller asserts the operator is entitled to, for the second half *of the challenged half* of the previous bisection. |
-
-### resolveChallenge
-
-```solidity
-function resolveChallenge(address operator) external
-```
-
-resolve an existing PaymentChallenge for an operator
-
-### paymentFraudproofInterval
-
-```solidity
-function paymentFraudproofInterval() external view returns (uint256)
-```
-
-Challenge window for submitting fraudproof in the case of an incorrect payment claim by a registered operator.
-
-### paymentChallengeAmount
-
-```solidity
-function paymentChallengeAmount() external view returns (uint256)
-```
-
-Specifies the payment that has to be made as a guarantee for fraudproof during payment challenges.
-
-### paymentToken
-
-```solidity
-function paymentToken() external view returns (contract IERC20)
-```
-
-the ERC20 token that will be used by the disperser to pay the service fees to middleware nodes.
-
-### paymentChallengeToken
-
-```solidity
-function paymentChallengeToken() external view returns (contract IERC20)
-```
-
-Token used for placing a guarantee on challenges & payment commits
-
-### getChallengeStatus
-
-```solidity
-function getChallengeStatus(address operator) external view returns (enum IPaymentManager.ChallengeStatus)
-```
-
-Returns the ChallengeStatus for the `operator`'s payment claim.
-
-### getAmount1
-
-```solidity
-function getAmount1(address operator) external view returns (uint96)
-```
-
-Returns the 'amount1' for the `operator`'s payment claim.
-
-### getAmount2
-
-```solidity
-function getAmount2(address operator) external view returns (uint96)
-```
-
-Returns the 'amount2' for the `operator`'s payment claim.
-
-### getToTaskNumber
-
-```solidity
-function getToTaskNumber(address operator) external view returns (uint48)
-```
-
-Returns the 'toTaskNumber' for the `operator`'s payment claim.
-
-### getFromTaskNumber
-
-```solidity
-function getFromTaskNumber(address operator) external view returns (uint48)
-```
-
-Returns the 'fromTaskNumber' for the `operator`'s payment claim.
-
-### getDiff
-
-```solidity
-function getDiff(address operator) external view returns (uint48)
-```
-
-Returns the task number difference for the `operator`'s payment claim.
-
-### getPaymentChallengeAmount
-
-```solidity
-function getPaymentChallengeAmount(address) external view returns (uint256)
-```
-
-Returns the active guarantee amount of the `operator` placed on their payment claim.
-
diff --git a/docs/docgen/interfaces/IQuorumRegistry.md b/docs/docgen/interfaces/IQuorumRegistry.md
deleted file mode 100644
index fd90f7ab12..0000000000
--- a/docs/docgen/interfaces/IQuorumRegistry.md
+++ /dev/null
@@ -1,220 +0,0 @@
-# Solidity API
-
-## IQuorumRegistry
-
-This contract does not currently support n-quorums where n >= 3.
-Note in particular the presence of only `firstQuorumStake` and `secondQuorumStake` in the `OperatorStake` struct.
-
-### Status
-
-```solidity
-enum Status {
- INACTIVE,
- ACTIVE
-}
-```
-
-### Operator
-
-```solidity
-struct Operator {
- bytes32 pubkeyHash;
- uint32 fromTaskNumber;
- enum IQuorumRegistry.Status status;
-}
-```
-
-### OperatorIndex
-
-```solidity
-struct OperatorIndex {
- uint32 toBlockNumber;
- uint32 index;
-}
-```
-
-### OperatorStake
-
-```solidity
-struct OperatorStake {
- uint32 updateBlockNumber;
- uint32 nextUpdateBlockNumber;
- uint96 firstQuorumStake;
- uint96 secondQuorumStake;
-}
-```
-
-### getLengthOfTotalStakeHistory
-
-```solidity
-function getLengthOfTotalStakeHistory() external view returns (uint256)
-```
-
-### getTotalStakeFromIndex
-
-```solidity
-function getTotalStakeFromIndex(uint256 index) external view returns (struct IQuorumRegistry.OperatorStake)
-```
-
-Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory`.
-
-_Function will revert in the event that `index` is out-of-bounds._
-
-### getOperatorPubkeyHash
-
-```solidity
-function getOperatorPubkeyHash(address operator) external view returns (bytes32)
-```
-
-Returns the stored pubkeyHash for the specified `operator`.
-
-### getFromTaskNumberForOperator
-
-```solidity
-function getFromTaskNumberForOperator(address operator) external view returns (uint32)
-```
-
-Returns task number from when `operator` has been registered.
-
-### getStakeFromPubkeyHashAndIndex
-
-```solidity
-function getStakeFromPubkeyHashAndIndex(bytes32 pubkeyHash, uint256 index) external view returns (struct IQuorumRegistry.OperatorStake)
-```
-
-Returns the stake weight corresponding to `pubkeyHash`, at the
-`index`-th entry in the `pubkeyHashToStakeHistory[pubkeyHash]` array.
-
-_Function will revert if `index` is out-of-bounds._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| pubkeyHash | bytes32 | Hash of the public key of the operator of interest. |
-| index | uint256 | Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash]`. |
-
-### checkOperatorActiveAtBlockNumber
-
-```solidity
-function checkOperatorActiveAtBlockNumber(address operator, uint256 blockNumber, uint256 stakeHistoryIndex) external view returns (bool)
-```
-
-Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
-
-_In order for this function to return 'true', the inputs must satisfy all of the following list:
-1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
-2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
-is must be strictly greater than `blockNumber`
-3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
-or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
-Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a
-bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator of interest |
-| blockNumber | uint256 | is the block number of interest |
-| stakeHistoryIndex | uint256 | specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up in `registry[operator].pubkeyHash` |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | 'true' if it is successfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise |
-
-### checkOperatorInactiveAtBlockNumber
-
-```solidity
-function checkOperatorInactiveAtBlockNumber(address operator, uint256 blockNumber, uint256 stakeHistoryIndex) external view returns (bool)
-```
-
-Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
-
-_In order for this function to return 'true', the inputs must satisfy all of the following list:
-1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
-2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
-is must be strictly greater than `blockNumber`
-3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
-or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
-Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a
-bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator of interest |
-| blockNumber | uint256 | is the block number of interest |
-| stakeHistoryIndex | uint256 | specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up in `registry[operator].pubkeyHash` |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | 'true' if it is successfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise |
-
-### getOperatorIndex
-
-```solidity
-function getOperatorIndex(address operator, uint32 blockNumber, uint32 index) external view returns (uint32)
-```
-
-Looks up the `operator`'s index in the dynamic array `operatorList` at the specified `blockNumber`.
-
-_Function will revert in the event that the specified `index` input does not identify the appropriate entry in the
-array `pubkeyHashToIndexHistory[pubkeyHash]` to pull the info from._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | |
-| blockNumber | uint32 | Is the desired block number at which we wish to query the operator's position in the `operatorList` array |
-| index | uint32 | Used to specify the entry within the dynamic array `pubkeyHashToIndexHistory[pubkeyHash]` to read data from, where `pubkeyHash` is looked up from `operator`'s registration info |
-
-### getTotalOperators
-
-```solidity
-function getTotalOperators(uint32 blockNumber, uint32 index) external view returns (uint32)
-```
-
-Looks up the number of total operators at the specified `blockNumber`.
-
-_This function will revert if the provided `index` is out of bounds._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| blockNumber | uint32 | |
-| index | uint32 | Input used to specify the entry within the dynamic array `totalOperatorsHistory` to read data from. |
-
-### numOperators
-
-```solidity
-function numOperators() external view returns (uint32)
-```
-
-Returns the current number of operators of this service.
-
-### operatorStakes
-
-```solidity
-function operatorStakes(address operator) external view returns (uint96, uint96)
-```
-
-Returns the most recent stake weights for the `operator`
-
-_Function returns weights of **0** in the event that the operator has no stake history_
-
-### totalStake
-
-```solidity
-function totalStake() external view returns (uint96, uint96)
-```
-
-Returns the stake amounts from the latest entry in `totalStakeHistory`.
-
diff --git a/docs/docgen/interfaces/IRegistry.md b/docs/docgen/interfaces/IRegistry.md
deleted file mode 100644
index 49a8a221ce..0000000000
--- a/docs/docgen/interfaces/IRegistry.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Solidity API
-
-## IRegistry
-
-Functions related to the registration process itself have been intentionally excluded
-because their function signatures may vary significantly.
-
-### isActiveOperator
-
-```solidity
-function isActiveOperator(address operator) external view returns (bool)
-```
-
-Returns 'true' if `operator` is registered as an active operator, and 'false' otherwise.
-
diff --git a/docs/docgen/interfaces/ISafe.md b/docs/docgen/interfaces/ISafe.md
deleted file mode 100644
index 24b5c8bbc8..0000000000
--- a/docs/docgen/interfaces/ISafe.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Solidity API
-
-## ISafe
-
-### Operation
-
-```solidity
-enum Operation {
- Call,
- DelegateCall
-}
-```
-
-### setup
-
-```solidity
-function setup(address[] _owners, uint256 _threshold, address to, bytes data, address fallbackHandler, address paymentToken, uint256 payment, address payable paymentReceiver) external
-```
-
-### execTransaction
-
-```solidity
-function execTransaction(address to, uint256 value, bytes data, enum ISafe.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes signatures) external payable returns (bytes)
-```
-
-### checkSignatures
-
-```solidity
-function checkSignatures(bytes32 dataHash, bytes signatures) external view
-```
-
-### approveHash
-
-```solidity
-function approveHash(bytes32 hashToApprove) external
-```
-
diff --git a/docs/docgen/interfaces/IServiceManager.md b/docs/docgen/interfaces/IServiceManager.md
deleted file mode 100644
index daa191bdea..0000000000
--- a/docs/docgen/interfaces/IServiceManager.md
+++ /dev/null
@@ -1,58 +0,0 @@
-# Solidity API
-
-## IServiceManager
-
-### taskNumber
-
-```solidity
-function taskNumber() external view returns (uint32)
-```
-
-Returns the current 'taskNumber' for the middleware
-
-### freezeOperator
-
-```solidity
-function freezeOperator(address operator) external
-```
-
-Permissioned function that causes the ServiceManager to freeze the operator on EigenLayer, through a call to the Slasher contract
-
-### recordFirstStakeUpdate
-
-```solidity
-function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external
-```
-
-Permissioned function to have the ServiceManager forward a call to the slasher, recording an initial stake update (on operator registration)
-
-### recordStakeUpdate
-
-```solidity
-function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external
-```
-
-Permissioned function to have the ServiceManager forward a call to the slasher, recording a stake update
-
-### recordLastStakeUpdateAndRevokeSlashingAbility
-
-```solidity
-function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external
-```
-
-Permissioned function to have the ServiceManager forward a call to the slasher, recording a final stake update (on operator deregistration)
-
-### latestServeUntilBlock
-
-```solidity
-function latestServeUntilBlock() external view returns (uint32)
-```
-
-Returns the latest block until which operators must serve.
-
-### owner
-
-```solidity
-function owner() external view returns (address)
-```
-
diff --git a/docs/docgen/interfaces/ISlasher.md b/docs/docgen/interfaces/ISlasher.md
deleted file mode 100644
index b01db6c952..0000000000
--- a/docs/docgen/interfaces/ISlasher.md
+++ /dev/null
@@ -1,249 +0,0 @@
-# Solidity API
-
-## ISlasher
-
-See the `Slasher` contract itself for implementation details.
-
-### MiddlewareTimes
-
-```solidity
-struct MiddlewareTimes {
- uint32 stalestUpdateBlock;
- uint32 latestServeUntilBlock;
-}
-```
-
-### MiddlewareDetails
-
-```solidity
-struct MiddlewareDetails {
- uint32 contractCanSlashOperatorUntilBlock;
- uint32 latestUpdateBlock;
-}
-```
-
-### optIntoSlashing
-
-```solidity
-function optIntoSlashing(address contractAddress) external
-```
-
-Gives the `contractAddress` permission to slash the funds of the caller.
-
-_Typically, this function must be called prior to registering for a middleware._
-
-### freezeOperator
-
-```solidity
-function freezeOperator(address toBeFrozen) external
-```
-
-Used for 'slashing' a certain operator.
-
-_Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop.
-The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| toBeFrozen | address | The operator to be frozen. |
-
-### resetFrozenStatus
-
-```solidity
-function resetFrozenStatus(address[] frozenAddresses) external
-```
-
-Removes the 'frozen' status from each of the `frozenAddresses`
-
-_Callable only by the contract owner (i.e. governance)._
-
-### recordFirstStakeUpdate
-
-```solidity
-function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external
-```
-
-this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
- is slashable until serveUntil
-
-_adds the middleware's slashing contract to the operator's linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the operator whose stake update is being recorded |
-| serveUntilBlock | uint32 | the block until which the operator's stake at the current block is slashable |
-
-### recordStakeUpdate
-
-```solidity
-function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 insertAfter) external
-```
-
-this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals)
- to make sure the operator's stake at updateBlock is slashable until serveUntil
-
-_insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
- but it is anticipated to be rare and not detrimental._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the operator whose stake update is being recorded |
-| updateBlock | uint32 | the block for which the stake update is being recorded |
-| serveUntilBlock | uint32 | the block until which the operator's stake at updateBlock is slashable |
-| insertAfter | uint256 | the element of the operators linked list that the currently updating middleware should be inserted after |
-
-### recordLastStakeUpdateAndRevokeSlashingAbility
-
-```solidity
-function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external
-```
-
-this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
- is slashable until serveUntil
-
-_removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to
-slash `operator` once `serveUntil` is reached_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | the operator whose stake update is being recorded |
-| serveUntilBlock | uint32 | the block until which the operator's stake at the current block is slashable |
-
-### isFrozen
-
-```solidity
-function isFrozen(address staker) external view returns (bool)
-```
-
-Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
-slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
-and the staker's status is reset (to 'unfrozen').
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| staker | address | The staker of interest. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated to an operator who has their status set to frozen. Otherwise returns 'false'. |
-
-### canSlash
-
-```solidity
-function canSlash(address toBeSlashed, address slashingContract) external view returns (bool)
-```
-
-Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`.
-
-### contractCanSlashOperatorUntilBlock
-
-```solidity
-function contractCanSlashOperatorUntilBlock(address operator, address serviceContract) external view returns (uint32)
-```
-
-Returns the block until which `serviceContract` is allowed to slash the `operator`.
-
-### latestUpdateBlock
-
-```solidity
-function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32)
-```
-
-Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
-
-### getCorrectValueForInsertAfter
-
-```solidity
-function getCorrectValueForInsertAfter(address operator, uint32 updateBlock) external view returns (uint256)
-```
-
-A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`.
-
-### canWithdraw
-
-```solidity
-function canWithdraw(address operator, uint32 withdrawalStartBlock, uint256 middlewareTimesIndex) external returns (bool)
-```
-
-Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used
-to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `operatorToMiddlewareTimes[operator]`). The specified
-struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
-This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event
-that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist.
-
-_The correct `middlewareTimesIndex` input should be computable off-chain._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator, this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartBlock`. |
-| withdrawalStartBlock | uint32 | The block number at which the withdrawal was initiated. |
-| middlewareTimesIndex | uint256 | Indicates an index in `operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw |
-
-### operatorToMiddlewareTimes
-
-```solidity
-function operatorToMiddlewareTimes(address operator, uint256 arrayIndex) external view returns (struct ISlasher.MiddlewareTimes)
-```
-
-operator =>
- [
- (
- the least recent update block of all of the middlewares it's serving/served,
- latest time that the stake bonded at that update needed to serve until
- )
- ]
-
-### middlewareTimesLength
-
-```solidity
-function middlewareTimesLength(address operator) external view returns (uint256)
-```
-
-Getter function for fetching `operatorToMiddlewareTimes[operator].length`
-
-### getMiddlewareTimesIndexBlock
-
-```solidity
-function getMiddlewareTimesIndexBlock(address operator, uint32 index) external view returns (uint32)
-```
-
-Getter function for fetching `operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
-
-### getMiddlewareTimesIndexServeUntilBlock
-
-```solidity
-function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32)
-```
-
-Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`.
-
-### operatorWhitelistedContractsLinkedListSize
-
-```solidity
-function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256)
-```
-
-Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
-
-### operatorWhitelistedContractsLinkedListEntry
-
-```solidity
-function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256)
-```
-
-Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
-
diff --git a/docs/docgen/interfaces/IStrategy.md b/docs/docgen/interfaces/IStrategy.md
deleted file mode 100644
index 7753401b9d..0000000000
--- a/docs/docgen/interfaces/IStrategy.md
+++ /dev/null
@@ -1,183 +0,0 @@
-# Solidity API
-
-## IStrategy
-
-Custom `Strategy` implementations may expand extensively on this interface.
-
-### deposit
-
-```solidity
-function deposit(contract IERC20 token, uint256 amount) external returns (uint256)
-```
-
-Used to deposit tokens into this Strategy
-
-_This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
-`depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| token | contract IERC20 | is the ERC20 token being deposited |
-| amount | uint256 | is the amount of token being deposited |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | newShares is the number of new shares issued at the current exchange ratio. |
-
-### withdraw
-
-```solidity
-function withdraw(address depositor, contract IERC20 token, uint256 amountShares) external
-```
-
-Used to withdraw tokens from this Strategy, to the `depositor`'s address
-
-_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._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | is the address to receive the withdrawn funds |
-| token | contract IERC20 | is the ERC20 token being transferred out |
-| amountShares | uint256 | is the amount of shares being withdrawn |
-
-### sharesToUnderlying
-
-```solidity
-function sharesToUnderlying(uint256 amountShares) external returns (uint256)
-```
-
-Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
-In contrast to `sharesToUnderlyingView`, this function **may** make state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountShares | uint256 | is the amount of shares to calculate its conversion into the underlying token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of underlying tokens corresponding to the input `amountShares` |
-
-### underlyingToShares
-
-```solidity
-function underlyingToShares(uint256 amountUnderlying) external returns (uint256)
-```
-
-Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
-In contrast to `underlyingToSharesView`, this function **may** make state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountUnderlying | uint256 | is the amount of `underlyingToken` to calculate its conversion into strategy shares |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of underlying tokens corresponding to the input `amountShares` |
-
-### userUnderlying
-
-```solidity
-function userUnderlying(address user) external returns (uint256)
-```
-
-convenience function for fetching the current underlying value of all of the `user`'s shares in
-this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications
-
-### sharesToUnderlyingView
-
-```solidity
-function sharesToUnderlyingView(uint256 amountShares) external view returns (uint256)
-```
-
-Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
-In contrast to `sharesToUnderlying`, this function guarantees no state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountShares | uint256 | is the amount of shares to calculate its conversion into the underlying token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of shares corresponding to the input `amountUnderlying` |
-
-### underlyingToSharesView
-
-```solidity
-function underlyingToSharesView(uint256 amountUnderlying) external view returns (uint256)
-```
-
-Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
-In contrast to `underlyingToShares`, this function guarantees no state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountUnderlying | uint256 | is the amount of `underlyingToken` to calculate its conversion into strategy shares |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of shares corresponding to the input `amountUnderlying` |
-
-### userUnderlyingView
-
-```solidity
-function userUnderlyingView(address user) external view returns (uint256)
-```
-
-convenience function for fetching the current underlying value of all of the `user`'s shares in
-this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications
-
-### underlyingToken
-
-```solidity
-function underlyingToken() external view returns (contract IERC20)
-```
-
-The underlying token for shares in this Strategy
-
-### totalShares
-
-```solidity
-function totalShares() external view returns (uint256)
-```
-
-The total number of extant shares in this Strategy
-
-### explanation
-
-```solidity
-function explanation() external view returns (string)
-```
-
-Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that explains in more detail.
-
diff --git a/docs/docgen/interfaces/IStrategyManager.md b/docs/docgen/interfaces/IStrategyManager.md
deleted file mode 100644
index 52c17f208b..0000000000
--- a/docs/docgen/interfaces/IStrategyManager.md
+++ /dev/null
@@ -1,345 +0,0 @@
-# Solidity API
-
-## IStrategyManager
-
-See the `StrategyManager` contract itself for implementation details.
-
-### WithdrawerAndNonce
-
-```solidity
-struct WithdrawerAndNonce {
- address withdrawer;
- uint96 nonce;
-}
-```
-
-### QueuedWithdrawal
-
-```solidity
-struct QueuedWithdrawal {
- contract IStrategy[] strategies;
- uint256[] shares;
- address depositor;
- struct IStrategyManager.WithdrawerAndNonce withdrawerAndNonce;
- uint32 withdrawalStartBlock;
- address delegatedAddress;
-}
-```
-
-### depositIntoStrategy
-
-```solidity
-function depositIntoStrategy(contract IStrategy strategy, contract IERC20 token, uint256 amount) external returns (uint256 shares)
-```
-
-Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
-
-_The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
-Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
-
-WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
- where the token balance and corresponding strategy shares are not in sync upon reentrancy._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategy | contract IStrategy | is the specified strategy where deposit is to be made, |
-| token | contract IERC20 | is the denomination in which the deposit is to be made, |
-| amount | uint256 | is the amount of token to be deposited in the strategy by the depositor |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| shares | uint256 | The amount of new shares in the `strategy` created as part of the action. |
-
-### depositBeaconChainETH
-
-```solidity
-function depositBeaconChainETH(address staker, uint256 amount) external
-```
-
-Deposits `amount` of beaconchain ETH into this contract on behalf of `staker`
-
-_Only callable by EigenPodManager._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| staker | address | is the entity that is restaking in eigenlayer, |
-| amount | uint256 | is the amount of beaconchain ETH being restaked, |
-
-### recordOvercommittedBeaconChainETH
-
-```solidity
-function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external
-```
-
-Records an overcommitment event on behalf of a staker. The staker's beaconChainETH shares are decremented by `amount`.
-
-_Only callable by EigenPodManager._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| overcommittedPodOwner | address | is the pod owner to be slashed |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy in case it must be removed, |
-| amount | uint256 | is the amount to decrement the slashedAddress's beaconChainETHStrategy shares |
-
-### depositIntoStrategyWithSignature
-
-```solidity
-function depositIntoStrategyWithSignature(contract IStrategy strategy, contract IERC20 token, uint256 amount, address staker, uint256 expiry, bytes signature) external returns (uint256 shares)
-```
-
-Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
-who must sign off on the action.
-Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
-purely to help one address deposit 'for' another.
-
-_The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
-A signature is required for this function to eliminate the possibility of griefing attacks, specifically those
-targeting stakers who may be attempting to undelegate.
-Cannot be called on behalf of a staker that is 'frozen' (this function will revert if the `staker` is frozen).
-
- WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
- where the token balance and corresponding strategy shares are not in sync upon reentrancy_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategy | contract IStrategy | is the specified strategy where deposit is to be made, |
-| token | contract IERC20 | is the denomination in which the deposit is to be made, |
-| amount | uint256 | is the amount of token to be deposited in the strategy by the depositor |
-| staker | address | the staker that the deposited assets will be credited to |
-| expiry | uint256 | the timestamp at which the signature expires |
-| signature | bytes | is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward following EIP-1271 if the `staker` is a contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| shares | uint256 | The amount of new shares in the `strategy` created as part of the action. |
-
-### stakerStrategyShares
-
-```solidity
-function stakerStrategyShares(address user, contract IStrategy strategy) external view returns (uint256 shares)
-```
-
-Returns the current shares of `user` in `strategy`
-
-### getDeposits
-
-```solidity
-function getDeposits(address depositor) external view returns (contract IStrategy[], uint256[])
-```
-
-Get all details on the depositor's deposits and corresponding shares
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | contract IStrategy[] | (depositor's strategies, shares in these strategies) |
-| [1] | uint256[] | |
-
-### stakerStrategyListLength
-
-```solidity
-function stakerStrategyListLength(address staker) external view returns (uint256)
-```
-
-Simple getter function that returns `stakerStrategyList[staker].length`.
-
-### queueWithdrawal
-
-```solidity
-function queueWithdrawal(uint256[] strategyIndexes, contract IStrategy[] strategies, uint256[] shares, address withdrawer, bool undelegateIfPossible) external returns (bytes32)
-```
-
-Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
-
-_Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
-User shares are decreased in this function, but the total number of shares in each strategy remains the same.
-The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
-the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
-that the value per share reported by each strategy will remain consistent, and that the shares will continue
-to accrue gains during the enforced withdrawal waiting period.
-Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
-popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
-is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
-`stakerStrategyList` to lowest index
-Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
-`withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed
-for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
-the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod)._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategyIndexes | uint256[] | is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies for which `msg.sender` is withdrawing 100% of their shares |
-| strategies | contract IStrategy[] | The Strategies to withdraw from |
-| shares | uint256[] | The amount of shares to withdraw from each of the respective Strategies in the `strategies` array |
-| withdrawer | address | The address that can complete the withdrawal and will receive any withdrawn funds or shares upon completing the withdrawal |
-| undelegateIfPossible | bool | If this param is marked as 'true' *and the withdrawal will result in `msg.sender` having no shares in any Strategy,* then this function will also make an internal call to `undelegate(msg.sender)` to undelegate the `msg.sender`. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | The 'withdrawalRoot' of the newly created Queued Withdrawal |
-
-### completeQueuedWithdrawal
-
-```solidity
-function completeQueuedWithdrawal(struct IStrategyManager.QueuedWithdrawal queuedWithdrawal, contract IERC20[] tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) external
-```
-
-Used to complete the specified `queuedWithdrawal`. The function caller must match `queuedWithdrawal.withdrawer`
-
-_middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| queuedWithdrawal | struct IStrategyManager.QueuedWithdrawal | The QueuedWithdrawal to complete. |
-| tokens | contract IERC20[] | Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array of the `queuedWithdrawal`. This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused) |
-| middlewareTimesIndex | uint256 | is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array |
-| receiveAsTokens | bool | If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies will simply be transferred to the caller directly. |
-
-### completeQueuedWithdrawals
-
-```solidity
-function completeQueuedWithdrawals(struct IStrategyManager.QueuedWithdrawal[] queuedWithdrawals, contract IERC20[][] tokens, uint256[] middlewareTimesIndexes, bool[] receiveAsTokens) external
-```
-
-Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer`
-
-_Array-ified version of `completeQueuedWithdrawal`
-middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| queuedWithdrawals | struct IStrategyManager.QueuedWithdrawal[] | The QueuedWithdrawals to complete. |
-| tokens | contract IERC20[][] | Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array. |
-| middlewareTimesIndexes | uint256[] | One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index. |
-| receiveAsTokens | bool[] | If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies will simply be transferred to the caller directly. |
-
-### slashShares
-
-```solidity
-function slashShares(address slashedAddress, address recipient, contract IStrategy[] strategies, contract IERC20[] tokens, uint256[] strategyIndexes, uint256[] shareAmounts) external
-```
-
-Slashes the shares of a 'frozen' operator (or a staker delegated to one)
-
-_strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
-popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
-is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
-`stakerStrategyList` to lowest index_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| slashedAddress | address | is the frozen address that is having its shares slashed |
-| recipient | address | is the address that will receive the slashed funds, which could e.g. be a harmed party themself, or a MerkleDistributor-type contract that further sub-divides the slashed funds. |
-| strategies | contract IStrategy[] | Strategies to slash |
-| tokens | contract IERC20[] | The tokens to use as input to the `withdraw` function of each of the provided `strategies` |
-| strategyIndexes | uint256[] | is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies for which `msg.sender` is withdrawing 100% of their shares |
-| shareAmounts | uint256[] | The amount of shares to slash in each of the provided `strategies` |
-
-### slashQueuedWithdrawal
-
-```solidity
-function slashQueuedWithdrawal(address recipient, struct IStrategyManager.QueuedWithdrawal queuedWithdrawal, contract IERC20[] tokens, uint256[] indicesToSkip) external
-```
-
-Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one)
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| recipient | address | The funds in the slashed withdrawal are withdrawn as tokens to this address. |
-| queuedWithdrawal | struct IStrategyManager.QueuedWithdrawal | The previously queued withdrawal to be slashed |
-| tokens | contract IERC20[] | Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array of the `queuedWithdrawal`. |
-| indicesToSkip | uint256[] | Optional input parameter -- indices in the `strategies` array to skip (i.e. not call the 'withdraw' function on). This input exists so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function, then the malicious strategy can be skipped (with the shares in effect "burned"), while the non-malicious strategies are still called as normal. |
-
-### calculateWithdrawalRoot
-
-```solidity
-function calculateWithdrawalRoot(struct IStrategyManager.QueuedWithdrawal queuedWithdrawal) external pure returns (bytes32)
-```
-
-Returns the keccak256 hash of `queuedWithdrawal`.
-
-### addStrategiesToDepositWhitelist
-
-```solidity
-function addStrategiesToDepositWhitelist(contract IStrategy[] strategiesToWhitelist) external
-```
-
-Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategiesToWhitelist | contract IStrategy[] | Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already) |
-
-### removeStrategiesFromDepositWhitelist
-
-```solidity
-function removeStrategiesFromDepositWhitelist(contract IStrategy[] strategiesToRemoveFromWhitelist) external
-```
-
-Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| strategiesToRemoveFromWhitelist | contract IStrategy[] | Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it) |
-
-### delegation
-
-```solidity
-function delegation() external view returns (contract IDelegationManager)
-```
-
-Returns the single, central Delegation contract of EigenLayer
-
-### slasher
-
-```solidity
-function slasher() external view returns (contract ISlasher)
-```
-
-Returns the single, central Slasher contract of EigenLayer
-
-### beaconChainETHStrategy
-
-```solidity
-function beaconChainETHStrategy() external view returns (contract IStrategy)
-```
-
-returns the enshrined, virtual 'beaconChainETH' Strategy
-
-### withdrawalDelayBlocks
-
-```solidity
-function withdrawalDelayBlocks() external view returns (uint256)
-```
-
-Returns the number of blocks that must pass between the time a withdrawal is queued and the time it can be completed
-
diff --git a/docs/docgen/interfaces/IVoteWeigher.md b/docs/docgen/interfaces/IVoteWeigher.md
deleted file mode 100644
index 10e91a839d..0000000000
--- a/docs/docgen/interfaces/IVoteWeigher.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# Solidity API
-
-## IVoteWeigher
-
-Note that `NUMBER_OF_QUORUMS` is expected to remain constant, as suggested by its uppercase formatting.
-
-### weightOfOperator
-
-```solidity
-function weightOfOperator(address operator, uint256 quorumNumber) external returns (uint96)
-```
-
-This function computes the total weight of the @param operator in the quorum @param quorumNumber.
-
-_returns zero in the case that `quorumNumber` is greater than or equal to `NUMBER_OF_QUORUMS`_
-
-### NUMBER_OF_QUORUMS
-
-```solidity
-function NUMBER_OF_QUORUMS() external view returns (uint256)
-```
-
-Number of quorums that are being used by the middleware.
-
-### quorumBips
-
-```solidity
-function quorumBips(uint256 quorumNumber) external view returns (uint256)
-```
-
-This defines the earnings split between different quorums. Mapping is quorumNumber => BIPS which the quorum earns, out of the total earnings.
-
-_The sum of all entries, i.e. sum(quorumBips[0] through quorumBips[NUMBER_OF_QUORUMS - 1]) should *always* be 10,000!_
-
diff --git a/docs/docgen/interfaces/IWhitelister.md b/docs/docgen/interfaces/IWhitelister.md
deleted file mode 100644
index a8b97e4a18..0000000000
--- a/docs/docgen/interfaces/IWhitelister.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Solidity API
-
-## IWhitelister
-
-### whitelist
-
-```solidity
-function whitelist(address operator) external
-```
-
-### getStaker
-
-```solidity
-function getStaker(address operator) external returns (address)
-```
-
-### depositIntoStrategy
-
-```solidity
-function depositIntoStrategy(address staker, contract IStrategy strategy, contract IERC20 token, uint256 amount) external returns (bytes)
-```
-
-### queueWithdrawal
-
-```solidity
-function queueWithdrawal(address staker, uint256[] strategyIndexes, contract IStrategy[] strategies, uint256[] shares, address withdrawer, bool undelegateIfPossible) external returns (bytes)
-```
-
-### completeQueuedWithdrawal
-
-```solidity
-function completeQueuedWithdrawal(address staker, struct IStrategyManager.QueuedWithdrawal queuedWithdrawal, contract IERC20[] tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) external returns (bytes)
-```
-
-### transfer
-
-```solidity
-function transfer(address staker, address token, address to, uint256 amount) external returns (bytes)
-```
-
-### callAddress
-
-```solidity
-function callAddress(address to, bytes data) external payable returns (bytes)
-```
-
diff --git a/docs/docgen/libraries/BN254.md b/docs/docgen/libraries/BN254.md
deleted file mode 100644
index 56e3f320a4..0000000000
--- a/docs/docgen/libraries/BN254.md
+++ /dev/null
@@ -1,215 +0,0 @@
-# Solidity API
-
-## BN254
-
-Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
-
-### FP_MODULUS
-
-```solidity
-uint256 FP_MODULUS
-```
-
-### FR_MODULUS
-
-```solidity
-uint256 FR_MODULUS
-```
-
-### G1Point
-
-```solidity
-struct G1Point {
- uint256 X;
- uint256 Y;
-}
-```
-
-### G2Point
-
-```solidity
-struct G2Point {
- uint256[2] X;
- uint256[2] Y;
-}
-```
-
-### G2x1
-
-```solidity
-uint256 G2x1
-```
-
-_Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1)._
-
-### G2x0
-
-```solidity
-uint256 G2x0
-```
-
-### G2y1
-
-```solidity
-uint256 G2y1
-```
-
-### G2y0
-
-```solidity
-uint256 G2y0
-```
-
-### generatorG2
-
-```solidity
-function generatorG2() internal pure returns (struct BN254.G2Point)
-```
-
-returns the G2 generator
-
-_mind the ordering of the 1s and 0s!
- this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
- "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
- https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding_
-
-### nG2x1
-
-```solidity
-uint256 nG2x1
-```
-
-_Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1)._
-
-### nG2x0
-
-```solidity
-uint256 nG2x0
-```
-
-### nG2y1
-
-```solidity
-uint256 nG2y1
-```
-
-### nG2y0
-
-```solidity
-uint256 nG2y0
-```
-
-### negGeneratorG2
-
-```solidity
-function negGeneratorG2() internal pure returns (struct BN254.G2Point)
-```
-
-### powersOfTauMerkleRoot
-
-```solidity
-bytes32 powersOfTauMerkleRoot
-```
-
-### negate
-
-```solidity
-function negate(struct BN254.G1Point p) internal pure returns (struct BN254.G1Point)
-```
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| p | struct BN254.G1Point | Some point in G1. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | struct BN254.G1Point | The negation of `p`, i.e. p.plus(p.negate()) should be zero. |
-
-### plus
-
-```solidity
-function plus(struct BN254.G1Point p1, struct BN254.G1Point p2) internal view returns (struct BN254.G1Point r)
-```
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| r | struct BN254.G1Point | the sum of two points of G1 |
-
-### scalar_mul
-
-```solidity
-function scalar_mul(struct BN254.G1Point p, uint256 s) internal view returns (struct BN254.G1Point r)
-```
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| r | struct BN254.G1Point | the product of a point on G1 and a scalar, i.e. p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all points p. |
-
-### pairing
-
-```solidity
-function pairing(struct BN254.G1Point a1, struct BN254.G2Point a2, struct BN254.G1Point b1, struct BN254.G2Point b2) internal view returns (bool)
-```
-
-@return The result of computing the pairing check
- e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
- For example,
- pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
-
-### safePairing
-
-```solidity
-function safePairing(struct BN254.G1Point a1, struct BN254.G2Point a2, struct BN254.G1Point b1, struct BN254.G2Point b2, uint256 pairingGas) internal view returns (bool, bool)
-```
-
-This function is functionally the same as pairing(), however it specifies a gas limit
- the user can set, as a precompile may use the entire gas budget if it reverts.
-
-### hashG1Point
-
-```solidity
-function hashG1Point(struct BN254.G1Point pk) internal pure returns (bytes32)
-```
-
-_used for BLS signatures_
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bytes32 | the keccak256 hash of the G1 Point |
-
-### hashToG1
-
-```solidity
-function hashToG1(bytes32 _x) internal view returns (uint256, uint256)
-```
-
-adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
-
-### findYFromX
-
-```solidity
-function findYFromX(uint256 x) internal view returns (uint256, uint256)
-```
-
-Given X, find Y
-
- where y = sqrt(x^3 + b)
-
-Returns: (x^3 + b), y
-
-### expMod
-
-```solidity
-function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval)
-```
-
diff --git a/docs/docgen/libraries/BeaconChainProofs.md b/docs/docgen/libraries/BeaconChainProofs.md
deleted file mode 100644
index f76d081cb5..0000000000
--- a/docs/docgen/libraries/BeaconChainProofs.md
+++ /dev/null
@@ -1,427 +0,0 @@
-# Solidity API
-
-## BeaconChainProofs
-
-### NUM_BEACON_BLOCK_HEADER_FIELDS
-
-```solidity
-uint256 NUM_BEACON_BLOCK_HEADER_FIELDS
-```
-
-### BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT
-```
-
-### NUM_BEACON_BLOCK_BODY_FIELDS
-
-```solidity
-uint256 NUM_BEACON_BLOCK_BODY_FIELDS
-```
-
-### BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT
-```
-
-### NUM_BEACON_STATE_FIELDS
-
-```solidity
-uint256 NUM_BEACON_STATE_FIELDS
-```
-
-### BEACON_STATE_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 BEACON_STATE_FIELD_TREE_HEIGHT
-```
-
-### NUM_ETH1_DATA_FIELDS
-
-```solidity
-uint256 NUM_ETH1_DATA_FIELDS
-```
-
-### ETH1_DATA_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 ETH1_DATA_FIELD_TREE_HEIGHT
-```
-
-### NUM_VALIDATOR_FIELDS
-
-```solidity
-uint256 NUM_VALIDATOR_FIELDS
-```
-
-### VALIDATOR_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 VALIDATOR_FIELD_TREE_HEIGHT
-```
-
-### NUM_EXECUTION_PAYLOAD_HEADER_FIELDS
-
-```solidity
-uint256 NUM_EXECUTION_PAYLOAD_HEADER_FIELDS
-```
-
-### EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT
-```
-
-### NUM_EXECUTION_PAYLOAD_FIELDS
-
-```solidity
-uint256 NUM_EXECUTION_PAYLOAD_FIELDS
-```
-
-### EXECUTION_PAYLOAD_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 EXECUTION_PAYLOAD_FIELD_TREE_HEIGHT
-```
-
-### HISTORICAL_ROOTS_TREE_HEIGHT
-
-```solidity
-uint256 HISTORICAL_ROOTS_TREE_HEIGHT
-```
-
-### HISTORICAL_BATCH_TREE_HEIGHT
-
-```solidity
-uint256 HISTORICAL_BATCH_TREE_HEIGHT
-```
-
-### STATE_ROOTS_TREE_HEIGHT
-
-```solidity
-uint256 STATE_ROOTS_TREE_HEIGHT
-```
-
-### BLOCK_ROOTS_TREE_HEIGHT
-
-```solidity
-uint256 BLOCK_ROOTS_TREE_HEIGHT
-```
-
-### NUM_WITHDRAWAL_FIELDS
-
-```solidity
-uint256 NUM_WITHDRAWAL_FIELDS
-```
-
-### WITHDRAWAL_FIELD_TREE_HEIGHT
-
-```solidity
-uint256 WITHDRAWAL_FIELD_TREE_HEIGHT
-```
-
-### VALIDATOR_TREE_HEIGHT
-
-```solidity
-uint256 VALIDATOR_TREE_HEIGHT
-```
-
-### BALANCE_TREE_HEIGHT
-
-```solidity
-uint256 BALANCE_TREE_HEIGHT
-```
-
-### WITHDRAWALS_TREE_HEIGHT
-
-```solidity
-uint256 WITHDRAWALS_TREE_HEIGHT
-```
-
-### EXECUTION_PAYLOAD_INDEX
-
-```solidity
-uint256 EXECUTION_PAYLOAD_INDEX
-```
-
-### STATE_ROOT_INDEX
-
-```solidity
-uint256 STATE_ROOT_INDEX
-```
-
-### PROPOSER_INDEX_INDEX
-
-```solidity
-uint256 PROPOSER_INDEX_INDEX
-```
-
-### SLOT_INDEX
-
-```solidity
-uint256 SLOT_INDEX
-```
-
-### BODY_ROOT_INDEX
-
-```solidity
-uint256 BODY_ROOT_INDEX
-```
-
-### STATE_ROOTS_INDEX
-
-```solidity
-uint256 STATE_ROOTS_INDEX
-```
-
-### BLOCK_ROOTS_INDEX
-
-```solidity
-uint256 BLOCK_ROOTS_INDEX
-```
-
-### HISTORICAL_ROOTS_INDEX
-
-```solidity
-uint256 HISTORICAL_ROOTS_INDEX
-```
-
-### ETH_1_ROOT_INDEX
-
-```solidity
-uint256 ETH_1_ROOT_INDEX
-```
-
-### VALIDATOR_TREE_ROOT_INDEX
-
-```solidity
-uint256 VALIDATOR_TREE_ROOT_INDEX
-```
-
-### BALANCE_INDEX
-
-```solidity
-uint256 BALANCE_INDEX
-```
-
-### EXECUTION_PAYLOAD_HEADER_INDEX
-
-```solidity
-uint256 EXECUTION_PAYLOAD_HEADER_INDEX
-```
-
-### HISTORICAL_BATCH_STATE_ROOT_INDEX
-
-```solidity
-uint256 HISTORICAL_BATCH_STATE_ROOT_INDEX
-```
-
-### VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX
-
-```solidity
-uint256 VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX
-```
-
-### VALIDATOR_BALANCE_INDEX
-
-```solidity
-uint256 VALIDATOR_BALANCE_INDEX
-```
-
-### VALIDATOR_SLASHED_INDEX
-
-```solidity
-uint256 VALIDATOR_SLASHED_INDEX
-```
-
-### VALIDATOR_WITHDRAWABLE_EPOCH_INDEX
-
-```solidity
-uint256 VALIDATOR_WITHDRAWABLE_EPOCH_INDEX
-```
-
-### BLOCK_NUMBER_INDEX
-
-```solidity
-uint256 BLOCK_NUMBER_INDEX
-```
-
-### WITHDRAWALS_ROOT_INDEX
-
-```solidity
-uint256 WITHDRAWALS_ROOT_INDEX
-```
-
-### WITHDRAWALS_INDEX
-
-```solidity
-uint256 WITHDRAWALS_INDEX
-```
-
-### WITHDRAWAL_VALIDATOR_INDEX_INDEX
-
-```solidity
-uint256 WITHDRAWAL_VALIDATOR_INDEX_INDEX
-```
-
-### WITHDRAWAL_VALIDATOR_AMOUNT_INDEX
-
-```solidity
-uint256 WITHDRAWAL_VALIDATOR_AMOUNT_INDEX
-```
-
-### HISTORICALBATCH_STATEROOTS_INDEX
-
-```solidity
-uint256 HISTORICALBATCH_STATEROOTS_INDEX
-```
-
-### SLOTS_PER_EPOCH
-
-```solidity
-uint256 SLOTS_PER_EPOCH
-```
-
-### UINT64_MASK
-
-```solidity
-bytes8 UINT64_MASK
-```
-
-### WithdrawalProofs
-
-```solidity
-struct WithdrawalProofs {
- bytes blockHeaderProof;
- bytes withdrawalProof;
- bytes slotProof;
- bytes executionPayloadProof;
- bytes blockNumberProof;
- uint64 blockHeaderRootIndex;
- uint64 withdrawalIndex;
- bytes32 blockHeaderRoot;
- bytes32 blockBodyRoot;
- bytes32 slotRoot;
- bytes32 blockNumberRoot;
- bytes32 executionPayloadRoot;
-}
-```
-
-### ValidatorFieldsAndBalanceProofs
-
-```solidity
-struct ValidatorFieldsAndBalanceProofs {
- bytes validatorFieldsProof;
- bytes validatorBalanceProof;
- bytes32 balanceRoot;
-}
-```
-
-### ValidatorFieldsProof
-
-```solidity
-struct ValidatorFieldsProof {
- bytes validatorProof;
- uint40 validatorIndex;
-}
-```
-
-### computePhase0BeaconBlockHeaderRoot
-
-```solidity
-function computePhase0BeaconBlockHeaderRoot(bytes32[5] blockHeaderFields) internal pure returns (bytes32)
-```
-
-### computePhase0BeaconStateRoot
-
-```solidity
-function computePhase0BeaconStateRoot(bytes32[21] beaconStateFields) internal pure returns (bytes32)
-```
-
-### computePhase0ValidatorRoot
-
-```solidity
-function computePhase0ValidatorRoot(bytes32[8] validatorFields) internal pure returns (bytes32)
-```
-
-### computePhase0Eth1DataRoot
-
-```solidity
-function computePhase0Eth1DataRoot(bytes32[3] eth1DataFields) internal pure returns (bytes32)
-```
-
-### getBalanceFromBalanceRoot
-
-```solidity
-function getBalanceFromBalanceRoot(uint40 validatorIndex, bytes32 balanceRoot) internal pure returns (uint64)
-```
-
-This function is parses the balanceRoot to get the uint64 balance of a validator. During merkleization of the
-beacon state balance tree, four uint64 values (making 32 bytes) are grouped together and treated as a single leaf in the merkle tree. Thus the
-validatorIndex mod 4 is used to determine which of the four uint64 values to extract from the balanceRoot.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| validatorIndex | uint40 | is the index of the validator being proven for. |
-| balanceRoot | bytes32 | is the combination of 4 validator balances being proven for. |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint64 | The validator's balance, in Gwei |
-
-### verifyValidatorFields
-
-```solidity
-function verifyValidatorFields(uint40 validatorIndex, bytes32 beaconStateRoot, bytes proof, bytes32[] validatorFields) internal view
-```
-
-This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| validatorIndex | uint40 | the index of the proven validator |
-| beaconStateRoot | bytes32 | is the beacon chain state root to be proven against. |
-| proof | bytes | is the data used in proving the validator's fields |
-| validatorFields | bytes32[] | the claimed fields of the validator |
-
-### verifyValidatorBalance
-
-```solidity
-function verifyValidatorBalance(uint40 validatorIndex, bytes32 beaconStateRoot, bytes proof, bytes32 balanceRoot) internal view
-```
-
-This function verifies merkle proofs of the balance of a certain validator against a beacon chain state root
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| validatorIndex | uint40 | the index of the proven validator |
-| beaconStateRoot | bytes32 | is the beacon chain state root to be proven against. |
-| proof | bytes | is the proof of the balance against the beacon chain state root |
-| balanceRoot | bytes32 | is the serialized balance used to prove the balance of the validator (refer to `getBalanceFromBalanceRoot` above for detailed explanation) |
-
-### verifyWithdrawalProofs
-
-```solidity
-function verifyWithdrawalProofs(bytes32 beaconStateRoot, struct BeaconChainProofs.WithdrawalProofs proofs, bytes32[] withdrawalFields) internal view
-```
-
-This function verifies the slot and the withdrawal fields for a given withdrawal
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| beaconStateRoot | bytes32 | is the beacon chain state root to be proven against. |
-| proofs | struct BeaconChainProofs.WithdrawalProofs | is the provided set of merkle proofs |
-| withdrawalFields | bytes32[] | is the serialized withdrawal container to be proven |
-
diff --git a/docs/docgen/libraries/BytesLib.md b/docs/docgen/libraries/BytesLib.md
deleted file mode 100644
index 6d3aa50552..0000000000
--- a/docs/docgen/libraries/BytesLib.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# Solidity API
-
-## BytesLib
-
-### concat
-
-```solidity
-function concat(bytes _preBytes, bytes _postBytes) internal pure returns (bytes)
-```
-
-### concatStorage
-
-```solidity
-function concatStorage(bytes _preBytes, bytes _postBytes) internal
-```
-
-### slice
-
-```solidity
-function slice(bytes _bytes, uint256 _start, uint256 _length) internal pure returns (bytes)
-```
-
-### toAddress
-
-```solidity
-function toAddress(bytes _bytes, uint256 _start) internal pure returns (address)
-```
-
-### toUint8
-
-```solidity
-function toUint8(bytes _bytes, uint256 _start) internal pure returns (uint8)
-```
-
-### toUint16
-
-```solidity
-function toUint16(bytes _bytes, uint256 _start) internal pure returns (uint16)
-```
-
-### toUint32
-
-```solidity
-function toUint32(bytes _bytes, uint256 _start) internal pure returns (uint32)
-```
-
-### toUint64
-
-```solidity
-function toUint64(bytes _bytes, uint256 _start) internal pure returns (uint64)
-```
-
-### toUint96
-
-```solidity
-function toUint96(bytes _bytes, uint256 _start) internal pure returns (uint96)
-```
-
-### toUint128
-
-```solidity
-function toUint128(bytes _bytes, uint256 _start) internal pure returns (uint128)
-```
-
-### toUint256
-
-```solidity
-function toUint256(bytes _bytes, uint256 _start) internal pure returns (uint256)
-```
-
-### toBytes32
-
-```solidity
-function toBytes32(bytes _bytes, uint256 _start) internal pure returns (bytes32)
-```
-
-### equal
-
-```solidity
-function equal(bytes _preBytes, bytes _postBytes) internal pure returns (bool)
-```
-
-### equalStorage
-
-```solidity
-function equalStorage(bytes _preBytes, bytes _postBytes) internal view returns (bool)
-```
-
diff --git a/docs/docgen/libraries/Endian.md b/docs/docgen/libraries/Endian.md
deleted file mode 100644
index b0f8941c07..0000000000
--- a/docs/docgen/libraries/Endian.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Solidity API
-
-## Endian
-
-### fromLittleEndianUint64
-
-```solidity
-function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n)
-```
-
-Converts a little endian-formatted uint64 to a big endian-formatted uint64
-
-_Note that the input is formatted as a 'bytes32' type (i.e. 256 bits), but it is immediately truncated to a uint64 (i.e. 64 bits)
-through a right-shift/shr operation._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| lenum | bytes32 | little endian-formatted uint64 input, provided as 'bytes32' type |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| n | uint64 | The big endian-formatted uint64 |
-
diff --git a/docs/docgen/libraries/Merkle.md b/docs/docgen/libraries/Merkle.md
deleted file mode 100644
index 52b4f8ad07..0000000000
--- a/docs/docgen/libraries/Merkle.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Solidity API
-
-## Merkle
-
-_These functions deal with verification of Merkle Tree proofs.
-
-The tree and the proofs can be generated using our
-https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
-You will find a quickstart guide in the readme.
-
-WARNING: You should avoid using leaf values that are 64 bytes long prior to
-hashing, or use a hash function other than keccak256 for hashing leaves.
-This is because the concatenation of a sorted pair of internal nodes in
-the merkle tree could be reinterpreted as a leaf value.
-OpenZeppelin's JavaScript library generates merkle trees that are safe
-against this attack out of the box._
-
-### verifyInclusionKeccak
-
-```solidity
-function verifyInclusionKeccak(bytes proof, bytes32 root, bytes32 leaf, uint256 index) internal pure returns (bool)
-```
-
-_Returns the rebuilt hash obtained by traversing a Merkle tree up
-from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
-hash matches the root of the tree. The tree is built assuming `leaf` is
-the 0 indexed `index`'th leaf from the bottom left of the tree.
-
-Note this is for a Merkle tree using the keccak/sha3 hash function_
-
-### processInclusionProofKeccak
-
-```solidity
-function processInclusionProofKeccak(bytes proof, bytes32 leaf, uint256 index) internal pure returns (bytes32)
-```
-
-_Returns the rebuilt hash obtained by traversing a Merkle tree up
-from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
-hash matches the root of the tree. The tree is built assuming `leaf` is
-the 0 indexed `index`'th leaf from the bottom left of the tree.
-
-_Available since v4.4._
-
-Note this is for a Merkle tree using the keccak/sha3 hash function_
-
-### verifyInclusionSha256
-
-```solidity
-function verifyInclusionSha256(bytes proof, bytes32 root, bytes32 leaf, uint256 index) internal view returns (bool)
-```
-
-_Returns the rebuilt hash obtained by traversing a Merkle tree up
-from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
-hash matches the root of the tree. The tree is built assuming `leaf` is
-the 0 indexed `index`'th leaf from the bottom left of the tree.
-
-Note this is for a Merkle tree using the sha256 hash function_
-
-### processInclusionProofSha256
-
-```solidity
-function processInclusionProofSha256(bytes proof, bytes32 leaf, uint256 index) internal view returns (bytes32)
-```
-
-_Returns the rebuilt hash obtained by traversing a Merkle tree up
-from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
-hash matches the root of the tree. The tree is built assuming `leaf` is
-the 0 indexed `index`'th leaf from the bottom left of the tree.
-
-_Available since v4.4._
-
-Note this is for a Merkle tree using the sha256 hash function_
-
-### merkleizeSha256
-
-```solidity
-function merkleizeSha256(bytes32[] leaves) internal pure returns (bytes32)
-```
-
-this function returns the merkle root of a tree created from a set of leaves using sha256 as its hash function
- @param leaves the leaves of the merkle tree
-
- @notice requires the leaves.length is a power of 2
- @return The computed Merkle root of the tree.
-
diff --git a/docs/docgen/libraries/MiddlewareUtils.md b/docs/docgen/libraries/MiddlewareUtils.md
deleted file mode 100644
index 5bc3e6d836..0000000000
--- a/docs/docgen/libraries/MiddlewareUtils.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Solidity API
-
-## MiddlewareUtils
-
-### computeSignatoryRecordHash
-
-```solidity
-function computeSignatoryRecordHash(uint32 globalDataStoreId, bytes32[] nonSignerPubkeyHashes, uint256 signedStakeFirstQuorum, uint256 signedStakeSecondQuorum) internal pure returns (bytes32)
-```
-
-Finds the `signatoryRecordHash`, used for fraudproofs.
-
diff --git a/docs/docgen/libraries/StructuredLinkedList.md b/docs/docgen/libraries/StructuredLinkedList.md
deleted file mode 100644
index 8d034c8706..0000000000
--- a/docs/docgen/libraries/StructuredLinkedList.md
+++ /dev/null
@@ -1,442 +0,0 @@
-# Solidity API
-
-## StructuredLinkedList
-
-Adapted from https://github.com/vittominacori/solidity-linked-list/blob/master/contracts/StructuredLinkedList.sol
-
-_An utility library for using sorted linked list data structures in your Solidity project._
-
-### _NULL
-
-```solidity
-uint256 _NULL
-```
-
-### _HEAD
-
-```solidity
-uint256 _HEAD
-```
-
-### _PREV
-
-```solidity
-bool _PREV
-```
-
-### _NEXT
-
-```solidity
-bool _NEXT
-```
-
-### List
-
-```solidity
-struct List {
- uint256 size;
- mapping(uint256 => mapping(bool => uint256)) list;
-}
-```
-
-### listExists
-
-```solidity
-function listExists(struct StructuredLinkedList.List self) internal view returns (bool)
-```
-
-_Checks if the list exists_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if list exists, false otherwise |
-
-### nodeExists
-
-```solidity
-function nodeExists(struct StructuredLinkedList.List self, uint256 _node) internal view returns (bool)
-```
-
-_Checks if the node exists_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | a node to search for |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if node exists, false otherwise |
-
-### sizeOf
-
-```solidity
-function sizeOf(struct StructuredLinkedList.List self) internal view returns (uint256)
-```
-
-_Returns the number of elements in the list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | uint256 |
-
-### getHead
-
-```solidity
-function getHead(struct StructuredLinkedList.List self) internal view returns (uint256)
-```
-
-_Gets the head of the list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | uint256 the head of the list |
-
-### getNode
-
-```solidity
-function getNode(struct StructuredLinkedList.List self, uint256 _node) internal view returns (bool, uint256, uint256)
-```
-
-_Returns the links of a node as a tuple_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | id of the node to get |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool, uint256, uint256 true if node exists or false otherwise, previous node, next node |
-| [1] | uint256 | |
-| [2] | uint256 | |
-
-### getAdjacent
-
-```solidity
-function getAdjacent(struct StructuredLinkedList.List self, uint256 _node, bool _direction) internal view returns (bool, uint256)
-```
-
-_Returns the link of a node `_node` in direction `_direction`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | id of the node to step from |
-| _direction | bool | direction to step in |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool, uint256 true if node exists or false otherwise, node in _direction |
-| [1] | uint256 | |
-
-### getNextNode
-
-```solidity
-function getNextNode(struct StructuredLinkedList.List self, uint256 _node) internal view returns (bool, uint256)
-```
-
-_Returns the link of a node `_node` in direction `_NEXT`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | id of the node to step from |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool, uint256 true if node exists or false otherwise, next node |
-| [1] | uint256 | |
-
-### getPreviousNode
-
-```solidity
-function getPreviousNode(struct StructuredLinkedList.List self, uint256 _node) internal view returns (bool, uint256)
-```
-
-_Returns the link of a node `_node` in direction `_PREV`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | id of the node to step from |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool, uint256 true if node exists or false otherwise, previous node |
-| [1] | uint256 | |
-
-### insertAfter
-
-```solidity
-function insertAfter(struct StructuredLinkedList.List self, uint256 _node, uint256 _new) internal returns (bool)
-```
-
-_Insert node `_new` beside existing node `_node` in direction `_NEXT`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | existing node |
-| _new | uint256 | new node to insert |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if success, false otherwise |
-
-### insertBefore
-
-```solidity
-function insertBefore(struct StructuredLinkedList.List self, uint256 _node, uint256 _new) internal returns (bool)
-```
-
-_Insert node `_new` beside existing node `_node` in direction `_PREV`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | existing node |
-| _new | uint256 | new node to insert |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if success, false otherwise |
-
-### remove
-
-```solidity
-function remove(struct StructuredLinkedList.List self, uint256 _node) internal returns (uint256)
-```
-
-_Removes an entry from the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | node to remove from the list |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | uint256 the removed node |
-
-### pushFront
-
-```solidity
-function pushFront(struct StructuredLinkedList.List self, uint256 _node) internal returns (bool)
-```
-
-_Pushes an entry to the head of the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | new entry to push to the head |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if success, false otherwise |
-
-### pushBack
-
-```solidity
-function pushBack(struct StructuredLinkedList.List self, uint256 _node) internal returns (bool)
-```
-
-_Pushes an entry to the tail of the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | new entry to push to the tail |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if success, false otherwise |
-
-### popFront
-
-```solidity
-function popFront(struct StructuredLinkedList.List self) internal returns (uint256)
-```
-
-_Pops the first entry from the head of the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | uint256 the removed node |
-
-### popBack
-
-```solidity
-function popBack(struct StructuredLinkedList.List self) internal returns (uint256)
-```
-
-_Pops the first entry from the tail of the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | uint256 the removed node |
-
-### _push
-
-```solidity
-function _push(struct StructuredLinkedList.List self, uint256 _node, bool _direction) private returns (bool)
-```
-
-_Pushes an entry to the head of the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | new entry to push to the head |
-| _direction | bool | push to the head (_NEXT) or tail (_PREV) |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if success, false otherwise |
-
-### _pop
-
-```solidity
-function _pop(struct StructuredLinkedList.List self, bool _direction) private returns (uint256)
-```
-
-_Pops the first entry from the linked list_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _direction | bool | pop from the head (_NEXT) or the tail (_PREV) |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | uint256 the removed node |
-
-### _insert
-
-```solidity
-function _insert(struct StructuredLinkedList.List self, uint256 _node, uint256 _new, bool _direction) private returns (bool)
-```
-
-_Insert node `_new` beside existing node `_node` in direction `_direction`._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | existing node |
-| _new | uint256 | new node to insert |
-| _direction | bool | direction to insert node in |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | bool true if success, false otherwise |
-
-### _createLink
-
-```solidity
-function _createLink(struct StructuredLinkedList.List self, uint256 _node, uint256 _link, bool _direction) private
-```
-
-_Creates a bidirectional link between two nodes on direction `_direction`_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| self | struct StructuredLinkedList.List | stored linked list from contract |
-| _node | uint256 | existing node |
-| _link | uint256 | node to link to in the _direction |
-| _direction | bool | direction to insert node in |
-
diff --git a/docs/docgen/middleware/BLSPublicKeyCompendium.md b/docs/docgen/middleware/BLSPublicKeyCompendium.md
deleted file mode 100644
index 3d26ecbb55..0000000000
--- a/docs/docgen/middleware/BLSPublicKeyCompendium.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Solidity API
-
-## BLSPublicKeyCompendium
-
-### ZERO_PK_HASH
-
-```solidity
-bytes32 ZERO_PK_HASH
-```
-
-### operatorToPubkeyHash
-
-```solidity
-mapping(address => bytes32) operatorToPubkeyHash
-```
-
-mapping from operator address to pubkey hash
-
-### pubkeyHashToOperator
-
-```solidity
-mapping(bytes32 => address) pubkeyHashToOperator
-```
-
-mapping from pubkey hash to operator address
-
-### NewPubkeyRegistration
-
-```solidity
-event NewPubkeyRegistration(address operator, struct BN254.G1Point pubkeyG1, struct BN254.G2Point pubkeyG2)
-```
-
-Emitted when `operator` registers with the public key `pk`.
-
-### registerBLSPublicKey
-
-```solidity
-function registerBLSPublicKey(uint256 s, struct BN254.G1Point rPoint, struct BN254.G1Point pubkeyG1, struct BN254.G2Point pubkeyG2) external
-```
-
-Called by an operator to register themselves as the owner of a BLS public key and reveal their G1 and G2 public key.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| s | uint256 | is the field element of the operator's Schnorr signature |
-| rPoint | struct BN254.G1Point | is the group element of the operator's Schnorr signature |
-| pubkeyG1 | struct BN254.G1Point | is the the G1 pubkey of the operator |
-| pubkeyG2 | struct BN254.G2Point | is the G2 with the same private key as the pubkeyG1 |
-
diff --git a/docs/docgen/middleware/BLSRegistry.md b/docs/docgen/middleware/BLSRegistry.md
deleted file mode 100644
index 977e8030b4..0000000000
--- a/docs/docgen/middleware/BLSRegistry.md
+++ /dev/null
@@ -1,303 +0,0 @@
-# Solidity API
-
-## BLSRegistry
-
-This contract is used for
-- registering new operators
-- committing to and finalizing de-registration as an operator
-- updating the stakes of the operator
-
-### ZERO_PK_HASH
-
-```solidity
-bytes32 ZERO_PK_HASH
-```
-
-### pubkeyCompendium
-
-```solidity
-contract IBLSPublicKeyCompendium pubkeyCompendium
-```
-
-contract used for looking up operators' BLS public keys
-
-### _apkUpdates
-
-```solidity
-struct IBLSRegistry.ApkUpdate[] _apkUpdates
-```
-
-list of keccak256(apk_x, apk_y) of operators, and the block numbers at which the aggregate
-pubkeys were updated. This occurs whenever a new operator registers or deregisters.
-
-### apk
-
-```solidity
-struct BN254.G1Point apk
-```
-
-used for storing current aggregate public key
-
-_Initialized value of APK is the point at infinity: (0, 0)_
-
-### operatorWhitelister
-
-```solidity
-address operatorWhitelister
-```
-
-the address that can whitelist people
-
-### operatorWhitelistEnabled
-
-```solidity
-bool operatorWhitelistEnabled
-```
-
-toggle of whether the operator whitelist is on or off
-
-### whitelisted
-
-```solidity
-mapping(address => bool) whitelisted
-```
-
-operator => are they whitelisted (can they register with the middleware)
-
-### Registration
-
-```solidity
-event Registration(address operator, bytes32 pkHash, struct BN254.G1Point pk, uint32 apkHashIndex, bytes32 apkHash, string socket)
-```
-
-Emitted upon the registration of a new operator for the middleware
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | Address of the new operator |
-| pkHash | bytes32 | The keccak256 hash of the operator's public key |
-| pk | struct BN254.G1Point | The operator's public key itself |
-| apkHashIndex | uint32 | The index of the latest (i.e. the new) APK update |
-| apkHash | bytes32 | The keccak256 hash of the new Aggregate Public Key |
-| socket | string | |
-
-### OperatorWhitelisterTransferred
-
-```solidity
-event OperatorWhitelisterTransferred(address previousAddress, address newAddress)
-```
-
-Emitted when the `operatorWhitelister` role is transferred.
-
-### onlyOperatorWhitelister
-
-```solidity
-modifier onlyOperatorWhitelister()
-```
-
-Modifier that restricts a function to only be callable by the `whitelister` role.
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IServiceManager _serviceManager, uint8 _NUMBER_OF_QUORUMS, contract IBLSPublicKeyCompendium _pubkeyCompendium) public
-```
-
-### initialize
-
-```solidity
-function initialize(address _operatorWhitelister, bool _operatorWhitelistEnabled, uint256[] _quorumBips, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _firstQuorumStrategiesConsideredAndMultipliers, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _secondQuorumStrategiesConsideredAndMultipliers) public virtual
-```
-
-Initialize the APK, the payment split between quorums, and the quorum strategies + multipliers.
-
-### setOperatorWhitelister
-
-```solidity
-function setOperatorWhitelister(address _operatorWhitelister) external
-```
-
-Called by the service manager owner to transfer the whitelister role to another address
-
-### setOperatorWhitelistStatus
-
-```solidity
-function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external
-```
-
-Callable only by the service manager owner, this function toggles the whitelist on or off
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _operatorWhitelistEnabled | bool | true if turning whitelist on, false otherwise |
-
-### addToOperatorWhitelist
-
-```solidity
-function addToOperatorWhitelist(address[] operators) external
-```
-
-Called by the whitelister, adds a list of operators to the whitelist
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operators | address[] | the operators to add to the whitelist |
-
-### removeFromWhitelist
-
-```solidity
-function removeFromWhitelist(address[] operators) external
-```
-
-Called by the whitelister, removes a list of operators to the whitelist
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operators | address[] | the operators to remove from the whitelist |
-
-### registerOperator
-
-```solidity
-function registerOperator(uint8 operatorType, struct BN254.G1Point pk, string socket) external virtual
-```
-
-called for registering as an operator
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operatorType | uint8 | specifies whether the operator want to register as staker for one or both quorums |
-| pk | struct BN254.G1Point | is the operator's G1 public key |
-| socket | string | is the socket address of the operator |
-
-### _registerOperator
-
-```solidity
-function _registerOperator(address operator, uint8 operatorType, struct BN254.G1Point pk, string socket) internal
-```
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the node who is registering to be a operator |
-| operatorType | uint8 | specifies whether the operator want to register as staker for one or both quorums |
-| pk | struct BN254.G1Point | is the operator's G1 public key |
-| socket | string | is the socket address of the operator |
-
-### deregisterOperator
-
-```solidity
-function deregisterOperator(struct BN254.G1Point pkToRemove, uint32 index) external virtual returns (bool)
-```
-
-Used by an operator to de-register itself from providing service to the middleware.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| pkToRemove | struct BN254.G1Point | is the sender's pubkey in affine coordinates |
-| index | uint32 | is the sender's location in the dynamic array `operatorList` |
-
-### _deregisterOperator
-
-```solidity
-function _deregisterOperator(address operator, struct BN254.G1Point pkToRemove, uint32 index) internal
-```
-
-Used to process de-registering an operator from providing service to the middleware.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | The operator to be deregistered |
-| pkToRemove | struct BN254.G1Point | is the sender's pubkey |
-| index | uint32 | is the sender's location in the dynamic array `operatorList` |
-
-### updateStakes
-
-```solidity
-function updateStakes(address[] operators, uint256[] prevElements) external
-```
-
-Used for updating information on deposits of nodes.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operators | address[] | are the nodes whose deposit information is getting updated |
-| prevElements | uint256[] | are the elements before this middleware in the operator's linked list within the slasher |
-
-### _processApkUpdate
-
-```solidity
-function _processApkUpdate(struct BN254.G1Point newApk) internal returns (bytes32)
-```
-
-Updates the stored APK to `newApk`, calculates its hash, and pushes new entries to the `_apkUpdates` array
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newApk | struct BN254.G1Point | The updated APK. This will be the `apk` after this function runs! |
-
-### _setOperatorWhitelister
-
-```solidity
-function _setOperatorWhitelister(address _operatorWhitelister) internal
-```
-
-### getCorrectApkHash
-
-```solidity
-function getCorrectApkHash(uint256 index, uint32 blockNumber) external view returns (bytes32)
-```
-
-get hash of a historical aggregated public key corresponding to a given index;
-called by checkSignatures in BLSSignatureChecker.sol.
-
-### getApkUpdatesLength
-
-```solidity
-function getApkUpdatesLength() external view returns (uint256)
-```
-
-returns the total number of APK updates that have ever occurred (including one for initializing the pubkey as the generator)
-
-### apkUpdates
-
-```solidity
-function apkUpdates(uint256 index) external view returns (struct IBLSRegistry.ApkUpdate)
-```
-
-returns the `ApkUpdate` struct at `index` in the list of APK updates
-
-### apkHashes
-
-```solidity
-function apkHashes(uint256 index) external view returns (bytes32)
-```
-
-returns the APK hash that resulted from the `index`th APK update
-
-### apkUpdateBlockNumbers
-
-```solidity
-function apkUpdateBlockNumbers(uint256 index) external view returns (uint32)
-```
-
-returns the block number at which the `index`th APK update occurred
-
diff --git a/docs/docgen/middleware/BLSSignatureChecker.md b/docs/docgen/middleware/BLSSignatureChecker.md
deleted file mode 100644
index 79c818bc0c..0000000000
--- a/docs/docgen/middleware/BLSSignatureChecker.md
+++ /dev/null
@@ -1,193 +0,0 @@
-# Solidity API
-
-## BLSSignatureChecker
-
-This is the contract for checking the validity of aggregate operator signatures.
-
-### SignatoryTotals
-
-```solidity
-struct SignatoryTotals {
- uint256 signedStakeFirstQuorum;
- uint256 signedStakeSecondQuorum;
- uint256 totalStakeFirstQuorum;
- uint256 totalStakeSecondQuorum;
-}
-```
-
-### SignatoryRecord
-
-```solidity
-event SignatoryRecord(bytes32 msgHash, uint32 taskNumber, uint256 signedStakeFirstQuorum, uint256 signedStakeSecondQuorum, bytes32[] pubkeyHashes)
-```
-
-used for recording the event that signature has been checked in checkSignatures function.
-
-### registry
-
-```solidity
-contract IQuorumRegistry registry
-```
-
-### constructor
-
-```solidity
-constructor(contract IQuorumRegistry _registry) internal
-```
-
-### BYTE_LENGTH_totalStakeIndex
-
-```solidity
-uint256 BYTE_LENGTH_totalStakeIndex
-```
-
-### BYTE_LENGTH_referenceBlockNumber
-
-```solidity
-uint256 BYTE_LENGTH_referenceBlockNumber
-```
-
-### BYTE_LENGTH_taskNumberToConfirm
-
-```solidity
-uint256 BYTE_LENGTH_taskNumberToConfirm
-```
-
-### BYTE_LENGTH_numberNonSigners
-
-```solidity
-uint256 BYTE_LENGTH_numberNonSigners
-```
-
-### BYTE_LENGTH_G1_POINT
-
-```solidity
-uint256 BYTE_LENGTH_G1_POINT
-```
-
-### BYTE_LENGTH_G2_POINT
-
-```solidity
-uint256 BYTE_LENGTH_G2_POINT
-```
-
-### BYTE_LENGTH_stakeIndex
-
-```solidity
-uint256 BYTE_LENGTH_stakeIndex
-```
-
-### BYTE_LENGTH_NON_SIGNER_INFO
-
-```solidity
-uint256 BYTE_LENGTH_NON_SIGNER_INFO
-```
-
-### BYTE_LENGTH_apkIndex
-
-```solidity
-uint256 BYTE_LENGTH_apkIndex
-```
-
-### BIT_SHIFT_totalStakeIndex
-
-```solidity
-uint256 BIT_SHIFT_totalStakeIndex
-```
-
-### BIT_SHIFT_referenceBlockNumber
-
-```solidity
-uint256 BIT_SHIFT_referenceBlockNumber
-```
-
-### BIT_SHIFT_taskNumberToConfirm
-
-```solidity
-uint256 BIT_SHIFT_taskNumberToConfirm
-```
-
-### BIT_SHIFT_numberNonSigners
-
-```solidity
-uint256 BIT_SHIFT_numberNonSigners
-```
-
-### BIT_SHIFT_stakeIndex
-
-```solidity
-uint256 BIT_SHIFT_stakeIndex
-```
-
-### BIT_SHIFT_apkIndex
-
-```solidity
-uint256 BIT_SHIFT_apkIndex
-```
-
-### CALLDATA_OFFSET_totalStakeIndex
-
-```solidity
-uint256 CALLDATA_OFFSET_totalStakeIndex
-```
-
-### CALLDATA_OFFSET_referenceBlockNumber
-
-```solidity
-uint256 CALLDATA_OFFSET_referenceBlockNumber
-```
-
-### CALLDATA_OFFSET_taskNumberToConfirm
-
-```solidity
-uint256 CALLDATA_OFFSET_taskNumberToConfirm
-```
-
-### CALLDATA_OFFSET_numberNonSigners
-
-```solidity
-uint256 CALLDATA_OFFSET_numberNonSigners
-```
-
-### CALLDATA_OFFSET_NonsignerPubkeys
-
-```solidity
-uint256 CALLDATA_OFFSET_NonsignerPubkeys
-```
-
-### checkSignatures
-
-```solidity
-function checkSignatures(bytes data) public returns (uint32 taskNumberToConfirm, uint32 referenceBlockNumber, bytes32 msgHash, struct BLSSignatureChecker.SignatoryTotals signedTotals, bytes32 compressedSignatoryRecord)
-```
-
-_This calldata is of the format:
-<
-bytes32 msgHash, the taskHash for which disperser is calling checkSignatures
-uint48 index of the totalStake corresponding to the dataStoreId in the 'totalStakeHistory' array of the BLSRegistry
-uint32 blockNumber, the blockNumber at which the task was initated
-uint32 taskNumberToConfirm
-uint32 numberOfNonSigners,
-{uint256[2] pubkeyG1, uint32 stakeIndex}[numberOfNonSigners] the G1 public key and the index to query of `pubkeyHashToStakeHistory` for each nonsigner,
-uint32 apkIndex, the index in the `apkUpdates` array at which we want to load the aggregate public key
-uint256[2] apkG1 (G1 aggregate public key, including nonSigners),
-uint256[4] apkG2 (G2 aggregate public key, not including nonSigners),
-uint256[2] sigma, the aggregate signature itself
->
-
-Before signature verification, the function verifies operator stake information. This includes ensuring that the provided `referenceBlockNumber`
-is correct, i.e., ensure that the stake returned from the specified block number is recent enough and that the stake is either the most recent update
-for the total stake (or the operator) or latest before the referenceBlockNumber.
-The next step involves computing the aggregated pub key of all the operators that are not part of the quorum for this specific taskNumber.
-We use a loop to iterate through the `nonSignerPK` array, loading each individual public key from calldata. Before the loop, we isolate the first public key
-calldataload - this implementation saves us one ecAdd operation, which would be performed in the i=0 iteration otherwise.
-Within the loop, each non-signer public key is loaded from the calldata into memory. The most recent staking-related information is retrieved and is subtracted
-from the total stake of validators in the quorum. Then the aggregate public key and the aggregate non-signer public key is subtracted from it.
-Finally the siganture is verified by computing the elliptic curve pairing._
-
-### _validateOperatorStake
-
-```solidity
-function _validateOperatorStake(struct IQuorumRegistry.OperatorStake opStake, uint32 referenceBlockNumber) internal pure
-```
-
diff --git a/docs/docgen/middleware/PaymentManager.md b/docs/docgen/middleware/PaymentManager.md
deleted file mode 100644
index b61ce3c51f..0000000000
--- a/docs/docgen/middleware/PaymentManager.md
+++ /dev/null
@@ -1,456 +0,0 @@
-# Solidity API
-
-## PaymentManager
-
-This contract is used for doing interactive payment challenges.
-The contract is marked as abstract since it does not implement the `respondToPaymentChallengeFinal`
-function -- see DataLayerPaymentManager for an example
-
-### PAUSED_NEW_PAYMENT_COMMIT
-
-```solidity
-uint8 PAUSED_NEW_PAYMENT_COMMIT
-```
-
-### PAUSED_REDEEM_PAYMENT
-
-```solidity
-uint8 PAUSED_REDEEM_PAYMENT
-```
-
-### paymentFraudproofInterval
-
-```solidity
-uint256 paymentFraudproofInterval
-```
-
-Challenge window for submitting fraudproof in the case of an incorrect payment claim by a registered operator.
-
-### MAX_BIPS
-
-```solidity
-uint256 MAX_BIPS
-```
-
-Constant used as a divisor in dealing with BIPS amounts
-
-### LOW_LEVEL_GAS_BUDGET
-
-```solidity
-uint256 LOW_LEVEL_GAS_BUDGET
-```
-
-Gas budget provided in calls to DelegationTerms contracts
-
-### delegationManager
-
-```solidity
-contract IDelegationManager delegationManager
-```
-
-The global EigenLayer Delegation contract, which is primarily used by
-stakers to delegate their stake to operators who serve as middleware nodes.
-
-_For more details, see DelegationManager.sol._
-
-### serviceManager
-
-```solidity
-contract IServiceManager serviceManager
-```
-
-The ServiceManager contract for this middleware, where tasks are created / initiated.
-
-### registry
-
-```solidity
-contract IQuorumRegistry registry
-```
-
-The Registry contract for this middleware, where operators register and deregister.
-
-### paymentToken
-
-```solidity
-contract IERC20 paymentToken
-```
-
-the ERC20 token that will be used by the disperser to pay the service fees to middleware nodes.
-
-### paymentChallengeToken
-
-```solidity
-contract IERC20 paymentChallengeToken
-```
-
-Token used for placing a guarantee on challenges & payment commits
-
-### paymentChallengeAmount
-
-```solidity
-uint256 paymentChallengeAmount
-```
-
-Specifies the payment that has to be made as a guarantee for fraudproof during payment challenges.
-
-### operatorToPayment
-
-```solidity
-mapping(address => struct IPaymentManager.Payment) operatorToPayment
-```
-
-mapping between the operator and its current committed payment or last redeemed payment
-
-### operatorToPaymentChallenge
-
-```solidity
-mapping(address => struct IPaymentManager.PaymentChallenge) operatorToPaymentChallenge
-```
-
-mapping from operator => PaymentChallenge
-
-### depositsOf
-
-```solidity
-mapping(address => uint256) depositsOf
-```
-
-Deposits of future fees to be drawn against when paying for service from the middleware
-
-### allowances
-
-```solidity
-mapping(address => mapping(address => uint256)) allowances
-```
-
-depositors => addresses approved to spend deposits => allowance
-
-### PaymentChallengeAmountSet
-
-```solidity
-event PaymentChallengeAmountSet(uint256 previousValue, uint256 newValue)
-```
-
-Emitted when the `paymentChallengeAmount` variable is modified
-
-### PaymentCommit
-
-```solidity
-event PaymentCommit(address operator, uint32 fromTaskNumber, uint32 toTaskNumber, uint256 fee)
-```
-
-Emitted when an operator commits to a payment by calling the `commitPayment` function
-
-### PaymentChallengeInit
-
-```solidity
-event PaymentChallengeInit(address operator, address challenger)
-```
-
-Emitted when a new challenge is created through a call to the `initPaymentChallenge` function
-
-### PaymentRedemption
-
-```solidity
-event PaymentRedemption(address operator, uint256 fee)
-```
-
-Emitted when an operator redeems a payment by calling the `redeemPayment` function
-
-### PaymentBreakdown
-
-```solidity
-event PaymentBreakdown(address operator, uint32 fromTaskNumber, uint32 toTaskNumber, uint96 amount1, uint96 amount2)
-```
-
-Emitted when a bisection step is performed in a challenge, through a call to the `performChallengeBisectionStep` function
-
-### PaymentChallengeResolution
-
-```solidity
-event PaymentChallengeResolution(address operator, bool operatorWon)
-```
-
-Emitted upon successful resolution of a payment challenge, within a call to `resolveChallenge`
-
-### OnPayForServiceCallFailure
-
-```solidity
-event OnPayForServiceCallFailure(contract IDelegationTerms delegationTerms, bytes32 returnData)
-```
-
-_Emitted when a low-level call to `delegationTerms.payForService` fails, returning `returnData`_
-
-### onlyServiceManager
-
-```solidity
-modifier onlyServiceManager()
-```
-
-when applied to a function, ensures that the function is only callable by the `serviceManager`
-
-### onlyRegistry
-
-```solidity
-modifier onlyRegistry()
-```
-
-when applied to a function, ensures that the function is only callable by the `registry`
-
-### onlyServiceManagerOwner
-
-```solidity
-modifier onlyServiceManagerOwner()
-```
-
-when applied to a function, ensures that the function is only callable by the owner of the `serviceManager`
-
-### constructor
-
-```solidity
-constructor(contract IDelegationManager _delegationManager, contract IServiceManager _serviceManager, contract IQuorumRegistry _registry, contract IERC20 _paymentToken, contract IERC20 _paymentChallengeToken) internal
-```
-
-### initialize
-
-```solidity
-function initialize(contract IPauserRegistry _pauserReg, uint256 _paymentChallengeAmount) public
-```
-
-### depositFutureFees
-
-```solidity
-function depositFutureFees(address depositFor, uint256 amount) external
-```
-
-deposit one-time fees by the `msg.sender` with this contract to pay for future tasks of this middleware
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositFor | address | could be the `msg.sender` themselves, or a different address for whom `msg.sender` is depositing these future fees |
-| amount | uint256 | is amount of futures fees being deposited |
-
-### setAllowance
-
-```solidity
-function setAllowance(address allowed, uint256 amount) external
-```
-
-Allows the `allowed` address to spend up to `amount` of the `msg.sender`'s funds that have been deposited in this contract
-
-### setPaymentChallengeAmount
-
-```solidity
-function setPaymentChallengeAmount(uint256 _paymentChallengeAmount) external virtual
-```
-
-Modifies the `paymentChallengeAmount` amount.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _paymentChallengeAmount | uint256 | The new value for `paymentChallengeAmount` to take. |
-
-### takeFee
-
-```solidity
-function takeFee(address initiator, address payer, uint256 feeAmount) external virtual
-```
-
-Used for deducting the fees from the payer to the middleware
-
-### commitPayment
-
-```solidity
-function commitPayment(uint32 toTaskNumber, uint96 amount) external
-```
-
-This is used by an operator to make a claim on the amount that they deserve for their service from their last payment until `toTaskNumber`
-
-_Once this payment is recorded, a fraud proof period commences during which a challenger can dispute the proposed payment._
-
-### redeemPayment
-
-```solidity
-function redeemPayment() external
-```
-
-Called by an operator to redeem a payment that they previously 'committed' to by calling `commitPayment`.
-
-_This function can only be called after the challenge window for the payment claim has completed._
-
-### _payForServiceHook
-
-```solidity
-function _payForServiceHook(contract IDelegationTerms dt, uint256 amount) internal
-```
-
-### initPaymentChallenge
-
-```solidity
-function initPaymentChallenge(address operator, uint96 amount1, uint96 amount2) external
-```
-
-This function is called by a fraud prover to challenge a payment, initiating an interactive-type fraudproof.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator against whose payment claim the fraudproof is being made |
-| amount1 | uint96 | is the reward amount the challenger in that round claims is for the first half of tasks |
-| amount2 | uint96 | is the reward amount the challenger in that round claims is for the second half of tasks |
-
-### performChallengeBisectionStep
-
-```solidity
-function performChallengeBisectionStep(address operator, bool secondHalf, uint96 amount1, uint96 amount2) external
-```
-
-Perform a single bisection step in an existing interactive payment challenge.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | The middleware operator who was challenged (used to look up challenge details) |
-| secondHalf | bool | If true, then the caller wishes to challenge the amount claimed as payment in the *second half* of the previous bisection step. If false then the *first half* is indicated instead. |
-| amount1 | uint96 | The amount that the caller asserts the operator is entitled to, for the first half *of the challenged half* of the previous bisection. |
-| amount2 | uint96 | The amount that the caller asserts the operator is entitled to, for the second half *of the challenged half* of the previous bisection. |
-
-### _updateStatus
-
-```solidity
-function _updateStatus(address operator, uint32 diff) internal returns (bool)
-```
-
-This function is used for updating the status of the challenge in terms of who has to respon
-to the interactive challenge mechanism next - is it going to be challenger or the operator.
-
-_If the challenge is over only one task, then the challenge is marked specially as a one step challenge –
-the smallest unit over which a challenge can be proposed – and 'true' is returned.
-Otherwise status is updated normally and 'false' is returned._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator whose payment claim is being challenged |
-| diff | uint32 | is the number of tasks across which payment is being challenged in this iteration |
-
-### _updateChallengeAmounts
-
-```solidity
-function _updateChallengeAmounts(address operator, enum IPaymentManager.DissectionType dissectionType, uint96 amount1, uint96 amount2) internal
-```
-
-Used to update challenge amounts when the operator (or challenger) breaks down the challenged amount (single bisection step)
-
-### resolveChallenge
-
-```solidity
-function resolveChallenge(address operator) external
-```
-
-resolve an existing PaymentChallenge for an operator
-
-### _resolve
-
-```solidity
-function _resolve(struct IPaymentManager.PaymentChallenge challenge, address winner) internal
-```
-
-Resolves a single payment challenge, paying the winner.
-
-_If challenger is proven correct, then they are refunded their own challengeAmount plus the challengeAmount put up by the operator.
-If operator is proven correct, then the challenger's challengeAmount is transferred to them, since the operator still hasn't been
-proven right, and thus their challengeAmount is still required in case they are challenged again._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| challenge | struct IPaymentManager.PaymentChallenge | The challenge that is being resolved. |
-| winner | address | Address of the winner of the challenge. |
-
-### getChallengeStatus
-
-```solidity
-function getChallengeStatus(address operator) external view returns (enum IPaymentManager.ChallengeStatus)
-```
-
-Returns the ChallengeStatus for the `operator`'s payment claim.
-
-### getAmount1
-
-```solidity
-function getAmount1(address operator) external view returns (uint96)
-```
-
-Returns the 'amount1' for the `operator`'s payment claim.
-
-### getAmount2
-
-```solidity
-function getAmount2(address operator) external view returns (uint96)
-```
-
-Returns the 'amount2' for the `operator`'s payment claim.
-
-### getToTaskNumber
-
-```solidity
-function getToTaskNumber(address operator) external view returns (uint48)
-```
-
-Returns the 'toTaskNumber' for the `operator`'s payment claim.
-
-### getFromTaskNumber
-
-```solidity
-function getFromTaskNumber(address operator) external view returns (uint48)
-```
-
-Returns the 'fromTaskNumber' for the `operator`'s payment claim.
-
-### getDiff
-
-```solidity
-function getDiff(address operator) external view returns (uint48)
-```
-
-Returns the task number difference for the `operator`'s payment claim.
-
-### getPaymentChallengeAmount
-
-```solidity
-function getPaymentChallengeAmount(address operator) external view returns (uint256)
-```
-
-Returns the active challengeAmount of the `operator` placed on their payment claim.
-
-### _taskNumber
-
-```solidity
-function _taskNumber() internal view returns (uint32)
-```
-
-Convenience function for fetching the current taskNumber from the `serviceManager`
-
-### _setPaymentChallengeAmount
-
-```solidity
-function _setPaymentChallengeAmount(uint256 _paymentChallengeAmount) internal
-```
-
-Modifies the `paymentChallengeAmount` amount.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _paymentChallengeAmount | uint256 | The new value for `paymentChallengeAmount` to take. |
-
diff --git a/docs/docgen/middleware/RegistryBase.md b/docs/docgen/middleware/RegistryBase.md
deleted file mode 100644
index bb72418083..0000000000
--- a/docs/docgen/middleware/RegistryBase.md
+++ /dev/null
@@ -1,461 +0,0 @@
-# Solidity API
-
-## RegistryBase
-
-This contract is used for
-- registering new operators
-- committing to and finalizing de-registration as an operator
-- updating the stakes of the operator
-
-_This contract is missing key functions. See `BLSRegistry` or `ECDSARegistry` for examples that inherit from this contract._
-
-### minimumStakeFirstQuorum
-
-```solidity
-uint128 minimumStakeFirstQuorum
-```
-
-In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as
-evaluated by this contract's 'VoteWeigher' logic.
-
-### minimumStakeSecondQuorum
-
-```solidity
-uint128 minimumStakeSecondQuorum
-```
-
-### registry
-
-```solidity
-mapping(address => struct IQuorumRegistry.Operator) registry
-```
-
-used for storing Operator info on each operator while registration
-
-### operatorList
-
-```solidity
-address[] operatorList
-```
-
-used for storing the list of current and past registered operators
-
-### totalStakeHistory
-
-```solidity
-struct IQuorumRegistry.OperatorStake[] totalStakeHistory
-```
-
-array of the history of the total stakes -- marked as internal since getTotalStakeFromIndex is a getter for this
-
-### totalOperatorsHistory
-
-```solidity
-struct IQuorumRegistry.OperatorIndex[] totalOperatorsHistory
-```
-
-array of the history of the number of operators, and the taskNumbers at which the number of operators changed
-
-### pubkeyHashToStakeHistory
-
-```solidity
-mapping(bytes32 => struct IQuorumRegistry.OperatorStake[]) pubkeyHashToStakeHistory
-```
-
-mapping from operator's pubkeyhash to the history of their stake updates
-
-### pubkeyHashToIndexHistory
-
-```solidity
-mapping(bytes32 => struct IQuorumRegistry.OperatorIndex[]) pubkeyHashToIndexHistory
-```
-
-mapping from operator's pubkeyhash to the history of their index in the array of all operators
-
-### SocketUpdate
-
-```solidity
-event SocketUpdate(address operator, string socket)
-```
-
-emitted when `operator` updates their socket address to `socket`
-
-### StakeUpdate
-
-```solidity
-event StakeUpdate(address operator, uint96 firstQuorumStake, uint96 secondQuorumStake, uint32 updateBlockNumber, uint32 prevUpdateBlockNumber)
-```
-
-emitted whenever the stake of `operator` is updated
-
-### Deregistration
-
-```solidity
-event Deregistration(address operator, address swapped)
-```
-
-Emitted whenever an operator deregisters.
-The `swapped` address is the address returned by an internal call to the `_popRegistrant` function.
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IServiceManager _serviceManager, uint8 _NUMBER_OF_QUORUMS) internal
-```
-
-Irrevocably sets the (immutable) `delegation` & `strategyManager` addresses, and `NUMBER_OF_QUORUMS` variable.
-
-### _initialize
-
-```solidity
-function _initialize(uint256[] _quorumBips, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _firstQuorumStrategiesConsideredAndMultipliers, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _secondQuorumStrategiesConsideredAndMultipliers) internal virtual
-```
-
-Adds empty first entries to the dynamic arrays `totalStakeHistory` and `totalOperatorsHistory`,
-to record an initial condition of zero operators with zero total stake.
-Adds `_firstQuorumStrategiesConsideredAndMultipliers` and `_secondQuorumStrategiesConsideredAndMultipliers` to the dynamic arrays
-`strategiesConsideredAndMultipliers[0]` and `strategiesConsideredAndMultipliers[1]` (i.e. to the weighing functions of the quorums)
-
-### getOperatorIndex
-
-```solidity
-function getOperatorIndex(address operator, uint32 blockNumber, uint32 index) external view returns (uint32)
-```
-
-Looks up the `operator`'s index in the dynamic array `operatorList` at the specified `blockNumber`.
-
-_Function will revert in the event that the specified `index` input does not identify the appropriate entry in the
-array `pubkeyHashToIndexHistory[pubkeyHash]` to pull the info from._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | |
-| blockNumber | uint32 | Is the desired block number at which we wish to query the operator's position in the `operatorList` array |
-| index | uint32 | Used to specify the entry within the dynamic array `pubkeyHashToIndexHistory[pubkeyHash]` to read data from, where `pubkeyHash` is looked up from `operator`'s registration info |
-
-### getTotalOperators
-
-```solidity
-function getTotalOperators(uint32 blockNumber, uint32 index) external view returns (uint32)
-```
-
-Looks up the number of total operators at the specified `blockNumber`.
-
-_This function will revert if the provided `index` is out of bounds._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| blockNumber | uint32 | |
-| index | uint32 | Input used to specify the entry within the dynamic array `totalOperatorsHistory` to read data from. |
-
-### isActiveOperator
-
-```solidity
-function isActiveOperator(address operator) external view virtual returns (bool)
-```
-
-Returns whether or not the `operator` is currently an active operator, i.e. is "registered".
-
-### getOperatorPubkeyHash
-
-```solidity
-function getOperatorPubkeyHash(address operator) public view returns (bytes32)
-```
-
-Returns the stored pubkeyHash for the specified `operator`.
-
-### getStakeFromPubkeyHashAndIndex
-
-```solidity
-function getStakeFromPubkeyHashAndIndex(bytes32 pubkeyHash, uint256 index) external view returns (struct IQuorumRegistry.OperatorStake)
-```
-
-Returns the stake weight corresponding to `pubkeyHash`, at the
-`index`-th entry in the `pubkeyHashToStakeHistory[pubkeyHash]` array.
-
-_Function will revert if `index` is out-of-bounds._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| pubkeyHash | bytes32 | Hash of the public key of the operator of interest. |
-| index | uint256 | Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash]`. |
-
-### checkOperatorActiveAtBlockNumber
-
-```solidity
-function checkOperatorActiveAtBlockNumber(address operator, uint256 blockNumber, uint256 stakeHistoryIndex) external view returns (bool)
-```
-
-Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
-
-_In order for this function to return 'true', the inputs must satisfy all of the following list:
-1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
-2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
-is must be strictly greater than `blockNumber`
-3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
-or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
-Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a
-bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator of interest |
-| blockNumber | uint256 | is the block number of interest |
-| stakeHistoryIndex | uint256 | specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up in `registry[operator].pubkeyHash` |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | 'true' if it is successfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise |
-
-### checkOperatorInactiveAtBlockNumber
-
-```solidity
-function checkOperatorInactiveAtBlockNumber(address operator, uint256 blockNumber, uint256 stakeHistoryIndex) external view returns (bool)
-```
-
-Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
-
-_In order for this function to return 'true', the inputs must satisfy all of the following list:
-1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
-2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
-is must be strictly greater than `blockNumber`
-3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
-or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
-Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a
-bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the operator of interest |
-| blockNumber | uint256 | is the block number of interest |
-| stakeHistoryIndex | uint256 | specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up in `registry[operator].pubkeyHash` |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | bool | 'true' if it is successfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise |
-
-### getMostRecentStakeByOperator
-
-```solidity
-function getMostRecentStakeByOperator(address operator) public view returns (struct IQuorumRegistry.OperatorStake)
-```
-
-Returns the most recent stake weight for the `operator`
-
-_Function returns an OperatorStake struct with **every entry equal to 0** in the event that the operator has no stake history_
-
-### getStakeHistoryLength
-
-```solidity
-function getStakeHistoryLength(bytes32 pubkeyHash) external view returns (uint256)
-```
-
-### firstQuorumStakedByOperator
-
-```solidity
-function firstQuorumStakedByOperator(address operator) external view returns (uint96)
-```
-
-### secondQuorumStakedByOperator
-
-```solidity
-function secondQuorumStakedByOperator(address operator) external view returns (uint96)
-```
-
-### operatorStakes
-
-```solidity
-function operatorStakes(address operator) public view returns (uint96, uint96)
-```
-
-Returns the most recent stake weights for the `operator`
-
-_Function returns weights of **0** in the event that the operator has no stake history_
-
-### totalStake
-
-```solidity
-function totalStake() external view returns (uint96, uint96)
-```
-
-Returns the stake amounts from the latest entry in `totalStakeHistory`.
-
-### getLengthOfPubkeyHashStakeHistory
-
-```solidity
-function getLengthOfPubkeyHashStakeHistory(bytes32 pubkeyHash) external view returns (uint256)
-```
-
-### getLengthOfPubkeyHashIndexHistory
-
-```solidity
-function getLengthOfPubkeyHashIndexHistory(bytes32 pubkeyHash) external view returns (uint256)
-```
-
-### getLengthOfTotalStakeHistory
-
-```solidity
-function getLengthOfTotalStakeHistory() external view returns (uint256)
-```
-
-### getLengthOfTotalOperatorsHistory
-
-```solidity
-function getLengthOfTotalOperatorsHistory() external view returns (uint256)
-```
-
-### getTotalStakeFromIndex
-
-```solidity
-function getTotalStakeFromIndex(uint256 index) external view returns (struct IQuorumRegistry.OperatorStake)
-```
-
-Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory`.
-
-_Function will revert in the event that `index` is out-of-bounds._
-
-### getFromTaskNumberForOperator
-
-```solidity
-function getFromTaskNumberForOperator(address operator) external view returns (uint32)
-```
-
-Returns task number from when `operator` has been registered.
-
-### numOperators
-
-```solidity
-function numOperators() public view returns (uint32)
-```
-
-Returns the current number of operators of this service.
-
-### setMinimumStakeFirstQuorum
-
-```solidity
-function setMinimumStakeFirstQuorum(uint128 _minimumStakeFirstQuorum) external
-```
-
-Adjusts the `minimumStakeFirstQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 1st quorum.
-
-### setMinimumStakeSecondQuorum
-
-```solidity
-function setMinimumStakeSecondQuorum(uint128 _minimumStakeSecondQuorum) external
-```
-
-Adjusts the `minimumStakeSecondQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 2nd quorum.
-
-### updateSocket
-
-```solidity
-function updateSocket(string newSocket) external
-```
-
-### _updateTotalOperatorsHistory
-
-```solidity
-function _updateTotalOperatorsHistory() internal
-```
-
-Called when the total number of operators has changed.
-Sets the `toBlockNumber` field on the last entry *so far* in thedynamic array `totalOperatorsHistory` to the current `block.number`,
-recording that the previous entry is *no longer the latest* and the block number at which the next was added.
-Pushes a new entry to `totalOperatorsHistory`, with `index` field set equal to the new amount of operators, recording the new number
-of total operators (and leaving the `toBlockNumber` field at zero, signaling that this is the latest entry in the array)
-
-### _removeOperator
-
-```solidity
-function _removeOperator(address operator, bytes32 pubkeyHash, uint32 index) internal virtual
-```
-
-Remove the operator from active status. Removes the operator with the given `pubkeyHash` from the `index` in `operatorList`,
-updates operatorList and index histories, and performs other necessary updates for removing operator
-
-### _removeOperatorStake
-
-```solidity
-function _removeOperatorStake(bytes32 pubkeyHash) internal returns (uint32)
-```
-
-Removes the stakes of the operator with pubkeyHash `pubkeyHash`
-
-### _popRegistrant
-
-```solidity
-function _popRegistrant(uint32 index) internal returns (address swappedOperator)
-```
-
-Removes the registrant at the given `index` from the `operatorList`
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| swappedOperator | address | is the operator who was swapped with the removed operator in the operatorList, or the *zero address* in the case that the removed operator was already the list operator in the operatorList. |
-
-### _addRegistrant
-
-```solidity
-function _addRegistrant(address operator, bytes32 pubkeyHash, struct IQuorumRegistry.OperatorStake _operatorStake) internal virtual
-```
-
-Adds the Operator `operator` with the given `pubkeyHash` to the `operatorList` and performs necessary related updates.
-
-### _registrationStakeEvaluation
-
-```solidity
-function _registrationStakeEvaluation(address operator, uint8 operatorType) internal returns (struct IQuorumRegistry.OperatorStake)
-```
-
-Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`.
-
-_This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere._
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | struct IQuorumRegistry.OperatorStake | The newly calculated `OperatorStake` for `operator`, stored in memory but not yet committed to storage. |
-
-### _updateOperatorStake
-
-```solidity
-function _updateOperatorStake(address operator, bytes32 pubkeyHash, struct IQuorumRegistry.OperatorStake currentOperatorStake, uint256 insertAfter) internal returns (struct IQuorumRegistry.OperatorStake updatedOperatorStake)
-```
-
-Finds the updated stake for `operator`, stores it and records the update.
-
-_**DOES NOT UPDATE `totalStake` IN ANY WAY** -- `totalStake` updates must be done elsewhere._
-
-### _recordTotalStakeUpdate
-
-```solidity
-function _recordTotalStakeUpdate(struct IQuorumRegistry.OperatorStake _totalStake) internal
-```
-
-Records that the `totalStake` is now equal to the input param @_totalStake
-
-### _deregistrationCheck
-
-```solidity
-function _deregistrationCheck(address operator, uint32 index) internal view
-```
-
-Verify that the `operator` is an active operator and that they've provided the correct `index`
-
diff --git a/docs/docgen/middleware/VoteWeigherBase.md b/docs/docgen/middleware/VoteWeigherBase.md
deleted file mode 100644
index 378e3ffbd6..0000000000
--- a/docs/docgen/middleware/VoteWeigherBase.md
+++ /dev/null
@@ -1,120 +0,0 @@
-# Solidity API
-
-## VoteWeigherBase
-
-This contract is used for
-- computing the total weight of an operator for any of the quorums that are considered
-by the middleware
-- addition and removal of strategies and the associated weighting criteria that are assigned
-by the middleware for each of the quorum(s)
-@dev
-
-### StrategyAddedToQuorum
-
-```solidity
-event StrategyAddedToQuorum(uint256 quorumNumber, contract IStrategy strategy)
-```
-
-emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]`
-
-### StrategyRemovedFromQuorum
-
-```solidity
-event StrategyRemovedFromQuorum(uint256 quorumNumber, contract IStrategy strategy)
-```
-
-emitted when `strategy` has removed from the array at `strategiesConsideredAndMultipliers[quorumNumber]`
-
-### onlyServiceManagerOwner
-
-```solidity
-modifier onlyServiceManagerOwner()
-```
-
-when applied to a function, ensures that the function is only callable by the current `owner` of the `serviceManager`
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IServiceManager _serviceManager, uint8 _NUMBER_OF_QUORUMS) internal
-```
-
-Sets the (immutable) `strategyManager` and `serviceManager` addresses, as well as the (immutable) `NUMBER_OF_QUORUMS` variable
-
-### _initialize
-
-```solidity
-function _initialize(uint256[] _quorumBips) internal virtual
-```
-
-Set the split in earnings between the different quorums.
-
-### weightOfOperator
-
-```solidity
-function weightOfOperator(address operator, uint256 quorumNumber) public virtual returns (uint96)
-```
-
-This function computes the total weight of the @param operator in the quorum @param quorumNumber.
-
-_returns zero in the case that `quorumNumber` is greater than or equal to `NUMBER_OF_QUORUMS`_
-
-### addStrategiesConsideredAndMultipliers
-
-```solidity
-function addStrategiesConsideredAndMultipliers(uint256 quorumNumber, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _newStrategiesConsideredAndMultipliers) external virtual
-```
-
-Adds new strategies and the associated multipliers to the @param quorumNumber.
-
-### removeStrategiesConsideredAndMultipliers
-
-```solidity
-function removeStrategiesConsideredAndMultipliers(uint256 quorumNumber, contract IStrategy[] _strategiesToRemove, uint256[] indicesToRemove) external virtual
-```
-
-This function is used for removing strategies and their associated weights from the
-mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber.
-
-_higher indices should be *first* in the list of @param indicesToRemove, since otherwise
-the removal of lower index entries will cause a shift in the indices of the other strategiesToRemove_
-
-### modifyStrategyWeights
-
-```solidity
-function modifyStrategyWeights(uint256 quorumNumber, uint256[] strategyIndices, uint96[] newMultipliers) external virtual
-```
-
-This function is used for modifying the weights of strategies that are already in the
-mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| quorumNumber | uint256 | |
-| strategyIndices | uint256[] | is a correctness-check input -- the supplied values must match the indices of the strategiesToModifyWeightsOf in strategiesConsideredAndMultipliers[quorumNumber] |
-| newMultipliers | uint96[] | |
-
-### strategiesConsideredAndMultipliersLength
-
-```solidity
-function strategiesConsideredAndMultipliersLength(uint256 quorumNumber) public view returns (uint256)
-```
-
-Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`.
-
-_Reverts if `quorumNumber` < `NUMBER_OF_QUORUMS`, i.e. the input is out of bounds._
-
-### _addStrategiesConsideredAndMultipliers
-
-```solidity
-function _addStrategiesConsideredAndMultipliers(uint256 quorumNumber, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _newStrategiesConsideredAndMultipliers) internal
-```
-
-Adds `_newStrategiesConsideredAndMultipliers` to the `quorumNumber`-th quorum.
-
-_Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies).
-This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a concious choice,
-since a middleware may want, e.g., a stablecoin quorum that accepts USDC, USDT, DAI, etc. as underlying assets and trades them as "equivalent"._
-
diff --git a/docs/docgen/middleware/VoteWeigherBaseStorage.md b/docs/docgen/middleware/VoteWeigherBaseStorage.md
deleted file mode 100644
index cca96e8ef3..0000000000
--- a/docs/docgen/middleware/VoteWeigherBaseStorage.md
+++ /dev/null
@@ -1,104 +0,0 @@
-# Solidity API
-
-## VoteWeigherBaseStorage
-
-This storage contract is separate from the logic to simplify the upgrade process.
-
-### StrategyAndWeightingMultiplier
-
-```solidity
-struct StrategyAndWeightingMultiplier {
- contract IStrategy strategy;
- uint96 multiplier;
-}
-```
-
-### WEIGHTING_DIVISOR
-
-```solidity
-uint256 WEIGHTING_DIVISOR
-```
-
-Constant used as a divisor in calculating weights.
-
-### MAX_WEIGHING_FUNCTION_LENGTH
-
-```solidity
-uint8 MAX_WEIGHING_FUNCTION_LENGTH
-```
-
-Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping.
-
-### MAX_BIPS
-
-```solidity
-uint256 MAX_BIPS
-```
-
-Constant used as a divisor in dealing with BIPS amounts.
-
-### delegation
-
-```solidity
-contract IDelegationManager delegation
-```
-
-The address of the Delegation contract for EigenLayer.
-
-### strategyManager
-
-```solidity
-contract IStrategyManager strategyManager
-```
-
-The address of the StrategyManager contract for EigenLayer.
-
-### slasher
-
-```solidity
-contract ISlasher slasher
-```
-
-The address of the Slasher contract for EigenLayer.
-
-### serviceManager
-
-```solidity
-contract IServiceManager serviceManager
-```
-
-The ServiceManager contract for this middleware, where tasks are created / initiated.
-
-### NUMBER_OF_QUORUMS
-
-```solidity
-uint256 NUMBER_OF_QUORUMS
-```
-
-Number of quorums that are being used by the middleware.
-
-### strategiesConsideredAndMultipliers
-
-```solidity
-mapping(uint256 => struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[]) strategiesConsideredAndMultipliers
-```
-
-mapping from quorum number to the list of strategies considered and their
-corresponding multipliers for that specific quorum
-
-### quorumBips
-
-```solidity
-mapping(uint256 => uint256) quorumBips
-```
-
-This defines the earnings split between different quorums. Mapping is quorumNumber => BIPS which the quorum earns, out of the total earnings.
-
-_The sum of all entries, i.e. sum(quorumBips[0] through quorumBips[NUMBER_OF_QUORUMS - 1]) should *always* be 10,000!_
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IServiceManager _serviceManager, uint8 _NUMBER_OF_QUORUMS) internal
-```
-
diff --git a/docs/docgen/middleware/example/ECDSARegistry.md b/docs/docgen/middleware/example/ECDSARegistry.md
deleted file mode 100644
index 15486642f7..0000000000
--- a/docs/docgen/middleware/example/ECDSARegistry.md
+++ /dev/null
@@ -1,205 +0,0 @@
-# Solidity API
-
-## ECDSARegistry
-
-This contract is used for
-- registering new operators
-- committing to and finalizing de-registration as an operator
-- updating the stakes of the operator
-
-### operatorWhitelister
-
-```solidity
-address operatorWhitelister
-```
-
-the address that can whitelist people
-
-### operatorWhitelistEnabled
-
-```solidity
-bool operatorWhitelistEnabled
-```
-
-toggle of whether the operator whitelist is on or off
-
-### whitelisted
-
-```solidity
-mapping(address => bool) whitelisted
-```
-
-operator => are they whitelisted (can they register with the middleware)
-
-### Registration
-
-```solidity
-event Registration(address operator, string socket)
-```
-
-Emitted upon the registration of a new operator for the middleware
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | Address of the new operator |
-| socket | string | The ip:port of the operator |
-
-### OperatorWhitelisterTransferred
-
-```solidity
-event OperatorWhitelisterTransferred(address previousAddress, address newAddress)
-```
-
-Emitted when the `operatorWhitelister` role is transferred.
-
-### onlyOperatorWhitelister
-
-```solidity
-modifier onlyOperatorWhitelister()
-```
-
-Modifier that restricts a function to only be callable by the `whitelister` role.
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IServiceManager _serviceManager) public
-```
-
-### initialize
-
-```solidity
-function initialize(address _operatorWhitelister, bool _operatorWhitelistEnabled, uint256[] _quorumBips, struct VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] _quorumStrategiesConsideredAndMultipliers) public virtual
-```
-
-Initialize whitelister and the quorum strategies + multipliers.
-
-### setOperatorWhitelister
-
-```solidity
-function setOperatorWhitelister(address _operatorWhitelister) external
-```
-
-Called by the service manager owner to transfer the whitelister role to another address
-
-### setOperatorWhitelistStatus
-
-```solidity
-function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external
-```
-
-Callable only by the service manager owner, this function toggles the whitelist on or off
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _operatorWhitelistEnabled | bool | true if turning whitelist on, false otherwise |
-
-### addToOperatorWhitelist
-
-```solidity
-function addToOperatorWhitelist(address[] operators) external
-```
-
-Called by the operatorWhitelister, adds a list of operators to the whitelist
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operators | address[] | the operators to add to the whitelist |
-
-### removeFromWhitelist
-
-```solidity
-function removeFromWhitelist(address[] operators) external
-```
-
-Called by the operatorWhitelister, removes a list of operators to the whitelist
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operators | address[] | the operators to remove from the whitelist |
-
-### registerOperator
-
-```solidity
-function registerOperator(string socket) external virtual
-```
-
-called for registering as an operator
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| socket | string | is the socket address of the operator |
-
-### _registerOperator
-
-```solidity
-function _registerOperator(address operator, string socket) internal
-```
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | is the node who is registering to be a operator |
-| socket | string | is the socket address of the operator |
-
-### deregisterOperator
-
-```solidity
-function deregisterOperator(uint32 index) external virtual returns (bool)
-```
-
-Used by an operator to de-register itself from providing service to the middleware.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| index | uint32 | is the sender's location in the dynamic array `operatorList` |
-
-### _deregisterOperator
-
-```solidity
-function _deregisterOperator(address operator, uint32 index) internal
-```
-
-Used to process de-registering an operator from providing service to the middleware.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operator | address | The operator to be deregistered |
-| index | uint32 | is the sender's location in the dynamic array `operatorList` |
-
-### updateStakes
-
-```solidity
-function updateStakes(address[] operators, uint256[] prevElements) external
-```
-
-Used for updating information on deposits of nodes.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| operators | address[] | are the nodes whose deposit information is getting updated |
-| prevElements | uint256[] | are the elements before this middleware in the operator's linked list within the slasher |
-
-### _setOperatorWhitelister
-
-```solidity
-function _setOperatorWhitelister(address _operatorWhitelister) internal
-```
-
diff --git a/docs/docgen/middleware/example/HashThreshold.md b/docs/docgen/middleware/example/HashThreshold.md
deleted file mode 100644
index 99cc3011d1..0000000000
--- a/docs/docgen/middleware/example/HashThreshold.md
+++ /dev/null
@@ -1,151 +0,0 @@
-# Solidity API
-
-## HashThreshold
-
-### disputePeriodBlocks
-
-```solidity
-uint32 disputePeriodBlocks
-```
-
-### numZeroes
-
-```solidity
-uint8 numZeroes
-```
-
-### slasher
-
-```solidity
-contract ISlasher slasher
-```
-
-### registry
-
-```solidity
-contract ECDSARegistry registry
-```
-
-### CertifiedMessageMetadata
-
-```solidity
-struct CertifiedMessageMetadata {
- bytes32 signaturesHash;
- uint32 validAfterBlock;
-}
-```
-
-### taskNumber
-
-```solidity
-uint32 taskNumber
-```
-
-Returns the current 'taskNumber' for the middleware
-
-### latestServeUntilBlock
-
-```solidity
-uint32 latestServeUntilBlock
-```
-
-Returns the latest block until which operators must serve.
-
-### certifiedMessageMetadatas
-
-```solidity
-mapping(bytes32 => struct HashThreshold.CertifiedMessageMetadata) certifiedMessageMetadatas
-```
-
-### MessageCertified
-
-```solidity
-event MessageCertified(bytes32)
-```
-
-### onlyRegistry
-
-```solidity
-modifier onlyRegistry()
-```
-
-### constructor
-
-```solidity
-constructor(contract ISlasher _slasher, contract ECDSARegistry _registry) public
-```
-
-### owner
-
-```solidity
-function owner() public view returns (address)
-```
-
-### decaHash
-
-```solidity
-function decaHash(bytes32 message) public pure returns (bytes32)
-```
-
-### submitSignatures
-
-```solidity
-function submitSignatures(bytes32 message, bytes signatures) external
-```
-
-This function is called by anyone to certify a message. Signers are certifying that the decahashed message starts with at least `numZeros` 0s.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| message | bytes32 | The message to certify |
-| signatures | bytes | The signatures of the message, certifying it |
-
-### slashSigners
-
-```solidity
-function slashSigners(bytes32 message, bytes signatures) external
-```
-
-This function is called by anyone to slash the signers of an invalid message that has been certified.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| message | bytes32 | The message to slash the signers of |
-| signatures | bytes | The signatures that certified the message |
-
-### freezeOperator
-
-```solidity
-function freezeOperator(address operator) external
-```
-
-Permissioned function that causes the ServiceManager to freeze the operator on EigenLayer, through a call to the Slasher contract
-
-### recordFirstStakeUpdate
-
-```solidity
-function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external
-```
-
-Permissioned function to have the ServiceManager forward a call to the slasher, recording an initial stake update (on operator registration)
-
-### recordLastStakeUpdateAndRevokeSlashingAbility
-
-```solidity
-function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external
-```
-
-Permissioned function to have the ServiceManager forward a call to the slasher, recording a final stake update (on operator deregistration)
-
-### recordStakeUpdate
-
-```solidity
-function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external
-```
-
-Permissioned function to have the ServiceManager forward a call to the slasher, recording a stake update
-
diff --git a/docs/docgen/operators/MerkleDelegationTerms.md b/docs/docgen/operators/MerkleDelegationTerms.md
deleted file mode 100644
index 166437dd80..0000000000
--- a/docs/docgen/operators/MerkleDelegationTerms.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# Solidity API
-
-## MerkleDelegationTerms
-
-This contract specifies the delegation terms of a given operator. When a staker delegates its stake to an operator,
-it has to agrees to the terms set in the operator's 'Delegation Terms' contract. Payments to an operator are routed through
-their specified 'Delegation Terms' contract for subsequent distribution of earnings to individual stakers.
-There are also hooks that call into an operator's DelegationTerms contract when a staker delegates to or undelegates from
-the operator.
-
-_This contract uses a system in which the operator posts roots of a *sparse Merkle tree*. Each leaf of the tree is expected
-to contain the **cumulative** earnings of a staker. This will reduce the total number of actions that stakers who claim only rarely
-have to take, while allowing stakers to claim their earnings as often as new Merkle roots are posted._
-
-### TokenAndAmount
-
-```solidity
-struct TokenAndAmount {
- contract IERC20 token;
- uint256 amount;
-}
-```
-
-### MerkleRootAndTreeHeight
-
-```solidity
-struct MerkleRootAndTreeHeight {
- bytes32 root;
- uint256 height;
-}
-```
-
-### MAX_HEIGHT
-
-```solidity
-uint256 MAX_HEIGHT
-```
-
-### cumulativeClaimedByStakerOfToken
-
-```solidity
-mapping(address => mapping(contract IERC20 => uint256)) cumulativeClaimedByStakerOfToken
-```
-
-staker => token => cumulative amount *claimed*
-
-### merkleRoots
-
-```solidity
-struct MerkleDelegationTerms.MerkleRootAndTreeHeight[] merkleRoots
-```
-
-Array of Merkle roots with heights, each posted by the operator (contract owner)
-
-### NewMerkleRootPosted
-
-```solidity
-event NewMerkleRootPosted(bytes32 newRoot, uint256 height)
-```
-
-### operatorWithdrawal
-
-```solidity
-function operatorWithdrawal(struct MerkleDelegationTerms.TokenAndAmount[] tokensAndAmounts) external
-```
-
-Used by the operator to withdraw tokens directly from this contract.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| tokensAndAmounts | struct MerkleDelegationTerms.TokenAndAmount[] | ERC20 tokens to withdraw and the amount of each respective ERC20 token to withdraw. |
-
-### postMerkleRoot
-
-```solidity
-function postMerkleRoot(bytes32 newRoot, uint256 height) external
-```
-
-Used by the operator to post an updated root of the stakers' all-time earnings
-
-### proveEarningsAndWithdraw
-
-```solidity
-function proveEarningsAndWithdraw(struct MerkleDelegationTerms.TokenAndAmount[] tokensAndAmounts, bytes proof, uint256 nodeIndex, uint256 rootIndex) external
-```
-
-Called by a staker to prove the inclusion of their earnings in a Merkle root (posted by the operator) and claim them.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| tokensAndAmounts | struct MerkleDelegationTerms.TokenAndAmount[] | ERC20 tokens to withdraw and the amount of each respective ERC20 token to withdraw. |
-| proof | bytes | Merkle proof showing that a leaf containing `(msg.sender, tokensAndAmounts)` was included in the `rootIndex`-th Merkle root posted by the operator. |
-| nodeIndex | uint256 | Specifies the node inside the Merkle tree corresponding to the specified root, `merkleRoots[rootIndex].root`. |
-| rootIndex | uint256 | Specifies the Merkle root to look up, using `merkleRoots[rootIndex]` |
-
-### calculateLeafHash
-
-```solidity
-function calculateLeafHash(address staker, struct MerkleDelegationTerms.TokenAndAmount[] tokensAndAmounts) public pure returns (bytes32)
-```
-
-Helper function for calculating a leaf in a Merkle tree formatted as `(address staker, TokenAndAmount[] calldata tokensAndAmounts)`
-
-### payForService
-
-```solidity
-function payForService(contract IERC20, uint256) external payable
-```
-
-### onDelegationReceived
-
-```solidity
-function onDelegationReceived(address, contract IStrategy[], uint256[]) external pure returns (bytes)
-```
-
-Hook for receiving new delegation
-
-### onDelegationWithdrawn
-
-```solidity
-function onDelegationWithdrawn(address, contract IStrategy[], uint256[]) external pure returns (bytes)
-```
-
-Hook for withdrawing delegation
-
diff --git a/docs/docgen/permissions/Pausable.md b/docs/docgen/permissions/Pausable.md
deleted file mode 100644
index 37277bc16e..0000000000
--- a/docs/docgen/permissions/Pausable.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# Solidity API
-
-## Pausable
-
-Contracts that inherit from this contract may define their own `pause` and `unpause` (and/or related) functions.
-These functions should be permissioned as "onlyPauser" which defers to a `PauserRegistry` for determining access control.
-
-_Pausability is implemented using a uint256, which allows up to 256 different single bit-flags; each bit can potentially pause different functionality.
-Inspiration for this was taken from the NearBridge design here https://etherscan.io/address/0x3FEFc5A4B1c02f21cBc8D3613643ba0635b9a873#code.
-For the `pause` and `unpause` functions we've implemented, if you pause, you can only flip (any number of) switches to on/1 (aka "paused"), and if you unpause,
-you can only flip (any number of) switches to off/0 (aka "paused").
-If you want a pauseXYZ function that just flips a single bit / "pausing flag", it will:
-1) 'bit-wise and' (aka `&`) a flag with the current paused state (as a uint256)
-2) update the paused state to this new value
-We note as well that we have chosen to identify flags by their *bit index* as opposed to their numerical value, so, e.g. defining `DEPOSITS_PAUSED = 3`
-indicates specifically that if the *third bit* of `_paused` is flipped -- i.e. it is a '1' -- then deposits should be paused_
-
-### pauserRegistry
-
-```solidity
-contract IPauserRegistry pauserRegistry
-```
-
-Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
-
-### _paused
-
-```solidity
-uint256 _paused
-```
-
-_whether or not the contract is currently paused_
-
-### UNPAUSE_ALL
-
-```solidity
-uint256 UNPAUSE_ALL
-```
-
-### PAUSE_ALL
-
-```solidity
-uint256 PAUSE_ALL
-```
-
-### Paused
-
-```solidity
-event Paused(address account, uint256 newPausedStatus)
-```
-
-Emitted when the pause is triggered by `account`, and changed to `newPausedStatus`.
-
-### Unpaused
-
-```solidity
-event Unpaused(address account, uint256 newPausedStatus)
-```
-
-Emitted when the pause is lifted by `account`, and changed to `newPausedStatus`.
-
-### onlyPauser
-
-```solidity
-modifier onlyPauser()
-```
-
-@notice
-
-### onlyUnpauser
-
-```solidity
-modifier onlyUnpauser()
-```
-
-### whenNotPaused
-
-```solidity
-modifier whenNotPaused()
-```
-
-Throws if the contract is paused, i.e. if any of the bits in `_paused` is flipped to 1.
-
-### onlyWhenNotPaused
-
-```solidity
-modifier onlyWhenNotPaused(uint8 index)
-```
-
-Throws if the `indexed`th bit of `_paused` is 1, i.e. if the `index`th pause switch is flipped.
-
-### _initializePauser
-
-```solidity
-function _initializePauser(contract IPauserRegistry _pauserRegistry, uint256 initPausedStatus) internal
-```
-
-One-time function for setting the `pauserRegistry` and initializing the value of `_paused`.
-
-### pause
-
-```solidity
-function pause(uint256 newPausedStatus) external
-```
-
-This function is used to pause an EigenLayer contract's functionality.
-It is permissioned to the `pauser` address, which is expected to be a low threshold multisig.
-
-_This function can only pause functionality, and thus cannot 'unflip' any bit in `_paused` from 1 to 0._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newPausedStatus | uint256 | represents the new value for `_paused` to take, which means it may flip several bits at once. |
-
-### pauseAll
-
-```solidity
-function pauseAll() external
-```
-
-Alias for `pause(type(uint256).max)`.
-
-### unpause
-
-```solidity
-function unpause(uint256 newPausedStatus) external
-```
-
-This function is used to unpause an EigenLayer contract's functionality.
-It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or governance contract.
-
-_This function can only unpause functionality, and thus cannot 'flip' any bit in `_paused` from 0 to 1._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newPausedStatus | uint256 | represents the new value for `_paused` to take, which means it may flip several bits at once. |
-
-### paused
-
-```solidity
-function paused() public view virtual returns (uint256)
-```
-
-Returns the current paused status as a uint256.
-
-### paused
-
-```solidity
-function paused(uint8 index) public view virtual returns (bool)
-```
-
-Returns 'true' if the `indexed`th bit of `_paused` is 1, and 'false' otherwise
-
-### __gap
-
-```solidity
-uint256[48] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/permissions/PauserRegistry.md b/docs/docgen/permissions/PauserRegistry.md
deleted file mode 100644
index 28c4a6f469..0000000000
--- a/docs/docgen/permissions/PauserRegistry.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# Solidity API
-
-## PauserRegistry
-
-### pauser
-
-```solidity
-address pauser
-```
-
-Unique address that holds the pauser role.
-
-### unpauser
-
-```solidity
-address unpauser
-```
-
-Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
-
-### PauserChanged
-
-```solidity
-event PauserChanged(address previousPauser, address newPauser)
-```
-
-### UnpauserChanged
-
-```solidity
-event UnpauserChanged(address previousUnpauser, address newUnpauser)
-```
-
-### onlyUnpauser
-
-```solidity
-modifier onlyUnpauser()
-```
-
-### constructor
-
-```solidity
-constructor(address _pauser, address _unpauser) public
-```
-
-### setPauser
-
-```solidity
-function setPauser(address newPauser) external
-```
-
-Sets new pauser - only callable by unpauser, as the unpauser is expected to be kept more secure, e.g. being a multisig with a higher threshold
-
-### setUnpauser
-
-```solidity
-function setUnpauser(address newUnpauser) external
-```
-
-Sets new unpauser - only callable by unpauser, as the unpauser is expected to be kept more secure, e.g. being a multisig with a higher threshold
-
-### _setPauser
-
-```solidity
-function _setPauser(address newPauser) internal
-```
-
-### _setUnpauser
-
-```solidity
-function _setUnpauser(address newUnpauser) internal
-```
-
diff --git a/docs/docgen/pods/BeaconChainOracle.md b/docs/docgen/pods/BeaconChainOracle.md
deleted file mode 100644
index 4b809cca6f..0000000000
--- a/docs/docgen/pods/BeaconChainOracle.md
+++ /dev/null
@@ -1,202 +0,0 @@
-# Solidity API
-
-## BeaconChainOracle
-
-The owner of this contract can edit a set of 'oracle signers', as well as changing the threshold number of oracle signers that must vote for a
- particular state root at a specified blockNumber before the state root is considered 'confirmed'.
-
-### MINIMUM_THRESHOLD
-
-```solidity
-uint256 MINIMUM_THRESHOLD
-```
-
-The minimum value which the `threshold` variable is allowed to take.
-
-### totalOracleSigners
-
-```solidity
-uint256 totalOracleSigners
-```
-
-Total number of members of the set of oracle signers.
-
-### threshold
-
-```solidity
-uint256 threshold
-```
-
-Number of oracle signers that must vote for a state root in order for the state root to be confirmed.
-Adjustable by this contract's owner through use of the `setThreshold` function.
-
-_We note that there is an edge case -- when the threshold is adjusted downward, if a state root already has enough votes to meet the *new* threshold,
-the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations._
-
-### latestConfirmedOracleBlockNumber
-
-```solidity
-uint64 latestConfirmedOracleBlockNumber
-```
-
-Largest blockNumber that has been confirmed by the oracle.
-
-### beaconStateRootAtBlockNumber
-
-```solidity
-mapping(uint64 => bytes32) beaconStateRootAtBlockNumber
-```
-
-Mapping: Beacon Chain blockNumber => the Beacon Chain state root at the specified blockNumber.
-
-_This will return `bytes32(0)` if the state root at the specified blockNumber is not yet confirmed._
-
-### isOracleSigner
-
-```solidity
-mapping(address => bool) isOracleSigner
-```
-
-Mapping: address => whether or not the address is in the set of oracle signers.
-
-### hasVoted
-
-```solidity
-mapping(uint64 => mapping(address => bool)) hasVoted
-```
-
-Mapping: Beacon Chain blockNumber => oracle signer address => whether or not the oracle signer has voted on the state root at the blockNumber.
-
-### stateRootVotes
-
-```solidity
-mapping(uint64 => mapping(bytes32 => uint256)) stateRootVotes
-```
-
-Mapping: Beacon Chain blockNumber => state root => total number of oracle signer votes for the state root at the blockNumber.
-
-### ThresholdModified
-
-```solidity
-event ThresholdModified(uint256 previousValue, uint256 newValue)
-```
-
-Emitted when the value of the `threshold` variable is changed from `previousValue` to `newValue`.
-
-### StateRootConfirmed
-
-```solidity
-event StateRootConfirmed(uint64 blockNumber, bytes32 stateRoot)
-```
-
-Emitted when the beacon chain state root at `blockNumber` is confirmed to be `stateRoot`.
-
-### OracleSignerAdded
-
-```solidity
-event OracleSignerAdded(address addedOracleSigner)
-```
-
-Emitted when `addedOracleSigner` is added to the set of oracle signers.
-
-### OracleSignerRemoved
-
-```solidity
-event OracleSignerRemoved(address removedOracleSigner)
-```
-
-Emitted when `removedOracleSigner` is removed from the set of oracle signers.
-
-### onlyOracleSigner
-
-```solidity
-modifier onlyOracleSigner()
-```
-
-Modifier that restricts functions to only be callable by members of the oracle signer set
-
-### constructor
-
-```solidity
-constructor(address initialOwner, uint256 initialThreshold, address[] initialOracleSigners) public
-```
-
-### setThreshold
-
-```solidity
-function setThreshold(uint256 _threshold) external
-```
-
-Owner-only function used to modify the value of the `threshold` variable.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _threshold | uint256 | Desired new value for the `threshold` variable. Function will revert if this is set to zero. |
-
-### addOracleSigners
-
-```solidity
-function addOracleSigners(address[] _oracleSigners) external
-```
-
-Owner-only function used to add a signer to the set of oracle signers.
-
-_Function will have no effect on the i-th input address if `_oracleSigners[i]`is already in the set of oracle signers._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _oracleSigners | address[] | Array of address to be added to the set. |
-
-### removeOracleSigners
-
-```solidity
-function removeOracleSigners(address[] _oracleSigners) external
-```
-
-Owner-only function used to remove a signer from the set of oracle signers.
-
-_Function will have no effect on the i-th input address if `_oracleSigners[i]`is already not in the set of oracle signers._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| _oracleSigners | address[] | Array of address to be removed from the set. |
-
-### voteForBeaconChainStateRoot
-
-```solidity
-function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external
-```
-
-Called by a member of the set of oracle signers to assert that the Beacon Chain state root is `stateRoot` at `blockNumber`.
-
-_The state root will be confirmed once the total number of votes *for this exact state root at this exact blockNumber* meets the `threshold` value._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| blockNumber | uint64 | The Beacon Chain blockNumber of interest. |
-| stateRoot | bytes32 | The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`. |
-
-### _setThreshold
-
-```solidity
-function _setThreshold(uint256 _threshold) internal
-```
-
-Internal function used for modifying the value of the `threshold` variable, used in the constructor and the `setThreshold` function
-
-### _addOracleSigners
-
-```solidity
-function _addOracleSigners(address[] _oracleSigners) internal
-```
-
-Internal counterpart of the `addOracleSigners` function. Also used in the constructor.
-
diff --git a/docs/docgen/pods/DelayedWithdrawalRouter.md b/docs/docgen/pods/DelayedWithdrawalRouter.md
deleted file mode 100644
index 095dd3f80c..0000000000
--- a/docs/docgen/pods/DelayedWithdrawalRouter.md
+++ /dev/null
@@ -1,201 +0,0 @@
-# Solidity API
-
-## DelayedWithdrawalRouter
-
-### WithdrawalDelayBlocksSet
-
-```solidity
-event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue)
-```
-
-Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
-
-### PAUSED_DELAYED_WITHDRAWAL_CLAIMS
-
-```solidity
-uint8 PAUSED_DELAYED_WITHDRAWAL_CLAIMS
-```
-
-### withdrawalDelayBlocks
-
-```solidity
-uint256 withdrawalDelayBlocks
-```
-
-Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner,
-up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
-
-### MAX_WITHDRAWAL_DELAY_BLOCKS
-
-```solidity
-uint256 MAX_WITHDRAWAL_DELAY_BLOCKS
-```
-
-### eigenPodManager
-
-```solidity
-contract IEigenPodManager eigenPodManager
-```
-
-The EigenPodManager contract of EigenLayer.
-
-### _userWithdrawals
-
-```solidity
-mapping(address => struct IDelayedWithdrawalRouter.UserDelayedWithdrawals) _userWithdrawals
-```
-
-Mapping: user => struct storing all delayedWithdrawal info. Marked as internal with an external getter function named `userWithdrawals`
-
-### DelayedWithdrawalCreated
-
-```solidity
-event DelayedWithdrawalCreated(address podOwner, address recipient, uint256 amount, uint256 index)
-```
-
-event for delayedWithdrawal creation
-
-### DelayedWithdrawalsClaimed
-
-```solidity
-event DelayedWithdrawalsClaimed(address recipient, uint256 amountClaimed, uint256 delayedWithdrawalsCompleted)
-```
-
-event for the claiming of delayedWithdrawals
-
-### onlyEigenPod
-
-```solidity
-modifier onlyEigenPod(address podOwner)
-```
-
-Modifier used to permission a function to only be called by the EigenPod of the specified `podOwner`
-
-### constructor
-
-```solidity
-constructor(contract IEigenPodManager _eigenPodManager) public
-```
-
-### initialize
-
-```solidity
-function initialize(address initOwner, contract IPauserRegistry _pauserRegistry, uint256 initPausedStatus, uint256 _withdrawalDelayBlocks) external
-```
-
-### createDelayedWithdrawal
-
-```solidity
-function createDelayedWithdrawal(address podOwner, address recipient) external payable
-```
-
-Creates a delayed withdrawal for `msg.value` to the `recipient`.
-
-_Only callable by the `podOwner`'s EigenPod contract._
-
-### claimDelayedWithdrawals
-
-```solidity
-function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfDelayedWithdrawalsToClaim) external
-```
-
-Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period.
-
-_WARNING: Note that the caller of this function cannot control where the funds are sent, but they can control when the
- funds are sent once the withdrawal becomes claimable._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| recipient | address | The address to claim delayedWithdrawals for. |
-| maxNumberOfDelayedWithdrawalsToClaim | uint256 | Used to limit the maximum number of delayedWithdrawals to loop through claiming. |
-
-### claimDelayedWithdrawals
-
-```solidity
-function claimDelayedWithdrawals(uint256 maxNumberOfDelayedWithdrawalsToClaim) external
-```
-
-Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| maxNumberOfDelayedWithdrawalsToClaim | uint256 | Used to limit the maximum number of delayedWithdrawals to loop through claiming. |
-
-### setWithdrawalDelayBlocks
-
-```solidity
-function setWithdrawalDelayBlocks(uint256 newValue) external
-```
-
-Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
-
-### userWithdrawals
-
-```solidity
-function userWithdrawals(address user) external view returns (struct IDelayedWithdrawalRouter.UserDelayedWithdrawals)
-```
-
-Getter function for the mapping `_userWithdrawals`
-
-### claimableUserDelayedWithdrawals
-
-```solidity
-function claimableUserDelayedWithdrawals(address user) external view returns (struct IDelayedWithdrawalRouter.DelayedWithdrawal[])
-```
-
-Getter function to get all delayedWithdrawals that are currently claimable by the `user`
-
-### userDelayedWithdrawalByIndex
-
-```solidity
-function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (struct IDelayedWithdrawalRouter.DelayedWithdrawal)
-```
-
-Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
-
-### userWithdrawalsLength
-
-```solidity
-function userWithdrawalsLength(address user) external view returns (uint256)
-```
-
-Getter function for fetching the length of the delayedWithdrawals array of a specific user
-
-### canClaimDelayedWithdrawal
-
-```solidity
-function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool)
-```
-
-Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable
-
-### _claimDelayedWithdrawals
-
-```solidity
-function _claimDelayedWithdrawals(address recipient, uint256 maxNumberOfDelayedWithdrawalsToClaim) internal
-```
-
-internal function used in both of the overloaded `claimDelayedWithdrawals` functions
-
-### _setWithdrawalDelayBlocks
-
-```solidity
-function _setWithdrawalDelayBlocks(uint256 newValue) internal
-```
-
-internal function for changing the value of `withdrawalDelayBlocks`. Also performs sanity check and emits an event.
-
-### __gap
-
-```solidity
-uint256[48] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/pods/EigenPod.md b/docs/docgen/pods/EigenPod.md
deleted file mode 100644
index 766f56474e..0000000000
--- a/docs/docgen/pods/EigenPod.md
+++ /dev/null
@@ -1,345 +0,0 @@
-# Solidity API
-
-## EigenPod
-
-The main functionalities are:
-- creating new ETH validators with their withdrawal credentials pointed to this contract
-- proving from beacon chain state roots that withdrawal credentials are pointed to this contract
-- proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials
- pointed to this contract
-- updating aggregate balances in the EigenPodManager
-- withdrawing eth when withdrawals are initiated
-
-_Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
- to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts_
-
-### GWEI_TO_WEI
-
-```solidity
-uint256 GWEI_TO_WEI
-```
-
-### VERIFY_OVERCOMMITTED_WINDOW_BLOCKS
-
-```solidity
-uint256 VERIFY_OVERCOMMITTED_WINDOW_BLOCKS
-```
-
-Maximum "staleness" of a Beacon Chain state root against which `verifyOvercommittedStake` may be proven. 7 days in blocks.
-
-### ethPOS
-
-```solidity
-contract IETHPOSDeposit ethPOS
-```
-
-This is the beacon chain deposit contract
-
-### delayedWithdrawalRouter
-
-```solidity
-contract IDelayedWithdrawalRouter delayedWithdrawalRouter
-```
-
-Contract used for withdrawal routing, to provide an extra "safety net" mechanism
-
-### eigenPodManager
-
-```solidity
-contract IEigenPodManager eigenPodManager
-```
-
-The single EigenPodManager for EigenLayer
-
-### REQUIRED_BALANCE_GWEI
-
-```solidity
-uint64 REQUIRED_BALANCE_GWEI
-```
-
-The amount of eth, in gwei, that is restaked per validator
-
-### REQUIRED_BALANCE_WEI
-
-```solidity
-uint256 REQUIRED_BALANCE_WEI
-```
-
-The amount of eth, in wei, that is restaked per ETH validator into EigenLayer
-
-### podOwner
-
-```solidity
-address podOwner
-```
-
-The owner of this EigenPod
-
-### mostRecentWithdrawalBlockNumber
-
-```solidity
-uint64 mostRecentWithdrawalBlockNumber
-```
-
-The latest block number at which the pod owner withdrew the balance of the pod.
-
-_This variable is only updated when the `withdraw` function is called, which can only occur before `hasRestaked` is set to true for this pod.
-Proofs for this pod are only valid against Beacon Chain state roots corresponding to blocks after the stored `mostRecentWithdrawalBlockNumber`._
-
-### restakedExecutionLayerGwei
-
-```solidity
-uint64 restakedExecutionLayerGwei
-```
-
-the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from the Beacon Chain but not from EigenLayer),
-
-### hasRestaked
-
-```solidity
-bool hasRestaked
-```
-
-an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
-
-### validatorStatus
-
-```solidity
-mapping(uint40 => enum IEigenPod.VALIDATOR_STATUS) validatorStatus
-```
-
-this is a mapping of validator indices to a Validator struct containing pertinent info about the validator
-
-### provenPartialWithdrawal
-
-```solidity
-mapping(uint40 => mapping(uint64 => bool)) provenPartialWithdrawal
-```
-
-This is a mapping of validatorIndex to withdrawalIndex to whether or not they have proven a withdrawal for that index
-
-### EigenPodStaked
-
-```solidity
-event EigenPodStaked(bytes pubkey)
-```
-
-Emitted when an ETH validator stakes via this eigenPod
-
-### ValidatorRestaked
-
-```solidity
-event ValidatorRestaked(uint40 validatorIndex)
-```
-
-Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
-
-### ValidatorOvercommitted
-
-```solidity
-event ValidatorOvercommitted(uint40 validatorIndex)
-```
-
-Emitted when an ETH validator is proven to have a balance less than `REQUIRED_BALANCE_GWEI` in the beacon chain
-
-### FullWithdrawalRedeemed
-
-```solidity
-event FullWithdrawalRedeemed(uint40 validatorIndex, address recipient, uint64 withdrawalAmountGwei)
-```
-
-Emitted when an ETH validator is prove to have withdrawn from the beacon chain
-
-### PartialWithdrawalRedeemed
-
-```solidity
-event PartialWithdrawalRedeemed(uint40 validatorIndex, address recipient, uint64 partialWithdrawalAmountGwei)
-```
-
-Emitted when a partial withdrawal claim is successfully redeemed
-
-### RestakedBeaconChainETHWithdrawn
-
-```solidity
-event RestakedBeaconChainETHWithdrawn(address recipient, uint256 amount)
-```
-
-Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
-
-### onlyEigenPodManager
-
-```solidity
-modifier onlyEigenPodManager()
-```
-
-### onlyEigenPodOwner
-
-```solidity
-modifier onlyEigenPodOwner()
-```
-
-### onlyNotFrozen
-
-```solidity
-modifier onlyNotFrozen()
-```
-
-### hasNeverRestaked
-
-```solidity
-modifier hasNeverRestaked()
-```
-
-### proofIsForValidBlockNumber
-
-```solidity
-modifier proofIsForValidBlockNumber(uint64 blockNumber)
-```
-
-Checks that `blockNumber` is strictly greater than the value stored in `mostRecentWithdrawalBlockNumber`
-
-### onlyWhenNotPaused
-
-```solidity
-modifier onlyWhenNotPaused(uint8 index)
-```
-
-Based on 'Pausable' code, but uses the storage of the EigenPodManager instead of this contract. This construction
-is necessary for enabling pausing all EigenPods at the same time (due to EigenPods being Beacon Proxies).
-Modifier throws if the `indexed`th bit of `_paused` in the EigenPodManager is 1, i.e. if the `index`th pause switch is flipped.
-
-### constructor
-
-```solidity
-constructor(contract IETHPOSDeposit _ethPOS, contract IDelayedWithdrawalRouter _delayedWithdrawalRouter, contract IEigenPodManager _eigenPodManager, uint256 _REQUIRED_BALANCE_WEI) public
-```
-
-### initialize
-
-```solidity
-function initialize(address _podOwner) external
-```
-
-Used to initialize the pointers to addresses crucial to the pod's functionality. Called on construction by the EigenPodManager.
-
-### stake
-
-```solidity
-function stake(bytes pubkey, bytes signature, bytes32 depositDataRoot) external payable
-```
-
-Called by EigenPodManager when the owner wants to create another ETH validator.
-
-### verifyWithdrawalCredentialsAndBalance
-
-```solidity
-function verifyWithdrawalCredentialsAndBalance(uint64 oracleBlockNumber, uint40 validatorIndex, struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs proofs, bytes32[] validatorFields) external
-```
-
-This function verifies that the withdrawal credentials of the podOwner are pointed to
-this contract. It also verifies the current (not effective) balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
-root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| oracleBlockNumber | uint64 | is the Beacon Chain blockNumber whose state root the `proof` will be proven against. |
-| validatorIndex | uint40 | is the index of the validator being proven, refer to consensus specs |
-| proofs | struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs | is the bytes that prove the ETH validator's balance and withdrawal credentials against a beacon chain state root |
-| validatorFields | bytes32[] | are the fields of the "Validator Container", refer to consensus specs for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator |
-
-### verifyOvercommittedStake
-
-```solidity
-function verifyOvercommittedStake(uint40 validatorIndex, struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs proofs, bytes32[] validatorFields, uint256 beaconChainETHStrategyIndex, uint64 oracleBlockNumber) external
-```
-
-This function records an overcommitment of stake to EigenLayer on behalf of a certain ETH validator.
- If successful, the overcommitted balance is penalized (available for withdrawal whenever the pod's balance allows).
- The ETH validator's shares in the enshrined beaconChainETH strategy are also removed from the StrategyManager and undelegated.
-
-_For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| validatorIndex | uint40 | is the index of the validator being proven, refer to consensus specs |
-| proofs | struct BeaconChainProofs.ValidatorFieldsAndBalanceProofs | is the proof of the validator's balance and validatorFields in the balance tree and the balanceRoot to prove for |
-| validatorFields | bytes32[] | are the fields of the "Validator Container", refer to consensus specs |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy for the pod owner for the callback to the StrategyManager in case it must be removed from the list of the podOwner's strategies |
-| oracleBlockNumber | uint64 | The oracleBlockNumber whose state root the `proof` will be proven against. Must be within `VERIFY_OVERCOMMITTED_WINDOW_BLOCKS` of the current block. |
-
-### verifyAndProcessWithdrawal
-
-```solidity
-function verifyAndProcessWithdrawal(struct BeaconChainProofs.WithdrawalProofs withdrawalProofs, bytes validatorFieldsProof, bytes32[] validatorFields, bytes32[] withdrawalFields, uint256 beaconChainETHStrategyIndex, uint64 oracleBlockNumber) external
-```
-
-This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| withdrawalProofs | struct BeaconChainProofs.WithdrawalProofs | is the information needed to check the veracity of the block number and withdrawal being proven |
-| validatorFieldsProof | bytes | is the information needed to check the veracity of the validator fields being proven |
-| validatorFields | bytes32[] | are the fields of the validator being proven |
-| withdrawalFields | bytes32[] | are the fields of the withdrawal being proven |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy for the pod owner for the callback to the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies |
-| oracleBlockNumber | uint64 | is the Beacon Chain blockNumber whose state root the `proof` will be proven against. |
-
-### _processFullWithdrawal
-
-```solidity
-function _processFullWithdrawal(uint64 withdrawalAmountGwei, uint40 validatorIndex, uint256 beaconChainETHStrategyIndex, address recipient, enum IEigenPod.VALIDATOR_STATUS status) internal
-```
-
-### _processPartialWithdrawal
-
-```solidity
-function _processPartialWithdrawal(uint64 withdrawalHappenedSlot, uint64 partialWithdrawalAmountGwei, uint40 validatorIndex, address recipient) internal
-```
-
-### withdrawRestakedBeaconChainETH
-
-```solidity
-function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external
-```
-
-Transfers `amountWei` in ether from this contract to the specified `recipient` address
-Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
-
-_Called during withdrawal or slashing._
-
-### withdrawBeforeRestaking
-
-```solidity
-function withdrawBeforeRestaking() external
-```
-
-Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
-
-### _podWithdrawalCredentials
-
-```solidity
-function _podWithdrawalCredentials() internal view returns (bytes)
-```
-
-### _sendETH
-
-```solidity
-function _sendETH(address recipient, uint256 amountWei) internal
-```
-
-### __gap
-
-```solidity
-uint256[46] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/pods/EigenPodManager.md b/docs/docgen/pods/EigenPodManager.md
deleted file mode 100644
index c2482ee6b3..0000000000
--- a/docs/docgen/pods/EigenPodManager.md
+++ /dev/null
@@ -1,260 +0,0 @@
-# Solidity API
-
-## EigenPodManager
-
-The main functionalities are:
-- creating EigenPods
-- staking for new validators on EigenPods
-- keeping track of the balances of all validators of EigenPods, and their stake in EigenLayer
-- withdrawing eth when withdrawals are initiated
-
-### beaconProxyBytecode
-
-```solidity
-bytes beaconProxyBytecode
-```
-
-Stored code of type(BeaconProxy).creationCode
-
-_Maintained as a constant to solve an edge case - changes to OpenZeppelin's BeaconProxy code should not cause
-addresses of EigenPods that are pre-computed with Create2 to change, even upon upgrading this contract, changing compiler version, etc._
-
-### ethPOS
-
-```solidity
-contract IETHPOSDeposit ethPOS
-```
-
-The ETH2 Deposit Contract
-
-### eigenPodBeacon
-
-```solidity
-contract IBeacon eigenPodBeacon
-```
-
-Beacon proxy to which the EigenPods point
-
-### strategyManager
-
-```solidity
-contract IStrategyManager strategyManager
-```
-
-EigenLayer's StrategyManager contract
-
-### slasher
-
-```solidity
-contract ISlasher slasher
-```
-
-EigenLayer's Slasher contract
-
-### beaconChainOracle
-
-```solidity
-contract IBeaconChainOracle beaconChainOracle
-```
-
-Oracle contract that provides updates to the beacon chain's state
-
-### ownerToPod
-
-```solidity
-mapping(address => contract IEigenPod) ownerToPod
-```
-
-Pod owner to deployed EigenPod address
-
-### BeaconOracleUpdated
-
-```solidity
-event BeaconOracleUpdated(address newOracleAddress)
-```
-
-Emitted to notify the update of the beaconChainOracle address
-
-### PodDeployed
-
-```solidity
-event PodDeployed(address eigenPod, address podOwner)
-```
-
-Emitted to notify the deployment of an EigenPod
-
-### BeaconChainETHDeposited
-
-```solidity
-event BeaconChainETHDeposited(address podOwner, uint256 amount)
-```
-
-Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
-
-### onlyEigenPod
-
-```solidity
-modifier onlyEigenPod(address podOwner)
-```
-
-### onlyStrategyManager
-
-```solidity
-modifier onlyStrategyManager()
-```
-
-### constructor
-
-```solidity
-constructor(contract IETHPOSDeposit _ethPOS, contract IBeacon _eigenPodBeacon, contract IStrategyManager _strategyManager, contract ISlasher _slasher) public
-```
-
-### initialize
-
-```solidity
-function initialize(contract IBeaconChainOracle _beaconChainOracle, address initialOwner, contract IPauserRegistry _pauserRegistry, uint256 _initPausedStatus) external
-```
-
-### createPod
-
-```solidity
-function createPod() external
-```
-
-Creates an EigenPod for the sender.
-
-_Function will revert if the `msg.sender` already has an EigenPod._
-
-### stake
-
-```solidity
-function stake(bytes pubkey, bytes signature, bytes32 depositDataRoot) external payable
-```
-
-Stakes for a new beacon chain validator on the sender's EigenPod.
-Also creates an EigenPod for the sender if they don't have one already.
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| pubkey | bytes | The 48 bytes public key of the beacon chain validator. |
-| signature | bytes | The validator's signature of the deposit data. |
-| depositDataRoot | bytes32 | The root/hash of the deposit data for the validator's deposit. |
-
-### restakeBeaconChainETH
-
-```solidity
-function restakeBeaconChainETH(address podOwner, uint256 amount) external
-```
-
-Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod.
-
-_Callable only by the podOwner's EigenPod contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| podOwner | address | The owner of the pod whose balance must be deposited. |
-| amount | uint256 | The amount of ETH to 'deposit' (i.e. be credited to the podOwner). |
-
-### recordOvercommittedBeaconChainETH
-
-```solidity
-function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external
-```
-
-Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
- balance of a validator is lower than how much stake they have committed to EigenLayer
-
-_Callable only by the podOwner's EigenPod contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| podOwner | address | The owner of the pod whose balance must be removed. |
-| beaconChainETHStrategyIndex | uint256 | is the index of the beaconChainETHStrategy for the pod owner for the callback to the StrategyManager in case it must be removed from the list of the podOwner's strategies |
-| amount | uint256 | The amount of beacon chain ETH to decrement from the podOwner's shares in the strategyManager. |
-
-### withdrawRestakedBeaconChainETH
-
-```solidity
-function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount) external
-```
-
-Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain.
-
-_Callable only by the StrategyManager contract._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| podOwner | address | The owner of the pod whose balance must be withdrawn. |
-| recipient | address | The recipient of the withdrawn ETH. |
-| amount | uint256 | The amount of ETH to withdraw. |
-
-### updateBeaconChainOracle
-
-```solidity
-function updateBeaconChainOracle(contract IBeaconChainOracle newBeaconChainOracle) external
-```
-
-Updates the oracle contract that provides the beacon chain state root
-
-_Callable only by the owner of this contract (i.e. governance)_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newBeaconChainOracle | contract IBeaconChainOracle | is the new oracle contract being pointed to |
-
-### _deployPod
-
-```solidity
-function _deployPod() internal returns (contract IEigenPod)
-```
-
-### _updateBeaconChainOracle
-
-```solidity
-function _updateBeaconChainOracle(contract IBeaconChainOracle newBeaconChainOracle) internal
-```
-
-### getPod
-
-```solidity
-function getPod(address podOwner) public view returns (contract IEigenPod)
-```
-
-Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
-
-### hasPod
-
-```solidity
-function hasPod(address podOwner) public view returns (bool)
-```
-
-Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
-
-### getBeaconChainStateRoot
-
-```solidity
-function getBeaconChainStateRoot(uint64 blockNumber) external view returns (bytes32)
-```
-
-Returns the Beacon Chain state root at `blockNumber`. Reverts if the Beacon Chain state root at `blockNumber` has not yet been finalized.
-
-### __gap
-
-```solidity
-uint256[48] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/pods/EigenPodPausingConstants.md b/docs/docgen/pods/EigenPodPausingConstants.md
deleted file mode 100644
index 5483e7081a..0000000000
--- a/docs/docgen/pods/EigenPodPausingConstants.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Solidity API
-
-## EigenPodPausingConstants
-
-### PAUSED_NEW_EIGENPODS
-
-```solidity
-uint8 PAUSED_NEW_EIGENPODS
-```
-
-Index for flag that pauses creation of new EigenPods when set. See EigenPodManager code for details.
-
-### PAUSED_WITHDRAW_RESTAKED_ETH
-
-```solidity
-uint8 PAUSED_WITHDRAW_RESTAKED_ETH
-```
-
-Index for flag that pauses the `withdrawRestakedBeaconChainETH` function *of the EigenPodManager* when set. See EigenPodManager code for details.
-
-### PAUSED_EIGENPODS_VERIFY_CREDENTIALS
-
-```solidity
-uint8 PAUSED_EIGENPODS_VERIFY_CREDENTIALS
-```
-
-Index for flag that pauses the `verifyCorrectWithdrawalCredentials` function *of the EigenPods* when set. see EigenPod code for details.
-
-### PAUSED_EIGENPODS_VERIFY_OVERCOMMITTED
-
-```solidity
-uint8 PAUSED_EIGENPODS_VERIFY_OVERCOMMITTED
-```
-
-Index for flag that pauses the `verifyOvercommittedStake` function *of the EigenPods* when set. see EigenPod code for details.
-
-### PAUSED_EIGENPODS_VERIFY_WITHDRAWAL
-
-```solidity
-uint8 PAUSED_EIGENPODS_VERIFY_WITHDRAWAL
-```
-
-Index for flag that pauses the `verifyBeaconChainFullWithdrawal` function *of the EigenPods* when set. see EigenPod code for details.
-
diff --git a/docs/docgen/strategies/StrategyBase.md b/docs/docgen/strategies/StrategyBase.md
deleted file mode 100644
index 9280a7d477..0000000000
--- a/docs/docgen/strategies/StrategyBase.md
+++ /dev/null
@@ -1,300 +0,0 @@
-# Solidity API
-
-## StrategyBase
-
-Simple, basic, "do-nothing" Strategy that holds a single underlying token and returns it on withdrawals.
-Implements minimal versions of the IStrategy functions, this contract is designed to be inherited by
-more complex strategies, which can then override its functions as necessary.
-This contract functions similarly to an ERC4626 vault, only without issuing a token.
-To mitigate against the common "inflation attack" vector, we have chosen to use the 'virtual shares' mitigation route,
-similar to [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol).
-We acknowledge that this mitigation has the known downside of the virtual shares causing some losses to users, which are pronounced
-particularly in the case of the share exchange rate changing signficantly, either positively or negatively.
-For a fairly thorough discussion of this issue and our chosen mitigation strategy, we recommend reading through
-[this thread](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706) on the OpenZeppelin repo.
-We specifically use a share offset of `SHARES_OFFSET` and a balance offset of `BALANCE_OFFSET`.
-
-_Note that some functions have their mutability restricted; developers inheriting from this contract cannot broaden
-the mutability without modifying this contract itself.
-This contract is expressly *not* intended for use with 'fee-on-transfer'-type tokens.
-Setting the `underlyingToken` to be a fee-on-transfer token may result in improper accounting._
-
-### PAUSED_DEPOSITS
-
-```solidity
-uint8 PAUSED_DEPOSITS
-```
-
-### PAUSED_WITHDRAWALS
-
-```solidity
-uint8 PAUSED_WITHDRAWALS
-```
-
-### SHARES_OFFSET
-
-```solidity
-uint256 SHARES_OFFSET
-```
-
-virtual shares used as part of the mitigation of the common 'share inflation' attack vector.
-Constant value chosen to reasonably reduce attempted share inflation by the first depositor, while still
-incurring reasonably small losses to depositors
-
-### BALANCE_OFFSET
-
-```solidity
-uint256 BALANCE_OFFSET
-```
-
-virtual balance used as part of the mitigation of the common 'share inflation' attack vector
-Constant value chosen to reasonably reduce attempted share inflation by the first depositor, while still
-incurring reasonably small losses to depositors
-
-### strategyManager
-
-```solidity
-contract IStrategyManager strategyManager
-```
-
-EigenLayer's StrategyManager contract
-
-### underlyingToken
-
-```solidity
-contract IERC20 underlyingToken
-```
-
-The underlying token for shares in this Strategy
-
-### totalShares
-
-```solidity
-uint256 totalShares
-```
-
-The total number of extant shares in this Strategy
-
-### onlyStrategyManager
-
-```solidity
-modifier onlyStrategyManager()
-```
-
-Simply checks that the `msg.sender` is the `strategyManager`, which is an address stored immutably at construction.
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager) public
-```
-
-Since this contract is designed to be initializable, the constructor simply sets `strategyManager`, the only immutable variable.
-
-### initialize
-
-```solidity
-function initialize(contract IERC20 _underlyingToken, contract IPauserRegistry _pauserRegistry) public virtual
-```
-
-### _initializeStrategyBase
-
-```solidity
-function _initializeStrategyBase(contract IERC20 _underlyingToken, contract IPauserRegistry _pauserRegistry) internal
-```
-
-Sets the `underlyingToken` and `pauserRegistry` for the strategy.
-
-### deposit
-
-```solidity
-function deposit(contract IERC20 token, uint256 amount) external virtual returns (uint256 newShares)
-```
-
-Used to deposit tokens into this Strategy
-
-_This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
-`depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well.
-Note that the assumption is made that `amount` of `token` has already been transferred directly to this contract
-(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)._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| token | contract IERC20 | is the ERC20 token being deposited |
-| amount | uint256 | is the amount of token being deposited |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| newShares | uint256 | is the number of new shares issued at the current exchange ratio. |
-
-### withdraw
-
-```solidity
-function withdraw(address depositor, contract IERC20 token, uint256 amountShares) external virtual
-```
-
-Used to withdraw tokens from this Strategy, to the `depositor`'s address
-
-_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._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | is the address to receive the withdrawn funds |
-| token | contract IERC20 | is the ERC20 token being transferred out |
-| amountShares | uint256 | is the amount of shares being withdrawn |
-
-### explanation
-
-```solidity
-function explanation() external pure virtual returns (string)
-```
-
-Currently returns a brief string explaining the strategy's goal & purpose, but for more complex
-strategies, may be a link to metadata that explains in more detail.
-
-### sharesToUnderlyingView
-
-```solidity
-function sharesToUnderlyingView(uint256 amountShares) public view virtual returns (uint256)
-```
-
-Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
-In contrast to `sharesToUnderlying`, this function guarantees no state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountShares | uint256 | is the amount of shares to calculate its conversion into the underlying token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of underlying tokens corresponding to the input `amountShares` |
-
-### sharesToUnderlying
-
-```solidity
-function sharesToUnderlying(uint256 amountShares) public view virtual returns (uint256)
-```
-
-Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
-In contrast to `sharesToUnderlyingView`, this function **may** make state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountShares | uint256 | is the amount of shares to calculate its conversion into the underlying token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of underlying tokens corresponding to the input `amountShares` |
-
-### underlyingToSharesView
-
-```solidity
-function underlyingToSharesView(uint256 amountUnderlying) public view virtual returns (uint256)
-```
-
-Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
-In contrast to `underlyingToShares`, this function guarantees no state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountUnderlying | uint256 | is the amount of `underlyingToken` to calculate its conversion into strategy shares |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of shares corresponding to the input `amountUnderlying` |
-
-### underlyingToShares
-
-```solidity
-function underlyingToShares(uint256 amountUnderlying) external view virtual returns (uint256)
-```
-
-Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
-In contrast to `underlyingToSharesView`, this function **may** make state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountUnderlying | uint256 | is the amount of `underlyingToken` to calculate its conversion into strategy shares |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of shares corresponding to the input `amountUnderlying` |
-
-### userUnderlyingView
-
-```solidity
-function userUnderlyingView(address user) external view virtual returns (uint256)
-```
-
-convenience function for fetching the current underlying value of all of the `user`'s shares in
-this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications
-
-### userUnderlying
-
-```solidity
-function userUnderlying(address user) external virtual returns (uint256)
-```
-
-convenience function for fetching the current underlying value of all of the `user`'s shares in
-this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications
-
-### shares
-
-```solidity
-function shares(address user) public view virtual returns (uint256)
-```
-
-convenience function for fetching the current total shares of `user` in this strategy, by
-querying the `strategyManager` contract
-
-### _tokenBalance
-
-```solidity
-function _tokenBalance() internal view virtual returns (uint256)
-```
-
-Internal function used to fetch this contract's current balance of `underlyingToken`.
-
-### __gap
-
-```solidity
-uint256[48] __gap
-```
-
-_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_
-
diff --git a/docs/docgen/strategies/StrategyWrapper.md b/docs/docgen/strategies/StrategyWrapper.md
deleted file mode 100644
index e39108b029..0000000000
--- a/docs/docgen/strategies/StrategyWrapper.md
+++ /dev/null
@@ -1,222 +0,0 @@
-# Solidity API
-
-## StrategyWrapper
-
-Simple, basic, "do-nothing" Strategy that holds a single underlying token and returns it on withdrawals.
-Assumes shares are always 1-to-1 with the underlyingToken.
-
-_Unlike `StrategyBase`, this contract is *not* designed to be inherited from.
-This contract is expressly *not* intended for use with 'fee-on-transfer'-type tokens.
-Setting the `underlyingToken` to be a fee-on-transfer token may result in improper accounting._
-
-### strategyManager
-
-```solidity
-contract IStrategyManager strategyManager
-```
-
-EigenLayer's StrategyManager contract
-
-### underlyingToken
-
-```solidity
-contract IERC20 underlyingToken
-```
-
-The underlying token for shares in this Strategy
-
-### totalShares
-
-```solidity
-uint256 totalShares
-```
-
-The total number of extant shares in this Strategy
-
-### onlyStrategyManager
-
-```solidity
-modifier onlyStrategyManager()
-```
-
-### constructor
-
-```solidity
-constructor(contract IStrategyManager _strategyManager, contract IERC20 _underlyingToken) public
-```
-
-### deposit
-
-```solidity
-function deposit(contract IERC20 token, uint256 amount) external returns (uint256)
-```
-
-Used to deposit tokens into this Strategy
-
-_This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
-`depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well.
-Note that the assumption is made that `amount` of `token` has already been transferred directly to this contract
-(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)._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| token | contract IERC20 | is the ERC20 token being deposited |
-| amount | uint256 | is the amount of token being deposited |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | newShares is the number of new shares issued at the current exchange ratio. |
-
-### withdraw
-
-```solidity
-function withdraw(address depositor, contract IERC20 token, uint256 amountShares) external
-```
-
-Used to withdraw tokens from this Strategy, to the `depositor`'s address
-
-_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._
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| depositor | address | is the address to receive the withdrawn funds |
-| token | contract IERC20 | is the ERC20 token being transferred out |
-| amountShares | uint256 | is the amount of shares being withdrawn |
-
-### explanation
-
-```solidity
-function explanation() external pure returns (string)
-```
-
-Currently returns a brief string explaining the strategy's goal & purpose, but for more complex
-strategies, may be a link to metadata that explains in more detail.
-
-### sharesToUnderlyingView
-
-```solidity
-function sharesToUnderlyingView(uint256 amountShares) public pure returns (uint256)
-```
-
-Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
-In contrast to `sharesToUnderlying`, this function guarantees no state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountShares | uint256 | is the amount of shares to calculate its conversion into the underlying token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of underlying tokens corresponding to the input `amountShares` |
-
-### sharesToUnderlying
-
-```solidity
-function sharesToUnderlying(uint256 amountShares) public pure returns (uint256)
-```
-
-Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
-In contrast to `sharesToUnderlyingView`, this function **may** make state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountShares | uint256 | is the amount of shares to calculate its conversion into the underlying token |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of underlying tokens corresponding to the input `amountShares` |
-
-### underlyingToSharesView
-
-```solidity
-function underlyingToSharesView(uint256 amountUnderlying) external pure returns (uint256)
-```
-
-Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
-In contrast to `underlyingToShares`, this function guarantees no state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountUnderlying | uint256 | is the amount of `underlyingToken` to calculate its conversion into strategy shares |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of shares corresponding to the input `amountUnderlying` |
-
-### underlyingToShares
-
-```solidity
-function underlyingToShares(uint256 amountUnderlying) external pure returns (uint256)
-```
-
-Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
-In contrast to `underlyingToSharesView`, this function **may** make state modifications
-
-_Implementation for these functions in particular may vary significantly for different strategies_
-
-#### Parameters
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| amountUnderlying | uint256 | is the amount of `underlyingToken` to calculate its conversion into strategy shares |
-
-#### Return Values
-
-| Name | Type | Description |
-| ---- | ---- | ----------- |
-| [0] | uint256 | The amount of shares corresponding to the input `amountUnderlying` |
-
-### userUnderlyingView
-
-```solidity
-function userUnderlyingView(address user) external view returns (uint256)
-```
-
-convenience function for fetching the current underlying value of all of the `user`'s shares in
-this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications
-
-### userUnderlying
-
-```solidity
-function userUnderlying(address user) external view returns (uint256)
-```
-
-convenience function for fetching the current underlying value of all of the `user`'s shares in
-this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications
-
-### shares
-
-```solidity
-function shares(address user) public view returns (uint256)
-```
-
-convenience function for fetching the current total shares of `user` in this strategy, by
-querying the `strategyManager` contract
-
diff --git a/docs/AVS-Guide.md b/docs/experimental/AVS-Guide.md
similarity index 77%
rename from docs/AVS-Guide.md
rename to docs/experimental/AVS-Guide.md
index 69eb1dd4c9..373f20425d 100644
--- a/docs/AVS-Guide.md
+++ b/docs/experimental/AVS-Guide.md
@@ -1,52 +1,70 @@
[middleware-folder-link]: https://github.com/Layr-Labs/eigenlayer-contracts/tree/master/src/contracts/middleware
[middleware-guide-link]: https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/docs/AVS-Guide.md#quick-start-guide-to-build-avs-contracts
# Purpose
-This document aims to describe and summarize how actively validated services (AVSs) building on EigenLayer interact with the core EigenLayer protocol. Currently, this doc explains how AVS developers can use the APIs for:
+This document aims to describe and summarize how actively validated services (AVSs) building on EigenLayer interact with the core EigenLayer protocol. Currently, this doc explains how AVS developers can use the current** APIs for:
- enabling operators to opt-in to the AVS,
-- enabling operators to withdraw stake from AVS, and
+- enabling operators to opt-out (withdraw stake) from the AVS,
+- enabling operators to continuously update their commitments to middlewares, and
- enabling AVS to freeze operators for the purpose of slashing (the corresponding unfreeze actions are determined by the veto committee).
+
+🚧 ** The Slasher contract is under active development and its interface is expected to change. We recommend writing slashing logic without integrating with the Slasher at this point in time. 🚧
+
+
We are currently in the process of implementing the API for payment flow from AVSs to operators in EigenLayer. Details of this API will be added to this document in the near future.
-The following figure summarizes scope of this document:
-![Doc Outline](./images/middleware_outline_doc.png)
+The following figure summarizes the scope of this document:
+![Doc Outline](../images/middleware_outline_doc.png)
+
# Introduction
-In designing EigenLayer, the EigenLabs team aspired to make minimal assumptions about the structure of AVSs built on top of it. If you are getting started looking at EigenLayer's codebase, the `Slasher.sol` contains most of the logic that actually mediates the interactions between EigenLayer and AVSs. Additionally, there is a general-purpose [/middleware/ folder][middleware-folder-link], which contains code that can be extended, used directly, or consulted as a reference in building an AVS on top of EigenLayer.
+In designing EigenLayer, the EigenLabs team aspired to make minimal assumptions about the structure of AVSs built on top of it. If you are getting started looking at EigenLayer's codebase, the `Slasher.sol` contains most of the logic that actually mediates the interactions between EigenLayer and AVSs. Additionally, there is a general-purpose [/middleware/ folder][middleware-folder-link], which contains code that can be extended, used directly, or consulted as a reference in building an AVS on top of EigenLayer. Note that there will be a single, EigenLayer-owned, `Slasher.sol` contract, but all the `middleware` contracts are AVS-specific and need to be deployed separately by AVS teams.
## Important Terminology
- **Tasks** - A task in EigenLayer is the smallest unit of work that operators commit to perform when serving an AVS. These tasks may be associated with one or more slashing conditions applicable to the AVS.
-- **Strategies** - A strategy in EigenLayer is a contract that holds staker deposits, i.e. it controls one or more restaked asset(s). At launch EigenLayer will feature only simple strategies which may hold a single token. However, EigenLayer's strategy design is flexible and open, and in the future strategies could be deployed which implement more complex logic, including DeFi integrations.
-- **Quorums** - A quorum in EigenLayer is a grouping of specific kinds of stake who opt into an AVS while satisfying a particular trait. Examples of such trait could be stETH stakers or native stakers. The purpose of having a quorum is that an AVS can customize the makeup of their security offering by choosing which kinds of stake/security they would like to utilize.
+- **Strategies** - A strategy in EigenLayer is a contract that holds staker deposits, i.e. it controls one or more asset(s) that can be restaked. At launch EigenLayer will feature only simple strategies which may hold a single token. However, EigenLayer's strategy design is flexible and open, and in the future strategies could be deployed which implement more complex logic, including DeFi integrations.
+- **Quorums** - A quorum in EigenLayer is a grouping of specific kinds of stake who opt into an AVS while satisfying a particular trait. Examples of such traits could be stETH stakers or native stakers. The purpose of having a quorum is that an AVS can customize the makeup of their security offering by choosing which kinds of stake/security they would like to utilize.
# Key Design Considerations
1. *Decomposition into "Tasks"*:
EigenLayer assumes that an AVS manages tasks that are executed over time by a registered operator. Each task is associated with the time period during which the AVS's operators' stakes are placed "at stake", i.e. potentially subject to slashing. Examples of tasks could be:
- - A “DataStore” in the context of EigenDA
+ - Hosting and serving a “DataStore” in the context of EigenDA
- Posting a state root of another blockchain for a bridge service
-2. *Stake is “At Stake” on Tasks for a Finite Duration*:
- It is assumed that every task (eventually) resolves. Each operator places their stake in EigenLayer “at stake” on the tasks that they perform. In order to “release” the stake (e.g. so the operator can withdraw their funds), these tasks need to eventually resolve. It is RECOMMENDED, but not required that a predefined duration is specified in the AVS contract for each task. As a guideline, the EigenLabs team believes that the duration of a task should be aligned with the longest reasonable duration that would be acceptable for an operator to keep funds “at stake”. An AVS builder should recognize that extending the duration of a task may impose significant negative externalities on the stakers of EigenLayer, and may disincentivize stakers opting-in to serving their application.
+2. *Stake is "At Stake" on Tasks for a Finite Duration*:
+ It is assumed that every task (eventually) resolves. Each operator places their stake in EigenLayer “at stake” on the tasks that they perform. In order to “release” the stake (e.g. so the operator can withdraw their funds), these tasks need to eventually resolve. It is RECOMMENDED, but not required that a predefined duration is specified in the AVS contract for each task. As a guideline, the EigenLabs team believes that the duration of a task should be aligned with the longest reasonable duration that would be acceptable for an operator to keep funds “at stake”. An AVS builder should recognize that extending the duration of a task may impose significant negative externalities on the stakers of EigenLayer, and may disincentivize operators from opting-in to serving their application (so that they can attract more delegated stake).
3. *Services Slash Only Objectively Attributable Behavior*:
- EigenLayer is built to support slashing as a result of an on-chain-checkable, objectively attributable action. An AVS SHOULD slash in EigenLayer only for such provable and attributable behavior. It is expected that operators will be very hesitant to opt-in to services that slash for other types of behavior, and other services may even choose to exclude operators who have opted-into serving one or more AVSs with such “subjective slashing conditions”, as these slashing conditions present a significant challenge for risk modeling, and may be perceived as more dangerous in general. Some examples of on-chain-checkable, objectively attributable behavior:
+ EigenLayer is built to support slashing as a result of an on-chain-checkable, objectively attributable action. An AVS SHOULD slash in EigenLayer only for such provable and attributable behavior. It is expected that operators will be very hesitant to opt-in to services that slash for other types of behavior, and other services may even choose to exclude operators who have opted-in to serving one or more AVSs with such “subjective slashing conditions”, as these slashing conditions present a significant challenge for risk modeling, and may be perceived as more dangerous in general. Some examples of on-chain-checkable, objectively attributable behavior:
- double-signing a block in Ethereum, but NOT inactivity leak;
- proofs-of-custody in EigenDA, but NOT a node ceasing to serve data;
- a node in a light-node-bridge AVS signing an invalid block from another chain.
4. *Single Point-of-Interaction for Services and EigenLayer*:
- It is assumed that services have a single contract that coordinates the service’s communications sent to EigenLayer. This contract – referred to as the ServiceManager – informs EigenLayer of operator registration, updates, and deregistration, as well as signaling to EigenLayer when an operator should be slashed (frozen). An AVS has full control over how it splits the actual logic involved, but is expected to route all calls to EigenLayer through a single contract. While technically possible, an AVS SHOULD NOT use multiple contracts to interact with EigenLayer . An AVS architecture using multiple contracts to interact with EigenLayer will impose additional burden on stakers in EigenLayer when withdrawing stake.
+ It is assumed that services have a single contract that coordinates the service’s communications sent to EigenLayer. This contract – referred to as the ServiceManager – informs EigenLayer of operator registration, updates, and deregistration, as well as signaling to EigenLayer when an operator should be slashed (frozen). An AVS has full control over how it splits the actual logic involved, but is expected to route all calls to EigenLayer through a single contract. While technically possible, an AVS SHOULD NOT use multiple contracts to interact with EigenLayer. An AVS architecture using multiple contracts to interact with EigenLayer will impose additional burden on stakers in EigenLayer when withdrawing stake.
## Integration with EigenLayer Contracts:
In this section, we will explain various API interfaces that EigenLayer provides which are essential for AVSs to integrate with EigenLayer.
-### *Opting into AVS*
+### *Operators Opting into AVS*
In order for any EigenLayer operator to be able to opt-in to an AVS, EigenLayer provides two interfaces: `optIntoSlashing(..)` and `recordFirstStakeUpdate(..)`. The sequential flow for opting into an AVS using these functions is as follows:
1. The operator first opts into slashing by calling `Slasher.optIntoSlashing(..)`, where it has to specify the address of the AVS's ServiceManager contract in the argument. This step results in the operator giving permission to the AVS's ServiceManager contract to slash the operator via EigenLayer, if the operator is ever proven to have engaged in adversarial behavior while responding to the AVS's task. A successful call to `Slasher.optIntoSlashing(..)` emits the `OptedIntoSlashing(..)` event.
2. Next, the operator needs to register with the AVS on chain via an AVS-specific registry contract (see [this][middleware-guide-link] section for examples). To integrate with EigenLayer, the AVS's Registry contract provides a registration endpoint that calls on the AVS's `ServiceManager.recordFirstStakeUpdate(..)` which in turn calls `Slasher.recordFirstStakeUpdate(..)`. On successful execution of this function call, the event `MiddlewareTimesAdded(..)` is emitted and the operator has to start serving the tasks from the AVS.
The following figure illustrates the above flow:
-![Operator opting-in](./images/operator_opting.png)
+![Operator opting-in](../images/operator_opting.png)
+
+### *Staker Delegation to an Operator: Which Opts-In to AVSs*
+
+A staker does not restake into AVSs. A staker delegates to an operator and it is the operator that registers for new AVSs (with the staker having option to opt-out).
+
+By delegating to a specific operator, stakers are implicitly agreeing to the AVSs they support. If desired, operators can pursue off-chain consensus with stakers prior to modifying their AVSs. Moreover, stakers will have a grace period to withdraw their delegation should an operator introduce an AVS that doesn't align with their objectives. This grace period is configurable on an operator level.
+
+### *AVS Visibility and Control*
+
+An AVS registration function can blacklist another AVS contract and during registration check that the operator is not registered in that AVS. Or it can check that the operator has not given permission to that AVS's service manager to slash it.
+
+An AVS registry contract should define quorums (eth LST quorum, erc20 quorum, etc.) and allow (or prefer) operators having a minimum amount of restaked assets in each of those quorums to register with the AVS.
### *Recording Stake Updates*
EigenLayer is a dynamic system where stakers and operators are constantly adjusting amounts of stake delegated via the system. It is therefore imperative for an AVS to be aware of any changes to stake delegated to its operators. In order to facilitate this, EigenLayer offers the `Slasher.recordStakeUpdate(..)`.
@@ -57,19 +75,19 @@ Let us illustrate the usage of this facility with an example: A staker has deleg
- The AVS provider now is aware of the change in stake, and the staker can eventually complete their withdrawal. Refer [here](https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/docs/EigenLayer-withdrawal-flow.md) for more details
The following figure illustrates the above flow:
-![Stake update](./images/staker_withdrawing.png)
+![Stake update](../images/staker_withdrawing.png)
### *Deregistering from AVS*
In order for any EigenLayer operator to be able to de-register from an AVS, EigenLayer provides the interface `Slasher.recordLastStakeUpdateAndRevokeSlashingAbility(..)`. Essentially, in order for an operator to deregister from an AVS, the operator has to call `Slasher.recordLastStakeUpdateAndRevokeSlashingAbility(..)` via the AVS's ServiceManager contract. It is important to note that the latest block number until which the operator is required to serve tasks for the service must be known by the service and included in the ServiceManager's call to `Slasher.recordLastStakeUpdateAndRevokeSlashingAbility`.
The following figure illustrates the above flow in which the operator calls the `deregister(..)` function in a sample Registry contract.
-![Operator deregistering](./images/operator_deregister.png)
+![Operator deregistering](../images/operator_deregister.png)
### *Slashing*
As mentioned above, EigenLayer is built to support slashing as a result of an on-chain-checkable, objectively attributable action. In order for an AVS to be able to slash an operator in an objective manner, the AVS needs to deploy a DisputeResolution contract which anyone can call to raise a challenge against an EigenLayer operator for its adversarial action. On successful challenge, the DisputeResolution contract calls `ServiceManager.freezeOperator(..)`; the ServiceManager in turn calls `Slasher.freezeOperator(..)` to freeze the operator in EigenLayer. EigenLayer's Slasher contract emits a `OperatorFrozen(..)` event whenever an operator is (successfully) frozen
The following figure illustrates the above flow:
-![Slashing](./images/slashing.png)
+![Slashing](../images/slashing.png)
## Quick Start Guide to Build AVS Contracts:
@@ -77,7 +95,7 @@ The EigenLayer team has built a set of reusable and extensible contracts for use
- The *VoteWeigherBase contract* tracks an operator’s “weight” in a given quorum, across all strategies that are associated with that quorum. This contract also manages which strategies are in each quorum - this includes functionalities for both adding and removing strategies, as well as changing strategy weights.
- The *RegistryBase contract* is a basic registry contract that can be used to track operators opted-into running an AVS. Importantly, this base registry contract assumes a maximum of two quorums, where each quorum represents an aggregation of a certain type of stake.
-It’s expected that many AVSs will require a quorum of registered operators to sign on commitments. To this end, the EigenLabs team have developed a set of contracts designed to optimize the cost of checking signatures through the use of a BLS aggregate signature scheme:
+Furthermore, it’s expected that many AVSs will require a quorum of registered operators to sign on commitments. To this end, the EigenLabs team has developed a set of contracts designed to optimize the cost of checking signatures through the use of a BLS aggregate signature scheme:
### BLSPublicKeyCompendium
This contract allows each Ethereum address to register a unique BLS public key; a single BLSPublicKeyCompendium contract can be shared amongst all AVSs using BLS signatures.
### BLSRegistry
diff --git a/docs/images/EL_delegating.png b/docs/images/EL_delegating.png
index 5bc7a1eb89..f371009f44 100644
Binary files a/docs/images/EL_delegating.png and b/docs/images/EL_delegating.png differ
diff --git a/docs/images/Staker Flow Diagrams/Complete Withdrawal as Shares.png b/docs/images/Staker Flow Diagrams/Complete Withdrawal as Shares.png
new file mode 100644
index 0000000000..04b2633db7
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Complete Withdrawal as Shares.png differ
diff --git a/docs/images/Staker Flow Diagrams/Complete Withdrawal as Tokens.png b/docs/images/Staker Flow Diagrams/Complete Withdrawal as Tokens.png
new file mode 100644
index 0000000000..9d7cadf8cd
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Complete Withdrawal as Tokens.png differ
diff --git a/docs/images/Staker Flow Diagrams/Delegating.png b/docs/images/Staker Flow Diagrams/Delegating.png
new file mode 100644
index 0000000000..e58c8e99a1
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Delegating.png differ
diff --git a/docs/images/Staker Flow Diagrams/Depositing.png b/docs/images/Staker Flow Diagrams/Depositing.png
new file mode 100644
index 0000000000..c2c9e8ea81
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Depositing.png differ
diff --git a/docs/images/Staker Flow Diagrams/Partial Withdrawals.png b/docs/images/Staker Flow Diagrams/Partial Withdrawals.png
new file mode 100644
index 0000000000..3a6ef385d1
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Partial Withdrawals.png differ
diff --git a/docs/images/Staker Flow Diagrams/Queue Withdrawal.png b/docs/images/Staker Flow Diagrams/Queue Withdrawal.png
new file mode 100644
index 0000000000..16cbc9dbba
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Queue Withdrawal.png differ
diff --git a/docs/images/Staker Flow Diagrams/Validator Exits.png b/docs/images/Staker Flow Diagrams/Validator Exits.png
new file mode 100644
index 0000000000..9b46fe2ed0
Binary files /dev/null and b/docs/images/Staker Flow Diagrams/Validator Exits.png differ
diff --git a/docs/images/Withdrawal_Credential_Proof.png b/docs/images/Withdrawal_Credential_Proof.png
new file mode 100644
index 0000000000..72a290e88d
Binary files /dev/null and b/docs/images/Withdrawal_Credential_Proof.png differ
diff --git a/docs/images/Withdrawal_Proof.png b/docs/images/Withdrawal_Proof.png
new file mode 100644
index 0000000000..16c94980dc
Binary files /dev/null and b/docs/images/Withdrawal_Proof.png differ
diff --git a/docs/images/samplemerkle.png b/docs/images/samplemerkle.png
new file mode 100644
index 0000000000..5729056697
Binary files /dev/null and b/docs/images/samplemerkle.png differ
diff --git a/docs/images/staterootproof.png b/docs/images/staterootproof.png
new file mode 100644
index 0000000000..ec2493cdc4
Binary files /dev/null and b/docs/images/staterootproof.png differ
diff --git a/foundry.toml b/foundry.toml
index 48b327703c..8a79acefaa 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -4,6 +4,8 @@ out = 'out'
libs = ['lib']
fs_permissions = [{ access = "read-write", path = "./"}]
gas_reports = ["*"]
+# ignore upgrade testing in scripts by default
+no_match_test = "queueUpgrade"
# A list of ignored solc error codes
@@ -18,5 +20,18 @@ solc_version = '0.8.12'
[rpc_endpoints]
mainnet = "${RPC_MAINNET}"
+holesky = "${RPC_HOLESKY}"
-# See more config options https://github.com/gakonst/foundry/tree/master/config
\ No newline at end of file
+[fmt]
+bracket_spacing = false
+int_types = "long"
+line_length = 120
+multiline_func_header = "params_first"
+number_underscore = "thousands"
+quote_style = "double"
+tab_width = 4
+
+# See more config options https://github.com/gakonst/foundry/tree/master/config
+
+[profile.forktest.fuzz]
+runs=20
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 3d2c7d47ad..b8c159c9dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,10 +12,521 @@
"solidity-docgen": "^0.6.0-beta.32"
},
"devDependencies": {
+ "@commitlint/cli": "^18.2.0",
+ "@commitlint/config-conventional": "^18.1.0",
+ "@types/yargs": "^17.0.28",
+ "chalk": "^4.1.0",
+ "dotenv": "^16.3.1",
+ "fs": "^0.0.1-security",
"hardhat": "^2.12.4",
"hardhat-preprocessor": "^0.1.5",
+ "husky": "^8.0.3",
"ts-node": "^10.9.1",
- "typescript": "^4.9.4"
+ "typescript": "^4.9.4",
+ "yargs": "^17.7.2"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.22.13",
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
+ "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@commitlint/cli": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.2.0.tgz",
+ "integrity": "sha512-F/DCG791kMFmWg5eIdogakuGeg4OiI2kD430ed1a1Hh3epvrJdeIAgcGADAMIOmF+m0S1+VlIYUKG2dvQQ1Izw==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/format": "^18.1.0",
+ "@commitlint/lint": "^18.1.0",
+ "@commitlint/load": "^18.2.0",
+ "@commitlint/read": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "execa": "^5.0.0",
+ "lodash.isfunction": "^3.0.9",
+ "resolve-from": "5.0.0",
+ "resolve-global": "1.0.0",
+ "yargs": "^17.0.0"
+ },
+ "bin": {
+ "commitlint": "cli.js"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/config-conventional": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.1.0.tgz",
+ "integrity": "sha512-8vvvtV3GOLEMHeKc8PjRL1lfP1Y4B6BG0WroFd9PJeRiOc3nFX1J0wlJenLURzl9Qus6YXVGWf+a/ZlbCKT3AA==",
+ "dev": true,
+ "dependencies": {
+ "conventional-changelog-conventionalcommits": "^7.0.2"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/config-validator": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.1.0.tgz",
+ "integrity": "sha512-kbHkIuItXn93o2NmTdwi5Mk1ujyuSIysRE/XHtrcps/27GuUKEIqBJp6TdJ4Sq+ze59RlzYSHMKuDKZbfg9+uQ==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/types": "^18.1.0",
+ "ajv": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/ensure": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.1.0.tgz",
+ "integrity": "sha512-CkPzJ9UBumIo54VDcpmBlaVX81J++wzEhN3DJH9+6PaLeiIG+gkSx8t7C2gfwG7PaiW4HzQtdQlBN5ab+c4vFQ==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/types": "^18.1.0",
+ "lodash.camelcase": "^4.3.0",
+ "lodash.kebabcase": "^4.1.1",
+ "lodash.snakecase": "^4.1.1",
+ "lodash.startcase": "^4.4.0",
+ "lodash.upperfirst": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/execute-rule": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.1.0.tgz",
+ "integrity": "sha512-w3Vt4K+O7+nSr9/gFSEfZ1exKUOPSlJaRpnk7Y+XowEhvwT7AIk1HNANH+gETf0zGZ020+hfiMW/Ome+SNCUsg==",
+ "dev": true,
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/format": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.1.0.tgz",
+ "integrity": "sha512-So/w217tGWMZZb1yXcUFNF2qFLyYtSVqbnGoMbX8a+JKcG4oB11Gc1adS0ssUOMivtiNpaLtkSHFynyiwtJtiQ==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/types": "^18.1.0",
+ "chalk": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/is-ignored": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.1.0.tgz",
+ "integrity": "sha512-fa1fY93J/Nx2GH6r6WOLdBOiL7x9Uc1N7wcpmaJ1C5Qs6P+rPSUTkofe2IOhSJIJoboHfAH6W0ru4xtK689t0Q==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/types": "^18.1.0",
+ "semver": "7.5.4"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/is-ignored/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@commitlint/is-ignored/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@commitlint/is-ignored/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/@commitlint/lint": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.1.0.tgz",
+ "integrity": "sha512-LGB3eI5UYu5LLayibNrRM4bSbowr1z9uyqvp0c7+0KaSJi+xHxy/QEhb6fy4bMAtbXEvygY0sUu9HxSWg41rVQ==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/is-ignored": "^18.1.0",
+ "@commitlint/parse": "^18.1.0",
+ "@commitlint/rules": "^18.1.0",
+ "@commitlint/types": "^18.1.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/load": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.2.0.tgz",
+ "integrity": "sha512-xjX3d3CRlOALwImhOsmLYZh14/+gW/KxsY7+bPKrzmGuFailf9K7ckhB071oYZVJdACnpY4hDYiosFyOC+MpAA==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/config-validator": "^18.1.0",
+ "@commitlint/execute-rule": "^18.1.0",
+ "@commitlint/resolve-extends": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "@types/node": "^18.11.9",
+ "chalk": "^4.1.0",
+ "cosmiconfig": "^8.0.0",
+ "cosmiconfig-typescript-loader": "^5.0.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "lodash.uniq": "^4.5.0",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/load/node_modules/cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "dev": true,
+ "dependencies": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@commitlint/load/node_modules/cosmiconfig-typescript-loader": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz",
+ "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==",
+ "dev": true,
+ "dependencies": {
+ "jiti": "^1.19.1"
+ },
+ "engines": {
+ "node": ">=v16"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "cosmiconfig": ">=8.2",
+ "typescript": ">=4"
+ }
+ },
+ "node_modules/@commitlint/load/node_modules/typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/@commitlint/message": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.1.0.tgz",
+ "integrity": "sha512-8dT/jJg73wf3o2Mut/fqEDTpBYSIEVtX5PWyuY/0uviEYeheZAczFo/VMIkeGzhJJn1IrcvAwWsvJ1lVGY2I/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/parse": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.1.0.tgz",
+ "integrity": "sha512-23yv8uBweXWYn8bXk4PjHIsmVA+RkbqPh2h7irupBo2LthVlzMRc4LM6UStasScJ4OlXYYaWOmuP7jcExUF50Q==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/types": "^18.1.0",
+ "conventional-changelog-angular": "^6.0.0",
+ "conventional-commits-parser": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/read": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.1.0.tgz",
+ "integrity": "sha512-rzfzoKUwxmvYO81tI5o1371Nwt3vhcQR36oTNfupPdU1jgSL3nzBIS3B93LcZh3IYKbCIMyMPN5WZ10BXdeoUg==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/top-level": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "fs-extra": "^11.0.0",
+ "git-raw-commits": "^2.0.11",
+ "minimist": "^1.2.6"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/read/node_modules/fs-extra": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
+ "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/@commitlint/read/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@commitlint/read/node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@commitlint/resolve-extends": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.1.0.tgz",
+ "integrity": "sha512-3mZpzOEJkELt7BbaZp6+bofJyxViyObebagFn0A7IHaLARhPkWTivXdjvZHS12nAORftv88Yhbh8eCPKfSvB7g==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/config-validator": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "import-fresh": "^3.0.0",
+ "lodash.mergewith": "^4.6.2",
+ "resolve-from": "^5.0.0",
+ "resolve-global": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/rules": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.1.0.tgz",
+ "integrity": "sha512-VJNQ674CRv4znI0DbsjZLVnn647J+BTxHGcrDIsYv7c99gW7TUGeIe5kL80G7l8+5+N0se8v9yn+Prr8xEy6Yw==",
+ "dev": true,
+ "dependencies": {
+ "@commitlint/ensure": "^18.1.0",
+ "@commitlint/message": "^18.1.0",
+ "@commitlint/to-lines": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "execa": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/to-lines": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.1.0.tgz",
+ "integrity": "sha512-aHIoSDjG0ckxPLYDpODUeSLbEKmF6Jrs1B5JIssbbE9eemBtXtjm9yzdiAx9ZXcwoHlhbTp2fbndDb3YjlvJag==",
+ "dev": true,
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/top-level": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.1.0.tgz",
+ "integrity": "sha512-1/USHlolIxJlsfLKecSXH+6PDojIvnzaJGPYwF7MtnTuuXCNQ4izkeqDsRuNMe9nU2VIKpK9OT8Q412kGNmgGw==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=v18"
+ }
+ },
+ "node_modules/@commitlint/top-level/node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@commitlint/top-level/node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@commitlint/top-level/node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@commitlint/top-level/node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@commitlint/top-level/node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@commitlint/types": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.1.0.tgz",
+ "integrity": "sha512-65vGxZmbs+2OVwEItxhp3Ul7X2m2LyLfifYI/NdPwRqblmuES2w2aIRhIjb7cwUIBHHSTT8WXj4ixVHQibmvLQ==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=v18"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -1185,11 +1696,23 @@
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="
},
+ "node_modules/@types/minimist": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.4.tgz",
+ "integrity": "sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "18.11.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
},
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz",
+ "integrity": "sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==",
+ "dev": true
+ },
"node_modules/@types/pbkdf2": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz",
@@ -1206,6 +1729,21 @@
"@types/node": "*"
}
},
+ "node_modules/@types/yargs": {
+ "version": "17.0.28",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz",
+ "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz",
+ "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==",
+ "dev": true
+ },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -1282,8 +1820,24 @@
"clean-stack": "^2.0.0",
"indent-string": "^4.0.0"
},
- "engines": {
- "node": ">=8"
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-colors": {
@@ -1350,6 +1904,21 @@
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
+ "node_modules/array-ify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
+ "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
+ "dev": true
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
@@ -1571,6 +2140,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
@@ -1582,6 +2160,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/camelcase-keys": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+ "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "map-obj": "^4.0.0",
+ "quick-lru": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/camelcase-keys/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/catering": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz",
@@ -1591,16 +2195,67 @@
}
},
"node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
},
"engines": {
- "node": ">=4"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/chalk/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/chalk/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/chokidar": {
@@ -1668,13 +2323,17 @@
}
},
"node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
"dependencies": {
"string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
+ "strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/color-convert": {
@@ -1700,11 +2359,63 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz",
"integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow=="
},
+ "node_modules/compare-func": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
+ "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
+ "dev": true,
+ "dependencies": {
+ "array-ify": "^1.0.0",
+ "dot-prop": "^5.1.0"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "node_modules/conventional-changelog-angular": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz",
+ "integrity": "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==",
+ "dev": true,
+ "dependencies": {
+ "compare-func": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/conventional-changelog-conventionalcommits": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz",
+ "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==",
+ "dev": true,
+ "dependencies": {
+ "compare-func": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/conventional-commits-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz",
+ "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==",
+ "dev": true,
+ "dependencies": {
+ "is-text-path": "^2.0.0",
+ "JSONStream": "^1.3.5",
+ "meow": "^12.0.1",
+ "split2": "^4.0.0"
+ },
+ "bin": {
+ "conventional-commits-parser": "cli.mjs"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
@@ -1755,6 +2466,29 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/dargs": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
+ "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1782,6 +2516,40 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/decamelize-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+ "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+ "dev": true,
+ "dependencies": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decamelize-keys/node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decamelize-keys/node_modules/map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1798,6 +2566,30 @@
"node": ">=0.3.1"
}
},
+ "node_modules/dot-prop": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
+ "dev": true,
+ "dependencies": {
+ "is-obj": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
"node_modules/elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -1847,6 +2639,15 @@
"node": ">=6"
}
},
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1967,6 +2768,35 @@
"safe-buffer": "^5.1.1"
}
},
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2030,6 +2860,12 @@
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz",
"integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg=="
},
+ "node_modules/fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==",
+ "dev": true
+ },
"node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@@ -2062,9 +2898,12 @@
}
},
"node_modules/function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/functional-red-black-tree": {
"version": "1.0.1",
@@ -2092,6 +2931,83 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/git-raw-commits": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz",
+ "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==",
+ "dev": true,
+ "dependencies": {
+ "dargs": "^7.0.0",
+ "lodash": "^4.17.15",
+ "meow": "^8.0.0",
+ "split2": "^3.0.0",
+ "through2": "^4.0.0"
+ },
+ "bin": {
+ "git-raw-commits": "cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/git-raw-commits/node_modules/meow": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
+ "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/minimist": "^1.2.0",
+ "camelcase-keys": "^6.2.2",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
+ "read-pkg-up": "^7.0.1",
+ "redent": "^3.0.0",
+ "trim-newlines": "^3.0.0",
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/git-raw-commits/node_modules/split2": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
+ "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "^3.0.0"
+ }
+ },
+ "node_modules/git-raw-commits/node_modules/type-fest": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -2122,6 +3038,18 @@
"node": ">= 6"
}
},
+ "node_modules/global-dirs": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+ "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==",
+ "dev": true,
+ "dependencies": {
+ "ini": "^1.3.4"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
@@ -2147,6 +3075,15 @@
"uglify-js": "^3.1.4"
}
},
+ "node_modules/hard-rejection": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/hardhat": {
"version": "2.12.4",
"resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.4.tgz",
@@ -2234,6 +3171,19 @@
"hardhat": "^2.0.5"
}
},
+ "node_modules/hardhat/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -2286,6 +3236,18 @@
"minimalistic-assert": "^1.0.1"
}
},
+ "node_modules/hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -2304,6 +3266,36 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/hosted-git-info/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/hosted-git-info/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -2331,6 +3323,30 @@
"node": ">= 6"
}
},
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/husky": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
+ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
+ "dev": true,
+ "bin": {
+ "husky": "lib/bin.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -2366,6 +3382,31 @@
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.0.tgz",
"integrity": "sha512-h4ujZ0OZ3kpvdFcwJAHXEdvawH7J8TYTB62e8xI03OSZhuGpuPY9DPXnonMN8s+uQ56gMUqMK71mXU8ob20xfA=="
},
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/import-fresh/node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/imul": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/imul/-/imul-1.0.1.tgz",
@@ -2397,6 +3438,12 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
"node_modules/io-ts": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz",
@@ -2405,6 +3452,12 @@
"fp-ts": "^1.0.0"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -2438,6 +3491,18 @@
"node": ">=4"
}
},
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2482,6 +3547,15 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
@@ -2490,6 +3564,30 @@
"node": ">=8"
}
},
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-text-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
+ "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==",
+ "dev": true,
+ "dependencies": {
+ "text-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -2501,11 +3599,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jiti": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz",
+ "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==",
+ "dev": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
"node_modules/js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
},
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2517,6 +3636,18 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -2525,6 +3656,31 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jsonparse": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
+ "dev": true,
+ "engines": [
+ "node >= 0.2.0"
+ ]
+ },
+ "node_modules/JSONStream": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+ "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+ "dev": true,
+ "dependencies": {
+ "jsonparse": "^1.2.0",
+ "through": ">=2.2.7 <3"
+ },
+ "bin": {
+ "JSONStream": "bin.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/keccak": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz",
@@ -2539,6 +3695,15 @@
"node": ">=10.0.0"
}
},
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/klaw": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
@@ -2583,6 +3748,12 @@
"node": ">=12"
}
},
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
"node_modules/locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -2600,6 +3771,66 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "dev": true
+ },
+ "node_modules/lodash.isfunction": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
+ "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
+ "dev": true
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true
+ },
+ "node_modules/lodash.kebabcase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
+ "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lodash.mergewith": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
+ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
+ "dev": true
+ },
+ "node_modules/lodash.snakecase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
+ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
+ "dev": true
+ },
+ "node_modules/lodash.startcase": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
+ "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
+ "dev": true
+ },
+ "node_modules/lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
+ "dev": true
+ },
+ "node_modules/lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==",
+ "dev": true
+ },
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -2615,70 +3846,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/log-symbols/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/log-symbols/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/log-symbols/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/log-symbols/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "node_modules/log-symbols/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/log-symbols/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/lru_map": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
@@ -2698,6 +3865,18 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"devOptional": true
},
+ "node_modules/map-obj": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mcl-wasm": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz",
@@ -2734,7 +3913,43 @@
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
"engines": {
- "node": ">= 0.10.0"
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/meow": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
+ "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
}
},
"node_modules/minimalistic-assert": {
@@ -2766,6 +3981,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minimist-options": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/minimist-options/node_modules/is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/mnemonist": {
"version": "0.38.5",
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz",
@@ -2829,6 +4067,16 @@
"balanced-match": "^1.0.0"
}
},
+ "node_modules/mocha/node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
"node_modules/mocha/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -2943,6 +4191,23 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/mocha/node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/module-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz",
@@ -3003,6 +4268,54 @@
"node-gyp-build-test": "build-test.js"
}
},
+ "node_modules/normalize-package-data": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+ "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^4.0.1",
+ "is-core-module": "^2.5.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -3011,6 +4324,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@@ -3032,6 +4357,21 @@
"wrappy": "1"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -3084,6 +4424,36 @@
"node": ">=4"
}
},
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@@ -3100,11 +4470,29 @@
"node": ">=0.10.0"
}
},
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/pbkdf2": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
@@ -3131,6 +4519,15 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@@ -3164,6 +4561,15 @@
}
]
},
+ "node_modules/quick-lru": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -3186,6 +4592,153 @@
"node": ">= 0.8"
}
},
+ "node_modules/read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+ "dev": true,
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg/node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true
+ },
+ "node_modules/read-pkg/node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/read-pkg/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -3210,6 +4763,19 @@
"node": ">=8.10.0"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -3237,6 +4803,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-global": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz",
+ "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==",
+ "dev": true,
+ "dependencies": {
+ "global-dirs": "^0.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -3376,6 +4963,27 @@
"sha.js": "bin.js"
}
},
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -3389,6 +4997,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
"node_modules/solc": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz",
@@ -3473,6 +5087,47 @@
"source-map": "^0.6.0"
}
},
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.16",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
+ "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
+ "dev": true
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
"node_modules/stacktrace-parser": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz",
@@ -3540,6 +5195,15 @@
"node": ">=8"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/strip-hex-prefix": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz",
@@ -3552,6 +5216,18 @@
"npm": ">=3"
}
},
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -3574,6 +5250,33 @@
"node": ">=4"
}
},
+ "node_modules/text-extensions": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
+ "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true
+ },
+ "node_modules/through2": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+ "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "3"
+ }
+ },
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -3604,6 +5307,15 @@
"node": ">=0.6"
}
},
+ "node_modules/trim-newlines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -3739,6 +5451,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -3758,6 +5479,31 @@
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"devOptional": true
},
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@@ -3853,20 +5599,21 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
"dependencies": {
- "cliui": "^7.0.2",
+ "cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
+ "yargs-parser": "^21.1.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
}
},
"node_modules/yargs-parser": {
@@ -3891,6 +5638,15 @@
"node": ">=10"
}
},
+ "node_modules/yargs/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
@@ -3913,6 +5669,379 @@
}
},
"dependencies": {
+ "@babel/code-frame": {
+ "version": "7.22.13",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+ "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.22.13",
+ "chalk": "^2.4.2"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ }
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
+ "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ }
+ }
+ },
+ "@commitlint/cli": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-18.2.0.tgz",
+ "integrity": "sha512-F/DCG791kMFmWg5eIdogakuGeg4OiI2kD430ed1a1Hh3epvrJdeIAgcGADAMIOmF+m0S1+VlIYUKG2dvQQ1Izw==",
+ "dev": true,
+ "requires": {
+ "@commitlint/format": "^18.1.0",
+ "@commitlint/lint": "^18.1.0",
+ "@commitlint/load": "^18.2.0",
+ "@commitlint/read": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "execa": "^5.0.0",
+ "lodash.isfunction": "^3.0.9",
+ "resolve-from": "5.0.0",
+ "resolve-global": "1.0.0",
+ "yargs": "^17.0.0"
+ }
+ },
+ "@commitlint/config-conventional": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-18.1.0.tgz",
+ "integrity": "sha512-8vvvtV3GOLEMHeKc8PjRL1lfP1Y4B6BG0WroFd9PJeRiOc3nFX1J0wlJenLURzl9Qus6YXVGWf+a/ZlbCKT3AA==",
+ "dev": true,
+ "requires": {
+ "conventional-changelog-conventionalcommits": "^7.0.2"
+ }
+ },
+ "@commitlint/config-validator": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-18.1.0.tgz",
+ "integrity": "sha512-kbHkIuItXn93o2NmTdwi5Mk1ujyuSIysRE/XHtrcps/27GuUKEIqBJp6TdJ4Sq+ze59RlzYSHMKuDKZbfg9+uQ==",
+ "dev": true,
+ "requires": {
+ "@commitlint/types": "^18.1.0",
+ "ajv": "^8.11.0"
+ }
+ },
+ "@commitlint/ensure": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-18.1.0.tgz",
+ "integrity": "sha512-CkPzJ9UBumIo54VDcpmBlaVX81J++wzEhN3DJH9+6PaLeiIG+gkSx8t7C2gfwG7PaiW4HzQtdQlBN5ab+c4vFQ==",
+ "dev": true,
+ "requires": {
+ "@commitlint/types": "^18.1.0",
+ "lodash.camelcase": "^4.3.0",
+ "lodash.kebabcase": "^4.1.1",
+ "lodash.snakecase": "^4.1.1",
+ "lodash.startcase": "^4.4.0",
+ "lodash.upperfirst": "^4.3.1"
+ }
+ },
+ "@commitlint/execute-rule": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-18.1.0.tgz",
+ "integrity": "sha512-w3Vt4K+O7+nSr9/gFSEfZ1exKUOPSlJaRpnk7Y+XowEhvwT7AIk1HNANH+gETf0zGZ020+hfiMW/Ome+SNCUsg==",
+ "dev": true
+ },
+ "@commitlint/format": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-18.1.0.tgz",
+ "integrity": "sha512-So/w217tGWMZZb1yXcUFNF2qFLyYtSVqbnGoMbX8a+JKcG4oB11Gc1adS0ssUOMivtiNpaLtkSHFynyiwtJtiQ==",
+ "dev": true,
+ "requires": {
+ "@commitlint/types": "^18.1.0",
+ "chalk": "^4.1.0"
+ }
+ },
+ "@commitlint/is-ignored": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.1.0.tgz",
+ "integrity": "sha512-fa1fY93J/Nx2GH6r6WOLdBOiL7x9Uc1N7wcpmaJ1C5Qs6P+rPSUTkofe2IOhSJIJoboHfAH6W0ru4xtK689t0Q==",
+ "dev": true,
+ "requires": {
+ "@commitlint/types": "^18.1.0",
+ "semver": "7.5.4"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
+ "@commitlint/lint": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-18.1.0.tgz",
+ "integrity": "sha512-LGB3eI5UYu5LLayibNrRM4bSbowr1z9uyqvp0c7+0KaSJi+xHxy/QEhb6fy4bMAtbXEvygY0sUu9HxSWg41rVQ==",
+ "dev": true,
+ "requires": {
+ "@commitlint/is-ignored": "^18.1.0",
+ "@commitlint/parse": "^18.1.0",
+ "@commitlint/rules": "^18.1.0",
+ "@commitlint/types": "^18.1.0"
+ }
+ },
+ "@commitlint/load": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-18.2.0.tgz",
+ "integrity": "sha512-xjX3d3CRlOALwImhOsmLYZh14/+gW/KxsY7+bPKrzmGuFailf9K7ckhB071oYZVJdACnpY4hDYiosFyOC+MpAA==",
+ "dev": true,
+ "requires": {
+ "@commitlint/config-validator": "^18.1.0",
+ "@commitlint/execute-rule": "^18.1.0",
+ "@commitlint/resolve-extends": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "@types/node": "^18.11.9",
+ "chalk": "^4.1.0",
+ "cosmiconfig": "^8.0.0",
+ "cosmiconfig-typescript-loader": "^5.0.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "lodash.uniq": "^4.5.0",
+ "resolve-from": "^5.0.0"
+ },
+ "dependencies": {
+ "cosmiconfig": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+ "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
+ "dev": true,
+ "requires": {
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0",
+ "path-type": "^4.0.0"
+ }
+ },
+ "cosmiconfig-typescript-loader": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz",
+ "integrity": "sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==",
+ "dev": true,
+ "requires": {
+ "jiti": "^1.19.1"
+ }
+ },
+ "typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "dev": true,
+ "peer": true
+ }
+ }
+ },
+ "@commitlint/message": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.1.0.tgz",
+ "integrity": "sha512-8dT/jJg73wf3o2Mut/fqEDTpBYSIEVtX5PWyuY/0uviEYeheZAczFo/VMIkeGzhJJn1IrcvAwWsvJ1lVGY2I/w==",
+ "dev": true
+ },
+ "@commitlint/parse": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-18.1.0.tgz",
+ "integrity": "sha512-23yv8uBweXWYn8bXk4PjHIsmVA+RkbqPh2h7irupBo2LthVlzMRc4LM6UStasScJ4OlXYYaWOmuP7jcExUF50Q==",
+ "dev": true,
+ "requires": {
+ "@commitlint/types": "^18.1.0",
+ "conventional-changelog-angular": "^6.0.0",
+ "conventional-commits-parser": "^5.0.0"
+ }
+ },
+ "@commitlint/read": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-18.1.0.tgz",
+ "integrity": "sha512-rzfzoKUwxmvYO81tI5o1371Nwt3vhcQR36oTNfupPdU1jgSL3nzBIS3B93LcZh3IYKbCIMyMPN5WZ10BXdeoUg==",
+ "dev": true,
+ "requires": {
+ "@commitlint/top-level": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "fs-extra": "^11.0.0",
+ "git-raw-commits": "^2.0.11",
+ "minimist": "^1.2.6"
+ },
+ "dependencies": {
+ "fs-extra": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
+ "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ },
+ "jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
+ "universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true
+ }
+ }
+ },
+ "@commitlint/resolve-extends": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-18.1.0.tgz",
+ "integrity": "sha512-3mZpzOEJkELt7BbaZp6+bofJyxViyObebagFn0A7IHaLARhPkWTivXdjvZHS12nAORftv88Yhbh8eCPKfSvB7g==",
+ "dev": true,
+ "requires": {
+ "@commitlint/config-validator": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "import-fresh": "^3.0.0",
+ "lodash.mergewith": "^4.6.2",
+ "resolve-from": "^5.0.0",
+ "resolve-global": "^1.0.0"
+ }
+ },
+ "@commitlint/rules": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-18.1.0.tgz",
+ "integrity": "sha512-VJNQ674CRv4znI0DbsjZLVnn647J+BTxHGcrDIsYv7c99gW7TUGeIe5kL80G7l8+5+N0se8v9yn+Prr8xEy6Yw==",
+ "dev": true,
+ "requires": {
+ "@commitlint/ensure": "^18.1.0",
+ "@commitlint/message": "^18.1.0",
+ "@commitlint/to-lines": "^18.1.0",
+ "@commitlint/types": "^18.1.0",
+ "execa": "^5.0.0"
+ }
+ },
+ "@commitlint/to-lines": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-18.1.0.tgz",
+ "integrity": "sha512-aHIoSDjG0ckxPLYDpODUeSLbEKmF6Jrs1B5JIssbbE9eemBtXtjm9yzdiAx9ZXcwoHlhbTp2fbndDb3YjlvJag==",
+ "dev": true
+ },
+ "@commitlint/top-level": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-18.1.0.tgz",
+ "integrity": "sha512-1/USHlolIxJlsfLKecSXH+6PDojIvnzaJGPYwF7MtnTuuXCNQ4izkeqDsRuNMe9nU2VIKpK9OT8Q412kGNmgGw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^5.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ }
+ }
+ },
+ "@commitlint/types": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-18.1.0.tgz",
+ "integrity": "sha512-65vGxZmbs+2OVwEItxhp3Ul7X2m2LyLfifYI/NdPwRqblmuES2w2aIRhIjb7cwUIBHHSTT8WXj4ixVHQibmvLQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.0"
+ }
+ },
"@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -4735,11 +6864,23 @@
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="
},
+ "@types/minimist": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.4.tgz",
+ "integrity": "sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==",
+ "dev": true
+ },
"@types/node": {
"version": "18.11.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
},
+ "@types/normalize-package-data": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz",
+ "integrity": "sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==",
+ "dev": true
+ },
"@types/pbkdf2": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz",
@@ -4756,6 +6897,21 @@
"@types/node": "*"
}
},
+ "@types/yargs": {
+ "version": "17.0.28",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz",
+ "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==",
+ "dev": true,
+ "requires": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "@types/yargs-parser": {
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz",
+ "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==",
+ "dev": true
+ },
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -4808,8 +6964,20 @@
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"requires": {
- "clean-stack": "^2.0.0",
- "indent-string": "^4.0.0"
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ }
+ },
+ "ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
}
},
"ansi-colors": {
@@ -4858,6 +7026,18 @@
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
+ "array-ify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
+ "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
+ "dev": true
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+ "dev": true
+ },
"async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
@@ -5030,24 +7210,84 @@
"get-intrinsic": "^1.0.2"
}
},
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
"camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
},
+ "camelcase-keys": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+ "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.3.1",
+ "map-obj": "^4.0.0",
+ "quick-lru": "^4.0.1"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ }
+ }
+ },
"catering": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz",
"integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w=="
},
"chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"requires": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
}
},
"chokidar": {
@@ -5097,12 +7337,13 @@
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
},
"cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
"requires": {
"string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
+ "strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
}
},
@@ -5129,11 +7370,51 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz",
"integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow=="
},
+ "compare-func": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
+ "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
+ "dev": true,
+ "requires": {
+ "array-ify": "^1.0.0",
+ "dot-prop": "^5.1.0"
+ }
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "conventional-changelog-angular": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz",
+ "integrity": "sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==",
+ "dev": true,
+ "requires": {
+ "compare-func": "^2.0.0"
+ }
+ },
+ "conventional-changelog-conventionalcommits": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz",
+ "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==",
+ "dev": true,
+ "requires": {
+ "compare-func": "^2.0.0"
+ }
+ },
+ "conventional-commits-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz",
+ "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==",
+ "dev": true,
+ "requires": {
+ "is-text-path": "^2.0.0",
+ "JSONStream": "^1.3.5",
+ "meow": "^12.0.1",
+ "split2": "^4.0.0"
+ }
+ },
"cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
@@ -5175,6 +7456,23 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "dargs": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
+ "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==",
+ "dev": true
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -5188,6 +7486,30 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
"integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="
},
+ "decamelize-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+ "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+ "dev": true,
+ "requires": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "dependencies": {
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+ "dev": true
+ }
+ }
+ },
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -5198,6 +7520,21 @@
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w=="
},
+ "dot-prop": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
+ "dev": true,
+ "requires": {
+ "is-obj": "^2.0.0"
+ }
+ },
+ "dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "dev": true
+ },
"elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -5243,6 +7580,15 @@
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
},
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -5354,6 +7700,29 @@
"safe-buffer": "^5.1.1"
}
},
+ "execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -5394,6 +7763,12 @@
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz",
"integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg=="
},
+ "fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==",
+ "dev": true
+ },
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@@ -5416,9 +7791,9 @@
"optional": true
},
"function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"functional-red-black-tree": {
"version": "1.0.1",
@@ -5440,6 +7815,61 @@
"has-symbols": "^1.0.3"
}
},
+ "get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true
+ },
+ "git-raw-commits": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz",
+ "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==",
+ "dev": true,
+ "requires": {
+ "dargs": "^7.0.0",
+ "lodash": "^4.17.15",
+ "meow": "^8.0.0",
+ "split2": "^3.0.0",
+ "through2": "^4.0.0"
+ },
+ "dependencies": {
+ "meow": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
+ "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==",
+ "dev": true,
+ "requires": {
+ "@types/minimist": "^1.2.0",
+ "camelcase-keys": "^6.2.2",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
+ "read-pkg-up": "^7.0.1",
+ "redent": "^3.0.0",
+ "trim-newlines": "^3.0.0",
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
+ }
+ },
+ "split2": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
+ "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^3.0.0"
+ }
+ },
+ "type-fest": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+ "dev": true
+ }
+ }
+ },
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -5461,6 +7891,15 @@
"is-glob": "^4.0.1"
}
},
+ "global-dirs": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+ "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.4"
+ }
+ },
"graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
@@ -5478,6 +7917,12 @@
"wordwrap": "^1.0.0"
}
},
+ "hard-rejection": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+ "dev": true
+ },
"hardhat": {
"version": "2.12.4",
"resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.4.tgz",
@@ -5533,6 +7978,18 @@
"undici": "^5.4.0",
"uuid": "^8.3.2",
"ws": "^7.4.6"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ }
}
},
"hardhat-preprocessor": {
@@ -5581,6 +8038,15 @@
"minimalistic-assert": "^1.0.1"
}
},
+ "hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.2"
+ }
+ },
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -5596,6 +8062,32 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
"http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -5617,6 +8109,18 @@
"debug": "4"
}
},
+ "human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true
+ },
+ "husky": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
+ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
+ "dev": true
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -5635,6 +8139,24 @@
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.0.tgz",
"integrity": "sha512-h4ujZ0OZ3kpvdFcwJAHXEdvawH7J8TYTB62e8xI03OSZhuGpuPY9DPXnonMN8s+uQ56gMUqMK71mXU8ob20xfA=="
},
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ }
+ }
+ },
"imul": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/imul/-/imul-1.0.1.tgz",
@@ -5660,6 +8182,12 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
+ "ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
"io-ts": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz",
@@ -5668,6 +8196,12 @@
"fp-ts": "^1.0.0"
}
},
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -5681,6 +8215,15 @@
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
},
+ "is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dev": true,
+ "requires": {
+ "hasown": "^2.0.0"
+ }
+ },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -5709,21 +8252,60 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
+ "is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "dev": true
+ },
"is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="
},
+ "is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true
+ },
+ "is-text-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
+ "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==",
+ "dev": true,
+ "requires": {
+ "text-extensions": "^2.0.0"
+ }
+ },
"is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="
},
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "jiti": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz",
+ "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==",
+ "dev": true
+ },
"js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
},
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -5732,6 +8314,18 @@
"argparse": "^2.0.1"
}
},
+ "json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -5740,6 +8334,22 @@
"graceful-fs": "^4.1.6"
}
},
+ "jsonparse": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
+ "dev": true
+ },
+ "JSONStream": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+ "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+ "dev": true,
+ "requires": {
+ "jsonparse": "^1.2.0",
+ "through": ">=2.2.7 <3"
+ }
+ },
"keccak": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz",
@@ -5750,6 +8360,12 @@
"readable-stream": "^3.6.0"
}
},
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ },
"klaw": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
@@ -5781,6 +8397,12 @@
"module-error": "^1.0.1"
}
},
+ "lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -5790,10 +8412,70 @@
"path-exists": "^3.0.0"
}
},
- "lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "dev": true
+ },
+ "lodash.isfunction": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
+ "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
+ "dev": true
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true
+ },
+ "lodash.kebabcase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
+ "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==",
+ "dev": true
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "lodash.mergewith": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
+ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
+ "dev": true
+ },
+ "lodash.snakecase": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
+ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
+ "dev": true
+ },
+ "lodash.startcase": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
+ "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
+ "dev": true
+ },
+ "lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
+ "dev": true
+ },
+ "lodash.upperfirst": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
+ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==",
+ "dev": true
},
"log-symbols": {
"version": "4.1.0",
@@ -5802,51 +8484,6 @@
"requires": {
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
}
},
"lru_map": {
@@ -5868,6 +8505,12 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"devOptional": true
},
+ "map-obj": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+ "dev": true
+ },
"mcl-wasm": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz",
@@ -5898,6 +8541,30 @@
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="
},
+ "meow": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
+ "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
+ "dev": true
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
+ },
+ "min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true
+ },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -5921,6 +8588,25 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
+ "minimist-options": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
+ },
+ "dependencies": {
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+ "dev": true
+ }
+ }
+ },
"mnemonist": {
"version": "0.38.5",
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz",
@@ -5970,6 +8656,16 @@
"balanced-match": "^1.0.0"
}
},
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -6038,6 +8734,20 @@
"requires": {
"has-flag": "^4.0.0"
}
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
}
}
},
@@ -6087,11 +8797,58 @@
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg=="
},
+ "normalize-package-data": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+ "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^4.0.1",
+ "is-core-module": "^2.5.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "dependencies": {
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ }
+ }
+ },
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
+ "npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.0.0"
+ }
+ },
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
@@ -6110,6 +8867,15 @@
"wrappy": "1"
}
},
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -6144,6 +8910,27 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww=="
},
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ }
+ },
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@@ -6154,11 +8941,23 @@
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
},
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
"pbkdf2": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
@@ -6176,6 +8975,12 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
+ "punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true
+ },
"qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@@ -6189,6 +8994,12 @@
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
+ "quick-lru": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "dev": true
+ },
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -6208,6 +9019,118 @@
"unpipe": "1.0.0"
}
},
+ "read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+ "dev": true,
+ "requires": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "dependencies": {
+ "hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true
+ },
+ "normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true
+ },
+ "type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true
+ }
+ }
+ },
+ "read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ }
+ }
+ },
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -6226,6 +9149,16 @@
"picomatch": "^2.2.1"
}
},
+ "redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "requires": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ }
+ },
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -6244,6 +9177,21 @@
"path-parse": "^1.0.6"
}
},
+ "resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true
+ },
+ "resolve-global": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz",
+ "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==",
+ "dev": true,
+ "requires": {
+ "global-dirs": "^0.1.1"
+ }
+ },
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -6339,6 +9287,21 @@
"safe-buffer": "^5.0.1"
}
},
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -6349,6 +9312,12 @@
"object-inspect": "^1.9.0"
}
},
+ "signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
"solc": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz",
@@ -6420,6 +9389,44 @@
"source-map": "^0.6.0"
}
},
+ "spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.16",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz",
+ "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==",
+ "dev": true
+ },
+ "split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "dev": true
+ },
"stacktrace-parser": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz",
@@ -6471,6 +9478,12 @@
"ansi-regex": "^5.0.1"
}
},
+ "strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true
+ },
"strip-hex-prefix": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz",
@@ -6479,6 +9492,15 @@
"is-hex-prefixed": "1.0.0"
}
},
+ "strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "requires": {
+ "min-indent": "^1.0.0"
+ }
+ },
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -6492,6 +9514,27 @@
"has-flag": "^3.0.0"
}
},
+ "text-extensions": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
+ "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true
+ },
+ "through2": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+ "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "3"
+ }
+ },
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -6513,6 +9556,12 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
+ "trim-newlines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
+ "dev": true
+ },
"ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -6597,6 +9646,15 @@
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
},
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -6613,6 +9671,25 @@
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"devOptional": true
},
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@@ -6678,17 +9755,26 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
"requires": {
- "cliui": "^7.0.2",
+ "cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
+ "yargs-parser": "^21.1.1"
+ },
+ "dependencies": {
+ "yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true
+ }
}
},
"yargs-parser": {
diff --git a/package.json b/package.json
index a5239ee771..413cfd75bb 100644
--- a/package.json
+++ b/package.json
@@ -21,10 +21,18 @@
},
"homepage": "https://github.com/Layr-Labs/eigenlayer-contracts#readme",
"devDependencies": {
+ "@commitlint/cli": "^18.2.0",
+ "@commitlint/config-conventional": "^18.1.0",
+ "@types/yargs": "^17.0.28",
+ "chalk": "^4.1.0",
+ "dotenv": "^16.3.1",
+ "fs": "^0.0.1-security",
"hardhat": "^2.12.4",
"hardhat-preprocessor": "^0.1.5",
+ "husky": "^8.0.3",
"ts-node": "^10.9.1",
- "typescript": "^4.9.4"
+ "typescript": "^4.9.4",
+ "yargs": "^17.7.2"
},
"dependencies": {
"solidity-docgen": "^0.6.0-beta.32"
diff --git a/script/Allocate.s.sol b/script/Allocate.s.sol
deleted file mode 100644
index da3d9768d2..0000000000
--- a/script/Allocate.s.sol
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./utils/Allocator.sol";
-import "./EigenLayerParser.sol";
-
-contract Allocate is Script, DSTest, EigenLayerParser {
- //performs basic deployment before each test
- function run() external {
- // read meta data from json
- parseEigenLayerParams();
-
- uint256 wethAmount = eigenTotalSupply / (numStaker + numDis + 50); // save 100 portions
- vm.startBroadcast();
-
- Allocator allocator = new Allocator();
-
- weth.approve(address(allocator), type(uint256).max);
- eigen.approve(address(allocator), type(uint256).max);
-
- address[] memory stakers = new address[](numStaker);
- // deployer allocate weth, eigen to staker
- for (uint i = 0; i < numStaker ; ++i) {
- address stakerAddr = stdJson.readAddress(configJson, string.concat(".staker[", string.concat(vm.toString(i), "].address")));
- stakers[i] = stakerAddr;
- emit log("stakerAddr");
- emit log_address(stakerAddr);
- }
- allocator.allocate(weth, stakers, wethAmount);
- allocator.allocate(eigen, stakers, wethAmount);
-
- address[] memory dispersers = new address[](numDis);
- // deployer allocate weth, eigen to disperser
- for (uint i = 0; i < numDis ; ++i) {
- address disAddr = stdJson.readAddress(configJson, string.concat(".dis[", string.concat(vm.toString(i), "].address")));
- dispersers[i] = disAddr;
- emit log("disAddr");
- emit log_address(disAddr);
- }
- allocator.allocate(weth, dispersers, wethAmount);
-
- vm.stopBroadcast();
- }
-}
-
-contract ProvisionWeth is Script, DSTest, EigenLayerParser {
- uint256 wethAmount = 100000000000000000000;
- //performs basic deployment before each test
-
- function run() external {
- vm.startBroadcast();
- // read meta data from json
- addressJson = vm.readFile("data/addresses.json");
- weth = IERC20(stdJson.readAddress(addressJson, ".weth"));
- address dlsm = stdJson.readAddress(addressJson, ".dlsm");
- // deployer allocate weth, eigen to disperser
- uint256 recipientPrivKey = cheats.parseUint(cheats.readLine("data/recipient"));
- emit log_uint(recipientPrivKey);
- address recipientAddr = cheats.addr(recipientPrivKey);
- weth.transfer(recipientAddr, wethAmount);
- payable(recipientAddr).transfer(1 ether);
- vm.stopBroadcast();
- //approve dlsm
- vm.broadcast(recipientPrivKey);
- weth.approve(dlsm, type(uint256).max);
- }
-}
diff --git a/script/BecomeOperator.s.sol b/script/BecomeOperator.s.sol
deleted file mode 100644
index ce6bfd70f5..0000000000
--- a/script/BecomeOperator.s.sol
+++ /dev/null
@@ -1,13 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./EigenLayerParser.sol";
-
-contract BecomeOperator is Script, DSTest, EigenLayerParser {
- //performs basic deployment before each test
- function run() external {
- parseEigenLayerParams();
- vm.broadcast(msg.sender);
- delegation.registerAsOperator(IDelegationTerms(msg.sender));
- }
-}
diff --git a/script/DepositAndDelegate.s.sol b/script/DepositAndDelegate.s.sol
deleted file mode 100644
index b989558a36..0000000000
--- a/script/DepositAndDelegate.s.sol
+++ /dev/null
@@ -1,35 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./EigenLayerParser.sol";
-
-contract DepositAndDelegate is Script, DSTest, EigenLayerParser {
-
- //performs basic deployment before each test
- function run() external {
- parseEigenLayerParams();
-
- uint256 wethAmount = eigenTotalSupply / (numStaker + numDis + 50); // save 100 portions
-
- address dlnAddr;
-
- //get the corresponding dln
- //is there an easier way to do this?
- for (uint256 i = 0; i < numStaker; i++) {
- address stakerAddr =
- stdJson.readAddress(configJson, string.concat(".staker[", string.concat(vm.toString(i), "].address")));
- if (stakerAddr == msg.sender) {
- dlnAddr =
- stdJson.readAddress(configJson, string.concat(".dln[", string.concat(vm.toString(i), "].address")));
- }
- }
-
- vm.startBroadcast(msg.sender);
- eigen.approve(address(strategyManager), wethAmount);
- strategyManager.depositIntoStrategy(eigenStrat, eigen, wethAmount);
- weth.approve(address(strategyManager), wethAmount);
- strategyManager.depositIntoStrategy(wethStrat, weth, wethAmount);
- delegation.delegateTo(dlnAddr);
- vm.stopBroadcast();
- }
-}
diff --git a/script/EigenLayerParser.sol b/script/EigenLayerParser.sol
deleted file mode 100644
index 833cf33df4..0000000000
--- a/script/EigenLayerParser.sol
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../src/contracts/interfaces/IDelegationManager.sol";
-import "../src/contracts/core/DelegationManager.sol";
-
-import "../src/contracts/core/StrategyManager.sol";
-import "../src/contracts/strategies/StrategyBase.sol";
-import "../src/contracts/core/Slasher.sol";
-
-import "forge-std/Test.sol";
-import "forge-std/Script.sol";
-import "forge-std/StdJson.sol";
-
-contract EigenLayerParser is Script, DSTest {
- Vm cheats = Vm(HEVM_ADDRESS);
-
- uint256 numDis;
- uint256 numDln;
- uint256 numStaker;
- uint256 numCha;
-
- uint256 public constant eigenTotalSupply = 1000e18;
- DelegationManager public delegation;
- StrategyManager public strategyManager;
- IERC20 public weth;
- StrategyBase public wethStrat;
- IERC20 public eigen;
- StrategyBase public eigenStrat;
-
- string internal configJson;
- string internal addressJson;
-
- function parseEigenLayerParams() internal {
- configJson = vm.readFile("data/participants.json");
- numDis = stdJson.readUint(configJson, ".numDis");
- numDln = stdJson.readUint(configJson, ".numDln");
- numStaker = stdJson.readUint(configJson, ".numStaker");
-
- addressJson = vm.readFile("data/addresses.json");
- delegation = DelegationManager(stdJson.readAddress(addressJson, ".delegation"));
- strategyManager = StrategyManager(stdJson.readAddress(addressJson, ".strategyManager"));
- weth = IERC20(stdJson.readAddress(addressJson, ".weth"));
- wethStrat = StrategyBase(stdJson.readAddress(addressJson, ".wethStrat"));
- eigen = IERC20(stdJson.readAddress(addressJson, ".eigen"));
- eigenStrat = StrategyBase(stdJson.readAddress(addressJson, ".eigenStrat"));
- }
-}
diff --git a/script/M2_Deploy.s.sol b/script/M2_Deploy.s.sol
deleted file mode 100644
index 658c3550f7..0000000000
--- a/script/M2_Deploy.s.sol
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./utils/ExistingDeploymentParser.sol";
-
-import "../src/contracts/pods/BeaconChainOracle.sol";
-
-// # To load the variables in the .env file
-// source .env
-
-// # To deploy and verify our contract
-// forge script script/M2_Deploy.s.sol:Deployer_M2 --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
-contract Deployer_M2 is ExistingDeploymentParser {
- Vm cheats = Vm(HEVM_ADDRESS);
-
- // string public existingDeploymentInfoPath = string(bytes("script/output/M1_deployment_goerli_2023_3_23.json"));
- string public existingDeploymentInfoPath = string(bytes("script/output/M1_MOCK_deployment_data.json"));
- string public deployConfigPath = string(bytes("script/M2_deploy.config.json"));
-
- BeaconChainOracle public beaconChainOracle;
-
- function run() external {
- // get info on all the already-deployed contracts
- _parseDeployedContracts(existingDeploymentInfoPath);
-
- // read and log the chainID
- uint256 currentChainId = block.chainid;
- emit log_named_uint("You are deploying on ChainID", currentChainId);
-
- // READ JSON CONFIG DATA
- string memory config_data = vm.readFile(deployConfigPath);
-
- // check that the chainID matches the one in the config
- uint256 configChainId = stdJson.readUint(config_data, ".chainInfo.chainId");
- require(configChainId == currentChainId, "You are on the wrong chain for this config");
-
- address oracleInitialOwner = executorMultisig;
- uint256 initialThreshold = stdJson.readUint(config_data, ".oracleInitialization.threshold");
- bytes memory oracleSignerListRaw = stdJson.parseRaw(config_data, ".oracleInitialization.signers");
- address[] memory initialOracleSigners = abi.decode(oracleSignerListRaw, (address[]));
-
- require(initialThreshold <= initialOracleSigners.length, "invalid initialThreshold");
-
- // START RECORDING TRANSACTIONS FOR DEPLOYMENT
- vm.startBroadcast();
-
- beaconChainOracle = new BeaconChainOracle(oracleInitialOwner, initialThreshold, initialOracleSigners);
-
- // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT
- vm.stopBroadcast();
-
- // additional check for correctness of deployment
- require(beaconChainOracle.owner() == executorMultisig, "beaconChainOracle owner not set correctly");
-
- // WRITE JSON DATA
- string memory parent_object = "parent object";
-
- string memory deployed_addresses = "addresses";
- string memory deployed_addresses_output = vm.serializeAddress(deployed_addresses, "beaconChainOracle", address(beaconChainOracle));
-
- string memory chain_info = "chainInfo";
- vm.serializeUint(chain_info, "deploymentBlock", block.number);
- string memory chain_info_output = vm.serializeUint(chain_info, "chainId", currentChainId);
-
- // serialize all the data
- vm.serializeString(parent_object, deployed_addresses, deployed_addresses_output);
- string memory finalJson = vm.serializeString(parent_object, chain_info, chain_info_output);
- vm.writeJson(finalJson, "script/output/M2_deployment_data.json");
- }
-}
-
-
-
-
-
diff --git a/script/M2_deploy.config.json b/script/M2_deploy.config.json
deleted file mode 100644
index dd9cf3a00d..0000000000
--- a/script/M2_deploy.config.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "chainInfo": {
- "chainId": 31337
- },
- "oracleInitialization": {
- "signers": [
- "0x37bAFb55BC02056c5fD891DFa503ee84a97d89bF",
- "0x040353E9d057689b77DF275c07FFe1A46b98a4a6"
- ],
- "threshold": "2"
- }
-}
\ No newline at end of file
diff --git a/script/configs/M1_deploy_goerli.config.json b/script/configs/M1_deploy_goerli.config.json
deleted file mode 100644
index 0745f8b084..0000000000
--- a/script/configs/M1_deploy_goerli.config.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "multisig_addresses": {
- "pauserMultisig": "0x040353E9d057689b77DF275c07FFe1A46b98a4a6",
- "communityMultisig": "0x37bAFb55BC02056c5fD891DFa503ee84a97d89bF",
- "operationsMultisig": "0x040353E9d057689b77DF275c07FFe1A46b98a4a6",
- "executorMultisig": "0x3d9C2c2B40d890ad53E27947402e977155CD2808",
- "timelock": "0xA7e72a0564ebf25Fa082Fc27020225edeAF1796E"
- },
- "strategies": [
- {
- "token_address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
- "token_name": "Wrapped Ether",
- "token_symbol": "WETH"
- },
- {
- "token_address": "0x6320cd32aa674d2898a68ec82e869385fc5f7e2f",
- "token_name": "Wrapped liquid staked Ether 2.0",
- "token_symbol": "wstETH"
- },
- {
- "token_address": "0x178e141a0e3b34152f73ff610437a7bf9b83267a",
- "token_name": "Rocket Pool ETH",
- "token_symbol": "rETH"
- },
- {
- "token_address": "0x",
- "token_name": "Test Staked Ether",
- "token_symbol": "tsETH"
- }
- ],
- "strategyManager":
- {
- "init_paused_status": 0,
- "init_withdrawal_delay_blocks": 10
- },
- "eigenPod":
- {
- "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
- "REQUIRED_BALANCE_WEI": "31000000000000000000"
- },
- "eigenPodManager":
- {
- "init_paused_status": 30
- },
- "delayedWithdrawalRouter":
- {
- "init_paused_status": 0,
- "init_withdrawal_delay_blocks": 10
- },
- "slasher":
- {
- "init_paused_status": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
- },
- "delegation":
- {
- "init_paused_status": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
- },
- "ethPOSDepositAddress": "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b"
- }
\ No newline at end of file
diff --git a/script/configs/M1_deploy_devnet.config.json b/script/configs/devnet/M1_deploy_devnet.config.json
similarity index 96%
rename from script/configs/M1_deploy_devnet.config.json
rename to script/configs/devnet/M1_deploy_devnet.config.json
index 980dae7827..6da5379e15 100644
--- a/script/configs/M1_deploy_devnet.config.json
+++ b/script/configs/devnet/M1_deploy_devnet.config.json
@@ -26,7 +26,7 @@
"eigenPod":
{
"PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
- "REQUIRED_BALANCE_WEI": "31000000000000000000"
+ "REQUIRED_BALANCE_WEI": "32000000000000000000"
},
"eigenPodManager":
{
diff --git a/script/configs/devnet/M2_deploy_from_scratch.anvil.config.json b/script/configs/devnet/M2_deploy_from_scratch.anvil.config.json
new file mode 100644
index 0000000000..0c7961427e
--- /dev/null
+++ b/script/configs/devnet/M2_deploy_from_scratch.anvil.config.json
@@ -0,0 +1,32 @@
+{
+ "maintainer": "samlaf@eigenlabs.org",
+ "multisig_addresses": {
+ "operationsMultisig": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+ "pauserMultisig": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+ "executorMultisig": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
+ },
+ "strategies": [],
+ "strategyManager": {
+ "init_paused_status": 0,
+ "init_withdrawal_delay_blocks": 1
+ },
+ "eigenPod": {
+ "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 1,
+ "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000"
+ },
+ "eigenPodManager": {
+ "init_paused_status": 30
+ },
+ "delayedWithdrawalRouter": {
+ "init_paused_status": 0,
+ "init_withdrawal_delay_blocks": 1
+ },
+ "slasher": {
+ "init_paused_status": 0
+ },
+ "delegation": {
+ "init_paused_status": 0,
+ "init_withdrawal_delay_blocks": 1
+ },
+ "ethPOSDepositAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa"
+}
\ No newline at end of file
diff --git a/script/configs/goerli/M1_deploy_goerli.config.json b/script/configs/goerli/M1_deploy_goerli.config.json
new file mode 100644
index 0000000000..c454fa4dbf
--- /dev/null
+++ b/script/configs/goerli/M1_deploy_goerli.config.json
@@ -0,0 +1,54 @@
+{
+ "multisig_addresses": {
+ "pauserMultisig": "0x040353E9d057689b77DF275c07FFe1A46b98a4a6",
+ "communityMultisig": "0x37bAFb55BC02056c5fD891DFa503ee84a97d89bF",
+ "operationsMultisig": "0x040353E9d057689b77DF275c07FFe1A46b98a4a6",
+ "executorMultisig": "0x3d9C2c2B40d890ad53E27947402e977155CD2808",
+ "timelock": "0xA7e72a0564ebf25Fa082Fc27020225edeAF1796E"
+ },
+ "strategies": [
+ {
+ "token_address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
+ "token_name": "Wrapped Ether",
+ "token_symbol": "WETH"
+ },
+ {
+ "token_address": "0x6320cd32aa674d2898a68ec82e869385fc5f7e2f",
+ "token_name": "Wrapped liquid staked Ether 2.0",
+ "token_symbol": "wstETH"
+ },
+ {
+ "token_address": "0x178e141a0e3b34152f73ff610437a7bf9b83267a",
+ "token_name": "Rocket Pool ETH",
+ "token_symbol": "rETH"
+ },
+ {
+ "token_address": "0x",
+ "token_name": "Test Staked Ether",
+ "token_symbol": "tsETH"
+ }
+ ],
+ "strategyManager": {
+ "init_paused_status": 0,
+ "init_withdrawal_delay_blocks": 10
+ },
+ "eigenPod": {
+ "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
+ "REQUIRED_BALANCE_WEI": "31000000000000000000"
+ },
+ "eigenPodManager": {
+ "init_paused_status": 30
+ },
+ "delayedWithdrawalRouter": {
+ "init_paused_status": 0,
+ "init_withdrawal_delay_blocks": 10
+ },
+ "slasher": {
+ "init_paused_status": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
+ },
+ "delegation": {
+ "init_paused_status": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
+ },
+ "ethPOSDepositAddress": "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b"
+}
+
diff --git a/script/configs/holesky/Holesky_current_deployment.config.json b/script/configs/holesky/Holesky_current_deployment.config.json
new file mode 100644
index 0000000000..a173ccb316
--- /dev/null
+++ b/script/configs/holesky/Holesky_current_deployment.config.json
@@ -0,0 +1,55 @@
+{
+ "addresses": {
+ "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf",
+ "avsDirectoryImplementation": "0xEF5BA995Bc7722fd1e163edF8Dc09375de3d3e3a",
+ "baseStrategyImplementation": "0xFb83e1D133D0157775eC4F19Ff81478Df1103305",
+ "beaconOracle": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25",
+ "delayedWithdrawalRouter": "0x642c646053eaf2254f088e9019ACD73d9AE0FA32",
+ "delayedWithdrawalRouterImplementation": "0xcE8b8D99773a718423F8040a6e52c06a4ce63407",
+ "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7",
+ "delegationManagerImplementation": "0x83f8F8f0BB125F7870F6bfCf76853f874C330D76",
+ "eigenLayerPauserReg": "0x85Ef7299F8311B25642679edBF02B62FA2212F06",
+ "eigenLayerProxyAdmin": "0xDB023566064246399b4AE851197a97729C93A6cf",
+ "eigenPodBeacon": "0x7261C2bd75a7ACE1762f6d7FAe8F63215581832D",
+ "eigenPodImplementation": "0xe98f9298344527608A1BCC23907B8145F9Cb641c",
+ "eigenPodManager": "0x30770d7E3e71112d7A6b7259542D1f680a70e315",
+ "eigenPodManagerImplementation": "0x5265C162f7d5F3fE3175a78828ab16bf5E324a7B",
+ "emptyContract": "0x9690d52B1Ce155DB2ec5eCbF5a262ccCc7B3A6D2",
+ "slasher": "0xcAe751b75833ef09627549868A04E32679386e7C",
+ "slasherImplementation": "0x99715D255E34a39bE9943b82F281CA734bcF345A",
+ "strategies": {
+ "WETH": "0x80528D6e9A2BAbFc766965E0E26d5aB08D9CFaF9",
+ "rETH": "0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0",
+ "stETH": "0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3",
+ "lsETH": "0x05037A81BD7B4C9E0F7B430f1F2A22c31a2FD943",
+ "frxETH": "0x15F70a41Afe34020B3B16079010D3e88c4A85daf",
+ "ETHx": "0x31B6F59e1627cEfC9fA174aD03859fC337666af7",
+ "osETH": "0x46281E3B7fDcACdBa44CADf069a94a588Fd4C6Ef",
+ "cbETH": "0x70EB4D3c164a6B4A5f908D4FBb5a9cAfFb66bAB6"
+ },
+ "strategyAddresses": [
+ "0x3A8fBdf9e77DFc25d09741f51d3E181b25d0c4E0",
+ "0x80528D6e9A2BAbFc766965E0E26d5aB08D9CFaF9",
+ "0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3",
+ "0x05037A81BD7B4C9E0F7B430f1F2A22c31a2FD943",
+ "0x15F70a41Afe34020B3B16079010D3e88c4A85daf",
+ "0x31B6F59e1627cEfC9fA174aD03859fC337666af7",
+ "0x46281E3B7fDcACdBa44CADf069a94a588Fd4C6Ef",
+ "0x70EB4D3c164a6B4A5f908D4FBb5a9cAfFb66bAB6"
+ ],
+ "strategyManager": "0xdfB5f6CE42aAA7830E94ECFCcAd411beF4d4D5b6",
+ "strategyManagerImplementation": "0x59f766A603C53f3AC8Be43bBe158c1519b193a18"
+ },
+ "numStrategies": 8,
+ "chainInfo": {
+ "chainId": 17000,
+ "deploymentBlock": 1167041
+ },
+ "parameters": {
+ "communityMultisig": "0xCb8d2f9e55Bc7B1FA9d089f9aC80C583D2BDD5F7",
+ "executorMultisig": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348",
+ "operationsMultisig": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d",
+ "pauserMultisig": "0x53410249ec7d3a3F9F1ba3912D50D6A3Df6d10A7",
+ "timelock": "0xcF19CE0561052a7A7Ff21156730285997B350A7D"
+ }
+}
diff --git a/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json b/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json
new file mode 100644
index 0000000000..6cd5bc2c39
--- /dev/null
+++ b/script/configs/holesky/M2_deploy_from_scratch.holesky.config.json
@@ -0,0 +1,46 @@
+{
+ "chainInfo": {
+ "chainId": 17000
+ },
+ "multisig_addresses": {
+ "pauserMultisig": "0x53410249ec7d3a3F9F1ba3912D50D6A3Df6d10A7",
+ "communityMultisig": "0xCb8d2f9e55Bc7B1FA9d089f9aC80C583D2BDD5F7",
+ "operationsMultisig": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d",
+ "executorMultisig": "0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348",
+ "timelock": "0xcF19CE0561052a7A7Ff21156730285997B350A7D"
+ },
+ "strategies": {
+ "numStrategies": 0,
+ "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
+ "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
+ "strategiesToDeploy": []
+ },
+ "strategyManager": {
+ "init_strategy_whitelister": "0xfaEF7338b7490b9E272d80A1a39f4657cAf2b97d",
+ "init_paused_status": 0
+ },
+ "delegationManager": {
+ "init_paused_status": 0,
+ "init_minWithdrawalDelayBlocks": 50400
+ },
+ "avsDirectory": {
+ "init_paused_status": 0
+ },
+ "slasher": {
+ "init_paused_status": 0
+ },
+ "eigenPod": {
+ "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000,
+ "GENESIS_TIME": 1695902400
+ },
+ "eigenPodManager": {
+ "init_paused_status": 0,
+ "deneb_fork_timestamp": "1707305664"
+ },
+ "delayedWithdrawalRouter": {
+ "init_paused_status": 0,
+ "init_withdrawalDelayBlocks": 50400
+ },
+ "ethPOSDepositAddress": "0x4242424242424242424242424242424242424242",
+ "beaconOracleAddress": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25"
+}
\ No newline at end of file
diff --git a/script/configs/holesky/M2_deploy_preprod.holesky.config.json b/script/configs/holesky/M2_deploy_preprod.holesky.config.json
new file mode 100644
index 0000000000..469ef613ec
--- /dev/null
+++ b/script/configs/holesky/M2_deploy_preprod.holesky.config.json
@@ -0,0 +1,58 @@
+{
+ "chainInfo": {
+ "chainId": 17000
+ },
+ "multisig_addresses": {
+ "pauserMultisig": "0x0000000000000000000000000000000000000000",
+ "communityMultisig": "0x0000000000000000000000000000000000000000",
+ "operationsMultisig": "0x0000000000000000000000000000000000000000",
+ "executorMultisig": "0x0000000000000000000000000000000000000000",
+ "timelock": "0x0000000000000000000000000000000000000000"
+ },
+ "numStrategies": 2,
+ "strategies": {
+ "numStrategies": 2,
+ "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
+ "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
+ "strategiesToDeploy": [
+ {
+ "token_address": "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034",
+ "token_name": "Liquid staked Ether 2.0",
+ "token_symbol": "stETH"
+ },
+ {
+ "token_address": "0x7322c24752f79c05FFD1E2a6FCB97020C1C264F1",
+ "token_name": "Rocket Pool ETH",
+ "token_symbol": "rETH"
+ }
+ ]
+ },
+ "strategyManager": {
+ "init_strategy_whitelister": "0x0000000000000000000000000000000000000000",
+ "init_paused_status": 0
+ },
+ "delegationManager": {
+ "init_paused_status": 0,
+ "init_minWithdrawalDelayBlocks": 50400
+ },
+ "avsDirectory": {
+ "init_paused_status": 0
+ },
+ "slasher": {
+ "init_paused_status": 0
+ },
+ "eigenPod": {
+ "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000000000000,
+ "GENESIS_TIME": 1695902400
+ },
+ "eigenPodManager": {
+ "init_paused_status": 0,
+ "deneb_fork_timestamp": "1707305664"
+ },
+ "delayedWithdrawalRouter": {
+ "init_paused_status": 0,
+ "init_withdrawalDelayBlocks": 50400
+ },
+ "ethPOSDepositAddress": "0x4242424242424242424242424242424242424242",
+ "beaconOracleAddress": "0x4C116BB629bff7A8373c2378bBd919f8349B8f25"
+}
\ No newline at end of file
diff --git a/script/configs/M1_deploy_mainnet.config.json b/script/configs/mainnet/M1_deploy_mainnet.config.json
similarity index 88%
rename from script/configs/M1_deploy_mainnet.config.json
rename to script/configs/mainnet/M1_deploy_mainnet.config.json
index 7f41082b4c..e5b43e804b 100644
--- a/script/configs/M1_deploy_mainnet.config.json
+++ b/script/configs/mainnet/M1_deploy_mainnet.config.json
@@ -1,10 +1,10 @@
{
"multisig_addresses": {
- "communityMultisig": "",
+ "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598",
"operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
"pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390",
- "executorMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
- "timelock": ""
+ "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111",
+ "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF"
},
"strategies": [
{
@@ -37,7 +37,7 @@
"eigenPod":
{
"PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
- "REQUIRED_BALANCE_WEI": "31000000000000000000"
+ "REQUIRED_BALANCE_WEI": "32000000000000000000"
},
"eigenPodManager":
{
diff --git a/script/M1_deploy.config.json b/script/configs/mainnet/M2_deploy_from_scratch.mainnet.config.json
similarity index 84%
rename from script/M1_deploy.config.json
rename to script/configs/mainnet/M2_deploy_from_scratch.mainnet.config.json
index 7f41082b4c..9724ce6cd4 100644
--- a/script/M1_deploy.config.json
+++ b/script/configs/mainnet/M2_deploy_from_scratch.mainnet.config.json
@@ -1,10 +1,8 @@
{
"multisig_addresses": {
- "communityMultisig": "",
"operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
"pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390",
- "executorMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
- "timelock": ""
+ "executorMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90"
},
"strategies": [
{
@@ -37,7 +35,7 @@
"eigenPod":
{
"PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400,
- "REQUIRED_BALANCE_WEI": "31000000000000000000"
+ "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000"
},
"eigenPodManager":
{
@@ -51,11 +49,12 @@
},
"slasher":
{
- "init_paused_status": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
+ "init_paused_status": 0
},
"delegation":
{
- "init_paused_status": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
+ "init_paused_status": 0,
+ "init_withdrawal_delay_blocks": 50400
},
"ethPOSDepositAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa"
}
\ No newline at end of file
diff --git a/script/configs/mainnet/M2_mainnet_upgrade.config.json b/script/configs/mainnet/M2_mainnet_upgrade.config.json
new file mode 100644
index 0000000000..744672facd
--- /dev/null
+++ b/script/configs/mainnet/M2_mainnet_upgrade.config.json
@@ -0,0 +1,46 @@
+{
+ "chainInfo": {
+ "chainId": 1
+ },
+ "multisig_addresses": {
+ "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598",
+ "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111",
+ "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
+ "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390",
+ "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF"
+ },
+ "strategies": {
+ "numStrategies": 0,
+ "MAX_PER_DEPOSIT": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
+ "MAX_TOTAL_DEPOSITS": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
+ "strategiesToDeploy": []
+ },
+ "strategyManager": {
+ "init_strategy_whitelister": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
+ "init_paused_status": 1
+ },
+ "delegationManager": {
+ "init_paused_status": 0,
+ "init_minWithdrawalDelayBlocks": 50400
+ },
+ "avsDirectory": {
+ "init_paused_status": 0
+ },
+ "slasher": {
+ "init_paused_status": 115792089237316195423570985008687907853269984665640564039457584007913129639935
+ },
+ "eigenPod": {
+ "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": 32000000000,
+ "GENESIS_TIME": 1606824023
+ },
+ "eigenPodManager": {
+ "init_paused_status": 0,
+ "deneb_fork_timestamp": "1710338135"
+ },
+ "delayedWithdrawalRouter": {
+ "init_paused_status": 0,
+ "init_withdrawalDelayBlocks": 50400
+ },
+ "ethPOSDepositAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa",
+ "beaconOracleAddress": "0x343907185b71aDF0eBa9567538314396aa985442"
+}
\ No newline at end of file
diff --git a/script/configs/mainnet/Mainnet_current_deployment.config.json b/script/configs/mainnet/Mainnet_current_deployment.config.json
new file mode 100644
index 0000000000..e69821b4b0
--- /dev/null
+++ b/script/configs/mainnet/Mainnet_current_deployment.config.json
@@ -0,0 +1,63 @@
+{
+ "addresses": {
+ "avsDirectory": "0x0000000000000000000000000000000000000000",
+ "avsDirectoryImplementation": "0x0000000000000000000000000000000000000000",
+ "beaconOracle": "0x343907185b71aDF0eBa9567538314396aa985442",
+ "baseStrategyImplementation": "0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3",
+ "delayedWithdrawalRouter": "0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8",
+ "delayedWithdrawalRouterImplementation": "0x44Bcb0E01CD0C5060D4Bb1A07b42580EF983E2AF",
+ "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A",
+ "delegationManagerImplementation": "0xf97E97649Da958d290e84E6D571c32F4b7F475e4",
+ "eigenLayerPauserReg": "0x0c431C66F4dE941d089625E5B423D00707977060",
+ "eigenLayerProxyAdmin": "0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444",
+ "eigenPodBeacon": "0x5a2a4F2F3C18f09179B6703e63D9eDD165909073",
+ "eigenPodImplementation": "0x5c86e9609fbBc1B754D0FD5a4963Fdf0F5b99dA7",
+ "eigenPodManager": "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338",
+ "eigenPodManagerImplementation": "0xEB86a5c40FdE917E6feC440aBbCDc80E3862e111",
+ "emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9",
+ "slasher": "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd",
+ "slasherImplementation": "0xef31c292801f24f16479DD83197F1E6AeBb8d6d8",
+ "strategyManager": "0x858646372CC42E1A627fcE94aa7A7033e7CF075A",
+ "strategyManagerImplementation": "0x5d25EEf8CfEdaA47d31fE2346726dE1c21e342Fb",
+ "strategies": {
+ "stETH": "0x93c4b944D05dfe6df7645A86cd2206016c51564D",
+ "rETH": "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2",
+ "cbETH": "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc",
+ "ETHx": "0x9d7eD45EE2E8FC5482fa2428f15C971e6369011d",
+ "ankrETH": "0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff",
+ "oETH": "0xa4C637e0F704745D182e4D38cAb7E7485321d059",
+ "osETH": "0x57ba429517c3473B6d34CA9aCd56c0e735b94c02",
+ "swETH": "0x0Fe4F44beE93503346A3Ac9EE5A26b130a5796d6",
+ "wBETH": "0x7CA911E83dabf90C90dD3De5411a10F1A6112184",
+ "sfrxETH": "0x8CA7A5d6f3acd3A7A8bC468a8CD0FB14B6BD28b6",
+ "lsETH": "0xAe60d8180437b5C34bB956822ac2710972584473",
+ "mETH": "0x298aFB19A105D59E74658C4C334Ff360BadE6dd2"
+ },
+ "strategyAddresses": [
+ "0x93c4b944D05dfe6df7645A86cd2206016c51564D",
+ "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2",
+ "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc",
+ "0x9d7eD45EE2E8FC5482fa2428f15C971e6369011d",
+ "0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff",
+ "0xa4C637e0F704745D182e4D38cAb7E7485321d059",
+ "0x57ba429517c3473B6d34CA9aCd56c0e735b94c02",
+ "0x0Fe4F44beE93503346A3Ac9EE5A26b130a5796d6",
+ "0x7CA911E83dabf90C90dD3De5411a10F1A6112184",
+ "0x8CA7A5d6f3acd3A7A8bC468a8CD0FB14B6BD28b6",
+ "0xAe60d8180437b5C34bB956822ac2710972584473",
+ "0x298aFB19A105D59E74658C4C334Ff360BadE6dd2"
+ ]
+ },
+ "numStrategies": 12,
+ "chainInfo": {
+ "chainId": 1,
+ "deploymentBlock": 17445559
+ },
+ "parameters": {
+ "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598",
+ "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111",
+ "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
+ "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390",
+ "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF"
+ }
+}
diff --git a/script/configs/mainnet/Mainnet_current_eigenPods.config.json b/script/configs/mainnet/Mainnet_current_eigenPods.config.json
new file mode 100644
index 0000000000..73f8dfb28d
--- /dev/null
+++ b/script/configs/mainnet/Mainnet_current_eigenPods.config.json
@@ -0,0 +1,46 @@
+{
+ "chainInfo": {
+ "chainId": 1,
+ "deploymentBlock": 19285000
+ },
+ "eigenPods": {
+ "multiValidators": [
+ "0x2641c2ded63a0c640629f5edf1189e0f53c06561",
+ "0xa345dcb63f984ed1c6d1a8901e0cdbd13b2b4d19",
+ "0x7fb3d3801883341a02ddd1beb2e624a278e87930",
+ "0x2f5cb052d397d0b628299a9d4cfc49a5019c4170",
+ "0xdb678e1056acd7db74507b921a6295c3d586ece9"
+ ],
+ "singleValidators": [
+ "0xc149531419db43b8cc0d4f2f2ce1700ae46f4c8a",
+ "0x61340dcc5aef625ded27f21e5068916ad334dad0",
+ "0x722e2350a55e6b617e66983b4b91d47fe9e9403e",
+ "0x49213606dc1953eae3b733187fed9e7307edd55b",
+ "0xa8fc63e72288b79009f7b5952760114513efc700"
+ ],
+ "inActive": [
+ "0xcbb42f0d320056453c497867119814c59615daeb",
+ "0xd6e50e7f6250ca26d5d5033138ad09cfe2aaeacb",
+ "0xe18c1447804563af9647cf8c879f35ced8172c1f",
+ "0x38de067514c77fed61630bb77ecc24f2adb73ff4",
+ "0xc3f9bbd74ded6b2bc2ff8b5c693fcbabf2e24efd"
+ ],
+ "allEigenPods": [
+ "0x2641c2ded63a0c640629f5edf1189e0f53c06561",
+ "0xa345dcb63f984ed1c6d1a8901e0cdbd13b2b4d19",
+ "0x7fb3d3801883341a02ddd1beb2e624a278e87930",
+ "0x2f5cb052d397d0b628299a9d4cfc49a5019c4170",
+ "0xdb678e1056acd7db74507b921a6295c3d586ece9",
+ "0xc149531419db43b8cc0d4f2f2ce1700ae46f4c8a",
+ "0x61340dcc5aef625ded27f21e5068916ad334dad0",
+ "0x722e2350a55e6b617e66983b4b91d47fe9e9403e",
+ "0x49213606dc1953eae3b733187fed9e7307edd55b",
+ "0xa8fc63e72288b79009f7b5952760114513efc700",
+ "0xcbb42f0d320056453c497867119814c59615daeb",
+ "0xd6e50e7f6250ca26d5d5033138ad09cfe2aaeacb",
+ "0xe18c1447804563af9647cf8c879f35ced8172c1f",
+ "0x38de067514c77fed61630bb77ecc24f2adb73ff4",
+ "0xc3f9bbd74ded6b2bc2ff8b5c693fcbabf2e24efd"
+ ]
+ }
+}
diff --git a/script/deploy/devnet/M2_Deploy_From_Scratch.s.sol b/script/deploy/devnet/M2_Deploy_From_Scratch.s.sol
new file mode 100644
index 0000000000..7d20193e07
--- /dev/null
+++ b/script/deploy/devnet/M2_Deploy_From_Scratch.s.sol
@@ -0,0 +1,617 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
+import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+
+import "../../../src/contracts/interfaces/IETHPOSDeposit.sol";
+import "../../../src/contracts/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";
+
+import "../../../src/contracts/pods/EigenPod.sol";
+import "../../../src/contracts/pods/EigenPodManager.sol";
+import "../../../src/contracts/pods/DelayedWithdrawalRouter.sol";
+
+import "../../../src/contracts/permissions/PauserRegistry.sol";
+
+import "../../../src/test/mocks/EmptyContract.sol";
+import "../../../src/test/mocks/ETHDepositMock.sol";
+
+import "forge-std/Script.sol";
+import "forge-std/Test.sol";
+
+// # To load the variables in the .env file
+// source .env
+
+// # To deploy and verify our contract
+// forge script script/testing/M2_Deploy_From_Scratch.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --sig "run(string memory configFile)" -- M2_deploy_from_scratch.anvil.config.json
+contract Deployer_M2 is Script, Test {
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ // struct used to encode token info in config file
+ struct StrategyConfig {
+ uint256 maxDeposits;
+ uint256 maxPerDeposit;
+ address tokenAddress;
+ string tokenSymbol;
+ }
+
+ string public deployConfigPath;
+
+ // EigenLayer Contracts
+ ProxyAdmin public eigenLayerProxyAdmin;
+ PauserRegistry public eigenLayerPauserReg;
+ Slasher public slasher;
+ Slasher public slasherImplementation;
+ DelegationManager public delegation;
+ DelegationManager public delegationImplementation;
+ StrategyManager public strategyManager;
+ StrategyManager public strategyManagerImplementation;
+ AVSDirectory public avsDirectory;
+ AVSDirectory public avsDirectoryImplementation;
+ EigenPodManager public eigenPodManager;
+ EigenPodManager public eigenPodManagerImplementation;
+ DelayedWithdrawalRouter public delayedWithdrawalRouter;
+ DelayedWithdrawalRouter public delayedWithdrawalRouterImplementation;
+ UpgradeableBeacon public eigenPodBeacon;
+ EigenPod public eigenPodImplementation;
+ StrategyBase public baseStrategyImplementation;
+
+ EmptyContract public emptyContract;
+
+ address executorMultisig;
+ address operationsMultisig;
+ address pauserMultisig;
+
+ // the ETH2 deposit contract -- if not on mainnet, we deploy a mock as stand-in
+ IETHPOSDeposit public ethPOSDeposit;
+
+ // strategies deployed
+ StrategyBaseTVLLimits[] public deployedStrategyArray;
+
+ // IMMUTABLES TO SET
+ uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ uint64 GOERLI_GENESIS_TIME = 1616508000;
+
+ // OTHER DEPLOYMENT PARAMETERS
+ uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS;
+ uint256 SLASHER_INIT_PAUSED_STATUS;
+ uint256 DELEGATION_INIT_PAUSED_STATUS;
+ uint256 DELEGATION_WITHDRAWAL_DELAY_BLOCKS;
+ uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS;
+ uint256 DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS;
+
+ // one week in blocks -- 50400
+ uint32 STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS;
+ uint32 DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS;
+
+ function run(string memory configFileName) external {
+ // read and log the chainID
+ uint256 chainId = block.chainid;
+ emit log_named_uint("You are deploying on ChainID", chainId);
+
+ // READ JSON CONFIG DATA
+ deployConfigPath = string(bytes(string.concat("script/configs/devnet/", configFileName)));
+ string memory config_data = vm.readFile(deployConfigPath);
+ // bytes memory parsedData = vm.parseJson(config_data);
+
+ STRATEGY_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".strategyManager.init_paused_status");
+ SLASHER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".slasher.init_paused_status");
+ DELEGATION_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".delegation.init_paused_status");
+ DELEGATION_WITHDRAWAL_DELAY_BLOCKS = stdJson.readUint(config_data, ".delegation.init_withdrawal_delay_blocks");
+ EIGENPOD_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".eigenPodManager.init_paused_status");
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS = stdJson.readUint(
+ config_data,
+ ".delayedWithdrawalRouter.init_paused_status"
+ );
+
+ STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(
+ stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks")
+ );
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(
+ stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks")
+ );
+
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = uint64(
+ stdJson.readUint(config_data, ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR")
+ );
+
+ // tokens to deploy strategies for
+ StrategyConfig[] memory strategyConfigs;
+
+ executorMultisig = stdJson.readAddress(config_data, ".multisig_addresses.executorMultisig");
+ operationsMultisig = stdJson.readAddress(config_data, ".multisig_addresses.operationsMultisig");
+ pauserMultisig = stdJson.readAddress(config_data, ".multisig_addresses.pauserMultisig");
+ // load token list
+ bytes memory strategyConfigsRaw = stdJson.parseRaw(config_data, ".strategies");
+ strategyConfigs = abi.decode(strategyConfigsRaw, (StrategyConfig[]));
+
+ require(executorMultisig != address(0), "executorMultisig address not configured correctly!");
+ require(operationsMultisig != address(0), "operationsMultisig address not configured correctly!");
+
+ // START RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.startBroadcast();
+
+ // deploy proxy admin for ability to upgrade proxy contracts
+ eigenLayerProxyAdmin = new ProxyAdmin();
+
+ //deploy pauser registry
+ {
+ address[] memory pausers = new address[](3);
+ pausers[0] = executorMultisig;
+ pausers[1] = operationsMultisig;
+ pausers[2] = pauserMultisig;
+ eigenLayerPauserReg = new PauserRegistry(pausers, executorMultisig);
+ }
+
+ /**
+ * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are
+ * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code.
+ */
+ emptyContract = new EmptyContract();
+ delegation = DelegationManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ strategyManager = StrategyManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ avsDirectory = AVSDirectory(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ slasher = Slasher(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ eigenPodManager = EigenPodManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ delayedWithdrawalRouter = DelayedWithdrawalRouter(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+
+ // if on mainnet, use the ETH2 deposit contract address
+ if (chainId == 1) {
+ ethPOSDeposit = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa);
+ // if not on mainnet, deploy a mock
+ } else {
+ ethPOSDeposit = IETHPOSDeposit(stdJson.readAddress(config_data, ".ethPOSDepositAddress"));
+ }
+ eigenPodImplementation = new EigenPod(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
+
+ eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
+
+ // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
+ delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
+ avsDirectoryImplementation = new AVSDirectory(delegation);
+ slasherImplementation = new Slasher(strategyManager, delegation);
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegation
+ );
+ 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,
+ DELEGATION_INIT_PAUSED_STATUS,
+ DELEGATION_WITHDRAWAL_DELAY_BLOCKS,
+ _strategies,
+ _withdrawalDelayBlocks
+ )
+ );
+ }
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation),
+ abi.encodeWithSelector(
+ StrategyManager.initialize.selector,
+ executorMultisig,
+ operationsMultisig,
+ eigenLayerPauserReg,
+ STRATEGY_MANAGER_INIT_PAUSED_STATUS
+ )
+ );
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ address(slasherImplementation),
+ abi.encodeWithSelector(
+ Slasher.initialize.selector,
+ executorMultisig,
+ eigenLayerPauserReg,
+ SLASHER_INIT_PAUSED_STATUS
+ )
+ );
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ address(avsDirectoryImplementation),
+ abi.encodeWithSelector(AVSDirectory.initialize.selector, executorMultisig, eigenLayerPauserReg, 0)
+ );
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation),
+ abi.encodeWithSelector(
+ EigenPodManager.initialize.selector,
+ IBeaconChainOracle(address(0)),
+ executorMultisig,
+ eigenLayerPauserReg,
+ EIGENPOD_MANAGER_INIT_PAUSED_STATUS
+ )
+ );
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ address(delayedWithdrawalRouterImplementation),
+ abi.encodeWithSelector(
+ DelayedWithdrawalRouter.initialize.selector,
+ executorMultisig,
+ eigenLayerPauserReg,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS
+ )
+ );
+
+ // deploy StrategyBaseTVLLimits contract implementation
+ baseStrategyImplementation = new StrategyBaseTVLLimits(strategyManager);
+ // create upgradeable proxies that each point to the implementation and initialize them
+ for (uint256 i = 0; i < strategyConfigs.length; ++i) {
+ deployedStrategyArray.push(
+ StrategyBaseTVLLimits(
+ address(
+ new TransparentUpgradeableProxy(
+ address(baseStrategyImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ StrategyBaseTVLLimits.initialize.selector,
+ strategyConfigs[i].maxPerDeposit,
+ strategyConfigs[i].maxDeposits,
+ IERC20(strategyConfigs[i].tokenAddress),
+ eigenLayerPauserReg
+ )
+ )
+ )
+ )
+ );
+ }
+
+ eigenLayerProxyAdmin.transferOwnership(executorMultisig);
+ eigenPodBeacon.transferOwnership(executorMultisig);
+
+ // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.stopBroadcast();
+
+ // CHECK CORRECTNESS OF DEPLOYMENT
+ _verifyContractsPointAtOneAnother(
+ delegationImplementation,
+ strategyManagerImplementation,
+ slasherImplementation,
+ eigenPodManagerImplementation,
+ delayedWithdrawalRouterImplementation
+ );
+ _verifyContractsPointAtOneAnother(
+ delegation,
+ strategyManager,
+ slasher,
+ eigenPodManager,
+ delayedWithdrawalRouter
+ );
+ _verifyImplementationsSetCorrectly();
+ _verifyInitialOwners();
+ _checkPauserInitializations();
+ _verifyInitializationParams();
+
+ // WRITE JSON DATA
+ string memory parent_object = "parent object";
+
+ string memory deployed_strategies = "strategies";
+ for (uint256 i = 0; i < strategyConfigs.length; ++i) {
+ vm.serializeAddress(deployed_strategies, strategyConfigs[i].tokenSymbol, address(deployedStrategyArray[i]));
+ }
+ string memory deployed_strategies_output = strategyConfigs.length == 0
+ ? ""
+ : vm.serializeAddress(
+ deployed_strategies,
+ strategyConfigs[strategyConfigs.length - 1].tokenSymbol,
+ address(deployedStrategyArray[strategyConfigs.length - 1])
+ );
+
+ string memory deployed_addresses = "addresses";
+ vm.serializeAddress(deployed_addresses, "eigenLayerProxyAdmin", address(eigenLayerProxyAdmin));
+ vm.serializeAddress(deployed_addresses, "eigenLayerPauserReg", address(eigenLayerPauserReg));
+ vm.serializeAddress(deployed_addresses, "slasher", address(slasher));
+ vm.serializeAddress(deployed_addresses, "slasherImplementation", address(slasherImplementation));
+ vm.serializeAddress(deployed_addresses, "delegation", address(delegation));
+ vm.serializeAddress(deployed_addresses, "delegationImplementation", address(delegationImplementation));
+ vm.serializeAddress(deployed_addresses, "avsDirectory", address(avsDirectory));
+ vm.serializeAddress(deployed_addresses, "avsDirectoryImplementation", address(avsDirectoryImplementation));
+ vm.serializeAddress(deployed_addresses, "strategyManager", address(strategyManager));
+ vm.serializeAddress(
+ deployed_addresses,
+ "strategyManagerImplementation",
+ address(strategyManagerImplementation)
+ );
+ vm.serializeAddress(deployed_addresses, "eigenPodManager", address(eigenPodManager));
+ vm.serializeAddress(
+ deployed_addresses,
+ "eigenPodManagerImplementation",
+ address(eigenPodManagerImplementation)
+ );
+ vm.serializeAddress(deployed_addresses, "delayedWithdrawalRouter", address(delayedWithdrawalRouter));
+ vm.serializeAddress(
+ deployed_addresses,
+ "delayedWithdrawalRouterImplementation",
+ address(delayedWithdrawalRouterImplementation)
+ );
+ vm.serializeAddress(deployed_addresses, "eigenPodBeacon", address(eigenPodBeacon));
+ vm.serializeAddress(deployed_addresses, "eigenPodImplementation", address(eigenPodImplementation));
+ vm.serializeAddress(deployed_addresses, "baseStrategyImplementation", address(baseStrategyImplementation));
+ vm.serializeAddress(deployed_addresses, "emptyContract", address(emptyContract));
+ string memory deployed_addresses_output = vm.serializeString(
+ deployed_addresses,
+ "strategies",
+ deployed_strategies_output
+ );
+
+ string memory parameters = "parameters";
+ vm.serializeAddress(parameters, "executorMultisig", executorMultisig);
+ string memory parameters_output = vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig);
+
+ string memory chain_info = "chainInfo";
+ vm.serializeUint(chain_info, "deploymentBlock", block.number);
+ string memory chain_info_output = vm.serializeUint(chain_info, "chainId", chainId);
+
+ // serialize all the data
+ vm.serializeString(parent_object, deployed_addresses, deployed_addresses_output);
+ vm.serializeString(parent_object, chain_info, chain_info_output);
+ string memory finalJson = vm.serializeString(parent_object, parameters, parameters_output);
+ // TODO: should output to different file depending on configFile passed to run()
+ // so that we don't override mainnet output by deploying to goerli for eg.
+ vm.writeJson(finalJson, "script/output/devnet/M2_from_scratch_deployment_data.json");
+ }
+
+ function _verifyContractsPointAtOneAnother(
+ DelegationManager delegationContract,
+ StrategyManager strategyManagerContract,
+ Slasher /*slasherContract*/,
+ EigenPodManager eigenPodManagerContract,
+ DelayedWithdrawalRouter delayedWithdrawalRouterContract
+ ) internal view {
+ require(delegationContract.slasher() == slasher, "delegation: slasher address not set correctly");
+ require(
+ delegationContract.strategyManager() == strategyManager,
+ "delegation: strategyManager address not set correctly"
+ );
+
+ require(strategyManagerContract.slasher() == slasher, "strategyManager: slasher address not set correctly");
+ require(
+ strategyManagerContract.delegation() == delegation,
+ "strategyManager: delegation address not set correctly"
+ );
+ require(
+ strategyManagerContract.eigenPodManager() == eigenPodManager,
+ "strategyManager: eigenPodManager address not set correctly"
+ );
+
+ // removing slasher requirements because there is no slasher as part of m2-mainnet release
+ // require(slasherContract.strategyManager() == strategyManager, "slasher: strategyManager not set correctly");
+ // require(slasherContract.delegation() == delegation, "slasher: delegation not set correctly");
+
+ require(
+ eigenPodManagerContract.ethPOS() == ethPOSDeposit,
+ " eigenPodManager: ethPOSDeposit contract address not set correctly"
+ );
+ require(
+ eigenPodManagerContract.eigenPodBeacon() == eigenPodBeacon,
+ "eigenPodManager: eigenPodBeacon contract address not set correctly"
+ );
+ require(
+ eigenPodManagerContract.strategyManager() == strategyManager,
+ "eigenPodManager: strategyManager contract address not set correctly"
+ );
+ require(
+ eigenPodManagerContract.slasher() == slasher,
+ "eigenPodManager: slasher contract address not set correctly"
+ );
+
+ require(
+ delayedWithdrawalRouterContract.eigenPodManager() == eigenPodManager,
+ "delayedWithdrawalRouterContract: eigenPodManager address not set correctly"
+ );
+ }
+
+ function _verifyImplementationsSetCorrectly() internal view {
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(delegation)))) ==
+ address(delegationImplementation),
+ "delegation: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(strategyManager)))
+ ) == address(strategyManagerImplementation),
+ "strategyManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(slasher)))) ==
+ address(slasherImplementation),
+ "slasher: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager)))
+ ) == address(eigenPodManagerImplementation),
+ "eigenPodManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter)))
+ ) == address(delayedWithdrawalRouterImplementation),
+ "delayedWithdrawalRouter: implementation set incorrectly"
+ );
+
+ for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i])))
+ ) == address(baseStrategyImplementation),
+ "strategy: implementation set incorrectly"
+ );
+ }
+
+ require(
+ eigenPodBeacon.implementation() == address(eigenPodImplementation),
+ "eigenPodBeacon: implementation set incorrectly"
+ );
+ }
+
+ function _verifyInitialOwners() internal view {
+ require(strategyManager.owner() == executorMultisig, "strategyManager: owner not set correctly");
+ require(delegation.owner() == executorMultisig, "delegation: owner not set correctly");
+ // removing slasher requirements because there is no slasher as part of m2-mainnet release
+ // require(slasher.owner() == executorMultisig, "slasher: owner not set correctly");
+ require(eigenPodManager.owner() == executorMultisig, "eigenPodManager: owner not set correctly");
+
+ require(eigenLayerProxyAdmin.owner() == executorMultisig, "eigenLayerProxyAdmin: owner not set correctly");
+ require(eigenPodBeacon.owner() == executorMultisig, "eigenPodBeacon: owner not set correctly");
+ require(
+ delayedWithdrawalRouter.owner() == executorMultisig,
+ "delayedWithdrawalRouter: owner not set correctly"
+ );
+ }
+
+ function _checkPauserInitializations() internal view {
+ require(delegation.pauserRegistry() == eigenLayerPauserReg, "delegation: pauser registry not set correctly");
+ require(
+ strategyManager.pauserRegistry() == eigenLayerPauserReg,
+ "strategyManager: pauser registry not set correctly"
+ );
+ // removing slasher requirements because there is no slasher as part of m2-mainnet release
+ // require(slasher.pauserRegistry() == eigenLayerPauserReg, "slasher: pauser registry not set correctly");
+ require(
+ eigenPodManager.pauserRegistry() == eigenLayerPauserReg,
+ "eigenPodManager: pauser registry not set correctly"
+ );
+ require(
+ delayedWithdrawalRouter.pauserRegistry() == eigenLayerPauserReg,
+ "delayedWithdrawalRouter: pauser registry not set correctly"
+ );
+
+ require(eigenLayerPauserReg.isPauser(operationsMultisig), "pauserRegistry: operationsMultisig is not pauser");
+ require(eigenLayerPauserReg.isPauser(executorMultisig), "pauserRegistry: executorMultisig is not pauser");
+ require(eigenLayerPauserReg.isPauser(pauserMultisig), "pauserRegistry: pauserMultisig is not pauser");
+ require(eigenLayerPauserReg.unpauser() == executorMultisig, "pauserRegistry: unpauser not set correctly");
+
+ for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
+ require(
+ deployedStrategyArray[i].pauserRegistry() == eigenLayerPauserReg,
+ "StrategyBaseTVLLimits: pauser registry not set correctly"
+ );
+ require(
+ deployedStrategyArray[i].paused() == 0,
+ "StrategyBaseTVLLimits: init paused status set incorrectly"
+ );
+ }
+
+ // // pause *nothing*
+ // uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS = 0;
+ // // pause *everything*
+ // uint256 SLASHER_INIT_PAUSED_STATUS = type(uint256).max;
+ // // pause *everything*
+ // uint256 DELEGATION_INIT_PAUSED_STATUS = type(uint256).max;
+ // // pause *all of the proof-related functionality* (everything that can be paused other than creation of EigenPods)
+ // uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS = (2**1) + (2**2) + (2**3) + (2**4); /* = 30 */
+ // // pause *nothing*
+ // uint256 DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS = 0;
+ // require(strategyManager.paused() == 0, "strategyManager: init paused status set incorrectly");
+ // require(slasher.paused() == type(uint256).max, "slasher: init paused status set incorrectly");
+ // require(delegation.paused() == type(uint256).max, "delegation: init paused status set incorrectly");
+ // require(eigenPodManager.paused() == 30, "eigenPodManager: init paused status set incorrectly");
+ // require(delayedWithdrawalRouter.paused() == 0, "delayedWithdrawalRouter: init paused status set incorrectly");
+ }
+
+ function _verifyInitializationParams() internal {
+ // // one week in blocks -- 50400
+ // uint32 STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds;
+ // uint32 DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds;
+ // require(strategyManager.withdrawalDelayBlocks() == 7 days / 12 seconds,
+ // "strategyManager: withdrawalDelayBlocks initialized incorrectly");
+ // require(delayedWithdrawalRouter.withdrawalDelayBlocks() == 7 days / 12 seconds,
+ // "delayedWithdrawalRouter: withdrawalDelayBlocks initialized incorrectly");
+ // uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32 ether;
+ require(
+ eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == 32 gwei,
+ "eigenPod: MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR initialized incorrectly"
+ );
+
+ require(
+ strategyManager.strategyWhitelister() == operationsMultisig,
+ "strategyManager: strategyWhitelister address not set correctly"
+ );
+
+ require(
+ eigenPodManager.beaconChainOracle() == IBeaconChainOracle(address(0)),
+ "eigenPodManager: eigenPodBeacon contract address not set correctly"
+ );
+
+ require(
+ delayedWithdrawalRouter.eigenPodManager() == eigenPodManager,
+ "delayedWithdrawalRouter: eigenPodManager set incorrectly"
+ );
+
+ require(
+ baseStrategyImplementation.strategyManager() == strategyManager,
+ "baseStrategyImplementation: strategyManager set incorrectly"
+ );
+
+ require(
+ eigenPodImplementation.ethPOS() == ethPOSDeposit,
+ "eigenPodImplementation: ethPOSDeposit contract address not set correctly"
+ );
+ require(
+ eigenPodImplementation.eigenPodManager() == eigenPodManager,
+ " eigenPodImplementation: eigenPodManager contract address not set correctly"
+ );
+ require(
+ eigenPodImplementation.delayedWithdrawalRouter() == delayedWithdrawalRouter,
+ " eigenPodImplementation: delayedWithdrawalRouter contract address not set correctly"
+ );
+
+ string memory config_data = vm.readFile(deployConfigPath);
+ for (uint i = 0; i < deployedStrategyArray.length; i++) {
+ uint256 maxPerDeposit = stdJson.readUint(
+ config_data,
+ string.concat(".strategies[", vm.toString(i), "].max_per_deposit")
+ );
+ uint256 maxDeposits = stdJson.readUint(
+ config_data,
+ string.concat(".strategies[", vm.toString(i), "].max_deposits")
+ );
+ (uint256 setMaxPerDeposit, uint256 setMaxDeposits) = deployedStrategyArray[i].getTVLLimits();
+ require(setMaxPerDeposit == maxPerDeposit, "setMaxPerDeposit not set correctly");
+ require(setMaxDeposits == maxDeposits, "setMaxDeposits not set correctly");
+ }
+ }
+}
diff --git a/script/misc/GoerliUpgrade1.s.sol b/script/deploy/goerli/GoerliUpgrade1.s.sol
similarity index 82%
rename from script/misc/GoerliUpgrade1.s.sol
rename to script/deploy/goerli/GoerliUpgrade1.s.sol
index 2964005cb5..45e1258b59 100644
--- a/script/misc/GoerliUpgrade1.s.sol
+++ b/script/deploy/goerli/GoerliUpgrade1.s.sol
@@ -1,21 +1,21 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
-import "../../src/contracts/interfaces/IETHPOSDeposit.sol";
-import "../../src/contracts/interfaces/IBeaconChainOracle.sol";
+import "../../../src/contracts/interfaces/IETHPOSDeposit.sol";
+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/StrategyManager.sol";
+import "../../../src/contracts/core/Slasher.sol";
+import "../../../src/contracts/core/DelegationManager.sol";
-import "../../src/contracts/strategies/StrategyBase.sol";
+import "../../../src/contracts/strategies/StrategyBase.sol";
-import "../../src/contracts/pods/EigenPod.sol";
-import "../../src/contracts/pods/EigenPodManager.sol";
-import "../../src/contracts/pods/DelayedWithdrawalRouter.sol";
+import "../../../src/contracts/pods/EigenPod.sol";
+import "../../../src/contracts/pods/EigenPodManager.sol";
+import "../../../src/contracts/pods/DelayedWithdrawalRouter.sol";
import "forge-std/Script.sol";
@@ -72,7 +72,8 @@ contract GoerliUpgrade1 is Script, Test {
IETHPOSDeposit(0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b),
delayedWithdrawalRouter,
eigenPodManager,
- 31 ether
+ 32e9,
+ 1616508000
)
);
@@ -86,4 +87,4 @@ contract GoerliUpgrade1 is Script, Test {
// SlasherImplementation: 0x2f82092969d156da92f0b787525042735fc4774a
// EigenPodImplementation: 0x4dd49853a27e3d4a0557876fe225ffce9b6b5d7a
}
-}
\ No newline at end of file
+}
diff --git a/script/deploy/goerli/GoerliUpgrade2.s.sol b/script/deploy/goerli/GoerliUpgrade2.s.sol
new file mode 100644
index 0000000000..d643634f16
--- /dev/null
+++ b/script/deploy/goerli/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
+ */
+ }
+}
diff --git a/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol b/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol
new file mode 100644
index 0000000000..7d66ec162a
--- /dev/null
+++ b/script/deploy/holesky/M2_Deploy_From_Scratch.s.sol
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../../utils/ExistingDeploymentParser.sol";
+
+/**
+ * @notice Script used for the first deployment of EigenLayer core contracts to Holesky
+ * forge script script/deploy/holesky/M2_Deploy_From_Scratch.s.sol --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv
+ * forge script script/deploy/holesky/M2_Deploy_From_Scratch.s.sol --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast -vvvv
+ *
+ */
+contract M2_Deploy_Holesky_From_Scratch is ExistingDeploymentParser {
+ function run() external virtual {
+ _parseInitialDeploymentParams("script/configs/holesky/M2_deploy_from_scratch.holesky.config.json");
+
+ // START RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.startBroadcast();
+
+ emit log_named_address("Deployer Address", msg.sender);
+
+ _deployFromScratch();
+
+ // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.stopBroadcast();
+
+ // Sanity Checks
+ _verifyContractPointers();
+ _verifyImplementations();
+ _verifyContractsInitialized({isInitialDeployment: true});
+ _verifyInitializationParams();
+
+ logAndOutputContractAddresses("script/output/holesky/M2_deploy_from_scratch.holesky.config.json");
+ }
+
+ /**
+ * @notice Deploy EigenLayer contracts from scratch for Holesky
+ */
+ function _deployFromScratch() internal {
+ // Deploy ProxyAdmin, later set admins for all proxies to be executorMultisig
+ eigenLayerProxyAdmin = new ProxyAdmin();
+
+ // Set multisigs as pausers, executorMultisig as unpauser
+ address[] memory pausers = new address[](3);
+ pausers[0] = executorMultisig;
+ pausers[1] = operationsMultisig;
+ pausers[2] = pauserMultisig;
+ address unpauser = executorMultisig;
+ eigenLayerPauserReg = new PauserRegistry(pausers, unpauser);
+
+ /**
+ * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are
+ * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code.
+ */
+ emptyContract = new EmptyContract();
+ avsDirectory = AVSDirectory(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ delegationManager = DelegationManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ strategyManager = StrategyManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ slasher = Slasher(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ eigenPodManager = EigenPodManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ delayedWithdrawalRouter = DelayedWithdrawalRouter(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+
+ // Deploy EigenPod Contracts
+ eigenPodImplementation = new EigenPod(
+ IETHPOSDeposit(ETHPOSDepositAddress),
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ EIGENPOD_GENESIS_TIME
+ );
+
+ eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
+ avsDirectoryImplementation = new AVSDirectory(delegationManager);
+ delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher);
+ slasherImplementation = new Slasher(strategyManager, delegationManager);
+ eigenPodManagerImplementation = new EigenPodManager(
+ IETHPOSDeposit(ETHPOSDepositAddress),
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegationManager
+ );
+ delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
+
+ // Third, upgrade the proxy contracts to point to the implementations
+ IStrategy[] memory initializeStrategiesToSetDelayBlocks = new IStrategy[](0);
+ uint256[] memory initializeWithdrawalDelayBlocks = new uint256[](0);
+ // AVSDirectory
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ address(avsDirectoryImplementation),
+ abi.encodeWithSelector(
+ AVSDirectory.initialize.selector,
+ executorMultisig, // initialOwner
+ eigenLayerPauserReg,
+ AVS_DIRECTORY_INIT_PAUSED_STATUS
+ )
+ );
+ // DelegationManager
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(delegationManager))),
+ address(delegationManagerImplementation),
+ abi.encodeWithSelector(
+ DelegationManager.initialize.selector,
+ executorMultisig, // initialOwner
+ eigenLayerPauserReg,
+ DELEGATION_MANAGER_INIT_PAUSED_STATUS,
+ DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ )
+ );
+ // StrategyManager
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation),
+ abi.encodeWithSelector(
+ StrategyManager.initialize.selector,
+ executorMultisig, //initialOwner
+ STRATEGY_MANAGER_WHITELISTER, //initial whitelister
+ eigenLayerPauserReg,
+ STRATEGY_MANAGER_INIT_PAUSED_STATUS
+ )
+ );
+ // Slasher
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ address(slasherImplementation),
+ abi.encodeWithSelector(
+ Slasher.initialize.selector,
+ executorMultisig,
+ eigenLayerPauserReg,
+ SLASHER_INIT_PAUSED_STATUS
+ )
+ );
+ // EigenPodManager
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation),
+ abi.encodeWithSelector(
+ EigenPodManager.initialize.selector,
+ beaconOracle,
+ msg.sender, // initialOwner is msg.sender for now to set forktimestamp later
+ eigenLayerPauserReg,
+ EIGENPOD_MANAGER_INIT_PAUSED_STATUS
+ )
+ );
+ // Delayed Withdrawal Router
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ address(delayedWithdrawalRouterImplementation),
+ abi.encodeWithSelector(
+ DelayedWithdrawalRouter.initialize.selector,
+ executorMultisig, // initialOwner
+ eigenLayerPauserReg,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS
+ )
+ );
+
+ // Deploy Strategies
+ baseStrategyImplementation = new StrategyBaseTVLLimits(strategyManager);
+ uint256 numStrategiesToDeploy = strategiesToDeploy.length;
+ for (uint256 i = 0; i < numStrategiesToDeploy; i++) {
+ StrategyUnderlyingTokenConfig memory strategyConfig = strategiesToDeploy[i];
+
+ // Deploy and upgrade strategy
+ StrategyBaseTVLLimits strategy = StrategyBaseTVLLimits(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(strategy))),
+ address(baseStrategyImplementation),
+ abi.encodeWithSelector(
+ StrategyBaseTVLLimits.initialize.selector,
+ STRATEGY_MAX_PER_DEPOSIT,
+ STRATEGY_MAX_TOTAL_DEPOSITS,
+ IERC20(strategyConfig.tokenAddress),
+ eigenLayerPauserReg
+ )
+ );
+
+ deployedStrategyArray.push(strategy);
+ }
+
+ // Fork timestamp config
+ eigenPodManager.setDenebForkTimestamp(EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP);
+
+ // Transfer ownership
+ eigenLayerProxyAdmin.transferOwnership(executorMultisig);
+ eigenPodManager.transferOwnership(executorMultisig);
+ eigenPodBeacon.transferOwnership(executorMultisig);
+ }
+}
diff --git a/script/deploy/holesky/M2_Deploy_Preprod.s.sol b/script/deploy/holesky/M2_Deploy_Preprod.s.sol
new file mode 100644
index 0000000000..d580599556
--- /dev/null
+++ b/script/deploy/holesky/M2_Deploy_Preprod.s.sol
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "./M2_Deploy_From_Scratch.s.sol";
+
+/**
+ * @notice Script used for the first deployment of EigenLayer core contracts to Holesky
+ * forge script script/deploy/holesky/M2_Deploy_Preprod.s.sol --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv
+ * forge script script/deploy/holesky/M2_Deploy_Preprod.s.sol --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast -vvvv
+ *
+ * Script for dev environment, exact same as M2_Deploy_From_Scratch.s.sol but with an EOAowner
+ * instead of multisig addresses for permissions.
+ * Unused config fields:
+ * - init_strategy_whitelister
+ * - multisig_addresses(operations, pauser, executor, community)
+ */
+contract M2_Deploy_Holesky_Preprod is M2_Deploy_Holesky_From_Scratch {
+ /// @dev EOAowner is the deployer and owner of the contracts
+ address EOAowner;
+
+ function run() external virtual override {
+ _parseInitialDeploymentParams("script/configs/holesky/M2_deploy_preprod.holesky.config.json");
+
+ // Overwrite multisig to be EOAowner
+ EOAowner = msg.sender;
+ executorMultisig = EOAowner;
+ operationsMultisig = EOAowner;
+ pauserMultisig = EOAowner;
+ communityMultisig = EOAowner;
+ STRATEGY_MANAGER_WHITELISTER = EOAowner;
+
+ // START RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.startBroadcast();
+
+ emit log_named_address("Deployer and EOAowner Address", EOAowner);
+
+ _deployFromScratch();
+
+ // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.stopBroadcast();
+
+ // Sanity Checks
+ _verifyContractPointers();
+ _verifyImplementations();
+ _verifyContractsInitialized({isInitialDeployment: true});
+ _verifyInitializationParams(); // override to check contract.owner() is EOAowner instead
+
+ logAndOutputContractAddresses("script/output/holesky/M2_deploy_preprod.holesky.config.json");
+ }
+}
diff --git a/script/M1_Deploy.s.sol b/script/deploy/mainnet/M1_Deploy.s.sol
similarity index 66%
rename from script/M1_Deploy.s.sol
rename to script/deploy/mainnet/M1_Deploy.s.sol
index d6feb1713f..48a7db7124 100644
--- a/script/M1_Deploy.s.sol
+++ b/script/deploy/mainnet/M1_Deploy.s.sol
@@ -1,29 +1,28 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
-import "../src/contracts/interfaces/IETHPOSDeposit.sol";
-import "../src/contracts/interfaces/IBeaconChainOracle.sol";
+import "../../../src/contracts/interfaces/IETHPOSDeposit.sol";
+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/StrategyManager.sol";
+import "../../../src/contracts/core/Slasher.sol";
+import "../../../src/contracts/core/DelegationManager.sol";
-import "../src/contracts/strategies/StrategyBaseTVLLimits.sol";
+import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
-import "../src/contracts/pods/EigenPod.sol";
-import "../src/contracts/pods/EigenPodManager.sol";
-import "../src/contracts/pods/DelayedWithdrawalRouter.sol";
+import "../../../src/contracts/pods/EigenPod.sol";
+import "../../../src/contracts/pods/EigenPodManager.sol";
+import "../../../src/contracts/pods/DelayedWithdrawalRouter.sol";
-import "../src/contracts/permissions/PauserRegistry.sol";
-import "../src/contracts/middleware/BLSPublicKeyCompendium.sol";
+import "../../../src/contracts/permissions/PauserRegistry.sol";
-import "../src/test/mocks/EmptyContract.sol";
-import "../src/test/mocks/ETHDepositMock.sol";
+import "../../../src/test/mocks/EmptyContract.sol";
+import "../../../src/test/mocks/ETHDepositMock.sol";
import "forge-std/Script.sol";
import "forge-std/Test.sol";
@@ -77,6 +76,8 @@ contract Deployer_M1 is Script, Test {
// IMMUTABLES TO SET
uint256 REQUIRED_BALANCE_WEI;
+ uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ uint64 GOERLI_GENESIS_TIME = 1616508000;
// OTHER DEPLOYMENT PARAMETERS
uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS;
@@ -104,12 +105,23 @@ contract Deployer_M1 is Script, Test {
DELEGATION_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".delegation.init_paused_status");
EIGENPOD_MANAGER_MAX_PODS = stdJson.readUint(config_data, ".eigenPodManager.max_pods");
EIGENPOD_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".eigenPodManager.init_paused_status");
- DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS = stdJson.readUint(config_data, ".delayedWithdrawalRouter.init_paused_status");
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS = stdJson.readUint(
+ config_data,
+ ".delayedWithdrawalRouter.init_paused_status"
+ );
- STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks"));
- DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks"));
+ STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(
+ stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks")
+ );
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(
+ stdJson.readUint(config_data, ".strategyManager.init_withdrawal_delay_blocks")
+ );
REQUIRED_BALANCE_WEI = stdJson.readUint(config_data, ".eigenPod.REQUIRED_BALANCE_WEI");
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = stdJson.readUint(
+ config_data,
+ ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR"
+ );
// tokens to deploy strategies for
StrategyConfig[] memory strategyConfigs;
@@ -163,7 +175,7 @@ contract Deployer_M1 is Script, Test {
// if on mainnet, use the ETH2 deposit contract address
if (chainId == 1) {
ethPOSDeposit = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa);
- // if not on mainnet, deploy a mock
+ // if not on mainnet, deploy a mock
} else {
ethPOSDeposit = IETHPOSDeposit(stdJson.readAddress(config_data, ".ethPOSDepositAddress"));
}
@@ -171,16 +183,23 @@ contract Deployer_M1 is Script, Test {
ethPOSDeposit,
delayedWithdrawalRouter,
eigenPodManager,
- REQUIRED_BALANCE_WEI
+ uint64(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR),
+ GOERLI_GENESIS_TIME
);
eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
- delegationImplementation = new DelegationManager(strategyManager, slasher);
+ delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
slasherImplementation = new Slasher(strategyManager, delegation);
- eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher);
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegation
+ );
delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
@@ -202,8 +221,7 @@ contract Deployer_M1 is Script, Test {
executorMultisig,
operationsMultisig,
eigenLayerPauserReg,
- STRATEGY_MANAGER_INIT_PAUSED_STATUS,
- STRATEGY_MANAGER_INIT_WITHDRAWAL_DELAY_BLOCKS
+ STRATEGY_MANAGER_INIT_PAUSED_STATUS
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -231,11 +249,13 @@ contract Deployer_M1 is Script, Test {
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
address(delayedWithdrawalRouterImplementation),
- abi.encodeWithSelector(DelayedWithdrawalRouter.initialize.selector,
- executorMultisig,
- eigenLayerPauserReg,
- DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS,
- DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS)
+ abi.encodeWithSelector(
+ DelayedWithdrawalRouter.initialize.selector,
+ executorMultisig,
+ eigenLayerPauserReg,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS
+ )
);
// deploy StrategyBaseTVLLimits contract implementation
@@ -243,13 +263,21 @@ contract Deployer_M1 is Script, Test {
// create upgradeable proxies that each point to the implementation and initialize them
for (uint256 i = 0; i < strategyConfigs.length; ++i) {
deployedStrategyArray.push(
- StrategyBaseTVLLimits(address(
- new TransparentUpgradeableProxy(
- address(baseStrategyImplementation),
- address(eigenLayerProxyAdmin),
- abi.encodeWithSelector(StrategyBaseTVLLimits.initialize.selector, strategyConfigs[i].maxPerDeposit, strategyConfigs[i].maxDeposits, IERC20(strategyConfigs[i].tokenAddress), eigenLayerPauserReg)
+ StrategyBaseTVLLimits(
+ address(
+ new TransparentUpgradeableProxy(
+ address(baseStrategyImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ StrategyBaseTVLLimits.initialize.selector,
+ strategyConfigs[i].maxPerDeposit,
+ strategyConfigs[i].maxDeposits,
+ IERC20(strategyConfigs[i].tokenAddress),
+ eigenLayerPauserReg
+ )
+ )
)
- ))
+ )
);
}
@@ -259,7 +287,6 @@ contract Deployer_M1 is Script, Test {
// STOP RECORDING TRANSACTIONS FOR DEPLOYMENT
vm.stopBroadcast();
-
// CHECK CORRECTNESS OF DEPLOYMENT
_verifyContractsPointAtOneAnother(
delegationImplementation,
@@ -280,7 +307,6 @@ contract Deployer_M1 is Script, Test {
_checkPauserInitializations();
_verifyInitializationParams();
-
// WRITE JSON DATA
string memory parent_object = "parent object";
@@ -289,7 +315,8 @@ contract Deployer_M1 is Script, Test {
vm.serializeAddress(deployed_strategies, strategyConfigs[i].tokenSymbol, address(deployedStrategyArray[i]));
}
string memory deployed_strategies_output = vm.serializeAddress(
- deployed_strategies, strategyConfigs[strategyConfigs.length - 1].tokenSymbol,
+ deployed_strategies,
+ strategyConfigs[strategyConfigs.length - 1].tokenSymbol,
address(deployedStrategyArray[strategyConfigs.length - 1])
);
@@ -301,16 +328,32 @@ contract Deployer_M1 is Script, Test {
vm.serializeAddress(deployed_addresses, "delegation", address(delegation));
vm.serializeAddress(deployed_addresses, "delegationImplementation", address(delegationImplementation));
vm.serializeAddress(deployed_addresses, "strategyManager", address(strategyManager));
- vm.serializeAddress(deployed_addresses, "strategyManagerImplementation", address(strategyManagerImplementation));
+ vm.serializeAddress(
+ deployed_addresses,
+ "strategyManagerImplementation",
+ address(strategyManagerImplementation)
+ );
vm.serializeAddress(deployed_addresses, "eigenPodManager", address(eigenPodManager));
- vm.serializeAddress(deployed_addresses, "eigenPodManagerImplementation", address(eigenPodManagerImplementation));
+ vm.serializeAddress(
+ deployed_addresses,
+ "eigenPodManagerImplementation",
+ address(eigenPodManagerImplementation)
+ );
vm.serializeAddress(deployed_addresses, "delayedWithdrawalRouter", address(delayedWithdrawalRouter));
- vm.serializeAddress(deployed_addresses, "delayedWithdrawalRouterImplementation", address(delayedWithdrawalRouterImplementation));
+ vm.serializeAddress(
+ deployed_addresses,
+ "delayedWithdrawalRouterImplementation",
+ address(delayedWithdrawalRouterImplementation)
+ );
vm.serializeAddress(deployed_addresses, "eigenPodBeacon", address(eigenPodBeacon));
vm.serializeAddress(deployed_addresses, "eigenPodImplementation", address(eigenPodImplementation));
vm.serializeAddress(deployed_addresses, "baseStrategyImplementation", address(baseStrategyImplementation));
vm.serializeAddress(deployed_addresses, "emptyContract", address(emptyContract));
- string memory deployed_addresses_output = vm.serializeString(deployed_addresses, "strategies", deployed_strategies_output);
+ string memory deployed_addresses_output = vm.serializeString(
+ deployed_addresses,
+ "strategies",
+ deployed_strategies_output
+ );
string memory parameters = "parameters";
vm.serializeAddress(parameters, "executorMultisig", executorMultisig);
@@ -328,56 +371,97 @@ contract Deployer_M1 is Script, Test {
}
function _verifyContractsPointAtOneAnother(
- DelegationManager delegationContract,
- StrategyManager strategyManagerContract,
- Slasher slasherContract,
+ DelegationManager delegationContract,
+ StrategyManager strategyManagerContract,
+ Slasher slasherContract,
EigenPodManager eigenPodManagerContract,
DelayedWithdrawalRouter delayedWithdrawalRouterContract
) internal view {
require(delegationContract.slasher() == slasher, "delegation: slasher address not set correctly");
- require(delegationContract.strategyManager() == strategyManager, "delegation: strategyManager address not set correctly");
+ require(
+ delegationContract.strategyManager() == strategyManager,
+ "delegation: strategyManager address not set correctly"
+ );
require(strategyManagerContract.slasher() == slasher, "strategyManager: slasher address not set correctly");
- require(strategyManagerContract.delegation() == delegation, "strategyManager: delegation address not set correctly");
- require(strategyManagerContract.eigenPodManager() == eigenPodManager, "strategyManager: eigenPodManager address not set correctly");
+ require(
+ strategyManagerContract.delegation() == delegation,
+ "strategyManager: delegation address not set correctly"
+ );
+ require(
+ strategyManagerContract.eigenPodManager() == eigenPodManager,
+ "strategyManager: eigenPodManager address not set correctly"
+ );
require(slasherContract.strategyManager() == strategyManager, "slasher: strategyManager not set correctly");
require(slasherContract.delegation() == delegation, "slasher: delegation not set correctly");
- require(eigenPodManagerContract.ethPOS() == ethPOSDeposit, " eigenPodManager: ethPOSDeposit contract address not set correctly");
- require(eigenPodManagerContract.eigenPodBeacon() == eigenPodBeacon, "eigenPodManager: eigenPodBeacon contract address not set correctly");
- require(eigenPodManagerContract.strategyManager() == strategyManager, "eigenPodManager: strategyManager contract address not set correctly");
- require(eigenPodManagerContract.slasher() == slasher, "eigenPodManager: slasher contract address not set correctly");
+ require(
+ eigenPodManagerContract.ethPOS() == ethPOSDeposit,
+ " eigenPodManager: ethPOSDeposit contract address not set correctly"
+ );
+ require(
+ eigenPodManagerContract.eigenPodBeacon() == eigenPodBeacon,
+ "eigenPodManager: eigenPodBeacon contract address not set correctly"
+ );
+ require(
+ eigenPodManagerContract.strategyManager() == strategyManager,
+ "eigenPodManager: strategyManager contract address not set correctly"
+ );
+ require(
+ eigenPodManagerContract.slasher() == slasher,
+ "eigenPodManager: slasher contract address not set correctly"
+ );
- require(delayedWithdrawalRouterContract.eigenPodManager() == eigenPodManager,
- "delayedWithdrawalRouterContract: eigenPodManager address not set correctly");
+ require(
+ delayedWithdrawalRouterContract.eigenPodManager() == eigenPodManager,
+ "delayedWithdrawalRouterContract: eigenPodManager address not set correctly"
+ );
}
function _verifyImplementationsSetCorrectly() internal view {
- require(eigenLayerProxyAdmin.getProxyImplementation(
- TransparentUpgradeableProxy(payable(address(delegation)))) == address(delegationImplementation),
- "delegation: implementation set incorrectly");
- require(eigenLayerProxyAdmin.getProxyImplementation(
- TransparentUpgradeableProxy(payable(address(strategyManager)))) == address(strategyManagerImplementation),
- "strategyManager: implementation set incorrectly");
- require(eigenLayerProxyAdmin.getProxyImplementation(
- TransparentUpgradeableProxy(payable(address(slasher)))) == address(slasherImplementation),
- "slasher: implementation set incorrectly");
- require(eigenLayerProxyAdmin.getProxyImplementation(
- TransparentUpgradeableProxy(payable(address(eigenPodManager)))) == address(eigenPodManagerImplementation),
- "eigenPodManager: implementation set incorrectly");
- require(eigenLayerProxyAdmin.getProxyImplementation(
- TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter)))) == address(delayedWithdrawalRouterImplementation),
- "delayedWithdrawalRouter: implementation set incorrectly");
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(delegation)))) ==
+ address(delegationImplementation),
+ "delegation: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(strategyManager)))
+ ) == address(strategyManagerImplementation),
+ "strategyManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(slasher)))) ==
+ address(slasherImplementation),
+ "slasher: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager)))
+ ) == address(eigenPodManagerImplementation),
+ "eigenPodManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter)))
+ ) == address(delayedWithdrawalRouterImplementation),
+ "delayedWithdrawalRouter: implementation set incorrectly"
+ );
for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
- require(eigenLayerProxyAdmin.getProxyImplementation(
- TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i])))) == address(baseStrategyImplementation),
- "strategy: implementation set incorrectly");
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i])))
+ ) == address(baseStrategyImplementation),
+ "strategy: implementation set incorrectly"
+ );
}
- require(eigenPodBeacon.implementation() == address(eigenPodImplementation),
- "eigenPodBeacon: implementation set incorrectly");
+ require(
+ eigenPodBeacon.implementation() == address(eigenPodImplementation),
+ "eigenPodBeacon: implementation set incorrectly"
+ );
}
function _verifyInitialOwners() internal view {
@@ -388,15 +472,27 @@ contract Deployer_M1 is Script, Test {
require(eigenLayerProxyAdmin.owner() == executorMultisig, "eigenLayerProxyAdmin: owner not set correctly");
require(eigenPodBeacon.owner() == executorMultisig, "eigenPodBeacon: owner not set correctly");
- require(delayedWithdrawalRouter.owner() == executorMultisig, "delayedWithdrawalRouter: owner not set correctly");
+ require(
+ delayedWithdrawalRouter.owner() == executorMultisig,
+ "delayedWithdrawalRouter: owner not set correctly"
+ );
}
function _checkPauserInitializations() internal view {
require(delegation.pauserRegistry() == eigenLayerPauserReg, "delegation: pauser registry not set correctly");
- require(strategyManager.pauserRegistry() == eigenLayerPauserReg, "strategyManager: pauser registry not set correctly");
+ require(
+ strategyManager.pauserRegistry() == eigenLayerPauserReg,
+ "strategyManager: pauser registry not set correctly"
+ );
require(slasher.pauserRegistry() == eigenLayerPauserReg, "slasher: pauser registry not set correctly");
- require(eigenPodManager.pauserRegistry() == eigenLayerPauserReg, "eigenPodManager: pauser registry not set correctly");
- require(delayedWithdrawalRouter.pauserRegistry() == eigenLayerPauserReg, "delayedWithdrawalRouter: pauser registry not set correctly");
+ require(
+ eigenPodManager.pauserRegistry() == eigenLayerPauserReg,
+ "eigenPodManager: pauser registry not set correctly"
+ );
+ require(
+ delayedWithdrawalRouter.pauserRegistry() == eigenLayerPauserReg,
+ "delayedWithdrawalRouter: pauser registry not set correctly"
+ );
require(eigenLayerPauserReg.isPauser(operationsMultisig), "pauserRegistry: operationsMultisig is not pauser");
require(eigenLayerPauserReg.isPauser(executorMultisig), "pauserRegistry: executorMultisig is not pauser");
@@ -404,18 +500,24 @@ contract Deployer_M1 is Script, Test {
require(eigenLayerPauserReg.unpauser() == executorMultisig, "pauserRegistry: unpauser not set correctly");
for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
- require(deployedStrategyArray[i].pauserRegistry() == eigenLayerPauserReg, "StrategyBaseTVLLimits: pauser registry not set correctly");
- require(deployedStrategyArray[i].paused() == 0, "StrategyBaseTVLLimits: init paused status set incorrectly");
+ require(
+ deployedStrategyArray[i].pauserRegistry() == eigenLayerPauserReg,
+ "StrategyBaseTVLLimits: pauser registry not set correctly"
+ );
+ require(
+ deployedStrategyArray[i].paused() == 0,
+ "StrategyBaseTVLLimits: init paused status set incorrectly"
+ );
}
// // pause *nothing*
// uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS = 0;
// // pause *everything*
- // uint256 SLASHER_INIT_PAUSED_STATUS = type(uint256).max;
+ // uint256 SLASHER_INIT_PAUSED_STATUS = type(uint256).max;
// // pause *everything*
- // uint256 DELEGATION_INIT_PAUSED_STATUS = type(uint256).max;
+ // uint256 DELEGATION_INIT_PAUSED_STATUS = type(uint256).max;
// // pause *all of the proof-related functionality* (everything that can be paused other than creation of EigenPods)
- // uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS = (2**1) + (2**2) + (2**3) + (2**4); /* = 30 */
+ // uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS = (2**1) + (2**2) + (2**3) + (2**4); /* = 30 */
// // pause *nothing*
// uint256 DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS = 0;
require(strategyManager.paused() == 0, "strategyManager: init paused status set incorrectly");
@@ -433,41 +535,54 @@ contract Deployer_M1 is Script, Test {
// "strategyManager: withdrawalDelayBlocks initialized incorrectly");
// require(delayedWithdrawalRouter.withdrawalDelayBlocks() == 7 days / 12 seconds,
// "delayedWithdrawalRouter: withdrawalDelayBlocks initialized incorrectly");
- // uint256 REQUIRED_BALANCE_WEI = 31 ether;
- require(eigenPodImplementation.REQUIRED_BALANCE_WEI() == 31 ether,
- "eigenPod: REQUIRED_BALANCE_WEI initialized incorrectly");
+ // uint256 REQUIRED_BALANCE_WEI = 32 ether;
- require(strategyManager.strategyWhitelister() == operationsMultisig,
- "strategyManager: strategyWhitelister address not set correctly");
+ require(
+ strategyManager.strategyWhitelister() == operationsMultisig,
+ "strategyManager: strategyWhitelister address not set correctly"
+ );
- require(eigenPodManager.beaconChainOracle() == IBeaconChainOracle(address(0)),
- "eigenPodManager: eigenPodBeacon contract address not set correctly");
+ require(
+ eigenPodManager.beaconChainOracle() == IBeaconChainOracle(address(0)),
+ "eigenPodManager: eigenPodBeacon contract address not set correctly"
+ );
- require(delayedWithdrawalRouter.eigenPodManager() == eigenPodManager,
- "delayedWithdrawalRouter: eigenPodManager set incorrectly");
+ require(
+ delayedWithdrawalRouter.eigenPodManager() == eigenPodManager,
+ "delayedWithdrawalRouter: eigenPodManager set incorrectly"
+ );
- require(baseStrategyImplementation.strategyManager() == strategyManager,
- "baseStrategyImplementation: strategyManager set incorrectly");
+ require(
+ baseStrategyImplementation.strategyManager() == strategyManager,
+ "baseStrategyImplementation: strategyManager set incorrectly"
+ );
- require(eigenPodImplementation.ethPOS() == ethPOSDeposit,
- "eigenPodImplementation: ethPOSDeposit contract address not set correctly");
- require(eigenPodImplementation.eigenPodManager() == eigenPodManager,
- " eigenPodImplementation: eigenPodManager contract address not set correctly");
- require(eigenPodImplementation.delayedWithdrawalRouter() == delayedWithdrawalRouter,
- " eigenPodImplementation: delayedWithdrawalRouter contract address not set correctly");
+ require(
+ eigenPodImplementation.ethPOS() == ethPOSDeposit,
+ "eigenPodImplementation: ethPOSDeposit contract address not set correctly"
+ );
+ require(
+ eigenPodImplementation.eigenPodManager() == eigenPodManager,
+ " eigenPodImplementation: eigenPodManager contract address not set correctly"
+ );
+ require(
+ eigenPodImplementation.delayedWithdrawalRouter() == delayedWithdrawalRouter,
+ " eigenPodImplementation: delayedWithdrawalRouter contract address not set correctly"
+ );
string memory config_data = vm.readFile(deployConfigPath);
for (uint i = 0; i < deployedStrategyArray.length; i++) {
- uint256 maxPerDeposit = stdJson.readUint(config_data, string.concat(".strategies[", vm.toString(i), "].max_per_deposit"));
- uint256 maxDeposits = stdJson.readUint(config_data, string.concat(".strategies[", vm.toString(i), "].max_deposits"));
+ uint256 maxPerDeposit = stdJson.readUint(
+ config_data,
+ string.concat(".strategies[", vm.toString(i), "].max_per_deposit")
+ );
+ uint256 maxDeposits = stdJson.readUint(
+ config_data,
+ string.concat(".strategies[", vm.toString(i), "].max_deposits")
+ );
(uint256 setMaxPerDeposit, uint256 setMaxDeposits) = deployedStrategyArray[i].getTVLLimits();
require(setMaxPerDeposit == maxPerDeposit, "setMaxPerDeposit not set correctly");
require(setMaxDeposits == maxDeposits, "setMaxDeposits not set correctly");
}
}
}
-
-
-
-
-
diff --git a/script/deploy/mainnet/M2Deploy.s.sol b/script/deploy/mainnet/M2Deploy.s.sol
new file mode 100644
index 0000000000..68f45520b6
--- /dev/null
+++ b/script/deploy/mainnet/M2Deploy.s.sol
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
+import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+
+import "../../../src/contracts/interfaces/IETHPOSDeposit.sol";
+import "../../../src/contracts/interfaces/IBeaconChainOracle.sol";
+
+import "../../../src/contracts/core/StrategyManager.sol";
+import "../../../src/contracts/core/Slasher.sol";
+import "../../../src/contracts/core/DelegationManager.sol";
+
+import "../../../src/contracts/pods/EigenPod.sol";
+import "../../../src/contracts/pods/EigenPodManager.sol";
+import "../../../src/contracts/pods/DelayedWithdrawalRouter.sol";
+
+import "../../../src/contracts/permissions/PauserRegistry.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/milestone/M2Deploy.s.sol:M2Deploy --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
+contract M2Deploy is Script, Test {
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ string public m1DeploymentOutputPath;
+ string public m2DeploymentOutputPath;
+
+ // EigenLayer core contracts
+ ISlasher public slasher;
+ IDelegationManager public delegation;
+ DelegationManager public delegationImplementation;
+ IStrategyManager public strategyManager;
+ StrategyManager public strategyManagerImplementation;
+ IEigenPodManager public eigenPodManager;
+ EigenPodManager public eigenPodManagerImplementation;
+ IDelayedWithdrawalRouter public delayedWithdrawalRouter;
+ IBeacon public eigenPodBeacon;
+ EigenPod public eigenPodImplementation;
+
+ // Eigenlayer Proxy Admin
+ ProxyAdmin public eigenLayerProxyAdmin;
+
+ // BeaconChain deposit contract & beacon chain oracle
+ IETHPOSDeposit public ethPOS;
+ address public beaconChainOracle;
+
+ // RPC url to fork from for pre-upgrade state change tests
+ string public rpcUrl;
+
+ // Pre-upgrade values to check post-upgrade
+ address public strategyWhitelister;
+ bytes32 public withdrawalDelayBlocksStorageSlot = bytes32(uint256(204)); // 0xcc == 204
+ uint256 public withdrawalsQueuedStorageSlot = 208; //0xd0 = 208
+ uint256 public withdrawalDelayBlocks;
+ bytes32 public delegationManagerDomainSeparator;
+ uint256 public numPods;
+
+ // Pointers to pre-upgrade values for lstDepositor
+ address public lstDepositor;
+ uint256 public stakerStrategyListLength;
+ uint256[] public stakerStrategyShares; // Array of shares in each strategy
+ IStrategy[] public stakerStrategyList; // Array of strategies staker has deposited into
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal public queuedWithdrawalLst; // queuedWithdrawal for
+ uint256 public m1PostWithdrawTokensReceived; // Number of tokens received after completing withdrawal on M1 contracts
+ bytes32 public withdrawalRootBeforeUpgrade;
+ IERC20[] public tokensToWithdraw;
+ uint256 public lstDepositorNonceBefore;
+ uint256 public lstDepositorNumWithdrawalsQueued;
+ uint256 public lstDepositorBalancePreUpgrade; // balance after withdrawal on m1 contracts
+ uint256 public lstDepositorSharesPreUpgrade; // shares after withdrawal on m1 contracts
+
+ // Pointers to pre-upgrade values for eigenPodDepositor
+ address public eigenPodDepositor;
+ IEigenPod public eigenPod;
+ address public eigenPodOwner;
+ bool public hasPod;
+ uint64 public mostRecentWithdrawalBlock;
+
+ function run() external {
+ // Read and log the chain ID
+ uint256 chainId = block.chainid;
+ emit log_named_uint("You are deploying on ChainID", chainId);
+
+ // Update deployment path addresses if on mainnet
+ if (chainId == 1) {
+ m1DeploymentOutputPath = string(bytes("script/output/M1_deployment_mainnet_2023_6_9.json"));
+ m2DeploymentOutputPath = "script/output/M2_deployment_data_mainnet.json";
+ rpcUrl = "RPC_MAINNET";
+ } else if (chainId == 5) {
+ m1DeploymentOutputPath = string(bytes("script/output/M1_deployment_goerli_2023_3_23.json"));
+ m2DeploymentOutputPath = "script/output/M2_deployment_data_goerli.json";
+ rpcUrl = "RPC_GOERLI";
+ } else {
+ revert("Chain not supported");
+ }
+
+ // Set beacon chain oracle, currently 0 address
+ beaconChainOracle = 0x0000000000000000000000000000000000000000;
+
+ // Read json data
+ string memory deployment_data = vm.readFile(m1DeploymentOutputPath);
+ slasher = Slasher(stdJson.readAddress(deployment_data, ".addresses.slasher"));
+ delegation = slasher.delegation();
+ strategyManager = slasher.strategyManager();
+ eigenPodManager = strategyManager.eigenPodManager();
+ eigenPodBeacon = eigenPodManager.eigenPodBeacon();
+ ethPOS = eigenPodManager.ethPOS();
+
+ eigenLayerProxyAdmin = ProxyAdmin(stdJson.readAddress(deployment_data, ".addresses.eigenLayerProxyAdmin"));
+
+ // Store pre-upgrade values to check against later
+ strategyWhitelister = strategyManager.strategyWhitelister();
+ withdrawalDelayBlocks = m1StrategyManager(address(strategyManager)).withdrawalDelayBlocks();
+ delegationManagerDomainSeparator = IDelegationManagerV0(address(delegation)).DOMAIN_SEPARATOR();
+ numPods = eigenPodManager.numPods();
+ delayedWithdrawalRouter = EigenPod(payable(eigenPodBeacon.implementation())).delayedWithdrawalRouter();
+
+ // Set chain-specific values
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ uint256[] memory shareAmounts = new uint256[](1);
+ if (chainId == 1) {
+ // no-op for now
+ } else if (chainId == 5) {
+ // Set LST Depositor values
+ lstDepositor = 0x01e453D2465cEC1BD2ac9aed06115Fbf28482b33;
+ strategyArray[0] = IStrategy(0x879944A8cB437a5f8061361f82A6d4EED59070b5);
+ shareAmounts[0] = 188647761812080108;
+ IStrategyManager.DeprecatedStruct_WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager
+ .DeprecatedStruct_WithdrawerAndNonce({withdrawer: lstDepositor, nonce: uint96(0)});
+ queuedWithdrawalLst = IStrategyManager.DeprecatedStruct_QueuedWithdrawal({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ staker: lstDepositor,
+ withdrawerAndNonce: withdrawerAndNonce,
+ withdrawalStartBlock: uint32(9083727),
+ delegatedAddress: delegation.delegatedTo(lstDepositor)
+ });
+ tokensToWithdraw.push(IERC20(0x178E141a0E3b34152f73Ff610437A7bf9B83267A));
+
+ // Set eigenPod owner values
+ eigenPodDepositor = 0xE9D04433bac1bd584B0493cbaBa170CCCBDA8F00;
+ } else {
+ revert("chain ID not supported");
+ }
+
+ // Store LST depositor pre-upgrade values
+ stakerStrategyListLength = strategyManager.stakerStrategyListLength(lstDepositor);
+ (stakerStrategyList, stakerStrategyShares) = strategyManager.getDeposits(lstDepositor);
+ withdrawalRootBeforeUpgrade = strategyManager.calculateWithdrawalRoot(queuedWithdrawalLst);
+ lstDepositorNonceBefore = StrategyManagerStorage(address(strategyManager)).nonces(lstDepositor);
+ lstDepositorNumWithdrawalsQueued = m1StrategyManager(address(strategyManager)).numWithdrawalsQueued(
+ lstDepositor
+ );
+
+ // Store eigenPod owner pre-ugprade values
+ eigenPod = eigenPodManager.ownerToPod(eigenPodDepositor);
+ require(address(eigenPod).balance > 0, "eigenPod to test has balance of 0");
+ hasPod = eigenPodManager.hasPod(eigenPodDepositor);
+ eigenPodOwner = eigenPod.podOwner();
+ mostRecentWithdrawalBlock = m1EigenPod(address(eigenPod)).mostRecentWithdrawalBlockNumber();
+
+ // Complete queued withdrawals before upgrade to sanity check post upgrade
+ _completeWithdrawalsPreUpgrade();
+
+ // Begin deployment
+ vm.startBroadcast();
+
+ // Deploy new implmementation contracts
+ delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOS,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegation
+ );
+ eigenPodImplementation = new EigenPod({
+ _ethPOS: ethPOS,
+ _delayedWithdrawalRouter: delayedWithdrawalRouter,
+ _eigenPodManager: eigenPodManager,
+ _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 32 gwei,
+ _GENESIS_TIME: 1616508000
+ });
+
+ vm.stopBroadcast();
+
+ // Write json data out
+ string memory parent_object = "parent object";
+ string memory deployed_addresses = "addresses";
+
+ // Serialize proxy and non-deployed addresses
+ vm.serializeAddress(deployed_addresses, "slasher", address(slasher));
+ vm.serializeAddress(deployed_addresses, "delegation", address(delegation));
+ vm.serializeAddress(deployed_addresses, "strategyManager", address(strategyManager));
+ vm.serializeAddress(deployed_addresses, "delayedWithdrawalRouter", address(delayedWithdrawalRouter));
+ vm.serializeAddress(deployed_addresses, "eigenPodManager", address(eigenPodManager));
+ vm.serializeAddress(deployed_addresses, "eigenPodBeacon", address(eigenPodBeacon));
+ vm.serializeAddress(deployed_addresses, "ethPOS", address(ethPOS));
+
+ // Serialize new implementation addresses
+ vm.serializeAddress(deployed_addresses, "delegationImplementation", address(delegationImplementation));
+ vm.serializeAddress(
+ deployed_addresses,
+ "strategyManagerImplementation",
+ address(strategyManagerImplementation)
+ );
+ vm.serializeAddress(
+ deployed_addresses,
+ "eigenPodManagerImplementation",
+ address(eigenPodManagerImplementation)
+ );
+ string memory deployed_addresses_output = vm.serializeAddress(
+ deployed_addresses,
+ "eigenPodImplementation",
+ address(eigenPodImplementation)
+ );
+
+ // Add chain info
+ string memory chain_info = "chainInfo";
+ vm.serializeUint(chain_info, "deploymentBlock", block.number);
+ string memory chain_info_output = vm.serializeUint(chain_info, "chainId", chainId);
+
+ // Save addresses
+ vm.serializeString(parent_object, deployed_addresses, deployed_addresses_output);
+ string memory finalJson = vm.serializeString(parent_object, chain_info, chain_info_output);
+
+ // Write output to file
+ vm.writeJson(finalJson, m2DeploymentOutputPath);
+
+ // Perform post-upgrade tests
+ simulatePerformingUpgrade();
+ checkUpgradeCorrectness();
+ }
+
+ function simulatePerformingUpgrade() public {
+ cheats.startPrank(eigenLayerProxyAdmin.owner());
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delegation))),
+ address(delegationImplementation)
+ );
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation)
+ );
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation)
+ );
+ cheats.stopPrank();
+
+ // Upgrade beacon
+ cheats.prank(UpgradeableBeacon(address(eigenPodBeacon)).owner());
+ UpgradeableBeacon(address(eigenPodBeacon)).upgradeTo(address(eigenPodImplementation));
+ }
+
+ function checkUpgradeCorrectness() public {
+ _verifyStorageSlots();
+
+ _verifyContractsInitialized();
+
+ _verifyLSTDepositorCorrectness();
+
+ _verifyEigenPodCorrectness();
+ }
+
+ // Call contracts to ensure that all simple view functions return the same values (e.g. the return value of `StrategyManager.delegation()` hasn’t changed)
+ // StrategyManager: delegation, eigenPodManager, slasher, strategyWhitelister, withdrawalDelayBlocks all unchanged
+ // DelegationManager: DOMAIN_SEPARATOR, strategyManager, slasher, eigenPodManager all unchanged
+ // EigenPodManager: ethPOS, eigenPodBeacon, strategyManager, slasher, beaconChainOracle, numPods all unchanged
+ // delegationManager is now correct (added immutable)
+ // Call contracts to make sure they are still “initialized” (ensure that trying to call initializer reverts)
+ function _verifyStorageSlots() internal view {
+ // StrategyManager: Check view functions return pre-upgraded values
+ require(strategyManager.delegation() == delegation, "strategyManager.delegation incorrect");
+ require(strategyManager.eigenPodManager() == eigenPodManager, "strategyManager.eigenPodManager incorrect");
+ require(strategyManager.slasher() == slasher, "strategyManager.slasher incorrect");
+ require(
+ strategyManager.strategyWhitelister() == strategyWhitelister,
+ "strategyManager.strategyWhitelister incorrect"
+ );
+ require(
+ cheats.load(address(strategyManager), withdrawalDelayBlocksStorageSlot) == bytes32(withdrawalDelayBlocks),
+ "strategyManager.withdrawalDelayBlocks incorrect"
+ );
+ // DelegationManager: Check view functions return pre-upgraded values
+ require(DelegationManagerStorage(address(delegation)).strategyManager() == strategyManager, "delegation.strategyManager incorrect");
+ require(
+ delegation.domainSeparator() == delegationManagerDomainSeparator,
+ "delegation.domainSeparator incorrect"
+ );
+ require(DelegationManagerStorage(address(delegation)).slasher() == slasher, "delegation.slasher incorrect");
+ require(DelegationManagerStorage(address(delegation)).eigenPodManager() == eigenPodManager, "delegation.eigenPodManager incorrect");
+ // EigenPodManager: check view functions return pre-upgraded values
+ require(eigenPodManager.ethPOS() == ethPOS, "eigenPodManager.ethPOS incorrect");
+ require(eigenPodManager.eigenPodBeacon() == eigenPodBeacon, "eigenPodManager.eigenPodBeacon incorrect");
+ require(eigenPodManager.strategyManager() == strategyManager, "eigenPodManager.strategyManager incorrect");
+ require(eigenPodManager.slasher() == slasher, "eigenPodManager.slasher incorrect");
+ require(
+ address(eigenPodManager.beaconChainOracle()) == beaconChainOracle,
+ "eigenPodManager.beaconChainOracle incorrect"
+ );
+ require(eigenPodManager.numPods() == numPods, "eigenPodManager.numPods incorrect");
+ require(EigenPodManagerStorage(address(eigenPodManager)).delegationManager() == delegation, "eigenPodManager.delegationManager incorrect");
+ }
+
+ function _verifyContractsInitialized() internal {
+ // Check that contracts are unable to be re-initialized
+ cheats.expectRevert(bytes("Initializable: contract is already initialized"));
+ StrategyManager(address(strategyManager)).initialize(
+ address(this),
+ address(this),
+ PauserRegistry(address(this)),
+ 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, // initialPausedStatus
+ 0, // minWithdrawalDelayBLocks
+ strategyArray,
+ withdrawalDelayBlocksArray
+ );
+
+ cheats.expectRevert(bytes("Initializable: contract is already initialized"));
+ EigenPodManager(address(eigenPodManager)).initialize(
+ IBeaconChainOracle(address(this)),
+ address(this),
+ PauserRegistry(address(this)),
+ 0
+ );
+ }
+
+ function _verifyLSTDepositorCorrectness() internal {
+ // Check that LST depositor has the same shares in the same strategies
+ require(
+ strategyManager.stakerStrategyListLength(lstDepositor) == stakerStrategyListLength,
+ "strategyManager.stakerStrategyListLength incorrect"
+ );
+ (IStrategy[] memory stakerStrategyListAfter, uint256[] memory stakerStrategySharesAfter) = strategyManager
+ .getDeposits(lstDepositor);
+ for (uint256 i = 0; i < stakerStrategyListAfter.length; i++) {
+ require(
+ stakerStrategyListAfter[i] == stakerStrategyList[i],
+ "strategyManager.stakerStrategyList incorrect"
+ );
+ require(
+ stakerStrategySharesAfter[i] == stakerStrategyShares[i],
+ "strategyManager.stakerStrategyShares incorrect"
+ );
+ }
+
+ // Check that withdrawal root resolves to prev root
+ require(
+ withdrawalRootBeforeUpgrade == strategyManager.calculateWithdrawalRoot(queuedWithdrawalLst),
+ "strategyManager.calculateWithdrawalRoot does not resolve to previous root"
+ );
+ require(
+ StrategyManagerStorage(address(strategyManager)).withdrawalRootPending(withdrawalRootBeforeUpgrade),
+ "strategyManager.withdrawalRootPending incorrect"
+ );
+
+ // Check that nonce and numWithdrawalsQueued remains the same
+ require(
+ lstDepositorNonceBefore == StrategyManagerStorage(address(strategyManager)).nonces(lstDepositor),
+ "strategyManager.nonces mismatch"
+ );
+ bytes32 withdrawalsQueuedSlot = keccak256(abi.encode(lstDepositor, withdrawalsQueuedStorageSlot));
+ require(
+ lstDepositorNumWithdrawalsQueued == uint256(cheats.load(address(strategyManager), withdrawalsQueuedSlot)),
+ "strategyManager.numWithdrawalsQueued mismatch"
+ );
+
+ // Unpause delegationManager withdrawals
+ uint256 paused = IPausable(address(delegation)).paused();
+ cheats.prank(IPauserRegistry(IPausable(address(delegation)).pauserRegistry()).unpauser());
+ IPausable(address(delegation)).unpause(paused ^ (1 << 2)); // Withdrawal queue on 2nd bit
+
+ // Migrate queued withdrawal to delegationManger
+ // Migrating the withdrawal root also verifies that the root has not been erroneously set to false
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal[]
+ memory queuedWithdrawals = new IStrategyManager.DeprecatedStruct_QueuedWithdrawal[](1);
+ queuedWithdrawals[0] = queuedWithdrawalLst;
+ delegation.migrateQueuedWithdrawals(queuedWithdrawals);
+
+ // If successful, confirms that queuedWithdrawal root has not been corrupted between upgrades
+ // Queue withdrawal on delegationManager
+ IDelegationManager.Withdrawal memory delegationManagerWithdrawal = IDelegationManager.Withdrawal({
+ staker: queuedWithdrawalLst.staker,
+ delegatedTo: queuedWithdrawalLst.delegatedAddress,
+ withdrawer: queuedWithdrawalLst.withdrawerAndNonce.withdrawer,
+ nonce: 0, // first withdrawal, so 0 nonce
+ startBlock: queuedWithdrawalLst.withdrawalStartBlock,
+ strategies: queuedWithdrawalLst.strategies,
+ shares: queuedWithdrawalLst.shares
+ });
+ cheats.prank(lstDepositor);
+ delegation.completeQueuedWithdrawal(delegationManagerWithdrawal, tokensToWithdraw, 0, true);
+
+ // Check balances and shares are the same as a withdrawal done pre-upgrade
+ uint256 lstDepositorBalancePostUpgrade = tokensToWithdraw[0].balanceOf(lstDepositor);
+ uint256 lstDepositorSharesPostUpgrade = strategyManager.stakerStrategyShares(
+ lstDepositor,
+ delegationManagerWithdrawal.strategies[0]
+ );
+ require(
+ lstDepositorBalancePostUpgrade == lstDepositorBalancePreUpgrade,
+ "delegationManager.completeQueuedWithdrawal incorrect post balance"
+ );
+ require(
+ lstDepositorSharesPostUpgrade == lstDepositorSharesPreUpgrade,
+ "delegationManager.completeQueuedWithdrawal incorrect post shares"
+ );
+ }
+
+ function _verifyEigenPodCorrectness() public {
+ // Check that state is correct
+ require(
+ address(eigenPodManager.ownerToPod(eigenPodDepositor)) == address(eigenPod),
+ "eigenPodManager.ownerToPod incorrect"
+ );
+ require(eigenPodManager.hasPod(eigenPodDepositor) == hasPod, "eigenPodManager.hasPod incorrect");
+ require(eigenPod.podOwner() == eigenPodOwner, "eigenPod.podOwner incorrect");
+ require(
+ eigenPod.mostRecentWithdrawalTimestamp() == mostRecentWithdrawalBlock,
+ "eigenPod.mostRecentWithdrawalTimestamp incorrect"
+ ); // Timestmap replace by block number in storage
+ require(!eigenPod.hasRestaked(), "eigenPod.hasRestaked incorrect");
+
+ // Unpause eigenpods verify credentials
+ uint256 paused = IPausable(address(eigenPodManager)).paused();
+ cheats.prank(IPauserRegistry(IPausable(address(eigenPodManager)).pauserRegistry()).unpauser());
+ IPausable(address(eigenPodManager)).unpause(paused ^ (1 << 2)); // eigenpods verify credentials on 2nd bit
+
+ // Get values to check post activating restaking
+ uint256 podBalanceBefore = address(eigenPod).balance;
+ uint256 userWithdrawalsLength = delayedWithdrawalRouter.userWithdrawalsLength(eigenPodDepositor);
+
+ // Activate restaking and expect emit
+ cheats.prank(eigenPodOwner);
+ cheats.expectEmit(true, true, true, true);
+ emit RestakingActivated(eigenPodOwner);
+ eigenPod.activateRestaking();
+
+ // Check updated storage values
+ require(eigenPod.hasRestaked(), "eigenPod.hasRestaked not set to true");
+ require(address(eigenPod).balance == 0, "eigenPod balance not 0 after activating restaking");
+ require(eigenPod.nonBeaconChainETHBalanceWei() == 0, "non beacon chain eth balance not 0");
+ require(
+ eigenPod.mostRecentWithdrawalTimestamp() == block.timestamp,
+ "eigenPod.mostRecentWithdrawalTimestamp not updated"
+ );
+ require(
+ eigenPod.mostRecentWithdrawalTimestamp() > mostRecentWithdrawalBlock,
+ "eigenPod.mostRecentWithdrawalTimestamp not updated"
+ );
+
+ // Check that delayed withdrawal has been created
+ require(
+ delayedWithdrawalRouter.userWithdrawalsLength(eigenPodDepositor) == userWithdrawalsLength + 1,
+ "delayedWithdrawalRouter.userWithdrawalsLength not incremented"
+ );
+ IDelayedWithdrawalRouter.DelayedWithdrawal memory delayedWithdrawal = delayedWithdrawalRouter
+ .userDelayedWithdrawalByIndex(eigenPodDepositor, userWithdrawalsLength);
+ require(delayedWithdrawal.amount == podBalanceBefore, "delayedWithdrawal.amount incorrect");
+ require(delayedWithdrawal.blockCreated == block.number, "delayedWithdrawal.blockCreated incorrect");
+ }
+
+ function _completeWithdrawalsPreUpgrade() public {
+ // Save fork and create new fork
+ uint256 forkId = cheats.activeFork();
+ cheats.createSelectFork(cheats.envString(rpcUrl), block.number);
+
+ // Complete lstDepositor withdrawal
+ cheats.prank(lstDepositor);
+ m1StrategyManager(address(strategyManager)).completeQueuedWithdrawal(
+ queuedWithdrawalLst,
+ tokensToWithdraw,
+ 0,
+ true
+ );
+ lstDepositorBalancePreUpgrade = tokensToWithdraw[0].balanceOf(lstDepositor);
+ lstDepositorSharesPreUpgrade = strategyManager.stakerStrategyShares(
+ lstDepositor,
+ queuedWithdrawalLst.strategies[0]
+ );
+
+ // Reload previous fork
+ cheats.selectFork(forkId);
+ }
+
+ // Existing LST depositor – ensure that strategy length and shares are all identical
+ // Existing LST depositor – ensure that an existing queued withdrawal remains queued
+ // Check from stored root, and recalculate root and make sure it matches
+ // Check that completing the withdrawal results in the same behavior (same transfer of ERC20 tokens)
+ // Check that staker nonce & numWithdrawalsQueued remains the same as before the upgrade
+ // Existing LST depositor – queuing a withdrawal before/after the upgrade has the same effects (same decrease in shares, resultant withdrawal root)
+ // Existing EigenPod owner – EigenPodManager.ownerToPod remains the same
+ // Existing EigenPod owner – EigenPodManager.hasPod remains the same
+ // Existing EigenPod owner – EigenPod.podOwner remains the same
+ // Existing EigenPod owner – EigenPod.mostRecentWithdrawalTimestamp (after upgrade) == EigenPod.mostRecentWithdrawalBlock (before upgrade)
+ // Existing EigenPod owner – EigenPod.hasRestaked remains false
+ // Can call EigenPod.activateRestaking and it correctly:
+ // Sends all funds in EigenPod (need to make sure it has nonzero balance beforehand)
+ // Sets `hasRestaked` to ‘true’
+ // Emits a ‘RestakingActivated’ event
+ // EigenPod.mostRecentWithdrawalTimestamp updates correctly
+ // EigenPod: ethPOS, delayedWithdrawalRouter, eigenPodManager
+ event RestakingActivated(address indexed podOwner);
+}
+
+interface IDelegationManagerV0 {
+ function DOMAIN_SEPARATOR() external view returns (bytes32);
+}
+
+interface m1StrategyManager {
+ function withdrawalDelayBlocks() external view returns (uint256);
+
+ function numWithdrawalsQueued(address staker) external view returns (uint256);
+
+ function completeQueuedWithdrawal(
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal,
+ IERC20[] memory tokensToWithdraw,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ ) external;
+}
+
+interface m1EigenPod {
+ function mostRecentWithdrawalBlockNumber() external view returns (uint64);
+}
diff --git a/script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol b/script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol
new file mode 100644
index 0000000000..daa042db51
--- /dev/null
+++ b/script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../../utils/ExistingDeploymentParser.sol";
+import "../../utils/TimelockEncoding.sol";
+import "../../utils/Multisend.sol";
+
+/**
+ * @notice Script used for the first deployment of EigenLayer core contracts to Holesky
+ * anvil --fork-url $RPC_MAINNET
+ * forge script script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol:M2_Mainnet_Upgrade --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv
+ *
+ * forge script script/deploy/mainnet/M2_Mainnet_Upgrade.s.sol:M2_Mainnet_Upgrade --rpc-url $RPC_MAINNET --private-key $PRIVATE_KEY --broadcast -vvvv
+ *
+ */
+contract M2_Mainnet_Upgrade is ExistingDeploymentParser {
+ function run() external virtual {
+ _parseDeployedContracts("script/output/mainnet/M1_deployment_mainnet_2023_6_9.json");
+ _parseInitialDeploymentParams("script/configs/mainnet/M2_mainnet_upgrade.config.json");
+
+ // START RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.startBroadcast();
+
+ emit log_named_address("Deployer Address", msg.sender);
+
+ _deployImplementationContracts();
+
+ // STOP RECORDING TRANSACTIONS FOR DEPLOYMENT
+ vm.stopBroadcast();
+
+ // Simulate upgrade of contracts to new implementations
+ _simulateUpgrade();
+
+ // Sanity Checks
+ _verifyContractPointers();
+ _verifyImplementations();
+ _verifyContractsInitialized({isInitialDeployment: true});
+ _verifyInitializationParams();
+
+ logAndOutputContractAddresses("script/output/mainnet/M2_mainnet_upgrade.output.json");
+ }
+
+ /**
+ * @notice Deploy EigenLayer contracts from scratch for Holesky
+ */
+ function _deployImplementationContracts() internal {
+ // 1. Deploy New TUPS
+ avsDirectoryImplementation = new AVSDirectory(delegationManager);
+ avsDirectory = AVSDirectory(
+ address(
+ new TransparentUpgradeableProxy(
+ address(avsDirectoryImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ AVSDirectory.initialize.selector,
+ executorMultisig, // initialOwner
+ eigenLayerPauserReg,
+ AVS_DIRECTORY_INIT_PAUSED_STATUS
+ )
+ )
+ )
+ );
+
+ // 2. Deploy Implementations
+ eigenPodImplementation = new EigenPod(
+ IETHPOSDeposit(ETHPOSDepositAddress),
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ EIGENPOD_GENESIS_TIME
+ );
+ delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher);
+ slasherImplementation = new Slasher(strategyManager, delegationManager);
+ eigenPodManagerImplementation = new EigenPodManager(
+ IETHPOSDeposit(ETHPOSDepositAddress),
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegationManager
+ );
+ delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
+ }
+
+ function _simulateUpgrade() internal {
+
+ vm.startPrank(executorMultisig);
+
+ // First, upgrade the proxy contracts to point to the implementations
+ // AVSDirectory
+ // eigenLayerProxyAdmin.upgrade(
+ // TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ // address(avsDirectoryImplementation)
+ // );
+ // DelegationManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delegationManager))),
+ address(delegationManagerImplementation)
+ );
+ // StrategyManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation)
+ );
+ // Slasher
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ address(slasherImplementation)
+ );
+ // EigenPodManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation)
+ );
+ // Delayed Withdrawal Router
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ address(delayedWithdrawalRouterImplementation)
+ );
+
+ // Second, configure additional settings and paused statuses
+ delegationManager.setMinWithdrawalDelayBlocks(DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS);
+ delegationManager.unpause(0);
+ eigenPodManager.unpause(0);
+
+ eigenPodManager.setDenebForkTimestamp(EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP);
+ eigenPodManager.updateBeaconChainOracle(beaconOracle);
+ eigenPodBeacon.upgradeTo(address(eigenPodImplementation));
+
+ vm.stopPrank();
+ }
+}
+
+// forge t --mt test_queueUpgrade --fork-url $RPC_MAINNET -vvvv
+contract Queue_M2_Upgrade is M2_Mainnet_Upgrade, TimelockEncoding {
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ // Thurs Apr 08 2024 12:00:00 GMT-0700 (Pacific Daylight Time)
+ uint256 timelockEta = 1712559600;
+
+ function test_queueUpgrade() external {
+ _parseDeployedContracts("script/output/mainnet/M2_mainnet_upgrade.output.json");
+ _parseInitialDeploymentParams("script/configs/mainnet/M2_mainnet_upgrade.config.json");
+
+ Tx[] memory txs = new Tx[](11);
+ // upgrade the DelegationManager, Slasher, StrategyManager, DelayedWithdrawalRouter, EigenPodManager, & EigenPod contracts
+ txs[0] = Tx(
+ address(eigenLayerProxyAdmin),
+ 0,
+ abi.encodeWithSelector(
+ ProxyAdmin.upgrade.selector,
+ TransparentUpgradeableProxy(payable(address(delegationManager))),
+ delegationManagerImplementation
+ )
+ );
+
+ txs[1] = Tx(
+ address(eigenLayerProxyAdmin),
+ 0,
+ abi.encodeWithSelector(
+ ProxyAdmin.upgrade.selector,
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ slasherImplementation
+ )
+ );
+
+ txs[2] = Tx(
+ address(eigenLayerProxyAdmin),
+ 0,
+ abi.encodeWithSelector(
+ ProxyAdmin.upgrade.selector,
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ strategyManagerImplementation
+ )
+ );
+
+ txs[3] = Tx(
+ address(eigenLayerProxyAdmin),
+ 0,
+ abi.encodeWithSelector(
+ ProxyAdmin.upgrade.selector,
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ delayedWithdrawalRouterImplementation
+ )
+ );
+
+ txs[4] = Tx(
+ address(eigenLayerProxyAdmin),
+ 0,
+ abi.encodeWithSelector(
+ ProxyAdmin.upgrade.selector,
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ eigenPodManagerImplementation
+ )
+ );
+
+ txs[5] = Tx(
+ address(eigenPodBeacon),
+ 0,
+ abi.encodeWithSelector(
+ UpgradeableBeacon.upgradeTo.selector,
+ eigenPodImplementation
+ )
+ );
+
+ // set the min withdrawal delay blocks on the DelegationManager
+ txs[6] = Tx(
+ address(delegationManager),
+ 0, // value
+ abi.encodeWithSelector(DelegationManager.setMinWithdrawalDelayBlocks.selector, DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS)
+ );
+
+ // set beacon chain oracle on EigenPodManager
+ txs[7] = Tx(
+ address(eigenPodManager),
+ 0, // value
+ abi.encodeWithSelector(EigenPodManager.updateBeaconChainOracle.selector, beaconOracle)
+ );
+
+ // set Deneb fork timestamp on EigenPodManager
+ txs[8] = Tx(
+ address(eigenPodManager),
+ 0, // value
+ abi.encodeWithSelector(EigenPodManager.setDenebForkTimestamp.selector, EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP)
+ );
+
+ // unpause everything on DelegationManager
+ txs[9] = Tx(
+ address(delegationManager),
+ 0, // value
+ abi.encodeWithSelector(Pausable.unpause.selector, 0)
+ );
+
+ // unpause everything on EigenPodManager
+ txs[10] = Tx(
+ address(eigenPodManager),
+ 0, // value
+ abi.encodeWithSelector(Pausable.unpause.selector, 0)
+ );
+
+ bytes memory calldata_to_multisend_contract = abi.encodeWithSelector(MultiSendCallOnly.multiSend.selector, encodeMultisendTxs(txs));
+ emit log_named_bytes("calldata_to_multisend_contract", calldata_to_multisend_contract);
+
+ bytes memory final_calldata_to_executor_multisig = encodeForExecutor({
+ // call to executor will be from the timelock
+ from: timelock,
+ // performing many operations at the same time
+ to: multiSendCallOnly,
+ // value to send in tx
+ value: 0,
+ // calldata for the operation
+ data: calldata_to_multisend_contract,
+ // operation type (for performing many operations at the same time)
+ operation: ISafe.Operation.DelegateCall
+ });
+
+ (bytes memory calldata_to_timelock_queuing_action, bytes memory calldata_to_timelock_executing_action) = encodeForTimelock({
+ // address to be called from the timelock
+ to: executorMultisig,
+ // value to send in tx
+ value: 0,
+ // calldata for the operation
+ data: final_calldata_to_executor_multisig,
+ // time at which the tx will become executable
+ timelockEta: timelockEta
+ });
+
+ bytes32 expectedTxHash = getTxHash({
+ target: executorMultisig,
+ _value: 0,
+ _data: final_calldata_to_executor_multisig,
+ eta: timelockEta
+ });
+ emit log_named_bytes32("expectedTxHash", expectedTxHash);
+
+ cheats.prank(operationsMultisig);
+ (bool success, ) = timelock.call(calldata_to_timelock_queuing_action);
+ require(success, "call to timelock queuing action failed");
+
+ require(ITimelock(timelock).queuedTransactions(expectedTxHash), "expectedTxHash not queued");
+
+ // test performing the upgrade
+ cheats.warp(timelockEta);
+ cheats.prank(operationsMultisig);
+ (success, ) = timelock.call(calldata_to_timelock_executing_action);
+ require(success, "call to timelock executing action failed");
+
+ // Check correctness after upgrade
+ _verifyContractPointers();
+ _verifyImplementations();
+ _verifyContractsInitialized({isInitialDeployment: true});
+ _verifyInitializationParams();
+ _postUpgradeChecks();
+ }
+
+ function _postUpgradeChecks() internal {
+ // check that LST deposits are paused
+ address rETH = 0xae78736Cd615f374D3085123A210448E74Fc6393;
+ address rETH_Strategy = 0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2;
+ uint256 amount = 1e18;
+ cheats.prank(rETH);
+ // this works because rETH has more than 1 ETH of its own token at its address :)
+ IERC20(rETH).transfer(address(this), amount);
+ IERC20(rETH).approve(address(strategyManager), amount);
+ cheats.expectRevert("Pausable: index is paused");
+ strategyManager.depositIntoStrategy({
+ strategy: IStrategy(rETH_Strategy),
+ token: IERC20(rETH),
+ amount: amount
+ });
+
+ // unpause LST deposits and check that a deposit works
+ cheats.prank(executorMultisig);
+ strategyManager.unpause(0);
+ strategyManager.depositIntoStrategy({
+ strategy: IStrategy(rETH_Strategy),
+ token: IERC20(rETH),
+ amount: amount
+ });
+
+ // check that EigenPod proofs are live (although this still reverts later in the call)
+ EigenPod existingEigenPod = EigenPod(payable(0x0b347D5E38296277E829CE1D8C6b82e4c63C2Df3));
+ BeaconChainProofs.StateRootProof memory stateRootProof;
+ uint40[] memory validatorIndices;
+ bytes[] memory validatorFieldsProofs;
+ bytes32[][] memory validatorFields;
+ cheats.startPrank(existingEigenPod.podOwner());
+ existingEigenPod.activateRestaking();
+ cheats.expectRevert("EigenPodManager.getBlockRootAtTimestamp: state root at timestamp not yet finalized");
+ existingEigenPod.verifyWithdrawalCredentials(
+ uint64(block.timestamp),
+ stateRootProof,
+ validatorIndices,
+ validatorFieldsProofs,
+ validatorFields
+ );
+ }
+
+ function getTxHash(address target, uint256 _value, bytes memory _data, uint256 eta) public pure returns (bytes32) {
+ // empty bytes
+ bytes memory signature;
+ bytes32 txHash = keccak256(abi.encode(target, _value, signature, _data, eta));
+ return txHash;
+ }
+}
diff --git a/script/misc/DeployGoerliStrategy.s.sol b/script/misc/DeployGoerliStrategy.s.sol
deleted file mode 100644
index 1a59578f66..0000000000
--- a/script/misc/DeployGoerliStrategy.s.sol
+++ /dev/null
@@ -1,53 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
-import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
-import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
-
-import "../../src/contracts/strategies/StrategyBase.sol";
-import "../../src/contracts/permissions/PauserRegistry.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/misc/DeployStrategy.s.sol:DeployStrategy --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
-
-// NOTE: ONLY WORKS ON GOERLI
-contract DeployStrategy is Script, Test {
- Vm cheats = Vm(HEVM_ADDRESS);
-
- // EigenLayer Contracts
- ProxyAdmin public eigenLayerProxyAdmin = ProxyAdmin(0x28ceac2ff82B2E00166e46636e2A4818C29902e2);
- PauserRegistry public eigenLayerPauserReg = PauserRegistry(0x7cB9c5D6b9702f2f680e4d35cb1fC945D08208F6);
- StrategyBase public baseStrategyImplementation = StrategyBase(0x9d7eD45EE2E8FC5482fa2428f15C971e6369011d);
-
- function run() external {
- // read and log the chainID
- uint256 chainId = block.chainid;
- emit log_named_uint("You are deploying on ChainID", chainId);
-
- address tokenAddress = 0x3ecCAdA3e11c1Cc3e9B5a53176A67cc3ABDD3E46;
-
- vm.startBroadcast();
-
- // create upgradeable proxies that each point to the implementation and initialize them
- address strategy = address(
- new TransparentUpgradeableProxy(
- address(baseStrategyImplementation),
- address(eigenLayerProxyAdmin),
- abi.encodeWithSelector(StrategyBase.initialize.selector, IERC20(tokenAddress), eigenLayerPauserReg)
- )
- );
-
- vm.stopBroadcast();
-
- emit log_named_address("Strategy", strategy);
-
- }
-}
\ No newline at end of file
diff --git a/script/output/M2_deployment_data.json b/script/output/M2_deployment_data.json
deleted file mode 100644
index acb160e9b0..0000000000
--- a/script/output/M2_deployment_data.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "addresses": {
- "beaconChainOracle": "0x90193C961A926261B756D1E5bb255e67ff9498A1"
- },
- "chainInfo": {
- "chainId": 31337,
- "deploymentBlock": 1
- }
-}
\ No newline at end of file
diff --git a/script/output/M1_MOCK_deployment_data.json b/script/output/devnet/M1_MOCK_deployment_data.json
similarity index 100%
rename from script/output/M1_MOCK_deployment_data.json
rename to script/output/devnet/M1_MOCK_deployment_data.json
diff --git a/script/output/devnet/M2_from_scratch_deployment_data.json b/script/output/devnet/M2_from_scratch_deployment_data.json
new file mode 100644
index 0000000000..693ddc287d
--- /dev/null
+++ b/script/output/devnet/M2_from_scratch_deployment_data.json
@@ -0,0 +1,34 @@
+{
+ "addresses": {
+ "baseStrategyImplementation": "0x5207CfA0166E8de0FCdFd78B4d17b68587bE306d",
+ "blsPublicKeyCompendium": "0x970670459734a83899773A0fd45941B5afC1200e",
+ "delayedWithdrawalRouter": "0xD718d5A27a29FF1cD22403426084bA0d479869a0",
+ "delayedWithdrawalRouterImplementation": "0x1c23A6d89F95ef3148BCDA8E242cAb145bf9c0E4",
+ "delegation": "0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76",
+ "delegationImplementation": "0xd21060559c9beb54fC07aFd6151aDf6cFCDDCAeB",
+ "eigenLayerPauserReg": "0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496",
+ "eigenLayerProxyAdmin": "0x90193C961A926261B756D1E5bb255e67ff9498A1",
+ "eigenPodBeacon": "0x416C42991d05b31E9A6dC209e91AD22b79D87Ae6",
+ "eigenPodImplementation": "0x4f559F30f5eB88D635FDe1548C4267DB8FaB0351",
+ "eigenPodManager": "0xDEb1E9a6Be7Baf84208BB6E10aC9F9bbE1D70809",
+ "eigenPodManagerImplementation": "0x8B71b41D4dBEb2b6821d44692d3fACAAf77480Bb",
+ "emptyContract": "0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3",
+ "slasher": "0x62c20Aa1e0272312BC100b4e23B4DC1Ed96dD7D1",
+ "slasherImplementation": "0x978e3286EB805934215a88694d80b09aDed68D90",
+ "strategies": {
+ "Coinbase Wrapped Staked ETH": "0x39Af23E00F1e662025aA01b0cEdA19542B78DF99",
+ "Liquid staked Ether 2.0": "0xEF179756ea6525AFade217cA5aB0b1b5CfE0fd92",
+ "Rocket Pool ETH": "0xd6EAF4c146261653EE059077B78ED088Add54309"
+ },
+ "strategyManager": "0x50EEf481cae4250d252Ae577A09bF514f224C6C4",
+ "strategyManagerImplementation": "0x4C52a6277b1B84121b3072C0c92b6Be0b7CC10F1"
+ },
+ "chainInfo": {
+ "chainId": 31337,
+ "deploymentBlock": 1
+ },
+ "parameters": {
+ "executorMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
+ "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90"
+ }
+}
\ No newline at end of file
diff --git a/script/output/goerli/GV2_deployment_2024_6_2.json b/script/output/goerli/GV2_deployment_2024_6_2.json
new file mode 100644
index 0000000000..e68db2428a
--- /dev/null
+++ b/script/output/goerli/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/goerli/GV2_preprod_deployment_2024_30_1.json b/script/output/goerli/GV2_preprod_deployment_2024_30_1.json
new file mode 100644
index 0000000000..f705e6f9f5
--- /dev/null
+++ b/script/output/goerli/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/M1_deployment_goerli_2023_3_23.json b/script/output/goerli/M1_deployment_goerli_2023_3_23.json
similarity index 100%
rename from script/output/M1_deployment_goerli_2023_3_23.json
rename to script/output/goerli/M1_deployment_goerli_2023_3_23.json
diff --git a/script/output/goerli/M2_deployment_data_goerli.json b/script/output/goerli/M2_deployment_data_goerli.json
new file mode 100644
index 0000000000..45acc38f22
--- /dev/null
+++ b/script/output/goerli/M2_deployment_data_goerli.json
@@ -0,0 +1,19 @@
+{
+ "addresses": {
+ "delayedWithdrawalRouter": "0x89581561f1F98584F88b0d57c2180fb89225388f",
+ "delegation": "0x1b7b8F6b258f95Cf9596EabB9aa18B62940Eb0a8",
+ "delegationImplementation": "0x9b7980a32ceCe2Aa936DD2E43AF74af62581A99d",
+ "eigenPodBeacon": "0x3093F3B560352F896F0e9567019902C9Aff8C9a5",
+ "eigenPodImplementation": "0x86bf376E0C0c9c6D332E13422f35Aca75C106CcA",
+ "eigenPodManager": "0xa286b84C96aF280a49Fe1F40B9627C2A2827df41",
+ "eigenPodManagerImplementation": "0xdD09d95bD25299EDBF4f33d76F84dBc77b0B901b",
+ "ethPOS": "0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b",
+ "slasher": "0xD11d60b669Ecf7bE10329726043B3ac07B380C22",
+ "strategyManager": "0x779d1b5315df083e3F9E94cB495983500bA8E907",
+ "strategyManagerImplementation": "0x8676bb5f792ED407a237234Fe422aC6ed3540055"
+ },
+ "chainInfo": {
+ "chainId": 5,
+ "deploymentBlock": 10002668
+ }
+}
\ No newline at end of file
diff --git a/script/output/goerli/M2_preprod_deployment_from_scratch.json b/script/output/goerli/M2_preprod_deployment_from_scratch.json
new file mode 100644
index 0000000000..0ac97a9e9d
--- /dev/null
+++ b/script/output/goerli/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/output/goerli/deployment_output.json b/script/output/goerli/deployment_output.json
new file mode 100644
index 0000000000..9f362dbd18
--- /dev/null
+++ b/script/output/goerli/deployment_output.json
@@ -0,0 +1,29 @@
+{
+ "addresses": {
+ "ERC20Mock": "0x7ad75e99869026FE521f34d1239AD633463bA520",
+ "ERC20MockStrategy": "0x3FF9067f06c7833560d2d669fa58D6b1b788EcF0",
+ "ERC20MockStrategyImplementation": "0xE0411693E86760840B6Ee90004b0C248ab5c9631",
+ "delayedWithdrawalRouter": "0x91BbcEd2DB7778c569Fbab34A3957f5ded92bb2d",
+ "delayedWithdrawalRouterImplementation": "0x007F25A938173F0995daB8e7806aC8b6EbfB7808",
+ "delegation": "0x1b0870C6a7472ED9Da774b4Ca0Fe1b5fd6B6D61E",
+ "delegationImplementation": "0x9cCb6f6BC4e7641Cd6d5E7BD7e97f55D9914AaAb",
+ "eigenLayerPauserReg": "0x18E5227d0E8D8053579d5c1eD6bbd7DD55139454",
+ "eigenLayerProxyAdmin": "0x81048ca94171C7B97ff0fE590eF67f4B442eD548",
+ "eigenPodBeacon": "0x4BE52ac49121421A9AF33c476f7f6511Fbf4fCc7",
+ "eigenPodImplementation": "0xeb873028bA8d079768F11C71b05564D1590238A5",
+ "eigenPodManager": "0x83622B4e84Daadd0AF1382caE2F8Aa2C67839D9e",
+ "eigenPodManagerImplementation": "0x009030ab40Db41F9D9336DfED1698D8FFeB6a604",
+ "emptyContract": "0xbeC65eD486c151202EF673A456e6d8e446726Df6",
+ "slasher": "0x5B617a19d39Ed8c0754fA31Ef86e6c398Ba1a24E",
+ "slasherImplementation": "0x8122eD67A26D835349438286FBd0A9cbA6841332",
+ "strategyManager": "0x5d55B8fDC847c1DF56d1dDd8E278424124199EC3",
+ "strategyManagerImplementation": "0xC69229bf9E6bb82FfB31fA2fdcEF5431b3a81453"
+ },
+ "chainInfo": {
+ "chainId": 5,
+ "deploymentBlock": 9548171
+ },
+ "parameters": {
+ "alphaMultisig": "0x95C7A3F90e80329C97A6493142Ab7923E15b8083"
+ }
+}
\ No newline at end of file
diff --git a/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json b/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json
new file mode 100644
index 0000000000..9482010cb6
--- /dev/null
+++ b/script/output/mainnet/M1_deployment_mainnet_2023_6_9.json
@@ -0,0 +1,39 @@
+{
+ "addresses": {
+ "avsDirectory": "0x0000000000000000000000000000000000000000",
+ "avsDirectoryImplementation": "0x0000000000000000000000000000000000000000",
+ "baseStrategyImplementation": "0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3",
+ "beaconOracle": "0x0000000000000000000000000000000000000000",
+ "delayedWithdrawalRouter": "0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8",
+ "delayedWithdrawalRouterImplementation": "0x44Bcb0E01CD0C5060D4Bb1A07b42580EF983E2AF",
+ "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A",
+ "delegationManagerImplementation": "0xf97E97649Da958d290e84E6D571c32F4b7F475e4",
+ "eigenLayerPauserReg": "0x0c431C66F4dE941d089625E5B423D00707977060",
+ "eigenLayerProxyAdmin": "0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444",
+ "eigenPodBeacon": "0x5a2a4F2F3C18f09179B6703e63D9eDD165909073",
+ "eigenPodImplementation": "0x5c86e9609fbBc1B754D0FD5a4963Fdf0F5b99dA7",
+ "eigenPodManager": "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338",
+ "eigenPodManagerImplementation": "0xEB86a5c40FdE917E6feC440aBbCDc80E3862e111",
+ "emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9",
+ "slasher": "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd",
+ "slasherImplementation": "0xef31c292801f24f16479DD83197F1E6AeBb8d6d8",
+ "strategies": {
+ "cbETH": "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc",
+ "stETH": "0x93c4b944D05dfe6df7645A86cd2206016c51564D",
+ "rETH": "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2"
+ },
+ "strategyManager": "0x858646372CC42E1A627fcE94aa7A7033e7CF075A",
+ "strategyManagerImplementation": "0x5d25EEf8CfEdaA47d31fE2346726dE1c21e342Fb"
+ },
+ "chainInfo": {
+ "chainId": 1,
+ "deploymentBlock": 17445559
+ },
+ "parameters": {
+ "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598",
+ "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111",
+ "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
+ "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390",
+ "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF"
+ }
+}
\ No newline at end of file
diff --git a/script/output/mainnet/M2_mainnet_upgrade.output.json b/script/output/mainnet/M2_mainnet_upgrade.output.json
new file mode 100644
index 0000000000..e9de0f32ef
--- /dev/null
+++ b/script/output/mainnet/M2_mainnet_upgrade.output.json
@@ -0,0 +1,35 @@
+{
+ "addresses": {
+ "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF",
+ "avsDirectoryImplementation": "0xdAbdB3Cd346B7D5F5779b0B614EdE1CC9DcBA5b7",
+ "baseStrategyImplementation": "0xdfdA04f980bE6A64E3607c95Ca26012Ab9aA46d3",
+ "beaconOracle": "0x343907185b71aDF0eBa9567538314396aa985442",
+ "delayedWithdrawalRouter": "0x7Fe7E9CC0F274d2435AD5d56D5fa73E47F6A23D8",
+ "delayedWithdrawalRouterImplementation": "0x4bB6731B02314d40aBbfFBC4540f508874014226",
+ "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A",
+ "delegationManagerImplementation": "0x1784BE6401339Fc0Fedf7E9379409f5c1BfE9dda",
+ "eigenLayerPauserReg": "0x0c431C66F4dE941d089625E5B423D00707977060",
+ "eigenLayerProxyAdmin": "0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444",
+ "eigenPodBeacon": "0x5a2a4F2F3C18f09179B6703e63D9eDD165909073",
+ "eigenPodImplementation": "0x8bA40dA60f0827d027F029aCEE62609F0527a255",
+ "eigenPodManager": "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338",
+ "eigenPodManagerImplementation": "0xe4297e3DaDBc7D99e26a2954820f514CB50C5762",
+ "emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9",
+ "slasher": "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd",
+ "slasherImplementation": "0xF3234220163a757edf1E11a8a085638D9B236614",
+ "strategies": "",
+ "strategyManager": "0x858646372CC42E1A627fcE94aa7A7033e7CF075A",
+ "strategyManagerImplementation": "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b"
+ },
+ "chainInfo": {
+ "chainId": 1,
+ "deploymentBlock": 19492753
+ },
+ "parameters": {
+ "communityMultisig": "0xFEA47018D632A77bA579846c840d5706705Dc598",
+ "executorMultisig": "0x369e6F597e22EaB55fFb173C6d9cD234BD699111",
+ "operationsMultisig": "0xBE1685C81aA44FF9FB319dD389addd9374383e90",
+ "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390",
+ "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF"
+ }
+}
\ No newline at end of file
diff --git a/script/utils/Allocator.sol b/script/utils/Allocator.sol
deleted file mode 100644
index d7f27b8e42..0000000000
--- a/script/utils/Allocator.sol
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-
-contract Allocator {
-
- function allocate(IERC20 token, address[] memory recipients, uint256 amount) public {
- token.transferFrom(msg.sender, address(this), recipients.length * amount);
- for (uint i = 0; i < recipients.length; i++) {
- token.transfer(recipients[i], amount);
- }
- }
-}
diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol
index d0c0c9a057..39fe0612f9 100644
--- a/script/utils/ExistingDeploymentParser.sol
+++ b/script/utils/ExistingDeploymentParser.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
@@ -8,8 +8,10 @@ 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";
+import "../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
import "../../src/contracts/pods/EigenPod.sol";
import "../../src/contracts/pods/EigenPodManager.sol";
@@ -22,34 +24,90 @@ import "../../src/test/mocks/EmptyContract.sol";
import "forge-std/Script.sol";
import "forge-std/Test.sol";
-contract ExistingDeploymentParser is Script, Test {
+struct StrategyUnderlyingTokenConfig {
+ address tokenAddress;
+ string tokenName;
+ string tokenSymbol;
+}
+struct DeployedEigenPods {
+ address[] multiValidatorPods;
+ address[] singleValidatorPods;
+ address[] inActivePods;
+}
+
+contract ExistingDeploymentParser is Script, Test {
// EigenLayer Contracts
ProxyAdmin public eigenLayerProxyAdmin;
PauserRegistry public eigenLayerPauserReg;
Slasher public slasher;
Slasher public slasherImplementation;
- DelegationManager public delegation;
- DelegationManager public delegationImplementation;
+ AVSDirectory public avsDirectory;
+ AVSDirectory public avsDirectoryImplementation;
+ DelegationManager public delegationManager;
+ DelegationManager public delegationManagerImplementation;
StrategyManager public strategyManager;
StrategyManager public strategyManagerImplementation;
EigenPodManager public eigenPodManager;
EigenPodManager public eigenPodManagerImplementation;
DelayedWithdrawalRouter public delayedWithdrawalRouter;
DelayedWithdrawalRouter public delayedWithdrawalRouterImplementation;
+ IBeaconChainOracle beaconOracle;
UpgradeableBeacon public eigenPodBeacon;
EigenPod public eigenPodImplementation;
StrategyBase public baseStrategyImplementation;
+ // EigenPods deployed
+ address[] public multiValidatorPods;
+ address[] public singleValidatorPods;
+ address[] public inActivePods;
+ // All eigenpods is just single array list of above eigenPods
+ address[] public allEigenPods;
+
EmptyContract public emptyContract;
address executorMultisig;
address operationsMultisig;
+ address communityMultisig;
+ address pauserMultisig;
+ address timelock;
// strategies deployed
+ uint256 numStrategiesDeployed;
StrategyBase[] public deployedStrategyArray;
+ // Strategies to Deploy
+ uint256 numStrategiesToDeploy;
+ StrategyUnderlyingTokenConfig[] public strategiesToDeploy;
+
+ /// @notice Initialization Params for first initial deployment scripts
+ // StrategyManager
+ uint256 STRATEGY_MANAGER_INIT_PAUSED_STATUS;
+ address STRATEGY_MANAGER_WHITELISTER;
+ // SLasher
+ uint256 SLASHER_INIT_PAUSED_STATUS;
+ // DelegationManager
+ uint256 DELEGATION_MANAGER_INIT_PAUSED_STATUS;
+ uint256 DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS;
+ // AVSDirectory
+ uint256 AVS_DIRECTORY_INIT_PAUSED_STATUS;
+ // EigenPodManager
+ uint256 EIGENPOD_MANAGER_INIT_PAUSED_STATUS;
+ uint64 EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP;
+ // EigenPod
+ uint64 EIGENPOD_GENESIS_TIME;
+ uint64 EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ address ETHPOSDepositAddress;
+ uint256 DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS;
+
+ // one week in blocks -- 50400
+ uint32 DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS;
- function _parseDeployedContracts(string memory existingDeploymentInfoPath) internal {
+ // Strategy Deployment
+ uint256 STRATEGY_MAX_PER_DEPOSIT;
+ uint256 STRATEGY_MAX_TOTAL_DEPOSITS;
+
+ /// @notice use for parsing already deployed EigenLayer contracts
+ function _parseDeployedContracts(string memory existingDeploymentInfoPath) internal virtual {
// read and log the chainID
uint256 currentChainId = block.chainid;
emit log_named_uint("You are parsing on ChainID", currentChainId);
@@ -64,38 +122,571 @@ contract ExistingDeploymentParser is Script, Test {
// read all of the deployed addresses
executorMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.executorMultisig");
operationsMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.operationsMultisig");
-
- eigenLayerProxyAdmin = ProxyAdmin(stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerProxyAdmin"));
- eigenLayerPauserReg = PauserRegistry(stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerPauserReg"));
+ communityMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.communityMultisig");
+ pauserMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.pauserMultisig");
+ timelock = stdJson.readAddress(existingDeploymentData, ".parameters.timelock");
+
+ eigenLayerProxyAdmin = ProxyAdmin(
+ stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerProxyAdmin")
+ );
+ eigenLayerPauserReg = PauserRegistry(
+ stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerPauserReg")
+ );
slasher = Slasher(stdJson.readAddress(existingDeploymentData, ".addresses.slasher"));
- slasherImplementation = Slasher(stdJson.readAddress(existingDeploymentData, ".addresses.slasherImplementation"));
- delegation = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegation"));
- delegationImplementation = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegationImplementation"));
+ slasherImplementation = Slasher(
+ stdJson.readAddress(existingDeploymentData, ".addresses.slasherImplementation")
+ );
+ delegationManager = DelegationManager(stdJson.readAddress(existingDeploymentData, ".addresses.delegationManager"));
+ delegationManagerImplementation = DelegationManager(
+ stdJson.readAddress(existingDeploymentData, ".addresses.delegationManagerImplementation")
+ );
+ 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"));
+ strategyManagerImplementation = StrategyManager(
+ stdJson.readAddress(existingDeploymentData, ".addresses.strategyManagerImplementation")
+ );
eigenPodManager = EigenPodManager(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodManager"));
- eigenPodManagerImplementation = EigenPodManager(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodManagerImplementation"));
- delayedWithdrawalRouter = DelayedWithdrawalRouter(stdJson.readAddress(existingDeploymentData, ".addresses.delayedWithdrawalRouter"));
- delayedWithdrawalRouterImplementation =
- DelayedWithdrawalRouter(stdJson.readAddress(existingDeploymentData, ".addresses.delayedWithdrawalRouterImplementation"));
+ eigenPodManagerImplementation = EigenPodManager(
+ stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodManagerImplementation")
+ );
+ delayedWithdrawalRouter = DelayedWithdrawalRouter(
+ stdJson.readAddress(existingDeploymentData, ".addresses.delayedWithdrawalRouter")
+ );
+ delayedWithdrawalRouterImplementation = DelayedWithdrawalRouter(
+ stdJson.readAddress(existingDeploymentData, ".addresses.delayedWithdrawalRouterImplementation")
+ );
+ beaconOracle = IBeaconChainOracle(
+ stdJson.readAddress(existingDeploymentData, ".addresses.beaconOracle")
+ );
eigenPodBeacon = UpgradeableBeacon(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodBeacon"));
- eigenPodImplementation = EigenPod(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodImplementation"));
- baseStrategyImplementation = StrategyBase(stdJson.readAddress(existingDeploymentData, ".addresses.baseStrategyImplementation"));
+ eigenPodImplementation = EigenPod(
+ payable(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodImplementation"))
+ );
+ baseStrategyImplementation = StrategyBase(
+ stdJson.readAddress(existingDeploymentData, ".addresses.baseStrategyImplementation")
+ );
emptyContract = EmptyContract(stdJson.readAddress(existingDeploymentData, ".addresses.emptyContract"));
- /*
- commented out -- needs JSON formatting of the form:
- strategies": [
- {"WETH": "0x7CA911E83dabf90C90dD3De5411a10F1A6112184"},
- {"rETH": "0x879944A8cB437a5f8061361f82A6d4EED59070b5"},
- {"tsETH": "0xcFA9da720682bC4BCb55116675f16F503093ba13"},
- {"wstETH": "0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff"}]
- // load strategy list
- bytes memory strategyListRaw = stdJson.parseRaw(existingDeploymentData, ".addresses.strategies");
- address[] memory strategyList = abi.decode(strategyListRaw, (address[]));
- for (uint256 i = 0; i < strategyList.length; ++i) {
- deployedStrategyArray.push(StrategyBase(strategyList[i]));
+ // Strategies Deployed, load strategy list
+ numStrategiesDeployed = stdJson.readUint(existingDeploymentData, ".numStrategies");
+ for (uint256 i = 0; i < numStrategiesDeployed; ++i) {
+ // Form the key for the current element
+ string memory key = string.concat(".addresses.strategyAddresses[", vm.toString(i), "]");
+
+ // Use the key and parse the strategy address
+ address strategyAddress = abi.decode(stdJson.parseRaw(existingDeploymentData, key), (address));
+ deployedStrategyArray.push(StrategyBase(strategyAddress));
}
- */
+ }
+
+ function _parseDeployedEigenPods(string memory existingDeploymentInfoPath) internal returns (DeployedEigenPods memory) {
+ uint256 currentChainId = block.chainid;
+
+ // READ JSON CONFIG DATA
+ string memory existingDeploymentData = vm.readFile(existingDeploymentInfoPath);
+
+ // check that the chainID matches the one in the config
+ uint256 configChainId = stdJson.readUint(existingDeploymentData, ".chainInfo.chainId");
+ require(configChainId == currentChainId, "You are on the wrong chain for this config");
+
+ multiValidatorPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.multiValidatorPods");
+ singleValidatorPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.singleValidatorPods");
+ inActivePods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.inActivePods");
+ allEigenPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.allEigenPods");
+ return DeployedEigenPods({
+ multiValidatorPods: multiValidatorPods,
+ singleValidatorPods: singleValidatorPods,
+ inActivePods: inActivePods
+ });
+ }
+
+ /// @notice use for deploying a new set of EigenLayer contracts
+ /// Note that this does require multisigs to already be deployed
+ function _parseInitialDeploymentParams(string memory initialDeploymentParamsPath) internal virtual {
+ // read and log the chainID
+ uint256 currentChainId = block.chainid;
+ emit log_named_uint("You are parsing on ChainID", currentChainId);
+
+ // READ JSON CONFIG DATA
+ string memory initialDeploymentData = vm.readFile(initialDeploymentParamsPath);
+
+ // check that the chainID matches the one in the config
+ uint256 configChainId = stdJson.readUint(initialDeploymentData, ".chainInfo.chainId");
+ require(configChainId == currentChainId, "You are on the wrong chain for this config");
+
+ // read beacon oracle
+ beaconOracle = IBeaconChainOracle(stdJson.readAddress(initialDeploymentData, ".beaconOracleAddress"));
+
+ // read all of the deployed addresses
+ executorMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.executorMultisig");
+ operationsMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.operationsMultisig");
+ communityMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.communityMultisig");
+ pauserMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.pauserMultisig");
+
+ // Strategies to Deploy, load strategy list
+ numStrategiesToDeploy = stdJson.readUint(initialDeploymentData, ".strategies.numStrategies");
+ STRATEGY_MAX_PER_DEPOSIT = stdJson.readUint(initialDeploymentData, ".strategies.MAX_PER_DEPOSIT");
+ STRATEGY_MAX_TOTAL_DEPOSITS = stdJson.readUint(initialDeploymentData, ".strategies.MAX_TOTAL_DEPOSITS");
+ for (uint256 i = 0; i < numStrategiesToDeploy; ++i) {
+ // Form the key for the current element
+ string memory key = string.concat(".strategies.strategiesToDeploy[", vm.toString(i), "]");
+
+ // Use parseJson with the key to get the value for the current element
+ bytes memory tokenInfoBytes = stdJson.parseRaw(initialDeploymentData, key);
+
+ // Decode the token information into the Token struct
+ StrategyUnderlyingTokenConfig memory tokenInfo = abi.decode(
+ tokenInfoBytes,
+ (StrategyUnderlyingTokenConfig)
+ );
+
+ strategiesToDeploy.push(tokenInfo);
+ }
+
+ // Read initialize params for upgradeable contracts
+ STRATEGY_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(
+ initialDeploymentData,
+ ".strategyManager.init_paused_status"
+ );
+ STRATEGY_MANAGER_WHITELISTER = stdJson.readAddress(initialDeploymentData, ".strategyManager.init_strategy_whitelister");
+ // Slasher
+ SLASHER_INIT_PAUSED_STATUS = stdJson.readUint(initialDeploymentData, ".slasher.init_paused_status");
+ // DelegationManager
+ DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS = stdJson.readUint(
+ initialDeploymentData,
+ ".delegationManager.init_minWithdrawalDelayBlocks"
+ );
+ DELEGATION_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(
+ initialDeploymentData,
+ ".delegationManager.init_paused_status"
+ );
+ // AVSDirectory
+ AVS_DIRECTORY_INIT_PAUSED_STATUS = stdJson.readUint(initialDeploymentData, ".avsDirectory.init_paused_status");
+ // EigenPodManager
+ EIGENPOD_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint(
+ initialDeploymentData,
+ ".eigenPodManager.init_paused_status"
+ );
+ EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP = uint64(stdJson.readUint(
+ initialDeploymentData,
+ ".eigenPodManager.deneb_fork_timestamp"
+ ));
+
+ // EigenPod
+ EIGENPOD_GENESIS_TIME = uint64(stdJson.readUint(initialDeploymentData, ".eigenPod.GENESIS_TIME"));
+ EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = uint64(
+ stdJson.readUint(initialDeploymentData, ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR")
+ );
+ ETHPOSDepositAddress = stdJson.readAddress(initialDeploymentData, ".ethPOSDepositAddress");
+ // DelayedWithdrawalRouter
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS = stdJson.readUint(
+ initialDeploymentData,
+ ".delayedWithdrawalRouter.init_paused_status"
+ );
+
+ // both set to one week in blocks 50400
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS = uint32(
+ stdJson.readUint(initialDeploymentData, ".delayedWithdrawalRouter.init_withdrawalDelayBlocks")
+ );
+
+ logInitialDeploymentParams();
+ }
+
+ /// @notice Ensure contracts point at each other correctly via constructors
+ function _verifyContractPointers() internal virtual view {
+ // AVSDirectory
+ require(
+ avsDirectory.delegation() == delegationManager,
+ "avsDirectory: delegationManager address not set correctly"
+ );
+ // DelegationManager
+ require(delegationManager.slasher() == slasher, "delegationManager: slasher address not set correctly");
+ require(
+ delegationManager.strategyManager() == strategyManager,
+ "delegationManager: strategyManager address not set correctly"
+ );
+ require(
+ delegationManager.eigenPodManager() == eigenPodManager,
+ "delegationManager: eigenPodManager address not set correctly"
+ );
+ // StrategyManager
+ require(strategyManager.slasher() == slasher, "strategyManager: slasher address not set correctly");
+ require(
+ strategyManager.delegation() == delegationManager,
+ "strategyManager: delegationManager address not set correctly"
+ );
+ require(
+ strategyManager.eigenPodManager() == eigenPodManager,
+ "strategyManager: eigenPodManager address not set correctly"
+ );
+ // EPM
+ require(
+ address(eigenPodManager.ethPOS()) == ETHPOSDepositAddress,
+ "eigenPodManager: ethPOSDeposit contract address not set correctly"
+ );
+ require(
+ eigenPodManager.eigenPodBeacon() == eigenPodBeacon,
+ "eigenPodManager: eigenPodBeacon contract address not set correctly"
+ );
+ require(
+ eigenPodManager.strategyManager() == strategyManager,
+ "eigenPodManager: strategyManager contract address not set correctly"
+ );
+ require(eigenPodManager.slasher() == slasher, "eigenPodManager: slasher contract address not set correctly");
+ require(
+ eigenPodManager.delegationManager() == delegationManager,
+ "eigenPodManager: delegationManager contract address not set correctly"
+ );
+ // DelayedWithdrawalRouter
+ require(
+ delayedWithdrawalRouter.eigenPodManager() == eigenPodManager,
+ "delayedWithdrawalRouterContract: eigenPodManager address not set correctly"
+ );
+ }
+
+ /// @notice verify implementations for Transparent Upgradeable Proxies
+ function _verifyImplementations() internal virtual view {
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(avsDirectory)))) ==
+ address(avsDirectoryImplementation),
+ "avsDirectory: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(delegationManager)))
+ ) == address(delegationManagerImplementation),
+ "delegationManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(strategyManager)))
+ ) == address(strategyManagerImplementation),
+ "strategyManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(slasher)))) ==
+ address(slasherImplementation),
+ "slasher: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager)))
+ ) == address(eigenPodManagerImplementation),
+ "eigenPodManager: implementation set incorrectly"
+ );
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter)))
+ ) == address(delayedWithdrawalRouterImplementation),
+ "delayedWithdrawalRouter: implementation set incorrectly"
+ );
+
+ for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
+ require(
+ eigenLayerProxyAdmin.getProxyImplementation(
+ TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i])))
+ ) == address(baseStrategyImplementation),
+ "strategy: implementation set incorrectly"
+ );
+ }
+
+ require(
+ eigenPodBeacon.implementation() == address(eigenPodImplementation),
+ "eigenPodBeacon: implementation set incorrectly"
+ );
+ }
+
+ /**
+ * @notice Verify initialization of Transparent Upgradeable Proxies. Also check
+ * initialization params if this is the first deployment.
+ * @param isInitialDeployment True if this is the first deployment of contracts from scratch
+ */
+ function _verifyContractsInitialized(bool isInitialDeployment) internal virtual{
+ // AVSDirectory
+ vm.expectRevert(bytes("Initializable: contract is already initialized"));
+ avsDirectory.initialize(address(0), eigenLayerPauserReg, AVS_DIRECTORY_INIT_PAUSED_STATUS);
+ // DelegationManager
+ vm.expectRevert(bytes("Initializable: contract is already initialized"));
+ IStrategy[] memory initializeStrategiesToSetDelayBlocks = new IStrategy[](0);
+ uint256[] memory initializeWithdrawalDelayBlocks = new uint256[](0);
+ delegationManager.initialize(
+ address(0),
+ eigenLayerPauserReg,
+ 0,
+ 0, // minWithdrawalDelayBLocks
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ );
+ // StrategyManager
+ vm.expectRevert(bytes("Initializable: contract is already initialized"));
+ strategyManager.initialize(address(0), address(0), eigenLayerPauserReg, STRATEGY_MANAGER_INIT_PAUSED_STATUS);
+ // EigenPodManager
+ vm.expectRevert(bytes("Initializable: contract is already initialized"));
+ eigenPodManager.initialize(
+ beaconOracle,
+ address(0),
+ eigenLayerPauserReg,
+ EIGENPOD_MANAGER_INIT_PAUSED_STATUS
+ );
+ // DelayedWithdrawalRouter
+ vm.expectRevert(bytes("Initializable: contract is already initialized"));
+ delayedWithdrawalRouter.initialize(
+ address(0),
+ eigenLayerPauserReg,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS,
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS
+ );
+ // Strategies
+ for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
+ vm.expectRevert(bytes("Initializable: contract is already initialized"));
+ StrategyBaseTVLLimits(address(deployedStrategyArray[i])).initialize(
+ 0,
+ 0,
+ IERC20(address(0)),
+ eigenLayerPauserReg
+ );
+ }
+ }
+
+ /// @notice Verify params based on config constants that are updated from calling `_parseInitialDeploymentParams`
+ function _verifyInitializationParams() internal virtual view {
+ // AVSDirectory
+ require(
+ avsDirectory.pauserRegistry() == eigenLayerPauserReg,
+ "avsdirectory: pauser registry not set correctly"
+ );
+ require(avsDirectory.owner() == executorMultisig, "avsdirectory: owner not set correctly");
+ require(
+ avsDirectory.paused() == AVS_DIRECTORY_INIT_PAUSED_STATUS,
+ "avsdirectory: init paused status set incorrectly"
+ );
+ // DelegationManager
+ require(
+ delegationManager.pauserRegistry() == eigenLayerPauserReg,
+ "delegationManager: pauser registry not set correctly"
+ );
+ require(delegationManager.owner() == executorMultisig, "delegationManager: owner not set correctly");
+ require(
+ delegationManager.paused() == DELEGATION_MANAGER_INIT_PAUSED_STATUS,
+ "delegationManager: init paused status set incorrectly"
+ );
+ require(
+ delegationManager.minWithdrawalDelayBlocks() == DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS,
+ "delegationManager: minWithdrawalDelayBlocks not set correctly"
+ );
+ // StrategyManager
+ require(
+ strategyManager.pauserRegistry() == eigenLayerPauserReg,
+ "strategyManager: pauser registry not set correctly"
+ );
+ require(strategyManager.owner() == executorMultisig, "strategyManager: owner not set correctly");
+ require(
+ strategyManager.paused() == STRATEGY_MANAGER_INIT_PAUSED_STATUS,
+ "strategyManager: init paused status set incorrectly"
+ );
+ require(
+ strategyManager.strategyWhitelister() == operationsMultisig,
+ "strategyManager: strategyWhitelister not set correctly"
+ );
+ // EigenPodManager
+ require(
+ eigenPodManager.pauserRegistry() == eigenLayerPauserReg,
+ "eigenPodManager: pauser registry not set correctly"
+ );
+ require(eigenPodManager.owner() == executorMultisig, "eigenPodManager: owner not set correctly");
+ require(
+ eigenPodManager.paused() == EIGENPOD_MANAGER_INIT_PAUSED_STATUS,
+ "eigenPodManager: init paused status set incorrectly"
+ );
+ require(
+ eigenPodManager.denebForkTimestamp() == EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP,
+ "eigenPodManager: denebForkTimestamp not set correctly"
+ );
+ require(
+ eigenPodManager.beaconChainOracle() == beaconOracle,
+ "eigenPodManager: beaconChainOracle not set correctly"
+ );
+ require(
+ eigenPodManager.ethPOS() == IETHPOSDeposit(ETHPOSDepositAddress),
+ "eigenPodManager: ethPOS not set correctly"
+ );
+ // EigenPodBeacon
+ require(eigenPodBeacon.owner() == executorMultisig, "eigenPodBeacon: owner not set correctly");
+ // EigenPodImplementation
+ require(
+ eigenPodImplementation.GENESIS_TIME() == EIGENPOD_GENESIS_TIME,
+ "eigenPodImplementation: GENESIS TIME not set correctly"
+ );
+ require(
+ eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() ==
+ EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR
+ && EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR % 1 gwei == 0,
+ "eigenPodImplementation: MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR not set correctly"
+ );
+ require(
+ eigenPodImplementation.ethPOS() == IETHPOSDeposit(ETHPOSDepositAddress),
+ "eigenPodImplementation: ethPOS not set correctly"
+ );
+ // DelayedWithdrawalRouter
+ require(
+ delayedWithdrawalRouter.pauserRegistry() == eigenLayerPauserReg,
+ "delayedWithdrawalRouter: pauser registry not set correctly"
+ );
+ require(
+ delayedWithdrawalRouter.owner() == executorMultisig,
+ "delayedWithdrawalRouter: owner not set correctly"
+ );
+ require(
+ delayedWithdrawalRouter.paused() == DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS,
+ "delayedWithdrawalRouter: init paused status set incorrectly"
+ );
+ require(
+ delayedWithdrawalRouter.withdrawalDelayBlocks() == DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS,
+ "delayedWithdrawalRouter: withdrawalDelayBlocks not set correctly"
+ );
+ // Strategies
+ for (uint256 i = 0; i < deployedStrategyArray.length; ++i) {
+ require(
+ deployedStrategyArray[i].pauserRegistry() == eigenLayerPauserReg,
+ "StrategyBaseTVLLimits: pauser registry not set correctly"
+ );
+ require(
+ deployedStrategyArray[i].paused() == 0,
+ "StrategyBaseTVLLimits: init paused status set incorrectly"
+ );
+ }
+
+ // Pausing Permissions
+ require(eigenLayerPauserReg.isPauser(operationsMultisig), "pauserRegistry: operationsMultisig is not pauser");
+ require(eigenLayerPauserReg.isPauser(executorMultisig), "pauserRegistry: executorMultisig is not pauser");
+ require(eigenLayerPauserReg.isPauser(pauserMultisig), "pauserRegistry: pauserMultisig is not pauser");
+ require(eigenLayerPauserReg.unpauser() == executorMultisig, "pauserRegistry: unpauser not set correctly");
+ }
+
+ function logInitialDeploymentParams() public {
+ emit log_string("==== Parsed Initilize Params for Initial Deployment ====");
+
+ emit log_named_address("executorMultisig", executorMultisig);
+ emit log_named_address("operationsMultisig", operationsMultisig);
+ emit log_named_address("communityMultisig", communityMultisig);
+ emit log_named_address("pauserMultisig", pauserMultisig);
+
+ emit log_named_uint("STRATEGY_MANAGER_INIT_PAUSED_STATUS", STRATEGY_MANAGER_INIT_PAUSED_STATUS);
+ emit log_named_address("STRATEGY_MANAGER_WHITELISTER", STRATEGY_MANAGER_WHITELISTER);
+ emit log_named_uint("SLASHER_INIT_PAUSED_STATUS", SLASHER_INIT_PAUSED_STATUS);
+ emit log_named_uint(
+ "DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS",
+ DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS
+ );
+ emit log_named_uint("DELEGATION_MANAGER_INIT_PAUSED_STATUS", DELEGATION_MANAGER_INIT_PAUSED_STATUS);
+ emit log_named_uint("AVS_DIRECTORY_INIT_PAUSED_STATUS", AVS_DIRECTORY_INIT_PAUSED_STATUS);
+ emit log_named_uint("EIGENPOD_MANAGER_INIT_PAUSED_STATUS", EIGENPOD_MANAGER_INIT_PAUSED_STATUS);
+ emit log_named_uint("EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP", EIGENPOD_MANAGER_DENEB_FORK_TIMESTAMP);
+ emit log_named_uint("EIGENPOD_GENESIS_TIME", EIGENPOD_GENESIS_TIME);
+ emit log_named_uint(
+ "EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR",
+ EIGENPOD_MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR
+ );
+ emit log_named_address("ETHPOSDepositAddress", ETHPOSDepositAddress);
+ emit log_named_uint(
+ "DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS",
+ DELAYED_WITHDRAWAL_ROUTER_INIT_PAUSED_STATUS
+ );
+ emit log_named_uint(
+ "DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS",
+ DELAYED_WITHDRAWAL_ROUTER_INIT_WITHDRAWAL_DELAY_BLOCKS
+ );
+
+ emit log_string("==== Strategies to Deploy ====");
+ for (uint256 i = 0; i < numStrategiesToDeploy; ++i) {
+ // Decode the token information into the Token struct
+ StrategyUnderlyingTokenConfig memory tokenInfo = strategiesToDeploy[i];
+
+ strategiesToDeploy.push(tokenInfo);
+ emit log_named_address("TOKEN ADDRESS", tokenInfo.tokenAddress);
+ emit log_named_string("TOKEN NAME", tokenInfo.tokenName);
+ emit log_named_string("TOKEN SYMBOL", tokenInfo.tokenSymbol);
+ }
+ }
+
+ /**
+ * @notice Log contract addresses and write to output json file
+ */
+ function logAndOutputContractAddresses(string memory outputPath) public {
+ // WRITE JSON DATA
+ string memory parent_object = "parent object";
+
+ string memory deployed_strategies = "strategies";
+ for (uint256 i = 0; i < numStrategiesToDeploy; ++i) {
+ vm.serializeAddress(deployed_strategies, strategiesToDeploy[i].tokenSymbol, address(deployedStrategyArray[i]));
+ }
+ string memory deployed_strategies_output = numStrategiesToDeploy == 0
+ ? ""
+ : vm.serializeAddress(
+ deployed_strategies,
+ strategiesToDeploy[numStrategiesToDeploy - 1].tokenSymbol,
+ address(deployedStrategyArray[numStrategiesToDeploy - 1])
+ );
+
+ string memory deployed_addresses = "addresses";
+ vm.serializeAddress(deployed_addresses, "eigenLayerProxyAdmin", address(eigenLayerProxyAdmin));
+ vm.serializeAddress(deployed_addresses, "eigenLayerPauserReg", address(eigenLayerPauserReg));
+ vm.serializeAddress(deployed_addresses, "slasher", address(slasher));
+ vm.serializeAddress(deployed_addresses, "slasherImplementation", address(slasherImplementation));
+ vm.serializeAddress(deployed_addresses, "avsDirectory", address(avsDirectory));
+ vm.serializeAddress(deployed_addresses, "avsDirectoryImplementation", address(avsDirectoryImplementation));
+ vm.serializeAddress(deployed_addresses, "delegationManager", address(delegationManager));
+ vm.serializeAddress(deployed_addresses, "delegationManagerImplementation", address(delegationManagerImplementation));
+ vm.serializeAddress(deployed_addresses, "strategyManager", address(strategyManager));
+ vm.serializeAddress(
+ deployed_addresses,
+ "strategyManagerImplementation",
+ address(strategyManagerImplementation)
+ );
+ vm.serializeAddress(deployed_addresses, "eigenPodManager", address(eigenPodManager));
+ vm.serializeAddress(
+ deployed_addresses,
+ "eigenPodManagerImplementation",
+ address(eigenPodManagerImplementation)
+ );
+ vm.serializeAddress(deployed_addresses, "delayedWithdrawalRouter", address(delayedWithdrawalRouter));
+ vm.serializeAddress(
+ deployed_addresses,
+ "delayedWithdrawalRouterImplementation",
+ address(delayedWithdrawalRouterImplementation)
+ );
+ vm.serializeAddress(deployed_addresses, "beaconOracle", address(beaconOracle));
+ vm.serializeAddress(deployed_addresses, "eigenPodBeacon", address(eigenPodBeacon));
+ vm.serializeAddress(deployed_addresses, "eigenPodImplementation", address(eigenPodImplementation));
+ vm.serializeAddress(deployed_addresses, "baseStrategyImplementation", address(baseStrategyImplementation));
+ vm.serializeAddress(deployed_addresses, "emptyContract", address(emptyContract));
+ string memory deployed_addresses_output = vm.serializeString(
+ deployed_addresses,
+ "strategies",
+ deployed_strategies_output
+ );
+
+ string memory parameters = "parameters";
+ vm.serializeAddress(parameters, "executorMultisig", executorMultisig);
+ vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig);
+ vm.serializeAddress(parameters, "communityMultisig", communityMultisig);
+ vm.serializeAddress(parameters, "pauserMultisig", pauserMultisig);
+ vm.serializeAddress(parameters, "timelock", timelock);
+ string memory parameters_output = vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig);
+
+ string memory chain_info = "chainInfo";
+ vm.serializeUint(chain_info, "deploymentBlock", block.number);
+ string memory chain_info_output = vm.serializeUint(chain_info, "chainId", block.chainid);
+
+ // serialize all the data
+ vm.serializeString(parent_object, deployed_addresses, deployed_addresses_output);
+ vm.serializeString(parent_object, chain_info, chain_info_output);
+ string memory finalJson = vm.serializeString(parent_object, parameters, parameters_output);
+
+ vm.writeJson(finalJson, outputPath);
+
}
}
diff --git a/script/utils/Multisend.sol b/script/utils/Multisend.sol
new file mode 100644
index 0000000000..72637959a3
--- /dev/null
+++ b/script/utils/Multisend.sol
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: LGPL-3.0-only
+pragma solidity >=0.7.0 <0.9.0;
+
+/// @title Multi Send Call Only - Allows to batch multiple transactions into one, but only calls
+/// @author Stefan George -
+/// @author Richard Meissner -
+/// @notice The guard logic is not required here as this contract doesn't support nested delegate calls
+contract MultiSendCallOnly {
+ /// @dev Sends multiple transactions and reverts all if one fails.
+ /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of
+ /// operation has to be uint8(0) in this version (=> 1 byte),
+ /// to as a address (=> 20 bytes),
+ /// value as a uint256 (=> 32 bytes),
+ /// data length as a uint256 (=> 32 bytes),
+ /// data as bytes.
+ /// see abi.encodePacked for more information on packed encoding
+ /// @notice The code is for most part the same as the normal MultiSend (to keep compatibility),
+ /// but reverts if a transaction tries to use a delegatecall.
+ /// @notice This method is payable as delegatecalls keep the msg.value from the previous call
+ /// If the calling method (e.g. execTransaction) received ETH this would revert otherwise
+ function multiSend(bytes memory transactions) public payable {
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ let length := mload(transactions)
+ let i := 0x20
+ for {
+ // Pre block is not used in "while mode"
+ } lt(i, length) {
+ // Post block is not used in "while mode"
+ } {
+ // First byte of the data is the operation.
+ // We shift by 248 bits (256 - 8 [operation byte]) it right since mload will always load 32 bytes (a word).
+ // This will also zero out unused data.
+ let operation := shr(0xf8, mload(add(transactions, i)))
+ // We offset the load address by 1 byte (operation byte)
+ // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out unused data.
+ let to := shr(0x60, mload(add(transactions, add(i, 0x01))))
+ // We offset the load address by 21 byte (operation byte + 20 address bytes)
+ let value := mload(add(transactions, add(i, 0x15)))
+ // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes)
+ let dataLength := mload(add(transactions, add(i, 0x35)))
+ // We offset the load address by 85 byte (operation byte + 20 address bytes + 32 value bytes + 32 data length bytes)
+ let data := add(transactions, add(i, 0x55))
+ let success := 0
+ switch operation
+ case 0 {
+ success := call(gas(), to, value, data, dataLength, 0, 0)
+ }
+ // This version does not allow delegatecalls
+ case 1 {
+ revert(0, 0)
+ }
+ if eq(success, 0) {
+ revert(0, 0)
+ }
+ // Next entry starts at 85 byte + data length
+ i := add(i, add(0x55, dataLength))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/script/utils/TimelockEncoding.sol b/script/utils/TimelockEncoding.sol
new file mode 100644
index 0000000000..cbb0c8da3c
--- /dev/null
+++ b/script/utils/TimelockEncoding.sol
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "forge-std/Script.sol";
+import "forge-std/Test.sol";
+
+import "./TxEncodingInterfaces.sol";
+
+contract TimelockEncoding is Test {
+ // CALLDATA FOR CALL FROM TIMELOCK TO EXECUTOR MULTISIG
+ uint256 safeTxGas = 0;
+ uint256 baseGas = 0;
+ uint256 gasPrice = 0;
+ address gasToken = address(uint160(0));
+ address payable refundReceiver = payable(address(uint160(0)));
+
+ // CALDATA FOR CALL TO TIMELOCK
+ uint256 timelockValue = 0;
+ // empty string, just encode all the data in 'timelockData'
+ string timelockSignature;
+
+ // appropriate address on mainnet, Holesky, and many other chains
+ address multiSendCallOnly = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D;
+
+ function encodeForTimelock(
+ address to,
+ uint256 value,
+ bytes memory data,
+ uint256 timelockEta
+ ) public returns (bytes memory calldata_to_timelock_queuing_action, bytes memory calldata_to_timelock_executing_action) {
+ calldata_to_timelock_queuing_action = abi.encodeWithSelector(ITimelock.queueTransaction.selector,
+ to,
+ value,
+ timelockSignature,
+ data,
+ timelockEta
+ );
+
+ emit log_named_bytes("calldata_to_timelock_queuing_action", calldata_to_timelock_queuing_action);
+
+ calldata_to_timelock_executing_action = abi.encodeWithSelector(ITimelock.executeTransaction.selector,
+ to,
+ value,
+ timelockSignature,
+ data,
+ timelockEta
+ );
+
+ emit log_named_bytes("calldata_to_timelock_executing_action", calldata_to_timelock_executing_action);
+
+ return (calldata_to_timelock_queuing_action, calldata_to_timelock_executing_action);
+ }
+
+ function encodeForExecutor(
+ address from,
+ address to,
+ uint256 value,
+ bytes memory data,
+ ISafe.Operation operation
+ ) public returns (bytes memory) {
+ // encode the "signature" required by the Safe
+ bytes1 v = bytes1(uint8(1));
+ bytes32 r = bytes32(uint256(uint160(from)));
+ bytes32 s;
+ bytes memory sig = abi.encodePacked(r,s,v);
+ emit log_named_bytes("sig", sig);
+
+ bytes memory final_calldata_to_executor_multisig = abi.encodeWithSelector(ISafe.execTransaction.selector,
+ to,
+ value,
+ data,
+ operation,
+ safeTxGas,
+ baseGas,
+ gasPrice,
+ gasToken,
+ refundReceiver,
+ sig
+ );
+
+ emit log_named_bytes("final_calldata_to_executor_multisig", final_calldata_to_executor_multisig);
+
+ return final_calldata_to_executor_multisig;
+ }
+
+ struct Tx {
+ address to;
+ uint256 value;
+ bytes data;
+ }
+
+ function encodeMultisendTxs(Tx[] memory txs) public pure returns (bytes memory) {
+ bytes memory ret = new bytes(0);
+ for (uint256 i = 0; i < txs.length; i++) {
+ ret = abi.encodePacked(
+ ret,
+ abi.encodePacked(
+ uint8(0),
+ txs[i].to,
+ txs[i].value,
+ uint256(txs[i].data.length),
+ txs[i].data
+ )
+ );
+ }
+ return ret;
+ }
+}
diff --git a/script/utils/TxEncodingInterfaces.sol b/script/utils/TxEncodingInterfaces.sol
new file mode 100644
index 0000000000..a967da943b
--- /dev/null
+++ b/script/utils/TxEncodingInterfaces.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: LGPL AND BSD 3-Clause
+pragma solidity >=0.5.0;
+
+// based on https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/GnosisSafe.sol
+interface ISafe {
+ function execTransaction(
+ address to,
+ uint256 value,
+ bytes calldata data,
+ uint8 operation,
+ uint256 safeTxGas,
+ uint256 baseGas,
+ uint256 gasPrice,
+ address gasToken,
+ address payable refundReceiver,
+ bytes memory signatures
+ ) external;
+
+ enum Operation {Call, DelegateCall}
+}
+
+// based on https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol
+interface ITimelock {
+ function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) external returns (bytes32);
+ function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) external payable returns (bytes memory);
+ function queuedTransactions(bytes32) external view returns (bool);
+}
+
diff --git a/script/utils/validateStorage/README.md b/script/utils/validateStorage/README.md
new file mode 100644
index 0000000000..4590714119
--- /dev/null
+++ b/script/utils/validateStorage/README.md
@@ -0,0 +1,23 @@
+# Storage Validation Scripts
+This script uses cast and forge inspect to get the layouts of the local and on-chain contract. Your `ETHERSCAN_API_KEY` must be set as an environment variable. The storage layouts are saved into csv files and passed into the typescript helper validateStorage.ts, which takes paths to two csv layouts and validates the storage slots.
+
+## Run validation
+To validate the storage of an upgradeable deployed contract against a local one, run the script:
+```bash
+bash script/upgrade/validateUpgrade.sh -n -c -a
+```
+
+The supported networks are `goerli` and `mainnet`. The supported contracts are `strategyManager`, `delegation`, `eigenPod`, `eigenPodManager`, and `slasher`.
+
+The above script generates two csv files, `localLayout.csv` and `onChainLayout.csv`. To keep these csv files after validating storage, add a `-k` flag to the above command
+
+Additionally, one can validate the storage of two csv files outputted by the `forge inspect` command by running
+
+```js
+npx ts-node script/upgrade/validateStorage.ts --old --new --keep
+```
+
+## Limitations
+Storage slot validation is NOT comprehensive, and errs on the side of caution. We recommend using this script as a tool along with manual storage slot verification. The validation is opinionated on storage for each contract consuming 50 slots and gaps being sized accordingly.
+
+The script does not validate legal type changes (ie. from bool to uint8) and errors out if the types of slots have updated, including having different struct names. A manual check will need to be done to validate this conversion. In addition, the script does not support non-contiguous gaps.
\ No newline at end of file
diff --git a/script/utils/validateStorage/validateStorage.ts b/script/utils/validateStorage/validateStorage.ts
new file mode 100644
index 0000000000..1ab871c830
--- /dev/null
+++ b/script/utils/validateStorage/validateStorage.ts
@@ -0,0 +1,262 @@
+import yargs, { string } from 'yargs';
+import * as fs from 'fs';
+import "dotenv/config";
+import chalk from 'chalk';
+
+// This function does basic validation of storage
+async function validateStorageSlots() {
+ const args = await yargs
+ .option('old', {
+ alias: 'o',
+ describe: 'Specify the filepath to the storage layout of the contract as it exists on chain',
+ demandOption: true,
+ })
+ .option('new', {
+ alias: 'n',
+ describe: 'Specify the filepath to the storage layout of the contract to upgrade to',
+ demandOption: true,
+ })
+ .option('keep', {
+ alias: 'k',
+ describe: "Whether to keep the csv storage layout files"
+ }).argv;
+
+ // Get csv files and format into mappings
+ const oldLayoutCSV = fs.readFileSync(args.old as string, 'utf8');
+ const oldLayout = formatCSV(oldLayoutCSV)
+ const newLayoutCSV = fs.readFileSync(args.new as string, 'utf8');
+ const newLayout = formatCSV(newLayoutCSV)
+
+ // Assert that the new layout is not smaller than the old layout
+ if (oldLayout.length > newLayout.length) {
+ throw new Error('New storage layout is smaller than old storage layout');
+ }
+
+ // List of warnings
+ const warnings = [];
+
+ // List of errors
+ const errors: Error[] = [];
+
+ // Loop through new layout
+ for(let slotNumber = 0; slotNumber < newLayout.length; slotNumber++) {
+ // No more storage slots to compare with in old layout
+ if (slotNumber >= oldLayout.length) {
+ break;
+ }
+
+ // Mark slot as read
+ oldLayout[slotNumber].read = true;
+
+ // Get item in slot
+ const newEntry = newLayout[slotNumber];
+ const oldEntry = oldLayout[slotNumber];
+
+ // Check that slot number is correct
+ if (newEntry.slot !== oldEntry.slot) {
+ throw new Error("Slot number mismatch");
+ }
+
+ // If old slot is empty and new slot is not, add an error - overwrote old storage with an empty slot
+ if (newEntry.empty && !oldEntry.empty) {
+ errors.push(new Error(`Slot ${newEntry.slot} has been incorrectly overridden`));
+ continue;
+ }
+
+ // If either slot is empty, continue
+ // Case1: newEntry.empty && oldEntry.empty -> fine
+ // Case2: newEntry.empty && !oldEntry.empty -> error already added
+ // Case3: !newEntry.empty && oldEntry.empty -> created new slot, will check with gaps
+ if (newEntry.empty || oldEntry.empty) {
+ continue;
+ }
+
+ // Remaining slots are now non-empty non-gaps
+ // Check that slot name has not changed
+ if (newEntry.name !== oldEntry.name) {
+ warnings.push(`Double check names: Slot ${slotNumber} has changed name from ${oldEntry.name} to ${newEntry.name}`);
+ }
+
+ // Check that slot type has not changed
+ if (newEntry.type !== oldEntry.type) {
+ errors.push(new Error(`Slot ${slotNumber} has changed type from ${oldEntry.type} to ${newEntry.type}`));
+ }
+ }
+
+ // Check gaps, starting from from the beginning of the old layout
+ for(let slotNumber = 0; slotNumber < oldLayout.length; slotNumber++) {
+ // Ignore non gap slots
+ if(oldLayout[slotNumber].name !== '__gap') {
+ continue;
+ }
+
+ // If the size is the same, continue
+ if (oldLayout[slotNumber].type === newLayout[slotNumber].type) {
+ continue;
+ }
+
+ // Gap is not present at the same slot in the new layout, find the next gap
+ let newGapIndex = slotNumber;
+ while (newLayout[newGapIndex].name !== '__gap' && newGapIndex < newLayout.length - 1) {
+ newGapIndex++;
+ }
+
+ // Add error if no gap is found, we should have gaps at the end of all contracts
+ if (newGapIndex === newLayout.length - 1 && newLayout[newGapIndex].name !== '__gap') {
+ errors.push(new Error("No gap added to end of new storage layout"));
+ continue;
+ }
+
+ // Get number of slots between gaps and extract the gap size
+ const newSlots = newLayout[newGapIndex].slot - newLayout[slotNumber].slot;
+ const oldGapSize = getGapSize(oldLayout[slotNumber].type);
+ const newGapSize = getGapSize(newLayout[newGapIndex].type);
+
+ // Check that gap has been properly resized
+ if(newSlots + newGapSize !== oldGapSize) {
+ // Okay to resize down if there is a non-empty slot after the gap and it's aligned to the 50th + 1 slot
+ if(newSlots + newGapSize < oldGapSize && !newLayout[newGapIndex + newGapSize].empty && newLayout[newGapIndex + newGapSize].slot % 50 === 1){
+ continue;
+ } else{
+ warnings.push(`Gap previously at slot ${oldLayout[slotNumber].slot} has incorrectly changed size from ${oldGapSize} to ${newGapSize} in new layout. Should be ${oldGapSize - newSlots}`);
+ }
+ }
+ }
+
+ // Check that gaps are aligned to the 50th slot in the new layout
+
+ // Find last gap slot
+ let lastGapSlot = newLayout.length;
+ for(let i = newLayout.length - 1; i >= 0; i--) {
+ if(newLayout[i].name === '__gap') {
+ lastGapSlot = i;
+ break;
+ }
+ }
+
+ for(let i = 0; i < lastGapSlot; i++) { // Ignore last gap
+ if(newLayout[i].name === '__gap') {
+ // Find the next non-gap slot
+ let nextDirtySlot = i;
+ while(newLayout[nextDirtySlot].empty && nextDirtySlot < newLayout.length - 1) {
+ nextDirtySlot++;
+ }
+
+ const nextSlot = newLayout[nextDirtySlot].slot;
+ if(nextSlot % 50 !== 1) {
+ warnings.push(`Next slot after the gap at ${newLayout[i].slot} is not aligned to the x*50th + 1 slot`);
+ }
+ }
+ }
+
+ // Sanity check that all slots in old layout have been read
+ for (let i = 0; i < oldLayout.length; i++) {
+ if (!oldLayout[i].read) {
+ errors.push(new Error(`Slot with name ${oldLayout[i].name} of type ${oldLayout[i].type} has been removed`));
+ }
+ }
+
+ // Delete files if keep is not specified
+ if (!args.keep) {
+ fs.unlinkSync(args.old as string);
+ fs.unlinkSync(args.new as string);
+ }
+
+ // Print warnings
+ if (warnings.length > 0) {
+ for (let i = 0; i < warnings.length; i++) {
+ console.log(chalk.yellow(warnings[i]));
+ }
+ }
+
+ // Print errors or success
+ if (errors.length > 0) {
+ const aggregatedError = new Error(chalk.red("Errors found in storage layout"));
+ (aggregatedError as any).error = errors;
+ throw aggregatedError;
+ } else {
+ console.log(chalk.bold.green('Storage layout is upgrade safe'));
+ }
+}
+
+function formatCSV(csv: string) {
+ const layoutFormatted = csv.split('\n')
+ // Array with all storage slots
+ const storageSlots: StorageSlot[] = [];
+
+ // Begin iterating when table begins, ignore any output before
+ let storageBegin = 2; // storage begins on line 2 if there is no extra output
+ for (let i = 0; i < layoutFormatted.length; i++) {
+ if (layoutFormatted[i].includes('| Name')){
+ break;
+ }
+ storageBegin++;
+ }
+
+ for (let i = storageBegin; i < layoutFormatted.length; i++) {
+ const data = layoutFormatted[i].split('|');
+
+ // Reached EOF
+ if (data.length <= 1) {
+ break;
+ }
+
+ const isEmpty = data[1].trim() === '__gap' ? true : false;
+ // Extract the relevant data from each line
+ const entry = {
+ name: data[1].trim(),
+ type: data[2].trim(),
+ slot: Number(data[3].trim()),
+ offset: Number(data[4].trim()),
+ bytes: Number(data[5].trim()),
+ empty: isEmpty,
+ read: false
+ }
+
+ // Push the entry to the storageSlots array
+ storageSlots.push(entry);
+
+ if (entry.name == '__gap'){
+ // Push empty slots for size of gap
+ const oldGapSize = getGapSize(entry.type);
+ for (let j = 1; j < oldGapSize; j++) {
+ storageSlots.push({
+ name: '',
+ type: '',
+ slot: entry.slot + j,
+ offset: 0,
+ bytes: 0,
+ empty: true,
+ read: false
+ });
+ }
+ }
+ }
+
+ // Assert that the storageSlots array is sorted by slot
+ for (let i = 0; i < storageSlots.length - 1; i++) {
+ if (storageSlots[i].slot > storageSlots[i+1].slot) {
+ throw new Error('Storage slots are not sorted by slot number');
+ }
+ }
+
+ return storageSlots;
+}
+
+// Gap in format `uint256[x]`, return x
+function getGapSize(gap: string): number {
+ return Number(gap.split('[')[1].split(']')[0]);
+}
+
+validateStorageSlots();
+
+
+type StorageSlot = {
+ name: string,
+ type: string,
+ slot: number,
+ offset: number,
+ bytes: number,
+ empty: boolean,
+ read: boolean
+}
\ No newline at end of file
diff --git a/script/utils/validateStorage/validateUpgrade.sh b/script/utils/validateStorage/validateUpgrade.sh
new file mode 100644
index 0000000000..943f30f6f4
--- /dev/null
+++ b/script/utils/validateStorage/validateUpgrade.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+source .env
+
+# Parse command-line arguments using getopt
+while getopts ":n:c:a:k:" opt; do
+ case $opt in
+ n) NETWORK="$OPTARG";;
+ c) CONTRACT="$OPTARG";;
+ a) ADDRESS="$OPTARG";;
+ \?) echo "Invalid option -$OPTARG" >&2; exit 1;;
+ esac
+done
+
+# Validate that network and contract inputs are provided
+if [ -z "$NETWORK" ] || [ -z "$CONTRACT" ] || [ -z "$ADDRESS" ]; then
+ echo "Usage: $0 -n -c -a -k"
+ exit 1
+fi
+
+# Validate the network input
+if [ "$NETWORK" != "mainnet" ] && [ "$NETWORK" != "goerli" ]; then
+ echo "Invalid network. Use 'mainnet' or 'goerli'."
+ exit 1
+fi
+
+# Get local path for contract & validate contract input
+case $CONTRACT in
+ "strategyManager") CONTRACT_PATH="src/contracts/core/StrategyManager.sol:StrategyManager";;
+ "delegation") CONTRACT_PATH="src/contracts/core/DelegationManager.sol:DelegationManager";;
+ "eigenPodManager") CONTRACT_PATH="src/contracts/pods/EigenPodManager.sol:EigenPodManager";;
+ "eigenPod") CONTRACT_PATH="src/contracts/pods/EigenPod.sol:EigenPod";;
+ "slasher") CONTRACT_PATH="src/contracts/core/Slasher.sol:Slasher";;
+ *)
+ echo "Invalid contract name."
+ exit 1
+ ;;
+esac
+
+# Set RPC
+if [ "$NETWORK" == "goerli" ]; then
+ RPC_URL="$RPC_GOERLI"
+else
+ RPC_URL="$RPC_MAINNET"
+fi
+
+# Print the selected network and contract
+echo "Checking storage layouts for contract on: $NETWORK"
+echo "Contract to validate upgrade: $CONTRACT"
+
+# Get storage layout for on-chain contract
+echo "Retrieving on-chain storage layout for $ADDRESS"
+command="cast storage $ADDRESS --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY"
+eval "$command > /dev/null 2>&1" # precompile contracts so onChainLayout.csv isn't filled with warnings
+output=$(eval $command)
+echo "$output" | tail -n +2 > onChainLayout.csv
+echo "On-chain storage saved to onChainLayout.csv"
+
+# Get storage layout for local contract
+echo "Retrieving local storage layout for $CONTRACT at $CONTRACT_PATH"
+command="forge inspect $CONTRACT_PATH storage --pretty"
+output=$(eval $command)
+echo "$output" | tail -n +1 > localLayout.csv
+echo "Local storage saved to localLayout.csv"
+
+# Compare the two storage layouts via typescript script
+echo "Comparing storage layouts..."
+
+# Add -k operator if present
+if [ ! -k "$1" ]; then
+ echo "Keeping old storage layout files"
+ eval "npx ts-node script/utils/validateStorage/validateStorage.ts --old onChainLayout.csv --new localLayout.csv --keep"
+else
+ eval "npx ts-node script/utils/validateStorage/validateStorage.ts --old onChainLayout.csv --new localLayout.csv"
+fi
\ No newline at end of file
diff --git a/script/whitelist/Staker.sol b/script/whitelist/Staker.sol
index 545d203ead..f353610cea 100644
--- a/script/whitelist/Staker.sol
+++ b/script/whitelist/Staker.sol
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../../src/contracts/interfaces/IStrategyManager.sol";
import "../../src/contracts/interfaces/IStrategy.sol";
import "../../src/contracts/interfaces/IDelegationManager.sol";
+import "../../src/contracts/interfaces/ISignatureUtils.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
@@ -21,7 +22,8 @@ contract Staker is Ownable {
) Ownable() {
token.approve(address(strategyManager), type(uint256).max);
strategyManager.depositIntoStrategy(strategy, token, amount);
- delegation.delegateTo(operator);
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry;
+ delegation.delegateTo(operator, signatureWithExpiry, bytes32(0));
}
function callAddress(address implementation, bytes memory data) external onlyOwner returns(bytes memory) {
@@ -46,4 +48,4 @@ contract Staker is Ownable {
}
-}
\ No newline at end of file
+}
diff --git a/script/whitelist/Whitelister.sol b/script/whitelist/Whitelister.sol
deleted file mode 100644
index 3eb9831e39..0000000000
--- a/script/whitelist/Whitelister.sol
+++ /dev/null
@@ -1,170 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../src/contracts/interfaces/IStrategyManager.sol";
-import "../../src/contracts/interfaces/IStrategy.sol";
-import "../../src/contracts/interfaces/IDelegationManager.sol";
-import "../../src/contracts/interfaces/IBLSRegistry.sol";
-import "../../src/contracts/interfaces/IWhitelister.sol";
-import "./Staker.sol";
-
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "./ERC20PresetMinterPauser.sol";
-import "@openzeppelin/contracts/access/Ownable.sol";
-import "@openzeppelin/contracts/utils/Create2.sol";
-
-
-contract Whitelister is IWhitelister, Ownable {
- //address constant strategyManager = 0x0000000000000000000000000000000000000000;
- //TODO: change before deploy
- IStrategyManager immutable strategyManager;
- ERC20PresetMinterPauser immutable stakeToken;
- IStrategy immutable stakeStrategy;
- IDelegationManager delegation;
-
- IBLSRegistry immutable registry;
-
- uint256 public constant DEFAULT_AMOUNT = 100e18;
-
- //TODO: Deploy ERC20PresetMinterPauser and a corresponding StrategyBase for it
- //TODO: Transfer ownership of Whitelister to multisig after deployment
- //TODO: Give mint/admin/pauser permssions of whitelistToken to Whitelister and multisig after deployment
- //TODO: Give up mint/admin/pauser permssions of whitelistToken for deployer
- constructor(
- IStrategyManager _strategyManager,
- IDelegationManager _delegation,
- ERC20PresetMinterPauser _token,
- IStrategy _strategy,
- IBLSRegistry _registry
- ) {
- strategyManager = _strategyManager;
- delegation = _delegation;
- stakeToken = _token;
- stakeStrategy = _strategy;
-
- registry = _registry;
- }
-
- function whitelist(address operator) public onlyOwner {
- // mint the staker the tokens
- stakeToken.mint(getStaker(operator), DEFAULT_AMOUNT);
- // deploy the staker
- Create2.deploy(
- 0,
- bytes32(uint256(uint160(operator))),
- abi.encodePacked(
- type(Staker).creationCode,
- abi.encode(
- stakeStrategy,
- strategyManager,
- delegation,
- stakeToken,
- DEFAULT_AMOUNT,
- operator
- )
- )
- );
-
- // add operator to whitelist
- address[] memory operators = new address[](1);
- operators[0] = operator;
- registry.addToOperatorWhitelist(operators);
- }
-
- function getStaker(address operator) public view returns (address) {
- return
- Create2.computeAddress(
- bytes32(uint256(uint160(operator))), //salt
- keccak256(
- abi.encodePacked(
- type(Staker).creationCode,
- abi.encode(
- stakeStrategy,
- strategyManager,
- delegation,
- stakeToken,
- DEFAULT_AMOUNT,
- operator
- )
- )
- )
- );
- }
-
- function depositIntoStrategy(
- address staker,
- IStrategy strategy,
- IERC20 token,
- uint256 amount
- ) public onlyOwner returns (bytes memory) {
-
- bytes memory data = abi.encodeWithSelector(
- IStrategyManager.depositIntoStrategy.selector,
- strategy,
- token,
- amount
- );
-
- return Staker(staker).callAddress(address(strategyManager), data);
- }
-
- function queueWithdrawal(
- address staker,
- uint256[] calldata strategyIndexes,
- IStrategy[] calldata strategies,
- uint256[] calldata shares,
- address withdrawer,
- bool undelegateIfPossible
- ) public onlyOwner returns (bytes memory) {
- bytes memory data = abi.encodeWithSelector(
- IStrategyManager.queueWithdrawal.selector,
- strategyIndexes,
- strategies,
- shares,
- withdrawer,
- undelegateIfPossible
- );
- return Staker(staker).callAddress(address(strategyManager), data);
- }
-
- function completeQueuedWithdrawal(
- address staker,
- IStrategyManager.QueuedWithdrawal calldata queuedWithdrawal,
- IERC20[] calldata tokens,
- uint256 middlewareTimesIndex,
- bool receiveAsTokens
- ) public onlyOwner returns (bytes memory) {
- bytes memory data = abi.encodeWithSelector(
- IStrategyManager.completeQueuedWithdrawal.selector,
- queuedWithdrawal,
- tokens,
- middlewareTimesIndex,
- receiveAsTokens
- );
-
- return Staker(staker).callAddress(address(strategyManager), data);
- }
-
- function transfer(
- address staker,
- address token,
- address to,
- uint256 amount
- ) public onlyOwner returns (bytes memory) {
- bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
-
- return Staker(staker).callAddress(token, data);
- }
-
- function callAddress(
- address to,
- bytes memory data
- ) public onlyOwner payable returns (bytes memory) {
- (bool ok, bytes memory res) = payable(to).call{value: msg.value}(data);
- if (!ok) {
- revert(string(res));
- }
- return res;
- }
-}
diff --git a/script/whitelist/delegationFaucet/DelegationFaucet.sol b/script/whitelist/delegationFaucet/DelegationFaucet.sol
new file mode 100644
index 0000000000..327e8e6783
--- /dev/null
+++ b/script/whitelist/delegationFaucet/DelegationFaucet.sol
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/contracts/interfaces/IStrategyManager.sol";
+import "src/contracts/interfaces/IStrategy.sol";
+import "src/contracts/interfaces/IDelegationManager.sol";
+import "src/contracts/interfaces/IDelegationFaucet.sol";
+import "script/whitelist/delegationFaucet/DelegationFaucetStaker.sol";
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "@openzeppelin/contracts/utils/Create2.sol";
+import "@openzeppelin/contracts/utils/Address.sol";
+
+import "../ERC20PresetMinterPauser.sol";
+
+/**
+ * @title DelegationFaucet for M2
+ * @author Layr Labs, Inc.
+ * @notice Faucet contract setup with a dummy ERC20 token and strategy for M2 testnet release.
+ * Each operator address gets a staker contract assigned to them deterministically.
+ * This contract assumes minting role of the ERC20 token and that the ERC20 token has a whitelisted strategy.
+ */
+contract DelegationFaucet is IDelegationFaucet, Ownable {
+ IStrategyManager immutable strategyManager;
+ ERC20PresetMinterPauser immutable stakeToken;
+ IStrategy immutable stakeStrategy;
+ IDelegationManager immutable delegation;
+
+ uint256 public constant DEFAULT_AMOUNT = 100e18;
+
+ constructor(
+ IStrategyManager _strategyManager,
+ IDelegationManager _delegation,
+ ERC20PresetMinterPauser _token,
+ IStrategy _strategy
+ ) {
+ strategyManager = _strategyManager;
+ delegation = _delegation;
+ stakeToken = _token;
+ stakeStrategy = _strategy;
+ }
+
+ /**
+ * Deploys a DelegationFaucetStaker contract if not already deployed for operator. DelegationFaucetStaker gets minted _depositAmount or
+ * DEFAULT_AMOUNT if _depositAmount is 0. Then DelegationFaucetStaker contract deposits into the strategy and delegates to operator.
+ * @param _operator The operator to delegate to
+ * @param _approverSignatureAndExpiry Verifies the operator approves of this delegation
+ * @param _approverSalt A unique single use value tied to an individual signature.
+ * @param _depositAmount The amount to deposit into the strategy
+ */
+ function mintDepositAndDelegate(
+ address _operator,
+ IDelegationManager.SignatureWithExpiry memory _approverSignatureAndExpiry,
+ bytes32 _approverSalt,
+ uint256 _depositAmount
+ ) public onlyOwner {
+ // Operator must be registered
+ require(delegation.isOperator(_operator), "DelegationFaucet: Operator not registered");
+ address staker = getStaker(_operator);
+ // Set deposit amount
+ if (_depositAmount == 0) {
+ _depositAmount = DEFAULT_AMOUNT;
+ }
+
+ // Deploy staker address if not already deployed, staker constructor will approve the StrategyManager to spend the stakeToken
+ if (!Address.isContract(staker)) {
+ Create2.deploy(
+ 0,
+ bytes32(uint256(uint160(_operator))),
+ abi.encodePacked(type(DelegationFaucetStaker).creationCode, abi.encode(strategyManager, stakeToken))
+ );
+ }
+
+ // mint stakeToken to staker
+ stakeToken.mint(staker, _depositAmount);
+ // deposit into stakeToken strategy, which will increase delegated shares to operator if already delegated
+ _depositIntoStrategy(staker, stakeStrategy, stakeToken, _depositAmount);
+ // delegateTo operator if not delegated
+ if (!delegation.isDelegated(staker)) {
+ delegateTo(_operator, _approverSignatureAndExpiry, _approverSalt);
+ }
+ }
+
+ /**
+ * Calls staker contract to deposit into designated strategy, mints staked token if stakeToken and stakeStrategy
+ * are specified.
+ * @param _staker address of staker contract for operator
+ * @param _strategy StakeToken strategy contract
+ * @param _token StakeToken
+ * @param _amount amount to get minted and to deposit
+ */
+ function depositIntoStrategy(
+ address _staker,
+ IStrategy _strategy,
+ IERC20 _token,
+ uint256 _amount
+ ) public onlyOwner returns (bytes memory) {
+ // mint stakeToken to staker
+ if (_token == stakeToken && _strategy == stakeStrategy) {
+ stakeToken.mint(_staker, _amount);
+ }
+ return _depositIntoStrategy(_staker, _strategy, _token, _amount);
+ }
+
+ /**
+ * Call staker to delegate to operator
+ * @param _operator operator to get staker address from and delegate to
+ * @param _approverSignatureAndExpiry Verifies the operator approves of this delegation
+ * @param _approverSalt A unique single use value tied to an individual signature.
+ */
+ function delegateTo(
+ address _operator,
+ IDelegationManager.SignatureWithExpiry memory _approverSignatureAndExpiry,
+ bytes32 _approverSalt
+ ) public onlyOwner returns (bytes memory) {
+ bytes memory data = abi.encodeWithSelector(
+ IDelegationManager.delegateTo.selector,
+ _operator,
+ _approverSignatureAndExpiry,
+ _approverSalt
+ );
+ return DelegationFaucetStaker(getStaker(_operator)).callAddress(address(delegation), data);
+ }
+
+ /**
+ * Call queueWithdrawal through staker contract
+ */
+ function queueWithdrawal(
+ address staker,
+ IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams
+ ) public onlyOwner returns (bytes memory) {
+ bytes memory data = abi.encodeWithSelector(
+ IDelegationManager.queueWithdrawals.selector,
+ queuedWithdrawalParams
+ );
+ return DelegationFaucetStaker(staker).callAddress(address(delegation), data);
+ }
+
+ /**
+ * Call completeQueuedWithdrawal through staker contract
+ */
+ function completeQueuedWithdrawal(
+ address staker,
+ IDelegationManager.Withdrawal calldata queuedWithdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ ) public onlyOwner returns (bytes memory) {
+ bytes memory data = abi.encodeWithSelector(
+ IDelegationManager.completeQueuedWithdrawal.selector,
+ queuedWithdrawal,
+ tokens,
+ middlewareTimesIndex,
+ receiveAsTokens
+ );
+ return DelegationFaucetStaker(staker).callAddress(address(delegation), data);
+ }
+
+ /**
+ * Transfers tokens from staker contract to designated address
+ * @param staker staker contract to transfer from
+ * @param token ERC20 token
+ * @param to the to address
+ * @param amount transfer amount
+ */
+ function transfer(
+ address staker,
+ address token,
+ address to,
+ uint256 amount
+ ) public onlyOwner returns (bytes memory) {
+ bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
+ return DelegationFaucetStaker(staker).callAddress(token, data);
+ }
+
+ function callAddress(address to, bytes memory data) public payable onlyOwner returns (bytes memory) {
+ (bool ok, bytes memory res) = payable(to).call{value: msg.value}(data);
+ if (!ok) {
+ revert(string(res));
+ }
+ return res;
+ }
+
+ /**
+ * @notice Returns the deterministic staker contract address for the operator
+ * @param _operator The operator to get the staker contract address for
+ */
+ function getStaker(address _operator) public view returns (address) {
+ return
+ Create2.computeAddress(
+ bytes32(uint256(uint160(_operator))), //salt
+ keccak256(
+ abi.encodePacked(type(DelegationFaucetStaker).creationCode, abi.encode(strategyManager, stakeToken))
+ )
+ );
+ }
+
+ /**
+ * @notice Internal function to deposit into a strategy, has same function signature as StrategyManager.depositIntoStrategy
+ */
+ function _depositIntoStrategy(
+ address _staker,
+ IStrategy _strategy,
+ IERC20 _token,
+ uint256 _amount
+ ) internal returns (bytes memory) {
+ bytes memory data = abi.encodeWithSelector(
+ IStrategyManager.depositIntoStrategy.selector,
+ _strategy,
+ _token,
+ _amount
+ );
+ return DelegationFaucetStaker(_staker).callAddress(address(strategyManager), data);
+ }
+}
diff --git a/script/whitelist/delegationFaucet/DelegationFaucetStaker.sol b/script/whitelist/delegationFaucet/DelegationFaucetStaker.sol
new file mode 100644
index 0000000000..fac0e543c9
--- /dev/null
+++ b/script/whitelist/delegationFaucet/DelegationFaucetStaker.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/contracts/interfaces/IStrategyManager.sol";
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "forge-std/Test.sol";
+
+contract DelegationFaucetStaker is Ownable {
+ constructor(IStrategyManager strategyManager, IERC20 token) Ownable() {
+ token.approve(address(strategyManager), type(uint256).max);
+ }
+
+ function callAddress(address implementation, bytes memory data) external onlyOwner returns (bytes memory) {
+ uint256 length = data.length;
+ bytes memory returndata;
+ assembly {
+ let result := call(gas(), implementation, callvalue(), add(data, 32), length, 0, 0)
+ mstore(returndata, returndatasize())
+ returndatacopy(add(returndata, 32), 0, returndatasize())
+ }
+
+ return returndata;
+ }
+}
diff --git a/script/whitelist/delegationFaucet/DeployDelegationFaucet.sol b/script/whitelist/delegationFaucet/DeployDelegationFaucet.sol
new file mode 100644
index 0000000000..db458e99d5
--- /dev/null
+++ b/script/whitelist/delegationFaucet/DeployDelegationFaucet.sol
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/contracts/interfaces/IDelegationManager.sol";
+import "src/contracts/interfaces/IStrategyManager.sol";
+import "src/contracts/strategies/StrategyBase.sol";
+import "src/contracts/permissions/PauserRegistry.sol";
+
+import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+
+import "./DelegationFaucet.sol";
+
+import "forge-std/Script.sol";
+import "forge-std/StdJson.sol";
+
+/**
+ * @notice Deploys the following contracts:
+ * - ERC20 Dummy token for testing
+ * - StrategyBase to be added to the StrategyManager whitelist
+ * - DelegationFaucet contract
+ */
+contract DeployDelegationFaucet is Script, DSTest {
+ // EigenLayer contracts
+ ProxyAdmin public eigenLayerProxyAdmin;
+ PauserRegistry public eigenLayerPauserReg;
+ IDelegationManager public delegation;
+ IStrategyManager public strategyManager;
+
+ DelegationFaucet public delegationFaucet;
+
+ // M2 testing/mock contracts
+ ERC20PresetMinterPauser public stakeToken;
+ StrategyBase public stakeTokenStrat;
+ StrategyBase public baseStrategyImplementation;
+
+ address eigenLayerProxyAdminAddress;
+ address eigenLayerPauserRegAddress;
+ address delegationAddress;
+ address strategyManagerAddress;
+ address operationsMultisig;
+ address executorMultisig;
+
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
+
+ function run() external {
+ string memory goerliDeploymentConfig = vm.readFile("script/output/M1_deployment_goerli_2023_3_23.json");
+ _setAddresses(goerliDeploymentConfig);
+
+ vm.startBroadcast();
+ // Deploy ERC20 stakeToken
+ stakeToken = new ERC20PresetMinterPauser("StakeToken", "STK");
+
+ // Deploy StrategyBase for stakeToken & whitelist it
+ baseStrategyImplementation = new StrategyBase(strategyManager);
+ stakeTokenStrat = StrategyBase(
+ address(
+ new TransparentUpgradeableProxy(
+ address(baseStrategyImplementation),
+ eigenLayerProxyAdminAddress,
+ abi.encodeWithSelector(StrategyBase.initialize.selector, stakeToken, eigenLayerPauserRegAddress)
+ )
+ )
+ );
+
+ // Needs to be strategyManager.strategyWhitelister() to add STK strategy
+ // IStrategy[] memory _strategy = new IStrategy[](1);
+ // _strategy[0] = stakeTokenStrat;
+ // strategyManager.addStrategiesToDepositWhitelist(_strategy);
+
+ // Deploy DelegationFaucet, grant it admin/mint/pauser roles, etc.
+ delegationFaucet = new DelegationFaucet(strategyManager, delegation, stakeToken, stakeTokenStrat);
+ stakeToken.grantRole(MINTER_ROLE, address(delegationFaucet));
+ vm.stopBroadcast();
+ }
+
+ function _setAddresses(string memory config) internal {
+ eigenLayerProxyAdminAddress = stdJson.readAddress(config, ".addresses.eigenLayerProxyAdmin");
+ eigenLayerPauserRegAddress = stdJson.readAddress(config, ".addresses.eigenLayerPauserReg");
+ delegationAddress = stdJson.readAddress(config, ".addresses.delegation");
+ strategyManagerAddress = stdJson.readAddress(config, ".addresses.strategyManager");
+ operationsMultisig = stdJson.readAddress(config, ".parameters.operationsMultisig");
+ executorMultisig = stdJson.readAddress(config, ".parameters.executorMultisig");
+ }
+}
diff --git a/slither.config.json b/slither.config.json
index 4e4f0227f6..d8ea48206b 100644
--- a/slither.config.json
+++ b/slither.config.json
@@ -1,5 +1,5 @@
{
- "detectors_to_exclude": "assembly,solc-version,naming-convention,incorrect-equality,uninitialized-local,timestamp,low-level-calls,unimplemented-functions,too-many-digits,similar-names,calls-loop,arbitrary-send-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,unused-state",
+ "detectors_to_exclude": "assembly,solc-version,naming-convention,incorrect-equality,uninitialized-local,timestamp,low-level-calls,unimplemented-functions,too-many-digits,similar-names,calls-loop,arbitrary-send-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,unused-state,incorrect-shift-in-assembly,dead-code",
"filter_paths": "lib/|test/|mocks/|BytesLib|script/",
"solc_remaps": [
"forge-std/=lib/forge-std/src/",
diff --git a/src/contracts/core/AVSDirectory.sol b/src/contracts/core/AVSDirectory.sol
new file mode 100644
index 0000000000..57844689f8
--- /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)));
+ }
+}
diff --git a/src/contracts/core/AVSDirectoryStorage.sol b/src/contracts/core/AVSDirectoryStorage.sol
new file mode 100644
index 0000000000..993025c155
--- /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 99cd37125e..ac14e6879c 100644
--- a/src/contracts/core/DelegationManager.sol
+++ b/src/contracts/core/DelegationManager.sol
@@ -1,354 +1,1012 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "@openzeppelin/contracts/interfaces/IERC1271.sol";
-import "@openzeppelin/contracts/utils/Address.sol";
-import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
-import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
-import "./DelegationManagerStorage.sol";
+import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
+import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
import "../permissions/Pausable.sol";
-import "./Slasher.sol";
+import "../libraries/EIP1271SignatureUtils.sol";
+import "./DelegationManagerStorage.sol";
/**
- * @title The primary delegation contract for EigenLayer.
+ * @title DelegationManager
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice This is the contract for delegation in EigenLayer. The main functionalities of this contract are
* - enabling anyone to register as an operator in EigenLayer
- * - allowing new operators to provide a DelegationTerms-type contract, which may mediate their interactions with stakers who delegate to them
- * - enabling any staker to delegate its stake to the operator of its choice
- * - enabling a staker to undelegate its assets from an operator (performed as part of the withdrawal process, initiated through the StrategyManager)
+ * - allowing operators to specify parameters related to stakers who delegate to them
+ * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time)
+ * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager)
*/
-contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage {
- // index for flag that pauses new delegations when set
+contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable {
+ // @dev Index for flag that pauses new delegations when set
uint8 internal constant PAUSED_NEW_DELEGATION = 0;
- // bytes4(keccak256("isValidSignature(bytes32,bytes)")
- bytes4 constant internal ERC1271_MAGICVALUE = 0x1626ba7e;
- // chain id at the time of contract deployment
- uint256 immutable ORIGINAL_CHAIN_ID;
+ // @dev Index for flag that pauses queuing new withdrawals when set.
+ uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1;
+
+ // @dev Index for flag that pauses completing existing withdrawals when set.
+ uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
+
+ // @dev Chain ID at the time of contract deployment
+ uint256 internal immutable ORIGINAL_CHAIN_ID;
+
+ // @dev Maximum Value for `stakerOptOutWindowBlocks`. Approximately equivalent to 6 months in blocks.
+ uint256 public constant MAX_STAKER_OPT_OUT_WINDOW_BLOCKS = (180 days) / 12;
+ /// @notice Canonical, virtual beacon chain ETH strategy
+ IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
- /// @notice Simple permission for functions that are only callable by the StrategyManager contract.
- modifier onlyStrategyManager() {
- require(msg.sender == address(strategyManager), "onlyStrategyManager");
+ // @notice Simple permission for functions that are only callable by the StrategyManager contract OR by the EigenPodManagerContract
+ modifier onlyStrategyManagerOrEigenPodManager() {
+ require(
+ msg.sender == address(strategyManager) || msg.sender == address(eigenPodManager),
+ "DelegationManager: onlyStrategyManagerOrEigenPodManager"
+ );
_;
}
- // INITIALIZING FUNCTIONS
- constructor(IStrategyManager _strategyManager, ISlasher _slasher)
- DelegationManagerStorage(_strategyManager, _slasher)
- {
+ /*******************************************************************************
+ INITIALIZING FUNCTIONS
+ *******************************************************************************/
+
+ /**
+ * @dev Initializes the immutable addresses of the strategy mananger and slasher.
+ */
+ constructor(
+ IStrategyManager _strategyManager,
+ ISlasher _slasher,
+ IEigenPodManager _eigenPodManager
+ ) DelegationManagerStorage(_strategyManager, _slasher, _eigenPodManager) {
_disableInitializers();
ORIGINAL_CHAIN_ID = block.chainid;
}
- /// @dev Emitted when a low-level call to `delegationTerms.onDelegationReceived` fails, returning `returnData`
- event OnDelegationReceivedCallFailure(IDelegationTerms indexed delegationTerms, bytes32 returnData);
-
- /// @dev Emitted when a low-level call to `delegationTerms.onDelegationWithdrawn` fails, returning `returnData`
- event OnDelegationWithdrawnCallFailure(IDelegationTerms indexed delegationTerms, bytes32 returnData);
-
- /// @dev Emitted when an entity registers itself as an operator in the DelegationManager
- event RegisterAsOperator(address indexed operator, IDelegationTerms indexed delegationTerms);
-
- function initialize(address initialOwner, IPauserRegistry _pauserRegistry, uint256 initialPausedStatus)
- external
- initializer
- {
+ /**
+ * @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,
+ uint256 _minWithdrawalDelayBlocks,
+ IStrategy[] calldata _strategies,
+ uint256[] calldata _withdrawalDelayBlocks
+ ) external initializer {
_initializePauser(_pauserRegistry, initialPausedStatus);
- DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), ORIGINAL_CHAIN_ID, address(this)));
+ _DOMAIN_SEPARATOR = _calculateDomainSeparator();
_transferOwnership(initialOwner);
+ _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
+ _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
}
- // EXTERNAL FUNCTIONS
+ /*******************************************************************************
+ EXTERNAL FUNCTIONS
+ *******************************************************************************/
+
/**
- * @notice This will be called by an operator to register itself as an operator that stakers can choose to delegate to.
- * @param dt is the `DelegationTerms` contract that the operator has for those who delegate to them.
- * @dev An operator can set `dt` equal to their own address (or another EOA address), in the event that they want to split payments
- * in a more 'trustful' manner.
- * @dev In the present design, once set, there is no way for an operator to ever modify the address of their DelegationTerms contract.
+ * @notice Registers the caller as an operator in EigenLayer.
+ * @param registeringOperatorDetails is the `OperatorDetails` for the operator.
+ * @param metadataURI is a URI for the operator's metadata, i.e. a link providing more details on the operator.
+ *
+ * @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered "delegated to themself".
+ * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
+ * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
*/
- function registerAsOperator(IDelegationTerms dt) external {
+ function registerAsOperator(
+ OperatorDetails calldata registeringOperatorDetails,
+ string calldata metadataURI
+ ) external {
require(
- address(delegationTerms[msg.sender]) == address(0),
+ _operatorDetails[msg.sender].earningsReceiver == address(0),
"DelegationManager.registerAsOperator: operator has already registered"
);
- // store the address of the delegation contract that the operator is providing.
- delegationTerms[msg.sender] = dt;
- _delegate(msg.sender, msg.sender);
- emit RegisterAsOperator(msg.sender, dt);
+ _setOperatorDetails(msg.sender, registeringOperatorDetails);
+ SignatureWithExpiry memory emptySignatureAndExpiry;
+ // delegate from the operator to themselves
+ _delegate(msg.sender, msg.sender, emptySignatureAndExpiry, bytes32(0));
+ // emit events
+ emit OperatorRegistered(msg.sender, registeringOperatorDetails);
+ emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
+ }
+
+ /**
+ * @notice Updates an operator's stored `OperatorDetails`.
+ * @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current OperatorDetails`.
+ *
+ * @dev The caller must have previously registered as an operator in EigenLayer.
+ * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
+ */
+ function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external {
+ require(isOperator(msg.sender), "DelegationManager.modifyOperatorDetails: caller must be an operator");
+ _setOperatorDetails(msg.sender, newOperatorDetails);
}
/**
- * @notice This will be called by a staker to delegate its assets to some operator.
- * @param operator is the operator to whom staker (msg.sender) is delegating its assets
+ * @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has updated.
+ * @param metadataURI The URI for metadata associated with an operator
*/
- function delegateTo(address operator) external {
- _delegate(msg.sender, operator);
+ function updateOperatorMetadataURI(string calldata metadataURI) external {
+ require(isOperator(msg.sender), "DelegationManager.updateOperatorMetadataURI: caller must be an operator");
+ emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
}
/**
- * @notice Delegates from `staker` to `operator`.
- * @dev requires that:
- * 1) if `staker` is an EOA, then `signature` is valid ECDSA signature from `staker`, indicating their intention for this action
- * 2) if `staker` is a contract, then `signature` must will be checked according to EIP-1271
+ * @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.
+ * @param approverSignatureAndExpiry Verifies the operator approves of this delegation
+ * @param approverSalt A unique single use value tied to an individual signature.
+ * @dev The approverSignatureAndExpiry is used in the event that:
+ * 1) the operator's `delegationApprover` address is set to a non-zero value.
+ * AND
+ * 2) neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator
+ * or their delegationApprover is the `msg.sender`, then approval is assumed.
+ * @dev In the event that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
+ * in this case to save on complexity + gas costs
*/
- function delegateToBySignature(address staker, address operator, uint256 expiry, bytes memory signature)
- external
- {
- require(expiry >= block.timestamp, "DelegationManager.delegateToBySignature: delegation signature expired");
+ function delegateTo(
+ address operator,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+ ) external {
+ // go through the internal delegation flow, checking the `approverSignatureAndExpiry` if applicable
+ _delegate(msg.sender, operator, approverSignatureAndExpiry, approverSalt);
+ }
- // calculate struct hash, then increment `staker`'s nonce
- uint256 nonce = nonces[staker];
- bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, staker, operator, nonce, expiry));
+ /**
+ * @notice Caller delegates a staker's stake to an operator with valid signatures from both parties.
+ * @param staker The account delegating stake to an `operator` account
+ * @param operator The account (`staker`) is delegating its assets to for use in serving applications built on EigenLayer.
+ * @param stakerSignatureAndExpiry Signed data from the staker authorizing delegating stake to an operator
+ * @param approverSignatureAndExpiry is a parameter that will be used for verifying that the operator approves of this delegation action in the event that:
+ * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver.
+ *
+ * @dev If `staker` is an EOA, then `stakerSignature` is verified to be a valid ECDSA stakerSignature from `staker`, indicating their intention for this action.
+ * @dev If `staker` is a contract, then `stakerSignature` will be checked according to EIP-1271.
+ * @dev the operator's `delegationApprover` address is set to a non-zero value.
+ * @dev neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator or their delegationApprover
+ * is the `msg.sender`, then approval is assumed.
+ * @dev This function will revert if the current `block.timestamp` is equal to or exceeds the expiry
+ * @dev In the case that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
+ * in this case to save on complexity + gas costs
+ */
+ function delegateToBySignature(
+ address staker,
+ address operator,
+ SignatureWithExpiry memory stakerSignatureAndExpiry,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+ ) external {
+ // check the signature expiry
+ require(
+ stakerSignatureAndExpiry.expiry >= block.timestamp,
+ "DelegationManager.delegateToBySignature: staker signature expired"
+ );
+
+ // calculate the digest hash, then increment `staker`'s nonce
+ uint256 currentStakerNonce = stakerNonce[staker];
+ bytes32 stakerDigestHash = calculateStakerDelegationDigestHash(
+ staker,
+ currentStakerNonce,
+ operator,
+ stakerSignatureAndExpiry.expiry
+ );
unchecked {
- nonces[staker] = nonce + 1;
+ stakerNonce[staker] = currentStakerNonce + 1;
}
- bytes32 digestHash;
- if (block.chainid != ORIGINAL_CHAIN_ID) {
- bytes32 domain_separator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
- digestHash = keccak256(abi.encodePacked("\x19\x01", domain_separator, structHash));
- } else{
- digestHash = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash));
+ // actually check that the signature is valid
+ EIP1271SignatureUtils.checkSignature_EIP1271(staker, stakerDigestHash, stakerSignatureAndExpiry.signature);
+
+ // go through the internal delegation flow, checking the `approverSignatureAndExpiry` if applicable
+ _delegate(staker, operator, approverSignatureAndExpiry, approverSalt);
+ }
+
+ /**
+ * Allows the staker, the staker's operator, or that operator's delegationApprover to undelegate
+ * a staker from their operator. Undelegation immediately removes ALL active shares/strategies from
+ * both the staker and operator, and places the shares and strategies in the withdrawal queue
+ */
+ function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory withdrawalRoots) {
+ require(isDelegated(staker), "DelegationManager.undelegate: staker must be delegated to undelegate");
+ require(!isOperator(staker), "DelegationManager.undelegate: operators cannot be undelegated");
+ require(staker != address(0), "DelegationManager.undelegate: cannot undelegate zero address");
+ address operator = delegatedTo[staker];
+ require(
+ msg.sender == staker ||
+ msg.sender == operator ||
+ msg.sender == _operatorDetails[operator].delegationApprover,
+ "DelegationManager.undelegate: caller cannot undelegate staker"
+ );
+
+ // Gather strategies and shares to remove from staker/operator during undelegation
+ // Undelegation removes ALL currently-active strategies and shares
+ (IStrategy[] memory strategies, uint256[] memory shares) = getDelegatableShares(staker);
+
+ // emit an event if this action was not initiated by the staker themselves
+ if (msg.sender != staker) {
+ emit StakerForceUndelegated(staker, operator);
}
- /**
- * check validity of signature:
- * 1) if `staker` is an EOA, then `signature` must be a valid ECDSA signature from `staker`,
- * indicating their intention for this action
- * 2) if `staker` is a contract, then `signature` must will be checked according to EIP-1271
- */
- if (Address.isContract(staker)) {
- require(IERC1271(staker).isValidSignature(digestHash, signature) == ERC1271_MAGICVALUE,
- "DelegationManager.delegateToBySignature: ERC1271 signature verification failed");
+ // undelegate the staker
+ emit StakerUndelegated(staker, operator);
+ delegatedTo[staker] = address(0);
+
+ // if no delegatable shares, return an empty array, and don't queue a withdrawal
+ if (strategies.length == 0) {
+ withdrawalRoots = new bytes32[](0);
} else {
- require(ECDSA.recover(digestHash, signature) == staker,
- "DelegationManager.delegateToBySignature: sig not from staker");
+ withdrawalRoots = new bytes32[](strategies.length);
+ for (uint256 i = 0; i < strategies.length; i++) {
+ IStrategy[] memory singleStrategy = new IStrategy[](1);
+ uint256[] memory singleShare = new uint256[](1);
+ singleStrategy[0] = strategies[i];
+ singleShare[0] = shares[i];
+
+ withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
+ staker: staker,
+ operator: operator,
+ withdrawer: staker,
+ strategies: singleStrategy,
+ shares: singleShare
+ });
+ }
}
- _delegate(staker, operator);
+ return withdrawalRoots;
}
/**
- * @notice Undelegates `staker` from the operator who they are delegated to.
- * @notice Callable only by the StrategyManager
- * @dev Should only ever be called in the event that the `staker` has no active deposits in EigenLayer.
+ * 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.
+ *
+ * All withdrawn shares/strategies are placed in a queue and can be fully withdrawn after a delay.
*/
- function undelegate(address staker) external onlyStrategyManager {
- require(!isOperator(staker), "DelegationManager.undelegate: operators cannot undelegate from themselves");
- delegatedTo[staker] = address(0);
+ function queueWithdrawals(
+ 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 == 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
+ // NOTE: This will fail if the staker doesn't have the shares implied by the input parameters
+ withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
+ staker: msg.sender,
+ operator: operator,
+ withdrawer: queuedWithdrawalParams[i].withdrawer,
+ strategies: queuedWithdrawalParams[i].strategies,
+ shares: queuedWithdrawalParams[i].shares
+ });
+ }
+ return withdrawalRoots;
}
/**
- * @notice Increases the `staker`'s delegated shares in `strategy` by `shares, typically called when the staker has further deposits into EigenLayer
- * @dev Callable only by the StrategyManager
+ * @notice Used to complete the specified `withdrawal`. The caller must match `withdrawal.withdrawer`
+ * @param withdrawal The Withdrawal to complete.
+ * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `withdrawal.strategies` array.
+ * This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
+ * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
+ * @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified strategies themselves
+ * and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
+ * will simply be transferred to the caller directly.
+ * @dev middlewareTimesIndex is unused, but will be used in the Slasher eventually
+ * @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and `withdrawal.withdrawer != withdrawal.staker`, note that
+ * any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, unlike shares in
+ * any other strategies, which will be transferred to the withdrawer.
*/
- function increaseDelegatedShares(address staker, IStrategy strategy, uint256 shares)
- external
- onlyStrategyManager
- {
- //if the staker is delegated to an operator
+ function completeQueuedWithdrawal(
+ Withdrawal calldata withdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
+ _completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex, receiveAsTokens);
+ }
+
+ /**
+ * @notice Array-ified version of `completeQueuedWithdrawal`.
+ * Used to complete the specified `withdrawals`. The function caller must match `withdrawals[...].withdrawer`
+ * @param withdrawals The Withdrawals to complete.
+ * @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
+ * @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
+ * @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for the usage of a single boolean.
+ * @dev See `completeQueuedWithdrawal` for relevant dev tags
+ */
+ function completeQueuedWithdrawals(
+ Withdrawal[] calldata withdrawals,
+ IERC20[][] calldata tokens,
+ uint256[] calldata middlewareTimesIndexes,
+ bool[] calldata receiveAsTokens
+ ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
+ for (uint256 i = 0; i < withdrawals.length; ++i) {
+ _completeQueuedWithdrawal(withdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]);
+ }
+ }
+
+ /// @notice Migrates an array of queued withdrawals from the StrategyManager contract to this contract.
+ /// @dev This function is expected to be removed in the next upgrade, after all queued withdrawals have been migrated.
+ function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToMigrate) external {
+ for(uint256 i = 0; i < withdrawalsToMigrate.length;) {
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory withdrawalToMigrate = withdrawalsToMigrate[i];
+ // Delete withdrawal root from strateyManager
+ (bool isDeleted, bytes32 oldWithdrawalRoot) = strategyManager.migrateQueuedWithdrawal(withdrawalToMigrate);
+ // If old storage is deleted from strategyManager
+ if (isDeleted) {
+ address staker = withdrawalToMigrate.staker;
+ // Create queue entry and increment withdrawal nonce
+ uint256 nonce = cumulativeWithdrawalsQueued[staker];
+ cumulativeWithdrawalsQueued[staker]++;
+
+ Withdrawal memory migratedWithdrawal = Withdrawal({
+ staker: staker,
+ delegatedTo: withdrawalToMigrate.delegatedAddress,
+ withdrawer: withdrawalToMigrate.withdrawerAndNonce.withdrawer,
+ nonce: nonce,
+ startBlock: withdrawalToMigrate.withdrawalStartBlock,
+ strategies: withdrawalToMigrate.strategies,
+ shares: withdrawalToMigrate.shares
+ });
+
+ // create the new storage
+ bytes32 newRoot = calculateWithdrawalRoot(migratedWithdrawal);
+ // safety check to ensure that root doesn't exist already -- this should *never* be hit
+ require(!pendingWithdrawals[newRoot], "DelegationManager.migrateQueuedWithdrawals: withdrawal already exists");
+ pendingWithdrawals[newRoot] = true;
+
+ emit WithdrawalQueued(newRoot, migratedWithdrawal);
+
+ emit WithdrawalMigrated(oldWithdrawalRoot, newRoot);
+ }
+ unchecked {
+ ++i;
+ }
+ }
+
+ }
+
+ /**
+ * @notice Increases a staker's delegated share balance in a strategy.
+ * @param staker The address to increase the delegated shares for their operator.
+ * @param strategy The strategy in which to increase the delegated shares.
+ * @param shares The number of shares to increase.
+ *
+ * @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
+ * @dev Callable only by the StrategyManager or EigenPodManager.
+ */
+ function increaseDelegatedShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+ ) external onlyStrategyManagerOrEigenPodManager {
+ // if the staker is delegated to an operator
if (isDelegated(staker)) {
address operator = delegatedTo[staker];
// add strategy shares to delegate's shares
- operatorShares[operator][strategy] += shares;
-
- //Calls into operator's delegationTerms contract to update weights of individual staker
- IStrategy[] memory stakerStrategyList = new IStrategy[](1);
- uint256[] memory stakerShares = new uint[](1);
- stakerStrategyList[0] = strategy;
- stakerShares[0] = shares;
-
- // call into hook in delegationTerms contract
- IDelegationTerms dt = delegationTerms[operator];
- _delegationReceivedHook(dt, staker, stakerStrategyList, stakerShares);
+ _increaseOperatorShares({operator: operator, staker: staker, strategy: strategy, shares: shares});
}
}
/**
- * @notice Decreases the `staker`'s delegated shares in each entry of `strategies` by its respective `shares[i]`, typically called when the staker withdraws from EigenLayer
- * @dev Callable only by the StrategyManager
+ * @notice Decreases a staker's delegated share balance in a strategy.
+ * @param staker The address to increase the delegated shares for their operator.
+ * @param strategy The strategy in which to decrease the delegated shares.
+ * @param shares The number of shares to decrease.
+ *
+ * @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
+ * @dev Callable only by the StrategyManager or EigenPodManager.
*/
function decreaseDelegatedShares(
address staker,
- IStrategy[] calldata strategies,
- uint256[] calldata shares
- )
- external
- onlyStrategyManager
- {
+ IStrategy strategy,
+ uint256 shares
+ ) external onlyStrategyManagerOrEigenPodManager {
+ // if the staker is delegated to an operator
if (isDelegated(staker)) {
address operator = delegatedTo[staker];
// subtract strategy shares from delegate's shares
- uint256 stratsLength = strategies.length;
- for (uint256 i = 0; i < stratsLength;) {
- operatorShares[operator][strategies[i]] -= shares[i];
- unchecked {
- ++i;
- }
- }
-
- // call into hook in delegationTerms contract
- IDelegationTerms dt = delegationTerms[operator];
- _delegationWithdrawnHook(dt, staker, strategies, shares);
+ _decreaseOperatorShares({
+ operator: operator,
+ staker: staker,
+ strategy: strategy,
+ shares: shares
+ });
}
}
- // INTERNAL FUNCTIONS
+ /**
+ * @notice Owner-only function for modifying the value of the `minWithdrawalDelayBlocks` variable.
+ * @param newMinWithdrawalDelayBlocks new value of `minWithdrawalDelayBlocks`.
+ */
+ function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner {
+ _setMinWithdrawalDelayBlocks(newMinWithdrawalDelayBlocks);
+ }
+
+ /**
+ * @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 setStrategyWithdrawalDelayBlocks(
+ IStrategy[] calldata strategies,
+ uint256[] calldata withdrawalDelayBlocks
+ ) external onlyOwner {
+ _setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
+ }
+
+ /*******************************************************************************
+ INTERNAL FUNCTIONS
+ *******************************************************************************/
+
+ /**
+ * @notice Sets operator parameters in the `_operatorDetails` mapping.
+ * @param operator The account registered as an operator updating their operatorDetails
+ * @param newOperatorDetails The new parameters for the operator
+ *
+ * @dev This function will revert if the operator attempts to set their `earningsReceiver` to address(0).
+ */
+ function _setOperatorDetails(address operator, OperatorDetails calldata newOperatorDetails) internal {
+ require(
+ newOperatorDetails.earningsReceiver != address(0),
+ "DelegationManager._setOperatorDetails: cannot set `earningsReceiver` to zero address"
+ );
+ require(
+ newOperatorDetails.stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS,
+ "DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS"
+ );
+ require(
+ newOperatorDetails.stakerOptOutWindowBlocks >= _operatorDetails[operator].stakerOptOutWindowBlocks,
+ "DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be decreased"
+ );
+ _operatorDetails[operator] = newOperatorDetails;
+ emit OperatorDetailsModified(msg.sender, newOperatorDetails);
+ }
- /**
- * @notice Makes a low-level call to `dt.onDelegationReceived(staker, strategies, shares)`, ignoring reverts and with a gas budget
- * equal to `LOW_LEVEL_GAS_BUDGET` (a constant defined in this contract).
- * @dev *If* the low-level call fails, then this function emits the event `OnDelegationReceivedCallFailure(dt, returnData)`, where
- * `returnData` is *only the first 32 bytes* returned by the call to `dt`.
+ /**
+ * @notice Delegates *from* a `staker` *to* an `operator`.
+ * @param staker The address to delegate *from* -- this address is delegating control of its own assets.
+ * @param operator The address to delegate *to* -- this address is being given power to place the `staker`'s assets at risk on services
+ * @param approverSignatureAndExpiry Verifies the operator approves of this delegation
+ * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver.
+ * @dev Ensures that:
+ * 1) the `staker` is not already delegated to an operator
+ * 2) the `operator` has indeed registered as an operator in EigenLayer
+ * 3) if applicable, that the approver signature is valid and non-expired
*/
- function _delegationReceivedHook(
- IDelegationTerms dt,
+ function _delegate(
address staker,
- IStrategy[] memory strategies,
- uint256[] memory shares
- )
- internal
- {
+ address operator,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+ ) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) {
+ require(!isDelegated(staker), "DelegationManager._delegate: staker is already actively delegated");
+ require(isOperator(operator), "DelegationManager._delegate: operator is not registered in EigenLayer");
+
+ // fetch the operator's `delegationApprover` address and store it in memory in case we need to use it multiple times
+ address _delegationApprover = _operatorDetails[operator].delegationApprover;
/**
- * We use low-level call functionality here to ensure that an operator cannot maliciously make this function fail in order to prevent undelegation.
- * In particular, in-line assembly is also used to prevent the copying of uncapped return data which is also a potential DoS vector.
+ * Check the `_delegationApprover`'s signature, if applicable.
+ * If the `_delegationApprover` is the zero address, then the operator allows all stakers to delegate to them and this verification is skipped.
+ * If the `_delegationApprover` or the `operator` themselves is the caller, then approval is assumed and signature verification is skipped as well.
*/
- // format calldata
- bytes memory lowLevelCalldata = abi.encodeWithSelector(IDelegationTerms.onDelegationReceived.selector, staker, strategies, shares);
- // Prepare memory for low-level call return data. We accept a max return data length of 32 bytes
- bool success;
- bytes32[1] memory returnData;
- // actually make the call
- assembly {
- success := call(
- // gas provided to this context
- LOW_LEVEL_GAS_BUDGET,
- // address to call
- dt,
- // value in wei for call
- 0,
- // memory location to copy for calldata
- add(lowLevelCalldata, 32),
- // length of memory to copy for calldata
- mload(lowLevelCalldata),
- // memory location to copy return data
- returnData,
- // byte size of return data to copy to memory
- 32
- )
+ if (_delegationApprover != address(0) && msg.sender != _delegationApprover && msg.sender != operator) {
+ // check the signature expiry
+ require(
+ approverSignatureAndExpiry.expiry >= block.timestamp,
+ "DelegationManager._delegate: approver signature expired"
+ );
+ // check that the salt hasn't been used previously, then mark the salt as spent
+ require(
+ !delegationApproverSaltIsSpent[_delegationApprover][approverSalt],
+ "DelegationManager._delegate: approverSalt already spent"
+ );
+ delegationApproverSaltIsSpent[_delegationApprover][approverSalt] = true;
+
+ // calculate the digest hash
+ bytes32 approverDigestHash = calculateDelegationApprovalDigestHash(
+ staker,
+ operator,
+ _delegationApprover,
+ approverSalt,
+ approverSignatureAndExpiry.expiry
+ );
+
+ // actually check that the signature is valid
+ EIP1271SignatureUtils.checkSignature_EIP1271(
+ _delegationApprover,
+ approverDigestHash,
+ approverSignatureAndExpiry.signature
+ );
}
- // if the call fails, we emit a special event rather than reverting
- if (!success) {
- emit OnDelegationReceivedCallFailure(dt, returnData[0]);
+
+ // record the delegation relation between the staker and operator, and emit an event
+ delegatedTo[staker] = operator;
+ emit StakerDelegated(staker, operator);
+
+ (IStrategy[] memory strategies, uint256[] memory shares)
+ = getDelegatableShares(staker);
+
+ for (uint256 i = 0; i < strategies.length;) {
+ _increaseOperatorShares({
+ operator: operator,
+ staker: staker,
+ strategy: strategies[i],
+ shares: shares[i]
+ });
+
+ unchecked { ++i; }
}
}
- /**
- * @notice Makes a low-level call to `dt.onDelegationWithdrawn(staker, strategies, shares)`, ignoring reverts and with a gas budget
- * equal to `LOW_LEVEL_GAS_BUDGET` (a constant defined in this contract).
- * @dev *If* the low-level call fails, then this function emits the event `OnDelegationReceivedCallFailure(dt, returnData)`, where
- * `returnData` is *only the first 32 bytes* returned by the call to `dt`.
+ /**
+ * @dev commented-out param (middlewareTimesIndex) is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
+ * This param is intended to be passed on to the Slasher contract, but is unused in the M2 release of these contracts, and is thus commented-out.
*/
- function _delegationWithdrawnHook(
- IDelegationTerms dt,
- address staker,
- IStrategy[] memory strategies,
+ function _completeQueuedWithdrawal(
+ Withdrawal calldata withdrawal,
+ IERC20[] calldata tokens,
+ uint256 /*middlewareTimesIndex*/,
+ bool receiveAsTokens
+ ) internal {
+ bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal);
+
+ require(
+ pendingWithdrawals[withdrawalRoot],
+ "DelegationManager._completeQueuedWithdrawal: action is not in queue"
+ );
+
+ require(
+ withdrawal.startBlock + minWithdrawalDelayBlocks <= block.number,
+ "DelegationManager._completeQueuedWithdrawal: minWithdrawalDelayBlocks period has not yet passed"
+ );
+
+ require(
+ msg.sender == withdrawal.withdrawer,
+ "DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action"
+ );
+
+ if (receiveAsTokens) {
+ require(
+ tokens.length == withdrawal.strategies.length,
+ "DelegationManager._completeQueuedWithdrawal: input length mismatch"
+ );
+ }
+
+ // Remove `withdrawalRoot` from pending roots
+ delete pendingWithdrawals[withdrawalRoot];
+
+ // Finalize action by converting shares to tokens for each strategy, or
+ // 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,
+ strategy: withdrawal.strategies[i],
+ shares: withdrawal.shares[i],
+ token: tokens[i]
+ });
+ unchecked { ++i; }
+ }
+ // Award shares back in StrategyManager/EigenPodManager. If withdrawer is delegated, increase the shares delegated to the operator
+ } 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.
+ */
+ if (withdrawal.strategies[i] == beaconChainETHStrategy) {
+ address staker = withdrawal.staker;
+ /**
+ * Update shares amount depending upon the returned value.
+ * The return value will be lower than the input value in the case where the staker has an existing share deficit
+ */
+ uint256 increaseInDelegateableShares = eigenPodManager.addShares({
+ podOwner: staker,
+ shares: withdrawal.shares[i]
+ });
+ address podOwnerOperator = delegatedTo[staker];
+ // Similar to `isDelegated` logic
+ if (podOwnerOperator != address(0)) {
+ _increaseOperatorShares({
+ operator: podOwnerOperator,
+ // the 'staker' here is the address receiving new shares
+ staker: staker,
+ strategy: withdrawal.strategies[i],
+ shares: increaseInDelegateableShares
+ });
+ }
+ } else {
+ strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]);
+ // Similar to `isDelegated` logic
+ if (currentOperator != address(0)) {
+ _increaseOperatorShares({
+ operator: currentOperator,
+ // the 'staker' here is the address receiving new shares
+ staker: msg.sender,
+ strategy: withdrawal.strategies[i],
+ shares: withdrawal.shares[i]
+ });
+ }
+ }
+ unchecked { ++i; }
+ }
+ }
+
+ emit WithdrawalCompleted(withdrawalRoot);
+ }
+
+ // @notice Increases `operator`s delegated shares in `strategy` by `shares` and emits an `OperatorSharesIncreased` event
+ function _increaseOperatorShares(address operator, address staker, IStrategy strategy, uint256 shares) internal {
+ operatorShares[operator][strategy] += shares;
+ emit OperatorSharesIncreased(operator, staker, strategy, shares);
+ }
+
+ // @notice Decreases `operator`s delegated shares in `strategy` by `shares` and emits an `OperatorSharesDecreased` event
+ function _decreaseOperatorShares(address operator, address staker, IStrategy strategy, uint256 shares) internal {
+ // This will revert on underflow, so no check needed
+ operatorShares[operator][strategy] -= shares;
+ emit OperatorSharesDecreased(operator, staker, strategy, shares);
+ }
+
+ /**
+ * @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,
+ address operator,
+ address withdrawer,
+ IStrategy[] memory strategies,
uint256[] memory shares
- )
- internal
- {
- /**
- * We use low-level call functionality here to ensure that an operator cannot maliciously make this function fail in order to prevent undelegation.
- * In particular, in-line assembly is also used to prevent the copying of uncapped return data which is also a potential DoS vector.
- */
- // format calldata
- bytes memory lowLevelCalldata = abi.encodeWithSelector(IDelegationTerms.onDelegationWithdrawn.selector, staker, strategies, shares);
- // Prepare memory for low-level call return data. We accept a max return data length of 32 bytes
- bool success;
- bytes32[1] memory returnData;
- // actually make the call
- assembly {
- success := call(
- // gas provided to this context
- LOW_LEVEL_GAS_BUDGET,
- // address to call
- dt,
- // value in wei for call
- 0,
- // memory location to copy for calldata
- add(lowLevelCalldata, 32),
- // length of memory to copy for calldata
- mload(lowLevelCalldata),
- // memory location to copy return data
- returnData,
- // byte size of return data to copy to memory
- 32
- )
+ ) internal returns (bytes32) {
+ require(staker != address(0), "DelegationManager._removeSharesAndQueueWithdrawal: staker cannot be zero address");
+ require(strategies.length != 0, "DelegationManager._removeSharesAndQueueWithdrawal: strategies cannot be empty");
+
+ // Remove shares from staker and operator
+ // Each of these operations fail if we attempt to remove more shares than exist
+ for (uint256 i = 0; i < strategies.length;) {
+ // Similar to `isDelegated` logic
+ if (operator != address(0)) {
+ _decreaseOperatorShares({
+ operator: operator,
+ staker: staker,
+ strategy: strategies[i],
+ shares: shares[i]
+ });
+ }
+
+ // Remove active shares from EigenPodManager/StrategyManager
+ if (strategies[i] == beaconChainETHStrategy) {
+ /**
+ * This call will revert if it would reduce the Staker's virtual beacon chain ETH shares below zero.
+ * This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
+ * shares from the operator to whom the staker is delegated.
+ * It will also revert if the share amount being withdrawn is not a whole Gwei amount.
+ */
+ 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]);
+ }
+
+ unchecked { ++i; }
}
- // if the call fails, we emit a special event rather than reverting
- if (!success) {
- emit OnDelegationWithdrawnCallFailure(dt, returnData[0]);
+
+ // Create queue entry and increment withdrawal nonce
+ uint256 nonce = cumulativeWithdrawalsQueued[staker];
+ cumulativeWithdrawalsQueued[staker]++;
+
+ Withdrawal memory withdrawal = Withdrawal({
+ staker: staker,
+ delegatedTo: operator,
+ withdrawer: withdrawer,
+ nonce: nonce,
+ startBlock: uint32(block.number),
+ strategies: strategies,
+ shares: shares
+ });
+
+ bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal);
+
+ // Place withdrawal in queue
+ pendingWithdrawals[withdrawalRoot] = true;
+
+ emit WithdrawalQueued(withdrawalRoot, withdrawal);
+ return withdrawalRoot;
+ }
+
+ /**
+ * @notice Withdraws `shares` in `strategy` to `withdrawer`. If the shares are virtual beaconChainETH shares, then a call is ultimately forwarded to the
+ * `staker`s EigenPod; otherwise a call is ultimately forwarded to the `strategy` with info on the `token`.
+ */
+ function _withdrawSharesAsTokens(address staker, address withdrawer, IStrategy strategy, uint256 shares, IERC20 token) internal {
+ if (strategy == beaconChainETHStrategy) {
+ eigenPodManager.withdrawSharesAsTokens({
+ podOwner: staker,
+ destination: withdrawer,
+ shares: shares
+ });
+ } else {
+ strategyManager.withdrawSharesAsTokens(withdrawer, strategy, shares, token);
}
}
+ function _setMinWithdrawalDelayBlocks(uint256 _minWithdrawalDelayBlocks) internal {
+ require(
+ _minWithdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS,
+ "DelegationManager._setMinWithdrawalDelayBlocks: _minWithdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
+ );
+ emit MinWithdrawalDelayBlocksSet(minWithdrawalDelayBlocks, _minWithdrawalDelayBlocks);
+ minWithdrawalDelayBlocks = _minWithdrawalDelayBlocks;
+ }
+
/**
- * @notice Internal function implementing the delegation *from* `staker` *to* `operator`.
- * @param staker The address to delegate *from* -- this address is delegating control of its own assets.
- * @param operator The address to delegate *to* -- this address is being given power to place the `staker`'s assets at risk on services
- * @dev Ensures that the operator has registered as a delegate (`address(dt) != address(0)`), verifies that `staker` is not already
- * delegated, and records the new delegation.
- */
- function _delegate(address staker, address operator) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) {
- IDelegationTerms dt = delegationTerms[operator];
+ * @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(
- address(dt) != address(0), "DelegationManager._delegate: operator has not yet registered as a delegate"
+ _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
+ );
+ }
+ }
- require(isNotDelegated(staker), "DelegationManager._delegate: staker has existing delegation");
- // checks that operator has not been frozen
- require(!slasher.isFrozen(operator), "DelegationManager._delegate: cannot delegate to a frozen operator");
+ /*******************************************************************************
+ VIEW FUNCTIONS
+ *******************************************************************************/
- // record delegation relation between the staker and operator
- delegatedTo[staker] = operator;
+ /**
+ * @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.
+ * @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature collision.
+ * for more detailed information please read EIP-712.
+ */
+ function domainSeparator() public view returns (bytes32) {
+ if (block.chainid == ORIGINAL_CHAIN_ID) {
+ return _DOMAIN_SEPARATOR;
+ } else {
+ return _calculateDomainSeparator();
+ }
+ }
- // retrieve list of strategies and their shares from strategy manager
- (IStrategy[] memory strategies, uint256[] memory shares) = strategyManager.getDeposits(staker);
+ /**
+ * @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
+ */
+ function isDelegated(address staker) public view returns (bool) {
+ return (delegatedTo[staker] != address(0));
+ }
- // add strategy shares to delegate's shares
- uint256 stratsLength = strategies.length;
- for (uint256 i = 0; i < stratsLength;) {
- // update the share amounts for each of the operator's strategies
- operatorShares[operator][strategies[i]] += shares[i];
- unchecked {
- ++i;
+ /**
+ * @notice Returns true is an operator has previously registered for delegation.
+ */
+ function isOperator(address operator) public view returns (bool) {
+ return (_operatorDetails[operator].earningsReceiver != address(0));
+ }
+
+ /**
+ * @notice Returns the OperatorDetails struct associated with an `operator`.
+ */
+ function operatorDetails(address operator) external view returns (OperatorDetails memory) {
+ return _operatorDetails[operator];
+ }
+
+ /*
+ * @notice Returns the earnings receiver address for an operator
+ */
+ function earningsReceiver(address operator) external view returns (address) {
+ return _operatorDetails[operator].earningsReceiver;
+ }
+
+ /**
+ * @notice Returns the delegationApprover account for an operator
+ */
+ function delegationApprover(address operator) external view returns (address) {
+ return _operatorDetails[operator].delegationApprover;
+ }
+
+ /**
+ * @notice Returns the stakerOptOutWindowBlocks for an operator
+ */
+ function stakerOptOutWindowBlocks(address operator) external view returns (uint256) {
+ 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.
+ */
+ function getDelegatableShares(address staker) public view returns (IStrategy[] memory, uint256[] memory) {
+ // Get currently active shares and strategies for `staker`
+ int256 podShares = eigenPodManager.podOwnerShares(staker);
+ (IStrategy[] memory strategyManagerStrats, uint256[] memory strategyManagerShares)
+ = strategyManager.getDeposits(staker);
+
+ // Has no shares in EigenPodManager, but potentially some in StrategyManager
+ if (podShares <= 0) {
+ return (strategyManagerStrats, strategyManagerShares);
+ }
+
+ IStrategy[] memory strategies;
+ uint256[] memory shares;
+
+ if (strategyManagerStrats.length == 0) {
+ // Has shares in EigenPodManager, but not in StrategyManager
+ strategies = new IStrategy[](1);
+ shares = new uint256[](1);
+ strategies[0] = beaconChainETHStrategy;
+ shares[0] = uint256(podShares);
+ } else {
+ // Has shares in both
+
+ // 1. Allocate return arrays
+ strategies = new IStrategy[](strategyManagerStrats.length + 1);
+ shares = new uint256[](strategies.length);
+
+ // 2. Place StrategyManager strats/shares in return arrays
+ for (uint256 i = 0; i < strategyManagerStrats.length; ) {
+ strategies[i] = strategyManagerStrats[i];
+ shares[i] = strategyManagerShares[i];
+
+ unchecked { ++i; }
}
+
+ // 3. Place EigenPodManager strat/shares in return arrays
+ strategies[strategies.length - 1] = beaconChainETHStrategy;
+ shares[strategies.length - 1] = uint256(podShares);
}
- // call into hook in delegationTerms contract
- _delegationReceivedHook(dt, staker, strategies, shares);
+ return (strategies, shares);
}
- // VIEW FUNCTIONS
+ /**
+ * @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 'true' if `staker` *is* actively delegated, and 'false' otherwise.
- function isDelegated(address staker) public view returns (bool) {
- return (delegatedTo[staker] != address(0));
+ /// @notice Returns the keccak256 hash of `withdrawal`.
+ function calculateWithdrawalRoot(Withdrawal memory withdrawal) public pure returns (bytes32) {
+ return keccak256(abi.encode(withdrawal));
}
- /// @notice Returns 'true' if `staker` is *not* actively delegated, and 'false' otherwise.
- function isNotDelegated(address staker) public view returns (bool) {
- return (delegatedTo[staker] == address(0));
+ /**
+ * @notice Calculates the digestHash for a `staker` to sign to delegate to an `operator`
+ * @param staker The signing staker
+ * @param operator The operator who is being delegated to
+ * @param expiry The desired expiry time of the staker's signature
+ */
+ function calculateCurrentStakerDelegationDigestHash(
+ address staker,
+ address operator,
+ uint256 expiry
+ ) external view returns (bytes32) {
+ // fetch the staker's current nonce
+ uint256 currentStakerNonce = stakerNonce[staker];
+ // calculate the digest hash
+ return calculateStakerDelegationDigestHash(staker, currentStakerNonce, operator, expiry);
}
- /// @notice Returns if an operator can be delegated to, i.e. it has called `registerAsOperator`.
- function isOperator(address operator) public view returns (bool) {
- return (address(delegationTerms[operator]) != address(0));
+ /**
+ * @notice Calculates the digest hash to be signed and used in the `delegateToBySignature` function
+ * @param staker The signing staker
+ * @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at `stakerNonce[staker]`
+ * @param operator The operator who is being delegated to
+ * @param expiry The desired expiry time of the staker's signature
+ */
+ function calculateStakerDelegationDigestHash(
+ address staker,
+ uint256 _stakerNonce,
+ address operator,
+ uint256 expiry
+ ) public view returns (bytes32) {
+ // calculate the struct hash
+ bytes32 stakerStructHash = keccak256(
+ abi.encode(STAKER_DELEGATION_TYPEHASH, staker, operator, _stakerNonce, expiry)
+ );
+ // calculate the digest hash
+ bytes32 stakerDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), stakerStructHash));
+ return stakerDigestHash;
+ }
+
+ /**
+ * @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo` and `delegateToBySignature` functions.
+ * @param staker The account delegating their stake
+ * @param operator The account receiving delegated stake
+ * @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in general)
+ * @param approverSalt A unique and single use value associated with the approver signature.
+ * @param expiry Time after which the approver's signature becomes invalid
+ */
+ function calculateDelegationApprovalDigestHash(
+ address staker,
+ address operator,
+ address _delegationApprover,
+ bytes32 approverSalt,
+ uint256 expiry
+ ) public view returns (bytes32) {
+ // calculate the struct hash
+ bytes32 approverStructHash = keccak256(
+ abi.encode(DELEGATION_APPROVAL_TYPEHASH, _delegationApprover, staker, operator, approverSalt, expiry)
+ );
+ // calculate the digest hash
+ bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash));
+ return approverDigestHash;
+ }
+
+ /**
+ * @dev Recalculates the domain separator when the chainid changes due to a fork.
+ */
+ function _calculateDomainSeparator() internal view returns (bytes32) {
+ return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
}
}
diff --git a/src/contracts/core/DelegationManagerStorage.sol b/src/contracts/core/DelegationManagerStorage.sol
index 8bc8642c53..8a86af6d3e 100644
--- a/src/contracts/core/DelegationManagerStorage.sol
+++ b/src/contracts/core/DelegationManagerStorage.sol
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../interfaces/IStrategyManager.sol";
-import "../interfaces/IDelegationTerms.sol";
import "../interfaces/IDelegationManager.sol";
import "../interfaces/ISlasher.sol";
+import "../interfaces/IEigenPodManager.sol";
/**
* @title Storage variables for the `DelegationManager` contract.
@@ -13,19 +13,24 @@ import "../interfaces/ISlasher.sol";
* @notice This storage contract is separate from the logic to simplify the upgrade process.
*/
abstract contract DelegationManagerStorage is IDelegationManager {
- /// @notice Gas budget provided in calls to DelegationTerms contracts
- uint256 internal constant LOW_LEVEL_GAS_BUDGET = 1e5;
-
/// @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 delegation struct used by the contract
- bytes32 public constant DELEGATION_TYPEHASH =
- keccak256("Delegation(address delegator,address operator,uint256 nonce,uint256 expiry)");
+ /// @notice The EIP-712 typehash for the `StakerDelegation` struct used by the contract
+ bytes32 public constant STAKER_DELEGATION_TYPEHASH =
+ keccak256("StakerDelegation(address staker,address operator,uint256 nonce,uint256 expiry)");
+
+ /// @notice The EIP-712 typehash for the `DelegationApproval` struct used by the contract
+ bytes32 public constant DELEGATION_APPROVAL_TYPEHASH =
+ keccak256("DelegationApproval(address delegationApprover,address staker,address operator,bytes32 salt,uint256 expiry)");
- /// @notice EIP-712 Domain separator
- bytes32 public DOMAIN_SEPARATOR;
+ /**
+ * @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 The StrategyManager contract for EigenLayer
IStrategyManager public immutable strategyManager;
@@ -33,20 +38,72 @@ abstract contract DelegationManagerStorage is IDelegationManager {
/// @notice The Slasher contract for EigenLayer
ISlasher public immutable slasher;
- /// @notice Mapping: operator => strategy => total number of shares in the strategy delegated to the operator
+ /// @notice The EigenPodManager contract for EigenLayer
+ IEigenPodManager public immutable eigenPodManager;
+
+ // 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`.
+ * @notice Mapping: operator => strategy => total number of shares in the strategy delegated to the operator.
+ * @dev By design, the following invariant should hold for each Strategy:
+ * (operator's shares in delegation manager) = sum (shares above zero of all stakers delegated to operator)
+ * = sum (delegateable shares of all stakers delegated to the operator)
+ */
mapping(address => mapping(IStrategy => uint256)) public operatorShares;
- /// @notice Mapping: operator => delegation terms contract
- mapping(address => IDelegationTerms) public delegationTerms;
+ /**
+ * @notice Mapping: operator => OperatorDetails struct
+ * @dev This struct is internal with an external getter so we can return an `OperatorDetails memory` object
+ */
+ mapping(address => OperatorDetails) internal _operatorDetails;
- /// @notice Mapping: staker => operator whom the staker has delegated to
+ /**
+ * @notice Mapping: staker => operator whom the staker is currently delegated to.
+ * @dev Note that returning address(0) indicates that the staker is not actively delegated to any operator.
+ */
mapping(address => address) public delegatedTo;
- /// @notice Mapping: delegator => number of signed delegation nonce (used in delegateToBySignature)
- mapping(address => uint256) public nonces;
+ /// @notice Mapping: staker => number of signed messages (used in `delegateToBySignature`) from the staker that this contract has already checked.
+ mapping(address => uint256) public stakerNonce;
+
+ /**
+ * @notice Mapping: delegationApprover => 32-byte salt => whether or not the salt has already been used by the delegationApprover.
+ * @dev Salts are used in the `delegateTo` and `delegateToBySignature` functions. Note that these functions only process the delegationApprover's
+ * signature + the provided salt if the operator being delegated to has specified a nonzero address as their `delegationApprover`.
+ */
+ mapping(address => mapping(bytes32 => bool)) public delegationApproverSaltIsSpent;
+
+ /**
+ * @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 minWithdrawalDelayBlocks;
+
+ /// @notice Mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending
+ mapping(bytes32 => bool) public pendingWithdrawals;
+
+ /// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated.
+ /// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals have unique hashes.
+ mapping(address => uint256) public cumulativeWithdrawalsQueued;
+
+ /// @notice Deprecated from an old Goerli release
+ /// See conversation here: https://github.com/Layr-Labs/eigenlayer-contracts/pull/365/files#r1417525270
+ address private __deprecated_stakeRegistry;
+
+ /**
+ * @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) {
+ constructor(IStrategyManager _strategyManager, ISlasher _slasher, IEigenPodManager _eigenPodManager) {
strategyManager = _strategyManager;
+ eigenPodManager = _eigenPodManager;
slasher = _slasher;
}
@@ -55,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[46] private __gap;
-}
\ No newline at end of file
+ uint256[39] private __gap;
+}
diff --git a/src/contracts/core/Slasher.sol b/src/contracts/core/Slasher.sol
index 5f5f315fa6..359383e84c 100644
--- a/src/contracts/core/Slasher.sol
+++ b/src/contracts/core/Slasher.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../interfaces/ISlasher.sol";
import "../interfaces/IDelegationManager.sol";
@@ -9,555 +9,94 @@ import "../permissions/Pausable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
+
/**
- * @title The primary 'slashing' contract for EigenLayer.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract specifies details on slashing. The functionalities are:
- * - adding contracts who have permission to perform slashing,
- * - revoking permission for slashing from specified contracts,
- * - tracking historic stake updates to ensure that withdrawals can only be completed once no middlewares have slashing rights
- * over the funds being withdrawn
- */
+ * @notice This contract is not in use as of the Eigenlayer M2 release.
+ *
+ * Although many contracts reference it as an immutable variable, they do not
+ * interact with it and it is effectively dead code. The Slasher was originally
+ * deployed during Eigenlayer M1, but remained paused and unused for the duration
+ * of that release as well.
+ *
+ * Eventually, slashing design will be finalized and the Slasher will be finished
+ * and more fully incorporated into the core contracts. For now, you can ignore this
+ * file. If you really want to see what the deployed M1 version looks like, check
+ * out the `init-mainnet-deployment` branch under "releases".
+ *
+ * This contract is a stub that maintains its original interface for use in testing
+ * and deploy scripts. Otherwise, it does nothing.
+ */
contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable {
- using StructuredLinkedList for StructuredLinkedList.List;
-
- uint256 private constant HEAD = 0;
-
- uint8 internal constant PAUSED_OPT_INTO_SLASHING = 0;
- uint8 internal constant PAUSED_FIRST_STAKE_UPDATE = 1;
- uint8 internal constant PAUSED_NEW_FREEZING = 2;
-
- /// @notice The central StrategyManager contract of EigenLayer
- IStrategyManager public immutable strategyManager;
- /// @notice The DelegationManager contract of EigenLayer
- IDelegationManager public immutable delegation;
- // operator => whitelisted contract with slashing permissions => (the time before which the contract is allowed to slash the user, block it was last updated)
- mapping(address => mapping(address => MiddlewareDetails)) internal _whitelistedContractDetails;
- // staker => if their funds are 'frozen' and potentially subject to slashing or not
- mapping(address => bool) internal frozenStatus;
-
- uint32 internal constant MAX_CAN_SLASH_UNTIL = type(uint32).max;
-
- /**
- * operator => a linked list of the addresses of the whitelisted middleware with permission to slash the operator, i.e. which
- * the operator is serving. Sorted by the block at which they were last updated (content of updates below) in ascending order.
- * This means the 'HEAD' (i.e. start) of the linked list will have the stalest 'updateBlock' value.
- */
- mapping(address => StructuredLinkedList.List) internal _operatorToWhitelistedContractsByUpdate;
-
- /**
- * operator =>
- * [
- * (
- * the least recent update block of all of the middlewares it's serving/served,
- * latest time that the stake bonded at that update needed to serve until
- * )
- * ]
- */
- mapping(address => MiddlewareTimes[]) internal _operatorToMiddlewareTimes;
-
- /// @notice Emitted when a middleware times is added to `operator`'s array.
- event MiddlewareTimesAdded(address operator, uint256 index, uint32 stalestUpdateBlock, uint32 latestServeUntilBlock);
+
+ constructor(IStrategyManager, IDelegationManager) {}
- /// @notice Emitted when `operator` begins to allow `contractAddress` to slash them.
- event OptedIntoSlashing(address indexed operator, address indexed contractAddress);
-
- /// @notice Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilBlock`.
- event SlashingAbilityRevoked(address indexed operator, address indexed contractAddress, uint32 contractCanSlashOperatorUntilBlock);
-
- /**
- * @notice Emitted when `slashingContract` 'freezes' the `slashedOperator`.
- * @dev The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'.
- */
- event OperatorFrozen(address indexed slashedOperator, address indexed slashingContract);
-
- /// @notice Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within EigenLayer.
- event FrozenStatusReset(address indexed previouslySlashedAddress);
-
- constructor(IStrategyManager _strategyManager, IDelegationManager _delegation) {
- strategyManager = _strategyManager;
- delegation = _delegation;
- _disableInitializers();
- }
-
- /// @notice Ensures that the operator has opted into slashing by the caller, and that the caller has never revoked its slashing ability.
- modifier onlyRegisteredForService(address operator) {
- require(_whitelistedContractDetails[operator][msg.sender].contractCanSlashOperatorUntilBlock == MAX_CAN_SLASH_UNTIL,
- "Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- _;
- }
-
- // EXTERNAL FUNCTIONS
function initialize(
- address initialOwner,
- IPauserRegistry _pauserRegistry,
- uint256 initialPausedStatus
- ) external initializer {
- _initializePauser(_pauserRegistry, initialPausedStatus);
- _transferOwnership(initialOwner);
- }
-
- /**
- * @notice Gives the `contractAddress` permission to slash the funds of the caller.
- * @dev Typically, this function must be called prior to registering for a middleware.
- */
- function optIntoSlashing(address contractAddress) external onlyWhenNotPaused(PAUSED_OPT_INTO_SLASHING) {
- require(delegation.isOperator(msg.sender), "Slasher.optIntoSlashing: msg.sender is not a registered operator");
- _optIntoSlashing(msg.sender, contractAddress);
- }
-
- /**
- * @notice Used for 'slashing' a certain operator.
- * @param toBeFrozen The operator to be frozen.
- * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop.
- * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`.
- */
- function freezeOperator(address toBeFrozen) external onlyWhenNotPaused(PAUSED_NEW_FREEZING) {
- require(
- canSlash(toBeFrozen, msg.sender),
- "Slasher.freezeOperator: msg.sender does not have permission to slash this operator"
- );
- _freezeOperator(toBeFrozen, msg.sender);
- }
-
- /**
- * @notice Removes the 'frozen' status from each of the `frozenAddresses`
- * @dev Callable only by the contract owner (i.e. governance).
- */
- function resetFrozenStatus(address[] calldata frozenAddresses) external onlyOwner {
- for (uint256 i = 0; i < frozenAddresses.length;) {
- _resetFrozenStatus(frozenAddresses[i]);
- unchecked {
- ++i;
- }
- }
- }
-
- /**
- * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
- * is slashable until serveUntilBlock
- * @param operator the operator whose stake update is being recorded
- * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
- * @dev adds the middleware's slashing contract to the operator's linked list
- */
- function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock)
- external
- onlyWhenNotPaused(PAUSED_FIRST_STAKE_UPDATE)
- onlyRegisteredForService(operator)
- {
- // update the 'stalest' stakes update time + latest 'serveUntil' time of the `operator`
- _recordUpdateAndAddToMiddlewareTimes(operator, uint32(block.number), serveUntilBlock);
-
- // Push the middleware to the end of the update list. This will fail if the caller *is* already in the list.
- require(_operatorToWhitelistedContractsByUpdate[operator].pushBack(_addressToUint(msg.sender)),
- "Slasher.recordFirstStakeUpdate: Appending middleware unsuccessful");
- }
-
- /**
- * @notice this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals)
- * to make sure the operator's stake at updateBlock is slashable until serveUntilBlock
- * @param operator the operator whose stake update is being recorded
- * @param updateBlock the block for which the stake update is being recorded
- * @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
- * @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after
- * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
- * but it is anticipated to be rare and not detrimental.
- */
- function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 insertAfter)
- external
- onlyRegisteredForService(operator)
- {
- // sanity check on input
- require(updateBlock <= block.number, "Slasher.recordStakeUpdate: cannot provide update for future block");
- // update the 'stalest' stakes update time + latest 'serveUntilBlock' of the `operator`
- _recordUpdateAndAddToMiddlewareTimes(operator, updateBlock, serveUntilBlock);
-
- /**
- * Move the middleware to its correct update position, determined by `updateBlock` and indicated via `insertAfter`.
- * If the the middleware is the only one in the list, then no need to mutate the list
- */
- if (_operatorToWhitelistedContractsByUpdate[operator].sizeOf() != 1) {
- // Remove the caller (middleware) from the list. This will fail if the caller is *not* already in the list.
- require(_operatorToWhitelistedContractsByUpdate[operator].remove(_addressToUint(msg.sender)) != 0,
- "Slasher.recordStakeUpdate: Removing middleware unsuccessful");
- // Run routine for updating the `operator`'s linked list of middlewares
- _updateMiddlewareList(operator, updateBlock, insertAfter);
- // if there is precisely one middleware in the list, then ensure that the caller is indeed the singular list entrant
- } else {
- require(_operatorToWhitelistedContractsByUpdate[operator].getHead() == _addressToUint(msg.sender),
- "Slasher.recordStakeUpdate: Caller is not the list entrant");
- }
- }
-
- /**
- * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
- * is slashable until serveUntilBlock
- * @param operator the operator whose stake update is being recorded
- * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
- * @dev removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to
- * slash `operator` once `serveUntilBlock` is reached
- */
- function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external onlyRegisteredForService(operator) {
- // update the 'stalest' stakes update time + latest 'serveUntilBlock' of the `operator`
- _recordUpdateAndAddToMiddlewareTimes(operator, uint32(block.number), serveUntilBlock);
- // remove the middleware from the list
- require(_operatorToWhitelistedContractsByUpdate[operator].remove(_addressToUint(msg.sender)) != 0,
- "Slasher.recordLastStakeUpdateAndRevokeSlashingAbility: Removing middleware unsuccessful");
- // revoke the middleware's ability to slash `operator` after `serverUntil`
- _revokeSlashingAbility(operator, msg.sender, serveUntilBlock);
- }
-
- // VIEW FUNCTIONS
-
- /// @notice Returns the block until which `serviceContract` is allowed to slash the `operator`.
- function contractCanSlashOperatorUntilBlock(address operator, address serviceContract) external view returns (uint32) {
- return _whitelistedContractDetails[operator][serviceContract].contractCanSlashOperatorUntilBlock;
- }
-
- /// @notice Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
- function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32) {
- return _whitelistedContractDetails[operator][serviceContract].latestUpdateBlock;
- }
-
- /*
- * @notice Returns `_whitelistedContractDetails[operator][serviceContract]`.
- * @dev A getter function like this appears to be necessary for returning a struct from storage in struct form, rather than as a tuple.
- */
- function whitelistedContractDetails(address operator, address serviceContract) external view returns (MiddlewareDetails memory) {
- return _whitelistedContractDetails[operator][serviceContract];
- }
-
-
- /**
- * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
- * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
- * and the staker's status is reset (to 'unfrozen').
- * @param staker The staker of interest.
- * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated
- * to an operator who has their status set to frozen. Otherwise returns 'false'.
- */
- function isFrozen(address staker) external view returns (bool) {
- if (frozenStatus[staker]) {
- return true;
- } else if (delegation.isDelegated(staker)) {
- address operatorAddress = delegation.delegatedTo(staker);
- return (frozenStatus[operatorAddress]);
- } else {
- return false;
- }
- }
-
- /// @notice Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`.
- function canSlash(address toBeSlashed, address slashingContract) public view returns (bool) {
- if (block.number < _whitelistedContractDetails[toBeSlashed][slashingContract].contractCanSlashOperatorUntilBlock) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * @notice Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used
- * to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `_operatorToMiddlewareTimes[operator]`). The specified
- * struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
- * This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event
- * that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist.
- * @param operator Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator,
- * this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartBlock`.
- * @param withdrawalStartBlock The block number at which the withdrawal was initiated.
- * @param middlewareTimesIndex Indicates an index in `_operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw
- * @dev The correct `middlewareTimesIndex` input should be computable off-chain.
- */
- function canWithdraw(address operator, uint32 withdrawalStartBlock, uint256 middlewareTimesIndex) external view returns (bool) {
- // if the operator has never registered for a middleware, just return 'true'
- if (_operatorToMiddlewareTimes[operator].length == 0) {
- return true;
- }
-
- // pull the MiddlewareTimes struct at the `middlewareTimesIndex`th position in `_operatorToMiddlewareTimes[operator]`
- MiddlewareTimes memory update = _operatorToMiddlewareTimes[operator][middlewareTimesIndex];
-
- /**
- * Case-handling for if the operator is not registered for any middlewares (i.e. they previously registered but are no longer registered for any),
- * AND the withdrawal was initiated after the 'stalestUpdateBlock' of the MiddlewareTimes struct specified by the provided `middlewareTimesIndex`.
- * NOTE: we check the 2nd of these 2 conditions first for gas efficiency, to help avoid an extra SLOAD in all other cases.
- */
- if (withdrawalStartBlock >= update.stalestUpdateBlock && _operatorToWhitelistedContractsByUpdate[operator].size == 0) {
- /**
- * In this case, we just check against the 'latestServeUntilBlock' of the last MiddlewareTimes struct. This is because the operator not being registered
- * for any middlewares (i.e. `_operatorToWhitelistedContractsByUpdate.size == 0`) means no new MiddlewareTimes structs will be being pushed, *and* the operator
- * will not be undertaking any new obligations (so just checking against the last entry is OK, unlike when the operator is actively registered for >=1 middleware).
- */
- update = _operatorToMiddlewareTimes[operator][_operatorToMiddlewareTimes[operator].length - 1];
- return (uint32(block.number) > update.latestServeUntilBlock);
- }
-
- /**
- * Make sure the stalest update block at the time of the update is strictly after `withdrawalStartBlock` and ensure that the current time
- * is after the `latestServeUntilBlock` of the update. This assures us that this that all middlewares were updated after the withdrawal began, and
- * that the stake is no longer slashable.
- */
- return(
- withdrawalStartBlock < update.stalestUpdateBlock
- &&
- uint32(block.number) > update.latestServeUntilBlock
- );
- }
+ address,
+ IPauserRegistry,
+ uint256
+ ) external {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator][arrayIndex]`.
- function operatorToMiddlewareTimes(address operator, uint256 arrayIndex) external view returns (MiddlewareTimes memory) {
- return _operatorToMiddlewareTimes[operator][arrayIndex];
- }
+ function optIntoSlashing(address) external {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator].length`.
- function middlewareTimesLength(address operator) external view returns (uint256) {
- return _operatorToMiddlewareTimes[operator].length;
- }
+ function freezeOperator(address) external {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
- function getMiddlewareTimesIndexBlock(address operator, uint32 index) external view returns (uint32) {
- return _operatorToMiddlewareTimes[operator][index].stalestUpdateBlock;
- }
+ function resetFrozenStatus(address[] calldata) external {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator][index].latestServeUntilBlock`.
- function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32) {
- return _operatorToMiddlewareTimes[operator][index].latestServeUntilBlock;
- }
+ function recordFirstStakeUpdate(address, uint32) external {}
- /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
- function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256) {
- return _operatorToWhitelistedContractsByUpdate[operator].size;
- }
+ function recordStakeUpdate(
+ address,
+ uint32,
+ uint32,
+ uint256
+ ) external {}
- /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
- function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256) {
- return StructuredLinkedList.getNode(_operatorToWhitelistedContractsByUpdate[operator], _addressToUint(node));
- }
+ function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external {}
- /**
- * @notice A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`.
- * @dev Used within this contract only as a fallback in the case when an incorrect value of `insertAfter` is supplied as an input to `_updateMiddlewareList`.
- * @dev The return value should *either* be 'HEAD' (i.e. zero) in the event that the node being inserted in the linked list has an `updateBlock`
- * that is less than the HEAD of the list, *or* the return value should specify the last `node` in the linked list for which
- * `_whitelistedContractDetails[operator][node].latestUpdateBlock <= updateBlock`,
- * i.e. the node such that the *next* node either doesn't exist,
- * OR
- * `_whitelistedContractDetails[operator][nextNode].latestUpdateBlock > updateBlock`.
- */
- function getCorrectValueForInsertAfter(address operator, uint32 updateBlock) public view returns (uint256) {
- uint256 node = _operatorToWhitelistedContractsByUpdate[operator].getHead();
- /**
- * Special case:
- * If the node being inserted in the linked list has an `updateBlock` that is less than the HEAD of the list, then we set `insertAfter = HEAD`.
- * In _updateMiddlewareList(), the new node will be pushed to the front (HEAD) of the list.
- */
- if (_whitelistedContractDetails[operator][_uintToAddress(node)].latestUpdateBlock > updateBlock) {
- return HEAD;
- }
- /**
- * `node` being zero (i.e. equal to 'HEAD') indicates an empty/non-existent node, i.e. reaching the end of the linked list.
- * Since the linked list is ordered in ascending order of update blocks, we simply start from the head of the list and step through until
- * we find a the *last* `node` for which `_whitelistedContractDetails[operator][node].latestUpdateBlock <= updateBlock`, or
- * otherwise reach the end of the list.
- */
- (, uint256 nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(node);
- while ((nextNode != HEAD) && (_whitelistedContractDetails[operator][_uintToAddress(node)].latestUpdateBlock <= updateBlock)) {
- node = nextNode;
- (, nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(node);
- }
- return node;
- }
+ function strategyManager() external view returns (IStrategyManager) {}
- /// @notice gets the node previous to the given node in the operators middleware update linked list
- /// @dev used in offchain libs for updating stakes
- function getPreviousWhitelistedContractByUpdate(address operator, uint256 node) external view returns (bool, uint256) {
- return _operatorToWhitelistedContractsByUpdate[operator].getPreviousNode(node);
- }
+ function delegation() external view returns (IDelegationManager) {}
- // INTERNAL FUNCTIONS
+ function isFrozen(address) external view returns (bool) {}
- function _optIntoSlashing(address operator, address contractAddress) internal {
- //allow the contract to slash anytime before a time VERY far in the future
- _whitelistedContractDetails[operator][contractAddress].contractCanSlashOperatorUntilBlock = MAX_CAN_SLASH_UNTIL;
- emit OptedIntoSlashing(operator, contractAddress);
- }
+ function canSlash(address, address) external view returns (bool) {}
- function _revokeSlashingAbility(address operator, address contractAddress, uint32 serveUntilBlock) internal {
- require(serveUntilBlock != MAX_CAN_SLASH_UNTIL, "Slasher._revokeSlashingAbility: serveUntilBlock time must be limited");
- // contractAddress can now only slash operator before `serveUntilBlock`
- _whitelistedContractDetails[operator][contractAddress].contractCanSlashOperatorUntilBlock = serveUntilBlock;
- emit SlashingAbilityRevoked(operator, contractAddress, serveUntilBlock);
- }
+ function contractCanSlashOperatorUntilBlock(
+ address,
+ address
+ ) external view returns (uint32) {}
- function _freezeOperator(address toBeFrozen, address slashingContract) internal {
- if (!frozenStatus[toBeFrozen]) {
- frozenStatus[toBeFrozen] = true;
- emit OperatorFrozen(toBeFrozen, slashingContract);
- }
- }
+ function latestUpdateBlock(address, address) external view returns (uint32) {}
- function _resetFrozenStatus(address previouslySlashedAddress) internal {
- if (frozenStatus[previouslySlashedAddress]) {
- frozenStatus[previouslySlashedAddress] = false;
- emit FrozenStatusReset(previouslySlashedAddress);
- }
- }
+ function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) {}
- /**
- * @notice records the most recent updateBlock for the currently updating middleware and appends an entry to the operator's list of
- * MiddlewareTimes if relavent information has updated
- * @param operator the entity whose stake update is being recorded
- * @param updateBlock the block number for which the currently updating middleware is updating the serveUntilBlock for
- * @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
- * @dev this function is only called during externally called stake updates by middleware contracts that can slash operator
- */
- function _recordUpdateAndAddToMiddlewareTimes(address operator, uint32 updateBlock, uint32 serveUntilBlock) internal {
- // reject any stale update, i.e. one from before that of the most recent recorded update for the currently updating middleware
- require(_whitelistedContractDetails[operator][msg.sender].latestUpdateBlock <= updateBlock,
- "Slasher._recordUpdateAndAddToMiddlewareTimes: can't push a previous update");
- _whitelistedContractDetails[operator][msg.sender].latestUpdateBlock = updateBlock;
- // get the latest recorded MiddlewareTimes, if the operator's list of MiddlwareTimes is non empty
- MiddlewareTimes memory curr;
- uint256 _operatorToMiddlewareTimesLength = _operatorToMiddlewareTimes[operator].length;
- if (_operatorToMiddlewareTimesLength != 0) {
- curr = _operatorToMiddlewareTimes[operator][_operatorToMiddlewareTimesLength - 1];
- }
- MiddlewareTimes memory next = curr;
- bool pushToMiddlewareTimes;
- // if the serve until is later than the latest recorded one, update it
- if (serveUntilBlock > curr.latestServeUntilBlock) {
- next.latestServeUntilBlock = serveUntilBlock;
- // mark that we need push next to middleware times array because it contains new information
- pushToMiddlewareTimes = true;
- }
-
- // If this is the very first middleware added to the operator's list of middleware, then we add an entry to _operatorToMiddlewareTimes
- if (_operatorToWhitelistedContractsByUpdate[operator].size == 0) {
- next.stalestUpdateBlock = updateBlock;
- pushToMiddlewareTimes = true;
- }
- // If the middleware is the first in the list, we will update the `stalestUpdateBlock` field in MiddlewareTimes
- else if (_operatorToWhitelistedContractsByUpdate[operator].getHead() == _addressToUint(msg.sender)) {
- // if the updated middleware was the earliest update, set it to the 2nd earliest update's update time
- (bool hasNext, uint256 nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(_addressToUint(msg.sender));
+ function canWithdraw(
+ address,
+ uint32,
+ uint256
+ ) external returns (bool) {}
- if (hasNext) {
- // get the next middleware's latest update block
- uint32 nextMiddlewaresLeastRecentUpdateBlock = _whitelistedContractDetails[operator][_uintToAddress(nextNode)].latestUpdateBlock;
- if (nextMiddlewaresLeastRecentUpdateBlock < updateBlock) {
- // if there is a next node, then set the stalestUpdateBlock to its recorded value
- next.stalestUpdateBlock = nextMiddlewaresLeastRecentUpdateBlock;
- } else {
- //otherwise updateBlock is the least recent update as well
- next.stalestUpdateBlock = updateBlock;
- }
- } else {
- // otherwise this is the only middleware so right now is the stalestUpdateBlock
- next.stalestUpdateBlock = updateBlock;
- }
- // mark that we need to push `next` to middleware times array because it contains new information
- pushToMiddlewareTimes = true;
- }
-
- // if `next` has new information, then push it
- if (pushToMiddlewareTimes) {
- _operatorToMiddlewareTimes[operator].push(next);
- emit MiddlewareTimesAdded(operator, _operatorToMiddlewareTimes[operator].length - 1, next.stalestUpdateBlock, next.latestServeUntilBlock);
- }
- }
+ function operatorToMiddlewareTimes(
+ address,
+ uint256
+ ) external view returns (MiddlewareTimes memory) {}
- /// @notice A routine for updating the `operator`'s linked list of middlewares, inside `recordStakeUpdate`.
- function _updateMiddlewareList(address operator, uint32 updateBlock, uint256 insertAfter) internal {
- /**
- * boolean used to track if the `insertAfter input to this function is incorrect. If it is, then `runFallbackRoutine` will
- * be flipped to 'true', and we will use `getCorrectValueForInsertAfter` to find the correct input. This routine helps solve
- * a race condition where the proper value of `insertAfter` changes while a transaction is pending.
- */
-
- bool runFallbackRoutine = false;
- // If this condition is met, then the `updateBlock` input should be after `insertAfter`'s latest updateBlock
- if (insertAfter != HEAD) {
- // Check that `insertAfter` exists. If not, we will use the fallback routine to find the correct value for `insertAfter`.
- if (!_operatorToWhitelistedContractsByUpdate[operator].nodeExists(insertAfter)) {
- runFallbackRoutine = true;
- }
+ function middlewareTimesLength(address) external view returns (uint256) {}
- /**
- * Make sure `insertAfter` specifies a node for which the most recent updateBlock was *at or before* updateBlock.
- * Again, if not, we will use the fallback routine to find the correct value for `insertAfter`.
- */
- if ((!runFallbackRoutine) && (_whitelistedContractDetails[operator][_uintToAddress(insertAfter)].latestUpdateBlock > updateBlock)) {
- runFallbackRoutine = true;
- }
+ function getMiddlewareTimesIndexStalestUpdateBlock(address, uint32) external view returns (uint32) {}
- // if we have not marked `runFallbackRoutine` as 'true' yet, then that means the `insertAfter` input was correct so far
- if (!runFallbackRoutine) {
- // Get `insertAfter`'s successor. `hasNext` will be false if `insertAfter` is the last node in the list
- (bool hasNext, uint256 nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(insertAfter);
- if (hasNext) {
- /**
- * Make sure the element after `insertAfter`'s most recent updateBlock was *strictly after* `updateBlock`.
- * Again, if not, we will use the fallback routine to find the correct value for `insertAfter`.
- */
- if (_whitelistedContractDetails[operator][_uintToAddress(nextNode)].latestUpdateBlock <= updateBlock) {
- runFallbackRoutine = true;
- }
- }
- }
+ function getMiddlewareTimesIndexServeUntilBlock(address, uint32) external view returns (uint32) {}
- // if we have not marked `runFallbackRoutine` as 'true' yet, then that means the `insertAfter` input was correct on all counts
- if (!runFallbackRoutine) {
- /**
- * Insert the caller (middleware) after `insertAfter`.
- * This will fail if `msg.sender` is already in the list, which they shouldn't be because they were removed from the list above.
- */
- require(_operatorToWhitelistedContractsByUpdate[operator].insertAfter(insertAfter, _addressToUint(msg.sender)),
- "Slasher.recordStakeUpdate: Inserting middleware unsuccessful");
- // in this case (runFallbackRoutine == true), we run a search routine to find the correct input value of `insertAfter` and then rerun this function
- } else {
- insertAfter = getCorrectValueForInsertAfter(operator, updateBlock);
- _updateMiddlewareList(operator, updateBlock, insertAfter);
- }
- // In this case (insertAfter == HEAD), the `updateBlock` input should be before every other middleware's latest updateBlock.
- } else {
- /**
- * Check that `updateBlock` is before any other middleware's latest updateBlock.
- * If not, use the fallback routine to find the correct value for `insertAfter`.
- */
- if (_whitelistedContractDetails[operator][
- _uintToAddress(_operatorToWhitelistedContractsByUpdate[operator].getHead()) ].latestUpdateBlock <= updateBlock)
- {
- runFallbackRoutine = true;
- }
- // if we have not marked `runFallbackRoutine` as 'true' yet, then that means the `insertAfter` input was correct on all counts
- if (!runFallbackRoutine) {
- /**
- * Insert the middleware at the start (i.e. HEAD) of the list.
- * This will fail if `msg.sender` is already in the list, which they shouldn't be because they were removed from the list above.
- */
- require(_operatorToWhitelistedContractsByUpdate[operator].pushFront(_addressToUint(msg.sender)),
- "Slasher.recordStakeUpdate: Preppending middleware unsuccessful");
- // in this case (runFallbackRoutine == true), we run a search routine to find the correct input value of `insertAfter` and then rerun this function
- } else {
- insertAfter = getCorrectValueForInsertAfter(operator, updateBlock);
- _updateMiddlewareList(operator, updateBlock, insertAfter);
- }
- }
- }
+ function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) {}
- function _addressToUint(address addr) internal pure returns(uint256) {
- return uint256(uint160(addr));
- }
+ function operatorWhitelistedContractsLinkedListEntry(
+ address,
+ address
+ ) external view returns (bool, uint256, uint256) {}
- function _uintToAddress(uint256 x) internal pure returns(address) {
- return address(uint160(x));
- }
+ function whitelistedContractDetails(
+ address,
+ address
+ ) external view returns (MiddlewareDetails memory) {}
- /**
- * @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[46] private __gap;
}
diff --git a/src/contracts/core/StrategyManager.sol b/src/contracts/core/StrategyManager.sol
index a70c0bf098..05db175605 100644
--- a/src/contracts/core/StrategyManager.sol
+++ b/src/contracts/core/StrategyManager.sol
@@ -1,16 +1,14 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "@openzeppelin/contracts/interfaces/IERC1271.sol";
-import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../interfaces/IEigenPodManager.sol";
import "../permissions/Pausable.sol";
import "./StrategyManagerStorage.sol";
+import "../libraries/EIP1271SignatureUtils.sol";
/**
* @title The primary entry- and exit-point for funds into and out of EigenLayer.
@@ -20,9 +18,6 @@ import "./StrategyManagerStorage.sol";
* functionalities are:
* - adding and removing strategies that any delegator can deposit into
* - enabling deposit of assets into specified strategy(s)
- * - enabling withdrawal of assets from specified strategy(s)
- * - recording deposit of ETH into settlement layer
- * - slashing of assets for permissioned strategies
*/
contract StrategyManager is
Initializable,
@@ -33,92 +28,30 @@ contract StrategyManager is
{
using SafeERC20 for IERC20;
- uint256 internal constant GWEI_TO_WEI = 1e9;
-
// index for flag that pauses deposits when set
uint8 internal constant PAUSED_DEPOSITS = 0;
- // index for flag that pauses withdrawals when set
- uint8 internal constant PAUSED_WITHDRAWALS = 1;
-
- uint256 immutable ORIGINAL_CHAIN_ID;
-
- // bytes4(keccak256("isValidSignature(bytes32,bytes)")
- bytes4 constant internal ERC1271_MAGICVALUE = 0x1626ba7e;
-
- /**
- * @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
- );
+ // chain id at the time of contract deployment
+ uint256 internal immutable ORIGINAL_CHAIN_ID;
- /// @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);
-
- modifier onlyNotFrozen(address staker) {
+ modifier onlyStrategyWhitelister() {
require(
- !slasher.isFrozen(staker),
- "StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"
+ msg.sender == strategyWhitelister,
+ "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"
);
_;
}
- modifier onlyFrozen(address staker) {
- require(slasher.isFrozen(staker), "StrategyManager.onlyFrozen: staker has not been frozen");
- _;
- }
-
- modifier onlyEigenPodManager {
- require(address(eigenPodManager) == msg.sender, "StrategyManager.onlyEigenPodManager: not the eigenPodManager");
- _;
- }
-
- modifier onlyStrategyWhitelister {
- require(msg.sender == strategyWhitelister, "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister");
+ modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) {
+ require(
+ strategyIsWhitelistedForDeposit[strategy],
+ "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"
+ );
_;
}
- modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) {
- require(strategyIsWhitelistedForDeposit[strategy], "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted");
+ modifier onlyDelegationManager() {
+ require(msg.sender == address(delegation), "StrategyManager.onlyDelegationManager: not the DelegationManager");
_;
}
@@ -127,9 +60,11 @@ contract StrategyManager is
* @param _slasher The primary slashing contract of EigenLayer.
* @param _eigenPodManager The contract that keeps track of EigenPod stakes for restaking beacon chain ether.
*/
- constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher)
- StrategyManagerStorage(_delegation, _eigenPodManager, _slasher)
- {
+ constructor(
+ IDelegationManager _delegation,
+ IEigenPodManager _eigenPodManager,
+ ISlasher _slasher
+ ) StrategyManagerStorage(_delegation, _eigenPodManager, _slasher) {
_disableInitializers();
ORIGINAL_CHAIN_ID = block.chainid;
}
@@ -143,99 +78,46 @@ contract StrategyManager is
* @param initialOwner Ownership of this contract is transferred to this address.
* @param initialStrategyWhitelister The initial value of `strategyWhitelister` to set.
* @param initialPausedStatus The initial value of `_paused` to set.
- * @param _withdrawalDelayBlocks The initial value of `withdrawalDelayBlocks` to set.
*/
- function initialize(address initialOwner, address initialStrategyWhitelister, IPauserRegistry _pauserRegistry, uint256 initialPausedStatus, uint256 _withdrawalDelayBlocks)
- external
- initializer
- {
- DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), ORIGINAL_CHAIN_ID, address(this)));
+ function initialize(
+ address initialOwner,
+ address initialStrategyWhitelister,
+ IPauserRegistry _pauserRegistry,
+ uint256 initialPausedStatus
+ ) external initializer {
+ _DOMAIN_SEPARATOR = _calculateDomainSeparator();
_initializePauser(_pauserRegistry, initialPausedStatus);
_transferOwnership(initialOwner);
_setStrategyWhitelister(initialStrategyWhitelister);
- _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
- }
-
- /**
- * @notice Deposits `amount` of beaconchain ETH into this contract on behalf of `staker`
- * @param staker is the entity that is restaking in eigenlayer,
- * @param amount is the amount of beaconchain ETH being restaked,
- * @dev Only callable by EigenPodManager.
- */
- function depositBeaconChainETH(address staker, uint256 amount)
- external
- onlyEigenPodManager
- onlyWhenNotPaused(PAUSED_DEPOSITS)
- onlyNotFrozen(staker)
- nonReentrant
- {
- // add shares for the enshrined beacon chain ETH strategy
- _addShares(staker, beaconChainETHStrategy, amount);
- }
-
- /**
- * @notice Records an overcommitment event on behalf of a staker. The staker's beaconChainETH shares are decremented by `amount`.
- * @param overcommittedPodOwner is the pod owner to be slashed
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy in case it must be removed,
- * @param amount is the amount to decrement the slashedAddress's beaconChainETHStrategy shares
- * @dev Only callable by EigenPodManager.
- */
- function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount)
- external
- onlyEigenPodManager
- nonReentrant
- {
- // get `overcommittedPodOwner`'s shares in the enshrined beacon chain ETH strategy
- uint256 userShares = stakerStrategyShares[overcommittedPodOwner][beaconChainETHStrategy];
- // if the amount exceeds the user's shares, then record it as an amount to be "paid off" when the user completes a withdrawal
- if (amount > userShares) {
- uint256 debt = amount - userShares;
- beaconChainETHSharesToDecrementOnWithdrawal[overcommittedPodOwner] += debt;
- amount -= debt;
- }
- // removes shares for the enshrined beacon chain ETH strategy
- if (amount != 0) {
- _removeShares(overcommittedPodOwner, beaconChainETHStrategyIndex, beaconChainETHStrategy, amount);
- }
- // create array wrappers for call to DelegationManager
- IStrategy[] memory strategies = new IStrategy[](1);
- strategies[0] = beaconChainETHStrategy;
- uint256[] memory shareAmounts = new uint256[](1);
- shareAmounts[0] = amount;
- // modify delegated shares accordingly, if applicable
- delegation.decreaseDelegatedShares(overcommittedPodOwner, strategies, shareAmounts);
}
/**
* @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
* @param strategy is the specified strategy where deposit is to be made,
* @param token is the denomination in which the deposit is to be made,
- * @param amount is the amount of token to be deposited in the strategy by the depositor
+ * @param amount is the amount of token to be deposited in the strategy by the staker
* @return shares The amount of new shares in the `strategy` created as part of the action.
* @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
- * @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
- *
+ *
* WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
* where the token balance and corresponding strategy shares are not in sync upon reentrancy.
*/
- function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount)
- external
- onlyWhenNotPaused(PAUSED_DEPOSITS)
- onlyNotFrozen(msg.sender)
- nonReentrant
- returns (uint256 shares)
- {
+ function depositIntoStrategy(
+ IStrategy strategy,
+ IERC20 token,
+ uint256 amount
+ ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
shares = _depositIntoStrategy(msg.sender, strategy, token, amount);
}
/**
* @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
* who must sign off on the action.
- * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
+ * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
* purely to help one address deposit 'for' another.
* @param strategy is the specified strategy where deposit is to be made,
* @param token is the denomination in which the deposit is to be made,
- * @param amount is the amount of token to be deposited in the strategy by the depositor
+ * @param amount is the amount of token to be deposited in the strategy by the staker
* @param staker the staker that the deposited assets will be credited to
* @param expiry the timestamp at which the signature expires
* @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward
@@ -244,8 +126,8 @@ 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 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
*/
@@ -256,359 +138,94 @@ contract StrategyManager is
address staker,
uint256 expiry,
bytes memory signature
- )
- external
- onlyWhenNotPaused(PAUSED_DEPOSITS)
- onlyNotFrozen(staker)
- nonReentrant
- returns (uint256 shares)
- {
+ ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
require(
- expiry >= block.timestamp,
- "StrategyManager.depositIntoStrategyWithSignature: signature expired"
+ !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;
}
- bytes32 digestHash;
- //if chainid has changed, we must re-compute the domain separator
- if (block.chainid != ORIGINAL_CHAIN_ID) {
- bytes32 domain_separator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
- digestHash = keccak256(abi.encodePacked("\x19\x01", domain_separator, structHash));
- } else {
- digestHash = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash));
- }
-
+ // calculate the digest hash
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
/**
* check validity of signature:
* 1) if `staker` is an EOA, then `signature` must be a valid ECDSA signature from `staker`,
* indicating their intention for this action
- * 2) if `staker` is a contract, then `signature` must will be checked according to EIP-1271
+ * 2) if `staker` is a contract, then `signature` will be checked according to EIP-1271
*/
- if (Address.isContract(staker)) {
- require(IERC1271(staker).isValidSignature(digestHash, signature) == ERC1271_MAGICVALUE,
- "StrategyManager.depositIntoStrategyWithSignature: ERC1271 signature verification failed");
- } else {
- require(ECDSA.recover(digestHash, signature) == staker,
- "StrategyManager.depositIntoStrategyWithSignature: signature not from staker");
- }
+ EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
+ // deposit the tokens (from the `msg.sender`) and credit the new shares to the `staker`
shares = _depositIntoStrategy(staker, strategy, token, amount);
}
- /**
- * @notice Called by a staker to undelegate entirely from EigenLayer. The staker must first withdraw all of their existing deposits
- * (through use of the `queueWithdrawal` function), or else otherwise have never deposited in EigenLayer prior to delegating.
- */
- function undelegate() external {
- _undelegate(msg.sender);
- }
-
- /**
- * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
- * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
- * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
- * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
- * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
- * that the value per share reported by each strategy will remain consistent, and that the shares will continue
- * to accrue gains during the enforced withdrawal waiting period.
- * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
- * for which `msg.sender` is withdrawing 100% of their shares
- * @param strategies The Strategies to withdraw from
- * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
- * @param withdrawer The address that can complete the withdrawal and will receive any withdrawn funds or shares upon completing the withdrawal
- * @param undelegateIfPossible If this param is marked as 'true' *and the withdrawal will result in `msg.sender` having no shares in any Strategy,*
- * then this function will also make an internal call to `undelegate(msg.sender)` to undelegate the `msg.sender`.
- * @return The 'withdrawalRoot' of the newly created Queued Withdrawal
- * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
- * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
- * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
- * `stakerStrategyList` to lowest index
- * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
- * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed
- * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
- * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
- */
- function queueWithdrawal(
- uint256[] calldata strategyIndexes,
- IStrategy[] calldata strategies,
- uint256[] calldata shares,
- address withdrawer,
- bool undelegateIfPossible
- )
- external
- onlyWhenNotPaused(PAUSED_WITHDRAWALS)
- onlyNotFrozen(msg.sender)
- nonReentrant
- returns (bytes32)
- {
- require(strategies.length == shares.length, "StrategyManager.queueWithdrawal: input length mismatch");
- require(withdrawer != address(0), "StrategyManager.queueWithdrawal: cannot withdraw to zero address");
-
- // modify delegated shares accordingly, if applicable
- delegation.decreaseDelegatedShares(msg.sender, strategies, shares);
-
- uint96 nonce = uint96(numWithdrawalsQueued[msg.sender]);
-
- // keeps track of the current index in the `strategyIndexes` array
- uint256 strategyIndexIndex;
-
- /**
- * Ensure that if the withdrawal includes beacon chain ETH, the specified 'withdrawer' is not different than the caller.
- * This is because shares in the enshrined `beaconChainETHStrategy` ultimately represent tokens in **non-fungible** EigenPods,
- * while other share in all other strategies represent purely fungible positions.
- */
- for (uint256 i = 0; i < strategies.length;) {
- if (strategies[i] == beaconChainETHStrategy) {
- require(withdrawer == msg.sender,
- "StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH to a different address");
- require(strategies.length == 1,
- "StrategyManager.queueWithdrawal: cannot queue a withdrawal including Beacon Chain ETH and other tokens");
- require(shares[i] % GWEI_TO_WEI == 0,
- "StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei");
- }
-
- // the internal function will return 'true' in the event the strategy was
- // removed from the depositor's array of strategies -- i.e. stakerStrategyList[depositor]
- if (_removeShares(msg.sender, strategyIndexes[strategyIndexIndex], strategies[i], shares[i])) {
- unchecked {
- ++strategyIndexIndex;
- }
- }
-
- emit ShareWithdrawalQueued(msg.sender, nonce, strategies[i], shares[i]);
-
- //increment the loop
- unchecked {
- ++i;
- }
- }
-
- // fetch the address that the `msg.sender` is delegated to
- address delegatedAddress = delegation.delegatedTo(msg.sender);
-
- QueuedWithdrawal memory queuedWithdrawal;
-
- {
- WithdrawerAndNonce memory withdrawerAndNonce = WithdrawerAndNonce({
- withdrawer: withdrawer,
- nonce: nonce
- });
- // increment the numWithdrawalsQueued of the sender
- unchecked {
- numWithdrawalsQueued[msg.sender] = nonce + 1;
- }
-
- // copy arguments into struct and pull delegation info
- queuedWithdrawal = QueuedWithdrawal({
- strategies: strategies,
- shares: shares,
- depositor: msg.sender,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: delegatedAddress
- });
-
- }
-
- // calculate the withdrawal root
- bytes32 withdrawalRoot = calculateWithdrawalRoot(queuedWithdrawal);
-
- // mark withdrawal as pending
- withdrawalRootPending[withdrawalRoot] = true;
-
- // If the `msg.sender` has withdrawn all of their funds from EigenLayer in this transaction, then they can choose to also undelegate
- /**
- * Checking that `stakerStrategyList[msg.sender].length == 0` is not strictly necessary here, but prevents reverting very late in logic,
- * in the case that 'undelegate' is set to true but the `msg.sender` still has active deposits in EigenLayer.
- */
- if (undelegateIfPossible && stakerStrategyList[msg.sender].length == 0) {
- _undelegate(msg.sender);
- }
-
- emit WithdrawalQueued(msg.sender, nonce, withdrawer, delegatedAddress, withdrawalRoot);
-
- return withdrawalRoot;
- }
-
- /**
- * @notice Used to complete the specified `queuedWithdrawal`. The function caller must match `queuedWithdrawal.withdrawer`
- * @param queuedWithdrawal The QueuedWithdrawal to complete.
- * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array
- * of the `queuedWithdrawal`. This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
- * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
- * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves
- * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
- * will simply be transferred to the caller directly.
- * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
- */
- function completeQueuedWithdrawal(QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256 middlewareTimesIndex, bool receiveAsTokens)
- external
- onlyWhenNotPaused(PAUSED_WITHDRAWALS)
- // check that the address that the staker *was delegated to* – at the time that they queued the withdrawal – is not frozen
- nonReentrant
- {
- _completeQueuedWithdrawal(queuedWithdrawal, tokens, middlewareTimesIndex, receiveAsTokens);
+ /// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the withdrawal queue
+ function removeShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+ ) external onlyDelegationManager {
+ _removeShares(staker, strategy, shares);
}
- /**
- * @notice Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer`
- * @param queuedWithdrawals The QueuedWithdrawals to complete.
- * @param tokens Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
- * @param middlewareTimesIndexes One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
- * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves
- * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
- * will simply be transferred to the caller directly.
- * @dev Array-ified version of `completeQueuedWithdrawal`
- * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
- */
- function completeQueuedWithdrawals(
- QueuedWithdrawal[] calldata queuedWithdrawals,
- IERC20[][] calldata tokens,
- uint256[] calldata middlewareTimesIndexes,
- bool[] calldata receiveAsTokens
- ) external
- onlyWhenNotPaused(PAUSED_WITHDRAWALS)
- // check that the address that the staker *was delegated to* – at the time that they queued the withdrawal – is not frozen
- nonReentrant
- {
- for(uint256 i = 0; i < queuedWithdrawals.length; i++) {
- _completeQueuedWithdrawal(queuedWithdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]);
- }
+ /// @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, token, strategy, shares);
}
- /**
- * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
- * @param slashedAddress is the frozen address that is having its shares slashed
- * @param recipient is the address that will receive the slashed funds, which could e.g. be a harmed party themself,
- * or a MerkleDistributor-type contract that further sub-divides the slashed funds.
- * @param strategies Strategies to slash
- * @param shareAmounts The amount of shares to slash in each of the provided `strategies`
- * @param tokens The tokens to use as input to the `withdraw` function of each of the provided `strategies`
- * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
- * for which `msg.sender` is withdrawing 100% of their shares
- * @param recipient The slashed funds are withdrawn as tokens to this address.
- * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
- * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
- * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
- * `stakerStrategyList` to lowest index
- */
- function slashShares(
- address slashedAddress,
+ /// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient
+ function withdrawSharesAsTokens(
address recipient,
- IStrategy[] calldata strategies,
- IERC20[] calldata tokens,
- uint256[] calldata strategyIndexes,
- uint256[] calldata shareAmounts
- )
- external
- onlyOwner
- onlyFrozen(slashedAddress)
- nonReentrant
- {
- require(tokens.length == strategies.length, "StrategyManager.slashShares: input length mismatch");
- uint256 strategyIndexIndex;
- uint256 strategiesLength = strategies.length;
- for (uint256 i = 0; i < strategiesLength;) {
- // the internal function will return 'true' in the event the strategy was
- // removed from the slashedAddress's array of strategies -- i.e. stakerStrategyList[slashedAddress]
- if (_removeShares(slashedAddress, strategyIndexes[strategyIndexIndex], strategies[i], shareAmounts[i])) {
- unchecked {
- ++strategyIndexIndex;
- }
- }
-
- if (strategies[i] == beaconChainETHStrategy) {
- //withdraw the beaconChainETH to the recipient
- _withdrawBeaconChainETH(slashedAddress, recipient, shareAmounts[i]);
- }
- else {
- // withdraw the shares and send funds to the recipient
- strategies[i].withdraw(recipient, tokens[i], shareAmounts[i]);
- }
-
- // increment the loop
- unchecked {
- ++i;
- }
- }
-
- // modify delegated shares accordingly, if applicable
- delegation.decreaseDelegatedShares(slashedAddress, strategies, shareAmounts);
+ IStrategy strategy,
+ uint256 shares,
+ IERC20 token
+ ) external onlyDelegationManager {
+ strategy.withdraw(recipient, token, shares);
}
-
- /**
- * @notice Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one)
- * @param recipient The funds in the slashed withdrawal are withdrawn as tokens to this address.
- * @param queuedWithdrawal The previously queued withdrawal to be slashed
- * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies`
- * array of the `queuedWithdrawal`.
- * @param indicesToSkip Optional input parameter -- indices in the `strategies` array to skip (i.e. not call the 'withdraw' function on). This input exists
- * so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function,
- * then the malicious strategy can be skipped (with the shares in effect "burned"), while the non-malicious strategies are still called as normal.
- */
- function slashQueuedWithdrawal(address recipient, QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256[] calldata indicesToSkip)
- external
- onlyOwner
- onlyFrozen(queuedWithdrawal.delegatedAddress)
- nonReentrant
- {
- require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.slashQueuedWithdrawal: input length mismatch");
-
- // find the withdrawalRoot
- bytes32 withdrawalRoot = calculateWithdrawalRoot(queuedWithdrawal);
-
- // verify that the queued withdrawal is pending
- require(
- withdrawalRootPending[withdrawalRoot],
- "StrategyManager.slashQueuedWithdrawal: withdrawal is not pending"
- );
- // reset the storage slot in mapping of queued withdrawals
- withdrawalRootPending[withdrawalRoot] = false;
-
- // keeps track of the index in the `indicesToSkip` array
- uint256 indicesToSkipIndex = 0;
-
- uint256 strategiesLength = queuedWithdrawal.strategies.length;
- for (uint256 i = 0; i < strategiesLength;) {
- // check if the index i matches one of the indices specified in the `indicesToSkip` array
- if (indicesToSkipIndex < indicesToSkip.length && indicesToSkip[indicesToSkipIndex] == i) {
- unchecked {
- ++indicesToSkipIndex;
- }
- } else {
- if (queuedWithdrawal.strategies[i] == beaconChainETHStrategy){
- //withdraw the beaconChainETH to the recipient
- _withdrawBeaconChainETH(queuedWithdrawal.depositor, recipient, queuedWithdrawal.shares[i]);
- } else {
- // tell the strategy to send the appropriate amount of funds to the recipient
- queuedWithdrawal.strategies[i].withdraw(recipient, tokens[i], queuedWithdrawal.shares[i]);
- }
- }
- unchecked {
- ++i;
- }
+ /// @notice Function called by the DelegationManager as part of the process of transferring existing queued withdrawals from this contract to that contract.
+ /// @dev This function is expected to be removed in the next upgrade, after all queued withdrawals have been migrated.
+ function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external onlyDelegationManager returns(bool, bytes32) {
+ bytes32 existingWithdrawalRoot = calculateWithdrawalRoot(queuedWithdrawal);
+ bool isDeleted;
+ // Delete the withdrawal root if it exists
+ if (withdrawalRootPending[existingWithdrawalRoot]) {
+ withdrawalRootPending[existingWithdrawalRoot] = false;
+ isDeleted = true;
}
+ return (isDeleted, existingWithdrawalRoot);
}
/**
- * @notice Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
- * @param _withdrawalDelayBlocks new value of `withdrawalDelayBlocks`.
- */
- function setWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) external onlyOwner {
- _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
+ * 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`.
- */
+ */
function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {
_setStrategyWhitelister(newStrategyWhitelister);
}
@@ -616,288 +233,190 @@ 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)
- */
- function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external onlyStrategyWhitelister {
+ * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
+ */
+ function addStrategiesToDepositWhitelist(
+ 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;) {
+ 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;
}
}
- }
+ }
/**
* @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
* @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it)
- */
- function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external onlyStrategyWhitelister {
+ */
+ function removeStrategiesFromDepositWhitelist(
+ IStrategy[] calldata strategiesToRemoveFromWhitelist
+ ) external onlyStrategyWhitelister {
uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length;
- for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength;) {
+ for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength; ) {
// change storage and emit event only if strategy is already in whitelist
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;
}
}
- }
+ }
// INTERNAL FUNCTIONS
/**
- * @notice This function adds `shares` for a given `strategy` to the `depositor` and runs through the necessary update logic.
- * @param depositor The address to add shares to
- * @param strategy The Strategy in which the `depositor` is receiving shares
- * @param shares The amount of shares to grant to the `depositor`
- * @dev In particular, this function calls `delegation.increaseDelegatedShares(depositor, strategy, shares)` to ensure that all
- * delegated shares are tracked, increases the stored share amount in `stakerStrategyShares[depositor][strategy]`, and adds `strategy`
- * to the `depositor`'s list of strategies, if it is not in the list already.
+ * @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 depositor, IStrategy strategy, uint256 shares) internal {
+ function _addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) internal {
// sanity checks on inputs
- require(depositor != address(0), "StrategyManager._addShares: depositor cannot be zero address");
+ require(staker != address(0), "StrategyManager._addShares: staker cannot be zero address");
require(shares != 0, "StrategyManager._addShares: shares should not be zero!");
// if they dont have existing shares of this strategy, add it to their strats
- if (stakerStrategyShares[depositor][strategy] == 0) {
+ if (stakerStrategyShares[staker][strategy] == 0) {
require(
- stakerStrategyList[depositor].length < MAX_STAKER_STRATEGY_LIST_LENGTH,
+ stakerStrategyList[staker].length < MAX_STAKER_STRATEGY_LIST_LENGTH,
"StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"
);
- stakerStrategyList[depositor].push(strategy);
+ stakerStrategyList[staker].push(strategy);
}
// add the returned shares to their existing shares for this strategy
- stakerStrategyShares[depositor][strategy] += shares;
+ stakerStrategyShares[staker][strategy] += shares;
- // if applicable, increase delegated shares accordingly
- delegation.increaseDelegatedShares(depositor, strategy, shares);
+ emit Deposit(staker, token, strategy, shares);
}
/**
* @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
- * `strategy`, with the resulting shares credited to `depositor`.
- * @param depositor The address that will be credited with the new shares.
+ * `strategy`, with the resulting shares credited to `staker`.
+ * @param staker The address that will be credited with the new shares.
* @param strategy The Strategy contract to deposit into.
* @param token The ERC20 token to deposit.
* @param amount The amount of `token` to deposit.
- * @return shares The amount of *new* shares in `strategy` that have been credited to the `depositor`.
+ * @return shares The amount of *new* shares in `strategy` that have been credited to the `staker`.
*/
- function _depositIntoStrategy(address depositor, IStrategy strategy, IERC20 token, uint256 amount)
- internal
- onlyStrategiesWhitelistedForDeposit(strategy)
- returns (uint256 shares)
- {
+ function _depositIntoStrategy(
+ address staker,
+ IStrategy strategy,
+ IERC20 token,
+ uint256 amount
+ ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
// transfer tokens from the sender to the strategy
token.safeTransferFrom(msg.sender, address(strategy), amount);
// deposit the assets into the specified strategy and get the equivalent amount of shares in that strategy
shares = strategy.deposit(token, amount);
- // add the returned shares to the depositor's existing shares for this strategy
- _addShares(depositor, strategy, shares);
+ // add the returned shares to the staker's existing shares for this strategy
+ _addShares(staker, token, strategy, shares);
+
+ // Increase shares delegated to operator, if needed
+ delegation.increaseDelegatedShares(staker, strategy, shares);
- emit Deposit(depositor, token, strategy, shares);
return shares;
}
/**
- * @notice Decreases the shares that `depositor` holds in `strategy` by `shareAmount`.
- * @param depositor The address to decrement shares from
- * @param strategyIndex The `strategyIndex` input for the internal `_removeStrategyFromStakerStrategyList`. Used only in the case that
- * the removal of the depositor's shares results in them having zero remaining shares in the `strategy`
- * @param strategy The strategy for which the `depositor`'s shares are being decremented
+ * @notice Decreases the shares that `staker` holds in `strategy` by `shareAmount`.
+ * @param staker The address to decrement shares from
+ * @param strategy The strategy for which the `staker`'s shares are being decremented
* @param shareAmount The amount of shares to decrement
- * @dev If the amount of shares represents all of the depositor`s shares in said strategy,
- * then the strategy is removed from stakerStrategyList[depositor] and 'true' is returned. Otherwise 'false' is returned.
+ * @dev If the amount of shares represents all of the staker`s shares in said strategy,
+ * then the strategy is removed from stakerStrategyList[staker] and 'true' is returned. Otherwise 'false' is returned.
*/
- function _removeShares(address depositor, uint256 strategyIndex, IStrategy strategy, uint256 shareAmount)
- internal
- returns (bool)
- {
+ function _removeShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shareAmount
+ ) internal returns (bool) {
// sanity checks on inputs
- require(depositor != address(0), "StrategyManager._removeShares: depositor cannot be zero address");
require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");
//check that the user has sufficient shares
- uint256 userShares = stakerStrategyShares[depositor][strategy];
-
+ uint256 userShares = stakerStrategyShares[staker][strategy];
+
require(shareAmount <= userShares, "StrategyManager._removeShares: shareAmount too high");
//unchecked arithmetic since we just checked this above
unchecked {
userShares = userShares - shareAmount;
}
- // subtract the shares from the depositor's existing shares for this strategy
- stakerStrategyShares[depositor][strategy] = userShares;
+ // subtract the shares from the staker's existing shares for this strategy
+ stakerStrategyShares[staker][strategy] = userShares;
- // if no existing shares, remove the strategy from the depositor's dynamic array of strategies
+ // if no existing shares, remove the strategy from the staker's dynamic array of strategies
if (userShares == 0) {
- _removeStrategyFromStakerStrategyList(depositor, strategyIndex, strategy);
+ _removeStrategyFromStakerStrategyList(staker, strategy);
- // return true in the event that the strategy was removed from stakerStrategyList[depositor]
+ // return true in the event that the strategy was removed from stakerStrategyList[staker]
return true;
}
- // return false in the event that the strategy was *not* removed from stakerStrategyList[depositor]
+ // return false in the event that the strategy was *not* removed from stakerStrategyList[staker]
return false;
}
/**
- * @notice Removes `strategy` from `depositor`'s dynamic array of strategies, i.e. from `stakerStrategyList[depositor]`
- * @param depositor The user whose array will have an entry removed
- * @param strategyIndex Preferably the index of `strategy` in `stakerStrategyList[depositor]`. If the input is incorrect, then a brute-force
- * fallback routine will be used to find the correct input
- * @param strategy The Strategy to remove from `stakerStrategyList[depositor]`
- * @dev the provided `strategyIndex` input is optimistically used to find the strategy quickly in the list. If the specified
- * index is incorrect, then we revert to a brute-force search.
+ * @notice Removes `strategy` from `staker`'s dynamic array of strategies, i.e. from `stakerStrategyList[staker]`
+ * @param staker The user whose array will have an entry removed
+ * @param strategy The Strategy to remove from `stakerStrategyList[staker]`
*/
- function _removeStrategyFromStakerStrategyList(address depositor, uint256 strategyIndex, IStrategy strategy) internal {
- // if the strategy matches with the strategy index provided
- if (stakerStrategyList[depositor][strategyIndex] == strategy) {
- // replace the strategy with the last strategy in the list
- stakerStrategyList[depositor][strategyIndex] =
- stakerStrategyList[depositor][stakerStrategyList[depositor].length - 1];
- } else {
- //loop through all of the strategies, find the right one, then replace
- uint256 stratsLength = stakerStrategyList[depositor].length;
- uint256 j = 0;
- for (; j < stratsLength;) {
- if (stakerStrategyList[depositor][j] == strategy) {
- //replace the strategy with the last strategy in the list
- stakerStrategyList[depositor][j] = stakerStrategyList[depositor][stakerStrategyList[depositor].length - 1];
- break;
- }
- unchecked {
- ++j;
- }
+ function _removeStrategyFromStakerStrategyList(
+ address staker,
+ IStrategy strategy
+ ) internal {
+ //loop through all of the strategies, find the right one, then replace
+ uint256 stratsLength = stakerStrategyList[staker].length;
+ uint256 j = 0;
+ for (; j < stratsLength; ) {
+ if (stakerStrategyList[staker][j] == strategy) {
+ //replace the strategy with the last strategy in the list
+ stakerStrategyList[staker][j] = stakerStrategyList[staker][
+ stakerStrategyList[staker].length - 1
+ ];
+ break;
}
- // if we didn't find the strategy, revert
- require(j != stratsLength, "StrategyManager._removeStrategyFromStakerStrategyList: strategy not found");
+ unchecked { ++j; }
}
+ // if we didn't find the strategy, revert
+ require(j != stratsLength, "StrategyManager._removeStrategyFromStakerStrategyList: strategy not found");
// pop off the last entry in the list of strategies
- stakerStrategyList[depositor].pop();
- }
-
- /**
- * @notice Internal function for completing the given `queuedWithdrawal`.
- * @param queuedWithdrawal The QueuedWithdrawal to complete
- * @param tokens The ERC20 tokens to provide as inputs to `Strategy.withdraw`. Only relevant if `receiveAsTokens = true`
- * @param middlewareTimesIndex Passed on as an input to the `slasher.canWithdraw` function, to ensure the withdrawal is completable.
- * @param receiveAsTokens If marked 'true', then calls will be passed on to the `Strategy.withdraw` function for each strategy.
- * If marked 'false', then the shares will simply be internally transferred to the `msg.sender`.
- */
- function _completeQueuedWithdrawal(QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256 middlewareTimesIndex, bool receiveAsTokens) onlyNotFrozen(queuedWithdrawal.delegatedAddress) internal {
- // find the withdrawalRoot
- bytes32 withdrawalRoot = calculateWithdrawalRoot(queuedWithdrawal);
-
- // verify that the queued withdrawal is pending
- require(
- withdrawalRootPending[withdrawalRoot],
- "StrategyManager.completeQueuedWithdrawal: withdrawal is not pending"
- );
-
- require(
- slasher.canWithdraw(queuedWithdrawal.delegatedAddress, queuedWithdrawal.withdrawalStartBlock, middlewareTimesIndex),
- "StrategyManager.completeQueuedWithdrawal: shares pending withdrawal are still slashable"
- );
-
- // enforce minimum delay lag (not applied to withdrawals of 'beaconChainETH', since the EigenPods enforce their own delay)
- require(queuedWithdrawal.withdrawalStartBlock + withdrawalDelayBlocks <= block.number
- || queuedWithdrawal.strategies[0] == beaconChainETHStrategy,
- "StrategyManager.completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed"
- );
-
- require(
- msg.sender == queuedWithdrawal.withdrawerAndNonce.withdrawer,
- "StrategyManager.completeQueuedWithdrawal: only specified withdrawer can complete a queued withdrawal"
- );
-
- // reset the storage slot in mapping of queued withdrawals
- withdrawalRootPending[withdrawalRoot] = false;
-
- // store length for gas savings
- uint256 strategiesLength = queuedWithdrawal.strategies.length;
- // if the withdrawer has flagged to receive the funds as tokens, withdraw from strategies
- if (receiveAsTokens) {
- require(tokens.length == queuedWithdrawal.strategies.length, "StrategyManager.completeQueuedWithdrawal: input length mismatch");
- // actually withdraw the funds
- for (uint256 i = 0; i < strategiesLength;) {
- if (queuedWithdrawal.strategies[i] == beaconChainETHStrategy) {
-
- // if the strategy is the beaconchaineth strat, then withdraw through the EigenPod flow
- _withdrawBeaconChainETH(queuedWithdrawal.depositor, msg.sender, queuedWithdrawal.shares[i]);
- } else {
- // tell the strategy to send the appropriate amount of funds to the depositor
- queuedWithdrawal.strategies[i].withdraw(
- msg.sender, tokens[i], queuedWithdrawal.shares[i]
- );
- }
- unchecked {
- ++i;
- }
- }
- } else {
- // else increase their shares
- for (uint256 i = 0; i < strategiesLength;) {
- _addShares(msg.sender, queuedWithdrawal.strategies[i], queuedWithdrawal.shares[i]);
- unchecked {
- ++i;
- }
- }
- }
- emit WithdrawalCompleted(queuedWithdrawal.depositor, queuedWithdrawal.withdrawerAndNonce.nonce, msg.sender, withdrawalRoot);
- }
-
- /**
- * @notice If the `depositor` has no existing shares, then they can `undelegate` themselves.
- * This allows people a "hard reset" in their relationship with EigenLayer after withdrawing all of their stake.
- * @param depositor The address to undelegate. Passed on as an input to the `delegation.undelegate` function.
- */
- function _undelegate(address depositor) internal onlyNotFrozen(depositor) {
- require(stakerStrategyList[depositor].length == 0, "StrategyManager._undelegate: depositor has active deposits");
- delegation.undelegate(depositor);
- }
-
- /*
- * @notice Withdraws `amount` of virtual 'beaconChainETH' shares from `staker`, with any successfully withdrawn funds going to `recipient`.
- * @param staker The address whose 'beaconChainETH' shares will be decremented
- * @param recipient Passed on as the recipient input to the `eigenPodManager.withdrawRestakedBeaconChainETH` function.
- * @param amount The amount of virtual 'beaconChainETH' shares to be 'withdrawn'
- * @dev First, the amount is drawn-down by any applicable 'beaconChainETHSharesToDecrementOnWithdrawal' that the staker has,
- * before passing any remaining amount (if applicable) onto a call to the `eigenPodManager.withdrawRestakedBeaconChainETH` function.
- */
- function _withdrawBeaconChainETH(address staker, address recipient, uint256 amount) internal {
- uint256 amountToDecrement = beaconChainETHSharesToDecrementOnWithdrawal[staker];
- if (amountToDecrement != 0) {
- if (amount > amountToDecrement) {
- beaconChainETHSharesToDecrementOnWithdrawal[staker] = 0;
- // decrease `amount` appropriately, so less is sent at the end
- amount -= amountToDecrement;
- } else {
- beaconChainETHSharesToDecrementOnWithdrawal[staker] = (amountToDecrement - amount);
- // rather than setting `amount` to 0, just return early
- return;
- }
- }
- // withdraw the beaconChainETH to the recipient
- eigenPodManager.withdrawRestakedBeaconChainETH(staker, recipient, amount);
+ stakerStrategyList[staker].pop();
}
/**
- * @notice internal function for changing the value of `withdrawalDelayBlocks`. Also performs sanity check and emits an event.
- * @param _withdrawalDelayBlocks The new value for `withdrawalDelayBlocks` to take.
+ * @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 _setWithdrawalDelayBlocks(uint256 _withdrawalDelayBlocks) internal {
- require(_withdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS, "StrategyManager.setWithdrawalDelay: _withdrawalDelayBlocks too high");
- emit WithdrawalDelayBlocksSet(withdrawalDelayBlocks, _withdrawalDelayBlocks);
- withdrawalDelayBlocks = _withdrawalDelayBlocks;
+ function _setThirdPartyTransfersForbidden(IStrategy strategy, bool value) internal {
+ emit UpdatedThirdPartyTransfersForbidden(strategy, value);
+ thirdPartyTransfersForbidden[strategy] = value;
}
/**
@@ -910,23 +429,22 @@ contract StrategyManager is
}
// VIEW FUNCTIONS
-
/**
- * @notice Get all details on the depositor's deposits and corresponding shares
- * @param depositor The staker of interest, whose deposits this function will fetch
- * @return (depositor's strategies, shares in these strategies)
+ * @notice Get all details on the staker's deposits and corresponding shares
+ * @param staker The staker of interest, whose deposits this function will fetch
+ * @return (staker's strategies, shares in these strategies)
*/
- function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory) {
- uint256 strategiesLength = stakerStrategyList[depositor].length;
+ function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory) {
+ uint256 strategiesLength = stakerStrategyList[staker].length;
uint256[] memory shares = new uint256[](strategiesLength);
- for (uint256 i = 0; i < strategiesLength;) {
- shares[i] = stakerStrategyShares[depositor][stakerStrategyList[depositor][i]];
+ for (uint256 i = 0; i < strategiesLength; ) {
+ shares[i] = stakerStrategyShares[staker][stakerStrategyList[staker][i]];
unchecked {
++i;
}
}
- return (stakerStrategyList[depositor], shares);
+ return (stakerStrategyList[staker], shares);
}
/// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
@@ -934,14 +452,32 @@ contract StrategyManager is
return stakerStrategyList[staker].length;
}
+ /**
+ * @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)));
+ }
+
+// LIMITED BACKWARDS-COMPATIBILITY FOR DEPRECATED FUNCTIONALITY
/// @notice Returns the keccak256 hash of `queuedWithdrawal`.
- function calculateWithdrawalRoot(QueuedWithdrawal memory queuedWithdrawal) public pure returns (bytes32) {
+ function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) public pure returns (bytes32) {
return (
keccak256(
abi.encode(
queuedWithdrawal.strategies,
queuedWithdrawal.shares,
- queuedWithdrawal.depositor,
+ queuedWithdrawal.staker,
queuedWithdrawal.withdrawerAndNonce,
queuedWithdrawal.withdrawalStartBlock,
queuedWithdrawal.delegatedAddress
diff --git a/src/contracts/core/StrategyManagerStorage.sol b/src/contracts/core/StrategyManagerStorage.sol
index ce807d6e15..b6c92a557d 100644
--- a/src/contracts/core/StrategyManagerStorage.sol
+++ b/src/contracts/core/StrategyManagerStorage.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../interfaces/IStrategyManager.sol";
import "../interfaces/IStrategy.sol";
@@ -19,12 +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)");
- /// @notice EIP-712 Domain separator
- bytes32 public DOMAIN_SEPARATOR;
- // staker => number of signed deposit nonce (used in depositIntoStrategyWithSignature)
- mapping(address => uint256) public nonces;
-
+ 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;
@@ -33,42 +28,49 @@ abstract contract StrategyManagerStorage is IStrategyManager {
IEigenPodManager public immutable eigenPodManager;
ISlasher public immutable slasher;
+ /**
+ * @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;
+ // staker => number of signed deposit nonce (used in depositIntoStrategyWithSignature)
+ mapping(address => uint256) public nonces;
/// @notice Permissioned role, which can be changed by the contract owner. Has the ability to edit the strategy whitelist
address public strategyWhitelister;
-
- /**
- * @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.
+ /*
+ * Reserved space previously used by the deprecated storage variable `withdrawalDelayBlocks.
+ * This variable was migrated to the DelegationManager instead.
*/
- 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;
-
+ uint256 internal withdrawalDelayBlocks;
/// @notice Mapping: staker => Strategy => number of shares which they currently hold
mapping(address => mapping(IStrategy => uint256)) public stakerStrategyShares;
/// @notice Mapping: staker => array of strategies in which they have nonzero shares
mapping(address => IStrategy[]) public stakerStrategyList;
- /// @notice Mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending
+ /// @notice *Deprecated* mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending
+ /// @dev This mapping is preserved to allow the migration of withdrawals to the DelegationManager contract.
mapping(bytes32 => bool) public withdrawalRootPending;
- /// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated. only increments (doesn't decrement)
- mapping(address => uint256) public numWithdrawalsQueued;
+ /*
+ * Reserved space previously used by the deprecated mapping(address => uint256) numWithdrawalsQueued.
+ * This mapping tracked the cumulative number of queued withdrawals initiated by a staker.
+ * Withdrawals are now initiated in the DlegationManager, so the mapping has moved to that contract.
+ */
+ mapping(address => uint256) internal numWithdrawalsQueued;
/// @notice Mapping: strategy => whether or not stakers are allowed to deposit into it
mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit;
/*
- * @notice Mapping: staker => virtual 'beaconChainETH' shares that the staker 'owes' due to overcommitments of beacon chain ETH.
- * When overcommitment is proven, `StrategyManager.recordOvercommittedBeaconChainETH` is called. However, it is possible that the
- * staker already queued a withdrawal for more beaconChainETH shares than the `amount` input to this function. In this edge case,
- * the amount that cannot be decremented is added to the staker's `beaconChainETHSharesToDecrementOnWithdrawal` -- then when the staker completes a
- * withdrawal of beaconChainETH, the amount they are withdrawing is first decreased by their `beaconChainETHSharesToDecrementOnWithdrawal` amount.
- * In other words, a staker's `beaconChainETHSharesToDecrementOnWithdrawal` must be 'paid down' before they can "actually withdraw" beaconChainETH.
- * @dev In practice, this means not passing a call to `eigenPodManager.withdrawRestakedBeaconChainETH` until the staker's
- * `beaconChainETHSharesToDecrementOnWithdrawal` has first been reduced to zero.
- */
- mapping(address => uint256) public beaconChainETHSharesToDecrementOnWithdrawal;
+ * Reserved space previously used by the deprecated mapping(address => uint256) beaconChainETHSharesToDecrementOnWithdrawal.
+ * This mapping tracked beaconChainETH "deficit" in cases where updates were made to shares retroactively. However, this construction was
+ * moved into the EigenPodManager contract itself.
+ */
+ mapping(address => uint256) internal beaconChainETHSharesToDecrementOnWithdrawal;
- IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+ /**
+ * @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;
@@ -81,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 0000000000..6187439ff8
--- /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/IBLSPublicKeyCompendium.sol b/src/contracts/interfaces/IBLSPublicKeyCompendium.sol
deleted file mode 100644
index 3d692d7b9b..0000000000
--- a/src/contracts/interfaces/IBLSPublicKeyCompendium.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../libraries/BN254.sol";
-
-/**
- * @title Minimal interface for the `BLSPublicKeyCompendium` contract.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-interface IBLSPublicKeyCompendium {
- /**
- * @notice mapping from operator address to pubkey hash.
- * Returns *zero* if the `operator` has never registered, and otherwise returns the hash of the public key of the operator.
- */
- function operatorToPubkeyHash(address operator) external view returns (bytes32);
-
- /**
- * @notice mapping from pubkey hash to operator address.
- * Returns *zero* if no operator has ever registered the public key corresponding to `pubkeyHash`,
- * and otherwise returns the (unique) registered operator who owns the BLS public key that is the preimage of `pubkeyHash`.
- */
- function pubkeyHashToOperator(bytes32 pubkeyHash) external view returns (address);
-
- /**
- * @notice Called by an operator to register themselves as the owner of a BLS public key and reveal their G1 and G2 public key.
- * @param s is the field element of the operator's Schnorr signature
- * @param rPoint is the group element of the operator's Schnorr signature
- * @param pubkeyG1 is the the G1 pubkey of the operator
- * @param pubkeyG2 is the G2 with the same private key as the pubkeyG1
- */
- function registerBLSPublicKey(uint256 s, BN254.G1Point memory rPoint, BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) external;
-}
diff --git a/src/contracts/interfaces/IBLSRegistry.sol b/src/contracts/interfaces/IBLSRegistry.sol
deleted file mode 100644
index b8f2a2b6aa..0000000000
--- a/src/contracts/interfaces/IBLSRegistry.sol
+++ /dev/null
@@ -1,47 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./IQuorumRegistry.sol";
-
-/**
- * @title Minimal interface extension to `IQuorumRegistry`.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice Adds BLS-specific functions to the base interface.
- */
-interface IBLSRegistry is IQuorumRegistry {
- /// @notice Data structure used to track the history of the Aggregate Public Key of all operators
- struct ApkUpdate {
- // keccak256(apk_x0, apk_x1, apk_y0, apk_y1)
- bytes32 apkHash;
- // block number at which the update occurred
- uint32 blockNumber;
- }
-
- /**
- * @notice get hash of a historical aggregated public key corresponding to a given index;
- * called by checkSignatures in BLSSignatureChecker.sol.
- */
- function getCorrectApkHash(uint256 index, uint32 blockNumber) external returns (bytes32);
-
- /// @notice returns the `ApkUpdate` struct at `index` in the list of APK updates
- function apkUpdates(uint256 index) external view returns (ApkUpdate memory);
-
- /// @notice returns the APK hash that resulted from the `index`th APK update
- function apkHashes(uint256 index) external view returns (bytes32);
-
- /// @notice returns the block number at which the `index`th APK update occurred
- function apkUpdateBlockNumbers(uint256 index) external view returns (uint32);
-
- function operatorWhitelister() external view returns(address);
-
- function operatorWhitelistEnabled() external view returns(bool);
-
- function whitelisted(address) external view returns(bool);
-
- function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external;
-
- function addToOperatorWhitelist(address[] calldata) external;
-
- function removeFromWhitelist(address[] calldata operators) external;
-}
\ No newline at end of file
diff --git a/src/contracts/interfaces/IBeaconChainOracle.sol b/src/contracts/interfaces/IBeaconChainOracle.sol
index 6bdff804dc..5f1afabdc5 100644
--- a/src/contracts/interfaces/IBeaconChainOracle.sol
+++ b/src/contracts/interfaces/IBeaconChainOracle.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
/**
* @title Interface for the BeaconStateOracle contract.
@@ -7,57 +7,6 @@ pragma solidity =0.8.12;
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface IBeaconChainOracle {
- /// @notice Largest blockNumber that has been confirmed by the oracle.
- function latestConfirmedOracleBlockNumber() external view returns(uint64);
- /// @notice Mapping: Beacon Chain blockNumber => the Beacon Chain state root at the specified blockNumber.
- /// @dev This will return `bytes32(0)` if the state root at the specified blockNumber is not yet confirmed.
- function beaconStateRootAtBlockNumber(uint64 blockNumber) external view returns(bytes32);
-
- /// @notice Mapping: address => whether or not the address is in the set of oracle signers.
- function isOracleSigner(address _oracleSigner) external view returns(bool);
-
- /// @notice Mapping: Beacon Chain blockNumber => oracle signer address => whether or not the oracle signer has voted on the state root at the blockNumber.
- function hasVoted(uint64 blockNumber, address oracleSigner) external view returns(bool);
-
- /// @notice Mapping: Beacon Chain blockNumber => state root => total number of oracle signer votes for the state root at the blockNumber.
- function stateRootVotes(uint64 blockNumber, bytes32 stateRoot) external view returns(uint256);
-
- /// @notice Total number of members of the set of oracle signers.
- function totalOracleSigners() external view returns(uint256);
-
- /**
- * @notice Number of oracle signers that must vote for a state root in order for the state root to be confirmed.
- * Adjustable by this contract's owner through use of the `setThreshold` function.
- * @dev We note that there is an edge case -- when the threshold is adjusted downward, if a state root already has enough votes to meet the *new* threshold,
- * the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations.
- */
- function threshold() external view returns(uint256);
-
- /**
- * @notice Owner-only function used to modify the value of the `threshold` variable.
- * @param _threshold Desired new value for the `threshold` variable. Function will revert if this is set to zero.
- */
- function setThreshold(uint256 _threshold) external;
-
- /**
- * @notice Owner-only function used to add a signer to the set of oracle signers.
- * @param _oracleSigners Array of address to be added to the set.
- * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already in the set of oracle signers.
- */
- function addOracleSigners(address[] memory _oracleSigners) external;
-
- /**
- * @notice Owner-only function used to remove a signer from the set of oracle signers.
- * @param _oracleSigners Array of address to be removed from the set.
- * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already not in the set of oracle signers.
- */
- function removeOracleSigners(address[] memory _oracleSigners) external;
-
- /**
- * @notice Called by a member of the set of oracle signers to assert that the Beacon Chain state root is `stateRoot` at `blockNumber`.
- * @dev The state root will be finalized once the total number of votes *for this exact state root at this exact blockNumber* meets the `threshold` value.
- * @param blockNumber The Beacon Chain blockNumber of interest.
- * @param stateRoot The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`.
- */
- function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external;
-}
\ No newline at end of file
+ /// @notice The block number to state root mapping.
+ function timestampToBlockRoot(uint256 timestamp) external view returns (bytes32);
+}
diff --git a/src/contracts/interfaces/IDelayedService.sol b/src/contracts/interfaces/IDelayedService.sol
deleted file mode 100644
index 1f905b3991..0000000000
--- a/src/contracts/interfaces/IDelayedService.sol
+++ /dev/null
@@ -1,17 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-/**
- * @title Interface for a middleware / service that may look at past stake amounts.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice Specifically, this interface is designed for services that consult stake amounts up to `BLOCK_STALE_MEASURE`
- * blocks in the past. This may be necessary due to, e.g., network processing & communication delays, or to avoid race conditions
- * that could be present with coordinating aggregate operator signatures while service operators are registering & de-registering.
- * @dev To clarify edge cases, the middleware can look `BLOCK_STALE_MEASURE` blocks into the past, i.e. it may trust stakes from the interval
- * [block.number - BLOCK_STALE_MEASURE, block.number] (specifically, *inclusive* of the block that is `BLOCK_STALE_MEASURE` before the current one)
- */
-interface IDelayedService {
- /// @notice The maximum amount of blocks in the past that the service will consider stake amounts to still be 'valid'.
- function BLOCK_STALE_MEASURE() external view returns(uint32);
-}
diff --git a/src/contracts/interfaces/IDelayedWithdrawalRouter.sol b/src/contracts/interfaces/IDelayedWithdrawalRouter.sol
index 093009aaea..797ac04877 100644
--- a/src/contracts/interfaces/IDelayedWithdrawalRouter.sol
+++ b/src/contracts/interfaces/IDelayedWithdrawalRouter.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
interface IDelayedWithdrawalRouter {
// struct used to pack data into a single storage slot
@@ -14,7 +14,16 @@ interface IDelayedWithdrawalRouter {
DelayedWithdrawal[] delayedWithdrawals;
}
- /**
+ /// @notice event for delayedWithdrawal creation
+ event DelayedWithdrawalCreated(address podOwner, address recipient, uint256 amount, uint256 index);
+
+ /// @notice event for the claiming of delayedWithdrawals
+ event DelayedWithdrawalsClaimed(address recipient, uint256 amountClaimed, uint256 delayedWithdrawalsCompleted);
+
+ /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
+ event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
+
+ /**
* @notice Creates an delayed withdrawal for `msg.value` to the `recipient`.
* @dev Only callable by the `podOwner`'s EigenPod contract.
*/
@@ -44,7 +53,7 @@ interface IDelayedWithdrawalRouter {
/// @notice Getter function to get all delayedWithdrawals that are currently claimable by the `user`
function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory);
-
+
/// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (DelayedWithdrawal memory);
@@ -59,4 +68,4 @@ interface IDelayedWithdrawalRouter {
* up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
*/
function withdrawalDelayBlocks() external view returns (uint256);
-}
\ No newline at end of file
+}
diff --git a/src/contracts/interfaces/IDelegationFaucet.sol b/src/contracts/interfaces/IDelegationFaucet.sol
new file mode 100644
index 0000000000..25147b68bd
--- /dev/null
+++ b/src/contracts/interfaces/IDelegationFaucet.sol
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity >=0.5.0;
+
+import "src/contracts/interfaces/IStrategyManager.sol";
+import "src/contracts/interfaces/IDelegationManager.sol";
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+interface IDelegationFaucet {
+ function mintDepositAndDelegate(
+ address _operator,
+ IDelegationManager.SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt,
+ uint256 _depositAmount
+ ) external;
+
+ function getStaker(address operator) external returns (address);
+
+ function depositIntoStrategy(
+ address staker,
+ IStrategy strategy,
+ IERC20 token,
+ uint256 amount
+ ) external returns (bytes memory);
+
+ function queueWithdrawal(
+ address staker,
+ IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams
+ ) external returns (bytes memory);
+
+ function completeQueuedWithdrawal(
+ address staker,
+ IDelegationManager.Withdrawal calldata queuedWithdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ ) external returns (bytes memory);
+
+ function transfer(address staker, address token, address to, uint256 amount) external returns (bytes memory);
+
+ function callAddress(address to, bytes memory data) external payable returns (bytes memory);
+}
diff --git a/src/contracts/interfaces/IDelegationManager.sol b/src/contracts/interfaces/IDelegationManager.sol
index b2d7715c26..5b663d4e82 100644
--- a/src/contracts/interfaces/IDelegationManager.sol
+++ b/src/contracts/interfaces/IDelegationManager.sol
@@ -1,81 +1,466 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
-import "./IDelegationTerms.sol";
+import "./IStrategy.sol";
+import "./ISignatureUtils.sol";
+import "./IStrategyManager.sol";
/**
- * @title The interface for the primary delegation contract for EigenLayer.
+ * @title DelegationManager
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice This is the contract for delegation in EigenLayer. The main functionalities of this contract are
* - enabling anyone to register as an operator in EigenLayer
- * - allowing new operators to provide a DelegationTerms-type contract, which may mediate their interactions with stakers who delegate to them
- * - enabling any staker to delegate its stake to the operator of its choice
- * - enabling a staker to undelegate its assets from an operator (performed as part of the withdrawal process, initiated through the StrategyManager)
+ * - allowing operators to specify parameters related to stakers who delegate to them
+ * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time)
+ * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager)
*/
-interface IDelegationManager {
+interface IDelegationManager is ISignatureUtils {
+ // @notice Struct used for storing information about a single operator who has registered with EigenLayer
+ struct OperatorDetails {
+ // @notice address to receive the rewards that the operator earns via serving applications built on EigenLayer.
+ address earningsReceiver;
+ /**
+ * @notice Address to verify signatures when a staker wishes to delegate to the operator, as well as controlling "forced undelegations".
+ * @dev Signature verification follows these rules:
+ * 1) If this address is left as address(0), then any staker will be free to delegate to the operator, i.e. no signature verification will be performed.
+ * 2) If this address is an EOA (i.e. it has no code), then we follow standard ECDSA signature verification for delegations to the operator.
+ * 3) If this address is a contract (i.e. it has code) then we forward a call to the contract and verify that it returns the correct EIP-1271 "magic value".
+ */
+ address delegationApprover;
+ /**
+ * @notice A minimum delay -- measured in blocks -- enforced between:
+ * 1) the operator signalling their intent to register for a service, via calling `Slasher.optIntoSlashing`
+ * and
+ * 2) the operator completing registration for the service, via the service ultimately calling `Slasher.recordFirstStakeUpdate`
+ * @dev note that for a specific operator, this value *cannot decrease*, i.e. if the operator wishes to modify their OperatorDetails,
+ * then they are only allowed to either increase this value or keep it the same.
+ */
+ uint32 stakerOptOutWindowBlocks;
+ }
/**
- * @notice This will be called by an operator to register itself as an operator that stakers can choose to delegate to.
- * @param dt is the `DelegationTerms` contract that the operator has for those who delegate to them.
- * @dev An operator can set `dt` equal to their own address (or another EOA address), in the event that they want to split payments
- * in a more 'trustful' manner.
- * @dev In the present design, once set, there is no way for an operator to ever modify the address of their DelegationTerms contract.
+ * @notice Abstract struct used in calculating an EIP712 signature for a staker to approve that they (the staker themselves) delegate to a specific operator.
+ * @dev Used in computing the `STAKER_DELEGATION_TYPEHASH` and as a reference in the computation of the stakerDigestHash in the `delegateToBySignature` function.
*/
- function registerAsOperator(IDelegationTerms dt) external;
+ struct StakerDelegation {
+ // the staker who is delegating
+ address staker;
+ // the operator being delegated to
+ address operator;
+ // the staker's nonce
+ uint256 nonce;
+ // the expiration timestamp (UTC) of the signature
+ uint256 expiry;
+ }
/**
- * @notice This will be called by a staker to delegate its assets to some operator.
- * @param operator is the operator to whom staker (msg.sender) is delegating its assets
+ * @notice Abstract struct used in calculating an EIP712 signature for an operator's delegationApprover to approve that a specific staker delegate to the operator.
+ * @dev Used in computing the `DELEGATION_APPROVAL_TYPEHASH` and as a reference in the computation of the approverDigestHash in the `_delegate` function.
*/
- function delegateTo(address operator) external;
+ struct DelegationApproval {
+ // the staker who is delegating
+ address staker;
+ // the operator being delegated to
+ address operator;
+ // the operator's provided salt
+ bytes32 salt;
+ // the expiration timestamp (UTC) of the signature
+ uint256 expiry;
+ }
/**
- * @notice Delegates from `staker` to `operator`.
- * @dev requires that:
- * 1) if `staker` is an EOA, then `signature` is valid ECDSA signature from `staker`, indicating their intention for this action
- * 2) if `staker` is a contract, then `signature` must will be checked according to EIP-1271
+ * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
+ * In functions that operate on existing queued withdrawals -- e.g. completeQueuedWithdrawal`, the data is resubmitted and the hash of the submitted
+ * data is computed by `calculateWithdrawalRoot` and checked against the stored hash in order to confirm the integrity of the submitted data.
*/
- function delegateToBySignature(address staker, address operator, uint256 expiry, bytes memory signature) external;
+ struct Withdrawal {
+ // The address that originated the Withdrawal
+ address staker;
+ // The address that the staker was delegated to at the time that the Withdrawal was created
+ address delegatedTo;
+ // The address that can complete the Withdrawal + will receive funds when completing the withdrawal
+ address withdrawer;
+ // Nonce used to guarantee that otherwise identical withdrawals have unique hashes
+ uint256 nonce;
+ // Block number when the Withdrawal was created
+ uint32 startBlock;
+ // Array of strategies that the Withdrawal contains
+ IStrategy[] strategies;
+ // Array containing the amount of shares in each Strategy in the `strategies` array
+ uint256[] shares;
+ }
+
+ struct QueuedWithdrawalParams {
+ // Array of strategies that the QueuedWithdrawal contains
+ IStrategy[] strategies;
+ // Array containing the amount of shares in each Strategy in the `strategies` array
+ uint256[] shares;
+ // The address of the withdrawer
+ address withdrawer;
+ }
+
+ // @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
+ event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails);
+
+ /// @notice Emitted when an operator updates their OperatorDetails to @param newOperatorDetails
+ event OperatorDetailsModified(address indexed operator, OperatorDetails newOperatorDetails);
/**
- * @notice Undelegates `staker` from the operator who they are delegated to.
- * @notice Callable only by the StrategyManager
- * @dev Should only ever be called in the event that the `staker` has no active deposits in EigenLayer.
+ * @notice Emitted when @param operator 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
*/
- function undelegate(address staker) external;
+ event OperatorMetadataURIUpdated(address indexed operator, string metadataURI);
- /// @notice returns the address of the operator that `staker` is delegated to.
- function delegatedTo(address staker) external view returns (address);
+ /// @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);
- /// @notice returns the DelegationTerms of the `operator`, which may mediate their interactions with stakers who delegate to them.
- function delegationTerms(address operator) external view returns (IDelegationTerms);
+ /// @notice Emitted whenever an operator's shares are decreased for a given strategy. Note that shares is the delta in the operator's shares.
+ event OperatorSharesDecreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
- /// @notice returns the total number of shares in `strategy` that are delegated to `operator`.
- function operatorShares(address operator, IStrategy strategy) external view returns (uint256);
+ /// @notice Emitted when @param staker delegates to @param operator.
+ event StakerDelegated(address indexed staker, address indexed operator);
+
+ /// @notice Emitted when @param staker undelegates from @param operator.
+ event StakerUndelegated(address indexed staker, address indexed operator);
+
+ /// @notice Emitted when @param staker is undelegated via a call not originating from the staker themself
+ event StakerForceUndelegated(address indexed staker, address indexed operator);
+
+ /**
+ * @notice Emitted when a new withdrawal is queued.
+ * @param withdrawalRoot Is the hash of the `withdrawal`.
+ * @param withdrawal Is the withdrawal itself.
+ */
+ event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal);
+
+ /// @notice Emitted when a queued withdrawal is completed
+ event WithdrawalCompleted(bytes32 withdrawalRoot);
+
+ /// @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 `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.
+ * @param registeringOperatorDetails is the `OperatorDetails` for the operator.
+ * @param metadataURI is a URI for the operator's metadata, i.e. a link providing more details on the operator.
+ *
+ * @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered "delegated to themself".
+ * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
+ * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
+ */
+ function registerAsOperator(
+ OperatorDetails calldata registeringOperatorDetails,
+ string calldata metadataURI
+ ) external;
+
+ /**
+ * @notice Updates an operator's stored `OperatorDetails`.
+ * @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current OperatorDetails`.
+ *
+ * @dev The caller must have previously registered as an operator in EigenLayer.
+ * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
+ */
+ function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external;
+
+ /**
+ * @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has updated.
+ * @param metadataURI The URI for metadata associated with an operator
+ * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
+ */
+ function updateOperatorMetadataURI(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.
+ * @param approverSignatureAndExpiry Verifies the operator approves of this delegation
+ * @param approverSalt A unique single use value tied to an individual signature.
+ * @dev The approverSignatureAndExpiry is used in the event that:
+ * 1) the operator's `delegationApprover` address is set to a non-zero value.
+ * AND
+ * 2) neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator
+ * or their delegationApprover is the `msg.sender`, then approval is assumed.
+ * @dev In the event that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
+ * in this case to save on complexity + gas costs
+ */
+ function delegateTo(
+ address operator,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+ ) external;
/**
- * @notice Increases the `staker`'s delegated shares in `strategy` by `shares, typically called when the staker has further deposits into EigenLayer
- * @dev Callable only by the StrategyManager
+ * @notice Caller delegates a staker's stake to an operator with valid signatures from both parties.
+ * @param staker The account delegating stake to an `operator` account
+ * @param operator The account (`staker`) is delegating its assets to for use in serving applications built on EigenLayer.
+ * @param stakerSignatureAndExpiry Signed data from the staker authorizing delegating stake to an operator
+ * @param approverSignatureAndExpiry is a parameter that will be used for verifying that the operator approves of this delegation action in the event that:
+ * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver.
+ *
+ * @dev If `staker` is an EOA, then `stakerSignature` is verified to be a valid ECDSA stakerSignature from `staker`, indicating their intention for this action.
+ * @dev If `staker` is a contract, then `stakerSignature` will be checked according to EIP-1271.
+ * @dev the operator's `delegationApprover` address is set to a non-zero value.
+ * @dev neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator or their delegationApprover
+ * is the `msg.sender`, then approval is assumed.
+ * @dev This function will revert if the current `block.timestamp` is equal to or exceeds the expiry
+ * @dev In the case that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
+ * in this case to save on complexity + gas costs
*/
- function increaseDelegatedShares(address staker, IStrategy strategy, uint256 shares) external;
+ function delegateToBySignature(
+ address staker,
+ address operator,
+ SignatureWithExpiry memory stakerSignatureAndExpiry,
+ SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 approverSalt
+ ) external;
+
+ /**
+ * @notice Undelegates the staker from the operator who they are delegated to. Puts the staker into the "undelegation limbo" mode of the EigenPodManager
+ * and queues a withdrawal of all of the staker's shares in the StrategyManager (to the staker), if necessary.
+ * @param staker The account to be undelegated.
+ * @return withdrawalRoot The root of the newly queued withdrawal, if a withdrawal was queued. Otherwise just bytes32(0).
+ *
+ * @dev Reverts if the `staker` is also an operator, since operators are not allowed to undelegate from themselves.
+ * @dev Reverts if the caller is not the staker, nor the operator who the staker is delegated to, nor the operator's specified "delegationApprover"
+ * @dev Reverts if the `staker` is already undelegated.
+ */
+ function undelegate(address staker) external returns (bytes32[] memory withdrawalRoot);
+
+ /**
+ * 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.
+ *
+ * All withdrawn shares/strategies are placed in a queue and can be fully withdrawn after a delay.
+ */
+ function queueWithdrawals(
+ QueuedWithdrawalParams[] calldata queuedWithdrawalParams
+ ) external returns (bytes32[] memory);
/**
- * @notice Decreases the `staker`'s delegated shares in each entry of `strategies` by its respective `shares[i]`, typically called when the staker withdraws from EigenLayer
- * @dev Callable only by the StrategyManager
+ * @notice Used to complete the specified `withdrawal`. The caller must match `withdrawal.withdrawer`
+ * @param withdrawal The Withdrawal to complete.
+ * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `withdrawal.strategies` array.
+ * This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
+ * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
+ * @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified strategies themselves
+ * and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
+ * will simply be transferred to the caller directly.
+ * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
+ * @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and `withdrawal.withdrawer != withdrawal.staker`, note that
+ * any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, unlike shares in
+ * any other strategies, which will be transferred to the withdrawer.
+ */
+ function completeQueuedWithdrawal(
+ Withdrawal calldata withdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ ) external;
+
+ /**
+ * @notice Array-ified version of `completeQueuedWithdrawal`.
+ * Used to complete the specified `withdrawals`. The function caller must match `withdrawals[...].withdrawer`
+ * @param withdrawals The Withdrawals to complete.
+ * @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
+ * @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
+ * @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for the usage of a single boolean.
+ * @dev See `completeQueuedWithdrawal` for relevant dev tags
+ */
+ function completeQueuedWithdrawals(
+ Withdrawal[] calldata withdrawals,
+ IERC20[][] calldata tokens,
+ uint256[] calldata middlewareTimesIndexes,
+ bool[] calldata receiveAsTokens
+ ) external;
+
+ /**
+ * @notice Increases a staker's delegated share balance in a strategy.
+ * @param staker The address to increase the delegated shares for their operator.
+ * @param strategy The strategy in which to increase the delegated shares.
+ * @param shares The number of shares to increase.
+ *
+ * @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
+ * @dev Callable only by the StrategyManager or EigenPodManager.
+ */
+ function increaseDelegatedShares(
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+ ) external;
+
+ /**
+ * @notice Decreases a staker's delegated share balance in a strategy.
+ * @param staker The address to increase the delegated shares for their operator.
+ * @param strategy The strategy in which to decrease the delegated shares.
+ * @param shares The number of shares to decrease.
+ *
+ * @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
+ * @dev Callable only by the StrategyManager or EigenPodManager.
*/
function decreaseDelegatedShares(
address staker,
- IStrategy[] calldata strategies,
- uint256[] calldata shares
+ IStrategy strategy,
+ uint256 shares
) external;
- /// @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
- function isDelegated(address staker) external view returns (bool);
+ /**
+ * @notice returns the address of the operator that `staker` is delegated to.
+ * @notice Mapping: staker => operator whom the staker is currently delegated to.
+ * @dev Note that returning address(0) indicates that the staker is not actively delegated to any operator.
+ */
+ function delegatedTo(address staker) external view returns (address);
+
+ /**
+ * @notice Returns the OperatorDetails struct associated with an `operator`.
+ */
+ function operatorDetails(address operator) external view returns (OperatorDetails memory);
+
+ /*
+ * @notice Returns the earnings receiver address for an operator
+ */
+ function earningsReceiver(address operator) external view returns (address);
+
+ /**
+ * @notice Returns the delegationApprover account for an operator
+ */
+ function delegationApprover(address operator) external view returns (address);
+
+ /**
+ * @notice Returns the stakerOptOutWindowBlocks for an operator
+ */
+ function stakerOptOutWindowBlocks(address operator) external view returns (uint256);
- /// @notice Returns 'true' if `staker` is *not* actively delegated, and 'false' otherwise.
- function isNotDelegated(address staker) external view returns (bool);
+ /**
+ * @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 if an operator can be delegated to, i.e. it has called `registerAsOperator`.
+ /**
+ * @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.
+ * @dev By design, the following invariant should hold for each Strategy:
+ * (operator's shares in delegation manager) = sum (shares above zero of all stakers delegated to operator)
+ * = sum (delegateable shares of all stakers delegated to the operator)
+ */
+ function operatorShares(address operator, IStrategy strategy) external view returns (uint256);
+
+ /**
+ * @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
+ */
+ function isDelegated(address staker) external view returns (bool);
+
+ /**
+ * @notice Returns true is an operator has previously registered for delegation.
+ */
function isOperator(address operator) external view returns (bool);
+
+ /// @notice Mapping: staker => number of signed delegation nonces (used in `delegateToBySignature`) from the staker that the contract has already checked
+ function stakerNonce(address staker) external view returns (uint256);
+
+ /**
+ * @notice Mapping: delegationApprover => 32-byte salt => whether or not the salt has already been used by the delegationApprover.
+ * @dev Salts are used in the `delegateTo` and `delegateToBySignature` functions. Note that these functions only process the delegationApprover's
+ * signature + the provided salt if the operator being delegated to has specified a nonzero address as their `delegationApprover`.
+ */
+ function delegationApproverSaltIsSpent(address _delegationApprover, bytes32 salt) external view returns (bool);
+
+ /**
+ * @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).
+ * 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 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`
+ * @param staker The signing staker
+ * @param operator The operator who is being delegated to
+ * @param expiry The desired expiry time of the staker's signature
+ */
+ function calculateCurrentStakerDelegationDigestHash(
+ address staker,
+ address operator,
+ uint256 expiry
+ ) external view returns (bytes32);
+
+ /**
+ * @notice Calculates the digest hash to be signed and used in the `delegateToBySignature` function
+ * @param staker The signing staker
+ * @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at `stakerNonce[staker]`
+ * @param operator The operator who is being delegated to
+ * @param expiry The desired expiry time of the staker's signature
+ */
+ function calculateStakerDelegationDigestHash(
+ address staker,
+ uint256 _stakerNonce,
+ address operator,
+ uint256 expiry
+ ) external view returns (bytes32);
+
+ /**
+ * @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo` and `delegateToBySignature` functions.
+ * @param staker The account delegating their stake
+ * @param operator The account receiving delegated stake
+ * @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in general)
+ * @param approverSalt A unique and single use value associated with the approver signature.
+ * @param expiry Time after which the approver's signature becomes invalid
+ */
+ function calculateDelegationApprovalDigestHash(
+ address staker,
+ address operator,
+ address _delegationApprover,
+ bytes32 approverSalt,
+ uint256 expiry
+ ) external view returns (bytes32);
+
+ /// @notice The EIP-712 typehash for the contract's domain
+ function DOMAIN_TYPEHASH() external view returns (bytes32);
+
+ /// @notice The EIP-712 typehash for the StakerDelegation struct used by the contract
+ function STAKER_DELEGATION_TYPEHASH() external view returns (bytes32);
+
+ /// @notice The EIP-712 typehash for the DelegationApproval struct used by the contract
+ function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32);
+
+ /**
+ * @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.
+ * @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature collision.
+ * for more detailed information please read EIP-712.
+ */
+ function domainSeparator() external view returns (bytes32);
+
+ /// @notice Mapping: staker => cumulative number of queued withdrawals they have ever initiated.
+ /// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals have unique hashes.
+ function cumulativeWithdrawalsQueued(address staker) external view returns (uint256);
+
+ /// @notice Returns the keccak256 hash of `withdrawal`.
+ function calculateWithdrawalRoot(Withdrawal memory withdrawal) external pure returns (bytes32);
+
+ function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToQueue) external;
}
diff --git a/src/contracts/interfaces/IDelegationTerms.sol b/src/contracts/interfaces/IDelegationTerms.sol
deleted file mode 100644
index 803cdfcde5..0000000000
--- a/src/contracts/interfaces/IDelegationTerms.sol
+++ /dev/null
@@ -1,26 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./IStrategy.sol";
-
-/**
- * @title Abstract interface for a contract that helps structure the delegation relationship.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice The gas budget provided to this contract in calls from EigenLayer contracts is limited.
- */
-interface IDelegationTerms {
- function payForService(IERC20 token, uint256 amount) external payable;
-
- function onDelegationWithdrawn(
- address delegator,
- IStrategy[] memory stakerStrategyList,
- uint256[] memory stakerShares
- ) external returns(bytes memory);
-
- function onDelegationReceived(
- address delegator,
- IStrategy[] memory stakerStrategyList,
- uint256[] memory stakerShares
- ) external returns(bytes memory);
-}
diff --git a/src/contracts/interfaces/IETHPOSDeposit.sol b/src/contracts/interfaces/IETHPOSDeposit.sol
index cb6676d559..5fc09a5cc4 100644
--- a/src/contracts/interfaces/IETHPOSDeposit.sol
+++ b/src/contracts/interfaces/IETHPOSDeposit.sol
@@ -9,7 +9,7 @@
// SPDX-License-Identifier: CC0-1.0
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
diff --git a/src/contracts/interfaces/IEigenPod.sol b/src/contracts/interfaces/IEigenPod.sol
index a44cb339d5..69a31274b0 100644
--- a/src/contracts/interfaces/IEigenPod.sol
+++ b/src/contracts/interfaces/IEigenPod.sol
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
import "../libraries/BeaconChainProofs.sol";
import "./IEigenPodManager.sol";
import "./IBeaconChainOracle.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
- * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
+ * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice The main functionalities are:
@@ -23,38 +24,85 @@ interface IEigenPod {
enum VALIDATOR_STATUS {
INACTIVE, // doesnt exist
ACTIVE, // staked on ethpos and withdrawal credentials are pointed to the EigenPod
- OVERCOMMITTED, // proven to be overcommitted to EigenLayer
WITHDRAWN // withdrawn from the Beacon Chain
}
- // this struct keeps track of PartialWithdrawalClaims
- struct PartialWithdrawalClaim {
- PARTIAL_WITHDRAWAL_CLAIM_STATUS status;
- // block at which the PartialWithdrawalClaim was created
- uint32 creationBlockNumber;
- // last block (inclusive) in which the PartialWithdrawalClaim can be fraudproofed
- uint32 fraudproofPeriodEndBlockNumber;
- // amount of ETH -- in Gwei -- to be withdrawn until completion of this claim
- uint64 partialWithdrawalAmountGwei;
+ struct ValidatorInfo {
+ // index of the validator in the beacon chain
+ uint64 validatorIndex;
+ // amount of beacon chain ETH restaked on EigenLayer in gwei
+ uint64 restakedBalanceGwei;
+ //timestamp of the validator's most recent balance update
+ uint64 mostRecentBalanceUpdateTimestamp;
+ // status of the validator
+ VALIDATOR_STATUS status;
}
+ /**
+ * @notice struct used to store amounts related to proven withdrawals in memory. Used to help
+ * manage stack depth and optimize the number of external calls, when batching withdrawal operations.
+ */
+ struct VerifiedWithdrawal {
+ // amount to send to a podOwner from a proven withdrawal
+ uint256 amountToSendGwei;
+ // difference in shares to be recorded in the eigenPodManager, as a result of the withdrawal
+ int256 sharesDeltaGwei;
+ }
+
+
enum PARTIAL_WITHDRAWAL_CLAIM_STATUS {
REDEEMED,
PENDING,
FAILED
}
- /// @notice The amount of eth, in gwei, that is restaked per validator
- function REQUIRED_BALANCE_GWEI() external view returns(uint64);
+ /// @notice Emitted when an ETH validator stakes via this eigenPod
+ event EigenPodStaked(bytes pubkey);
+
+ /// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
+ event ValidatorRestaked(uint40 validatorIndex);
+
+ /// @notice Emitted when an ETH validator's balance is proven to be updated. Here newValidatorBalanceGwei
+ // is the validator's balance that is credited on EigenLayer.
+ event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newValidatorBalanceGwei);
+
+ /// @notice Emitted when an ETH validator is prove to have withdrawn from the beacon chain
+ event FullWithdrawalRedeemed(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address indexed recipient,
+ uint64 withdrawalAmountGwei
+ );
+
+ /// @notice Emitted when a partial withdrawal claim is successfully redeemed
+ event PartialWithdrawalRedeemed(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address indexed recipient,
+ uint64 partialWithdrawalAmountGwei
+ );
+
+ /// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
+ event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
+
+ /// @notice Emitted when podOwner enables restaking
+ event RestakingActivated(address indexed podOwner);
+
+ /// @notice Emitted when ETH is received via the `receive` fallback
+ event NonBeaconChainETHReceived(uint256 amountReceived);
+
+ /// @notice Emitted when ETH that was previously received via the `receive` fallback is withdrawn
+ event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn);
+
- /// @notice The amount of eth, in wei, that is restaked per validator
- function REQUIRED_BALANCE_WEI() external view returns(uint256);
+ /// @notice The max amount of eth, in gwei, that can be restaked per validator
+ function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external view returns (uint64);
- /// @notice this is a mapping of validator indices to a Validator struct containing pertinent info about the validator
- function validatorStatus(uint40 validatorIndex) external view returns(VALIDATOR_STATUS);
+ /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
+ function withdrawableRestakedExecutionLayerGwei() external view returns (uint64);
- /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
- function restakedExecutionLayerGwei() external view returns(uint64);
+ /// @notice any ETH deposited into the EigenPod contract via the `receive` fallback function
+ function nonBeaconChainETHBalanceWei() external view returns (uint256);
/// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
function initialize(address owner) external;
@@ -65,8 +113,9 @@ interface IEigenPod {
/**
* @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
* @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
- * @dev Called during withdrawal or slashing.
- * @dev Note that this function is marked as non-reentrant to prevent the recipient calling back into it
+ * @dev The podOwner must have already proved sufficient withdrawals, so that this pod's `withdrawableRestakedExecutionLayerGwei` exceeds the
+ * `amountWei` input (when converted to GWEI).
+ * @dev Reverts if `amountWei` is not a whole Gwei amount
*/
function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external;
@@ -79,69 +128,96 @@ interface IEigenPod {
/// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
function hasRestaked() external view returns (bool);
- /// @notice block number of the most recent withdrawal
- function mostRecentWithdrawalBlockNumber() external view returns (uint64);
+ /**
+ * @notice The latest timestamp at which the pod owner withdrew the balance of the pod, via calling `withdrawBeforeRestaking`.
+ * @dev This variable is only updated when the `withdrawBeforeRestaking` function is called, which can only occur before `hasRestaked` is set to true for this pod.
+ * Proofs for this pod are only valid against Beacon Chain state roots corresponding to timestamps after the stored `mostRecentWithdrawalTimestamp`.
+ */
+ function mostRecentWithdrawalTimestamp() external view returns (uint64);
+
+ /// @notice Returns the validatorInfo struct for the provided pubkeyHash
+ function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory);
+
+ /// @notice Returns the validatorInfo struct for the provided pubkey
+ function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory);
+ ///@notice mapping that tracks proven withdrawals
+ function provenWithdrawal(bytes32 validatorPubkeyHash, uint64 slot) external view returns (bool);
- ///@notice mapping that tracks proven partial withdrawals
- function provenPartialWithdrawal(uint40 validatorIndex, uint64 slot) external view returns (bool);
+ /// @notice This returns the status of a given validator
+ function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS);
+
+ /// @notice This returns the status of a given validator pubkey
+ function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS);
/**
- * @notice This function verifies that the withdrawal credentials of the podOwner are pointed to
- * this contract. It also verifies the current (not effective) balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
+ * @notice This function verifies that the withdrawal credentials of validator(s) owned by the podOwner are pointed to
+ * this contract. It also verifies the effective balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
* root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
- * @param oracleBlockNumber is the Beacon Chain blockNumber whose state root the `proof` will be proven against.
- * @param validatorIndex is the index of the validator being proven, refer to consensus specs
- * @param proofs is the bytes that prove the ETH validator's balance and withdrawal credentials against a beacon chain state root
- * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
+ * @param oracleTimestamp is the Beacon Chain timestamp whose state root the `proof` will be proven against.
+ * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
+ * @param withdrawalCredentialProofs is an array of proofs, where each proof proves each ETH validator's balance and withdrawal credentials
+ * against a beacon chain state root
+ * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
* for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
*/
- function verifyWithdrawalCredentialsAndBalance(
- uint64 oracleBlockNumber,
- uint40 validatorIndex,
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs,
- bytes32[] calldata validatorFields
- ) external;
-
+ function verifyWithdrawalCredentials(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ uint40[] calldata validatorIndices,
+ bytes[] calldata withdrawalCredentialProofs,
+ bytes32[][] calldata validatorFields
+ )
+ external;
+
/**
- * @notice This function records an overcommitment of stake to EigenLayer on behalf of a certain ETH validator.
- * If successful, the overcommitted balance is penalized (available for withdrawal whenever the pod's balance allows).
- * The ETH validator's shares in the enshrined beaconChainETH strategy are also removed from the StrategyManager and undelegated.
- * @param oracleBlockNumber The oracleBlockNumber whose state root the `proof` will be proven against.
- * Must be within `VERIFY_OVERCOMMITTED_WINDOW_BLOCKS` of the current block.
- * @param validatorIndex is the index of the validator being proven, refer to consensus specs
- * @param proofs is the proof of the validator's balance and validatorFields in the balance tree and the balanceRoot to prove for
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
- * the StrategyManager in case it must be removed from the list of the podOwners strategies
+ * @notice This function records an update (either increase or decrease) in the pod's balance in the StrategyManager.
+ It also verifies a merkle proof of the validator's current beacon chain balance.
+ * @param oracleTimestamp The oracleTimestamp whose state root the `proof` will be proven against.
+ * Must be within `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` of the current block.
+ * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
+ * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
* @param validatorFields are the fields of the "Validator Container", refer to consensus specs
* @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
*/
- function verifyOvercommittedStake(
- uint40 validatorIndex,
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs calldata proofs,
- bytes32[] calldata validatorFields,
- uint256 beaconChainETHStrategyIndex,
- uint64 oracleBlockNumber
+ function verifyBalanceUpdates(
+ uint64 oracleTimestamp,
+ uint40[] calldata validatorIndices,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields
) external;
/**
- * @notice This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
- * @param withdrawalProofs is the information needed to check the veracity of the block number and withdrawal being proven
- * @param validatorFieldsProof is the proof of the validator's fields in the validator tree
- * @param withdrawalFields are the fields of the withdrawal being proven
- * @param validatorFields are the fields of the validator being proven
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
- * the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies
+ * @notice This function records full and partial withdrawals on behalf of one of the Ethereum validators for this EigenPod
+ * @param oracleTimestamp is the timestamp of the oracle slot that the withdrawal is being proven against
+ * @param withdrawalProofs is the information needed to check the veracity of the block numbers and withdrawals being proven
+ * @param validatorFieldsProofs is the proof of the validator's fields' in the validator tree
+ * @param withdrawalFields are the fields of the withdrawals being proven
+ * @param validatorFields are the fields of the validators being proven
*/
- function verifyAndProcessWithdrawal(
- BeaconChainProofs.WithdrawalProofs calldata withdrawalProofs,
- bytes calldata validatorFieldsProof,
- bytes32[] calldata validatorFields,
- bytes32[] calldata withdrawalFields,
- uint256 beaconChainETHStrategyIndex,
- uint64 oracleBlockNumber
+ function verifyAndProcessWithdrawals(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields,
+ bytes32[][] calldata withdrawalFields
) external;
+ /**
+ * @notice Called by the pod owner to activate restaking by withdrawing
+ * all existing ETH from the pod and preventing further withdrawals via
+ * "withdrawBeforeRestaking()"
+ */
+ function activateRestaking() external;
+
/// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
function withdrawBeforeRestaking() external;
-}
\ No newline at end of file
+
+ /// @notice Called by the pod owner to withdraw the nonBeaconChainETHBalanceWei
+ function withdrawNonBeaconChainETHBalanceWei(address recipient, uint256 amountToWithdraw) external;
+
+ /// @notice called by owner of a pod to remove any ERC20s deposited in the pod
+ function recoverTokens(IERC20[] memory tokenList, uint256[] memory amountsToWithdraw, address recipient) external;
+}
diff --git a/src/contracts/interfaces/IEigenPodManager.sol b/src/contracts/interfaces/IEigenPodManager.sol
index 3455262f27..4860cff408 100644
--- a/src/contracts/interfaces/IEigenPodManager.sol
+++ b/src/contracts/interfaces/IEigenPodManager.sol
@@ -1,10 +1,14 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
+import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
+import "./IETHPOSDeposit.sol";
import "./IStrategyManager.sol";
import "./IEigenPod.sol";
import "./IBeaconChainOracle.sol";
import "./IPausable.sol";
+import "./ISlasher.sol";
+import "./IStrategy.sol";
/**
* @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials pointed to EigenLayer.
@@ -13,14 +17,39 @@ import "./IPausable.sol";
*/
interface IEigenPodManager is IPausable {
+ /// @notice Emitted to notify the update of the beaconChainOracle address
+ event BeaconOracleUpdated(address indexed newOracleAddress);
+
+ /// @notice Emitted to notify the deployment of an EigenPod
+ event PodDeployed(address indexed eigenPod, address indexed podOwner);
+
+ /// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
+ event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
+
+ /// @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,
+ uint256 shares,
+ uint96 nonce,
+ address delegatedAddress,
+ address withdrawer,
+ 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.
+ * @dev Returns EigenPod address
*/
- function createPod() external;
+ function createPod() external returns (address);
/**
- * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
+ * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
* Also creates an EigenPod for the sender if they don't have one already.
* @param pubkey The 48 bytes public key of the beacon chain validator.
* @param signature The validator's signature of the deposit data.
@@ -29,32 +58,14 @@ interface IEigenPodManager is IPausable {
function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
/**
- * @notice Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod.
- * @param podOwner The owner of the pod whose balance must be deposited.
- * @param amount The amount of ETH to 'deposit' (i.e. be credited to the podOwner).
+ * @notice Changes the `podOwner`'s shares by `sharesDelta` and performs a call to the DelegationManager
+ * to ensure that delegated shares are also tracked correctly
+ * @param podOwner is the pod owner whose balance is being updated.
+ * @param sharesDelta is the change in podOwner's beaconChainETHStrategy shares
* @dev Callable only by the podOwner's EigenPod contract.
+ * @dev Reverts if `sharesDelta` is not a whole Gwei amount
*/
- function restakeBeaconChainETH(address podOwner, uint256 amount) external;
-
- /**
- * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
- * balance of a validator is lower than how much stake they have committed to EigenLayer
- * @param podOwner The owner of the pod whose balance must be removed.
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
- * the StrategyManager in case it must be removed from the list of the podOwner's strategies
- * @param amount The amount of ETH to remove.
- * @dev Callable only by the podOwner's EigenPod contract.
- */
- function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external;
-
- /**
- * @notice Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain.
- * @param podOwner The owner of the pod whose balance must be withdrawn.
- * @param recipient The recipient of the withdrawn ETH.
- * @param amount The amount of ETH to withdraw.
- * @dev Callable only by the StrategyManager contract.
- */
- function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount) external;
+ function recordBeaconChainETHBalanceUpdate(address podOwner, int256 sharesDelta) external;
/**
* @notice Updates the oracle contract that provides the beacon chain state root
@@ -64,22 +75,85 @@ interface IEigenPodManager is IPausable {
function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external;
/// @notice Returns the address of the `podOwner`'s EigenPod if it has been deployed.
- function ownerToPod(address podOwner) external view returns(IEigenPod);
+ function ownerToPod(address podOwner) external view returns (IEigenPod);
/// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
- function getPod(address podOwner) external view returns(IEigenPod);
+ function getPod(address podOwner) external view returns (IEigenPod);
+
+ /// @notice The ETH2 Deposit Contract
+ function ethPOS() external view returns (IETHPOSDeposit);
+
+ /// @notice Beacon proxy to which the EigenPods point
+ function eigenPodBeacon() external view returns (IBeacon);
/// @notice Oracle contract that provides updates to the beacon chain's state
- function beaconChainOracle() external view returns(IBeaconChainOracle);
+ function beaconChainOracle() external view returns (IBeaconChainOracle);
- /// @notice Returns the Beacon Chain state root at `blockNumber`. Reverts if the Beacon Chain state root at `blockNumber` has not yet been finalized.
- function getBeaconChainStateRoot(uint64 blockNumber) external view returns(bytes32);
+ /// @notice Returns the beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized.
+ function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32);
/// @notice EigenLayer's StrategyManager contract
- function strategyManager() external view returns(IStrategyManager);
+ function strategyManager() external view returns (IStrategyManager);
/// @notice EigenLayer's Slasher contract
- function slasher() external view returns(ISlasher);
+ function slasher() external view returns (ISlasher);
+ /// @notice Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
function hasPod(address podOwner) external view returns (bool);
-}
\ No newline at end of file
+
+ /// @notice Returns the number of EigenPods that have been created
+ function numPods() external view returns (uint256);
+
+ /**
+ * @notice Mapping from Pod owner owner to the number of shares they have in the virtual beacon chain ETH strategy.
+ * @dev The share amount can become negative. This is necessary to accommodate the fact that a pod owner's virtual beacon chain ETH shares can
+ * decrease between the pod owner queuing and completing a withdrawal.
+ * When the pod owner's shares would otherwise increase, this "deficit" is decreased first _instead_.
+ * Likewise, when a withdrawal is completed, this "deficit" is decreased and the withdrawal amount is decreased; We can think of this
+ * as the withdrawal "paying off the deficit".
+ */
+ function podOwnerShares(address podOwner) external view returns (int256);
+
+ /// @notice returns canonical, virtual beaconChainETH strategy
+ function beaconChainETHStrategy() external view returns (IStrategy);
+
+ /**
+ * @notice Used by the DelegationManager to remove a pod owner's shares while they're in the withdrawal queue.
+ * Simply decreases the `podOwner`'s shares by `shares`, down to a minimum of zero.
+ * @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is forbidden for this function to
+ * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
+ * shares from the operator to whom the staker is delegated.
+ * @dev Reverts if `shares` is not a whole Gwei amount
+ */
+ function removeShares(address podOwner, uint256 shares) external;
+
+ /**
+ * @notice Increases the `podOwner`'s shares by `shares`, paying off deficit if possible.
+ * Used by the DelegationManager to award a pod owner shares on exiting the withdrawal queue
+ * @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the `shares` input
+ * in the event that the podOwner has an existing shares deficit (i.e. `podOwnerShares[podOwner]` starts below zero)
+ * @dev Reverts if `shares` is not a whole Gwei amount
+ */
+ function addShares(address podOwner, uint256 shares) external returns (uint256);
+
+ /**
+ * @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address
+ * @dev Prioritizes decreasing the podOwner's share deficit, if they have one
+ * @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/IPausable.sol b/src/contracts/interfaces/IPausable.sol
index 11450067aa..14f357d302 100644
--- a/src/contracts/interfaces/IPausable.sol
+++ b/src/contracts/interfaces/IPausable.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
import "../interfaces/IPauserRegistry.sol";
@@ -21,8 +21,17 @@ import "../interfaces/IPauserRegistry.sol";
*/
interface IPausable {
+ /// @notice Emitted when the `pauserRegistry` is set to `newPauserRegistry`.
+ event PauserRegistrySet(IPauserRegistry pauserRegistry, IPauserRegistry newPauserRegistry);
+
+ /// @notice Emitted when the pause is triggered by `account`, and changed to `newPausedStatus`.
+ event Paused(address indexed account, uint256 newPausedStatus);
+
+ /// @notice Emitted when the pause is lifted by `account`, and changed to `newPausedStatus`.
+ event Unpaused(address indexed account, uint256 newPausedStatus);
+
/// @notice Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
- function pauserRegistry() external view returns (IPauserRegistry);
+ function pauserRegistry() external view returns (IPauserRegistry);
/**
* @notice This function is used to pause an EigenLayer contract's functionality.
diff --git a/src/contracts/interfaces/IPauserRegistry.sol b/src/contracts/interfaces/IPauserRegistry.sol
index 7a3a986f77..53cc0125aa 100644
--- a/src/contracts/interfaces/IPauserRegistry.sol
+++ b/src/contracts/interfaces/IPauserRegistry.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
/**
* @title Interface for the `PauserRegistry` contract.
@@ -7,6 +7,10 @@ pragma solidity =0.8.12;
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface IPauserRegistry {
+ event PauserStatusChanged(address pauser, bool canPause);
+
+ event UnpauserChanged(address previousUnpauser, address newUnpauser);
+
/// @notice Mapping of addresses to whether they hold the pauser role.
function isPauser(address pauser) external view returns (bool);
diff --git a/src/contracts/interfaces/IPaymentManager.sol b/src/contracts/interfaces/IPaymentManager.sol
deleted file mode 100644
index f0d87ab9f7..0000000000
--- a/src/contracts/interfaces/IPaymentManager.sol
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-
-/**
- * @title Interface for a `PaymentManager` contract.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-interface IPaymentManager {
- enum DissectionType {
- INVALID,
- FIRST_HALF,
- SECOND_HALF
- }
- enum PaymentStatus {
- REDEEMED,
- COMMITTED,
- CHALLENGED
- }
- enum ChallengeStatus {
- RESOLVED,
- OPERATOR_TURN,
- CHALLENGER_TURN,
- OPERATOR_TURN_ONE_STEP,
- CHALLENGER_TURN_ONE_STEP
- }
-
- /**
- * @notice used for storing information on the most recent payment made to the operator
- */
- struct Payment {
- // taskNumber starting from which payment is being claimed
- uint32 fromTaskNumber;
- // taskNumber until which payment is being claimed (exclusive)
- uint32 toTaskNumber;
- // recording when the payment will optimistically be confirmed; used for fraudproof period
- uint32 confirmAt;
- // payment for range [fromTaskNumber, toTaskNumber)
- /// @dev max 1.3e36, keep in mind for token decimals
- uint96 amount;
- /**
- * @notice The possible statuses are:
- * - 0: REDEEMED,
- * - 1: COMMITTED,
- * - 2: CHALLENGED
- */
- PaymentStatus status;
- uint256 challengeAmount; //account for if challengeAmount changed
- }
-
- /**
- * @notice used for storing information on the payment challenge as part of the interactive process
- */
- struct PaymentChallenge {
- // operator whose payment claim is being challenged,
- address operator;
- // the entity challenging with the fraudproof
- address challenger;
- // address of the service manager contract
- address serviceManager;
- // the TaskNumber from which payment has been computed
- uint32 fromTaskNumber;
- // the TaskNumber until which payment has been computed to
- uint32 toTaskNumber;
- // reward amount the challenger claims is for the first half of tasks
- uint96 amount1;
- // reward amount the challenger claims is for the second half of tasks
- uint96 amount2;
- // used for recording the time when challenge was created
- uint32 settleAt; // when committed, used for fraudproof period
- // indicates the status of the challenge
- /**
- * @notice The possible statuses are:
- * - 0: RESOLVED,
- * - 1: operator turn (dissection),
- * - 2: challenger turn (dissection),
- * - 3: operator turn (one step),
- * - 4: challenger turn (one step)
- */
- ChallengeStatus status;
- }
-
- struct TotalStakes {
- uint256 signedStakeFirstQuorum;
- uint256 signedStakeSecondQuorum;
- }
-
- /**
- * @notice deposit one-time fees by the `msg.sender` with this contract to pay for future tasks of this middleware
- * @param depositFor could be the `msg.sender` themselves, or a different address for whom `msg.sender` is depositing these future fees
- * @param amount is amount of futures fees being deposited
- */
- function depositFutureFees(address depositFor, uint256 amount) external;
-
- /// @notice Allows the `allowed` address to spend up to `amount` of the `msg.sender`'s funds that have been deposited in this contract
- function setAllowance(address allowed, uint256 amount) external;
-
- /// @notice Used for deducting the fees from the payer to the middleware
- function takeFee(address initiator, address payer, uint256 feeAmount) external;
-
- /**
- * @notice Modifies the `paymentChallengeAmount` amount.
- * @param _paymentChallengeAmount The new value for `paymentChallengeAmount` to take.
- */
- function setPaymentChallengeAmount(uint256 _paymentChallengeAmount) external;
-
- /**
- * @notice This is used by an operator to make a claim on the amount that they deserve for their service from their last payment until `toTaskNumber`
- * @dev Once this payment is recorded, a fraud proof period commences during which a challenger can dispute the proposed payment.
- */
- function commitPayment(uint32 toTaskNumber, uint96 amount) external;
-
- /**
- * @notice Called by an operator to redeem a payment that they previously 'committed' to by calling `commitPayment`.
- * @dev This function can only be called after the challenge window for the payment claim has completed.
- */
- function redeemPayment() external;
-
- /**
- * @notice This function is called by a fraud prover to challenge a payment, initiating an interactive-type fraudproof.
- * @param operator is the operator against whose payment claim the fraudproof is being made
- * @param amount1 is the reward amount the challenger in that round claims is for the first half of tasks
- * @param amount2 is the reward amount the challenger in that round claims is for the second half of tasks
- *
- */
- function initPaymentChallenge(address operator, uint96 amount1, uint96 amount2) external;
-
- /**
- * @notice Perform a single bisection step in an existing interactive payment challenge.
- * @param operator The middleware operator who was challenged (used to look up challenge details)
- * @param secondHalf If true, then the caller wishes to challenge the amount claimed as payment in the *second half* of the
- * previous bisection step. If false then the *first half* is indicated instead.
- * @param amount1 The amount that the caller asserts the operator is entitled to, for the first half *of the challenged half* of the previous bisection.
- * @param amount2 The amount that the caller asserts the operator is entitled to, for the second half *of the challenged half* of the previous bisection.
- */
- function performChallengeBisectionStep(address operator, bool secondHalf, uint96 amount1, uint96 amount2)
- external;
-
- /// @notice resolve an existing PaymentChallenge for an operator
- function resolveChallenge(address operator) external;
-
- /**
- * @notice Challenge window for submitting fraudproof in the case of an incorrect payment claim by a registered operator.
- */
- function paymentFraudproofInterval() external view returns (uint256);
-
- /**
- * @notice Specifies the payment that has to be made as a guarantee for fraudproof during payment challenges.
- */
- function paymentChallengeAmount() external view returns (uint256);
-
- /// @notice the ERC20 token that will be used by the disperser to pay the service fees to middleware nodes.
- function paymentToken() external view returns (IERC20);
-
- /// @notice Token used for placing a guarantee on challenges & payment commits
- function paymentChallengeToken() external view returns (IERC20);
-
- /// @notice Returns the ChallengeStatus for the `operator`'s payment claim.
- function getChallengeStatus(address operator) external view returns (ChallengeStatus);
-
- /// @notice Returns the 'amount1' for the `operator`'s payment claim.
- function getAmount1(address operator) external view returns (uint96);
-
- /// @notice Returns the 'amount2' for the `operator`'s payment claim.
- function getAmount2(address operator) external view returns (uint96);
-
- /// @notice Returns the 'toTaskNumber' for the `operator`'s payment claim.
- function getToTaskNumber(address operator) external view returns (uint48);
-
- /// @notice Returns the 'fromTaskNumber' for the `operator`'s payment claim.
- function getFromTaskNumber(address operator) external view returns (uint48);
-
- /// @notice Returns the task number difference for the `operator`'s payment claim.
- function getDiff(address operator) external view returns (uint48);
-
- /// @notice Returns the active guarantee amount of the `operator` placed on their payment claim.
- function getPaymentChallengeAmount(address) external view returns (uint256);
-}
diff --git a/src/contracts/interfaces/IQuorumRegistry.sol b/src/contracts/interfaces/IQuorumRegistry.sol
deleted file mode 100644
index c73770b909..0000000000
--- a/src/contracts/interfaces/IQuorumRegistry.sol
+++ /dev/null
@@ -1,156 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./IRegistry.sol";
-
-/**
- * @title Interface for a `Registry`-type contract that uses either 1 or 2 quorums.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract does not currently support n-quorums where n >= 3.
- * Note in particular the presence of only `firstQuorumStake` and `secondQuorumStake` in the `OperatorStake` struct.
- */
-interface IQuorumRegistry is IRegistry {
- // DATA STRUCTURES
- enum Status
- {
- // default is inactive
- INACTIVE,
- ACTIVE
- }
-
- /**
- * @notice Data structure for storing info on operators to be used for:
- * - sending data by the sequencer
- * - payment and associated challenges
- */
- struct Operator {
- // hash of pubkey of the operator
- bytes32 pubkeyHash;
- // start taskNumber from which the operator has been registered
- uint32 fromTaskNumber;
- // indicates whether the operator is actively registered for serving the middleware or not
- Status status;
- }
-
- // struct used to give definitive ordering to operators at each blockNumber
- struct OperatorIndex {
- // blockNumber number at which operator index changed
- // note that the operator's index is different *for this block number*, i.e. the *new* index is *inclusive* of this value
- uint32 toBlockNumber;
- // index of the operator in array of operators, or the total number of operators if in the 'totalOperatorsHistory'
- uint32 index;
- }
-
- /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage
- struct OperatorStake {
- // the block number at which the stake amounts were updated and stored
- uint32 updateBlockNumber;
- // the block number at which the *next update* occurred.
- /// @notice This entry has the value **0** until another update takes place.
- uint32 nextUpdateBlockNumber;
- // stake weight for the first quorum
- uint96 firstQuorumStake;
- // stake weight for the second quorum. Will always be zero in the event that only one quorum is used
- uint96 secondQuorumStake;
- }
-
- function getLengthOfTotalStakeHistory() external view returns (uint256);
-
- /**
- * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory`.
- * @dev Function will revert in the event that `index` is out-of-bounds.
- */
- function getTotalStakeFromIndex(uint256 index) external view returns (OperatorStake memory);
-
- /// @notice Returns the stored pubkeyHash for the specified `operator`.
- function getOperatorPubkeyHash(address operator) external view returns (bytes32);
-
- /// @notice Returns task number from when `operator` has been registered.
- function getFromTaskNumberForOperator(address operator) external view returns (uint32);
-
- /**
- * @notice Returns the stake weight corresponding to `pubkeyHash`, at the
- * `index`-th entry in the `pubkeyHashToStakeHistory[pubkeyHash]` array.
- * @param pubkeyHash Hash of the public key of the operator of interest.
- * @param index Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash]`.
- * @dev Function will revert if `index` is out-of-bounds.
- */
- function getStakeFromPubkeyHashAndIndex(bytes32 pubkeyHash, uint256 index)
- external
- view
- returns (OperatorStake memory);
-
- /**
- * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
- * @param operator is the operator of interest
- * @param blockNumber is the block number of interest
- * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up
- * in `registry[operator].pubkeyHash`
- * @return 'true' if it is successfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise
- * @dev In order for this function to return 'true', the inputs must satisfy all of the following list:
- * 1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
- * 2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
- * is must be strictly greater than `blockNumber`
- * 3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
- * or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
- * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a
- * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'.
- */
- function checkOperatorActiveAtBlockNumber(
- address operator,
- uint256 blockNumber,
- uint256 stakeHistoryIndex
- ) external view returns (bool);
-
- /**
- * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
- * @param operator is the operator of interest
- * @param blockNumber is the block number of interest
- * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up
- * in `registry[operator].pubkeyHash`
- * @return 'true' if it is successfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise
- * @dev In order for this function to return 'true', the inputs must satisfy all of the following list:
- * 1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
- * 2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
- * is must be strictly greater than `blockNumber`
- * 3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
- * or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
- * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a
- * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'.
- */
- function checkOperatorInactiveAtBlockNumber(
- address operator,
- uint256 blockNumber,
- uint256 stakeHistoryIndex
- ) external view returns (bool);
-
- /**
- * @notice Looks up the `operator`'s index in the dynamic array `operatorList` at the specified `blockNumber`.
- * @param index Used to specify the entry within the dynamic array `pubkeyHashToIndexHistory[pubkeyHash]` to
- * read data from, where `pubkeyHash` is looked up from `operator`'s registration info
- * @param blockNumber Is the desired block number at which we wish to query the operator's position in the `operatorList` array
- * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the
- * array `pubkeyHashToIndexHistory[pubkeyHash]` to pull the info from.
- */
- function getOperatorIndex(address operator, uint32 blockNumber, uint32 index) external view returns (uint32);
-
- /**
- * @notice Looks up the number of total operators at the specified `blockNumber`.
- * @param index Input used to specify the entry within the dynamic array `totalOperatorsHistory` to read data from.
- * @dev This function will revert if the provided `index` is out of bounds.
- */
- function getTotalOperators(uint32 blockNumber, uint32 index) external view returns (uint32);
-
- /// @notice Returns the current number of operators of this service.
- function numOperators() external view returns (uint32);
-
- /**
- * @notice Returns the most recent stake weights for the `operator`
- * @dev Function returns weights of **0** in the event that the operator has no stake history
- */
- function operatorStakes(address operator) external view returns (uint96, uint96);
-
- /// @notice Returns the stake amounts from the latest entry in `totalStakeHistory`.
- function totalStake() external view returns (uint96, uint96);
-}
diff --git a/src/contracts/interfaces/IRegistry.sol b/src/contracts/interfaces/IRegistry.sol
deleted file mode 100644
index 9166dcb1c3..0000000000
--- a/src/contracts/interfaces/IRegistry.sol
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-/**
- * @title Minimal interface for a `Registry`-type contract.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice Functions related to the registration process itself have been intentionally excluded
- * because their function signatures may vary significantly.
- */
-interface IRegistry {
- /// @notice Returns 'true' if `operator` is registered as an active operator, and 'false' otherwise.
- function isActiveOperator(address operator) external view returns (bool);
-}
diff --git a/src/contracts/interfaces/IServiceManager.sol b/src/contracts/interfaces/IServiceManager.sol
deleted file mode 100644
index 953ab38447..0000000000
--- a/src/contracts/interfaces/IServiceManager.sol
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "./IDelegationManager.sol";
-
-/**
- * @title Interface for a `ServiceManager`-type contract.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-interface IServiceManager {
- /// @notice Returns the current 'taskNumber' for the middleware
- function taskNumber() external view returns (uint32);
-
- /// @notice Permissioned function that causes the ServiceManager to freeze the operator on EigenLayer, through a call to the Slasher contract
- function freezeOperator(address operator) external;
-
- /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording an initial stake update (on operator registration)
- function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external;
-
- /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording a stake update
- function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external;
-
- /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording a final stake update (on operator deregistration)
- function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external;
-
- /// @notice Returns the latest block until which operators must serve.
- function latestServeUntilBlock() external view returns (uint32);
-
- function owner() external view returns (address);
-}
\ No newline at end of file
diff --git a/src/contracts/interfaces/ISignatureUtils.sol b/src/contracts/interfaces/ISignatureUtils.sol
new file mode 100644
index 0000000000..e1f4887796
--- /dev/null
+++ b/src/contracts/interfaces/ISignatureUtils.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity >=0.5.0;
+
+/**
+ * @title The interface for common signature utilities.
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ */
+interface ISignatureUtils {
+ // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management.
+ struct SignatureWithExpiry {
+ // the signature itself, formatted as a single bytes object
+ bytes signature;
+ // the expiration timestamp (UTC) of the signature
+ uint256 expiry;
+ }
+
+ // @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management.
+ struct SignatureWithSaltAndExpiry {
+ // the signature itself, formatted as a single bytes object
+ bytes signature;
+ // the salt used to generate the signature
+ bytes32 salt;
+ // the expiration timestamp (UTC) of the signature
+ uint256 expiry;
+ }
+}
\ No newline at end of file
diff --git a/src/contracts/interfaces/ISlasher.sol b/src/contracts/interfaces/ISlasher.sol
index 63612f29d9..a79e4ae83a 100644
--- a/src/contracts/interfaces/ISlasher.sol
+++ b/src/contracts/interfaces/ISlasher.sol
@@ -1,5 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
+
+import "./IStrategyManager.sol";
+import "./IDelegationManager.sol";
/**
* @title Interface for the primary 'slashing' contract for EigenLayer.
@@ -18,12 +21,41 @@ interface ISlasher {
// struct used to store details relevant to a single middleware that an operator has opted-in to serving
struct MiddlewareDetails {
+ // the block at which the contract begins being able to finalize the operator's registration with the service via calling `recordFirstStakeUpdate`
+ uint32 registrationMayBeginAtBlock;
// the block before which the contract is allowed to slash the user
uint32 contractCanSlashOperatorUntilBlock;
// the block at which the middleware's view of the operator's stake was most recently updated
uint32 latestUpdateBlock;
}
+ /// @notice Emitted when a middleware times is added to `operator`'s array.
+ event MiddlewareTimesAdded(
+ address operator,
+ uint256 index,
+ uint32 stalestUpdateBlock,
+ uint32 latestServeUntilBlock
+ );
+
+ /// @notice Emitted when `operator` begins to allow `contractAddress` to slash them.
+ event OptedIntoSlashing(address indexed operator, address indexed contractAddress);
+
+ /// @notice Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilBlock`.
+ event SlashingAbilityRevoked(
+ address indexed operator,
+ address indexed contractAddress,
+ uint32 contractCanSlashOperatorUntilBlock
+ );
+
+ /**
+ * @notice Emitted when `slashingContract` 'freezes' the `slashedOperator`.
+ * @dev The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'.
+ */
+ event OperatorFrozen(address indexed slashedOperator, address indexed slashingContract);
+
+ /// @notice Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within EigenLayer.
+ event FrozenStatusReset(address indexed previouslySlashedAddress);
+
/**
* @notice Gives the `contractAddress` permission to slash the funds of the caller.
* @dev Typically, this function must be called prior to registering for a middleware.
@@ -37,7 +69,7 @@ interface ISlasher {
* @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`.
*/
function freezeOperator(address toBeFrozen) external;
-
+
/**
* @notice Removes the 'frozen' status from each of the `frozenAddresses`
* @dev Callable only by the contract owner (i.e. governance).
@@ -45,7 +77,7 @@ interface ISlasher {
function resetFrozenStatus(address[] calldata frozenAddresses) external;
/**
- * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
+ * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
* is slashable until serveUntil
* @param operator the operator whose stake update is being recorded
* @param serveUntilBlock the block until which the operator's stake at the current block is slashable
@@ -60,13 +92,18 @@ interface ISlasher {
* @param updateBlock the block for which the stake update is being recorded
* @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
* @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after
- * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
+ * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
* but it is anticipated to be rare and not detrimental.
*/
- function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 insertAfter) external;
+ function recordStakeUpdate(
+ address operator,
+ uint32 updateBlock,
+ uint32 serveUntilBlock,
+ uint256 insertAfter
+ ) external;
/**
- * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
+ * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
* is slashable until serveUntil
* @param operator the operator whose stake update is being recorded
* @param serveUntilBlock the block until which the operator's stake at the current block is slashable
@@ -75,6 +112,12 @@ interface ISlasher {
*/
function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external;
+ /// @notice The StrategyManager contract of EigenLayer
+ function strategyManager() external view returns (IStrategyManager);
+
+ /// @notice The DelegationManager contract of EigenLayer
+ function delegation() external view returns (IDelegationManager);
+
/**
* @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
* slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
@@ -89,7 +132,10 @@ interface ISlasher {
function canSlash(address toBeSlashed, address slashingContract) external view returns (bool);
/// @notice Returns the block until which `serviceContract` is allowed to slash the `operator`.
- function contractCanSlashOperatorUntilBlock(address operator, address serviceContract) external view returns (uint32);
+ function contractCanSlashOperatorUntilBlock(
+ address operator,
+ address serviceContract
+ ) external view returns (uint32);
/// @notice Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32);
@@ -109,31 +155,41 @@ interface ISlasher {
* @param middlewareTimesIndex Indicates an index in `operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw
* @dev The correct `middlewareTimesIndex` input should be computable off-chain.
*/
- function canWithdraw(address operator, uint32 withdrawalStartBlock, uint256 middlewareTimesIndex) external returns(bool);
+ function canWithdraw(
+ address operator,
+ uint32 withdrawalStartBlock,
+ uint256 middlewareTimesIndex
+ ) external returns (bool);
/**
- * operator =>
+ * operator =>
* [
* (
- * the least recent update block of all of the middlewares it's serving/served,
+ * the least recent update block of all of the middlewares it's serving/served,
* latest time that the stake bonded at that update needed to serve until
* )
* ]
*/
- function operatorToMiddlewareTimes(address operator, uint256 arrayIndex) external view returns (MiddlewareTimes memory);
+ function operatorToMiddlewareTimes(
+ address operator,
+ uint256 arrayIndex
+ ) external view returns (MiddlewareTimes memory);
/// @notice Getter function for fetching `operatorToMiddlewareTimes[operator].length`
function middlewareTimesLength(address operator) external view returns (uint256);
/// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
- function getMiddlewareTimesIndexBlock(address operator, uint32 index) external view returns(uint32);
+ function getMiddlewareTimesIndexStalestUpdateBlock(address operator, uint32 index) external view returns (uint32);
/// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`.
- function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns(uint32);
+ function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32);
/// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256);
/// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
- function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256);
+ function operatorWhitelistedContractsLinkedListEntry(
+ address operator,
+ address node
+ ) external view returns (bool, uint256, uint256);
}
diff --git a/src/contracts/interfaces/ISocketUpdater.sol b/src/contracts/interfaces/ISocketUpdater.sol
new file mode 100644
index 0000000000..8f28465e1a
--- /dev/null
+++ b/src/contracts/interfaces/ISocketUpdater.sol
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+/**
+ * @title Interface for an `ISocketUpdater` where operators can update their sockets.
+ * @author Layr Labs, Inc.
+ */
+interface ISocketUpdater {
+ // EVENTS
+
+ event OperatorSocketUpdate(bytes32 indexed operatorId, string socket);
+
+ // FUNCTIONS
+
+ /**
+ * @notice Updates the socket of the msg.sender given they are a registered operator
+ * @param socket is the new socket of the operator
+ */
+ function updateSocket(string memory socket) external;
+}
+
diff --git a/src/contracts/interfaces/IStrategy.sol b/src/contracts/interfaces/IStrategy.sol
index 80bc9a323e..5db9a9736a 100644
--- a/src/contracts/interfaces/IStrategy.sol
+++ b/src/contracts/interfaces/IStrategy.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
@@ -21,14 +21,14 @@ interface IStrategy {
function deposit(IERC20 token, uint256 amount) external returns (uint256);
/**
- * @notice Used to withdraw tokens from this Strategy, to the `depositor`'s address
- * @param depositor is the address to receive the withdrawn funds
+ * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address
+ * @param recipient is the address to receive the withdrawn funds
* @param token is the ERC20 token being transferred out
* @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.
*/
- function withdraw(address depositor, IERC20 token, uint256 amountShares) external;
+ function withdraw(address recipient, IERC20 token, uint256 amountShares) external;
/**
* @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
@@ -54,7 +54,13 @@ interface IStrategy {
*/
function userUnderlying(address user) external returns (uint256);
- /**
+ /**
+ * @notice convenience function for fetching the current total shares of `user` in this strategy, by
+ * querying the `strategyManager` contract
+ */
+ function shares(address user) external view returns (uint256);
+
+ /**
* @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
* @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications
* @param amountShares is the amount of shares to calculate its conversion into the underlying token
diff --git a/src/contracts/interfaces/IStrategyManager.sol b/src/contracts/interfaces/IStrategyManager.sol
index 49313727dd..ecdb175c84 100644
--- a/src/contracts/interfaces/IStrategyManager.sol
+++ b/src/contracts/interfaces/IStrategyManager.sol
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
import "./IStrategy.sol";
import "./ISlasher.sol";
import "./IDelegationManager.sol";
+import "./IEigenPodManager.sol";
/**
* @title Interface for the primary entrypoint for funds into EigenLayer.
@@ -12,70 +13,49 @@ import "./IDelegationManager.sol";
* @notice See the `StrategyManager` contract itself for implementation details.
*/
interface IStrategyManager {
- // packed struct for queued withdrawals; helps deal with stack-too-deep errors
- struct WithdrawerAndNonce {
- address withdrawer;
- uint96 nonce;
- }
-
/**
- * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
- * In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or `completeQueuedWithdrawal`,
- * the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked against the
- * stored hash in order to confirm the integrity of the submitted data.
+ * @notice Emitted when a new deposit occurs on behalf of `staker`.
+ * @param staker Is the staker who is depositing funds into EigenLayer.
+ * @param strategy Is the strategy that `staker` has deposited into.
+ * @param token Is the token that `staker` deposited.
+ * @param shares Is the number of new shares `staker` has been granted in `strategy`.
*/
- struct QueuedWithdrawal {
- IStrategy[] strategies;
- uint256[] shares;
- address depositor;
- WithdrawerAndNonce withdrawerAndNonce;
- uint32 withdrawalStartBlock;
- address delegatedAddress;
- }
+ 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);
+
+ /// @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 Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
* @param strategy is the specified strategy where deposit is to be made,
* @param token is the denomination in which the deposit is to be made,
- * @param amount is the amount of token to be deposited in the strategy by the depositor
+ * @param amount is the amount of token to be deposited in the strategy by the staker
* @return shares The amount of new shares in the `strategy` created as part of the action.
* @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
* @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
- *
+ *
* WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
* where the token balance and corresponding strategy shares are not in sync upon reentrancy.
*/
- function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount)
- external
- returns (uint256 shares);
-
-
- /**
- * @notice Deposits `amount` of beaconchain ETH into this contract on behalf of `staker`
- * @param staker is the entity that is restaking in eigenlayer,
- * @param amount is the amount of beaconchain ETH being restaked,
- * @dev Only callable by EigenPodManager.
- */
- function depositBeaconChainETH(address staker, uint256 amount) external;
-
- /**
- * @notice Records an overcommitment event on behalf of a staker. The staker's beaconChainETH shares are decremented by `amount`.
- * @param overcommittedPodOwner is the pod owner to be slashed
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy in case it must be removed,
- * @param amount is the amount to decrement the slashedAddress's beaconChainETHStrategy shares
- * @dev Only callable by EigenPodManager.
- */
- function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount)
- external;
+ function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount) external returns (uint256 shares);
/**
* @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
* who must sign off on the action.
- * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
+ * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
* purely to help one address deposit 'for' another.
* @param strategy is the specified strategy where deposit is to be made,
* @param token is the denomination in which the deposit is to be made,
- * @param amount is the amount of token to be deposited in the strategy by the depositor
+ * @param amount is the amount of token to be deposited in the strategy by the staker
* @param staker the staker that the deposited assets will be credited to
* @param expiry the timestamp at which the signature expires
* @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward
@@ -84,8 +64,8 @@ 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
*/
@@ -96,151 +76,43 @@ interface IStrategyManager {
address staker,
uint256 expiry,
bytes memory signature
- )
- external
- returns (uint256 shares);
+ ) external returns (uint256 shares);
+
+ /// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the withdrawal queue
+ 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, 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;
/// @notice Returns the current shares of `user` in `strategy`
function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares);
/**
- * @notice Get all details on the depositor's deposits and corresponding shares
- * @return (depositor's strategies, shares in these strategies)
+ * @notice Get all details on the staker's deposits and corresponding shares
+ * @return (staker's strategies, shares in these strategies)
*/
- function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory);
+ function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory);
/// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
function stakerStrategyListLength(address staker) external view returns (uint256);
- /**
- * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
- * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
- * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
- * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
- * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
- * that the value per share reported by each strategy will remain consistent, and that the shares will continue
- * to accrue gains during the enforced withdrawal waiting period.
- * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
- * for which `msg.sender` is withdrawing 100% of their shares
- * @param strategies The Strategies to withdraw from
- * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
- * @param withdrawer The address that can complete the withdrawal and will receive any withdrawn funds or shares upon completing the withdrawal
- * @param undelegateIfPossible If this param is marked as 'true' *and the withdrawal will result in `msg.sender` having no shares in any Strategy,*
- * then this function will also make an internal call to `undelegate(msg.sender)` to undelegate the `msg.sender`.
- * @return The 'withdrawalRoot' of the newly created Queued Withdrawal
- * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
- * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
- * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
- * `stakerStrategyList` to lowest index
- * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
- * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed
- * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
- * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
- */
- function queueWithdrawal(
- uint256[] calldata strategyIndexes,
- IStrategy[] calldata strategies,
- uint256[] calldata shares,
- address withdrawer,
- bool undelegateIfPossible
- )
- external returns(bytes32);
-
- /**
- * @notice Used to complete the specified `queuedWithdrawal`. The function caller must match `queuedWithdrawal.withdrawer`
- * @param queuedWithdrawal The QueuedWithdrawal to complete.
- * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array
- * of the `queuedWithdrawal`. This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
- * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
- * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves
- * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
- * will simply be transferred to the caller directly.
- * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
- */
- function completeQueuedWithdrawal(
- QueuedWithdrawal calldata queuedWithdrawal,
- IERC20[] calldata tokens,
- uint256 middlewareTimesIndex,
- bool receiveAsTokens
- )
- external;
-
- /**
- * @notice Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer`
- * @param queuedWithdrawals The QueuedWithdrawals to complete.
- * @param tokens Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
- * @param middlewareTimesIndexes One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
- * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves
- * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
- * will simply be transferred to the caller directly.
- * @dev Array-ified version of `completeQueuedWithdrawal`
- * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
- */
- function completeQueuedWithdrawals(
- QueuedWithdrawal[] calldata queuedWithdrawals,
- IERC20[][] calldata tokens,
- uint256[] calldata middlewareTimesIndexes,
- bool[] calldata receiveAsTokens
- )
- external;
-
- /**
- * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
- * @param slashedAddress is the frozen address that is having its shares slashed
- * @param recipient is the address that will receive the slashed funds, which could e.g. be a harmed party themself,
- * or a MerkleDistributor-type contract that further sub-divides the slashed funds.
- * @param strategies Strategies to slash
- * @param shareAmounts The amount of shares to slash in each of the provided `strategies`
- * @param tokens The tokens to use as input to the `withdraw` function of each of the provided `strategies`
- * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
- * for which `msg.sender` is withdrawing 100% of their shares
- * @param recipient The slashed funds are withdrawn as tokens to this address.
- * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
- * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
- * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
- * `stakerStrategyList` to lowest index
- */
- function slashShares(
- address slashedAddress,
- address recipient,
- IStrategy[] calldata strategies,
- IERC20[] calldata tokens,
- uint256[] calldata strategyIndexes,
- uint256[] calldata shareAmounts
- )
- external;
-
- /**
- * @notice Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one)
- * @param recipient The funds in the slashed withdrawal are withdrawn as tokens to this address.
- * @param queuedWithdrawal The previously queued withdrawal to be slashed
- * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies`
- * array of the `queuedWithdrawal`.
- * @param indicesToSkip Optional input parameter -- indices in the `strategies` array to skip (i.e. not call the 'withdraw' function on). This input exists
- * so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function,
- * then the malicious strategy can be skipped (with the shares in effect "burned"), while the non-malicious strategies are still called as normal.
- */
- function slashQueuedWithdrawal(address recipient, QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256[] calldata indicesToSkip)
- external;
-
- /// @notice Returns the keccak256 hash of `queuedWithdrawal`.
- function calculateWithdrawalRoot(
- QueuedWithdrawal memory queuedWithdrawal
- )
- external
- pure
- returns (bytes32);
-
/**
* @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into
* @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already)
- */
- function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external;
+ * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
+ */
+ 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
* @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it)
- */
+ */
function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external;
/// @notice Returns the single, central Delegation contract of EigenLayer
@@ -249,9 +121,41 @@ interface IStrategyManager {
/// @notice Returns the single, central Slasher contract of EigenLayer
function slasher() external view returns (ISlasher);
- /// @notice returns the enshrined, virtual 'beaconChainETH' Strategy
- function beaconChainETHStrategy() external view returns (IStrategy);
+ /// @notice Returns the EigenPodManager contract of EigenLayer
+ function eigenPodManager() external view returns (IEigenPodManager);
+
+ /// @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 {
+ address withdrawer;
+ uint96 nonce;
+ }
+
+ /**
+ * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
+ * In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or `completeQueuedWithdrawal`,
+ * the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked against the
+ * stored hash in order to confirm the integrity of the submitted data.
+ */
+ struct DeprecatedStruct_QueuedWithdrawal {
+ IStrategy[] strategies;
+ uint256[] shares;
+ address staker;
+ DeprecatedStruct_WithdrawerAndNonce withdrawerAndNonce;
+ uint32 withdrawalStartBlock;
+ address delegatedAddress;
+ }
+
+ function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external returns (bool, bytes32);
- /// @notice Returns the number of blocks that must pass between the time a withdrawal is queued and the time it can be completed
- function withdrawalDelayBlocks() external view returns (uint256);
+ function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external pure returns (bytes32);
}
diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol
deleted file mode 100644
index cc0f94dd28..0000000000
--- a/src/contracts/interfaces/IVoteWeigher.sol
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-/**
- * @title Interface for a `VoteWeigher`-type contract.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice Note that `NUMBER_OF_QUORUMS` is expected to remain constant, as suggested by its uppercase formatting.
- */
-interface IVoteWeigher {
- /**
- * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber.
- * @dev returns zero in the case that `quorumNumber` is greater than or equal to `NUMBER_OF_QUORUMS`
- */
- function weightOfOperator(address operator, uint256 quorumNumber) external returns (uint96);
-
- /// @notice Number of quorums that are being used by the middleware.
- function NUMBER_OF_QUORUMS() external view returns (uint256);
-
- /**
- * @notice This defines the earnings split between different quorums. Mapping is quorumNumber => BIPS which the quorum earns, out of the total earnings.
- * @dev The sum of all entries, i.e. sum(quorumBips[0] through quorumBips[NUMBER_OF_QUORUMS - 1]) should *always* be 10,000!
- */
- function quorumBips(uint256 quorumNumber) external view returns (uint256);
-}
diff --git a/src/contracts/interfaces/IWhitelister.sol b/src/contracts/interfaces/IWhitelister.sol
index 2467bc3ebf..da68992c86 100644
--- a/src/contracts/interfaces/IWhitelister.sol
+++ b/src/contracts/interfaces/IWhitelister.sol
@@ -1,19 +1,16 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity >=0.5.0;
import "../../contracts/interfaces/IStrategyManager.sol";
import "../../contracts/interfaces/IStrategy.sol";
import "../../contracts/interfaces/IDelegationManager.sol";
-import "../../contracts/interfaces/IBLSRegistry.sol";
import "../../../script/whitelist/Staker.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Create2.sol";
-
interface IWhitelister {
-
function whitelist(address operator) external;
function getStaker(address operator) external returns (address);
@@ -27,30 +24,18 @@ interface IWhitelister {
function queueWithdrawal(
address staker,
- uint256[] calldata strategyIndexes,
- IStrategy[] calldata strategies,
- uint256[] calldata shares,
- address withdrawer,
- bool undelegateIfPossible
+ IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams
) external returns (bytes memory);
function completeQueuedWithdrawal(
address staker,
- IStrategyManager.QueuedWithdrawal calldata queuedWithdrawal,
+ IDelegationManager.Withdrawal calldata queuedWithdrawal,
IERC20[] calldata tokens,
uint256 middlewareTimesIndex,
bool receiveAsTokens
) external returns (bytes memory);
- function transfer(
- address staker,
- address token,
- address to,
- uint256 amount
- ) external returns (bytes memory) ;
-
- function callAddress(
- address to,
- bytes memory data
- ) external payable returns (bytes memory);
+ function transfer(address staker, address token, address to, uint256 amount) external returns (bytes memory);
+
+ function callAddress(address to, bytes memory data) external payable returns (bytes memory);
}
diff --git a/src/contracts/libraries/BN254.sol b/src/contracts/libraries/BN254.sol
deleted file mode 100644
index ca01f2240e..0000000000
--- a/src/contracts/libraries/BN254.sol
+++ /dev/null
@@ -1,325 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1 AND MIT
-// several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
-// Copyright 2017 Christian Reitwiessner
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-
-// The remainder of the code is written by LayrLabs Inc. and is under the BUSL-1.1 license
-
-pragma solidity =0.8.12;
-
-/**
- * @title Library for operations on the BN254 elliptic curve.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
- */
-library BN254 {
- // modulus for the underlying field F_p of the elliptic curve
- uint256 internal constant FP_MODULUS =
- 21888242871839275222246405745257275088696311157297823662689037894645226208583;
- // modulus for the underlying field F_r of the elliptic curve
- uint256 internal constant FR_MODULUS =
- 21888242871839275222246405745257275088548364400416034343698204186575808495617;
-
- struct G1Point {
- uint256 X;
- uint256 Y;
- }
-
- // Encoding of field elements is: X[1] * i + X[0]
- struct G2Point {
- uint256[2] X;
- uint256[2] Y;
- }
-
- // generator of group G2
- /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
- uint256 internal constant G2x1 =
- 11559732032986387107991004021392285783925812861821192530917403151452391805634;
- uint256 internal constant G2x0 =
- 10857046999023057135944570762232829481370756359578518086990519993285655852781;
- uint256 internal constant G2y1 =
- 4082367875863433681332203403145435568316851327593401208105741076214120093531;
- uint256 internal constant G2y0 =
- 8495653923123431417604973247489272438418190587263600148770280649306958101930;
- /// @notice returns the G2 generator
- /// @dev mind the ordering of the 1s and 0s!
- /// this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
- /// "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
- /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
- function generatorG2() internal pure returns (G2Point memory) {
- return G2Point(
- [G2x1, G2x0], [G2y1, G2y0]
- );
- }
-
- // negation of the generator of group G2
- /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
- uint256 internal constant nG2x1 =
- 11559732032986387107991004021392285783925812861821192530917403151452391805634;
- uint256 internal constant nG2x0 =
- 10857046999023057135944570762232829481370756359578518086990519993285655852781;
- uint256 internal constant nG2y1 =
- 17805874995975841540914202342111839520379459829704422454583296818431106115052;
- uint256 internal constant nG2y0 =
- 13392588948715843804641432497768002650278120570034223513918757245338268106653;
- function negGeneratorG2() internal pure returns (G2Point memory) {
- return G2Point(
- [nG2x1, nG2x0], [nG2y1, nG2y0]
- );
- }
-
- bytes32 internal constant powersOfTauMerkleRoot =
- 0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;
-
-
- /**
- * @param p Some point in G1.
- * @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
- */
- function negate(G1Point memory p) internal pure returns (G1Point memory) {
- // The prime q in the base field F_q for G1
- if (p.X == 0 && p.Y == 0) {
- return G1Point(0, 0);
- } else {
- return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
- }
- }
-
- /**
- * @return r the sum of two points of G1
- */
- function plus(
- G1Point memory p1,
- G1Point memory p2
- ) internal view returns (G1Point memory r) {
- uint256[4] memory input;
- input[0] = p1.X;
- input[1] = p1.Y;
- input[2] = p2.X;
- input[3] = p2.Y;
- bool success;
-
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
- // Use "invalid" to make gas estimation work
- switch success
- case 0 {
- invalid()
- }
- }
-
- require(success, "ec-add-failed");
- }
-
- /**
- * @return r the product of a point on G1 and a scalar, i.e.
- * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
- * points p.
- */
- function scalar_mul(
- G1Point memory p,
- uint256 s
- ) internal view returns (G1Point memory r) {
- uint256[3] memory input;
- input[0] = p.X;
- input[1] = p.Y;
- input[2] = s;
- bool success;
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
- // Use "invalid" to make gas estimation work
- switch success
- case 0 {
- invalid()
- }
- }
- require(success, "ec-mul-failed");
- }
-
- /**
- * @return The result of computing the pairing check
- * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
- * For example,
- * pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
- */
- function pairing(
- G1Point memory a1,
- G2Point memory a2,
- G1Point memory b1,
- G2Point memory b2
- ) internal view returns (bool) {
- G1Point[2] memory p1 = [a1, b1];
- G2Point[2] memory p2 = [a2, b2];
-
- uint256[12] memory input;
-
- for (uint256 i = 0; i < 2; i++) {
- uint256 j = i * 6;
- input[j + 0] = p1[i].X;
- input[j + 1] = p1[i].Y;
- input[j + 2] = p2[i].X[0];
- input[j + 3] = p2[i].X[1];
- input[j + 4] = p2[i].Y[0];
- input[j + 5] = p2[i].Y[1];
- }
-
- uint256[1] memory out;
- bool success;
-
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(
- sub(gas(), 2000),
- 8,
- input,
- mul(12, 0x20),
- out,
- 0x20
- )
- // Use "invalid" to make gas estimation work
- switch success
- case 0 {
- invalid()
- }
- }
-
- require(success, "pairing-opcode-failed");
-
- return out[0] != 0;
- }
-
- /**
- * @notice This function is functionally the same as pairing(), however it specifies a gas limit
- * the user can set, as a precompile may use the entire gas budget if it reverts.
- */
- function safePairing(
- G1Point memory a1,
- G2Point memory a2,
- G1Point memory b1,
- G2Point memory b2,
- uint256 pairingGas
- ) internal view returns (bool, bool) {
- G1Point[2] memory p1 = [a1, b1];
- G2Point[2] memory p2 = [a2, b2];
-
- uint256[12] memory input;
-
- for (uint256 i = 0; i < 2; i++) {
- uint256 j = i * 6;
- input[j + 0] = p1[i].X;
- input[j + 1] = p1[i].Y;
- input[j + 2] = p2[i].X[0];
- input[j + 3] = p2[i].X[1];
- input[j + 4] = p2[i].Y[0];
- input[j + 5] = p2[i].Y[1];
- }
-
- uint256[1] memory out;
- bool success;
-
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(
- pairingGas,
- 8,
- input,
- mul(12, 0x20),
- out,
- 0x20
- )
- }
-
- //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
- //Success is true if the precompile actually goes through (aka all inputs are valid)
-
- return (success, out[0] != 0);
- }
-
- /// @return the keccak256 hash of the G1 Point
- /// @dev used for BLS signatures
- function hashG1Point(
- BN254.G1Point memory pk
- ) internal pure returns (bytes32) {
- return keccak256(abi.encodePacked(pk.X, pk.Y));
- }
-
-
- /**
- * @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
- */
- function hashToG1(bytes32 _x) internal view returns (uint256, uint256) {
- uint256 beta = 0;
- uint256 y = 0;
-
- // XXX: Gen Order (n) or Field Order (p) ?
- uint256 x = uint256(_x) % FP_MODULUS;
-
- while( true ) {
- (beta, y) = findYFromX(x);
-
- // y^2 == beta
- if( beta == mulmod(y, y, FP_MODULUS) ) {
- return (x, y);
- }
-
- x = addmod(x, 1, FP_MODULUS);
- }
- return (0, 0);
- }
-
- /**
- * Given X, find Y
- *
- * where y = sqrt(x^3 + b)
- *
- * Returns: (x^3 + b), y
- */
- function findYFromX(uint256 x)
- internal view returns(uint256, uint256)
- {
- // beta = (x^3 + b) % p
- uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);
-
- // y^2 = x^3 + b
- // this acts like: y = sqrt(beta) = beta^((p+1) / 4)
- uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS);
-
- return (beta, y);
- }
-
- function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) {
- bool success;
- uint256[1] memory output;
- uint[6] memory input;
- input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
- input[1] = 0x20; // expLen = new(big.Int).SetBytes(getData(input, 32, 32))
- input[2] = 0x20; // modLen = new(big.Int).SetBytes(getData(input, 64, 32))
- input[3] = _base;
- input[4] = _exponent;
- input[5] = _modulus;
- assembly {
- success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
- // Use "invalid" to make gas estimation work
- switch success case 0 { invalid() }
- }
- require(success, "BN254.expMod: call failure");
- return output[0];
- }
-}
diff --git a/src/contracts/libraries/BeaconChainProofs.sol b/src/contracts/libraries/BeaconChainProofs.sol
index 8491418582..2c4b886629 100644
--- a/src/contracts/libraries/BeaconChainProofs.sol
+++ b/src/contracts/libraries/BeaconChainProofs.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.0;
import "./Merkle.sol";
import "../libraries/Endian.sol";
@@ -11,78 +11,54 @@ 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
+ uint256 internal constant HISTORICAL_SUMMARIES_TREE_HEIGHT = 24;
+
+ //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;
uint256 internal constant VALIDATOR_TREE_HEIGHT = 40;
- //refer to the eigenlayer-cli proof library. Despite being the same dimensions as the validator tree, the balance tree is merkleized differently
- uint256 internal constant BALANCE_TREE_HEIGHT = 38;
// MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4
uint256 internal constant WITHDRAWALS_TREE_HEIGHT = 4;
- //in beacon block body
+ //in beacon block body https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconblockbody
uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9;
- // in beacon block header
- uint256 internal constant STATE_ROOT_INDEX = 3;
- uint256 internal constant PROPOSER_INDEX_INDEX = 1;
+ // 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 STATE_ROOT_INDEX = 3;
uint256 internal constant BODY_ROOT_INDEX = 4;
- // in beacon state
- uint256 internal constant STATE_ROOTS_INDEX = 6;
- uint256 internal constant BLOCK_ROOTS_INDEX = 5;
- uint256 internal constant HISTORICAL_ROOTS_INDEX = 7;
- uint256 internal constant ETH_1_ROOT_INDEX = 8;
+ // in beacon state https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate
uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11;
- uint256 internal constant BALANCE_INDEX = 12;
- uint256 internal constant EXECUTION_PAYLOAD_HEADER_INDEX = 24;
- uint256 internal constant HISTORICAL_BATCH_STATE_ROOT_INDEX = 1;
+ uint256 internal constant HISTORICAL_SUMMARIES_INDEX = 27;
- // in validator
+ // 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 BLOCK_NUMBER_INDEX = 6;
- uint256 internal constant WITHDRAWALS_ROOT_INDEX = 14;
+ uint256 internal constant TIMESTAMP_INDEX = 9;
//in execution payload
uint256 internal constant WITHDRAWALS_INDEX = 14;
@@ -91,173 +67,343 @@ 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
- uint256 internal constant SLOTS_PER_EPOCH = 32;
- bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
+ /// @notice The number of slots each epoch in the beacon chain
+ uint64 internal constant SLOTS_PER_EPOCH = 32;
+ /// @notice The number of seconds in a slot in the beacon chain
+ uint64 internal constant SECONDS_PER_SLOT = 12;
+ /// @notice Number of seconds per epoch: 384 == 32 slots/epoch * 12 seconds/slot
+ uint64 internal constant SECONDS_PER_EPOCH = SLOTS_PER_EPOCH * SECONDS_PER_SLOT;
- struct WithdrawalProofs {
- bytes blockHeaderProof;
+ bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
+
+ /// @notice This struct contains the merkle proofs and leaves needed to verify a partial/full withdrawal
+ struct WithdrawalProof {
bytes withdrawalProof;
bytes slotProof;
bytes executionPayloadProof;
- bytes blockNumberProof;
- uint64 blockHeaderRootIndex;
+ bytes timestampProof;
+ bytes historicalSummaryBlockRootProof;
+ uint64 blockRootIndex;
+ uint64 historicalSummaryIndex;
uint64 withdrawalIndex;
- bytes32 blockHeaderRoot;
- bytes32 blockBodyRoot;
+ bytes32 blockRoot;
bytes32 slotRoot;
- bytes32 blockNumberRoot;
+ bytes32 timestampRoot;
bytes32 executionPayloadRoot;
}
- struct ValidatorFieldsAndBalanceProofs {
- bytes validatorFieldsProof;
- bytes validatorBalanceProof;
- bytes32 balanceRoot;
- }
-
- struct ValidatorFieldsProof {
- bytes validatorProof;
- uint40 validatorIndex;
- }
-
- /**
- *
- * @notice This function is parses the balanceRoot to get the uint64 balance of a validator. During merkleization of the
- * beacon state balance tree, four uint64 values (making 32 bytes) are grouped together and treated as a single leaf in the merkle tree. Thus the
- * validatorIndex mod 4 is used to determine which of the four uint64 values to extract from the balanceRoot.
- * @param validatorIndex is the index of the validator being proven for.
- * @param balanceRoot is the combination of 4 validator balances being proven for.
- * @return The validator's balance, in Gwei
- */
- function getBalanceFromBalanceRoot(uint40 validatorIndex, bytes32 balanceRoot) internal pure returns (uint64) {
- uint256 bitShiftAmount = (validatorIndex % 4) * 64;
- bytes32 validatorBalanceLittleEndian = bytes32((uint256(balanceRoot) << bitShiftAmount));
- uint64 validatorBalance = Endian.fromLittleEndianUint64(validatorBalanceLittleEndian);
- return validatorBalance;
+ /// @notice This struct contains the root and proof for verifying the state root against the oracle block root
+ struct StateRootProof {
+ bytes32 beaconStateRoot;
+ bytes proof;
}
/**
* @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root
* @param validatorIndex the index of the proven validator
* @param beaconStateRoot is the beacon chain state root to be proven against.
- * @param proof is the data used in proving the validator's fields
+ * @param validatorFieldsProof is the data used in proving the validator's fields
* @param validatorFields the claimed fields of the validator
*/
function verifyValidatorFields(
- uint40 validatorIndex,
bytes32 beaconStateRoot,
- bytes calldata proof,
- bytes32[] calldata validatorFields
+ bytes32[] calldata validatorFields,
+ bytes calldata validatorFieldsProof,
+ uint40 validatorIndex
) internal view {
-
- require(validatorFields.length == 2**VALIDATOR_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length");
+ require(
+ validatorFields.length == 2 ** VALIDATOR_FIELD_TREE_HEIGHT,
+ "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length"
+ );
/**
* Note: the length of the validator merkle proof is BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1.
* There is an additional layer added by hashing the root with the length of the validator list
*/
- require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");
+ require(
+ validatorFieldsProof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length"
+ );
uint256 index = (VALIDATOR_TREE_ROOT_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex);
// merkleize the validatorFields to get the leaf to prove
bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields);
// verify the proof of the validatorRoot against the beaconStateRoot
- require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, validatorRoot, index), "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof");
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: validatorFieldsProof,
+ root: beaconStateRoot,
+ leaf: validatorRoot,
+ index: index
+ }),
+ "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof"
+ );
}
/**
- * @notice This function verifies merkle proofs of the balance of a certain validator against a beacon chain state root
- * @param validatorIndex the index of the proven validator
+ * @notice This function verifies the latestBlockHeader against the state root. the latestBlockHeader is
+ * a tracked in the beacon state.
* @param beaconStateRoot is the beacon chain state root to be proven against.
- * @param proof is the proof of the balance against the beacon chain state root
- * @param balanceRoot is the serialized balance used to prove the balance of the validator (refer to `getBalanceFromBalanceRoot` above for detailed explanation)
+ * @param stateRootProof is the provided merkle proof
+ * @param latestBlockRoot is hashtree root of the latest block header in the beacon state
*/
- function verifyValidatorBalance(
- uint40 validatorIndex,
+ function verifyStateRootAgainstLatestBlockRoot(
+ bytes32 latestBlockRoot,
bytes32 beaconStateRoot,
- bytes calldata proof,
- bytes32 balanceRoot
+ bytes calldata stateRootProof
) internal view {
- require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");
-
- /**
- * the beacon state's balance list is a list of uint64 values, and these are grouped together in 4s when merkleized.
- * Therefore, the index of the balance of a validator is validatorIndex/4
- */
- uint256 balanceIndex = uint256(validatorIndex/4);
- balanceIndex = (BALANCE_INDEX << (BALANCE_TREE_HEIGHT + 1)) | balanceIndex;
-
- require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, balanceRoot, balanceIndex), "BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof");
+ require(
+ stateRootProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot: Proof has incorrect length"
+ );
+ //Next we verify the slot against the blockRoot
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: stateRootProof,
+ root: latestBlockRoot,
+ leaf: beaconStateRoot,
+ index: STATE_ROOT_INDEX
+ }),
+ "BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot: Invalid latest block header root merkle proof"
+ );
}
/**
* @notice This function verifies the slot and the withdrawal fields for a given withdrawal
- * @param beaconStateRoot is the beacon chain state root to be proven against.
- * @param proofs is the provided set of merkle proofs
+ * @param withdrawalProof is the provided set of merkle proofs
* @param withdrawalFields is the serialized withdrawal container to be proven
*/
- function verifyWithdrawalProofs(
+ function verifyWithdrawal(
bytes32 beaconStateRoot,
- WithdrawalProofs calldata proofs,
- bytes32[] calldata withdrawalFields
+ bytes32[] calldata withdrawalFields,
+ WithdrawalProof calldata withdrawalProof,
+ uint64 denebForkTimestamp
) internal view {
- require(withdrawalFields.length == 2**WITHDRAWAL_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalFields has incorrect length");
-
- require(proofs.blockHeaderRootIndex < 2**BLOCK_ROOTS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: blockRootIndex is too large");
- require(proofs.withdrawalIndex < 2**WITHDRAWALS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalIndex is too large");
-
- // verify the block header proof length
- require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT),
- "BeaconChainProofs.verifyWithdrawalProofs: blockHeaderProof has incorrect length");
- require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
- "BeaconChainProofs.verifyWithdrawalProofs: withdrawalProof has incorrect length");
- require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
- "BeaconChainProofs.verifyWithdrawalProofs: executionPayloadProof has incorrect length");
- require(proofs.slotProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
- "BeaconChainProofs.verifyWithdrawalProofs: slotProof has incorrect length");
- require(proofs.blockNumberProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT),
- "BeaconChainProofs.verifyWithdrawalProofs: blockNumberProof has incorrect length");
-
-
+ require(
+ withdrawalFields.length == 2 ** WITHDRAWAL_FIELD_TREE_HEIGHT,
+ "BeaconChainProofs.verifyWithdrawal: withdrawalFields has incorrect length"
+ );
+
+ require(
+ withdrawalProof.blockRootIndex < 2 ** BLOCK_ROOTS_TREE_HEIGHT,
+ "BeaconChainProofs.verifyWithdrawal: blockRootIndex is too large"
+ );
+ require(
+ withdrawalProof.withdrawalIndex < 2 ** WITHDRAWALS_TREE_HEIGHT,
+ "BeaconChainProofs.verifyWithdrawal: withdrawalIndex is too large"
+ );
+
+ require(
+ withdrawalProof.historicalSummaryIndex < 2 ** HISTORICAL_SUMMARIES_TREE_HEIGHT,
+ "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 * (executionPayloadHeaderFieldTreeHeight + WITHDRAWALS_TREE_HEIGHT + 1),
+ "BeaconChainProofs.verifyWithdrawal: withdrawalProof has incorrect length"
+ );
+ require(
+ withdrawalProof.executionPayloadProof.length ==
+ 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyWithdrawal: executionPayloadProof has incorrect length"
+ );
+ require(
+ withdrawalProof.slotProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyWithdrawal: slotProof has incorrect length"
+ );
+ require(
+ withdrawalProof.timestampProof.length == 32 * (executionPayloadHeaderFieldTreeHeight),
+ "BeaconChainProofs.verifyWithdrawal: timestampProof has incorrect length"
+ );
+
+ require(
+ withdrawalProof.historicalSummaryBlockRootProof.length ==
+ 32 *
+ (BEACON_STATE_FIELD_TREE_HEIGHT +
+ (HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) +
+ 1 +
+ (BLOCK_ROOTS_TREE_HEIGHT)),
+ "BeaconChainProofs.verifyWithdrawal: historicalSummaryBlockRootProof has incorrect length"
+ );
/**
- * Computes the block_header_index relative to the beaconStateRoot. It concatenates the indexes of all the
- * intermediate root indexes from the bottom of the sub trees (the block header container) to the top of the tree
+ * Note: Here, the "1" in "1 + (BLOCK_ROOTS_TREE_HEIGHT)" signifies that extra step of choosing the "block_root_summary" within the individual
+ * "historical_summary". Everywhere else it signifies merkelize_with_mixin, where the length of an array is hashed with the root of the array,
+ * but not here.
*/
- uint256 blockHeaderIndex = BLOCK_ROOTS_INDEX << (BLOCK_ROOTS_TREE_HEIGHT) | uint256(proofs.blockHeaderRootIndex);
- // Verify the blockHeaderRoot against the beaconStateRoot
- require(Merkle.verifyInclusionSha256(proofs.blockHeaderProof, beaconStateRoot, proofs.blockHeaderRoot, blockHeaderIndex),
- "BeaconChainProofs.verifyWithdrawalProofs: Invalid block header merkle proof");
+ uint256 historicalBlockHeaderIndex = (HISTORICAL_SUMMARIES_INDEX <<
+ ((HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) + 1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
+ (uint256(withdrawalProof.historicalSummaryIndex) << (1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
+ (BLOCK_SUMMARY_ROOT_INDEX << (BLOCK_ROOTS_TREE_HEIGHT)) |
+ uint256(withdrawalProof.blockRootIndex);
+
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: withdrawalProof.historicalSummaryBlockRootProof,
+ root: beaconStateRoot,
+ leaf: withdrawalProof.blockRoot,
+ index: historicalBlockHeaderIndex
+ }),
+ "BeaconChainProofs.verifyWithdrawal: Invalid historicalsummary merkle proof"
+ );
+
+ //Next we verify the slot against the blockRoot
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: withdrawalProof.slotProof,
+ root: withdrawalProof.blockRoot,
+ leaf: withdrawalProof.slotRoot,
+ index: SLOT_INDEX
+ }),
+ "BeaconChainProofs.verifyWithdrawal: Invalid slot merkle proof"
+ );
+
+ {
+ // Next we verify the executionPayloadRoot against the blockRoot
+ uint256 executionPayloadIndex = (BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)) |
+ EXECUTION_PAYLOAD_INDEX;
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: withdrawalProof.executionPayloadProof,
+ root: withdrawalProof.blockRoot,
+ leaf: withdrawalProof.executionPayloadRoot,
+ index: executionPayloadIndex
+ }),
+ "BeaconChainProofs.verifyWithdrawal: Invalid executionPayload merkle proof"
+ );
+ }
+
+ // Next we verify the timestampRoot against the executionPayload root
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: withdrawalProof.timestampProof,
+ root: withdrawalProof.executionPayloadRoot,
+ leaf: withdrawalProof.timestampRoot,
+ index: TIMESTAMP_INDEX
+ }),
+ "BeaconChainProofs.verifyWithdrawal: Invalid timestamp merkle proof"
+ );
+
+ {
+ /**
+ * Next we verify the withdrawal fields 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.
+ */
+ uint256 withdrawalIndex = (WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1)) |
+ uint256(withdrawalProof.withdrawalIndex);
+ bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
+ require(
+ Merkle.verifyInclusionSha256({
+ proof: withdrawalProof.withdrawalProof,
+ root: withdrawalProof.executionPayloadRoot,
+ leaf: withdrawalRoot,
+ index: withdrawalIndex
+ }),
+ "BeaconChainProofs.verifyWithdrawal: Invalid withdrawal merkle proof"
+ );
+ }
+ }
+
+ /**
+ * @notice This function replicates the ssz hashing of a validator's pubkey, outlined below:
+ * hh := ssz.NewHasher()
+ * hh.PutBytes(validatorPubkey[:])
+ * validatorPubkeyHash := hh.Hash()
+ * hh.Reset()
+ */
+ function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) {
+ require(validatorPubkey.length == 48, "Input should be 48 bytes in length");
+ return sha256(abi.encodePacked(validatorPubkey, bytes16(0)));
+ }
- //Next we verify the slot against the blockHeaderRoot
- require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof");
+ /**
+ * @dev Retrieve the withdrawal timestamp
+ */
+ function getWithdrawalTimestamp(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) {
+ return
+ Endian.fromLittleEndianUint64(withdrawalProof.timestampRoot);
+ }
+
+ /**
+ * @dev Converts the withdrawal's slot to an epoch
+ */
+ function getWithdrawalEpoch(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) {
+ return
+ Endian.fromLittleEndianUint64(withdrawalProof.slotRoot) / SLOTS_PER_EPOCH;
+ }
+
+ /**
+ * Indices for validator fields (refer to consensus specs):
+ * 0: pubkey
+ * 1: withdrawal credentials
+ * 2: effective balance
+ * 3: slashed?
+ * 4: activation elligibility epoch
+ * 5: activation epoch
+ * 6: exit epoch
+ * 7: withdrawable epoch
+ */
- // Next we verify the executionPayloadRoot against the blockHeaderRoot
- uint256 executionPayloadIndex = BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)| EXECUTION_PAYLOAD_INDEX ;
- require(Merkle.verifyInclusionSha256(proofs.executionPayloadProof, proofs.blockHeaderRoot, proofs.executionPayloadRoot, executionPayloadIndex),
- "BeaconChainProofs.verifyWithdrawalProofs: Invalid executionPayload merkle proof");
+ /**
+ * @dev Retrieves a validator's pubkey hash
+ */
+ function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) {
+ return
+ validatorFields[VALIDATOR_PUBKEY_INDEX];
+ }
- // Next we verify the blockNumberRoot against the executionPayload root
- require(Merkle.verifyInclusionSha256(proofs.blockNumberProof, proofs.executionPayloadRoot, proofs.blockNumberRoot, BLOCK_NUMBER_INDEX),
- "BeaconChainProofs.verifyWithdrawalProofs: Invalid blockNumber merkle proof");
+ function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) {
+ return
+ validatorFields[VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX];
+ }
- /**
- * Next we verify the withdrawal fields against the blockHeaderRoot:
- * First we compute the withdrawal_index relative to the blockHeaderRoot by concatenating the indexes of all the
- * intermediate root indexes from the bottom of the sub trees (the withdrawal container) to the top, the blockHeaderRoot.
- * Then we calculate merkleize the withdrawalFields container to calculate the the withdrawalRoot.
- * Finally we verify the withdrawalRoot against the executionPayloadRoot.
- */
- uint256 withdrawalIndex = WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1) | uint256(proofs.withdrawalIndex);
- bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
- require(Merkle.verifyInclusionSha256(proofs.withdrawalProof, proofs.executionPayloadRoot, withdrawalRoot, withdrawalIndex),
- "BeaconChainProofs.verifyWithdrawalProofs: Invalid withdrawal merkle proof");
+ /**
+ * @dev Retrieves a validator's effective balance (in gwei)
+ */
+ function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) {
+ return
+ Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_BALANCE_INDEX]);
+ }
+
+ /**
+ * @dev Retrieves a validator's withdrawable epoch
+ */
+ function getWithdrawableEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) {
+ return
+ Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]);
+ }
+
+ /**
+ * Indices for withdrawal fields (refer to consensus specs):
+ * 0: withdrawal index
+ * 1: validator index
+ * 2: execution address
+ * 3: withdrawal amount
+ */
+
+ /**
+ * @dev Retrieves a withdrawal's validator index
+ */
+ function getValidatorIndex(bytes32[] memory withdrawalFields) internal pure returns (uint40) {
+ return
+ uint40(Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
}
-}
\ No newline at end of file
+ /**
+ * @dev Retrieves a withdrawal's withdrawal amount (in gwei)
+ */
+ function getWithdrawalAmountGwei(bytes32[] memory withdrawalFields) internal pure returns (uint64) {
+ return
+ Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
+ }
+}
diff --git a/src/contracts/libraries/BytesArrayBitmaps.sol b/src/contracts/libraries/BytesArrayBitmaps.sol
deleted file mode 100644
index fcf6c5be79..0000000000
--- a/src/contracts/libraries/BytesArrayBitmaps.sol
+++ /dev/null
@@ -1,274 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-
-pragma solidity =0.8.12;
-
-/**
- * @title Library for converting between an array of bytes and a bitmap.
- * @author Layr Labs, Inc.
- */
-library BytesArrayBitmaps {
- /**
- * @notice Byte arrays are meant to contain unique bytes.
- * If the array length exceeds 256, then it's impossible for all entries to be unique.
- * This constant captures the max allowed array length (inclusive, i.e. 256 is allowed).
- */
- uint256 constant MAX_BYTE_ARRAY_LENGTH = 256;
-
- /**
- * @notice Converts an array of bytes into a bitmap.
- * @param bytesArray The array of bytes to convert/compress into a bitmap.
- * @return The resulting bitmap.
- * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap
- * @dev This function will also revert if the `bytesArray` input contains any duplicate entries (i.e. duplicate bytes).
- */
- function bytesArrayToBitmap(bytes calldata bytesArray) internal pure returns (uint256) {
- // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s)
- require(bytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
- "BytesArrayBitmaps.bytesArrayToBitmap: bytesArray is too long");
-
- // return empty bitmap early if length of array is 0
- if (bytesArray.length == 0) {
- return uint256(0);
- }
-
- // initialize the empty bitmap, to be built inside the loop
- uint256 bitmap;
- // initialize an empty uint256 to be used as a bitmask inside the loop
- uint256 bitMask;
-
- // perform the 0-th loop iteration with the ordering check *omitted* (since it is unnecessary / will always pass)
- // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap
- bitmap = uint256(1 << uint8(bytesArray[0]));
-
- // loop through each byte in the array to construct the bitmap
- for (uint256 i = 1; i < bytesArray.length; ++i) {
- // construct a single-bit mask from the numerical value of the next byte out of the array
- bitMask = uint256(1 << uint8(bytesArray[i]));
- // check that the entry is not a repeat
- require(bitmap & bitMask == 0, "BytesArrayBitmaps.bytesArrayToBitmap: repeat entry in bytesArray");
- // add the entry to the bitmap
- bitmap = (bitmap | bitMask);
- }
- return bitmap;
- }
-
- /**
- * @notice Converts an ordered array of bytes into a bitmap.
- * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order.
- * @return The resulting bitmap.
- * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap.
- * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order).
- * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes).
- */
- function orderedBytesArrayToBitmap(bytes calldata orderedBytesArray) internal pure returns (uint256) {
- // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s)
- require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
- "BytesArrayBitmaps.orderedBytesArrayToBitmap: orderedBytesArray is too long");
-
- // return empty bitmap early if length of array is 0
- if (orderedBytesArray.length == 0) {
- return uint256(0);
- }
-
- // initialize the empty bitmap, to be built inside the loop
- uint256 bitmap;
- // initialize an empty uint256 to be used as a bitmask inside the loop
- uint256 bitMask;
-
- // perform the 0-th loop iteration with the ordering check *omitted* (since it is unnecessary / will always pass)
- // construct a single-bit mask from the numerical value of the 0th byte of the array, and immediately add it to the bitmap
- bitmap = uint256(1 << uint8(orderedBytesArray[0]));
-
- // loop through each byte in the array to construct the bitmap
- for (uint256 i = 1; i < orderedBytesArray.length; ++i) {
- // construct a single-bit mask from the numerical value of the next byte of the array
- bitMask = uint256(1 << uint8(orderedBytesArray[i]));
- // check strictly ascending array ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap)
- require(bitMask > bitmap, "BytesArrayBitmaps.orderedBytesArrayToBitmap: orderedBytesArray is not ordered");
- // add the entry to the bitmap
- bitmap = (bitmap | bitMask);
- }
- return bitmap;
- }
-
- /**
- * @notice Converts an ordered array of bytes into a bitmap. Optimized, Yul-heavy version of `orderedBytesArrayToBitmap`.
- * @param orderedBytesArray The array of bytes to convert/compress into a bitmap. Must be in strictly ascending order.
- * @return The resulting bitmap.
- * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap.
- * @dev This function will eventually revert in the event that the `orderedBytesArray` is not properly ordered (in ascending order).
- * @dev This function will also revert if the `orderedBytesArray` input contains any duplicate entries (i.e. duplicate bytes).
- */
- function orderedBytesArrayToBitmap_Yul(bytes calldata orderedBytesArray) internal pure returns (uint256) {
- // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s)
- require(orderedBytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
- "BytesArrayBitmaps.orderedBytesArrayToBitmap: orderedBytesArray is too long");
-
- // return empty bitmap early if length of array is 0
- if (orderedBytesArray.length == 0) {
- return uint256(0);
- }
-
- assembly {
- // get first entry in bitmap (single byte => single-bit mask)
- let bitmap :=
- shl(
- // extract single byte to get the correct value for the left shift
- shr(
- 248,
- calldataload(
- orderedBytesArray.offset
- )
- ),
- 1
- )
- // loop through other entries (byte by byte)
- for { let i := 1 } lt(i, orderedBytesArray.length) { i := add(i, 1) } {
- // first construct the single-bit mask by left-shifting a '1'
- let bitMask :=
- shl(
- // extract single byte to get the correct value for the left shift
- shr(
- 248,
- calldataload(
- add(
- orderedBytesArray.offset,
- i
- )
- )
- ),
- 1
- )
- // check strictly ascending ordering by comparing the mask to the bitmap so far (revert if mask isn't greater than bitmap)
- // TODO: revert with a good message instead of using `revert(0, 0)`
- // REFERENCE: require(bitMask > bitmap, "BytesArrayBitmaps.orderedBytesArrayToBitmap: orderedBytesArray is not ordered");
- if iszero(gt(bitMask, bitmap)) { revert(0, 0) }
- // update the bitmap by adding the single bit in the mask
- bitmap := or(bitmap, bitMask)
- }
- // after the loop is complete, store the bitmap at the value encoded at the free memory pointer, then return it
- mstore(mload(0x40), bitmap)
- return(mload(0x40), 32)
- }
- }
-
- /**
- * @notice Converts an array of bytes into a bitmap. Optimized, Yul-heavy version of `bytesArrayToBitmap`.
- * @param bytesArray The array of bytes to convert/compress into a bitmap.
- * @return The resulting bitmap.
- * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap.
- * @dev This function will eventually revert in the event that the `bytesArray` is not properly ordered (in ascending order).
- * @dev This function will also revert if the `bytesArray` input contains any duplicate entries (i.e. duplicate bytes).
- */
- function bytesArrayToBitmap_Yul(bytes calldata bytesArray) internal pure returns (uint256) {
- // sanity-check on input. a too-long input would fail later on due to having duplicate entry(s)
- require(bytesArray.length <= MAX_BYTE_ARRAY_LENGTH,
- "BytesArrayBitmaps.bytesArrayToBitmap: bytesArray is too long");
-
- // return empty bitmap early if length of array is 0
- if (bytesArray.length == 0) {
- return uint256(0);
- }
-
- assembly {
- // get first entry in bitmap (single byte => single-bit mask)
- let bitmap :=
- shl(
- // extract single byte to get the correct value for the left shift
- shr(
- 248,
- calldataload(
- bytesArray.offset
- )
- ),
- 1
- )
- // loop through other entries (byte by byte)
- for { let i := 1 } lt(i, bytesArray.length) { i := add(i, 1) } {
- // first construct the single-bit mask by left-shifting a '1'
- let bitMask :=
- shl(
- // extract single byte to get the correct value for the left shift
- shr(
- 248,
- calldataload(
- add(
- bytesArray.offset,
- i
- )
- )
- ),
- 1
- )
- // check against duplicates by comparing the bitmask and bitmap (revert if the bitmap already contains the entry, i.e. bitmap & bitMask != 0)
- // TODO: revert with a good message instead of using `revert(0, 0)`
- // REFERENCE: require(bitmap & bitMask == 0, "BytesArrayBitmaps.bytesArrayToBitmap: repeat entry in bytesArray");
- if gt(and(bitmap, bitMask), 0) { revert(0, 0) }
- // update the bitmap by adding the single bit in the mask
- bitmap := or(bitmap, bitMask)
- }
- // after the loop is complete, store the bitmap at the value encoded at the free memory pointer, then return it
- mstore(mload(0x40), bitmap)
- return(mload(0x40), 32)
- }
- }
-
- /**
- * @notice Utility function for checking if a bytes array is strictly ordered, in ascending order.
- * @param bytesArray the bytes array of interest
- * @return Returns 'true' if the array is ordered in strictly ascending order, and 'false' otherwise.
- * @dev This function returns 'true' for the edge case of the `bytesArray` having zero length.
- * It also returns 'false' early for arrays with length in excess of MAX_BYTE_ARRAY_LENGTH (i.e. so long that they cannot be strictly ordered)
- */
- function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) internal pure returns (bool) {
- // return 'false' early for too-long (i.e. unorderable) arrays
- if (bytesArray.length > MAX_BYTE_ARRAY_LENGTH) {
- return false;
- }
-
- // return 'true' early if length of array is 0
- if (bytesArray.length == 0) {
- return true;
- }
-
- // initialize an empty byte object, to be re-used inside the loop
- bytes1 singleByte;
-
- // perform the 0-th loop iteration with the ordering check *omitted* (otherwise it will break with an out-of-bounds error)
- // pull the 0th byte out of the array
- singleByte = bytesArray[0];
-
- // loop through each byte in the array to construct the bitmap
- for (uint256 i = 1; i < bytesArray.length; ++i) {
- // check if the entry is *less than or equal to* the previous entry. if it is, then the array isn't strictly ordered!
- if (uint256(uint8(bytesArray[i])) <= uint256(uint8(singleByte))) {
- return false;
- }
- // pull the next byte out of the array
- singleByte = bytesArray[i];
- }
- return true;
- }
-
- /**
- * @notice Converts a bitmap into an array of bytes.
- * @param bitmap The bitmap to decompress/convert to an array of bytes.
- * @return bytesArray The resulting bitmap array of bytes.
- * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap
- */
- function bitmapToBytesArray(uint256 bitmap) internal pure returns (bytes memory bytesArray) {
- // initialize an empty uint256 to be used as a bitmask inside the loop
- uint256 bitMask;
- // loop through each index in the bitmap to construct the array
- for (uint256 i = 0; i < 256; ++i) {
- // construct a single-bit mask for the i-th bit
- bitMask = uint256(1 << i);
- // check if the i-th bit is flipped in the bitmap
- if (bitmap & bitMask != 0) {
- // if the i-th bit is flipped, then add a byte encoding the value 'i' to the `bytesArray`
- bytesArray = bytes.concat(bytesArray, bytes1(uint8(i)));
- }
- }
- return bytesArray;
- }
-}
diff --git a/src/contracts/libraries/BytesLib.sol b/src/contracts/libraries/BytesLib.sol
index a168f037fa..961d239ff2 100644
--- a/src/contracts/libraries/BytesLib.sol
+++ b/src/contracts/libraries/BytesLib.sol
@@ -57,10 +57,14 @@ library BytesLib {
// length of the arrays.
end := add(mc, length)
- for { let cc := add(_postBytes, 0x20) } lt(mc, end) {
+ for {
+ let cc := add(_postBytes, 0x20)
+ } lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
- } { mstore(mc, mload(cc)) }
+ } {
+ mstore(mc, mload(cc))
+ }
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
@@ -168,7 +172,9 @@ library BytesLib {
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
- } { sstore(sc, mload(mc)) }
+ } {
+ sstore(sc, mload(mc))
+ }
mask := exp(0x100, sub(mc, end))
@@ -201,7 +207,9 @@ library BytesLib {
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
- } { sstore(sc, mload(mc)) }
+ } {
+ sstore(sc, mload(mc))
+ }
mask := exp(0x100, sub(mc, end))
@@ -247,7 +255,9 @@ library BytesLib {
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
- } { mstore(mc, mload(cc)) }
+ } {
+ mstore(mc, mload(cc))
+ }
mstore(tempBytes, _length)
@@ -386,9 +396,9 @@ library BytesLib {
let mc := add(_preBytes, 0x20)
let end := add(mc, length)
- for { let cc := add(_postBytes, 0x20) }
- // the next line is the loop condition:
- // while(uint256(mc < end) + cb == 2)
+ for {
+ let cc := add(_postBytes, 0x20)
+ } // while(uint256(mc < end) + cb == 2) // the next line is the loop condition:
eq(add(lt(mc, end), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
@@ -454,7 +464,9 @@ library BytesLib {
// the next line is the loop condition:
// while(uint256(mc < end) + cb == 2)
// solhint-disable-next-line no-empty-blocks
- for {} eq(add(lt(mc, end), cb), 2) {
+ for {
+
+ } eq(add(lt(mc, end), cb), 2) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
diff --git a/src/contracts/libraries/EIP1271SignatureUtils.sol b/src/contracts/libraries/EIP1271SignatureUtils.sol
new file mode 100644
index 0000000000..a4452f91e8
--- /dev/null
+++ b/src/contracts/libraries/EIP1271SignatureUtils.sol
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/interfaces/IERC1271.sol";
+import "@openzeppelin/contracts/utils/Address.sol";
+import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
+
+/**
+ * @title Library of utilities for making EIP1271-compliant signature checks.
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ */
+library EIP1271SignatureUtils {
+ // bytes4(keccak256("isValidSignature(bytes32,bytes)")
+ bytes4 internal constant EIP1271_MAGICVALUE = 0x1626ba7e;
+
+ /**
+ * @notice Checks @param signature is a valid signature of @param digestHash from @param signer.
+ * If the `signer` contains no code -- i.e. it is not (yet, at least) a contract address, then checks using standard ECDSA logic
+ * Otherwise, passes on the signature to the signer to verify the signature and checks that it returns the `EIP1271_MAGICVALUE`.
+ */
+ function checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signature) internal view {
+ /**
+ * check validity of signature:
+ * 1) if `signer` is an EOA, then `signature` must be a valid ECDSA signature from `signer`,
+ * indicating their intention for this action
+ * 2) if `signer` is a contract, then `signature` must will be checked according to EIP-1271
+ */
+ if (Address.isContract(signer)) {
+ require(
+ IERC1271(signer).isValidSignature(digestHash, signature) == EIP1271_MAGICVALUE,
+ "EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed"
+ );
+ } else {
+ require(
+ ECDSA.recover(digestHash, signature) == signer,
+ "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer"
+ );
+ }
+ }
+}
diff --git a/src/contracts/libraries/Endian.sol b/src/contracts/libraries/Endian.sol
index 42db62096c..ac996ce38a 100644
--- a/src/contracts/libraries/Endian.sol
+++ b/src/contracts/libraries/Endian.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.0;
library Endian {
/**
@@ -9,9 +9,7 @@ library Endian {
* @dev Note that the input is formatted as a 'bytes32' type (i.e. 256 bits), but it is immediately truncated to a uint64 (i.e. 64 bits)
* through a right-shift/shr operation.
*/
- function fromLittleEndianUint64(
- bytes32 lenum
- ) internal pure returns (uint64 n) {
+ function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n) {
// the number needs to be stored in little-endian encoding (ie in bytes 0-8)
n = uint64(uint256(lenum >> 192));
return
diff --git a/src/contracts/libraries/Merkle.sol b/src/contracts/libraries/Merkle.sol
index 0fe9ee2395..ddb0bd4c5b 100644
--- a/src/contracts/libraries/Merkle.sol
+++ b/src/contracts/libraries/Merkle.sol
@@ -1,7 +1,7 @@
-// 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.12;
+pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
@@ -21,9 +21,9 @@ library Merkle {
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
- * hash matches the root of the tree. The tree is built assuming `leaf` is
+ * hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
- *
+ *
* Note this is for a Merkle tree using the keccak/sha3 hash function
*/
function verifyInclusionKeccak(
@@ -38,18 +38,25 @@ library Merkle {
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
- * hash matches the root of the tree. The tree is built assuming `leaf` is
+ * hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
- *
+ *
* _Available since v4.4._
- *
+ *
* Note this is for a Merkle tree using the keccak/sha3 hash function
*/
- function processInclusionProofKeccak(bytes memory proof, bytes32 leaf, uint256 index) internal pure returns (bytes32) {
- require(proof.length != 0 && proof.length % 32 == 0, "Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32");
+ function processInclusionProofKeccak(
+ bytes memory proof,
+ bytes32 leaf,
+ uint256 index
+ ) internal pure returns (bytes32) {
+ require(
+ proof.length != 0 && proof.length % 32 == 0,
+ "Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32"
+ );
bytes32 computedHash = leaf;
- for (uint256 i = 32; i <= proof.length; i+=32) {
- if(index % 2 == 0) {
+ for (uint256 i = 32; i <= proof.length; i += 32) {
+ if (index % 2 == 0) {
// if ith bit of index is 0, then computedHash is a left sibling
assembly {
mstore(0x00, computedHash)
@@ -64,7 +71,7 @@ library Merkle {
mstore(0x20, computedHash)
computedHash := keccak256(0x00, 0x40)
index := div(index, 2)
- }
+ }
}
}
return computedHash;
@@ -73,9 +80,9 @@ library Merkle {
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
- * hash matches the root of the tree. The tree is built assuming `leaf` is
+ * hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
- *
+ *
* Note this is for a Merkle tree using the sha256 hash function
*/
function verifyInclusionSha256(
@@ -90,23 +97,32 @@ library Merkle {
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
- * hash matches the root of the tree. The tree is built assuming `leaf` is
+ * hash matches the root of the tree. The tree is built assuming `leaf` is
* the 0 indexed `index`'th leaf from the bottom left of the tree.
*
* _Available since v4.4._
- *
+ *
* Note this is for a Merkle tree using the sha256 hash function
*/
- function processInclusionProofSha256(bytes memory proof, bytes32 leaf, uint256 index) internal view returns (bytes32) {
- require(proof.length != 0 && proof.length % 32 == 0, "Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32");
+ function processInclusionProofSha256(
+ bytes memory proof,
+ bytes32 leaf,
+ uint256 index
+ ) internal view returns (bytes32) {
+ require(
+ proof.length != 0 && proof.length % 32 == 0,
+ "Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32"
+ );
bytes32[1] memory computedHash = [leaf];
- for (uint256 i = 32; i <= proof.length; i+=32) {
- if(index % 2 == 0) {
+ for (uint256 i = 32; i <= proof.length; i += 32) {
+ if (index % 2 == 0) {
// if ith bit of index is 0, then computedHash is a left sibling
assembly {
mstore(0x00, mload(computedHash))
mstore(0x20, mload(add(proof, i)))
- if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}
+ if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
+ revert(0, 0)
+ }
index := div(index, 2)
}
} else {
@@ -114,9 +130,11 @@ library Merkle {
assembly {
mstore(0x00, mload(add(proof, i)))
mstore(0x20, mload(computedHash))
- if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {revert(0, 0)}
+ if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
+ revert(0, 0)
+ }
index := div(index, 2)
- }
+ }
}
}
return computedHash[0];
@@ -127,25 +145,23 @@ library Merkle {
@param leaves the leaves of the merkle tree
@return The computed Merkle root of the tree.
@dev A pre-condition to this function is that leaves.length is a power of two. If not, the function will merkleize the inputs incorrectly.
- */
- function merkleizeSha256(
- bytes32[] memory leaves
- ) internal pure returns (bytes32) {
+ */
+ function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32) {
//there are half as many nodes in the layer above the leaves
uint256 numNodesInLayer = leaves.length / 2;
//create a layer to store the internal nodes
bytes32[] memory layer = new bytes32[](numNodesInLayer);
//fill the layer with the pairwise hashes of the leaves
- for (uint i = 0; i < numNodesInLayer; i++) {
- layer[i] = sha256(abi.encodePacked(leaves[2*i], leaves[2*i+1]));
+ for (uint256 i = 0; i < numNodesInLayer; i++) {
+ layer[i] = sha256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1]));
}
//the next layer above has half as many nodes
numNodesInLayer /= 2;
//while we haven't computed the root
while (numNodesInLayer != 0) {
//overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
- for (uint i = 0; i < numNodesInLayer; i++) {
- layer[i] = sha256(abi.encodePacked(layer[2*i], layer[2*i+1]));
+ for (uint256 i = 0; i < numNodesInLayer; i++) {
+ layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
}
//the next layer above has half as many nodes
numNodesInLayer /= 2;
@@ -153,4 +169,4 @@ library Merkle {
//the first node in the layer is the root
return layer[0];
}
-}
\ No newline at end of file
+}
diff --git a/src/contracts/libraries/MiddlewareUtils.sol b/src/contracts/libraries/MiddlewareUtils.sol
deleted file mode 100644
index de323b4252..0000000000
--- a/src/contracts/libraries/MiddlewareUtils.sol
+++ /dev/null
@@ -1,22 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-
-pragma solidity =0.8.12;
-
-/**
- * @title Library of functions shared across DataLayr.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-library MiddlewareUtils {
- /// @notice Finds the `signatoryRecordHash`, used for fraudproofs.
- function computeSignatoryRecordHash(
- uint32 globalDataStoreId,
- bytes32[] memory nonSignerPubkeyHashes,
- uint256 signedStakeFirstQuorum,
- uint256 signedStakeSecondQuorum
- ) internal pure returns (bytes32) {
- return keccak256(
- abi.encodePacked(globalDataStoreId, nonSignerPubkeyHashes, signedStakeFirstQuorum, signedStakeSecondQuorum)
- );
- }
-}
diff --git a/src/contracts/libraries/StructuredLinkedList.sol b/src/contracts/libraries/StructuredLinkedList.sol
index 5ef81b5e71..29410656fc 100644
--- a/src/contracts/libraries/StructuredLinkedList.sol
+++ b/src/contracts/libraries/StructuredLinkedList.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
/**
* @title StructuredLinkedList
@@ -255,4 +255,4 @@ library StructuredLinkedList {
self.list[_link][!_direction] = _node;
self.list[_node][_direction] = _link;
}
-}
\ No newline at end of file
+}
diff --git a/src/contracts/middleware/BLSPublicKeyCompendium.sol b/src/contracts/middleware/BLSPublicKeyCompendium.sol
deleted file mode 100644
index 7a7d5c4534..0000000000
--- a/src/contracts/middleware/BLSPublicKeyCompendium.sol
+++ /dev/null
@@ -1,81 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../interfaces/IBLSPublicKeyCompendium.sol";
-import "../libraries/BN254.sol";
-
-/**
- * @title A shared contract for EigenLayer operators to register their BLS public keys.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-contract BLSPublicKeyCompendium is IBLSPublicKeyCompendium {
- //Hash of the zero public key: BN254.hashG1Point(G1Point(0,0))
- bytes32 internal constant ZERO_PK_HASH = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5";
-
- /// @notice mapping from operator address to pubkey hash
- mapping(address => bytes32) public operatorToPubkeyHash;
- /// @notice mapping from pubkey hash to operator address
- mapping(bytes32 => address) public pubkeyHashToOperator;
-
- // EVENTS
- /// @notice Emitted when `operator` registers with the public key `pk`.
- event NewPubkeyRegistration(address operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2);
-
- /**
- * @notice Called by an operator to register themselves as the owner of a BLS public key and reveal their G1 and G2 public key.
- * @param s is the field element of the operator's Schnorr signature
- * @param rPoint is the group element of the operator's Schnorr signature
- * @param pubkeyG1 is the the G1 pubkey of the operator
- * @param pubkeyG2 is the G2 with the same private key as the pubkeyG1
- */
- function registerBLSPublicKey(uint256 s, BN254.G1Point memory rPoint, BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) external {
- // calculate -g1
- BN254.G1Point memory negGeneratorG1 = BN254.negate(BN254.G1Point({X: 1, Y: 2}));
- // verify a Schnorr signature (s, R) of pubkeyG1
- // calculate s*-g1 + (R + H(msg.sender, P, R)*P) = 0
- // which is the Schnorr signature verification equation
- BN254.G1Point memory shouldBeZero =
- BN254.plus(
- BN254.scalar_mul(negGeneratorG1, s),
- BN254.plus(
- rPoint,
- BN254.scalar_mul(
- pubkeyG1,
- uint256(keccak256(abi.encodePacked(msg.sender, pubkeyG1.X, pubkeyG1.Y, rPoint.X, rPoint.Y))) % BN254.FR_MODULUS
- )
- )
- );
-
- require(shouldBeZero.X == 0 && shouldBeZero.Y == 0, "BLSPublicKeyCompendium.registerBLSPublicKey: incorrect schnorr singature");
-
- // verify that the G2 pubkey has the same discrete log as the G1 pubkey
- // e(P, [1]_2) = e([-1]_1, P')
- require(BN254.pairing(
- pubkeyG1,
- BN254.generatorG2(),
- negGeneratorG1,
- pubkeyG2
- ), "BLSPublicKeyCompendium.registerBLSPublicKey: G1 and G2 private key do not match");
-
- // getting pubkey hash
- bytes32 pubkeyHash = BN254.hashG1Point(pubkeyG1);
-
- require(pubkeyHash != ZERO_PK_HASH, "BLSPublicKeyCompendium.registerBLSPublicKey: operator attempting to register the zero public key");
-
- require(
- operatorToPubkeyHash[msg.sender] == bytes32(0),
- "BLSPublicKeyCompendium.registerBLSPublicKey: operator already registered pubkey"
- );
- require(
- pubkeyHashToOperator[pubkeyHash] == address(0),
- "BLSPublicKeyCompendium.registerBLSPublicKey: public key already registered"
- );
-
- // store updates
- operatorToPubkeyHash[msg.sender] = pubkeyHash;
- pubkeyHashToOperator[pubkeyHash] = msg.sender;
-
- emit NewPubkeyRegistration(msg.sender, pubkeyG1, pubkeyG2);
- }
-}
diff --git a/src/contracts/middleware/BLSRegistry.sol b/src/contracts/middleware/BLSRegistry.sol
deleted file mode 100644
index e0bb5a7609..0000000000
--- a/src/contracts/middleware/BLSRegistry.sol
+++ /dev/null
@@ -1,390 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./RegistryBase.sol";
-import "../interfaces/IBLSPublicKeyCompendium.sol";
-import "../interfaces/IBLSRegistry.sol";
-import "../libraries/BN254.sol";
-
-/**
- * @title A Registry-type contract using aggregate BLS signatures.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract is used for
- * - registering new operators
- * - committing to and finalizing de-registration as an operator
- * - updating the stakes of the operator
- */
-contract BLSRegistry is RegistryBase, IBLSRegistry {
-
- // Hash of the zero public key
- bytes32 internal constant ZERO_PK_HASH = hex"012893657d8eb2efad4de0a91bcd0e39ad9837745dec3ea923737ea803fc8e3d";
-
- /// @notice contract used for looking up operators' BLS public keys
- IBLSPublicKeyCompendium public immutable pubkeyCompendium;
-
- /**
- * @notice list of keccak256(apk_x, apk_y) of operators, and the block numbers at which the aggregate
- * pubkeys were updated. This occurs whenever a new operator registers or deregisters.
- */
- ApkUpdate[] internal _apkUpdates;
-
- /**
- * @dev Initialized value of APK is the point at infinity: (0, 0)
- * @notice used for storing current aggregate public key
- */
- BN254.G1Point public apk;
-
- /// @notice the address that can whitelist people
- address public operatorWhitelister;
- /// @notice toggle of whether the operator whitelist is on or off
- bool public operatorWhitelistEnabled;
- /// @notice operator => are they whitelisted (can they register with the middleware)
- mapping(address => bool) public whitelisted;
-
- // EVENTS
- /**
- * @notice Emitted upon the registration of a new operator for the middleware
- * @param operator Address of the new operator
- * @param pkHash The keccak256 hash of the operator's public key
- * @param pk The operator's public key itself
- * @param apkHashIndex The index of the latest (i.e. the new) APK update
- * @param apkHash The keccak256 hash of the new Aggregate Public Key
- */
- event Registration(
- address indexed operator,
- bytes32 pkHash,
- BN254.G1Point pk,
- uint32 apkHashIndex,
- bytes32 apkHash,
- string socket
- );
-
- /// @notice Emitted when the `operatorWhitelister` role is transferred.
- event OperatorWhitelisterTransferred(address previousAddress, address newAddress);
-
- /// @notice Modifier that restricts a function to only be callable by the `whitelister` role.
- modifier onlyOperatorWhitelister{
- require(operatorWhitelister == msg.sender, "BLSRegistry.onlyOperatorWhitelister: not operatorWhitelister");
- _;
- }
-
- constructor(
- IStrategyManager _strategyManager,
- IServiceManager _serviceManager,
- uint8 _NUMBER_OF_QUORUMS,
- IBLSPublicKeyCompendium _pubkeyCompendium
- )
- RegistryBase(
- _strategyManager,
- _serviceManager,
- _NUMBER_OF_QUORUMS
- )
- {
- //set compendium
- pubkeyCompendium = _pubkeyCompendium;
- }
-
- /// @notice Initialize the APK, the payment split between quorums, and the quorum strategies + multipliers.
- function initialize(
- address _operatorWhitelister,
- bool _operatorWhitelistEnabled,
- uint256[] memory _quorumBips,
- StrategyAndWeightingMultiplier[] memory _firstQuorumStrategiesConsideredAndMultipliers,
- StrategyAndWeightingMultiplier[] memory _secondQuorumStrategiesConsideredAndMultipliers
- ) public virtual initializer {
- _setOperatorWhitelister(_operatorWhitelister);
- operatorWhitelistEnabled = _operatorWhitelistEnabled;
- // process an apk update to get index and totalStake arrays to the same length
- _processApkUpdate(BN254.G1Point(0, 0));
- RegistryBase._initialize(
- _quorumBips,
- _firstQuorumStrategiesConsideredAndMultipliers,
- _secondQuorumStrategiesConsideredAndMultipliers
- );
- }
-
- /**
- * @notice Called by the service manager owner to transfer the whitelister role to another address
- */
- function setOperatorWhitelister(address _operatorWhitelister) external onlyServiceManagerOwner {
- _setOperatorWhitelister(_operatorWhitelister);
- }
-
- /**
- * @notice Callable only by the service manager owner, this function toggles the whitelist on or off
- * @param _operatorWhitelistEnabled true if turning whitelist on, false otherwise
- */
- function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external onlyServiceManagerOwner {
- operatorWhitelistEnabled = _operatorWhitelistEnabled;
- }
-
- /**
- * @notice Called by the whitelister, adds a list of operators to the whitelist
- * @param operators the operators to add to the whitelist
- */
- function addToOperatorWhitelist(address[] calldata operators) external onlyOperatorWhitelister {
- for (uint i = 0; i < operators.length; i++) {
- whitelisted[operators[i]] = true;
- }
- }
-
- /**
- * @notice Called by the whitelister, removes a list of operators to the whitelist
- * @param operators the operators to remove from the whitelist
- */
- function removeFromWhitelist(address[] calldata operators) external onlyOperatorWhitelister {
- for (uint i = 0; i < operators.length; i++) {
- whitelisted[operators[i]] = false;
- }
- }
-
- /**
- * @notice called for registering as an operator
- * @param operatorType specifies whether the operator want to register as staker for one or both quorums
- * @param pk is the operator's G1 public key
- * @param socket is the socket address of the operator
- */
- function registerOperator(uint8 operatorType, BN254.G1Point memory pk, string calldata socket) external virtual {
- _registerOperator(msg.sender, operatorType, pk, socket);
- }
-
- /**
- * @param operator is the node who is registering to be a operator
- * @param operatorType specifies whether the operator want to register as staker for one or both quorums
- * @param pk is the operator's G1 public key
- * @param socket is the socket address of the operator
- */
- function _registerOperator(address operator, uint8 operatorType, BN254.G1Point memory pk, string calldata socket)
- internal
- {
- if(operatorWhitelistEnabled) {
- require(whitelisted[operator], "BLSRegistry._registerOperator: not whitelisted");
- }
-
- // validate the registration of `operator` and find their `OperatorStake`
- OperatorStake memory _operatorStake = _registrationStakeEvaluation(operator, operatorType);
-
- // getting pubkey hash
- bytes32 pubkeyHash = BN254.hashG1Point(pk);
-
- require(pubkeyHash != ZERO_PK_HASH, "BLSRegistry._registerOperator: Cannot register with 0x0 public key");
-
-
- require(
- pubkeyCompendium.pubkeyHashToOperator(pubkeyHash) == operator,
- "BLSRegistry._registerOperator: operator does not own pubkey"
- );
-
- // the new aggregate public key is the current one added to registering operator's public key
- BN254.G1Point memory newApk = BN254.plus(apk, pk);
-
- // record the APK update and get the hash of the new APK
- bytes32 newApkHash = _processApkUpdate(newApk);
-
- // add the operator to the list of registrants and do accounting
- _addRegistrant(operator, pubkeyHash, _operatorStake);
-
- emit Registration(operator, pubkeyHash, pk, uint32(_apkUpdates.length - 1), newApkHash, socket);
- }
-
- /**
- * @notice Used by an operator to de-register itself from providing service to the middleware.
- * @param pkToRemove is the sender's pubkey in affine coordinates
- * @param index is the sender's location in the dynamic array `operatorList`
- */
- function deregisterOperator(BN254.G1Point memory pkToRemove, uint32 index) external virtual returns (bool) {
- _deregisterOperator(msg.sender, pkToRemove, index);
- return true;
- }
-
- /**
- * @notice Used to process de-registering an operator from providing service to the middleware.
- * @param operator The operator to be deregistered
- * @param pkToRemove is the sender's pubkey
- * @param index is the sender's location in the dynamic array `operatorList`
- */
- function _deregisterOperator(address operator, BN254.G1Point memory pkToRemove, uint32 index) internal {
- // verify that the `operator` is an active operator and that they've provided the correct `index`
- _deregistrationCheck(operator, index);
-
-
- /// @dev Fetch operator's stored pubkeyHash
- bytes32 pubkeyHash = registry[operator].pubkeyHash;
- /// @dev Verify that the stored pubkeyHash matches the 'pubkeyToRemoveAff' input
- require(
- pubkeyHash == BN254.hashG1Point(pkToRemove),
- "BLSRegistry._deregisterOperator: pubkey input does not match stored pubkeyHash"
- );
-
- // Perform necessary updates for removing operator, including updating operator list and index histories
- _removeOperator(operator, pubkeyHash, index);
-
- // the new apk is the current one minus the sender's pubkey (apk = apk + (-pk))
- BN254.G1Point memory newApk = BN254.plus(apk, BN254.negate(pkToRemove));
- // update the aggregate public key of all registered operators and record this update in history
- _processApkUpdate(newApk);
- }
-
- /**
- * @notice Used for updating information on deposits of nodes.
- * @param operators are the nodes whose deposit information is getting updated
- * @param prevElements are the elements before this middleware in the operator's linked list within the slasher
- */
- function updateStakes(address[] calldata operators, uint256[] calldata prevElements) external {
- // copy total stake to memory
- OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1];
-
- // placeholders reused inside of loop
- OperatorStake memory currentStakes;
- bytes32 pubkeyHash;
- uint256 operatorsLength = operators.length;
- // make sure lengths are consistent
- require(operatorsLength == prevElements.length, "BLSRegistry.updateStakes: prevElement is not the same length as operators");
- // iterating over all the tuples that are to be updated
- for (uint256 i = 0; i < operatorsLength;) {
- // get operator's pubkeyHash
- pubkeyHash = registry[operators[i]].pubkeyHash;
- // fetch operator's existing stakes
- currentStakes = pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistory[pubkeyHash].length - 1];
- // decrease _totalStake by operator's existing stakes
- _totalStake.firstQuorumStake -= currentStakes.firstQuorumStake;
- _totalStake.secondQuorumStake -= currentStakes.secondQuorumStake;
-
- // update the stake for the i-th operator
- currentStakes = _updateOperatorStake(operators[i], pubkeyHash, currentStakes, prevElements[i]);
-
- // increase _totalStake by operator's updated stakes
- _totalStake.firstQuorumStake += currentStakes.firstQuorumStake;
- _totalStake.secondQuorumStake += currentStakes.secondQuorumStake;
-
- unchecked {
- ++i;
- }
- }
-
- // update storage of total stake
- _recordTotalStakeUpdate(_totalStake);
- }
-
- //TODO: The subgraph doesnt like uint256[4][] argument here. Figure this out laters
- // // TODO: de-dupe code copied from `updateStakes`, if reasonably possible
- // /**
- // * @notice Used for removing operators that no longer meet the minimum requirements
- // * @param operators are the nodes who will potentially be booted
- // */
- // function bootOperators(
- // address[] calldata operators,
- // uint256[4][] memory pubkeysToRemoveAff,
- // uint32[] memory indices
- // )
- // external
- // {
- // // copy total stake to memory
- // OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1];
-
- // // placeholders reused inside of loop
- // OperatorStake memory currentStakes;
- // bytes32 pubkeyHash;
- // uint256 operatorsLength = operators.length;
- // // iterating over all the tuples that are to be updated
- // for (uint256 i = 0; i < operatorsLength;) {
- // // get operator's pubkeyHash
- // pubkeyHash = registry[operators[i]].pubkeyHash;
- // // fetch operator's existing stakes
- // currentStakes = pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistory[pubkeyHash].length - 1];
- // // decrease _totalStake by operator's existing stakes
- // _totalStake.firstQuorumStake -= currentStakes.firstQuorumStake;
- // _totalStake.secondQuorumStake -= currentStakes.secondQuorumStake;
-
- // // update the stake for the i-th operator
- // currentStakes = _updateOperatorStake(operators[i], pubkeyHash, currentStakes);
-
- // // remove the operator from the list of operators if they do *not* meet the minimum requirements
- // if (
- // (currentStakes.firstQuorumStake < minimumStakeFirstQuorum)
- // && (currentStakes.secondQuorumStake < minimumStakeSecondQuorum)
- // ) {
- // // TODO: optimize better if possible? right now this pushes an APK update for each operator removed.
- // _deregisterOperator(operators[i], pubkeysToRemoveAff[i], indices[i]);
- // }
- // // in the case that the operator *does indeed* meet the minimum requirements
- // else {
- // // increase _totalStake by operator's updated stakes
- // _totalStake.firstQuorumStake += currentStakes.firstQuorumStake;
- // _totalStake.secondQuorumStake += currentStakes.secondQuorumStake;
- // }
-
- // unchecked {
- // ++i;
- // }
- // }
-
- // // update storage of total stake
- // _recordTotalStakeUpdate(_totalStake);
- // }
-
- /**
- * @notice Updates the stored APK to `newApk`, calculates its hash, and pushes new entries to the `_apkUpdates` array
- * @param newApk The updated APK. This will be the `apk` after this function runs!
- */
- function _processApkUpdate(BN254.G1Point memory newApk) internal returns (bytes32) {
- // update stored aggregate public key
- // slither-disable-next-line costly-loop
- apk = newApk;
-
- // find the hash of aggregate pubkey
- bytes32 newApkHash = BN254.hashG1Point(newApk);
-
- // store the apk hash and the current block number in which the aggregated pubkey is being updated
- _apkUpdates.push(ApkUpdate({
- apkHash: newApkHash,
- blockNumber: uint32(block.number)
- }));
-
- return newApkHash;
- }
-
- function _setOperatorWhitelister(address _operatorWhitelister) internal {
- require(_operatorWhitelister != address(0), "BLSRegistry.initialize: cannot set operatorWhitelister to zero address");
- emit OperatorWhitelisterTransferred(operatorWhitelister, _operatorWhitelister);
- operatorWhitelister = _operatorWhitelister;
- }
-
- /**
- * @notice get hash of a historical aggregated public key corresponding to a given index;
- * called by checkSignatures in BLSSignatureChecker.sol.
- */
- function getCorrectApkHash(uint256 index, uint32 blockNumber) external view returns (bytes32) {
- // check that the `index`-th APK update occurred at or before `blockNumber`
- require(blockNumber >= _apkUpdates[index].blockNumber, "BLSRegistry.getCorrectApkHash: index too recent");
-
- // if not last update
- if (index != _apkUpdates.length - 1) {
- // check that there was not *another APK update* that occurred at or before `blockNumber`
- require(blockNumber < _apkUpdates[index + 1].blockNumber, "BLSRegistry.getCorrectApkHash: Not latest valid apk update");
- }
-
- return _apkUpdates[index].apkHash;
- }
-
- /// @notice returns the total number of APK updates that have ever occurred (including one for initializing the pubkey as the generator)
- function getApkUpdatesLength() external view returns (uint256) {
- return _apkUpdates.length;
- }
-
- /// @notice returns the `ApkUpdate` struct at `index` in the list of APK updates
- function apkUpdates(uint256 index) external view returns (ApkUpdate memory) {
- return _apkUpdates[index];
- }
-
- /// @notice returns the APK hash that resulted from the `index`th APK update
- function apkHashes(uint256 index) external view returns (bytes32) {
- return _apkUpdates[index].apkHash;
- }
-
- /// @notice returns the block number at which the `index`th APK update occurred
- function apkUpdateBlockNumbers(uint256 index) external view returns (uint32) {
- return _apkUpdates[index].blockNumber;
- }
-}
diff --git a/src/contracts/middleware/BLSSignatureChecker.sol b/src/contracts/middleware/BLSSignatureChecker.sol
deleted file mode 100644
index d9654c01a3..0000000000
--- a/src/contracts/middleware/BLSSignatureChecker.sol
+++ /dev/null
@@ -1,508 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../interfaces/IBLSRegistry.sol";
-import "../libraries/BytesLib.sol";
-import "../libraries/MiddlewareUtils.sol";
-import "../libraries/BN254.sol";
-
-/**
- * @title Used for checking BLS aggregate signatures from the operators of a `BLSRegistry`.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This is the contract for checking the validity of aggregate operator signatures.
- */
-abstract contract BLSSignatureChecker {
- // DATA STRUCTURES
- /**
- * @notice this data structure is used for recording the details on the total stake of the registered
- * operators and those operators who are part of the quorum for a particular taskNumber
- */
-
- struct SignatoryTotals {
- // total stake of the operators who are in the first quorum
- uint256 signedStakeFirstQuorum;
- // total stake of the operators who are in the second quorum
- uint256 signedStakeSecondQuorum;
- // total amount staked by all operators (irrespective of whether they are in the quorum or not)
- uint256 totalStakeFirstQuorum;
- // total amount staked by all operators (irrespective of whether they are in the quorum or not)
- uint256 totalStakeSecondQuorum;
- }
-
- // EVENTS
- /**
- * @notice used for recording the event that signature has been checked in checkSignatures function.
- */
- event SignatoryRecord(
- bytes32 msgHash,
- uint32 taskNumber,
- uint256 signedStakeFirstQuorum,
- uint256 signedStakeSecondQuorum,
- // uint256 totalStakeFirstQuorum,
- // uint256 totalStakeSecondQuorum,
- bytes32[] pubkeyHashes
- );
-
- IQuorumRegistry public immutable registry;
-
- constructor(IQuorumRegistry _registry) {
- registry = _registry;
- }
-
- // CONSTANTS -- commented out lines are due to inline assembly supporting *only* 'direct number constants' (for now, at least)
- uint256 internal constant BYTE_LENGTH_totalStakeIndex = 6;
- uint256 internal constant BYTE_LENGTH_referenceBlockNumber = 4;
- uint256 internal constant BYTE_LENGTH_taskNumberToConfirm = 4;
- uint256 internal constant BYTE_LENGTH_numberNonSigners = 4;
- // specifying a G2 public key requires 4 32-byte slots worth of data
- uint256 internal constant BYTE_LENGTH_G1_POINT = 64;
- uint256 internal constant BYTE_LENGTH_G2_POINT = 128;
- uint256 internal constant BYTE_LENGTH_stakeIndex = 4;
- // uint256 internal constant BYTE_LENGTH_NON_SIGNER_INFO = BYTE_LENGTH_G1_POINT + BYTE_LENGTH_stakeIndex;
- uint256 internal constant BYTE_LENGTH_NON_SIGNER_INFO = 68;
- uint256 internal constant BYTE_LENGTH_apkIndex = 4;
-
- // uint256 internal constant BIT_SHIFT_totalStakeIndex = 256 - (BYTE_LENGTH_totalStakeIndex * 8);
- uint256 internal constant BIT_SHIFT_totalStakeIndex = 208;
- // uint256 internal constant BIT_SHIFT_referenceBlockNumber = 256 - (BYTE_LENGTH_referenceBlockNumber * 8);
- uint256 internal constant BIT_SHIFT_referenceBlockNumber = 224;
- // uint256 internal constant BIT_SHIFT_taskNumberToConfirm = 256 - (BYTE_LENGTH_taskNumberToConfirm * 8);
- uint256 internal constant BIT_SHIFT_taskNumberToConfirm = 224;
- // uint256 internal constant BIT_SHIFT_numberNonSigners = 256 - (BYTE_LENGTH_numberNonSigners * 8);
- uint256 internal constant BIT_SHIFT_numberNonSigners = 224;
- // uint256 internal constant BIT_SHIFT_stakeIndex = 256 - (BYTE_LENGTH_stakeIndex * 8);
- uint256 internal constant BIT_SHIFT_stakeIndex = 224;
- // uint256 internal constant BIT_SHIFT_apkIndex = 256 - (BYTE_LENGTH_apkIndex * 8);
- uint256 internal constant BIT_SHIFT_apkIndex = 224;
-
- uint256 internal constant CALLDATA_OFFSET_totalStakeIndex = 32;
- // uint256 internal constant CALLDATA_OFFSET_referenceBlockNumber = CALLDATA_OFFSET_totalStakeIndex + BYTE_LENGTH_totalStakeIndex;
- uint256 internal constant CALLDATA_OFFSET_referenceBlockNumber = 38;
- // uint256 internal constant CALLDATA_OFFSET_taskNumberToConfirm = CALLDATA_OFFSET_referenceBlockNumber + BYTE_LENGTH_referenceBlockNumber;
- uint256 internal constant CALLDATA_OFFSET_taskNumberToConfirm = 42;
- // uint256 internal constant CALLDATA_OFFSET_numberNonSigners = CALLDATA_OFFSET_taskNumberToConfirm + BYTE_LENGTH_taskNumberToConfirm;
- uint256 internal constant CALLDATA_OFFSET_numberNonSigners = 46;
- // uint256 internal constant CALLDATA_OFFSET_NonsignerPubkeys = CALLDATA_OFFSET_numberNonSigners + BYTE_LENGTH_numberNonSigners;
- uint256 internal constant CALLDATA_OFFSET_NonsignerPubkeys = 50;
-
- /**
- * @notice This function is called by disperser when it has aggregated all the signatures of the operators
- * that are part of the quorum for a particular taskNumber and is asserting them into on-chain. The function
- * checks that the claim for aggregated signatures are valid.
- *
- * The thesis of this procedure entails:
- * - computing the aggregated pubkey of all the operators that are not part of the quorum for
- * this specific taskNumber (represented by aggNonSignerPubkey)
- * - getting the aggregated pubkey of all registered nodes at the time of pre-commit by the
- * disperser (represented by pk),
- * - do subtraction of aggNonSignerPubkey from pk over Jacobian coordinate system to get aggregated pubkey
- * of all operators that are part of quorum.
- * - use this aggregated pubkey to verify the aggregated signature under BLS scheme.
- */
- /**
- * @dev This calldata is of the format:
- * <
- * bytes32 msgHash, the taskHash for which disperser is calling checkSignatures
- * uint48 index of the totalStake corresponding to the dataStoreId in the 'totalStakeHistory' array of the BLSRegistry
- * uint32 blockNumber, the blockNumber at which the task was initated
- * uint32 taskNumberToConfirm
- * uint32 numberOfNonSigners,
- * {uint256[2] pubkeyG1, uint32 stakeIndex}[numberOfNonSigners] the G1 public key and the index to query of `pubkeyHashToStakeHistory` for each nonsigner,
- * uint32 apkIndex, the index in the `apkUpdates` array at which we want to load the aggregate public key
- * uint256[2] apkG1 (G1 aggregate public key, including nonSigners),
- * uint256[4] apkG2 (G2 aggregate public key, not including nonSigners),
- * uint256[2] sigma, the aggregate signature itself
- * >
- *
- * @dev Before signature verification, the function verifies operator stake information. This includes ensuring that the provided `referenceBlockNumber`
- * is correct, i.e., ensure that the stake returned from the specified block number is recent enough and that the stake is either the most recent update
- * for the total stake (or the operator) or latest before the referenceBlockNumber.
- * The next step involves computing the aggregated pub key of all the operators that are not part of the quorum for this specific taskNumber.
- * We use a loop to iterate through the `nonSignerPK` array, loading each individual public key from calldata. Before the loop, we isolate the first public key
- * calldataload - this implementation saves us one ecAdd operation, which would be performed in the i=0 iteration otherwise.
- * Within the loop, each non-signer public key is loaded from the calldata into memory. The most recent staking-related information is retrieved and is subtracted
- * from the total stake of validators in the quorum. Then the aggregate public key and the aggregate non-signer public key is subtracted from it.
- * Finally the siganture is verified by computing the elliptic curve pairing.
- */
- function checkSignatures(bytes calldata data)
- public
- returns (
- uint32 taskNumberToConfirm,
- uint32 referenceBlockNumber,
- bytes32 msgHash,
- SignatoryTotals memory signedTotals,
- bytes32 compressedSignatoryRecord
- )
- {
- // temporary variable used to hold various numbers
- uint256 placeholder;
-
- uint256 pointer;
-
- assembly {
- pointer := data.offset
- /**
- * Get the 32 bytes immediately after the function signature and length + offset encoding of 'bytes
- * calldata' input type, which represents the msgHash for which the disperser is calling `checkSignatures`
- */
- msgHash := calldataload(pointer)
-
- // Get the 6 bytes immediately after the above, which represent the index of the totalStake in the 'totalStakeHistory' array
- placeholder := shr(BIT_SHIFT_totalStakeIndex, calldataload(add(pointer, CALLDATA_OFFSET_totalStakeIndex)))
- }
-
- // fetch the 4 byte referenceBlockNumber, the block number from which stakes are going to be read from
- assembly {
- referenceBlockNumber :=
- shr(BIT_SHIFT_referenceBlockNumber, calldataload(add(pointer, CALLDATA_OFFSET_referenceBlockNumber)))
- }
-
- // get information on total stakes
- IQuorumRegistry.OperatorStake memory localStakeObject = registry.getTotalStakeFromIndex(placeholder);
-
- // check that the returned OperatorStake object is the most recent for the referenceBlockNumber
- _validateOperatorStake(localStakeObject, referenceBlockNumber);
-
- // copy total stakes amounts to `signedTotals` -- the 'signedStake' amounts are decreased later, to reflect non-signers
- signedTotals.totalStakeFirstQuorum = localStakeObject.firstQuorumStake;
- signedTotals.signedStakeFirstQuorum = localStakeObject.firstQuorumStake;
- signedTotals.totalStakeSecondQuorum = localStakeObject.secondQuorumStake;
- signedTotals.signedStakeSecondQuorum = localStakeObject.secondQuorumStake;
-
- assembly {
- //fetch the task number to avoid replay signing on same taskhash for different datastore
- taskNumberToConfirm :=
- shr(BIT_SHIFT_taskNumberToConfirm, calldataload(add(pointer, CALLDATA_OFFSET_taskNumberToConfirm)))
- // get the 4 bytes immediately after the above, which represent the
- // number of operators that aren't present in the quorum
- // slither-disable-next-line write-after-write
- placeholder := shr(BIT_SHIFT_numberNonSigners, calldataload(add(pointer, CALLDATA_OFFSET_numberNonSigners)))
- }
-
- // we have read (32 + 6 + 4 + 4 + 4) = 50 bytes of calldata so far
- pointer += CALLDATA_OFFSET_NonsignerPubkeys;
-
- // to be used for holding the pub key hashes of the operators that aren't part of the quorum
- bytes32[] memory pubkeyHashes = new bytes32[](placeholder);
- // intialize some memory eventually to be the input for call to ecPairing precompile contract
- uint256[12] memory input;
- // used for verifying that precompile calls are successful
- bool success;
-
- /**
- * @dev The next step involves computing the aggregated pub key of all the operators
- * that are not part of the quorum for this specific taskNumber.
- */
-
- /**
- * @dev loading pubkey for the first operator that is not part of the quorum as listed in the calldata;
- * Note that this need not be a special case and *could* be subsumed in the for loop below.
- * However, this implementation saves one ecAdd operation, which would be performed in the i=0 iteration otherwise.
- * @dev Recall that `placeholder` here is the number of operators *not* included in the quorum
- * @dev (input[0], input[1]) is the aggregated non singer public key
- */
- if (placeholder != 0) {
- //load compressed pubkey and the index in the stakes array into memory
- uint32 stakeIndex;
- assembly {
- /**
- * @notice retrieving the pubkey of the node in Jacobian coordinates
- */
- // pk.X
- mstore(input, calldataload(pointer))
- // pk.Y
- mstore(add(input, 32), calldataload(add(pointer, 32)))
-
- /**
- * @notice retrieving the index of the stake of the operator in pubkeyHashToStakeHistory in
- * Registry.sol that was recorded at the time of pre-commit.
- */
- stakeIndex := shr(BIT_SHIFT_stakeIndex, calldataload(add(pointer, BYTE_LENGTH_G1_POINT)))
- }
- // We have read (32 + 32 + 4) = 68 additional bytes of calldata in the above assembly block.
- // Update pointer accordingly.
- unchecked {
- pointer += BYTE_LENGTH_NON_SIGNER_INFO;
- }
-
- // get pubkeyHash and add it to pubkeyHashes of operators that aren't part of the quorum.
- bytes32 pubkeyHash = keccak256(abi.encodePacked(input[0], input[1]));
-
-
- pubkeyHashes[0] = pubkeyHash;
-
- // querying the VoteWeigher for getting information on the operator's stake
- // at the time of pre-commit
- localStakeObject = registry.getStakeFromPubkeyHashAndIndex(pubkeyHash, stakeIndex);
-
- // check that the returned OperatorStake object is the most recent for the referenceBlockNumber
- _validateOperatorStake(localStakeObject, referenceBlockNumber);
-
- // subtract operator stakes from totals
- signedTotals.signedStakeFirstQuorum -= localStakeObject.firstQuorumStake;
- signedTotals.signedStakeSecondQuorum -= localStakeObject.secondQuorumStake;
- }
-
- /**
- * @dev store each non signer's public key in (input[2], input[3]) and add them to the aggregate non signer public key
- * @dev keep track of the aggreagate non signing stake too
- */
- for (uint256 i = 1; i < placeholder;) {
- //load compressed pubkey and the index in the stakes array into memory
- uint32 stakeIndex;
- assembly {
- /// @notice retrieving the pubkey of the operator that is not part of the quorum
- mstore(add(input, 64), calldataload(pointer))
- mstore(add(input, 96), calldataload(add(pointer, 32)))
-
- /**
- * @notice retrieving the index of the stake of the operator in pubkeyHashToStakeHistory in
- * Registry.sol that was recorded at the time of pre-commit.
- */
- // slither-disable-next-line variable-scope
- stakeIndex := shr(BIT_SHIFT_stakeIndex, calldataload(add(pointer, BYTE_LENGTH_G1_POINT)))
- }
-
- // We have read (32 + 32 + 4) = 68 additional bytes of calldata in the above assembly block.
- // Update pointer accordingly.
- unchecked {
- pointer += BYTE_LENGTH_NON_SIGNER_INFO;
- }
-
- // get pubkeyHash and add it to pubkeyHashes of operators that aren't part of the quorum.
- bytes32 pubkeyHash = keccak256(abi.encodePacked(input[2], input[3]));
-
- //pubkeys should be ordered in ascending order of hash to make proofs of signing or
- // non signing constant time
- /**
- * @dev this invariant is used in forceOperatorToDisclose in ServiceManager.sol
- */
- require(uint256(pubkeyHash) > uint256(pubkeyHashes[i - 1]), "BLSSignatureChecker.checkSignatures: Pubkey hashes must be in ascending order");
-
- // recording the pubkey hash
- pubkeyHashes[i] = pubkeyHash;
-
- // querying the VoteWeigher for getting information on the operator's stake
- // at the time of pre-commit
- localStakeObject = registry.getStakeFromPubkeyHashAndIndex(pubkeyHash, stakeIndex);
-
- // check that the returned OperatorStake object is the most recent for the referenceBlockNumber
- _validateOperatorStake(localStakeObject, referenceBlockNumber);
-
- //subtract validator stakes from totals
- signedTotals.signedStakeFirstQuorum -= localStakeObject.firstQuorumStake;
- signedTotals.signedStakeSecondQuorum -= localStakeObject.secondQuorumStake;
-
- // call to ecAdd
- // aggregateNonSignerPublicKey = aggregateNonSignerPublicKey + nonSignerPublicKey
- // (input[0], input[1]) = (input[0], input[1]) + (input[2], input[3])
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 6, input, 0x80, input, 0x40)
- // Use "invalid" to make gas estimation work
- switch success
- case 0 {
- invalid()
- }
- }
- require(success, "BLSSignatureChecker.checkSignatures: non signer addition failed");
-
- unchecked {
- ++i;
- }
- }
- // usage of a scoped block here minorly decreases gas usage
- {
- uint32 apkIndex;
- assembly {
- //get next 32 bits which would be the apkIndex of apkUpdates in Registry.sol
- apkIndex := shr(BIT_SHIFT_apkIndex, calldataload(pointer))
- // Update pointer to account for the 4 bytes specifying the apkIndex
- pointer := add(pointer, BYTE_LENGTH_apkIndex)
-
- /**
- * @notice Get the aggregated publickey at the moment when pre-commit happened
- * @dev Aggregated pubkey given as part of calldata instead of being retrieved from voteWeigher reduces number of SLOADs
- * @dev (input[2], input[3]) is the apk
- */
- mstore(add(input, 64), calldataload(pointer))
- mstore(add(input, 96), calldataload(add(pointer, 32)))
- }
-
- // We have read (32 + 32) = 64 additional bytes of calldata in the above assembly block.
- // Update pointer accordingly.
- unchecked {
- pointer += BYTE_LENGTH_G1_POINT;
- }
-
- // make sure the caller has provided the correct aggPubKey
- require(
- IBLSRegistry(address(registry)).getCorrectApkHash(apkIndex, referenceBlockNumber) == keccak256(abi.encodePacked(input[2], input[3])),
- "BLSSignatureChecker.checkSignatures: Incorrect apk provided"
- );
-
-
- }
-
- // if at least 1 non-signer
- if (placeholder != 0) {
- /**
- * @notice need to subtract aggNonSignerPubkey from the apk to get aggregate signature of all
- * operators that are part of the quorum
- */
- // negate aggNonSignerPubkey
- input[1] = (BN254.FP_MODULUS - input[1]) % BN254.FP_MODULUS;
-
- // call to ecAdd
- // singerPublicKey = -aggregateNonSignerPublicKey + apk
- // (input[2], input[3]) = (input[0], input[1]) + (input[2], input[3])
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 6, input, 0x80, add(input, 0x40), 0x40)
- }
- require(success, "BLSSignatureChecker.checkSignatures: aggregate non signer addition failed");
-
- // emit log_named_uint("agg new pubkey", input[2]);
- // emit log_named_uint("agg new pubkey", input[3]);
-
- }
-
- // Now, (input[2], input[3]) is the signingPubkey
-
- // compute H(M) in G1
- (input[6], input[7]) = BN254.hashToG1(msgHash);
-
- // emit log_named_uint("msgHash G1", input[6]);
- // emit log_named_uint("msgHash G1", pointer);
-
-
- // Load the G2 public key into (input[8], input[9], input[10], input[11])
- assembly {
- mstore(add(input, 288), calldataload(pointer)) //input[9] = pkG2.X1
- mstore(add(input, 256), calldataload(add(pointer, 32))) //input[8] = pkG2.X0
- mstore(add(input, 352), calldataload(add(pointer, 64))) //input[11] = pkG2.Y1
- mstore(add(input, 320), calldataload(add(pointer, 96))) //input[10] = pkG2.Y0
- }
-
- unchecked {
- pointer += BYTE_LENGTH_G2_POINT;
- }
-
- // Load the G1 signature, sigma, into (input[0], input[1])
- assembly {
- mstore(input, calldataload(pointer))
- mstore(add(input, 32), calldataload(add(pointer, 32)))
- }
-
- unchecked {
- pointer += BYTE_LENGTH_G1_POINT;
- }
-
- // generate random challenge for public key equality
- // gamma = keccak(simga.X, sigma.Y, signingPublicKey.X, signingPublicKey.Y, H(m).X, H(m).Y,
- // signingPublicKeyG2.X1, signingPublicKeyG2.X0, signingPublicKeyG2.Y1, signingPublicKeyG2.Y0)
- input[4] = uint256(keccak256(abi.encodePacked(input[0], input[1], input[2], input[3], input[6], input[7], input[8], input[9], input[10], input[11])));
-
- // call ecMul
- // (input[2], input[3]) = (input[2], input[3]) * input[4] = signingPublicKey * gamma
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 7, add(input, 0x40), 0x60, add(input, 0x40), 0x40)
- }
- require(success, "BLSSignatureChecker.checkSignatures: aggregate signer public key random shift failed");
-
-
-
- // call ecAdd
- // (input[0], input[1]) = (input[0], input[1]) + (input[2], input[3]) = sigma + gamma * signingPublicKey
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 6, input, 0x80, input, 0x40)
- }
- require(success, "BLSSignatureChecker.checkSignatures: aggregate signer public key and signature addition failed");
-
- // (input[2], input[3]) = g1, the G1 generator
- input[2] = 1;
- input[3] = 2;
-
- // call ecMul
- // (input[4], input[5]) = (input[2], input[3]) * input[4] = g1 * gamma
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 7, add(input, 0x40), 0x60, add(input, 0x80), 0x40)
- }
- require(success, "BLSSignatureChecker.checkSignatures: generator random shift failed");
-
- // (input[6], input[7]) = (input[4], input[5]) + (input[6], input[7]) = g1 * gamma + H(m)
- // solium-disable-next-line security/no-inline-assembly
- assembly {
- success := staticcall(sub(gas(), 2000), 6, add(input, 0x80), 0x80, add(input, 0xC0), 0x40)
- }
- require(success, "BLSSignatureChecker.checkSignatures: generator random shift and G1 hash addition failed");
-
- // insert negated coordinates of the generator for G2
- input[2] = BN254.nG2x1;
- input[3] = BN254.nG2x0;
- input[4] = BN254.nG2y1;
- input[5] = BN254.nG2y0;
-
- // in summary
- // (input[0], input[1]) = sigma + gamma * signingPublicKey
- // (input[2], input[3], input[4], input[5]) = negated generator of G2
- // (input[6], input[7]) = g1 * gamma + H(m)
- // (input[8], input[9], input[10], input[11]) = public key in G2
-
-
- /**
- * @notice now we verify that e(sigma + gamma * pk, -g2)e(H(m) + gamma * g1, pkG2) == 1
- */
-
- assembly {
- // check the pairing; if incorrect, revert
- // staticcall address 8 (ecPairing precompile), forward all gas, send 384 bytes (0x180 in hex) = 12 (32-byte) inputs.
- // store the return data in input[0], and copy only 32 bytes of return data (since precompile returns boolean)
- success := staticcall(sub(gas(), 2000), 8, input, 0x180, input, 0x20)
- }
- require(success, "BLSSignatureChecker.checkSignatures: pairing precompile call failed");
- // check that the provided signature is correct
- require(input[0] == 1, "BLSSignatureChecker.checkSignatures: Pairing unsuccessful");
-
- emit SignatoryRecord(
- msgHash,
- taskNumberToConfirm,
- signedTotals.signedStakeFirstQuorum,
- signedTotals.signedStakeSecondQuorum,
- pubkeyHashes
- );
-
- // set compressedSignatoryRecord variable used for fraudproofs
- compressedSignatoryRecord = MiddlewareUtils.computeSignatoryRecordHash(
- taskNumberToConfirm,
- pubkeyHashes,
- signedTotals.signedStakeFirstQuorum,
- signedTotals.signedStakeSecondQuorum
- );
-
- // return taskNumber, referenceBlockNumber, msgHash, total stakes that signed, and a hash of the signatories
- return (taskNumberToConfirm, referenceBlockNumber, msgHash, signedTotals, compressedSignatoryRecord);
- }
-
- // simple internal function for validating that the OperatorStake returned from a specified index is the correct one
- function _validateOperatorStake(IQuorumRegistry.OperatorStake memory opStake, uint32 referenceBlockNumber)
- internal
- pure
- {
- // check that the stake returned from the specified index is recent enough
- require(opStake.updateBlockNumber <= referenceBlockNumber, "Provided stake index is too early");
-
- /**
- * check that stake is either the most recent update for the total stake (or the operator),
- * or latest before the referenceBlockNumber
- */
- require(
- opStake.nextUpdateBlockNumber == 0 || opStake.nextUpdateBlockNumber > referenceBlockNumber,
- "Provided stake index is not the most recent for blockNumber"
- );
- }
-}
diff --git a/src/contracts/middleware/PaymentManager.sol b/src/contracts/middleware/PaymentManager.sol
deleted file mode 100644
index 7b7ff9b1db..0000000000
--- a/src/contracts/middleware/PaymentManager.sol
+++ /dev/null
@@ -1,550 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
-import "../interfaces/IServiceManager.sol";
-import "../interfaces/IQuorumRegistry.sol";
-import "../interfaces/IDelegationManager.sol";
-import "../interfaces/IPaymentManager.sol";
-import "../permissions/Pausable.sol";
-
-/**
- * @title Controls 'rolled-up' middleware payments.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract is used for doing interactive payment challenges.
- * @notice The contract is marked as abstract since it does not implement the `respondToPaymentChallengeFinal`
- * function -- see DataLayerPaymentManager for an example
- */
-//
-abstract contract PaymentManager is Initializable, IPaymentManager, Pausable {
- using SafeERC20 for IERC20;
-
- uint8 constant internal PAUSED_NEW_PAYMENT_COMMIT = 0;
- uint8 constant internal PAUSED_REDEEM_PAYMENT = 1;
-
- // DATA STRUCTURES
-
- /**
- * @notice Challenge window for submitting fraudproof in the case of an incorrect payment claim by a registered operator.
- */
- uint256 public constant paymentFraudproofInterval = 7 days;
- /// @notice Constant used as a divisor in dealing with BIPS amounts
- uint256 internal constant MAX_BIPS = 10000;
- /// @notice Gas budget provided in calls to DelegationTerms contracts
- uint256 internal constant LOW_LEVEL_GAS_BUDGET = 1e5;
-
- /**
- * @notice The global EigenLayer Delegation contract, which is primarily used by
- * stakers to delegate their stake to operators who serve as middleware nodes.
- * @dev For more details, see DelegationManager.sol.
- */
- IDelegationManager public immutable delegationManager;
-
- /// @notice The ServiceManager contract for this middleware, where tasks are created / initiated.
- IServiceManager public immutable serviceManager;
-
- /// @notice The Registry contract for this middleware, where operators register and deregister.
- IQuorumRegistry public immutable registry;
-
- /// @notice the ERC20 token that will be used by the disperser to pay the service fees to middleware nodes.
- IERC20 public immutable paymentToken;
-
- /// @notice Token used for placing a guarantee on challenges & payment commits
- IERC20 public immutable paymentChallengeToken;
-
- /**
- * @notice Specifies the payment that has to be made as a guarantee for fraudproof during payment challenges.
- */
- uint256 public paymentChallengeAmount;
-
- /// @notice mapping between the operator and its current committed payment or last redeemed payment
- mapping(address => Payment) public operatorToPayment;
-
- /// @notice mapping from operator => PaymentChallenge
- mapping(address => PaymentChallenge) public operatorToPaymentChallenge;
-
- /// @notice Deposits of future fees to be drawn against when paying for service from the middleware
- mapping(address => uint256) public depositsOf;
-
- /// @notice depositors => addresses approved to spend deposits => allowance
- mapping(address => mapping(address => uint256)) public allowances;
-
- // EVENTS
- /// @notice Emitted when the `paymentChallengeAmount` variable is modified
- event PaymentChallengeAmountSet(uint256 previousValue, uint256 newValue);
-
- /// @notice Emitted when an operator commits to a payment by calling the `commitPayment` function
- event PaymentCommit(address operator, uint32 fromTaskNumber, uint32 toTaskNumber, uint256 fee);
-
- /// @notice Emitted when a new challenge is created through a call to the `initPaymentChallenge` function
- event PaymentChallengeInit(address indexed operator, address challenger);
-
- /// @notice Emitted when an operator redeems a payment by calling the `redeemPayment` function
- event PaymentRedemption(address indexed operator, uint256 fee);
-
- /// @notice Emitted when a bisection step is performed in a challenge, through a call to the `performChallengeBisectionStep` function
- event PaymentBreakdown(
- address indexed operator, uint32 fromTaskNumber, uint32 toTaskNumber, uint96 amount1, uint96 amount2
- );
-
- /// @notice Emitted upon successful resolution of a payment challenge, within a call to `resolveChallenge`
- event PaymentChallengeResolution(address indexed operator, bool operatorWon);
-
- /// @dev Emitted when a low-level call to `delegationTerms.payForService` fails, returning `returnData`
- event OnPayForServiceCallFailure(IDelegationTerms indexed delegationTerms, bytes32 returnData);
-
- /// @notice when applied to a function, ensures that the function is only callable by the `serviceManager`
- modifier onlyServiceManager() {
- require(msg.sender == address(serviceManager), "onlyServiceManager");
- _;
- }
-
- /// @notice when applied to a function, ensures that the function is only callable by the `registry`
- modifier onlyRegistry() {
- require(msg.sender == address(registry), "onlyRegistry");
- _;
- }
-
- /// @notice when applied to a function, ensures that the function is only callable by the owner of the `serviceManager`
- modifier onlyServiceManagerOwner() {
- require(msg.sender == serviceManager.owner(), "onlyServiceManagerOwner");
- _;
- }
-
- constructor(
- IDelegationManager _delegationManager,
- IServiceManager _serviceManager,
- IQuorumRegistry _registry,
- IERC20 _paymentToken,
- IERC20 _paymentChallengeToken
- ) {
- delegationManager = _delegationManager;
- serviceManager = _serviceManager;
- registry = _registry;
- paymentToken = _paymentToken;
- paymentChallengeToken = _paymentChallengeToken;
- _disableInitializers();
- }
-
- function initialize(IPauserRegistry _pauserReg, uint256 _paymentChallengeAmount) public initializer {
- _initializePauser(_pauserReg, UNPAUSE_ALL);
- _setPaymentChallengeAmount(_paymentChallengeAmount);
- }
-
- /**
- * @notice deposit one-time fees by the `msg.sender` with this contract to pay for future tasks of this middleware
- * @param depositFor could be the `msg.sender` themselves, or a different address for whom `msg.sender` is depositing these future fees
- * @param amount is amount of futures fees being deposited
- */
- function depositFutureFees(address depositFor, uint256 amount) external {
- paymentToken.safeTransferFrom(msg.sender, address(this), amount);
- depositsOf[depositFor] += amount;
- }
-
- /// @notice Allows the `allowed` address to spend up to `amount` of the `msg.sender`'s funds that have been deposited in this contract
- function setAllowance(address allowed, uint256 amount) external {
- allowances[msg.sender][allowed] = amount;
- }
-
- /**
- * @notice Modifies the `paymentChallengeAmount` amount.
- * @param _paymentChallengeAmount The new value for `paymentChallengeAmount` to take.
- */
- function setPaymentChallengeAmount(uint256 _paymentChallengeAmount) external virtual onlyServiceManagerOwner {
- _setPaymentChallengeAmount(_paymentChallengeAmount);
- }
-
- /// @notice Used for deducting the fees from the payer to the middleware
- function takeFee(address initiator, address payer, uint256 feeAmount) external virtual onlyServiceManager {
- if (initiator != payer) {
- if (allowances[payer][initiator] != type(uint256).max) {
- allowances[payer][initiator] -= feeAmount;
- }
- }
-
- // decrement `payer`'s stored deposits
- depositsOf[payer] -= feeAmount;
- }
-
- /**
- * @notice This is used by an operator to make a claim on the amount that they deserve for their service from their last payment until `toTaskNumber`
- * @dev Once this payment is recorded, a fraud proof period commences during which a challenger can dispute the proposed payment.
- */
- function commitPayment(uint32 toTaskNumber, uint96 amount) external onlyWhenNotPaused(PAUSED_NEW_PAYMENT_COMMIT) {
- // only active operators can call
- require(
- registry.isActiveOperator(msg.sender),
- "PaymentManager.commitPayment: Only registered operators can call this function"
- );
-
- require(toTaskNumber <= _taskNumber(), "PaymentManager.commitPayment: Cannot claim future payments");
-
- // can only claim for a payment after redeeming the last payment
- require(
- operatorToPayment[msg.sender].status == PaymentStatus.REDEEMED,
- "PaymentManager.commitPayment: Require last payment is redeemed"
- );
-
- // operator puts up tokens which can be slashed in case of wrongful payment claim
- paymentChallengeToken.safeTransferFrom(msg.sender, address(this), paymentChallengeAmount);
-
- // recording payment claims for the operator
- uint32 fromTaskNumber;
-
- // calculate the UTC timestamp at which the payment claim will be optimistically confirmed
- uint32 confirmAt = uint32(block.timestamp + paymentFraudproofInterval);
-
- /**
- * @notice For the special case of this being the first payment that is being claimed by the operator,
- * the operator must be claiming payment starting from when they registered.
- */
- if (operatorToPayment[msg.sender].fromTaskNumber == 0) {
- // get the taskNumber when the operator registered
- fromTaskNumber = registry.getFromTaskNumberForOperator(msg.sender);
- } else {
- // you have to redeem starting from the last task you previously redeemed up to
- fromTaskNumber = operatorToPayment[msg.sender].toTaskNumber;
- }
-
- require(fromTaskNumber < toTaskNumber, "invalid payment range");
-
- // update the record for the commitment to payment made by the operator
- operatorToPayment[msg.sender] = Payment(
- fromTaskNumber,
- toTaskNumber,
- confirmAt,
- amount,
- // set payment status as 1: committed
- PaymentStatus.COMMITTED,
- // storing guarantee amount deposited
- paymentChallengeAmount
- );
-
- emit PaymentCommit(msg.sender, fromTaskNumber, toTaskNumber, amount);
- }
-
- /**
- * @notice Called by an operator to redeem a payment that they previously 'committed' to by calling `commitPayment`.
- * @dev This function can only be called after the challenge window for the payment claim has completed.
- */
- function redeemPayment() external onlyWhenNotPaused(PAUSED_REDEEM_PAYMENT) {
- // verify that the `msg.sender` has a committed payment
- require(
- operatorToPayment[msg.sender].status == PaymentStatus.COMMITTED,
- "PaymentManager.redeemPayment: Payment Status is not 'COMMITTED'"
- );
-
- // check that the fraudproof period has already transpired
- require(
- block.timestamp > operatorToPayment[msg.sender].confirmAt,
- "PaymentManager.redeemPayment: Payment still eligible for fraudproof"
- );
-
- // update the status to show that operator's payment is getting redeemed
- operatorToPayment[msg.sender].status = PaymentStatus.REDEEMED;
-
- // Transfer back the challengeAmount to the operator as there was no successful challenge to the payment commitment made by the operator.
- paymentChallengeToken.safeTransfer(msg.sender, operatorToPayment[msg.sender].challengeAmount);
-
- // look up payment amount and delegation terms address for the `msg.sender`
- uint256 amount = operatorToPayment[msg.sender].amount;
- IDelegationTerms dt = delegationManager.delegationTerms(msg.sender);
-
- // transfer the amount due in the payment claim of the operator to its delegation terms contract, where the delegators can withdraw their rewards.
- paymentToken.safeTransfer(address(dt), amount);
-
- // emit event
- emit PaymentRedemption(msg.sender, amount);
-
- // inform the DelegationTerms contract of the payment, which will determine the rewards the operator and its delegators are eligible for
- _payForServiceHook(dt, amount);
- }
-
- // inform the DelegationTerms contract of the payment, which will determine the rewards the operator and its delegators are eligible for
- function _payForServiceHook(IDelegationTerms dt, uint256 amount) internal {
- /**
- * We use low-level call functionality here to ensure that an operator cannot maliciously make this function fail in order to prevent undelegation.
- * In particular, in-line assembly is also used to prevent the copying of uncapped return data which is also a potential DoS vector.
- */
- // format calldata
- bytes memory lowLevelCalldata = abi.encodeWithSelector(IDelegationTerms.payForService.selector, paymentToken, amount);
- // Prepare memory for low-level call return data. We accept a max return data length of 32 bytes
- bool success;
- bytes32[1] memory returnData;
- // actually make the call
- assembly {
- success := call(
- // gas provided to this context
- LOW_LEVEL_GAS_BUDGET,
- // address to call
- dt,
- // value in wei for call
- 0,
- // memory location to copy for calldata
- add(lowLevelCalldata, 32),
- // length of memory to copy for calldata
- mload(lowLevelCalldata),
- // memory location to copy return data
- returnData,
- // byte size of return data to copy to memory
- 32
- )
- }
- // if the call fails, we emit a special event rather than reverting
- if (!success) {
- emit OnPayForServiceCallFailure(dt, returnData[0]);
- }
- }
-
- /**
- * @notice This function is called by a fraud prover to challenge a payment, initiating an interactive-type fraudproof.
- * @param operator is the operator against whose payment claim the fraudproof is being made
- * @param amount1 is the reward amount the challenger in that round claims is for the first half of tasks
- * @param amount2 is the reward amount the challenger in that round claims is for the second half of tasks
- *
- */
- function initPaymentChallenge(address operator, uint96 amount1, uint96 amount2) external {
- require(
- block.timestamp < operatorToPayment[operator].confirmAt
- && operatorToPayment[operator].status == PaymentStatus.COMMITTED,
- "PaymentManager.initPaymentChallenge: Fraudproof interval has passed for payment"
- );
-
- // store challenge details
- operatorToPaymentChallenge[operator] = PaymentChallenge(
- operator,
- msg.sender,
- address(serviceManager),
- operatorToPayment[operator].fromTaskNumber,
- operatorToPayment[operator].toTaskNumber,
- amount1,
- amount2,
- // recording current timestamp plus the fraudproof interval as the `settleAt` timestamp for this challenge
- uint32(block.timestamp + paymentFraudproofInterval),
- // set the status for the operator to respond next
- ChallengeStatus.OPERATOR_TURN
- );
-
- // move challengeAmount over
- uint256 challengeAmount = operatorToPayment[operator].challengeAmount;
- paymentChallengeToken.safeTransferFrom(msg.sender, address(this), challengeAmount);
- // update the payment status and reset the fraudproof window for this payment
- operatorToPayment[operator].status = PaymentStatus.CHALLENGED;
- operatorToPayment[operator].confirmAt = uint32(block.timestamp + paymentFraudproofInterval);
- emit PaymentChallengeInit(operator, msg.sender);
- }
-
- /**
- * @notice Perform a single bisection step in an existing interactive payment challenge.
- * @param operator The middleware operator who was challenged (used to look up challenge details)
- * @param secondHalf If true, then the caller wishes to challenge the amount claimed as payment in the *second half* of the
- * previous bisection step. If false then the *first half* is indicated instead.
- * @param amount1 The amount that the caller asserts the operator is entitled to, for the first half *of the challenged half* of the previous bisection.
- * @param amount2 The amount that the caller asserts the operator is entitled to, for the second half *of the challenged half* of the previous bisection.
- */
- function performChallengeBisectionStep(address operator, bool secondHalf, uint96 amount1, uint96 amount2)
- external
- {
- // copy challenge struct to memory
- PaymentChallenge memory challenge = operatorToPaymentChallenge[operator];
-
- ChallengeStatus status = challenge.status;
-
- require(
- (status == ChallengeStatus.CHALLENGER_TURN && challenge.challenger == msg.sender)
- || (status == ChallengeStatus.OPERATOR_TURN && challenge.operator == msg.sender),
- "PaymentManager.performChallengeBisectionStep: Must be challenger and their turn or operator and their turn"
- );
-
- require(
- block.timestamp < challenge.settleAt,
- "PaymentManager.performChallengeBisectionStep: Challenge has already settled"
- );
-
- uint32 fromTaskNumber = challenge.fromTaskNumber;
- uint32 toTaskNumber = challenge.toTaskNumber;
- uint32 diff = (toTaskNumber - fromTaskNumber) / 2;
-
- /**
- * @notice Change the challenged interval to the one the challenger cares about.
- * If the difference between the current start and end is even, then the new interval has an endpoint halfway in-between
- * If the difference is odd = 2n + 1, the new interval has a "from" endpoint at (start + n = end - (n + 1)) if the second half is challenged,
- * or a "to" endpoint at (end - (2n + 2)/2 = end - (n + 1) = start + n) if the first half is challenged
- * In other words, it's simple when the difference is even, and when the difference is odd, we just always make the first half the smaller one.
- */
- if (secondHalf) {
- challenge.fromTaskNumber = fromTaskNumber + diff;
- _updateChallengeAmounts(operator, DissectionType.SECOND_HALF, amount1, amount2);
- } else {
- challenge.toTaskNumber = fromTaskNumber + diff;
- _updateChallengeAmounts(operator, DissectionType.FIRST_HALF, amount1, amount2);
- }
-
- // update who must respond next to the challenge
- _updateStatus(operator, diff);
-
- // extend the settlement time for the challenge, giving the next participant in the interactive fraudproof `paymentFraudproofInterval` to respond
- challenge.settleAt = uint32(block.timestamp + paymentFraudproofInterval);
-
- // update challenge struct in storage
- operatorToPaymentChallenge[operator] = challenge;
-
- emit PaymentBreakdown(
- operator, challenge.fromTaskNumber, challenge.toTaskNumber, challenge.amount1, challenge.amount2
- );
- }
-
- /**
- * @notice This function is used for updating the status of the challenge in terms of who has to respon
- * to the interactive challenge mechanism next - is it going to be challenger or the operator.
- * @param operator is the operator whose payment claim is being challenged
- * @param diff is the number of tasks across which payment is being challenged in this iteration
- * @dev If the challenge is over only one task, then the challenge is marked specially as a one step challenge –
- * the smallest unit over which a challenge can be proposed – and 'true' is returned.
- * Otherwise status is updated normally and 'false' is returned.
- */
- function _updateStatus(address operator, uint32 diff) internal returns (bool) {
- // payment challenge for one task
- if (diff == 1) {
- //set to one step turn of either challenger or operator
- operatorToPaymentChallenge[operator].status =
- msg.sender == operator
- ? ChallengeStatus.CHALLENGER_TURN_ONE_STEP
- : ChallengeStatus.OPERATOR_TURN_ONE_STEP;
- return false;
-
- // payment challenge across more than one task
- } else {
- // set to dissection turn of either challenger or operator
- operatorToPaymentChallenge[operator].status =
- msg.sender == operator ? ChallengeStatus.CHALLENGER_TURN : ChallengeStatus.OPERATOR_TURN;
- return true;
- }
- }
-
- /// @notice Used to update challenge amounts when the operator (or challenger) breaks down the challenged amount (single bisection step)
- function _updateChallengeAmounts(address operator, DissectionType dissectionType, uint96 amount1, uint96 amount2)
- internal
- {
- if (dissectionType == DissectionType.FIRST_HALF) {
- // if first half is challenged, break the first half of the payment into two halves
- require(
- amount1 + amount2 != operatorToPaymentChallenge[operator].amount1,
- "PaymentManager._updateChallengeAmounts: Invalid amount breakdown"
- );
- } else if (dissectionType == DissectionType.SECOND_HALF) {
- // if second half is challenged, break the second half of the payment into two halves
- require(
- amount1 + amount2 != operatorToPaymentChallenge[operator].amount2,
- "PaymentManager._updateChallengeAmounts: Invalid amount breakdown"
- );
- } else {
- revert("PaymentManager._updateChallengeAmounts: invalid DissectionType");
- }
- // update the stored payment halves
- operatorToPaymentChallenge[operator].amount1 = amount1;
- operatorToPaymentChallenge[operator].amount2 = amount2;
- }
-
- /// @notice resolve an existing PaymentChallenge for an operator
- function resolveChallenge(address operator) external {
- // copy challenge struct to memory
- PaymentChallenge memory challenge = operatorToPaymentChallenge[operator];
-
- require(
- block.timestamp > challenge.settleAt,
- "PaymentManager.resolveChallenge: challenge has not yet reached settlement time"
- );
- ChallengeStatus status = challenge.status;
- // if operator did not respond
- if (status == ChallengeStatus.OPERATOR_TURN || status == ChallengeStatus.OPERATOR_TURN_ONE_STEP) {
- _resolve(challenge, challenge.challenger);
- // if challenger did not respond
- } else if (status == ChallengeStatus.CHALLENGER_TURN || status == ChallengeStatus.CHALLENGER_TURN_ONE_STEP) {
- _resolve(challenge, challenge.operator);
- }
- }
-
- /**
- * @notice Resolves a single payment challenge, paying the winner.
- * @param challenge The challenge that is being resolved.
- * @param winner Address of the winner of the challenge.
- * @dev If challenger is proven correct, then they are refunded their own challengeAmount plus the challengeAmount put up by the operator.
- * If operator is proven correct, then the challenger's challengeAmount is transferred to them, since the operator still hasn't been
- * proven right, and thus their challengeAmount is still required in case they are challenged again.
- */
- function _resolve(PaymentChallenge memory challenge, address winner) internal {
- address operator = challenge.operator;
- address challenger = challenge.challenger;
- if (winner == operator) {
- // operator was correct, allow for another challenge
- operatorToPayment[operator].status = PaymentStatus.COMMITTED;
- operatorToPayment[operator].confirmAt = uint32(block.timestamp + paymentFraudproofInterval);
- /*
- * Since the operator hasn't been proved right (only challenger has been proved wrong)
- * transfer them only challengers challengeAmount, not their own challengeAmount (which is still
- * locked up in this contract)
- */
- paymentChallengeToken.safeTransfer(operator, operatorToPayment[operator].challengeAmount);
- emit PaymentChallengeResolution(operator, true);
- } else {
- // challeger was correct, reset payment
- operatorToPayment[operator].status = PaymentStatus.REDEEMED;
- //give them their challengeAmount and the operator's
- paymentChallengeToken.safeTransfer(challenger, 2 * operatorToPayment[operator].challengeAmount);
- emit PaymentChallengeResolution(operator, false);
- }
- }
-
- /// @notice Returns the ChallengeStatus for the `operator`'s payment claim.
- function getChallengeStatus(address operator) external view returns (ChallengeStatus) {
- return operatorToPaymentChallenge[operator].status;
- }
-
- /// @notice Returns the 'amount1' for the `operator`'s payment claim.
- function getAmount1(address operator) external view returns (uint96) {
- return operatorToPaymentChallenge[operator].amount1;
- }
-
- /// @notice Returns the 'amount2' for the `operator`'s payment claim.
- function getAmount2(address operator) external view returns (uint96) {
- return operatorToPaymentChallenge[operator].amount2;
- }
-
- /// @notice Returns the 'toTaskNumber' for the `operator`'s payment claim.
- function getToTaskNumber(address operator) external view returns (uint48) {
- return operatorToPaymentChallenge[operator].toTaskNumber;
- }
-
- /// @notice Returns the 'fromTaskNumber' for the `operator`'s payment claim.
- function getFromTaskNumber(address operator) external view returns (uint48) {
- return operatorToPaymentChallenge[operator].fromTaskNumber;
- }
-
- /// @notice Returns the task number difference for the `operator`'s payment claim.
- function getDiff(address operator) external view returns (uint48) {
- return operatorToPaymentChallenge[operator].toTaskNumber - operatorToPaymentChallenge[operator].fromTaskNumber;
- }
-
- /// @notice Returns the active challengeAmount of the `operator` placed on their payment claim.
- function getPaymentChallengeAmount(address operator) external view returns (uint256) {
- return operatorToPayment[operator].challengeAmount;
- }
-
- /// @notice Convenience function for fetching the current taskNumber from the `serviceManager`
- function _taskNumber() internal view returns (uint32) {
- return serviceManager.taskNumber();
- }
-
- /**
- * @notice Modifies the `paymentChallengeAmount` amount.
- * @param _paymentChallengeAmount The new value for `paymentChallengeAmount` to take.
- */
- function _setPaymentChallengeAmount(uint256 _paymentChallengeAmount) internal {
- emit PaymentChallengeAmountSet(paymentChallengeAmount, _paymentChallengeAmount);
- paymentChallengeAmount = _paymentChallengeAmount;
- }
-}
diff --git a/src/contracts/middleware/RegistryBase.sol b/src/contracts/middleware/RegistryBase.sol
deleted file mode 100644
index 133175bf97..0000000000
--- a/src/contracts/middleware/RegistryBase.sol
+++ /dev/null
@@ -1,669 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/utils/math/Math.sol";
-import "../interfaces/IServiceManager.sol";
-import "../interfaces/IQuorumRegistry.sol";
-import "./VoteWeigherBase.sol";
-
-/**
- * @title An abstract Registry-type contract that is signature scheme agnostic.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract is used for
- * - registering new operators
- * - committing to and finalizing de-registration as an operator
- * - updating the stakes of the operator
- * @dev This contract is missing key functions. See `BLSRegistry` or `ECDSARegistry` for examples that inherit from this contract.
- */
-abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry {
-
- // TODO: set these on initialization
- /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as
- /// evaluated by this contract's 'VoteWeigher' logic.
- uint128 public minimumStakeFirstQuorum = 1 wei;
- uint128 public minimumStakeSecondQuorum = 1 wei;
-
- /// @notice used for storing Operator info on each operator while registration
- mapping(address => Operator) public registry;
-
- /// @notice used for storing the list of current and past registered operators
- address[] public operatorList;
-
- /// @notice array of the history of the total stakes -- marked as internal since getTotalStakeFromIndex is a getter for this
- OperatorStake[] internal totalStakeHistory;
-
- /// @notice array of the history of the number of operators, and the taskNumbers at which the number of operators changed
- OperatorIndex[] public totalOperatorsHistory;
-
- /// @notice mapping from operator's pubkeyhash to the history of their stake updates
- mapping(bytes32 => OperatorStake[]) public pubkeyHashToStakeHistory;
-
- /// @notice mapping from operator's pubkeyhash to the history of their index in the array of all operators
- mapping(bytes32 => OperatorIndex[]) public pubkeyHashToIndexHistory;
-
- // EVENTS
- /// @notice emitted when `operator` updates their socket address to `socket`
- event SocketUpdate(address operator, string socket);
-
- /// @notice emitted whenever the stake of `operator` is updated
- event StakeUpdate(
- address operator,
- uint96 firstQuorumStake,
- uint96 secondQuorumStake,
- uint32 updateBlockNumber,
- uint32 prevUpdateBlockNumber
- );
-
- /**
- * @notice Emitted whenever an operator deregisters.
- * The `swapped` address is the address returned by an internal call to the `_popRegistrant` function.
- */
- event Deregistration(address operator, address swapped);
-
- /**
- * @notice Irrevocably sets the (immutable) `delegation` & `strategyManager` addresses, and `NUMBER_OF_QUORUMS` variable.
- */
- constructor(
- IStrategyManager _strategyManager,
- IServiceManager _serviceManager,
- uint8 _NUMBER_OF_QUORUMS
- ) VoteWeigherBase(_strategyManager, _serviceManager, _NUMBER_OF_QUORUMS)
- // solhint-disable-next-line no-empty-blocks
- {
- require(_NUMBER_OF_QUORUMS <= 2 && _NUMBER_OF_QUORUMS > 0, "RegistryBase: NUMBER_OF_QUORUMS must be less than or equal to 2 and greater than 0");
- }
-
- /**
- * @notice Adds empty first entries to the dynamic arrays `totalStakeHistory` and `totalOperatorsHistory`,
- * to record an initial condition of zero operators with zero total stake.
- * Adds `_firstQuorumStrategiesConsideredAndMultipliers` and `_secondQuorumStrategiesConsideredAndMultipliers` to the dynamic arrays
- * `strategiesConsideredAndMultipliers[0]` and `strategiesConsideredAndMultipliers[1]` (i.e. to the weighing functions of the quorums)
- */
- function _initialize(
- uint256[] memory _quorumBips,
- StrategyAndWeightingMultiplier[] memory _firstQuorumStrategiesConsideredAndMultipliers,
- StrategyAndWeightingMultiplier[] memory _secondQuorumStrategiesConsideredAndMultipliers
- ) internal virtual onlyInitializing {
- VoteWeigherBase._initialize(_quorumBips);
-
- // push an empty OperatorStake struct to the total stake history to record starting with zero stake
- OperatorStake memory _totalStake;
- totalStakeHistory.push(_totalStake);
-
- // push an empty OperatorIndex struct to the total operators history to record starting with zero operators
- OperatorIndex memory _totalOperators;
- totalOperatorsHistory.push(_totalOperators);
-
- _addStrategiesConsideredAndMultipliers(0, _firstQuorumStrategiesConsideredAndMultipliers);
- _addStrategiesConsideredAndMultipliers(1, _secondQuorumStrategiesConsideredAndMultipliers);
- }
-
- /**
- * @notice Looks up the `operator`'s index in the dynamic array `operatorList` at the specified `blockNumber`.
- * @param index Used to specify the entry within the dynamic array `pubkeyHashToIndexHistory[pubkeyHash]` to
- * read data from, where `pubkeyHash` is looked up from `operator`'s registration info
- * @param blockNumber Is the desired block number at which we wish to query the operator's position in the `operatorList` array
- * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the
- * array `pubkeyHashToIndexHistory[pubkeyHash]` to pull the info from.
- */
- function getOperatorIndex(address operator, uint32 blockNumber, uint32 index) external view returns (uint32) {
- // look up the operator's stored pubkeyHash
- bytes32 pubkeyHash = getOperatorPubkeyHash(operator);
-
- /**
- * Since the 'to' field represents the blockNumber at which a new index started, it is OK if the
- * previous array entry has 'to' == blockNumber, so we check not strict inequality here
- */
- require(
- index == 0 || pubkeyHashToIndexHistory[pubkeyHash][index - 1].toBlockNumber <= blockNumber,
- "RegistryBase.getOperatorIndex: Operator indexHistory index is too high"
- );
- OperatorIndex memory operatorIndex = pubkeyHashToIndexHistory[pubkeyHash][index];
- /**
- * When deregistering, the operator does *not* serve the current block number -- 'to' gets set (from zero) to the current block number.
- * Since the 'to' field represents the blocknumber at which a new index started, we want to check strict inequality here.
- */
- require(
- operatorIndex.toBlockNumber == 0 || blockNumber < operatorIndex.toBlockNumber,
- "RegistryBase.getOperatorIndex: indexHistory index is too low"
- );
- return operatorIndex.index;
- }
-
- /**
- * @notice Looks up the number of total operators at the specified `blockNumber`.
- * @param index Input used to specify the entry within the dynamic array `totalOperatorsHistory` to read data from.
- * @dev This function will revert if the provided `index` is out of bounds.
- */
- function getTotalOperators(uint32 blockNumber, uint32 index) external view returns (uint32) {
- /**
- * Since the 'to' field represents the blockNumber at which a new index started, it is OK if the
- * previous array entry has 'to' == blockNumber, so we check not strict inequality here
- */
- require(
- index == 0 || totalOperatorsHistory[index - 1].toBlockNumber <= blockNumber,
- "RegistryBase.getTotalOperators: TotalOperatorsHistory index is too high"
- );
-
-
- OperatorIndex memory operatorIndex = totalOperatorsHistory[index];
-
- // since the 'to' field represents the blockNumber at which a new index started, we want to check strict inequality here
-
- require(
- operatorIndex.toBlockNumber == 0 || blockNumber < operatorIndex.toBlockNumber,
- "RegistryBase.getTotalOperators: TotalOperatorsHistory index is too low"
- );
- return operatorIndex.index;
- }
-
- /// @notice Returns whether or not the `operator` is currently an active operator, i.e. is "registered".
- function isActiveOperator(address operator) external view virtual returns (bool) {
- return (registry[operator].status == IQuorumRegistry.Status.ACTIVE);
- }
-
- /// @notice Returns the stored pubkeyHash for the specified `operator`.
- function getOperatorPubkeyHash(address operator) public view returns (bytes32) {
- return registry[operator].pubkeyHash;
- }
-
- /**
- * @notice Returns the stake weight corresponding to `pubkeyHash`, at the
- * `index`-th entry in the `pubkeyHashToStakeHistory[pubkeyHash]` array.
- * @param pubkeyHash Hash of the public key of the operator of interest.
- * @param index Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash]`.
- * @dev Function will revert if `index` is out-of-bounds.
- */
- function getStakeFromPubkeyHashAndIndex(bytes32 pubkeyHash, uint256 index)
- external
- view
- returns (OperatorStake memory)
- {
- return pubkeyHashToStakeHistory[pubkeyHash][index];
- }
-
- /**
- * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
- * @param operator is the operator of interest
- * @param blockNumber is the block number of interest
- * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up
- * in `registry[operator].pubkeyHash`
- * @return 'true' if it is successfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise
- * @dev In order for this function to return 'true', the inputs must satisfy all of the following list:
- * 1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
- * 2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
- * is must be strictly greater than `blockNumber`
- * 3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
- * or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
- * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a
- * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'.
- */
- function checkOperatorActiveAtBlockNumber(
- address operator,
- uint256 blockNumber,
- uint256 stakeHistoryIndex
- ) external view returns (bool)
- {
- // fetch the `operator`'s pubkey hash
- bytes32 pubkeyHash = registry[operator].pubkeyHash;
- // pull the stake history entry specified by `stakeHistoryIndex`
- OperatorStake memory operatorStake = pubkeyHashToStakeHistory[pubkeyHash][stakeHistoryIndex];
- return (
- // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber`
- (operatorStake.updateBlockNumber <= blockNumber)
- &&
- // if there is a next update, then check that the next update occurred strictly after `blockNumber`
- (operatorStake.nextUpdateBlockNumber == 0 || operatorStake.nextUpdateBlockNumber > blockNumber)
- &&
- /// verify that the stake was non-zero at the time (note: here was use the assumption that the operator was 'inactive'
- /// once their stake fell to zero)
- (operatorStake.firstQuorumStake != 0 || operatorStake.secondQuorumStake != 0)
- );
- }
-
- /**
- * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` as proof.
- * @param operator is the operator of interest
- * @param blockNumber is the block number of interest
- * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up
- * in `registry[operator].pubkeyHash`
- * @return 'true' if it is successfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise
- * @dev In order for this function to return 'true', the inputs must satisfy all of the following list:
- * 1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber`
- * 2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or
- * is must be strictly greater than `blockNumber`
- * 3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0`
- * or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake
- * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a
- * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'.
- */
- function checkOperatorInactiveAtBlockNumber(
- address operator,
- uint256 blockNumber,
- uint256 stakeHistoryIndex
- ) external view returns (bool)
- {
- // fetch the `operator`'s pubkey hash
- bytes32 pubkeyHash = registry[operator].pubkeyHash;
- // special case for `pubkeyHashToStakeHistory[pubkeyHash]` having lenght zero -- in which case we know the operator was never registered
- if (pubkeyHashToStakeHistory[pubkeyHash].length == 0) {
- return true;
- }
- // pull the stake history entry specified by `stakeHistoryIndex`
- OperatorStake memory operatorStake = pubkeyHashToStakeHistory[pubkeyHash][stakeHistoryIndex];
- return (
- // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber`
- (operatorStake.updateBlockNumber <= blockNumber)
- &&
- // if there is a next update, then check that the next update occurred strictly after `blockNumber`
- (operatorStake.nextUpdateBlockNumber == 0 || operatorStake.nextUpdateBlockNumber > blockNumber)
- &&
- /// verify that the stake was zero at the time (note: here was use the assumption that the operator was 'inactive'
- /// once their stake fell to zero)
- (operatorStake.firstQuorumStake == 0 && operatorStake.secondQuorumStake == 0)
- );
- }
-
- /**
- * @notice Returns the most recent stake weight for the `operator`
- * @dev Function returns an OperatorStake struct with **every entry equal to 0** in the event that the operator has no stake history
- */
- function getMostRecentStakeByOperator(address operator) public view returns (OperatorStake memory) {
- bytes32 pubkeyHash = getOperatorPubkeyHash(operator);
- uint256 historyLength = pubkeyHashToStakeHistory[pubkeyHash].length;
- OperatorStake memory opStake;
- if (historyLength == 0) {
- return opStake;
- } else {
- opStake = pubkeyHashToStakeHistory[pubkeyHash][historyLength - 1];
- return opStake;
- }
- }
-
- function getStakeHistoryLength(bytes32 pubkeyHash) external view returns (uint256) {
- return pubkeyHashToStakeHistory[pubkeyHash].length;
- }
-
- function firstQuorumStakedByOperator(address operator) external view returns (uint96) {
- OperatorStake memory opStake = getMostRecentStakeByOperator(operator);
- return opStake.firstQuorumStake;
- }
-
- function secondQuorumStakedByOperator(address operator) external view returns (uint96) {
- OperatorStake memory opStake = getMostRecentStakeByOperator(operator);
- return opStake.secondQuorumStake;
- }
-
- /**
- * @notice Returns the most recent stake weights for the `operator`
- * @dev Function returns weights of **0** in the event that the operator has no stake history
- */
- function operatorStakes(address operator) public view returns (uint96, uint96) {
- OperatorStake memory opStake = getMostRecentStakeByOperator(operator);
- return (opStake.firstQuorumStake, opStake.secondQuorumStake);
- }
-
- /// @notice Returns the stake amounts from the latest entry in `totalStakeHistory`.
- function totalStake() external view returns (uint96, uint96) {
- // no chance of underflow / error in next line, since an empty entry is pushed in the constructor
- OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1];
- return (_totalStake.firstQuorumStake, _totalStake.secondQuorumStake);
- }
-
- function getLengthOfPubkeyHashStakeHistory(bytes32 pubkeyHash) external view returns (uint256) {
- return pubkeyHashToStakeHistory[pubkeyHash].length;
- }
-
- function getLengthOfPubkeyHashIndexHistory(bytes32 pubkeyHash) external view returns (uint256) {
- return pubkeyHashToIndexHistory[pubkeyHash].length;
- }
-
- function getLengthOfTotalStakeHistory() external view returns (uint256) {
- return totalStakeHistory.length;
- }
-
- function getLengthOfTotalOperatorsHistory() external view returns (uint256) {
- return totalOperatorsHistory.length;
- }
-
- /**
- * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory`.
- * @dev Function will revert in the event that `index` is out-of-bounds.
- */
- function getTotalStakeFromIndex(uint256 index) external view returns (OperatorStake memory) {
- return totalStakeHistory[index];
- }
-
- /// @notice Returns task number from when `operator` has been registered.
- function getFromTaskNumberForOperator(address operator) external view returns (uint32) {
- return registry[operator].fromTaskNumber;
- }
-
- /// @notice Returns the current number of operators of this service.
- function numOperators() public view returns (uint32) {
- return uint32(operatorList.length);
- }
-
- // MUTATING FUNCTIONS
-
- /// @notice Adjusts the `minimumStakeFirstQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 1st quorum.
- function setMinimumStakeFirstQuorum(uint128 _minimumStakeFirstQuorum) external onlyServiceManagerOwner {
- minimumStakeFirstQuorum = _minimumStakeFirstQuorum;
- }
-
- /// @notice Adjusts the `minimumStakeSecondQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 2nd quorum.
- function setMinimumStakeSecondQuorum(uint128 _minimumStakeSecondQuorum) external onlyServiceManagerOwner {
- minimumStakeSecondQuorum = _minimumStakeSecondQuorum;
- }
-
- function updateSocket(string calldata newSocket) external {
- require(
- registry[msg.sender].status == IQuorumRegistry.Status.ACTIVE,
- "RegistryBase.updateSocket: Can only update socket if active on the service"
- );
- emit SocketUpdate(msg.sender, newSocket);
- }
-
-
- // INTERNAL FUNCTIONS
- /**
- * @notice Called when the total number of operators has changed.
- * Sets the `toBlockNumber` field on the last entry *so far* in thedynamic array `totalOperatorsHistory` to the current `block.number`,
- * recording that the previous entry is *no longer the latest* and the block number at which the next was added.
- * Pushes a new entry to `totalOperatorsHistory`, with `index` field set equal to the new amount of operators, recording the new number
- * of total operators (and leaving the `toBlockNumber` field at zero, signaling that this is the latest entry in the array)
- */
- function _updateTotalOperatorsHistory() internal {
- // set the 'toBlockNumber' field on the last entry *so far* in 'totalOperatorsHistory' to the current block number
- totalOperatorsHistory[totalOperatorsHistory.length - 1].toBlockNumber = uint32(block.number);
- // push a new entry to 'totalOperatorsHistory', with 'index' field set equal to the new amount of operators
- OperatorIndex memory _totalOperators;
- _totalOperators.index = uint32(operatorList.length);
- totalOperatorsHistory.push(_totalOperators);
- }
-
- /**
- * @notice Remove the operator from active status. Removes the operator with the given `pubkeyHash` from the `index` in `operatorList`,
- * updates operatorList and index histories, and performs other necessary updates for removing operator
- */
- function _removeOperator(address operator, bytes32 pubkeyHash, uint32 index) internal virtual {
- // remove the operator's stake
- uint32 updateBlockNumber = _removeOperatorStake(pubkeyHash);
-
- // store blockNumber at which operator index changed (stopped being applicable)
- pubkeyHashToIndexHistory[pubkeyHash][pubkeyHashToIndexHistory[pubkeyHash].length - 1].toBlockNumber =
- uint32(block.number);
-
- // remove the operator at `index` from the `operatorList`
- address swappedOperator = _popRegistrant(index);
-
- // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges
- uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock();
- // committing to not signing off on any more middleware tasks
- registry[operator].status = IQuorumRegistry.Status.INACTIVE;
-
- // record a stake update unbonding the operator after `latestServeUntilBlock`
- serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock);
-
- // Emit `Deregistration` event
- emit Deregistration(operator, swappedOperator);
-
- emit StakeUpdate(
- operator,
- // new stakes are zero
- 0,
- 0,
- uint32(block.number),
- updateBlockNumber
- );
- }
-
- /**
- * @notice Removes the stakes of the operator with pubkeyHash `pubkeyHash`
- */
- function _removeOperatorStake(bytes32 pubkeyHash) internal returns(uint32) {
- // gas saving by caching length here
- uint256 pubkeyHashToStakeHistoryLengthMinusOne = pubkeyHashToStakeHistory[pubkeyHash].length - 1;
-
- // determine current stakes
- OperatorStake memory currentStakes =
- pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistoryLengthMinusOne];
- //set nextUpdateBlockNumber in current stakes
- pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistoryLengthMinusOne].nextUpdateBlockNumber =
- uint32(block.number);
-
- /**
- * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here.
- */
- pubkeyHashToStakeHistory[pubkeyHash].push(
- OperatorStake({
- // recording the current block number where the operator stake got updated
- updateBlockNumber: uint32(block.number),
- // mark as 0 since the next update has not yet occurred
- nextUpdateBlockNumber: 0,
- // setting the operator's stakes to 0
- firstQuorumStake: 0,
- secondQuorumStake: 0
- })
- );
-
- // subtract the amounts staked by the operator that is getting deregistered from the total stake
- // copy latest totalStakes to memory
- OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1];
- _totalStake.firstQuorumStake -= currentStakes.firstQuorumStake;
- _totalStake.secondQuorumStake -= currentStakes.secondQuorumStake;
- // update storage of total stake
- _recordTotalStakeUpdate(_totalStake);
-
- emit StakeUpdate(
- msg.sender,
- // new stakes are zero
- 0,
- 0,
- uint32(block.number),
- currentStakes.updateBlockNumber
- );
- return currentStakes.updateBlockNumber;
- }
-
- /**
- * @notice Removes the registrant at the given `index` from the `operatorList`
- * @return swappedOperator is the operator who was swapped with the removed operator in the operatorList,
- * or the *zero address* in the case that the removed operator was already the list operator in the operatorList.
- */
- function _popRegistrant(uint32 index) internal returns (address swappedOperator) {
- // gas saving by caching length here
- uint256 operatorListLengthMinusOne = operatorList.length - 1;
- // Update index info for operator at end of list, if they are not the same as the removed operator
- if (index < operatorListLengthMinusOne) {
- // get existing operator at end of list, and retrieve their pubkeyHash
- swappedOperator = operatorList[operatorListLengthMinusOne];
- Operator memory registrant = registry[swappedOperator];
- bytes32 pubkeyHash = registrant.pubkeyHash;
- // store blockNumber at which operator index changed
- // same operation as above except pubkeyHash is now different (since different operator)
- pubkeyHashToIndexHistory[pubkeyHash][pubkeyHashToIndexHistory[pubkeyHash].length - 1].toBlockNumber =
- uint32(block.number);
- // push new 'OperatorIndex' struct to operator's array of historical indices, with 'index' set equal to 'index' input
- OperatorIndex memory operatorIndex;
- operatorIndex.index = index;
- pubkeyHashToIndexHistory[pubkeyHash].push(operatorIndex);
-
- // move 'swappedOperator' into 'index' slot in operatorList (swapping them with removed operator)
- operatorList[index] = swappedOperator;
- }
-
- // slither-disable-next-line costly-loop
- operatorList.pop();
-
- // Update totalOperatorsHistory
- _updateTotalOperatorsHistory();
-
- return swappedOperator;
- }
-
- /// @notice Adds the Operator `operator` with the given `pubkeyHash` to the `operatorList` and performs necessary related updates.
- function _addRegistrant(
- address operator,
- bytes32 pubkeyHash,
- OperatorStake memory _operatorStake
- )
- internal virtual
- {
-
- require(
- slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max,
- "RegistryBase._addRegistrant: operator must be opted into slashing by the serviceManager"
- );
- // store the Operator's info in mapping
- registry[operator] = Operator({
- pubkeyHash: pubkeyHash,
- status: IQuorumRegistry.Status.ACTIVE,
- fromTaskNumber: serviceManager.taskNumber()
- });
-
- // add the operator to the list of operators
- operatorList.push(operator);
-
- // add the `updateBlockNumber` info
- _operatorStake.updateBlockNumber = uint32(block.number);
- // check special case that operator is re-registering (and thus already has some history)
- if (pubkeyHashToStakeHistory[pubkeyHash].length != 0) {
- // correctly set the 'nextUpdateBlockNumber' field for the re-registering operator's oldest history entry
- pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistory[pubkeyHash].length - 1].nextUpdateBlockNumber
- = uint32(block.number);
- }
- // push the new stake for the operator to storage
- pubkeyHashToStakeHistory[pubkeyHash].push(_operatorStake);
-
- // record `operator`'s index in list of operators
- OperatorIndex memory operatorIndex;
- operatorIndex.index = uint32(operatorList.length - 1);
- pubkeyHashToIndexHistory[pubkeyHash].push(operatorIndex);
-
- // copy latest totalStakes to memory
- OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1];
- // add operator stakes to total stake (in memory)
- _totalStake.firstQuorumStake += _operatorStake.firstQuorumStake;
- _totalStake.secondQuorumStake += _operatorStake.secondQuorumStake;
- // update storage of total stake
- _recordTotalStakeUpdate(_totalStake);
-
- // Update totalOperatorsHistory array
- _updateTotalOperatorsHistory();
-
- // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet
- serviceManager.recordFirstStakeUpdate(operator, 0);
-
- emit StakeUpdate(
- operator,
- _operatorStake.firstQuorumStake,
- _operatorStake.secondQuorumStake,
- uint32(block.number),
- // no previous update block number -- use 0 instead
- 0
- );
- }
-
- /**
- * TODO: critique: "Currently only `_registrationStakeEvaluation` uses the `uint8 registrantType` input -- we should **EITHER** store this
- * and keep using it in other places as well, **OR** stop using it altogether"
- */
- /**
- * @notice Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`.
- * @dev This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere.
- * @return The newly calculated `OperatorStake` for `operator`, stored in memory but not yet committed to storage.
- */
- function _registrationStakeEvaluation(address operator, uint8 operatorType)
- internal
- returns (OperatorStake memory)
- {
- // verify that the `operator` is not already registered
- require(
- registry[operator].status == IQuorumRegistry.Status.INACTIVE,
- "RegistryBase._registrationStakeEvaluation: Operator is already registered"
- );
-
- OperatorStake memory _operatorStake;
-
- // if first bit of operatorType is '1', then operator wants to be a validator for the first quorum
- if ((operatorType & 1) == 1) {
- _operatorStake.firstQuorumStake = uint96(weightOfOperator(operator, 0));
- // check if minimum requirement has been met
- if (_operatorStake.firstQuorumStake < minimumStakeFirstQuorum) {
- _operatorStake.firstQuorumStake = uint96(0);
- }
- }
-
- //if second bit of operatorType is '1', then operator wants to be a validator for the second quorum
- if ((operatorType & 2) == 2) {
- _operatorStake.secondQuorumStake = uint96(weightOfOperator(operator, 1));
- // check if minimum requirement has been met
- if (_operatorStake.secondQuorumStake < minimumStakeSecondQuorum) {
- _operatorStake.secondQuorumStake = uint96(0);
- }
- }
-
- require(
- _operatorStake.firstQuorumStake > 0 || _operatorStake.secondQuorumStake > 0,
- "RegistryBase._registrationStakeEvaluation: Must register as at least one type of validator"
- );
-
- return _operatorStake;
- }
-
- /**
- * @notice Finds the updated stake for `operator`, stores it and records the update.
- * @dev **DOES NOT UPDATE `totalStake` IN ANY WAY** -- `totalStake` updates must be done elsewhere.
- */
- function _updateOperatorStake(address operator, bytes32 pubkeyHash, OperatorStake memory currentOperatorStake, uint256 insertAfter)
- internal
- returns (OperatorStake memory updatedOperatorStake)
- {
- // determine new stakes
- updatedOperatorStake.updateBlockNumber = uint32(block.number);
- updatedOperatorStake.firstQuorumStake = weightOfOperator(operator, 0);
- updatedOperatorStake.secondQuorumStake = weightOfOperator(operator, 1);
-
- // check if minimum requirements have been met
- if (updatedOperatorStake.firstQuorumStake < minimumStakeFirstQuorum) {
- updatedOperatorStake.firstQuorumStake = uint96(0);
- }
- if (updatedOperatorStake.secondQuorumStake < minimumStakeSecondQuorum) {
- updatedOperatorStake.secondQuorumStake = uint96(0);
- }
- // set nextUpdateBlockNumber in prev stakes
- pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistory[pubkeyHash].length - 1].nextUpdateBlockNumber =
- uint32(block.number);
- // push new stake to storage
- pubkeyHashToStakeHistory[pubkeyHash].push(updatedOperatorStake);
- // record stake update in the slasher
- serviceManager.recordStakeUpdate(operator, uint32(block.number), serviceManager.latestServeUntilBlock(), insertAfter);
-
- emit StakeUpdate(
- operator,
- updatedOperatorStake.firstQuorumStake,
- updatedOperatorStake.secondQuorumStake,
- uint32(block.number),
- currentOperatorStake.updateBlockNumber
- );
- }
-
- /// @notice Records that the `totalStake` is now equal to the input param @_totalStake
- function _recordTotalStakeUpdate(OperatorStake memory _totalStake) internal {
- _totalStake.updateBlockNumber = uint32(block.number);
- totalStakeHistory[totalStakeHistory.length - 1].nextUpdateBlockNumber = uint32(block.number);
- totalStakeHistory.push(_totalStake);
- }
-
- /// @notice Verify that the `operator` is an active operator and that they've provided the correct `index`
- function _deregistrationCheck(address operator, uint32 index) internal view {
- require(
- registry[operator].status == IQuorumRegistry.Status.ACTIVE,
- "RegistryBase._deregistrationCheck: Operator is not registered"
- );
-
- require(operator == operatorList[index], "RegistryBase._deregistrationCheck: Incorrect index supplied");
- }
-}
diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol
deleted file mode 100644
index c7cfff6375..0000000000
--- a/src/contracts/middleware/VoteWeigherBase.sol
+++ /dev/null
@@ -1,209 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "../interfaces/IStrategyManager.sol";
-import "./VoteWeigherBaseStorage.sol";
-
-/**
- * @title A simple implementation of the `IVoteWeigher` interface.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract is used for
- * - computing the total weight of an operator for any of the quorums that are considered
- * by the middleware
- * - addition and removal of strategies and the associated weighting criteria that are assigned
- * by the middleware for each of the quorum(s)
- * @dev
- */
-abstract contract VoteWeigherBase is VoteWeigherBaseStorage {
- /// @notice emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]`
- event StrategyAddedToQuorum(uint256 indexed quorumNumber, IStrategy strategy);
- /// @notice emitted when `strategy` has removed from the array at `strategiesConsideredAndMultipliers[quorumNumber]`
- event StrategyRemovedFromQuorum(uint256 indexed quorumNumber, IStrategy strategy);
-
- /// @notice when applied to a function, ensures that the function is only callable by the current `owner` of the `serviceManager`
- modifier onlyServiceManagerOwner() {
- require(msg.sender == serviceManager.owner(), "onlyServiceManagerOwner");
- _;
- }
-
- /// @notice Sets the (immutable) `strategyManager` and `serviceManager` addresses, as well as the (immutable) `NUMBER_OF_QUORUMS` variable
- constructor(
- IStrategyManager _strategyManager,
- IServiceManager _serviceManager,
- uint8 _NUMBER_OF_QUORUMS
- ) VoteWeigherBaseStorage(_strategyManager, _serviceManager, _NUMBER_OF_QUORUMS)
- // solhint-disable-next-line no-empty-blocks
- {}
-
- /// @notice Set the split in earnings between the different quorums.
- function _initialize(uint256[] memory _quorumBips) internal virtual onlyInitializing {
- // verify that the provided `_quorumBips` is of the correct length
- require(
- _quorumBips.length == NUMBER_OF_QUORUMS,
- "VoteWeigherBase._initialize: _quorumBips.length != NUMBER_OF_QUORUMS"
- );
- uint256 totalQuorumBips;
- for (uint256 i; i < NUMBER_OF_QUORUMS; ++i) {
- totalQuorumBips += _quorumBips[i];
- quorumBips[i] = _quorumBips[i];
- }
- // verify that the provided `_quorumBips` do indeed sum to 10,000!
- require(totalQuorumBips == MAX_BIPS, "VoteWeigherBase._initialize: totalQuorumBips != MAX_BIPS");
- }
-
- /**
- * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber.
- * @dev returns zero in the case that `quorumNumber` is greater than or equal to `NUMBER_OF_QUORUMS`
- */
- function weightOfOperator(address operator, uint256 quorumNumber) public virtual returns (uint96) {
- uint96 weight;
-
- if (quorumNumber < NUMBER_OF_QUORUMS) {
- uint256 stratsLength = strategiesConsideredAndMultipliersLength(quorumNumber);
-
- StrategyAndWeightingMultiplier memory strategyAndMultiplier;
-
- for (uint256 i = 0; i < stratsLength;) {
- // accessing i^th StrategyAndWeightingMultiplier struct for the quorumNumber
- strategyAndMultiplier = strategiesConsideredAndMultipliers[quorumNumber][i];
-
- // shares of the operator in the strategy
- uint256 sharesAmount = delegation.operatorShares(operator, strategyAndMultiplier.strategy);
-
- // add the weight from the shares for this strategy to the total weight
- if (sharesAmount > 0) {
- weight += uint96(
- (
- (strategyAndMultiplier.strategy).sharesToUnderlying(sharesAmount)
- * strategyAndMultiplier.multiplier
- ) / WEIGHTING_DIVISOR
- );
- }
-
- unchecked {
- ++i;
- }
- }
- }
-
- return weight;
- }
-
- /// @notice Adds new strategies and the associated multipliers to the @param quorumNumber.
- function addStrategiesConsideredAndMultipliers(
- uint256 quorumNumber,
- StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers
- ) external virtual onlyServiceManagerOwner {
- _addStrategiesConsideredAndMultipliers(quorumNumber, _newStrategiesConsideredAndMultipliers);
- }
-
- /**
- * @notice This function is used for removing strategies and their associated weights from the
- * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber.
- * @dev higher indices should be *first* in the list of @param indicesToRemove, since otherwise
- * the removal of lower index entries will cause a shift in the indices of the other strategiesToRemove
- */
- function removeStrategiesConsideredAndMultipliers(
- uint256 quorumNumber,
- IStrategy[] calldata _strategiesToRemove,
- uint256[] calldata indicesToRemove
- ) external virtual onlyServiceManagerOwner {
- uint256 numStrats = _strategiesToRemove.length;
- // sanity check on input lengths
- require(indicesToRemove.length == numStrats, "VoteWeigherBase.removeStrategiesConsideredAndWeights: input length mismatch");
-
- for (uint256 i = 0; i < numStrats;) {
- // check that the provided index is correct
- require(
- strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]].strategy == _strategiesToRemove[i],
- "VoteWeigherBase.removeStrategiesConsideredAndWeights: index incorrect"
- );
-
- // remove strategy and its associated multiplier
- strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]] = strategiesConsideredAndMultipliers[quorumNumber][strategiesConsideredAndMultipliers[quorumNumber]
- .length - 1];
- strategiesConsideredAndMultipliers[quorumNumber].pop();
- emit StrategyRemovedFromQuorum(quorumNumber, _strategiesToRemove[i]);
-
- unchecked {
- ++i;
- }
- }
- }
-
- /**
- * @notice This function is used for modifying the weights of strategies that are already in the
- * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber.
- * @param strategyIndices is a correctness-check input -- the supplied values must match the indices of the
- * strategiesToModifyWeightsOf in strategiesConsideredAndMultipliers[quorumNumber]
- */
- function modifyStrategyWeights(
- uint256 quorumNumber,
- uint256[] calldata strategyIndices,
- uint96[] calldata newMultipliers
- ) external virtual onlyServiceManagerOwner {
- uint256 numStrats = strategyIndices.length;
- // sanity check on input lengths
- require(newMultipliers.length == numStrats,
- "VoteWeigherBase.modifyStrategyWeights: input length mismatch");
-
- for (uint256 i = 0; i < numStrats;) {
- // change the strategy's associated multiplier
- strategiesConsideredAndMultipliers[quorumNumber][strategyIndices[i]].multiplier = newMultipliers[i];
-
- unchecked {
- ++i;
- }
- }
- }
-
- /**
- * @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`.
- * @dev Reverts if `quorumNumber` < `NUMBER_OF_QUORUMS`, i.e. the input is out of bounds.
- */
- function strategiesConsideredAndMultipliersLength(uint256 quorumNumber) public view returns (uint256) {
- require(
- quorumNumber < NUMBER_OF_QUORUMS,
- "VoteWeigherBase.strategiesConsideredAndMultipliersLength: quorumNumber input exceeds NUMBER_OF_QUORUMS"
- );
- return strategiesConsideredAndMultipliers[quorumNumber].length;
- }
-
- /**
- * @notice Adds `_newStrategiesConsideredAndMultipliers` to the `quorumNumber`-th quorum.
- * @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies).
- * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a concious choice,
- * since a middleware may want, e.g., a stablecoin quorum that accepts USDC, USDT, DAI, etc. as underlying assets and trades them as "equivalent".
- */
- function _addStrategiesConsideredAndMultipliers(
- uint256 quorumNumber,
- StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers
- ) internal {
- uint256 numStratsToAdd = _newStrategiesConsideredAndMultipliers.length;
- uint256 numStratsExisting = strategiesConsideredAndMultipliers[quorumNumber].length;
- require(
- numStratsExisting + numStratsToAdd <= MAX_WEIGHING_FUNCTION_LENGTH,
- "VoteWeigherBase._addStrategiesConsideredAndMultipliers: exceed MAX_WEIGHING_FUNCTION_LENGTH"
- );
- for (uint256 i = 0; i < numStratsToAdd;) {
- // fairly gas-expensive internal loop to make sure that the *same* strategy cannot be added multiple times
- for (uint256 j = 0; j < (numStratsExisting + i);) {
- require(
- strategiesConsideredAndMultipliers[quorumNumber][j].strategy
- != _newStrategiesConsideredAndMultipliers[i].strategy,
- "VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add same strategy 2x"
- );
- unchecked {
- ++j;
- }
- }
- strategiesConsideredAndMultipliers[quorumNumber].push(_newStrategiesConsideredAndMultipliers[i]);
- emit StrategyAddedToQuorum(quorumNumber, _newStrategiesConsideredAndMultipliers[i].strategy);
- unchecked {
- ++i;
- }
- }
- }
-}
diff --git a/src/contracts/middleware/VoteWeigherBaseStorage.sol b/src/contracts/middleware/VoteWeigherBaseStorage.sol
deleted file mode 100644
index d3ae9a5e46..0000000000
--- a/src/contracts/middleware/VoteWeigherBaseStorage.sol
+++ /dev/null
@@ -1,77 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../interfaces/IDelegationManager.sol";
-import "../interfaces/IStrategy.sol";
-import "../interfaces/IStrategyManager.sol";
-import "../interfaces/IVoteWeigher.sol";
-import "../interfaces/IServiceManager.sol";
-
-import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
-
-/**
- * @title Storage variables for the `VoteWeigherBase` contract.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This storage contract is separate from the logic to simplify the upgrade process.
- */
-abstract contract VoteWeigherBaseStorage is Initializable, IVoteWeigher {
- /**
- * @notice In weighing a particular strategy, the amount of underlying asset for that strategy is
- * multiplied by its multiplier, then divided by WEIGHTING_DIVISOR
- */
- struct StrategyAndWeightingMultiplier {
- IStrategy strategy;
- uint96 multiplier;
- }
-
- /// @notice Constant used as a divisor in calculating weights.
- uint256 internal constant WEIGHTING_DIVISOR = 1e18;
- /// @notice Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping.
- uint8 internal constant MAX_WEIGHING_FUNCTION_LENGTH = 32;
- /// @notice Constant used as a divisor in dealing with BIPS amounts.
- uint256 internal constant MAX_BIPS = 10000;
-
- /// @notice The address of the Delegation contract for EigenLayer.
- IDelegationManager public immutable delegation;
-
- /// @notice The address of the StrategyManager contract for EigenLayer.
- IStrategyManager public immutable strategyManager;
-
- /// @notice The address of the Slasher contract for EigenLayer.
- ISlasher public immutable slasher;
-
- /// @notice The ServiceManager contract for this middleware, where tasks are created / initiated.
- IServiceManager public immutable serviceManager;
-
- /// @notice Number of quorums that are being used by the middleware.
- uint256 public immutable NUMBER_OF_QUORUMS;
-
- /**
- * @notice mapping from quorum number to the list of strategies considered and their
- * corresponding multipliers for that specific quorum
- */
- mapping(uint256 => StrategyAndWeightingMultiplier[]) public strategiesConsideredAndMultipliers;
-
- /**
- * @notice This defines the earnings split between different quorums. Mapping is quorumNumber => BIPS which the quorum earns, out of the total earnings.
- * @dev The sum of all entries, i.e. sum(quorumBips[0] through quorumBips[NUMBER_OF_QUORUMS - 1]) should *always* be 10,000!
- */
- mapping(uint256 => uint256) public quorumBips;
-
- constructor(
- IStrategyManager _strategyManager,
- IServiceManager _serviceManager,
- uint8 _NUMBER_OF_QUORUMS
- ) {
- // sanity check that the VoteWeigher is being initialized with at least 1 quorum
- require(_NUMBER_OF_QUORUMS != 0, "VoteWeigherBaseStorage.constructor: _NUMBER_OF_QUORUMS == 0");
- strategyManager = _strategyManager;
- delegation = _strategyManager.delegation();
- slasher = _strategyManager.slasher();
- serviceManager = _serviceManager;
- NUMBER_OF_QUORUMS = _NUMBER_OF_QUORUMS;
- // disable initializers so that the implementation contract cannot be initialized
- _disableInitializers();
- }
-}
diff --git a/src/contracts/middleware/example/ECDSARegistry.sol b/src/contracts/middleware/example/ECDSARegistry.sol
deleted file mode 100644
index ae5458aded..0000000000
--- a/src/contracts/middleware/example/ECDSARegistry.sol
+++ /dev/null
@@ -1,202 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../RegistryBase.sol";
-
-/**
- * @title A Registry-type contract identifying operators by their Ethereum address, with only 1 quorum.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract is used for
- * - registering new operators
- * - committing to and finalizing de-registration as an operator
- * - updating the stakes of the operator
- */
-contract ECDSARegistry is RegistryBase {
-
- /// @notice the address that can whitelist people
- address public operatorWhitelister;
- /// @notice toggle of whether the operator whitelist is on or off
- bool public operatorWhitelistEnabled;
- /// @notice operator => are they whitelisted (can they register with the middleware)
- mapping(address => bool) public whitelisted;
-
- // EVENTS
- /**
- * @notice Emitted upon the registration of a new operator for the middleware
- * @param operator Address of the new operator
- * @param socket The ip:port of the operator
- */
- event Registration(
- address indexed operator,
- string socket
- );
-
- /// @notice Emitted when the `operatorWhitelister` role is transferred.
- event OperatorWhitelisterTransferred(address previousAddress, address newAddress);
-
- /// @notice Modifier that restricts a function to only be callable by the `whitelister` role.
- modifier onlyOperatorWhitelister {
- require(operatorWhitelister == msg.sender, "BLSRegistry.onlyOperatorWhitelister: not operatorWhitelister");
- _;
- }
-
- constructor(
- IStrategyManager _strategyManager,
- IServiceManager _serviceManager
- )
- RegistryBase(
- _strategyManager,
- _serviceManager,
- 1 // set the number of quorums to 1
- )
- {}
-
- /// @notice Initialize whitelister and the quorum strategies + multipliers.
- function initialize(
- address _operatorWhitelister,
- bool _operatorWhitelistEnabled,
- uint256[] memory _quorumBips,
- StrategyAndWeightingMultiplier[] memory _quorumStrategiesConsideredAndMultipliers
- ) public virtual initializer {
- _setOperatorWhitelister(_operatorWhitelister);
- operatorWhitelistEnabled = _operatorWhitelistEnabled;
-
- RegistryBase._initialize(
- _quorumBips,
- _quorumStrategiesConsideredAndMultipliers,
- new StrategyAndWeightingMultiplier[](0)
- );
- }
-
- /**
- * @notice Called by the service manager owner to transfer the whitelister role to another address
- */
- function setOperatorWhitelister(address _operatorWhitelister) external onlyServiceManagerOwner {
- _setOperatorWhitelister(_operatorWhitelister);
- }
-
- /**
- * @notice Callable only by the service manager owner, this function toggles the whitelist on or off
- * @param _operatorWhitelistEnabled true if turning whitelist on, false otherwise
- */
- function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external onlyServiceManagerOwner {
- operatorWhitelistEnabled = _operatorWhitelistEnabled;
- }
-
- /**
- * @notice Called by the operatorWhitelister, adds a list of operators to the whitelist
- * @param operators the operators to add to the whitelist
- */
- function addToOperatorWhitelist(address[] calldata operators) external onlyOperatorWhitelister {
- for (uint i = 0; i < operators.length; i++) {
- whitelisted[operators[i]] = true;
- }
- }
-
- /**
- * @notice Called by the operatorWhitelister, removes a list of operators to the whitelist
- * @param operators the operators to remove from the whitelist
- */
- function removeFromWhitelist(address[] calldata operators) external onlyOperatorWhitelister {
- for (uint i = 0; i < operators.length; i++) {
- whitelisted[operators[i]] = false;
- }
- }
- /**
- * @notice called for registering as an operator
- * @param socket is the socket address of the operator
- */
- function registerOperator(string calldata socket) external virtual {
- _registerOperator(msg.sender, socket);
- }
-
- /**
- * @param operator is the node who is registering to be a operator
- * @param socket is the socket address of the operator
- */
- function _registerOperator(address operator, string calldata socket)
- internal
- {
- if(operatorWhitelistEnabled) {
- require(whitelisted[operator], "BLSRegistry._registerOperator: not whitelisted");
- }
-
- // validate the registration of `operator` and find their `OperatorStake`
- OperatorStake memory _operatorStake = _registrationStakeEvaluation(operator, 1);
-
- // add the operator to the list of registrants and do accounting
- _addRegistrant(operator, bytes32(uint256(uint160(operator))), _operatorStake);
-
- emit Registration(operator, socket);
- }
-
- /**
- * @notice Used by an operator to de-register itself from providing service to the middleware.
- * @param index is the sender's location in the dynamic array `operatorList`
- */
- function deregisterOperator(uint32 index) external virtual returns (bool) {
- _deregisterOperator(msg.sender, index);
- return true;
- }
-
- /**
- * @notice Used to process de-registering an operator from providing service to the middleware.
- * @param operator The operator to be deregistered
- * @param index is the sender's location in the dynamic array `operatorList`
- */
- function _deregisterOperator(address operator, uint32 index) internal {
- // verify that the `operator` is an active operator and that they've provided the correct `index`
- _deregistrationCheck(operator, index);
-
- // Perform necessary updates for removing operator, including updating operator list and index histories
- _removeOperator(operator, bytes32(uint256(uint160(operator))), index);
- }
-
- /**
- * @notice Used for updating information on deposits of nodes.
- * @param operators are the nodes whose deposit information is getting updated
- * @param prevElements are the elements before this middleware in the operator's linked list within the slasher
- */
- function updateStakes(address[] calldata operators, uint256[] calldata prevElements) external {
- // copy total stake to memory
- OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1];
-
- // placeholders reused inside of loop
- OperatorStake memory currentStakes;
- bytes32 pubkeyHash;
- uint256 operatorsLength = operators.length;
- // make sure lengths are consistent
- require(operatorsLength == prevElements.length, "BLSRegistry.updateStakes: prevElement is not the same length as operators");
- // iterating over all the tuples that are to be updated
- for (uint256 i = 0; i < operatorsLength;) {
- // get operator's pubkeyHash
- pubkeyHash = bytes32(uint256(uint160(operators[i])));
- // fetch operator's existing stakes
- currentStakes = pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistory[pubkeyHash].length - 1];
-
- // Note: we only edit the first quorum stake because this is a single quorum registry example
- // decrease _totalStake by operator's existing stakes
- _totalStake.firstQuorumStake -= currentStakes.firstQuorumStake;
-
- // update the stake for the i-th operator
- currentStakes = _updateOperatorStake(operators[i], pubkeyHash, currentStakes, prevElements[i]);
-
- // increase _totalStake by operator's updated stakes
- _totalStake.firstQuorumStake += currentStakes.firstQuorumStake;
-
- unchecked {
- ++i;
- }
- }
-
- // update storage of total stake
- _recordTotalStakeUpdate(_totalStake);
- }
-
- function _setOperatorWhitelister(address _operatorWhitelister) internal {
- require(_operatorWhitelister != address(0), "BLSRegistry.initialize: cannot set operatorWhitelister to zero address");
- emit OperatorWhitelisterTransferred(operatorWhitelister, _operatorWhitelister);
- operatorWhitelister = _operatorWhitelister;
- }
-}
diff --git a/src/contracts/middleware/example/HashThreshold.sol b/src/contracts/middleware/example/HashThreshold.sol
deleted file mode 100644
index 4ff4a370ad..0000000000
--- a/src/contracts/middleware/example/HashThreshold.sol
+++ /dev/null
@@ -1,140 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/access/Ownable.sol";
-import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
-
-import "../../interfaces/IServiceManager.sol";
-import "./ECDSARegistry.sol";
-
-/**
- * @title An EigenLayer middleware example service manager that slashes validators that sign a message that, when hashed 10 times starts with less than a certain number of 0s.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-contract HashThreshold is Ownable, IServiceManager {
- uint32 constant disputePeriodBlocks = 1 days / 12 seconds;
- uint8 constant numZeroes = 5;
- ISlasher public immutable slasher;
- ECDSARegistry public immutable registry;
-
-
- struct CertifiedMessageMetadata {
- bytes32 signaturesHash;
- uint32 validAfterBlock;
- }
-
- uint32 public taskNumber = 0;
- uint32 public latestServeUntilBlock = 0;
- mapping(bytes32 => CertifiedMessageMetadata) public certifiedMessageMetadatas;
-
- event MessageCertified(bytes32);
-
- modifier onlyRegistry {
- require(msg.sender == address(registry), "Only registry can call this function");
- _;
- }
-
- constructor(
- ISlasher _slasher,
- ECDSARegistry _registry
- ) {
- slasher = _slasher;
- registry = _registry;
- }
-
- function owner() public view override(Ownable, IServiceManager) returns (address) {
- return Ownable.owner();
- }
-
- function decaHash(bytes32 message) public pure returns (bytes32) {
- bytes32 hash = message;
- for (uint256 i = 0; i < 10; i++) {
- hash = keccak256(abi.encodePacked(hash));
- }
- return hash;
- }
-
- /**
- * This function is called by anyone to certify a message. Signers are certifying that the decahashed message starts with at least `numZeros` 0s.
- *
- * @param message The message to certify
- * @param signatures The signatures of the message, certifying it
- */
- function submitSignatures(bytes32 message, bytes calldata signatures) external {
- // we check that the message has not already been certified
- require(certifiedMessageMetadatas[message].validAfterBlock == 0, "Message already certified");
- // this makes it so that the signatures are viewable in calldata
- require(msg.sender == tx.origin, "EOA must call this function");
- uint128 stakeSigned = 0;
- for(uint256 i = 0; i < signatures.length; i += 65) {
- // we fetch all the signers and check their signatures and their stake
- address signer = ECDSA.recover(message, signatures[i:i+65]);
- require(registry.isActiveOperator(signer), "Signer is not an active operator");
- stakeSigned += registry.firstQuorumStakedByOperator(signer);
- }
- // We require that 2/3 of the stake signed the message
- // We only take the first quorum stake because this is a single quorum middleware
- (uint96 totalStake,) = registry.totalStake();
- require(stakeSigned >= 666667 * uint256(totalStake) / 1000000, "Need more than 2/3 of stake to sign");
-
- uint32 newLatestServeUntilBlock = uint32(block.number + disputePeriodBlocks);
-
- certifiedMessageMetadatas[message] = CertifiedMessageMetadata({
- validAfterBlock: newLatestServeUntilBlock,
- signaturesHash: keccak256(signatures)
- });
-
- // increment global service manager values
- taskNumber++;
- // Note: latestServeUntilBlock is the latest block at which anyone currently staked on the middleware can be frozen
- latestServeUntilBlock = newLatestServeUntilBlock;
-
- emit MessageCertified(message);
- }
-
-
- /**
- * This function is called by anyone to slash the signers of an invalid message that has been certified.
- *
- * @param message The message to slash the signers of
- * @param signatures The signatures that certified the message
- */
- function slashSigners(bytes32 message, bytes calldata signatures) external {
- CertifiedMessageMetadata memory certifiedMessageMetadata = certifiedMessageMetadatas[message];
- // we check that the message has been certified
- require(certifiedMessageMetadata.validAfterBlock > block.number, "Dispute period has passed");
- // we check that the signatures match the ones that were certified
- require(certifiedMessageMetadata.signaturesHash == keccak256(signatures), "Signatures do not match");
- // we check that the message hashes to enough zeroes
- require(decaHash(message) >> (256 - numZeroes) == 0, "Message does not hash to enough zeroes");
- // we freeze all the signers
- for (uint i = 0; i < signatures.length; i += 65) {
- // this is eigenlayer's means of escalating an operators stake for review for slashing
- // this immediately prevents all withdrawals for the operator and stakers delegated to them
- slasher.freezeOperator(ECDSA.recover(message, signatures[i:i+65]));
- }
- // we invalidate the message
- certifiedMessageMetadatas[message].validAfterBlock = type(uint32).max;
- }
-
- /// @inheritdoc IServiceManager
- function freezeOperator(address operator) external onlyRegistry {
- slasher.freezeOperator(operator);
- }
-
- /// @inheritdoc IServiceManager
- function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external onlyRegistry {
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- }
-
- /// @inheritdoc IServiceManager
- function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external onlyRegistry {
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- }
-
- /// @inheritdoc IServiceManager
- function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external onlyRegistry {
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, prevElement);
- }
-}
diff --git a/src/contracts/operators/MerkleDelegationTerms.sol b/src/contracts/operators/MerkleDelegationTerms.sol
deleted file mode 100644
index ffa40586a2..0000000000
--- a/src/contracts/operators/MerkleDelegationTerms.sol
+++ /dev/null
@@ -1,154 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "@openzeppelin/contracts/access/Ownable.sol";
-import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
-
-import "../interfaces/IDelegationTerms.sol";
-import "../libraries/Merkle.sol";
-
-/**
- * @title A 'Delegation Terms' contract that an operator can use to distribute earnings to stakers by periodically posting Merkle roots
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract specifies the delegation terms of a given operator. When a staker delegates its stake to an operator,
- * it has to agrees to the terms set in the operator's 'Delegation Terms' contract. Payments to an operator are routed through
- * their specified 'Delegation Terms' contract for subsequent distribution of earnings to individual stakers.
- * There are also hooks that call into an operator's DelegationTerms contract when a staker delegates to or undelegates from
- * the operator.
- * @dev This contract uses a system in which the operator posts roots of a *sparse Merkle tree*. Each leaf of the tree is expected
- * to contain the **cumulative** earnings of a staker. This will reduce the total number of actions that stakers who claim only rarely
- * have to take, while allowing stakers to claim their earnings as often as new Merkle roots are posted.
- */
-contract MerkleDelegationTerms is Ownable, IDelegationTerms {
- using SafeERC20 for IERC20;
-
- struct TokenAndAmount {
- IERC20 token;
- uint256 amount;
- }
-
- struct MerkleRootAndTreeHeight {
- bytes32 root;
- uint256 height;
- }
-
- // sanity-check parameter on Merkle tree height
- uint256 internal constant MAX_HEIGHT = 256;
-
- /// @notice staker => token => cumulative amount *claimed*
- mapping(address => mapping(IERC20 => uint256)) public cumulativeClaimedByStakerOfToken;
-
- /// @notice Array of Merkle roots with heights, each posted by the operator (contract owner)
- MerkleRootAndTreeHeight[] public merkleRoots;
-
- // TODO: more events?
- event NewMerkleRootPosted(bytes32 newRoot, uint256 height);
-
- /**
- * @notice Used by the operator to withdraw tokens directly from this contract.
- * @param tokensAndAmounts ERC20 tokens to withdraw and the amount of each respective ERC20 token to withdraw.
- */
- function operatorWithdrawal(TokenAndAmount[] calldata tokensAndAmounts) external onlyOwner {
- uint256 tokensAndAmountsLength = tokensAndAmounts.length;
- for (uint256 i; i < tokensAndAmountsLength;) {
- tokensAndAmounts[i].token.safeTransfer(msg.sender, tokensAndAmounts[i].amount);
- cumulativeClaimedByStakerOfToken[msg.sender][tokensAndAmounts[i].token] += tokensAndAmounts[i].amount;
- unchecked {
- ++i;
- }
- }
- }
-
- /// @notice Used by the operator to post an updated root of the stakers' all-time earnings
- function postMerkleRoot(bytes32 newRoot, uint256 height) external onlyOwner {
- // sanity check
- require(height <= MAX_HEIGHT, "MerkleDelegationTerms.postMerkleRoot: height input too large");
- merkleRoots.push(
- MerkleRootAndTreeHeight({
- root: newRoot,
- height: height
- })
- );
- emit NewMerkleRootPosted(newRoot, height);
- }
-
- /**
- * @notice Called by a staker to prove the inclusion of their earnings in a Merkle root (posted by the operator) and claim them.
- * @param tokensAndAmounts ERC20 tokens to withdraw and the amount of each respective ERC20 token to withdraw.
- * @param proof Merkle proof showing that a leaf containing `(msg.sender, tokensAndAmounts)` was included in the `rootIndex`-th
- * Merkle root posted by the operator.
- * @param nodeIndex Specifies the node inside the Merkle tree corresponding to the specified root, `merkleRoots[rootIndex].root`.
- * @param rootIndex Specifies the Merkle root to look up, using `merkleRoots[rootIndex]`
- */
- function proveEarningsAndWithdraw(
- TokenAndAmount[] calldata tokensAndAmounts,
- bytes memory proof,
- uint256 nodeIndex,
- uint256 rootIndex
- ) external {
- // calculate the leaf that the `msg.sender` is claiming
- bytes32 leafHash = calculateLeafHash(msg.sender, tokensAndAmounts);
-
- // verify that the proof length is appropriate for the chosen root
- require(proof.length == 32 * merkleRoots[rootIndex].height, "MerkleDelegationTerms.proveEarningsAndWithdraw: incorrect proof length");
-
- // check inclusion of the leafHash in the tree corresponding to `merkleRoots[rootIndex]`
- require(
- Merkle.verifyInclusionKeccak(
- proof,
- merkleRoots[rootIndex].root,
- leafHash,
- nodeIndex
- ),
- "MerkleDelegationTerms.proveEarningsAndWithdraw: proof of inclusion failed"
- );
-
- uint256 tokensAndAmountsLength = tokensAndAmounts.length;
- for (uint256 i; i < tokensAndAmountsLength;) {
- // calculate amount to send
- uint256 amountToSend = tokensAndAmounts[i].amount - cumulativeClaimedByStakerOfToken[msg.sender][tokensAndAmounts[i].token];
-
- if (amountToSend != 0) {
- // update claimed amount in storage
- cumulativeClaimedByStakerOfToken[msg.sender][tokensAndAmounts[i].token] = tokensAndAmounts[i].amount;
-
- // actually send the tokens
- tokensAndAmounts[i].token.safeTransfer(msg.sender, amountToSend);
- }
- unchecked {
- ++i;
- }
- }
- }
-
- /// @notice Helper function for calculating a leaf in a Merkle tree formatted as `(address staker, TokenAndAmount[] calldata tokensAndAmounts)`
- function calculateLeafHash(address staker, TokenAndAmount[] calldata tokensAndAmounts) public pure returns (bytes32) {
- return keccak256(abi.encode(staker, tokensAndAmounts));
- }
-
- // FUNCTIONS FROM INTERFACE
- function payForService(IERC20, uint256) external payable
- // solhint-disable-next-line no-empty-blocks
- {}
-
- /// @notice Hook for receiving new delegation
- function onDelegationReceived(
- address,
- IStrategy[] memory,
- uint256[] memory
- ) external pure returns(bytes memory)
- // solhint-disable-next-line no-empty-blocks
- {}
-
- /// @notice Hook for withdrawing delegation
- function onDelegationWithdrawn(
- address,
- IStrategy[] memory,
- uint256[] memory
- ) external pure returns(bytes memory)
- // solhint-disable-next-line no-empty-blocks
- {}
-}
\ No newline at end of file
diff --git a/src/contracts/permissions/Pausable.sol b/src/contracts/permissions/Pausable.sol
index d9b38317c8..76996420ba 100644
--- a/src/contracts/permissions/Pausable.sol
+++ b/src/contracts/permissions/Pausable.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../interfaces/IPausable.sol";
@@ -27,17 +27,8 @@ contract Pausable is IPausable {
/// @dev whether or not the contract is currently paused
uint256 private _paused;
- uint256 constant internal UNPAUSE_ALL = 0;
- uint256 constant internal PAUSE_ALL = type(uint256).max;
-
- /// @notice Emitted when the `pauserRegistry` is set to `newPauserRegistry`.
- event PauserRegistrySet(IPauserRegistry pauserRegistry, IPauserRegistry newPauserRegistry);
-
- /// @notice Emitted when the pause is triggered by `account`, and changed to `newPausedStatus`.
- event Paused(address indexed account, uint256 newPausedStatus);
-
- /// @notice Emitted when the pause is lifted by `account`, and changed to `newPausedStatus`.
- event Unpaused(address indexed account, uint256 newPausedStatus);
+ uint256 internal constant UNPAUSE_ALL = 0;
+ uint256 internal constant PAUSE_ALL = type(uint256).max;
/// @notice
modifier onlyPauser() {
@@ -102,7 +93,10 @@ contract Pausable is IPausable {
*/
function unpause(uint256 newPausedStatus) external onlyUnpauser {
// verify that the `newPausedStatus` does not *flip* any bits (i.e. doesn't pause anything, all 0 bits remain)
- require(((~_paused) & (~newPausedStatus)) == (~_paused), "Pausable.unpause: invalid attempt to pause functionality");
+ require(
+ ((~_paused) & (~newPausedStatus)) == (~_paused),
+ "Pausable.unpause: invalid attempt to pause functionality"
+ );
_paused = newPausedStatus;
emit Unpaused(msg.sender, newPausedStatus);
}
@@ -125,7 +119,10 @@ contract Pausable is IPausable {
/// internal function for setting pauser registry
function _setPauserRegistry(IPauserRegistry newPauserRegistry) internal {
- require(address(newPauserRegistry) != address(0), "Pausable._setPauserRegistry: newPauserRegistry cannot be the zero address");
+ require(
+ address(newPauserRegistry) != address(0),
+ "Pausable._setPauserRegistry: newPauserRegistry cannot be the zero address"
+ );
emit PauserRegistrySet(pauserRegistry, newPauserRegistry);
pauserRegistry = newPauserRegistry;
}
diff --git a/src/contracts/permissions/PauserRegistry.sol b/src/contracts/permissions/PauserRegistry.sol
index 89bddd2540..1058784ec5 100644
--- a/src/contracts/permissions/PauserRegistry.sol
+++ b/src/contracts/permissions/PauserRegistry.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../interfaces/IPauserRegistry.sol";
@@ -15,17 +15,13 @@ contract PauserRegistry is IPauserRegistry {
/// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
address public unpauser;
- event PauserStatusChanged(address pauser, bool canPause);
-
- event UnpauserChanged(address previousUnpauser, address newUnpauser);
-
modifier onlyUnpauser() {
require(msg.sender == unpauser, "msg.sender is not permissioned as unpauser");
_;
}
constructor(address[] memory _pausers, address _unpauser) {
- for(uint256 i = 0; i < _pausers.length; i++) {
+ for (uint256 i = 0; i < _pausers.length; i++) {
_setIsPauser(_pausers[i], true);
}
_setUnpauser(_unpauser);
diff --git a/src/contracts/pods/BeaconChainOracle.sol b/src/contracts/pods/BeaconChainOracle.sol
deleted file mode 100644
index a1358209e7..0000000000
--- a/src/contracts/pods/BeaconChainOracle.sol
+++ /dev/null
@@ -1,143 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/access/Ownable.sol";
-import "../interfaces/IBeaconChainOracle.sol";
-
-/**
- * @title Oracle contract used for bringing state roots of the Beacon Chain to the Execution Layer.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice The owner of this contract can edit a set of 'oracle signers', as well as changing the threshold number of oracle signers that must vote for a
- * particular state root at a specified blockNumber before the state root is considered 'confirmed'.
- */
-contract BeaconChainOracle is IBeaconChainOracle, Ownable {
- /// @notice The minimum value which the `threshold` variable is allowed to take.
- uint256 public constant MINIMUM_THRESHOLD = 1;
-
- /// @notice Total number of members of the set of oracle signers.
- uint256 public totalOracleSigners;
- /**
- * @notice Number of oracle signers that must vote for a state root in order for the state root to be confirmed.
- * Adjustable by this contract's owner through use of the `setThreshold` function.
- * @dev We note that there is an edge case -- when the threshold is adjusted downward, if a state root already has enough votes to meet the *new* threshold,
- * the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations.
- */
- uint256 public threshold;
- /// @notice Largest blockNumber that has been confirmed by the oracle.
- uint64 public latestConfirmedOracleBlockNumber;
-
- /// @notice Mapping: Beacon Chain blockNumber => the Beacon Chain state root at the specified blockNumber.
- /// @dev This will return `bytes32(0)` if the state root at the specified blockNumber is not yet confirmed.
- mapping(uint64 => bytes32) public beaconStateRootAtBlockNumber;
- /// @notice Mapping: address => whether or not the address is in the set of oracle signers.
- mapping(address => bool) public isOracleSigner;
- /// @notice Mapping: Beacon Chain blockNumber => oracle signer address => whether or not the oracle signer has voted on the state root at the blockNumber.
- mapping(uint64 => mapping(address => bool)) public hasVoted;
- /// @notice Mapping: Beacon Chain blockNumber => state root => total number of oracle signer votes for the state root at the blockNumber.
- mapping(uint64 => mapping(bytes32 => uint256)) public stateRootVotes;
-
- /// @notice Emitted when the value of the `threshold` variable is changed from `previousValue` to `newValue`.
- event ThresholdModified(uint256 previousValue, uint256 newValue);
-
- /// @notice Emitted when the beacon chain state root at `blockNumber` is confirmed to be `stateRoot`.
- event StateRootConfirmed(uint64 blockNumber, bytes32 stateRoot);
-
- /// @notice Emitted when `addedOracleSigner` is added to the set of oracle signers.
- event OracleSignerAdded(address addedOracleSigner);
-
- /// @notice Emitted when `removedOracleSigner` is removed from the set of oracle signers.
- event OracleSignerRemoved(address removedOracleSigner);
-
- /// @notice Modifier that restricts functions to only be callable by members of the oracle signer set
- modifier onlyOracleSigner() {
- require(isOracleSigner[msg.sender], "BeaconChainOracle.onlyOracleSigner: Not an oracle signer");
- _;
- }
-
- constructor(address initialOwner, uint256 initialThreshold, address[] memory initialOracleSigners) {
- _transferOwnership(initialOwner);
- _setThreshold(initialThreshold);
- _addOracleSigners(initialOracleSigners);
- }
-
- /**
- * @notice Owner-only function used to modify the value of the `threshold` variable.
- * @param _threshold Desired new value for the `threshold` variable. Function will revert if this is set to zero.
- */
- function setThreshold(uint256 _threshold) external onlyOwner {
- _setThreshold(_threshold);
- }
-
- /**
- * @notice Owner-only function used to add a signer to the set of oracle signers.
- * @param _oracleSigners Array of address to be added to the set.
- * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already in the set of oracle signers.
- */
- function addOracleSigners(address[] memory _oracleSigners) external onlyOwner {
- _addOracleSigners(_oracleSigners);
- }
-
- /**
- * @notice Owner-only function used to remove a signer from the set of oracle signers.
- * @param _oracleSigners Array of address to be removed from the set.
- * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already not in the set of oracle signers.
- */
- function removeOracleSigners(address[] memory _oracleSigners) external onlyOwner {
- for (uint256 i = 0; i < _oracleSigners.length;) {
- if (isOracleSigner[_oracleSigners[i]]) {
- emit OracleSignerRemoved(_oracleSigners[i]);
- isOracleSigner[_oracleSigners[i]] = false;
- totalOracleSigners -= 1;
- }
- unchecked {
- ++i;
- }
- }
- }
-
- /**
- * @notice Called by a member of the set of oracle signers to assert that the Beacon Chain state root is `stateRoot` at `blockNumber`.
- * @dev The state root will be confirmed once the total number of votes *for this exact state root at this exact blockNumber* meets the `threshold` value.
- * @param blockNumber The Beacon Chain blockNumber of interest.
- * @param stateRoot The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`.
- */
- function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external onlyOracleSigner {
- require(!hasVoted[blockNumber][msg.sender], "BeaconChainOracle.voteForBeaconChainStateRoot: Signer has already voted");
- require(beaconStateRootAtBlockNumber[blockNumber] == bytes32(0), "BeaconChainOracle.voteForBeaconChainStateRoot: State root already confirmed");
- // Mark the signer as having voted
- hasVoted[blockNumber][msg.sender] = true;
- // Increment the vote count for the state root
- stateRootVotes[blockNumber][stateRoot] += 1;
- // If the state root has enough votes, confirm it as the beacon state root
- if (stateRootVotes[blockNumber][stateRoot] >= threshold) {
- emit StateRootConfirmed(blockNumber, stateRoot);
- beaconStateRootAtBlockNumber[blockNumber] = stateRoot;
- // update latestConfirmedOracleBlockNumber if necessary
- if (blockNumber > latestConfirmedOracleBlockNumber) {
- latestConfirmedOracleBlockNumber = blockNumber;
- }
- }
- }
-
- /// @notice Internal function used for modifying the value of the `threshold` variable, used in the constructor and the `setThreshold` function
- function _setThreshold(uint256 _threshold) internal {
- require(_threshold >= MINIMUM_THRESHOLD, "BeaconChainOracle._setThreshold: cannot set threshold below MINIMUM_THRESHOLD");
- emit ThresholdModified(threshold, _threshold);
- threshold = _threshold;
- }
-
- /// @notice Internal counterpart of the `addOracleSigners` function. Also used in the constructor.
- function _addOracleSigners(address[] memory _oracleSigners) internal {
- for (uint256 i = 0; i < _oracleSigners.length;) {
- if (!isOracleSigner[_oracleSigners[i]]) {
- emit OracleSignerAdded(_oracleSigners[i]);
- isOracleSigner[_oracleSigners[i]] = true;
- totalOracleSigners += 1;
- }
- unchecked {
- ++i;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/contracts/pods/DelayedWithdrawalRouter.sol b/src/contracts/pods/DelayedWithdrawalRouter.sol
index 797218a738..9e57cfa451 100644
--- a/src/contracts/pods/DelayedWithdrawalRouter.sol
+++ b/src/contracts/pods/DelayedWithdrawalRouter.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
@@ -8,10 +8,13 @@ import "../interfaces/IEigenPodManager.sol";
import "../interfaces/IDelayedWithdrawalRouter.sol";
import "../permissions/Pausable.sol";
-contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, Pausable, IDelayedWithdrawalRouter{
- /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
- event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
-
+contract DelayedWithdrawalRouter is
+ Initializable,
+ OwnableUpgradeable,
+ ReentrancyGuardUpgradeable,
+ Pausable,
+ IDelayedWithdrawalRouter
+{
// index for flag that pauses withdrawals (i.e. 'delayedWithdrawal claims') when set
uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;
@@ -20,8 +23,8 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
* 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;
@@ -29,35 +32,47 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
/// @notice Mapping: user => struct storing all delayedWithdrawal info. Marked as internal with an external getter function named `userWithdrawals`
mapping(address => UserDelayedWithdrawals) internal _userWithdrawals;
- /// @notice event for delayedWithdrawal creation
- event DelayedWithdrawalCreated(address podOwner, address recipient, uint256 amount, uint256 index);
-
- /// @notice event for the claiming of delayedWithdrawals
- event DelayedWithdrawalsClaimed(address recipient, uint256 amountClaimed, uint256 delayedWithdrawalsCompleted);
-
/// @notice Modifier used to permission a function to only be called by the EigenPod of the specified `podOwner`
modifier onlyEigenPod(address podOwner) {
- require(address(eigenPodManager.getPod(podOwner)) == msg.sender, "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod");
+ require(
+ address(eigenPodManager.getPod(podOwner)) == msg.sender,
+ "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod"
+ );
_;
}
constructor(IEigenPodManager _eigenPodManager) {
- require(address(_eigenPodManager) != address(0), "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address");
+ require(
+ address(_eigenPodManager) != address(0),
+ "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address"
+ );
eigenPodManager = _eigenPodManager;
+ _disableInitializers();
}
- function initialize(address initOwner, IPauserRegistry _pauserRegistry, uint256 initPausedStatus, uint256 _withdrawalDelayBlocks) external initializer {
+ function initialize(
+ address initOwner,
+ IPauserRegistry _pauserRegistry,
+ uint256 initPausedStatus,
+ uint256 _withdrawalDelayBlocks
+ ) external initializer {
_transferOwnership(initOwner);
_initializePauser(_pauserRegistry, initPausedStatus);
_setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
}
- /**
+ /**
* @notice Creates a delayed withdrawal for `msg.value` to the `recipient`.
* @dev Only callable by the `podOwner`'s EigenPod contract.
*/
- function createDelayedWithdrawal(address podOwner, address recipient) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
- require(recipient != address(0), "DelayedWithdrawalRouter.createDelayedWithdrawal: recipient cannot be zero address");
+ function createDelayedWithdrawal(
+ address podOwner,
+ address recipient
+ ) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
+ require(
+ recipient != address(0),
+ "DelayedWithdrawalRouter.createDelayedWithdrawal: recipient cannot be zero address"
+ );
uint224 withdrawalAmount = uint224(msg.value);
if (withdrawalAmount != 0) {
DelayedWithdrawal memory delayedWithdrawal = DelayedWithdrawal({
@@ -65,7 +80,12 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
blockCreated: uint32(block.number)
});
_userWithdrawals[recipient].delayedWithdrawals.push(delayedWithdrawal);
- emit DelayedWithdrawalCreated(podOwner, recipient, withdrawalAmount, _userWithdrawals[recipient].delayedWithdrawals.length - 1);
+ emit DelayedWithdrawalCreated(
+ podOwner,
+ recipient,
+ withdrawalAmount,
+ _userWithdrawals[recipient].delayedWithdrawals.length - 1
+ );
}
}
@@ -73,15 +93,14 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
* @notice Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period.
* @param recipient The address to claim delayedWithdrawals for.
* @param maxNumberOfDelayedWithdrawalsToClaim Used to limit the maximum number of delayedWithdrawals to loop through claiming.
- * @dev
- * WARNING: Note that the caller of this function cannot control where the funds are sent, but they can control when the
+ * @dev
+ * WARNING: Note that the caller of this function cannot control where the funds are sent, but they can control when the
* funds are sent once the withdrawal becomes claimable.
*/
- function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfDelayedWithdrawalsToClaim)
- external
- nonReentrant
- onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
- {
+ function claimDelayedWithdrawals(
+ address recipient,
+ uint256 maxNumberOfDelayedWithdrawalsToClaim
+ ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
_claimDelayedWithdrawals(recipient, maxNumberOfDelayedWithdrawalsToClaim);
}
@@ -89,11 +108,9 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
* @notice Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period.
* @param maxNumberOfDelayedWithdrawalsToClaim Used to limit the maximum number of delayedWithdrawals to loop through claiming.
*/
- function claimDelayedWithdrawals(uint256 maxNumberOfDelayedWithdrawalsToClaim)
- external
- nonReentrant
- onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)
- {
+ function claimDelayedWithdrawals(
+ uint256 maxNumberOfDelayedWithdrawalsToClaim
+ ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
_claimDelayedWithdrawals(msg.sender, maxNumberOfDelayedWithdrawalsToClaim);
}
@@ -128,7 +145,9 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
uint256 firstNonClaimableWithdrawalIndex = userDelayedWithdrawalsLength;
for (uint256 i = 0; i < userDelayedWithdrawalsLength; i++) {
- DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[user].delayedWithdrawals[delayedWithdrawalsCompleted + i];
+ DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[user].delayedWithdrawals[
+ delayedWithdrawalsCompleted + i
+ ];
// check if delayedWithdrawal can be claimed. break the loop as soon as a delayedWithdrawal cannot be claimed
if (block.number < delayedWithdrawal.blockCreated + withdrawalDelayBlocks) {
firstNonClaimableWithdrawalIndex = i;
@@ -137,17 +156,22 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
}
uint256 numberOfClaimableWithdrawals = firstNonClaimableWithdrawalIndex;
DelayedWithdrawal[] memory claimableDelayedWithdrawals = new DelayedWithdrawal[](numberOfClaimableWithdrawals);
-
- if(numberOfClaimableWithdrawals != 0) {
+
+ if (numberOfClaimableWithdrawals != 0) {
for (uint256 i = 0; i < numberOfClaimableWithdrawals; i++) {
- claimableDelayedWithdrawals[i] = _userWithdrawals[user].delayedWithdrawals[delayedWithdrawalsCompleted + i];
+ claimableDelayedWithdrawals[i] = _userWithdrawals[user].delayedWithdrawals[
+ delayedWithdrawalsCompleted + i
+ ];
}
}
return claimableDelayedWithdrawals;
}
/// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
- function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (DelayedWithdrawal memory) {
+ function userDelayedWithdrawalByIndex(
+ address user,
+ uint256 index
+ ) external view returns (DelayedWithdrawal memory) {
return _userWithdrawals[user].delayedWithdrawals[index];
}
@@ -158,7 +182,8 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
/// @notice Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable
function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool) {
- return ((index >= _userWithdrawals[user].delayedWithdrawalsCompleted) && (block.number >= _userWithdrawals[user].delayedWithdrawals[index].blockCreated + withdrawalDelayBlocks));
+ return ((index >= _userWithdrawals[user].delayedWithdrawalsCompleted) &&
+ (block.number >= _userWithdrawals[user].delayedWithdrawals[index].blockCreated + withdrawalDelayBlocks));
}
/// @notice internal function used in both of the overloaded `claimDelayedWithdrawals` functions
@@ -167,9 +192,13 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
uint256 delayedWithdrawalsCompletedBefore = _userWithdrawals[recipient].delayedWithdrawalsCompleted;
uint256 _userWithdrawalsLength = _userWithdrawals[recipient].delayedWithdrawals.length;
uint256 i = 0;
- while (i < maxNumberOfDelayedWithdrawalsToClaim && (delayedWithdrawalsCompletedBefore + i) < _userWithdrawalsLength) {
+ while (
+ i < maxNumberOfDelayedWithdrawalsToClaim && (delayedWithdrawalsCompletedBefore + i) < _userWithdrawalsLength
+ ) {
// copy delayedWithdrawal from storage to memory
- DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[recipient].delayedWithdrawals[delayedWithdrawalsCompletedBefore + i];
+ DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[recipient].delayedWithdrawals[
+ delayedWithdrawalsCompletedBefore + i
+ ];
// check if delayedWithdrawal can be claimed. break the loop as soon as a delayedWithdrawal cannot be claimed
if (block.number < delayedWithdrawal.blockCreated + withdrawalDelayBlocks) {
break;
@@ -192,7 +221,10 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
/// @notice internal function for changing the value of `withdrawalDelayBlocks`. Also performs sanity check and emits an event.
function _setWithdrawalDelayBlocks(uint256 newValue) internal {
- require(newValue <= MAX_WITHDRAWAL_DELAY_BLOCKS, "DelayedWithdrawalRouter._setWithdrawalDelayBlocks: newValue too large");
+ require(
+ newValue <= MAX_WITHDRAWAL_DELAY_BLOCKS,
+ "DelayedWithdrawalRouter._setWithdrawalDelayBlocks: newValue too large"
+ );
emit WithdrawalDelayBlocksSet(withdrawalDelayBlocks, newValue);
withdrawalDelayBlocks = newValue;
}
@@ -203,4 +235,4 @@ contract DelayedWithdrawalRouter is Initializable, OwnableUpgradeable, Reentranc
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
-}
\ No newline at end of file
+}
diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol
index aa52666dab..2532b9381e 100644
--- a/src/contracts/pods/EigenPod.sol
+++ b/src/contracts/pods/EigenPod.sol
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+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 "@openzeppelin-upgrades/contracts/utils/AddressUpgradeable.sol";
+import "@openzeppelin-upgrades/contracts/utils/math/MathUpgradeable.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../libraries/BeaconChainProofs.sol";
import "../libraries/BytesLib.sol";
@@ -19,7 +21,7 @@ import "../interfaces/IPausable.sol";
import "./EigenPodPausingConstants.sol";
/**
- * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
+ * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice The main functionalities are:
@@ -29,17 +31,24 @@ import "./EigenPodPausingConstants.sol";
* pointed to this contract
* - updating aggregate balances in the EigenPodManager
* - withdrawing eth when withdrawals are initiated
+ * @notice This EigenPod Beacon Proxy implementation adheres to the current Capella consensus specs
* @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
* to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts
*/
contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
using BytesLib for bytes;
+ using SafeERC20 for IERC20;
+ using BeaconChainProofs for *;
// CONSTANTS + IMMUTABLES
+ // @notice Internal constant used in calculations, since the beacon chain stores balances in Gwei rather than wei
uint256 internal constant GWEI_TO_WEI = 1e9;
- /// @notice Maximum "staleness" of a Beacon Chain state root against which `verifyOvercommittedStake` may be proven. 7 days in blocks.
- uint256 internal constant VERIFY_OVERCOMMITTED_WINDOW_BLOCKS = 50400;
+ /**
+ * @notice Maximum "staleness" of a Beacon Chain state root against which `verifyBalanceUpdate` or `verifyWithdrawalCredentials` may be proven.
+ * We can't allow "stale" roots to be used for restaking as the validator may have been slashed in a more updated beacon state root.
+ */
+ uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours;
/// @notice This is the beacon chain deposit contract
IETHPOSDeposit public immutable ethPOS;
@@ -50,77 +59,71 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
/// @notice The single EigenPodManager for EigenLayer
IEigenPodManager public immutable eigenPodManager;
- /// @notice The amount of eth, in gwei, that is restaked per validator
- uint64 public immutable REQUIRED_BALANCE_GWEI;
+ ///@notice The maximum amount of ETH, in gwei, a validator can have restaked in the eigenlayer
+ uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
- /// @notice The amount of eth, in wei, that is restaked per ETH validator into EigenLayer
- uint256 public immutable REQUIRED_BALANCE_WEI;
+ /// @notice This is the genesis time of the beacon state, to help us calculate conversions between slot and timestamp
+ uint64 public immutable GENESIS_TIME;
+ // STORAGE VARIABLES
/// @notice The owner of this EigenPod
address public podOwner;
/**
- * @notice The latest block number at which the pod owner withdrew the balance of the pod.
- * @dev This variable is only updated when the `withdraw` function is called, which can only occur before `hasRestaked` is set to true for this pod.
- * Proofs for this pod are only valid against Beacon Chain state roots corresponding to blocks after the stored `mostRecentWithdrawalBlockNumber`.
+ * @notice The latest timestamp at which the pod owner withdrew the balance of the pod, via calling `withdrawBeforeRestaking`.
+ * @dev This variable is only updated when the `withdrawBeforeRestaking` function is called, which can only occur before `hasRestaked` is set to true for this pod.
+ * Proofs for this pod are only valid against Beacon Chain state roots corresponding to timestamps after the stored `mostRecentWithdrawalTimestamp`.
*/
- uint64 public mostRecentWithdrawalBlockNumber;
+ uint64 public mostRecentWithdrawalTimestamp;
- // STORAGE VARIABLES
- /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from the Beacon Chain but not from EigenLayer),
- uint64 public restakedExecutionLayerGwei;
+ /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from the Beacon Chain but not from EigenLayer),
+ uint64 public withdrawableRestakedExecutionLayerGwei;
/// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
bool public hasRestaked;
- /// @notice this is a mapping of validator indices to a Validator struct containing pertinent info about the validator
- mapping(uint40 => VALIDATOR_STATUS) public validatorStatus;
-
- /// @notice This is a mapping of validatorIndex to withdrawalIndex to whether or not they have proven a withdrawal for that index
- mapping(uint40 => mapping(uint64 => bool)) public provenPartialWithdrawal;
-
- /// @notice Emitted when an ETH validator stakes via this eigenPod
- event EigenPodStaked(bytes pubkey);
+ /// @notice This is a mapping of validatorPubkeyHash to timestamp to whether or not they have proven a withdrawal for that timestamp
+ mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal;
- /// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
- event ValidatorRestaked(uint40 validatorIndex);
+ /// @notice This is a mapping that tracks a validator's information by their pubkey hash
+ mapping(bytes32 => ValidatorInfo) internal _validatorPubkeyHashToInfo;
- /// @notice Emitted when an ETH validator is proven to have a balance less than `REQUIRED_BALANCE_GWEI` in the beacon chain
- event ValidatorOvercommitted(uint40 validatorIndex);
-
- /// @notice Emitted when an ETH validator is prove to have withdrawn from the beacon chain
- event FullWithdrawalRedeemed(uint40 validatorIndex, address indexed recipient, uint64 withdrawalAmountGwei);
+ /// @notice This variable tracks any ETH deposited into this contract via the `receive` fallback function
+ uint256 public nonBeaconChainETHBalanceWei;
- /// @notice Emitted when a partial withdrawal claim is successfully redeemed
- event PartialWithdrawalRedeemed(uint40 validatorIndex, address indexed recipient, uint64 partialWithdrawalAmountGwei);
+ /// @notice This variable tracks the total amount of partial withdrawals claimed via merkle proofs prior to a switch to ZK proofs for claiming partial withdrawals
+ uint64 public sumOfPartialWithdrawalsClaimedGwei;
- /// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
- event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
+ /// @notice Number of validators with proven withdrawal credentials, who do not have proven full withdrawals
+ uint256 activeValidatorCount;
- modifier onlyEigenPodManager {
+ modifier onlyEigenPodManager() {
require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager");
_;
}
- modifier onlyEigenPodOwner {
+ modifier onlyEigenPodOwner() {
require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner");
_;
}
- modifier onlyNotFrozen {
- require(!eigenPodManager.slasher().isFrozen(podOwner), "EigenPod.onlyNotFrozen: pod owner is frozen");
+ modifier hasNeverRestaked() {
+ require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");
_;
}
- modifier hasNeverRestaked {
- require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");
+ /// @notice checks that hasRestaked is set to true by calling activateRestaking()
+ modifier hasEnabledRestaking() {
+ require(hasRestaked, "EigenPod.hasEnabledRestaking: restaking is not enabled");
_;
}
- /// @notice Checks that `blockNumber` is strictly greater than the value stored in `mostRecentWithdrawalBlockNumber`
- modifier proofIsForValidBlockNumber(uint64 blockNumber) {
- require(blockNumber > mostRecentWithdrawalBlockNumber,
- "EigenPod.proofIsForValidBlockNumber: beacon chain proof must be for block number after mostRecentWithdrawalBlockNumber");
+ /// @notice Checks that `timestamp` is greater than or equal to the value stored in `mostRecentWithdrawalTimestamp`
+ modifier proofIsForValidTimestamp(uint64 timestamp) {
+ require(
+ timestamp >= mostRecentWithdrawalTimestamp,
+ "EigenPod.proofIsForValidTimestamp: beacon chain proof must be at or after mostRecentWithdrawalTimestamp"
+ );
_;
}
@@ -130,7 +133,10 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
* Modifier throws if the `indexed`th bit of `_paused` in the EigenPodManager is 1, i.e. if the `index`th pause switch is flipped.
*/
modifier onlyWhenNotPaused(uint8 index) {
- require(!IPausable(address(eigenPodManager)).paused(index), "EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager");
+ require(
+ !IPausable(address(eigenPodManager)).paused(index),
+ "EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"
+ );
_;
}
@@ -138,14 +144,14 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
IETHPOSDeposit _ethPOS,
IDelayedWithdrawalRouter _delayedWithdrawalRouter,
IEigenPodManager _eigenPodManager,
- uint256 _REQUIRED_BALANCE_WEI
+ uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ uint64 _GENESIS_TIME
) {
ethPOS = _ethPOS;
delayedWithdrawalRouter = _delayedWithdrawalRouter;
eigenPodManager = _eigenPodManager;
- REQUIRED_BALANCE_WEI = _REQUIRED_BALANCE_WEI;
- REQUIRED_BALANCE_GWEI = uint64(_REQUIRED_BALANCE_WEI / GWEI_TO_WEI);
- require(_REQUIRED_BALANCE_WEI % GWEI_TO_WEI == 0, "EigenPod.contructor: _REQUIRED_BALANCE_WEI is not a whole number of gwei");
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ GENESIS_TIME = _GENESIS_TIME;
_disableInitializers();
}
@@ -153,324 +159,654 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
function initialize(address _podOwner) external initializer {
require(_podOwner != address(0), "EigenPod.initialize: podOwner cannot be zero address");
podOwner = _podOwner;
+ /**
+ * From the M2 deployment onwards, we are requiring that pods deployed are by default enabled with restaking
+ * In prior deployments without proofs, EigenPods could be deployed with restaking disabled so as to allow
+ * simple (proof-free) withdrawals. However, this is no longer the case. Thus going forward, all pods are
+ * initialized with hasRestaked set to true.
+ */
+ hasRestaked = true;
+ emit RestakingActivated(podOwner);
}
- /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
- function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable onlyEigenPodManager {
- // stake on ethpos
- require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");
- ethPOS.deposit{value : 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot);
- emit EigenPodStaked(pubkey);
+ /// @notice payable fallback function that receives ether deposited to the eigenpods contract
+ receive() external payable {
+ nonBeaconChainETHBalanceWei += msg.value;
+ emit NonBeaconChainETHReceived(msg.value);
}
/**
- * @notice This function verifies that the withdrawal credentials of the podOwner are pointed to
- * this contract. It also verifies the current (not effective) balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
+ * @notice This function records an update (either increase or decrease) in a validator's balance.
+ * @param oracleTimestamp The oracleTimestamp whose state root the proof will be proven against.
+ * Must be within `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` of the current block.
+ * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
+ * @param stateRootProof proves a `beaconStateRoot` against a block root fetched from the oracle
+ * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
+ * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
+ * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
+ */
+ function verifyBalanceUpdates(
+ uint64 oracleTimestamp,
+ uint40[] calldata validatorIndices,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields
+ ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) {
+ require(
+ (validatorIndices.length == validatorFieldsProofs.length) && (validatorFieldsProofs.length == validatorFields.length),
+ "EigenPod.verifyBalanceUpdates: validatorIndices and proofs must be same length"
+ );
+
+ // Balance updates should not be "stale" (older than VERIFY_BALANCE_UPDATE_WINDOW_SECONDS)
+ require(
+ oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
+ "EigenPod.verifyBalanceUpdates: specified timestamp is too far in past"
+ );
+
+ // Verify passed-in beaconStateRoot against oracle-provided block root:
+ BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
+ latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
+ beaconStateRoot: stateRootProof.beaconStateRoot,
+ stateRootProof: stateRootProof.proof
+ });
+
+ int256 sharesDeltaGwei;
+ for (uint256 i = 0; i < validatorIndices.length; i++) {
+ sharesDeltaGwei += _verifyBalanceUpdate(
+ oracleTimestamp,
+ validatorIndices[i],
+ stateRootProof.beaconStateRoot,
+ validatorFieldsProofs[i], // Use validator fields proof because contains the effective balance
+ validatorFields[i]
+ );
+ }
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, sharesDeltaGwei * int256(GWEI_TO_WEI));
+ }
+
+ /**
+ * @notice This function records full and partial withdrawals on behalf of one or more of this EigenPod's validators
+ * @param oracleTimestamp is the timestamp of the oracle slot that the withdrawal is being proven against
+ * @param stateRootProof proves a `beaconStateRoot` against a block root fetched from the oracle
+ * @param withdrawalProofs proves several withdrawal-related values against the `beaconStateRoot`
+ * @param validatorFieldsProofs proves `validatorFields` against the `beaconStateRoot`
+ * @param withdrawalFields are the fields of the withdrawals being proven
+ * @param validatorFields are the fields of the validators being proven
+ */
+ function verifyAndProcessWithdrawals(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields,
+ bytes32[][] calldata withdrawalFields
+ ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL) {
+ require(
+ (validatorFields.length == validatorFieldsProofs.length) &&
+ (validatorFieldsProofs.length == withdrawalProofs.length) &&
+ (withdrawalProofs.length == withdrawalFields.length),
+ "EigenPod.verifyAndProcessWithdrawals: inputs must be same length"
+ );
+
+ // Verify passed-in beaconStateRoot against oracle-provided block root:
+ BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
+ latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
+ beaconStateRoot: stateRootProof.beaconStateRoot,
+ stateRootProof: stateRootProof.proof
+ });
+
+ VerifiedWithdrawal memory withdrawalSummary;
+ for (uint256 i = 0; i < withdrawalFields.length; i++) {
+ VerifiedWithdrawal memory verifiedWithdrawal = _verifyAndProcessWithdrawal(
+ stateRootProof.beaconStateRoot,
+ withdrawalProofs[i],
+ validatorFieldsProofs[i],
+ validatorFields[i],
+ withdrawalFields[i]
+ );
+
+ withdrawalSummary.amountToSendGwei += verifiedWithdrawal.amountToSendGwei;
+ withdrawalSummary.sharesDeltaGwei += verifiedWithdrawal.sharesDeltaGwei;
+ }
+
+ // If any withdrawals are eligible for immediate redemption, send to the pod owner via
+ // DelayedWithdrawalRouter
+ if (withdrawalSummary.amountToSendGwei != 0) {
+ _sendETH_AsDelayedWithdrawal(podOwner, withdrawalSummary.amountToSendGwei * GWEI_TO_WEI);
+ }
+ // If any withdrawals resulted in a change in the pod's shares, update the EigenPodManager
+ if (withdrawalSummary.sharesDeltaGwei != 0) {
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, withdrawalSummary.sharesDeltaGwei * int256(GWEI_TO_WEI));
+ }
+ }
+
+ /*******************************************************************************
+ EXTERNAL FUNCTIONS CALLABLE BY EIGENPOD OWNER
+ *******************************************************************************/
+
+ /**
+ * @notice This function verifies that the withdrawal credentials of validator(s) owned by the podOwner are pointed to
+ * this contract. It also verifies the effective balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
* root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
- * @param oracleBlockNumber is the Beacon Chain blockNumber whose state root the `proof` will be proven against.
- * @param validatorIndex is the index of the validator being proven, refer to consensus specs
- * @param proofs is the bytes that prove the ETH validator's balance and withdrawal credentials against a beacon chain state root
- * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
+ * @param oracleTimestamp is the Beacon Chain timestamp whose state root the `proof` will be proven against.
+ * @param stateRootProof proves a `beaconStateRoot` against a block root fetched from the oracle
+ * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
+ * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
+ * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
* for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
*/
- function verifyWithdrawalCredentialsAndBalance(
- uint64 oracleBlockNumber,
- uint40 validatorIndex,
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs calldata proofs,
- bytes32[] calldata validatorFields
+ function verifyWithdrawalCredentials(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ uint40[] calldata validatorIndices,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields
)
external
+ onlyEigenPodOwner
onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
- // check that the provided `oracleBlockNumber` is after the `mostRecentWithdrawalBlockNumber`
- proofIsForValidBlockNumber(oracleBlockNumber)
+ // check that the provided `oracleTimestamp` is after the `mostRecentWithdrawalTimestamp`
+ proofIsForValidTimestamp(oracleTimestamp)
+ // ensure that caller has previously enabled restaking by calling `activateRestaking()`
+ hasEnabledRestaking
{
- require(validatorStatus[validatorIndex] == VALIDATOR_STATUS.INACTIVE,
- "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials");
-
- require(validatorFields[BeaconChainProofs.VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX] == bytes32(_podWithdrawalCredentials()),
- "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod");
- // deserialize the balance field from the balanceRoot
- uint64 validatorCurrentBalanceGwei = BeaconChainProofs.getBalanceFromBalanceRoot(validatorIndex, proofs.balanceRoot);
-
- // make sure the balance is greater than the amount restaked per validator
- require(validatorCurrentBalanceGwei >= REQUIRED_BALANCE_GWEI,
- "EigenPod.verifyCorrectWithdrawalCredentials: ETH validator's balance must be greater than or equal to the restaked balance per validator");
+ require(
+ (validatorIndices.length == validatorFieldsProofs.length) &&
+ (validatorFieldsProofs.length == validatorFields.length),
+ "EigenPod.verifyWithdrawalCredentials: validatorIndices and proofs must be same length"
+ );
- // verify ETH validator proof
- bytes32 beaconStateRoot = eigenPodManager.getBeaconChainStateRoot(oracleBlockNumber);
- BeaconChainProofs.verifyValidatorFields(
- validatorIndex,
- beaconStateRoot,
- proofs.validatorFieldsProof,
- validatorFields
+ /**
+ * Withdrawal credential proof should not be "stale" (older than VERIFY_BALANCE_UPDATE_WINDOW_SECONDS) as we are doing a balance check here
+ * The validator container persists as the state evolves and even after the validator exits. So we can use a more "fresh" credential proof within
+ * the VERIFY_BALANCE_UPDATE_WINDOW_SECONDS window, not just the first proof where the validator container is registered in the state.
+ */
+ require(
+ oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
+ "EigenPod.verifyWithdrawalCredentials: specified timestamp is too far in past"
);
- // verify ETH validator's current balance, which is stored in the `balances` container of the beacon state
- BeaconChainProofs.verifyValidatorBalance(
- validatorIndex,
- beaconStateRoot,
- proofs.validatorBalanceProof,
- proofs.balanceRoot
+ // Verify passed-in beaconStateRoot against oracle-provided block root:
+ BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
+ latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
+ beaconStateRoot: stateRootProof.beaconStateRoot,
+ stateRootProof: stateRootProof.proof
+ });
+
+ uint256 totalAmountToBeRestakedWei;
+ for (uint256 i = 0; i < validatorIndices.length; i++) {
+ totalAmountToBeRestakedWei += _verifyWithdrawalCredentials(
+ oracleTimestamp,
+ stateRootProof.beaconStateRoot,
+ validatorIndices[i],
+ validatorFieldsProofs[i],
+ validatorFields[i]
+ );
+ }
+
+ // Update the EigenPodManager on this pod's new balance
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, int256(totalAmountToBeRestakedWei));
+ }
+
+ /// @notice Called by the pod owner to withdraw the nonBeaconChainETHBalanceWei
+ function withdrawNonBeaconChainETHBalanceWei(
+ address recipient,
+ uint256 amountToWithdraw
+ ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
+ require(
+ amountToWithdraw <= nonBeaconChainETHBalanceWei,
+ "EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei"
);
- // set the status to active
- validatorStatus[validatorIndex] = VALIDATOR_STATUS.ACTIVE;
+ nonBeaconChainETHBalanceWei -= amountToWithdraw;
+ emit NonBeaconChainETHWithdrawn(recipient, amountToWithdraw);
+ _sendETH_AsDelayedWithdrawal(recipient, amountToWithdraw);
+ }
- // Sets "hasRestaked" to true if it hasn't been set yet.
- if (!hasRestaked) {
- hasRestaked = true;
+ /// @notice called by owner of a pod to remove any ERC20s deposited in the pod
+ function recoverTokens(
+ IERC20[] memory tokenList,
+ uint256[] memory amountsToWithdraw,
+ address recipient
+ ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
+ require(
+ tokenList.length == amountsToWithdraw.length,
+ "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
+ );
+ for (uint256 i = 0; i < tokenList.length; i++) {
+ tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]);
}
+ }
- emit ValidatorRestaked(validatorIndex);
+ /**
+ * @notice Called by the pod owner to activate restaking by withdrawing
+ * all existing ETH from the pod and preventing further withdrawals via
+ * "withdrawBeforeRestaking()"
+ */
+ function activateRestaking()
+ external
+ onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
+ onlyEigenPodOwner
+ hasNeverRestaked
+ {
+ hasRestaked = true;
+ _processWithdrawalBeforeRestaking(podOwner);
- // virtually deposit REQUIRED_BALANCE_WEI for new ETH validator
- eigenPodManager.restakeBeaconChainETH(podOwner, REQUIRED_BALANCE_WEI);
+ emit RestakingActivated(podOwner);
}
+ /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
+ function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked {
+ _processWithdrawalBeforeRestaking(podOwner);
+ }
+
+ /*******************************************************************************
+ EXTERNAL FUNCTIONS CALLABLE BY EIGENPODMANAGER
+ *******************************************************************************/
+
+ /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
+ function stake(
+ bytes calldata pubkey,
+ bytes calldata signature,
+ bytes32 depositDataRoot
+ ) external payable onlyEigenPodManager {
+ // stake on ethpos
+ require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");
+ ethPOS.deposit{value: 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot);
+ emit EigenPodStaked(pubkey);
+ }
+
+ /**
+ * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
+ * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
+ * @dev The podOwner must have already proved sufficient withdrawals, so that this pod's `withdrawableRestakedExecutionLayerGwei` exceeds the
+ * `amountWei` input (when converted to GWEI).
+ * @dev Reverts if `amountWei` is not a whole Gwei amount
+ */
+ function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external onlyEigenPodManager {
+ require(amountWei % GWEI_TO_WEI == 0, "EigenPod.withdrawRestakedBeaconChainETH: amountWei must be a whole Gwei amount");
+ uint64 amountGwei = uint64(amountWei / GWEI_TO_WEI);
+ require(amountGwei <= withdrawableRestakedExecutionLayerGwei, "EigenPod.withdrawRestakedBeaconChainETH: amountGwei exceeds withdrawableRestakedExecutionLayerGwei");
+ withdrawableRestakedExecutionLayerGwei -= amountGwei;
+ emit RestakedBeaconChainETHWithdrawn(recipient, amountWei);
+ // transfer ETH from pod to `recipient` directly
+ _sendETH(recipient, amountWei);
+ }
+
+ /*******************************************************************************
+ INTERNAL FUNCTIONS
+ *******************************************************************************/
/**
- * @notice This function records an overcommitment of stake to EigenLayer on behalf of a certain ETH validator.
- * If successful, the overcommitted balance is penalized (available for withdrawal whenever the pod's balance allows).
- * The ETH validator's shares in the enshrined beaconChainETH strategy are also removed from the StrategyManager and undelegated.
- * @param oracleBlockNumber The oracleBlockNumber whose state root the `proof` will be proven against.
- * Must be within `VERIFY_OVERCOMMITTED_WINDOW_BLOCKS` of the current block.
- * @param validatorIndex is the index of the validator being proven, refer to consensus specs
- * @param proofs is the proof of the validator's balance and validatorFields in the balance tree and the balanceRoot to prove for
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
- * the StrategyManager in case it must be removed from the list of the podOwner's strategies
+ * @notice internal function that proves an individual validator's withdrawal credentials
+ * @param oracleTimestamp is the timestamp whose state root the `proof` will be proven against.
+ * @param validatorIndex is the index of the validator being proven
+ * @param validatorFieldsProof is the bytes that prove the ETH validator's withdrawal credentials against a beacon chain state root
* @param validatorFields are the fields of the "Validator Container", refer to consensus specs
- * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
*/
- function verifyOvercommittedStake(
+ function _verifyWithdrawalCredentials(
+ uint64 oracleTimestamp,
+ bytes32 beaconStateRoot,
uint40 validatorIndex,
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs calldata proofs,
- bytes32[] calldata validatorFields,
- uint256 beaconChainETHStrategyIndex,
- uint64 oracleBlockNumber
- ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_OVERCOMMITTED) {
- // ensure that the blockNumber being proven against is not "too stale", i.e. that the validator was *recently* overcommitted.
- require(oracleBlockNumber + VERIFY_OVERCOMMITTED_WINDOW_BLOCKS >= block.number,
- "EigenPod.verifyOvercommittedStake: specified blockNumber is too far in past");
-
- require(validatorStatus[validatorIndex] == VALIDATOR_STATUS.ACTIVE, "EigenPod.verifyOvercommittedStake: Validator not active");
+ bytes calldata validatorFieldsProof,
+ bytes32[] calldata validatorFields
+ ) internal returns (uint256) {
+ bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
+ ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
+
+ // Withdrawal credential proofs should only be processed for "INACTIVE" validators
+ require(
+ validatorInfo.status == VALIDATOR_STATUS.INACTIVE,
+ "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials"
+ );
- // deserialize the balance field from the balanceRoot
- uint64 validatorCurrentBalanceGwei = BeaconChainProofs.getBalanceFromBalanceRoot(validatorIndex, proofs.balanceRoot);
+ // Ensure the `validatorFields` we're proving have the correct withdrawal credentials
+ require(
+ validatorFields.getWithdrawalCredentials() == bytes32(_podWithdrawalCredentials()),
+ "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"
+ );
- require(validatorCurrentBalanceGwei < REQUIRED_BALANCE_GWEI,
- "EigenPod.verifyOvercommittedStake: validator's balance must be less than the restaked balance per validator");
-
- // verify ETH validator proof
- bytes32 beaconStateRoot = eigenPodManager.getBeaconChainStateRoot(oracleBlockNumber);
-
/**
- * If validator's balance is zero, then either they have fully withdrawn or they have been slashed down zero.
- * If the validator *has* been slashed, then this function can proceed. If they have *not* been slashed, then
- * the `verifyAndProcessWithdrawal` function should be called instead.
+ * Deserialize the balance field from the Validator struct. Note that this is the "effective" balance of the validator
+ * rather than the current balance. Effective balance is generated via a hystersis function such that an effective
+ * balance, always a multiple of 1 ETH, will only lower to the next multiple of 1 ETH if the current balance is less
+ * than 0.25 ETH below their current effective balance. For example, if the effective balance is 31ETH, it only falls to
+ * 30ETH when the true balance falls below 30.75ETH. Thus in the worst case, the effective balance is overestimating the
+ * actual validator balance by 0.25 ETH.
*/
- if (validatorCurrentBalanceGwei == 0) {
- uint64 slashedStatus = Endian.fromLittleEndianUint64(validatorFields[BeaconChainProofs.VALIDATOR_SLASHED_INDEX]);
- require(slashedStatus == 1, "EigenPod.verifyOvercommittedStake: Validator must be slashed to be overcommitted");
- //Verify the validator fields, which contain the validator's slashed status
- BeaconChainProofs.verifyValidatorFields(
- validatorIndex,
- beaconStateRoot,
- proofs.validatorFieldsProof,
- validatorFields
- );
+ uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
+
+ // Verify passed-in validatorFields against verified beaconStateRoot:
+ BeaconChainProofs.verifyValidatorFields({
+ beaconStateRoot: beaconStateRoot,
+ validatorFields: validatorFields,
+ validatorFieldsProof: validatorFieldsProof,
+ validatorIndex: validatorIndex
+ });
+
+ // Proofs complete - update this validator's status, record its proven balance, and save in state:
+ activeValidatorCount++;
+ validatorInfo.status = VALIDATOR_STATUS.ACTIVE;
+ validatorInfo.validatorIndex = validatorIndex;
+ validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
+
+ if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
+ validatorInfo.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ } else {
+ validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei;
}
- // verify ETH validator's current balance, which is stored in the `balances` container of the beacon state
- BeaconChainProofs.verifyValidatorBalance(
- validatorIndex,
- beaconStateRoot,
- proofs.validatorBalanceProof,
- proofs.balanceRoot
+ _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
+
+ emit ValidatorRestaked(validatorIndex);
+ emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, validatorInfo.restakedBalanceGwei);
+
+ return validatorInfo.restakedBalanceGwei * GWEI_TO_WEI;
+ }
+
+ function _verifyBalanceUpdate(
+ uint64 oracleTimestamp,
+ uint40 validatorIndex,
+ bytes32 beaconStateRoot,
+ bytes calldata validatorFieldsProof,
+ bytes32[] calldata validatorFields
+ ) internal returns(int256 sharesDeltaGwei){
+ uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
+ bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
+ ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
+
+ // 1. Balance updates should be more recent than the most recent update
+ require(
+ validatorInfo.mostRecentBalanceUpdateTimestamp < oracleTimestamp,
+ "EigenPod.verifyBalanceUpdate: Validators balance has already been updated for this timestamp"
+ );
+
+ // 2. Balance updates should only be performed on "ACTIVE" validators
+ require(
+ validatorInfo.status == VALIDATOR_STATUS.ACTIVE,
+ "EigenPod.verifyBalanceUpdate: Validator not active"
);
- // mark the ETH validator as overcommitted
- validatorStatus[validatorIndex] = VALIDATOR_STATUS.OVERCOMMITTED;
+ // 3. Balance updates should only be made before a validator is fully withdrawn.
+ // -- A withdrawable validator may not have withdrawn yet, so we require their balance is nonzero
+ // -- A fully withdrawn validator should withdraw via verifyAndProcessWithdrawals
+ if (validatorFields.getWithdrawableEpoch() <= _timestampToEpoch(oracleTimestamp)) {
+ require(
+ validatorEffectiveBalanceGwei > 0,
+ "EigenPod.verifyBalanceUpdate: validator is withdrawable but has not withdrawn"
+ );
+ }
- emit ValidatorOvercommitted(validatorIndex);
+ // Verify passed-in validatorFields against verified beaconStateRoot:
+ BeaconChainProofs.verifyValidatorFields({
+ beaconStateRoot: beaconStateRoot,
+ validatorFields: validatorFields,
+ validatorFieldsProof: validatorFieldsProof,
+ validatorIndex: validatorIndex
+ });
- // remove and undelegate shares in EigenLayer
- eigenPodManager.recordOvercommittedBeaconChainETH(podOwner, beaconChainETHStrategyIndex, REQUIRED_BALANCE_WEI);
+ // Done with proofs! Now update the validator's balance and send to the EigenPodManager if needed
+
+ uint64 currentRestakedBalanceGwei = validatorInfo.restakedBalanceGwei;
+ uint64 newRestakedBalanceGwei;
+ if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
+ newRestakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ } else {
+ newRestakedBalanceGwei = validatorEffectiveBalanceGwei;
+ }
+
+ // Update validator balance and timestamp, and save to state:
+ validatorInfo.restakedBalanceGwei = newRestakedBalanceGwei;
+ validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
+ _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
+
+ // If our new and old balances differ, calculate the delta and send to the EigenPodManager
+ if (newRestakedBalanceGwei != currentRestakedBalanceGwei) {
+ emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, newRestakedBalanceGwei);
+
+ sharesDeltaGwei = _calculateSharesDelta({
+ newAmountGwei: newRestakedBalanceGwei,
+ previousAmountGwei: currentRestakedBalanceGwei
+ });
+ }
}
- /**
- * @notice This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
- * @param withdrawalProofs is the information needed to check the veracity of the block number and withdrawal being proven
- * @param validatorFieldsProof is the information needed to check the veracity of the validator fields being proven
- * @param withdrawalFields are the fields of the withdrawal being proven
- * @param validatorFields are the fields of the validator being proven
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
- * the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies
- * @param oracleBlockNumber is the Beacon Chain blockNumber whose state root the `proof` will be proven against.
- */
- function verifyAndProcessWithdrawal(
- BeaconChainProofs.WithdrawalProofs calldata withdrawalProofs,
+ function _verifyAndProcessWithdrawal(
+ bytes32 beaconStateRoot,
+ BeaconChainProofs.WithdrawalProof calldata withdrawalProof,
bytes calldata validatorFieldsProof,
bytes32[] calldata validatorFields,
- bytes32[] calldata withdrawalFields,
- uint256 beaconChainETHStrategyIndex,
- uint64 oracleBlockNumber
+ bytes32[] calldata withdrawalFields
)
- external
- onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL)
- onlyNotFrozen
- /**
- * Check that the provided block number being proven against is after the `mostRecentWithdrawalBlockNumber`.
+ internal
+ /**
+ * Check that the provided timestamp being proven against is after the `mostRecentWithdrawalTimestamp`.
* Without this check, there is an edge case where a user proves a past withdrawal for a validator whose funds they already withdrew,
* as a way to "withdraw the same funds twice" without providing adequate proof.
- * Note that this check is not made using the oracleBlockNumber as in the `verifyWithdrawalCredentials` proof; instead this proof
- * proof is made for the block number of the withdrawal, which may be within 8192 slots of the oracleBlockNumber.
+ * Note that this check is not made using the oracleTimestamp as in the `verifyWithdrawalCredentials` proof; instead this proof
+ * proof is made for the timestamp of the withdrawal, which may be within SLOTS_PER_HISTORICAL_ROOT slots of the oracleTimestamp.
* This difference in modifier usage is OK, since it is still not possible to `verifyAndProcessWithdrawal` against a slot that occurred
* *prior* to the proof provided in the `verifyWithdrawalCredentials` function.
*/
- proofIsForValidBlockNumber(Endian.fromLittleEndianUint64(withdrawalProofs.blockNumberRoot))
+ proofIsForValidTimestamp(withdrawalProof.getWithdrawalTimestamp())
+ returns (VerifiedWithdrawal memory)
{
+ uint64 withdrawalTimestamp = withdrawalProof.getWithdrawalTimestamp();
+ bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
+
/**
- * If the validator status is inactive, then withdrawal credentials were never verified for the validator,
- * and thus we cannot know that the validator is related to this EigenPod at all!
+ * Withdrawal processing should only be performed for "ACTIVE" or "WITHDRAWN" validators.
+ * (WITHDRAWN is allowed because technically you can deposit to a validator even after it exits)
*/
- uint40 validatorIndex = uint40(Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
-
- require(validatorStatus[validatorIndex] != VALIDATOR_STATUS.INACTIVE,
- "EigenPod.verifyOvercommittedStake: Validator never proven to have withdrawal credentials pointed to this contract");
+ require(
+ _validatorPubkeyHashToInfo[validatorPubkeyHash].status != VALIDATOR_STATUS.INACTIVE,
+ "EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract"
+ );
+
+ // Ensure we don't process the same withdrawal twice
+ require(
+ !provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp],
+ "EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp"
+ );
- // fetch the beacon state root for the specified block
- bytes32 beaconStateRoot = eigenPodManager.getBeaconChainStateRoot(oracleBlockNumber);
+ provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp] = true;
- // Verifying the withdrawal as well as the slot
- BeaconChainProofs.verifyWithdrawalProofs(beaconStateRoot, withdrawalProofs, withdrawalFields);
- // Verifying the validator fields, specifically the withdrawable epoch
- BeaconChainProofs.verifyValidatorFields(validatorIndex, beaconStateRoot, validatorFieldsProof, validatorFields);
+ // Verifying the withdrawal against verified beaconStateRoot:
+ BeaconChainProofs.verifyWithdrawal({
+ beaconStateRoot: beaconStateRoot,
+ withdrawalFields: withdrawalFields,
+ withdrawalProof: withdrawalProof,
+ denebForkTimestamp: eigenPodManager.denebForkTimestamp()
+ });
- uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
+ uint40 validatorIndex = withdrawalFields.getValidatorIndex();
- //check if the withdrawal occured after mostRecentWithdrawalBlockNumber
- uint64 slot = Endian.fromLittleEndianUint64(withdrawalProofs.slotRoot);
+ // Verify passed-in validatorFields against verified beaconStateRoot:
+ BeaconChainProofs.verifyValidatorFields({
+ beaconStateRoot: beaconStateRoot,
+ validatorFields: validatorFields,
+ validatorFieldsProof: validatorFieldsProof,
+ validatorIndex: validatorIndex
+ });
+ uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
+
/**
- * if the validator's withdrawable epoch is less than or equal to the slot's epoch, then the validator has fully withdrawn because
- * a full withdrawal is only processable after the withdrawable epoch has passed.
+ * If the withdrawal's epoch comes after the validator's "withdrawable epoch," we know the validator
+ * has fully withdrawn, and we process this as a full withdrawal.
*/
- // reference: uint64 withdrawableEpoch = Endian.fromLittleEndianUint64(validatorFields[BeaconChainProofs.VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]);
- if (Endian.fromLittleEndianUint64(validatorFields[BeaconChainProofs.VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]) <= slot/BeaconChainProofs.SLOTS_PER_EPOCH) {
- _processFullWithdrawal(withdrawalAmountGwei, validatorIndex, beaconChainETHStrategyIndex, podOwner, validatorStatus[validatorIndex]);
+ if (withdrawalProof.getWithdrawalEpoch() >= validatorFields.getWithdrawableEpoch()) {
+ return
+ _processFullWithdrawal(
+ validatorIndex,
+ validatorPubkeyHash,
+ withdrawalTimestamp,
+ podOwner,
+ withdrawalAmountGwei,
+ _validatorPubkeyHashToInfo[validatorPubkeyHash]
+ );
} else {
- _processPartialWithdrawal(slot, withdrawalAmountGwei, validatorIndex, podOwner);
+ return
+ _processPartialWithdrawal(
+ validatorIndex,
+ withdrawalTimestamp,
+ podOwner,
+ withdrawalAmountGwei
+ );
}
}
function _processFullWithdrawal(
- uint64 withdrawalAmountGwei,
uint40 validatorIndex,
- uint256 beaconChainETHStrategyIndex,
+ bytes32 validatorPubkeyHash,
+ uint64 withdrawalTimestamp,
address recipient,
- VALIDATOR_STATUS status
- ) internal {
- uint256 amountToSend;
-
- // if the validator has not previously been proven to be "overcommitted"
- if (status == VALIDATOR_STATUS.ACTIVE) {
- // if the withdrawal amount is greater than the REQUIRED_BALANCE_GWEI (i.e. the amount restaked on EigenLayer, per ETH validator)
- if (withdrawalAmountGwei >= REQUIRED_BALANCE_GWEI) {
- // then the excess is immediately withdrawable
- amountToSend = uint256(withdrawalAmountGwei - REQUIRED_BALANCE_GWEI) * uint256(GWEI_TO_WEI);
- // and the extra execution layer ETH in the contract is REQUIRED_BALANCE_GWEI, which must be withdrawn through EigenLayer's normal withdrawal process
- restakedExecutionLayerGwei += REQUIRED_BALANCE_GWEI;
- } else {
- // otherwise, just use the full withdrawal amount to continue to "back" the podOwner's remaining shares in EigenLayer (i.e. none is instantly withdrawable)
- restakedExecutionLayerGwei += withdrawalAmountGwei;
- // remove and undelegate 'extra' (i.e. "overcommitted") shares in EigenLayer
- eigenPodManager.recordOvercommittedBeaconChainETH(podOwner, beaconChainETHStrategyIndex, uint256(REQUIRED_BALANCE_GWEI - withdrawalAmountGwei) * GWEI_TO_WEI);
- }
- // if the validator *has* previously been proven to be "overcommitted"
- } else if (status == VALIDATOR_STATUS.OVERCOMMITTED) {
- // if the withdrawal amount is greater than the REQUIRED_BALANCE_GWEI (i.e. the amount restaked on EigenLayer, per ETH validator)
- if (withdrawalAmountGwei >= REQUIRED_BALANCE_GWEI) {
- // then the excess is immediately withdrawable
- amountToSend = uint256(withdrawalAmountGwei - REQUIRED_BALANCE_GWEI) * uint256(GWEI_TO_WEI);
- // and the extra execution layer ETH in the contract is REQUIRED_BALANCE_GWEI, which must be withdrawn through EigenLayer's normal withdrawal process
- restakedExecutionLayerGwei += REQUIRED_BALANCE_GWEI;
- /**
- * since in `verifyOvercommittedStake` the podOwner's beaconChainETH shares are decremented by `REQUIRED_BALANCE_WEI`, we must reverse the process here,
- * in order to allow the podOwner to complete their withdrawal through EigenLayer's normal withdrawal process
- */
- eigenPodManager.restakeBeaconChainETH(podOwner, REQUIRED_BALANCE_WEI);
- } else {
- // otherwise, just use the full withdrawal amount to continue to "back" the podOwner's remaining shares in EigenLayer (i.e. none is instantly withdrawable)
- restakedExecutionLayerGwei += withdrawalAmountGwei;
- /**
- * since in `verifyOvercommittedStake` the podOwner's beaconChainETH shares are decremented by `REQUIRED_BALANCE_WEI`, we must reverse the process here,
- * in order to allow the podOwner to complete their withdrawal through EigenLayer's normal withdrawal process
- */
- eigenPodManager.restakeBeaconChainETH(podOwner, uint256(withdrawalAmountGwei) * GWEI_TO_WEI);
- }
- // If the validator status is withdrawn, they have already processed their ETH withdrawal
- } else {
- revert("EigenPod.verifyBeaconChainFullWithdrawal: VALIDATOR_STATUS is WITHDRAWN or invalid VALIDATOR_STATUS");
+ uint64 withdrawalAmountGwei,
+ ValidatorInfo memory validatorInfo
+ ) internal returns (VerifiedWithdrawal memory) {
+
+ /**
+ * First, determine withdrawal amounts. We need to know:
+ * 1. How much can be withdrawn immediately
+ * 2. How much needs to be withdrawn via the EigenLayer withdrawal queue
+ */
+
+ uint64 amountToQueueGwei;
+
+ if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
+ amountToQueueGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ } else {
+ amountToQueueGwei = withdrawalAmountGwei;
}
- // set the ETH validator status to withdrawn
- validatorStatus[validatorIndex] = VALIDATOR_STATUS.WITHDRAWN;
+ /**
+ * If the withdrawal is for more than the max per-validator balance, we mark
+ * the max as "withdrawable" via the queue, and withdraw the excess immediately
+ */
- emit FullWithdrawalRedeemed(validatorIndex, recipient, withdrawalAmountGwei);
+ VerifiedWithdrawal memory verifiedWithdrawal;
+ verifiedWithdrawal.amountToSendGwei = uint256(withdrawalAmountGwei - amountToQueueGwei);
+ withdrawableRestakedExecutionLayerGwei += amountToQueueGwei;
+
+ /**
+ * Next, calculate the change in number of shares this validator is "backing":
+ * - Anything that needs to go through the withdrawal queue IS backed
+ * - Anything immediately withdrawn IS NOT backed
+ *
+ * This means that this validator is currently backing `amountToQueueGwei` shares.
+ */
+
+ verifiedWithdrawal.sharesDeltaGwei = _calculateSharesDelta({
+ newAmountGwei: amountToQueueGwei,
+ previousAmountGwei: validatorInfo.restakedBalanceGwei
+ });
+
+ /**
+ * Finally, the validator is fully withdrawn. Update their status and place in state:
+ */
- // send ETH to the `recipient`, if applicable
- if (amountToSend != 0) {
- _sendETH(recipient, amountToSend);
+ if (validatorInfo.status != VALIDATOR_STATUS.WITHDRAWN) {
+ activeValidatorCount--;
+ validatorInfo.status = VALIDATOR_STATUS.WITHDRAWN;
}
+
+ validatorInfo.restakedBalanceGwei = 0;
+ _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
+
+ emit FullWithdrawalRedeemed(validatorIndex, withdrawalTimestamp, recipient, withdrawalAmountGwei);
+
+ return verifiedWithdrawal;
+ }
+
+ function _processPartialWithdrawal(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address recipient,
+ uint64 partialWithdrawalAmountGwei
+ ) internal returns (VerifiedWithdrawal memory) {
+ emit PartialWithdrawalRedeemed(
+ validatorIndex,
+ withdrawalTimestamp,
+ recipient,
+ partialWithdrawalAmountGwei
+ );
+
+ sumOfPartialWithdrawalsClaimedGwei += partialWithdrawalAmountGwei;
+
+ // For partial withdrawals, the withdrawal amount is immediately sent to the pod owner
+ return
+ VerifiedWithdrawal({
+ amountToSendGwei: uint256(partialWithdrawalAmountGwei),
+ sharesDeltaGwei: 0
+ });
+ }
+
+ function _processWithdrawalBeforeRestaking(address _podOwner) internal {
+ mostRecentWithdrawalTimestamp = uint32(block.timestamp);
+ nonBeaconChainETHBalanceWei = 0;
+ _sendETH_AsDelayedWithdrawal(_podOwner, address(this).balance);
+ }
+
+ function _sendETH(address recipient, uint256 amountWei) internal {
+ Address.sendValue(payable(recipient), amountWei);
+ }
+
+ function _sendETH_AsDelayedWithdrawal(address recipient, uint256 amountWei) internal {
+ delayedWithdrawalRouter.createDelayedWithdrawal{value: amountWei}(podOwner, recipient);
}
- function _processPartialWithdrawal(uint64 withdrawalHappenedSlot, uint64 partialWithdrawalAmountGwei, uint40 validatorIndex, address recipient) internal {
- require(!provenPartialWithdrawal[validatorIndex][withdrawalHappenedSlot], "EigenPod._processPartialWithdrawal: partial withdrawal has already been proven for this slot");
+ function _podWithdrawalCredentials() internal view returns (bytes memory) {
+ return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(this));
+ }
- provenPartialWithdrawal[validatorIndex][withdrawalHappenedSlot] = true;
- emit PartialWithdrawalRedeemed(validatorIndex, recipient, partialWithdrawalAmountGwei);
+ ///@notice Calculates the pubkey hash of a validator's pubkey as per SSZ spec
+ function _calculateValidatorPubkeyHash(bytes memory validatorPubkey) internal pure returns (bytes32){
+ require(validatorPubkey.length == 48, "EigenPod._calculateValidatorPubkeyHash must be a 48-byte BLS public key");
+ return sha256(abi.encodePacked(validatorPubkey, bytes16(0)));
+ }
- // send the ETH to the `recipient`
- _sendETH(recipient, uint256(partialWithdrawalAmountGwei) * uint256(GWEI_TO_WEI));
+ /**
+ * Calculates delta between two share amounts and returns as an int256
+ */
+ function _calculateSharesDelta(uint64 newAmountGwei, uint64 previousAmountGwei) internal pure returns (int256) {
+ return
+ int256(uint256(newAmountGwei)) - int256(uint256(previousAmountGwei));
}
/**
- * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
- * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
- * @dev Called during withdrawal or slashing.
+ * @dev Converts a timestamp to a beacon chain epoch by calculating the number of
+ * seconds since genesis, and dividing by seconds per epoch.
+ * reference: https://github.com/ethereum/consensus-specs/blob/ce240ca795e257fc83059c4adfd591328c7a7f21/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot
*/
- function withdrawRestakedBeaconChainETH(
- address recipient,
- uint256 amountWei
- )
- external
- onlyEigenPodManager
- {
- // reduce the restakedExecutionLayerGwei
- restakedExecutionLayerGwei -= uint64(amountWei / GWEI_TO_WEI);
+ function _timestampToEpoch(uint64 timestamp) internal view returns (uint64) {
+ require(timestamp >= GENESIS_TIME, "EigenPod._timestampToEpoch: timestamp is before genesis");
+ return (timestamp - GENESIS_TIME) / BeaconChainProofs.SECONDS_PER_EPOCH;
+ }
- emit RestakedBeaconChainETHWithdrawn(recipient, amountWei);
+ /*******************************************************************************
+ VIEW FUNCTIONS
+ *******************************************************************************/
- // transfer ETH from pod to `recipient`
- _sendETH(recipient, amountWei);
+ function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory) {
+ return _validatorPubkeyHashToInfo[validatorPubkeyHash];
}
- /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
- function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked {
- mostRecentWithdrawalBlockNumber = uint32(block.number);
- _sendETH(podOwner, address(this).balance);
+ /// @notice Returns the validatorInfo for a given validatorPubkey
+ function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory) {
+ return _validatorPubkeyHashToInfo[_calculateValidatorPubkeyHash(validatorPubkey)];
}
- // INTERNAL FUNCTIONS
- function _podWithdrawalCredentials() internal view returns(bytes memory) {
- return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(this));
+ function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].status;
}
- function _sendETH(address recipient, uint256 amountWei) internal {
- delayedWithdrawalRouter.createDelayedWithdrawal{value: amountWei}(podOwner, recipient);
+ /// @notice Returns the validator status for a given validatorPubkey
+ function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS) {
+ bytes32 validatorPubkeyHash = _calculateValidatorPubkeyHash(validatorPubkey);
+ return _validatorPubkeyHashToInfo[validatorPubkeyHash].status;
}
+
/**
* @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[46] private __gap;
-}
\ No newline at end of file
+ uint256[44] private __gap;
+}
diff --git a/src/contracts/pods/EigenPodManager.sol b/src/contracts/pods/EigenPodManager.sol
index 2b062a781b..f28f9fc7b2 100644
--- a/src/contracts/pods/EigenPodManager.sol
+++ b/src/contracts/pods/EigenPodManager.sol
@@ -1,23 +1,16 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/utils/Create2.sol";
-import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
-import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
-
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
-
-import "../interfaces/IStrategyManager.sol";
-import "../interfaces/IDelegationManager.sol";
-import "../interfaces/IEigenPodManager.sol";
-import "../interfaces/IETHPOSDeposit.sol";
-import "../interfaces/IEigenPod.sol";
+import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
import "../interfaces/IBeaconChainOracle.sol";
import "../permissions/Pausable.sol";
import "./EigenPodPausingConstants.sol";
+import "./EigenPodManagerStorage.sol";
/**
* @title The contract used for creating and managing EigenPods
@@ -26,105 +19,79 @@ import "./EigenPodPausingConstants.sol";
* @notice The main functionalities are:
* - creating EigenPods
* - staking for new validators on EigenPods
- * - keeping track of the balances of all validators of EigenPods, and their stake in EigenLayer
- * - withdrawing eth when withdrawals are initiated
+ * - keeping track of the restaked balances of all EigenPod owners
+ * - withdrawing eth when withdrawals are completed
*/
-contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenPodManager, EigenPodPausingConstants {
- /**
- * @notice Stored code of type(BeaconProxy).creationCode
- * @dev Maintained as a constant to solve an edge case - changes to OpenZeppelin's BeaconProxy code should not cause
- * addresses of EigenPods that are pre-computed with Create2 to change, even upon upgrading this contract, changing compiler version, etc.
- */
- bytes internal constant beaconProxyBytecode = hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";
-
- /// @notice The ETH2 Deposit Contract
- IETHPOSDeposit public immutable ethPOS;
+contract EigenPodManager is
+ Initializable,
+ OwnableUpgradeable,
+ Pausable,
+ EigenPodPausingConstants,
+ EigenPodManagerStorage,
+ ReentrancyGuardUpgradeable
+{
- /// @notice Beacon proxy to which the EigenPods point
- IBeacon public immutable eigenPodBeacon;
-
- /// @notice EigenLayer's StrategyManager contract
- IStrategyManager public immutable strategyManager;
-
- /// @notice EigenLayer's Slasher contract
- ISlasher public immutable slasher;
-
- /// @notice Oracle contract that provides updates to the beacon chain's state
- IBeaconChainOracle public beaconChainOracle;
-
- /// @notice Pod owner to deployed EigenPod address
- mapping(address => IEigenPod) public ownerToPod;
-
- // BEGIN STORAGE VARIABLES ADDED AFTER FIRST TESTNET DEPLOYMENT -- DO NOT SUGGEST REORDERING TO CONVENTIONAL ORDER
- /// @notice The number of EigenPods that have been deployed
- uint256 public numPods;
-
- /// @notice The maximum number of EigenPods that can be deployed
- uint256 public maxPods;
-
- /// @notice Emitted to notify the update of the beaconChainOracle address
- event BeaconOracleUpdated(address indexed newOracleAddress);
-
- /// @notice Emitted to notify the deployment of an EigenPod
- event PodDeployed(address indexed eigenPod, address indexed podOwner);
-
- /// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
- event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
-
- /// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue`
- event MaxPodsUpdated(uint256 previousValue, uint256 newValue);
-
modifier onlyEigenPod(address podOwner) {
require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");
_;
}
- modifier onlyStrategyManager {
- require(msg.sender == address(strategyManager), "EigenPodManager.onlyStrategyManager: not strategyManager");
+ modifier onlyDelegationManager() {
+ require(
+ msg.sender == address(delegationManager),
+ "EigenPodManager.onlyDelegationManager: not the DelegationManager"
+ );
_;
}
- constructor(IETHPOSDeposit _ethPOS, IBeacon _eigenPodBeacon, IStrategyManager _strategyManager, ISlasher _slasher) {
- ethPOS = _ethPOS;
- eigenPodBeacon = _eigenPodBeacon;
- strategyManager = _strategyManager;
- slasher = _slasher;
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IBeacon _eigenPodBeacon,
+ IStrategyManager _strategyManager,
+ ISlasher _slasher,
+ IDelegationManager _delegationManager
+ ) EigenPodManagerStorage(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {
_disableInitializers();
}
function initialize(
- uint256 _maxPods,
IBeaconChainOracle _beaconChainOracle,
address initialOwner,
IPauserRegistry _pauserRegistry,
uint256 _initPausedStatus
) external initializer {
- _setMaxPods(_maxPods);
_updateBeaconChainOracle(_beaconChainOracle);
_transferOwnership(initialOwner);
_initializePauser(_pauserRegistry, _initPausedStatus);
}
-
+
/**
* @notice Creates an EigenPod for the sender.
* @dev Function will revert if the `msg.sender` already has an EigenPod.
+ * @dev Returns EigenPod address
*/
- function createPod() external {
+ function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address) {
require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
// deploy a pod if the sender doesn't have one already
- _deployPod();
+ IEigenPod pod = _deployPod();
+
+ return address(pod);
}
/**
- * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
+ * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
* Also creates an EigenPod for the sender if they don't have one already.
* @param pubkey The 48 bytes public key of the beacon chain validator.
* @param signature The validator's signature of the deposit data.
* @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
*/
- function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable {
+ function stake(
+ bytes calldata pubkey,
+ bytes calldata signature,
+ bytes32 depositDataRoot
+ ) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) {
IEigenPod pod = ownerToPod[msg.sender];
- if(address(pod) == address(0)) {
+ if (address(pod) == address(0)) {
//deploy a pod if the sender doesn't have one already
pod = _deployPod();
}
@@ -132,49 +99,126 @@ contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenP
}
/**
- * @notice Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod.
- * @param podOwner The owner of the pod whose balance must be deposited.
- * @param amount The amount of ETH to 'deposit' (i.e. be credited to the podOwner).
+ * @notice Changes the `podOwner`'s shares by `sharesDelta` and performs a call to the DelegationManager
+ * to ensure that delegated shares are also tracked correctly
+ * @param podOwner is the pod owner whose balance is being updated.
+ * @param sharesDelta is the change in podOwner's beaconChainETHStrategy shares
* @dev Callable only by the podOwner's EigenPod contract.
+ * @dev Reverts if `sharesDelta` is not a whole Gwei amount
*/
- function restakeBeaconChainETH(address podOwner, uint256 amount) external onlyEigenPod(podOwner) {
- strategyManager.depositBeaconChainETH(podOwner, amount);
- emit BeaconChainETHDeposited(podOwner, amount);
+ function recordBeaconChainETHBalanceUpdate(
+ address podOwner,
+ int256 sharesDelta
+ ) external onlyEigenPod(podOwner) nonReentrant {
+ require(podOwner != address(0), "EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address");
+ require(sharesDelta % int256(GWEI_TO_WEI) == 0,
+ "EigenPodManager.recordBeaconChainETHBalanceUpdate: sharesDelta must be a whole Gwei amount");
+ int256 currentPodOwnerShares = podOwnerShares[podOwner];
+ int256 updatedPodOwnerShares = currentPodOwnerShares + sharesDelta;
+ podOwnerShares[podOwner] = updatedPodOwnerShares;
+
+ // inform the DelegationManager of the change in delegateable shares
+ int256 changeInDelegatableShares = _calculateChangeInDelegatableShares({
+ sharesBefore: currentPodOwnerShares,
+ sharesAfter: updatedPodOwnerShares
+ });
+ // skip making a call to the DelegationManager if there is no change in delegateable shares
+ if (changeInDelegatableShares != 0) {
+ if (changeInDelegatableShares < 0) {
+ delegationManager.decreaseDelegatedShares({
+ staker: podOwner,
+ strategy: beaconChainETHStrategy,
+ shares: uint256(-changeInDelegatableShares)
+ });
+ } else {
+ delegationManager.increaseDelegatedShares({
+ staker: podOwner,
+ strategy: beaconChainETHStrategy,
+ shares: uint256(changeInDelegatableShares)
+ });
+ }
+ }
+ emit PodSharesUpdated(podOwner, sharesDelta);
}
/**
- * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
- * balance of a validator is lower than how much stake they have committed to EigenLayer
- * @param podOwner The owner of the pod whose balance must be removed.
- * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
- * the StrategyManager in case it must be removed from the list of the podOwner's strategies
- * @param amount The amount of beacon chain ETH to decrement from the podOwner's shares in the strategyManager.
- * @dev Callable only by the podOwner's EigenPod contract.
+ * @notice Used by the DelegationManager to remove a pod owner's shares while they're in the withdrawal queue.
+ * Simply decreases the `podOwner`'s shares by `shares`, down to a minimum of zero.
+ * @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is forbidden for this function to
+ * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
+ * shares from the operator to whom the staker is delegated.
+ * @dev Reverts if `shares` is not a whole Gwei amount
+ * @dev The delegation manager validates that the podOwner is not address(0)
*/
- function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external onlyEigenPod(podOwner) {
- strategyManager.recordOvercommittedBeaconChainETH(podOwner, beaconChainETHStrategyIndex, amount);
+ function removeShares(
+ address podOwner,
+ uint256 shares
+ ) external onlyDelegationManager {
+ require(int256(shares) >= 0, "EigenPodManager.removeShares: shares cannot be negative");
+ require(shares % GWEI_TO_WEI == 0, "EigenPodManager.removeShares: shares must be a whole Gwei amount");
+ int256 updatedPodOwnerShares = podOwnerShares[podOwner] - int256(shares);
+ require(updatedPodOwnerShares >= 0, "EigenPodManager.removeShares: cannot result in pod owner having negative shares");
+ podOwnerShares[podOwner] = updatedPodOwnerShares;
}
/**
- * @notice Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain.
- * @param podOwner The owner of the pod whose balance must be withdrawn.
- * @param recipient The recipient of the withdrawn ETH.
- * @param amount The amount of ETH to withdraw.
- * @dev Callable only by the StrategyManager contract.
+ * @notice Increases the `podOwner`'s shares by `shares`, paying off deficit if possible.
+ * Used by the DelegationManager to award a pod owner shares on exiting the withdrawal queue
+ * @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the `shares` input
+ * in the event that the podOwner has an existing shares deficit (i.e. `podOwnerShares[podOwner]` starts below zero)
+ * @dev Reverts if `shares` is not a whole Gwei amount
*/
- function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount)
- external onlyStrategyManager onlyWhenNotPaused(PAUSED_WITHDRAW_RESTAKED_ETH)
- {
- ownerToPod[podOwner].withdrawRestakedBeaconChainETH(recipient, amount);
+ function addShares(
+ address podOwner,
+ uint256 shares
+ ) external onlyDelegationManager returns (uint256) {
+ require(podOwner != address(0), "EigenPodManager.addShares: podOwner cannot be zero address");
+ require(int256(shares) >= 0, "EigenPodManager.addShares: shares cannot be negative");
+ require(shares % GWEI_TO_WEI == 0, "EigenPodManager.addShares: shares must be a whole Gwei amount");
+ int256 currentPodOwnerShares = podOwnerShares[podOwner];
+ int256 updatedPodOwnerShares = currentPodOwnerShares + int256(shares);
+ podOwnerShares[podOwner] = updatedPodOwnerShares;
+
+ emit PodSharesUpdated(podOwner, int256(shares));
+
+ return uint256(_calculateChangeInDelegatableShares({sharesBefore: currentPodOwnerShares, sharesAfter: updatedPodOwnerShares}));
}
/**
- * Sets the maximum number of pods that can be deployed
- * @param newMaxPods The new maximum number of pods that can be deployed
- * @dev Callable by the unpauser of this contract
+ * @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address
+ * @dev Prioritizes decreasing the podOwner's share deficit, if they have one
+ * @dev Reverts if `shares` is not a whole Gwei amount
+ * @dev This function assumes that `removeShares` has already been called by the delegationManager, hence why
+ * we do not need to update the podOwnerShares if `currentPodOwnerShares` is positive
*/
- function setMaxPods(uint256 newMaxPods) external onlyUnpauser {
- _setMaxPods(newMaxPods);
+ function withdrawSharesAsTokens(
+ address podOwner,
+ address destination,
+ uint256 shares
+ ) external onlyDelegationManager {
+ require(podOwner != address(0), "EigenPodManager.withdrawSharesAsTokens: podOwner cannot be zero address");
+ require(destination != address(0), "EigenPodManager.withdrawSharesAsTokens: destination cannot be zero address");
+ require(int256(shares) >= 0, "EigenPodManager.withdrawSharesAsTokens: shares cannot be negative");
+ require(shares % GWEI_TO_WEI == 0, "EigenPodManager.withdrawSharesAsTokens: shares must be a whole Gwei amount");
+ int256 currentPodOwnerShares = podOwnerShares[podOwner];
+
+ // if there is an existing shares deficit, prioritize decreasing the deficit first
+ if (currentPodOwnerShares < 0) {
+ uint256 currentShareDeficit = uint256(-currentPodOwnerShares);
+ // get rid of the whole deficit if possible, and pass any remaining shares onto destination
+ 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);
}
/**
@@ -186,24 +230,31 @@ contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenP
_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 onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (IEigenPod) {
- // check that the limit of EigenPods has not been hit, and increment the EigenPod count
- require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
+
+ function _deployPod() internal returns (IEigenPod) {
++numPods;
// create the pod
- IEigenPod pod =
- IEigenPod(
- Create2.deploy(
- 0,
- bytes32(uint256(uint160(msg.sender))),
- // set the beacon address to the eigenPodBeacon and initialize it
- abi.encodePacked(
- beaconProxyBytecode,
- abi.encode(eigenPodBeacon, "")
- )
- )
- );
+ IEigenPod pod = IEigenPod(
+ Create2.deploy(
+ 0,
+ bytes32(uint256(uint160(msg.sender))),
+ // set the beacon address to the eigenPodBeacon and initialize it
+ abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
+ )
+ );
pod.initialize(msg.sender);
// store the pod in the mapping
ownerToPod[msg.sender] = pod;
@@ -217,10 +268,29 @@ contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenP
emit BeaconOracleUpdated(address(newBeaconChainOracle));
}
- /// @notice Internal setter for `maxPods` that also emits an event
- function _setMaxPods(uint256 _maxPods) internal {
- emit MaxPodsUpdated(maxPods, _maxPods);
- maxPods = _maxPods;
+ /**
+ * @notice Calculates the change in a pod owner's delegateable shares as a result of their beacon chain ETH shares changing
+ * from `sharesBefore` to `sharesAfter`. The key concept here is that negative/"deficit" shares are not delegateable.
+ */
+ function _calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) internal pure returns (int256) {
+ if (sharesBefore <= 0) {
+ // if the shares started negative and stayed negative, then there cannot have been an increase in delegateable shares
+ if (sharesAfter <= 0) {
+ return 0;
+ // if the shares started negative and became positive, then the increase in delegateable shares is the ending share amount
+ } else {
+ return sharesAfter;
+ }
+ } else {
+ // if the shares started positive and became negative, then the decrease in delegateable shares is the starting share amount
+ if (sharesAfter <= 0) {
+ return (-sharesBefore);
+ // if the shares started positive and stayed positive, then the change in delegateable shares
+ // is the difference between starting and ending amounts
+ } else {
+ return (sharesAfter - sharesBefore);
+ }
+ }
}
// VIEW FUNCTIONS
@@ -232,11 +302,9 @@ contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenP
pod = IEigenPod(
Create2.computeAddress(
bytes32(uint256(uint160(podOwner))), //salt
- keccak256(abi.encodePacked(
- beaconProxyBytecode,
- abi.encode(eigenPodBeacon, "")
- )) //bytecode
- ));
+ keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))) //bytecode
+ )
+ );
}
return pod;
}
@@ -246,17 +314,26 @@ contract EigenPodManager is Initializable, OwnableUpgradeable, Pausable, IEigenP
return address(ownerToPod[podOwner]) != address(0);
}
- /// @notice Returns the Beacon Chain state root at `blockNumber`. Reverts if the Beacon Chain state root at `blockNumber` has not yet been finalized.
- function getBeaconChainStateRoot(uint64 blockNumber) external view returns(bytes32) {
- bytes32 stateRoot = beaconChainOracle.beaconStateRootAtBlockNumber(blockNumber);
- require(stateRoot != bytes32(0), "EigenPodManager.getBeaconChainStateRoot: state root at blockNumber not yet finalized");
+ /// @notice Returns the Beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized.
+ function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32) {
+ bytes32 stateRoot = beaconChainOracle.timestampToBlockRoot(timestamp);
+ require(
+ stateRoot != bytes32(0),
+ "EigenPodManager.getBlockRootAtTimestamp: state root at timestamp not yet finalized"
+ );
return stateRoot;
}
/**
- * @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
+ * @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.
*/
- uint256[46] private __gap;
-}
\ No newline at end of file
+ function denebForkTimestamp() public view returns (uint64) {
+ uint64 timestamp = _denebForkTimestamp;
+ if (timestamp == 0) {
+ return type(uint64).max;
+ } else {
+ return timestamp;
+ }
+ }
+}
diff --git a/src/contracts/pods/EigenPodManagerStorage.sol b/src/contracts/pods/EigenPodManagerStorage.sol
new file mode 100644
index 0000000000..39c98f2c2f
--- /dev/null
+++ b/src/contracts/pods/EigenPodManagerStorage.sol
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
+
+import "../interfaces/IStrategy.sol";
+import "../interfaces/IEigenPodManager.sol";
+import "../interfaces/IStrategyManager.sol";
+import "../interfaces/IDelegationManager.sol";
+import "../interfaces/IETHPOSDeposit.sol";
+import "../interfaces/IEigenPod.sol";
+
+abstract contract EigenPodManagerStorage is IEigenPodManager {
+ /// @notice The ETH2 Deposit Contract
+ IETHPOSDeposit public immutable ethPOS;
+
+ /// @notice Beacon proxy to which the EigenPods point
+ IBeacon public immutable eigenPodBeacon;
+
+ /// @notice EigenLayer's StrategyManager contract
+ IStrategyManager public immutable strategyManager;
+
+ /// @notice EigenLayer's Slasher contract
+ ISlasher public immutable slasher;
+
+ /// @notice EigenLayer's DelegationManager contract
+ IDelegationManager public immutable delegationManager;
+
+ /**
+ * @notice Stored code of type(BeaconProxy).creationCode
+ * @dev Maintained as a constant to solve an edge case - changes to OpenZeppelin's BeaconProxy code should not cause
+ * addresses of EigenPods that are pre-computed with Create2 to change, even upon upgrading this contract, changing compiler version, etc.
+ */
+ bytes internal constant beaconProxyBytecode =
+ hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";
+
+ // @notice Internal constant used in calculations, since the beacon chain stores balances in Gwei rather than wei
+ uint256 internal constant GWEI_TO_WEI = 1e9;
+
+ /// @notice Canonical, virtual beacon chain ETH strategy
+ IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+
+ /// @notice Oracle contract that provides updates to the beacon chain's state
+ IBeaconChainOracle public beaconChainOracle;
+
+ /// @notice Pod owner to deployed EigenPod address
+ mapping(address => IEigenPod) public ownerToPod;
+
+ // BEGIN STORAGE VARIABLES ADDED AFTER FIRST TESTNET DEPLOYMENT -- DO NOT SUGGEST REORDERING TO CONVENTIONAL ORDER
+ /// @notice The number of EigenPods that have been deployed
+ uint256 public numPods;
+
+ /// @notice Deprecated from old mainnet release. Was initially used to limit growth early on but there is no longer
+ /// a maximum number of EigenPods that can be deployed.
+ uint256 private __deprecated_maxPods;
+
+ // BEGIN STORAGE VARIABLES ADDED AFTER MAINNET DEPLOYMENT -- DO NOT SUGGEST REORDERING TO CONVENTIONAL ORDER
+ /**
+ * @notice Mapping from Pod owner owner to the number of shares they have in the virtual beacon chain ETH strategy.
+ * @dev The share amount can become negative. This is necessary to accommodate the fact that a pod owner's virtual beacon chain ETH shares can
+ * decrease between the pod owner queuing and completing a withdrawal.
+ * When the pod owner's shares would otherwise increase, this "deficit" is decreased first _instead_.
+ * Likewise, when a withdrawal is completed, this "deficit" is decreased and the withdrawal amount is decreased; We can think of this
+ * as the withdrawal "paying off the deficit".
+ */
+ mapping(address => int256) public podOwnerShares;
+
+ uint64 internal _denebForkTimestamp;
+
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IBeacon _eigenPodBeacon,
+ IStrategyManager _strategyManager,
+ ISlasher _slasher,
+ IDelegationManager _delegationManager
+ ) {
+ ethPOS = _ethPOS;
+ eigenPodBeacon = _eigenPodBeacon;
+ strategyManager = _strategyManager;
+ slasher = _slasher;
+ delegationManager = _delegationManager;
+ }
+
+ /**
+ * @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[44] private __gap;
+}
diff --git a/src/contracts/pods/EigenPodPausingConstants.sol b/src/contracts/pods/EigenPodPausingConstants.sol
index 62dba9bb56..f7eca3ece1 100644
--- a/src/contracts/pods/EigenPodPausingConstants.sol
+++ b/src/contracts/pods/EigenPodPausingConstants.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
/**
* @title Constants shared between 'EigenPod' and 'EigenPodManager' contracts.
@@ -9,13 +9,18 @@ pragma solidity =0.8.12;
abstract contract EigenPodPausingConstants {
/// @notice Index for flag that pauses creation of new EigenPods when set. See EigenPodManager code for details.
uint8 internal constant PAUSED_NEW_EIGENPODS = 0;
- /// @notice Index for flag that pauses the `withdrawRestakedBeaconChainETH` function *of the EigenPodManager* when set. See EigenPodManager code for details.
+ /**
+ * @notice Index for flag that pauses all withdrawal-of-restaked ETH related functionality `
+ * function *of the EigenPodManager* when set. See EigenPodManager code for details.
+ */
uint8 internal constant PAUSED_WITHDRAW_RESTAKED_ETH = 1;
- /// @notice Index for flag that pauses the `verifyCorrectWithdrawalCredentials` function *of the EigenPods* when set. see EigenPod code for details.
+ /// @notice Index for flag that pauses the deposit related functions *of the EigenPods* when set. see EigenPod code for details.
uint8 internal constant PAUSED_EIGENPODS_VERIFY_CREDENTIALS = 2;
- /// @notice Index for flag that pauses the `verifyOvercommittedStake` function *of the EigenPods* when set. see EigenPod code for details.
- uint8 internal constant PAUSED_EIGENPODS_VERIFY_OVERCOMMITTED = 3;
+ /// @notice Index for flag that pauses the `verifyBalanceUpdate` function *of the EigenPods* when set. see EigenPod code for details.
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE = 3;
/// @notice Index for flag that pauses the `verifyBeaconChainFullWithdrawal` function *of the EigenPods* when set. see EigenPod code for details.
uint8 internal constant PAUSED_EIGENPODS_VERIFY_WITHDRAWAL = 4;
-}
\ No newline at end of file
+ /// @notice Pausability for EigenPod's "accidental transfer" withdrawal methods
+ uint8 internal constant PAUSED_NON_PROOF_WITHDRAWALS = 5;
+}
diff --git a/src/contracts/strategies/StrategyBase.sol b/src/contracts/strategies/StrategyBase.sol
index c56d06d50a..1e159ab7dd 100644
--- a/src/contracts/strategies/StrategyBase.sol
+++ b/src/contracts/strategies/StrategyBase.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../interfaces/IStrategyManager.sol";
import "../permissions/Pausable.sol";
@@ -39,7 +39,7 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
* incurring reasonably small losses to depositors
*/
uint256 internal constant SHARES_OFFSET = 1e3;
- /**
+ /**
* @notice virtual balance used as part of the mitigation of the common 'share inflation' attack vector
* Constant value chosen to reasonably reduce attempted share inflation by the first depositor, while still
* incurring reasonably small losses to depositors
@@ -72,7 +72,10 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
}
/// @notice Sets the `underlyingToken` and `pauserRegistry` for the strategy.
- function _initializeStrategyBase(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) internal onlyInitializing {
+ function _initializeStrategyBase(
+ IERC20 _underlyingToken,
+ IPauserRegistry _pauserRegistry
+ ) internal onlyInitializing {
underlyingToken = _underlyingToken;
_initializePauser(_pauserRegistry, UNPAUSE_ALL);
}
@@ -87,21 +90,16 @@ 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(IERC20 token, uint256 amount)
- external
- virtual
- override
- onlyWhenNotPaused(PAUSED_DEPOSITS)
- onlyStrategyManager
- returns (uint256 newShares)
- {
+ function deposit(
+ IERC20 token,
+ uint256 amount
+ ) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares) {
// 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;
@@ -125,24 +123,21 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
}
/**
- * @notice Used to withdraw tokens from this Strategy, to the `depositor`'s address
- * @param depositor is the address to receive the withdrawn funds
+ * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address
+ * @param recipient is the address to receive the withdrawn funds
* @param token is the ERC20 token being transferred out
* @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 depositor, IERC20 token, uint256 amountShares)
- external
- virtual
- override
- onlyWhenNotPaused(PAUSED_WITHDRAWALS)
- onlyStrategyManager
- {
+ function withdraw(
+ address recipient,
+ IERC20 token,
+ uint256 amountShares
+ ) external virtual override onlyWhenNotPaused(PAUSED_WITHDRAWALS) onlyStrategyManager {
// call hook to allow for any pre-withdrawal logic
- _beforeWithdrawal(depositor, token, amountShares);
-
- require(token == underlyingToken, "StrategyBase.withdraw: Can only withdraw the strategy token");
+ _beforeWithdrawal(recipient, token, amountShares);
// copy `totalShares` value to memory, prior to any change
uint256 priorTotalShares = totalShares;
@@ -165,24 +160,38 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
// Decrease the `totalShares` value to reflect the withdrawal
totalShares = priorTotalShares - amountShares;
- underlyingToken.safeTransfer(depositor, amountToSend);
+ _afterWithdrawal(recipient, token, amountToSend);
}
-
/**
* @notice Called in the external `deposit` function, before any logic is executed. Expected to be overridden if strategies want such logic.
* @param token The token being deposited
* @param amount The amount of `token` being deposited
*/
- 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.
- * @param depositor The address that will receive the withdrawn tokens
+ * @param recipient The address that will receive the withdrawn tokens
* @param token The token being withdrawn
* @param amountShares The amount of shares being withdrawn
*/
- function _beforeWithdrawal(address depositor, 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 81d47379d4..63b76ff45d 100644
--- a/src/contracts/strategies/StrategyBaseTVLLimits.sol
+++ b/src/contracts/strategies/StrategyBaseTVLLimits.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "./StrategyBase.sol";
@@ -23,12 +23,15 @@ contract StrategyBaseTVLLimits is StrategyBase {
/// @notice Emitted when `maxTotalDeposits` value is updated from `previousValue` to `newValue`
event MaxTotalDepositsUpdated(uint256 previousValue, uint256 newValue);
-
+ // solhint-disable-next-line no-empty-blocks
constructor(IStrategyManager _strategyManager) StrategyBase(_strategyManager) {}
- function initialize(uint256 _maxPerDeposit, uint256 _maxTotalDeposits, IERC20 _underlyingToken, IPauserRegistry _pauserRegistry)
- public virtual initializer
- {
+ function initialize(
+ uint256 _maxPerDeposit,
+ uint256 _maxTotalDeposits,
+ IERC20 _underlyingToken,
+ IPauserRegistry _pauserRegistry
+ ) public virtual initializer {
_setTVLLimits(_maxPerDeposit, _maxTotalDeposits);
_initializeStrategyBase(_underlyingToken, _pauserRegistry);
}
@@ -53,7 +56,10 @@ contract StrategyBaseTVLLimits is StrategyBase {
function _setTVLLimits(uint256 newMaxPerDeposit, uint256 newMaxTotalDeposits) internal {
emit MaxPerDepositUpdated(maxPerDeposit, newMaxPerDeposit);
emit MaxTotalDepositsUpdated(maxTotalDeposits, newMaxTotalDeposits);
- require(newMaxPerDeposit <= newMaxTotalDeposits, "StrategyBaseTVLLimits._setTVLLimits: maxPerDeposit exceeds maxTotalDeposits");
+ require(
+ newMaxPerDeposit <= newMaxTotalDeposits,
+ "StrategyBaseTVLLimits._setTVLLimits: maxPerDeposit exceeds maxTotalDeposits"
+ );
maxPerDeposit = newMaxPerDeposit;
maxTotalDeposits = newMaxTotalDeposits;
}
@@ -70,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);
}
/**
@@ -81,4 +89,4 @@ contract StrategyBaseTVLLimits is StrategyBase {
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
-}
\ No newline at end of file
+}
diff --git a/src/contracts/utils/UpgradeableSignatureCheckingUtils.sol b/src/contracts/utils/UpgradeableSignatureCheckingUtils.sol
new file mode 100644
index 0000000000..b9b899e533
--- /dev/null
+++ b/src/contracts/utils/UpgradeableSignatureCheckingUtils.sol
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
+import "../libraries/EIP1271SignatureUtils.sol";
+
+/**
+ * @title Abstract contract that implements minimal signature-related storage & functionality for upgradeable contracts.
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ */
+abstract contract UpgradeableSignatureCheckingUtils is Initializable {
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH =
+ keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
+
+ // chain id at the time of contract deployment
+ uint256 internal immutable ORIGINAL_CHAIN_ID;
+
+ /**
+ * @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;
+
+ // INITIALIZING FUNCTIONS
+ constructor() {
+ ORIGINAL_CHAIN_ID = block.chainid;
+ }
+
+ function _initializeSignatureCheckingUtils() internal onlyInitializing {
+ _DOMAIN_SEPARATOR = _calculateDomainSeparator();
+ }
+
+ // VIEW FUNCTIONS
+ /**
+ * @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)));
+ }
+}
diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol
index 13a2a3b0fb..b92343100b 100644
--- a/src/test/Delegation.t.sol
+++ b/src/test/Delegation.t.sol
@@ -1,29 +1,19 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "@openzeppelin/contracts/utils/math/Math.sol";
-import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
+import "src/contracts/interfaces/ISignatureUtils.sol";
import "../test/EigenLayerTestHelper.t.sol";
-import "./mocks/MiddlewareRegistryMock.sol";
-import "./mocks/MiddlewareVoteWeigherMock.sol";
-import "./mocks/ServiceManagerMock.sol";
-
-import "./SigP/DelegationTerms.sol";
-
-
contract DelegationTests is EigenLayerTestHelper {
- using Math for uint256;
-
uint256 public PRIVATE_KEY = 420;
uint32 serveUntil = 100;
- ServiceManagerMock public serviceManager;
- MiddlewareVoteWeigherMock public voteWeigher;
- MiddlewareVoteWeigherMock public voteWeigherImplementation;
+ address public registryCoordinator = address(uint160(uint256(keccak256("registryCoordinator"))));
+ uint8 defaultQuorumNumber = 0;
+ bytes32 defaultOperatorId = bytes32(uint256(0));
modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) {
cheats.assume(ethAmount >= 0 && ethAmount <= 1e18);
@@ -33,52 +23,11 @@ contract DelegationTests is EigenLayerTestHelper {
function setUp() public virtual override {
EigenLayerDeployer.setUp();
-
- initializeMiddlewares();
- }
-
- function initializeMiddlewares() public {
- serviceManager = new ServiceManagerMock(slasher);
-
-
- voteWeigher = MiddlewareVoteWeigherMock(
- address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
- );
-
-
- voteWeigherImplementation = new MiddlewareVoteWeigherMock(delegation, strategyManager, serviceManager);
-
-
- {
- uint96 multiplier = 1e18;
- uint8 _NUMBER_OF_QUORUMS = 2;
- uint256[] memory _quorumBips = new uint256[](_NUMBER_OF_QUORUMS);
- // split 60% ETH quorum, 40% EIGEN quorum
- _quorumBips[0] = 6000;
- _quorumBips[1] = 4000;
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1);
- ethStratsAndMultipliers[0].strategy = wethStrat;
- ethStratsAndMultipliers[0].multiplier = multiplier;
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1);
- eigenStratsAndMultipliers[0].strategy = eigenStrat;
- eigenStratsAndMultipliers[0].multiplier = multiplier;
-
- cheats.startPrank(eigenLayerProxyAdmin.owner());
- eigenLayerProxyAdmin.upgradeAndCall(
- TransparentUpgradeableProxy(payable(address(voteWeigher))),
- address(voteWeigherImplementation),
- abi.encodeWithSelector(MiddlewareVoteWeigherMock.initialize.selector, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers)
- );
- cheats.stopPrank();
-
- }
}
/// @notice testing if an operator can register to themselves.
function testSelfOperatorRegister() public {
- _testRegisterAdditionalOperator(0, serveUntil);
+ _testRegisterAdditionalOperator(0);
}
/// @notice testing if an operator can delegate to themselves.
@@ -86,157 +35,165 @@ contract DelegationTests is EigenLayerTestHelper {
function testSelfOperatorDelegate(address sender) public {
cheats.assume(sender != address(0));
cheats.assume(sender != address(eigenLayerProxyAdmin));
- _testRegisterAsOperator(sender, IDelegationTerms(sender));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: sender,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(sender, operatorDetails);
}
function testTwoSelfOperatorsRegister() public {
- _testRegisterAdditionalOperator(0, serveUntil);
- _testRegisterAdditionalOperator(1, serveUntil);
+ _testRegisterAdditionalOperator(0);
+ _testRegisterAdditionalOperator(1);
}
/// @notice registers a fixed address as a delegate, delegates to it from a second address,
/// and checks that the delegate's voteWeights increase properly
/// @param operator is the operator being delegated to.
/// @param staker is the staker delegating stake to the operator.
- function testDelegation(address operator, address staker, uint96 ethAmount, uint96 eigenAmount)
- public
- fuzzedAddress(operator)
- fuzzedAddress(staker)
- fuzzedAmounts(ethAmount, eigenAmount)
- {
+ function testDelegation(
+ address operator,
+ address staker,
+ uint96 ethAmount,
+ uint96 eigenAmount
+ ) public fuzzedAddress(operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) {
cheats.assume(staker != operator);
// base strategy will revert if these amounts are too small on first deposit
cheats.assume(ethAmount >= 1);
- cheats.assume(eigenAmount >= 1);
-
- _testDelegation(operator, staker, ethAmount, eigenAmount, voteWeigher);
+ cheats.assume(eigenAmount >= 2);
+
+ // Set weights ahead of the helper function call
+ bytes memory quorumNumbers = new bytes(2);
+ quorumNumbers[0] = bytes1(uint8(0));
+ quorumNumbers[0] = bytes1(uint8(1));
+ _testDelegation(operator, staker, ethAmount, eigenAmount);
}
/// @notice tests that a when an operator is delegated to, that delegation is properly accounted for.
- function testDelegationReceived(address _operator, address staker, uint64 ethAmount, uint64 eigenAmount)
- public
- fuzzedAddress(_operator)
- fuzzedAddress(staker)
- fuzzedAmounts(ethAmount, eigenAmount)
- {
+ function testDelegationReceived(
+ address _operator,
+ address staker,
+ uint64 ethAmount,
+ uint64 eigenAmount
+ ) public fuzzedAddress(_operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) {
cheats.assume(staker != _operator);
cheats.assume(ethAmount >= 1);
- cheats.assume(eigenAmount >= 1);
+ cheats.assume(eigenAmount >= 2);
// use storage to solve stack-too-deep
operator = _operator;
- SigPDelegationTerms dt = new SigPDelegationTerms();
-
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
if (!delegation.isOperator(operator)) {
- _testRegisterAsOperator(operator, dt);
+ _testRegisterAsOperator(operator, operatorDetails);
}
- uint256[3] memory amountsBefore;
- amountsBefore[0] = voteWeigher.weightOfOperator(operator, 0);
- amountsBefore[1] = voteWeigher.weightOfOperator(operator, 1);
- amountsBefore[2] = delegation.operatorShares(operator, wethStrat);
+ uint256 amountBefore = delegation.operatorShares(operator, wethStrat);
//making additional deposits to the strategies
- assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate");
+ assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
_testDepositWeth(staker, ethAmount);
_testDepositEigen(staker, eigenAmount);
_testDelegateToOperator(staker, operator);
assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
- (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) =
- strategyManager.getDeposits(staker);
-
- {
- uint256 stakerEthWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[0]);
- uint256 stakerEigenWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[1]);
-
- uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(operator, 0);
- uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(operator, 1);
+ (/*IStrategy[] memory updatedStrategies*/, uint256[] memory updatedShares) = strategyManager.getDeposits(staker);
- assertTrue(
- operatorEthWeightAfter - amountsBefore[0] == stakerEthWeight,
- "testDelegation: operatorEthWeight did not increment by the right amount"
- );
- assertTrue(
- operatorEigenWeightAfter - amountsBefore[1] == stakerEigenWeight,
- "Eigen weights did not increment by the right amount"
- );
- }
{
IStrategy _strat = wethStrat;
// IStrategy _strat = strategyManager.stakerStrats(staker, 0);
assertTrue(address(_strat) != address(0), "stakerStrats not updated correctly");
assertTrue(
- delegation.operatorShares(operator, _strat) - updatedShares[0] == amountsBefore[2],
+ delegation.operatorShares(operator, _strat) - updatedShares[0] == amountBefore,
"ETH operatorShares not updated correctly"
);
cheats.startPrank(address(strategyManager));
- IDelegationTerms expectedDt = delegation.delegationTerms(operator);
- assertTrue(address(expectedDt) == address(dt), "failed to set dt");
- delegation.increaseDelegatedShares(staker, _strat, 1);
-
- // dt.delegate();
- assertTrue(keccak256(dt.isDelegationReceived()) == keccak256(bytes("received")), "failed to fire expected onDelegationReceived callback");
+ IDelegationManager.OperatorDetails memory expectedOperatorDetails = delegation.operatorDetails(operator);
+ assertTrue(
+ keccak256(abi.encode(expectedOperatorDetails)) == keccak256(abi.encode(operatorDetails)),
+ "failed to set correct operator details"
+ );
}
}
/// @notice tests that a when an operator is undelegated from, that the staker is properly classified as undelegated.
- function testUndelegation(address operator, address staker, uint96 ethAmount, uint96 eigenAmount)
- public
- fuzzedAddress(operator)
- fuzzedAddress(staker)
- fuzzedAmounts(ethAmount, eigenAmount)
- {
+ function testUndelegation(
+ address operator,
+ address staker,
+ uint96 ethAmount,
+ uint96 eigenAmount
+ ) public fuzzedAddress(operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) {
cheats.assume(staker != operator);
// base strategy will revert if these amounts are too small on first deposit
cheats.assume(ethAmount >= 1);
cheats.assume(eigenAmount >= 1);
+ _testDelegation(operator, staker, ethAmount, eigenAmount);
- _testDelegation(operator, staker, ethAmount, eigenAmount, voteWeigher);
- cheats.startPrank(address(strategyManager));
+ (IStrategy[] memory strategyArray, uint256[] memory shareAmounts) = strategyManager.getDeposits(staker);
+ uint256[] memory strategyIndexes = new uint256[](strategyArray.length);
+
+ // withdraw shares
+ _testQueueWithdrawal(staker, strategyIndexes, strategyArray, shareAmounts, staker /*withdrawer*/);
+
+ cheats.startPrank(staker);
delegation.undelegate(staker);
cheats.stopPrank();
require(delegation.delegatedTo(staker) == address(0), "undelegation unsuccessful");
}
- /// @notice tests delegation from a staker to operator via ECDSA signature.
- function testDelegateToBySignature(address operator, uint96 ethAmount, uint96 eigenAmount, uint256 expiry)
- public
- fuzzedAddress(operator)
- {
+ /// @notice tests delegation from a staker to operator via ECDSA signature.
+ function testDelegateToBySignature(
+ address operator,
+ uint96 ethAmount,
+ uint96 eigenAmount,
+ uint256 expiry
+ ) public fuzzedAddress(operator) {
address staker = cheats.addr(PRIVATE_KEY);
- _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
+ _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
- uint256 nonceBefore = delegation.nonces(staker);
+ uint256 nonceBefore = delegation.stakerNonce(staker);
- bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash));
+ bytes32 structHash = keccak256(
+ abi.encode(delegation.STAKER_DELEGATION_TYPEHASH(), staker, operator, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.domainSeparator(), structHash));
- (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash);
+ bytes memory signature;
+ {
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash);
+ signature = abi.encodePacked(r, s, v);
+ }
- bytes memory signature = abi.encodePacked(r, s, v);
-
if (expiry < block.timestamp) {
- cheats.expectRevert("DelegationManager.delegateToBySignature: delegation signature expired");
+ cheats.expectRevert("DelegationManager.delegateToBySignature: staker signature expired");
}
- delegation.delegateToBySignature(staker, operator, expiry, signature);
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
+ signature: signature,
+ expiry: expiry
+ });
+ delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0));
if (expiry >= block.timestamp) {
assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
- assertTrue(nonceBefore + 1 == delegation.nonces(staker), "nonce not incremented correctly");
- assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator");
+ assertTrue(nonceBefore + 1 == delegation.stakerNonce(staker), "nonce not incremented correctly");
+ assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator");
}
}
/// @notice tries delegating using a signature and an EIP 1271 compliant wallet
- function testDelegateToBySignature_WithContractWallet_Successfully(address operator, uint96 ethAmount, uint96 eigenAmount)
- public
- fuzzedAddress(operator)
- {
+ function testDelegateToBySignature_WithContractWallet_Successfully(
+ address operator,
+ uint96 ethAmount,
+ uint96 eigenAmount
+ ) public fuzzedAddress(operator) {
address staker = cheats.addr(PRIVATE_KEY);
// deploy ERC1271WalletMock for staker to use
@@ -245,28 +202,37 @@ contract DelegationTests is EigenLayerTestHelper {
cheats.stopPrank();
staker = address(wallet);
- _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
-
- uint256 nonceBefore = delegation.nonces(staker);
+ _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
- bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash));
+ uint256 nonceBefore = delegation.stakerNonce(staker);
- (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash);
+ bytes32 structHash = keccak256(
+ abi.encode(delegation.STAKER_DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.domainSeparator(), structHash));
- bytes memory signature = abi.encodePacked(r, s, v);
-
- delegation.delegateToBySignature(staker, operator, type(uint256).max, signature);
+ bytes memory signature;
+ {
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash);
+ signature = abi.encodePacked(r, s, v);
+ }
+
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
+ signature: signature,
+ expiry: type(uint256).max
+ });
+ delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0));
assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
- assertTrue(nonceBefore + 1 == delegation.nonces(staker), "nonce not incremented correctly");
+ assertTrue(nonceBefore + 1 == delegation.stakerNonce(staker), "nonce not incremented correctly");
assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator");
}
/// @notice tries delegating using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature
- function testDelegateToBySignature_WithContractWallet_BadSignature(address operator, uint96 ethAmount, uint96 eigenAmount)
- public
- fuzzedAddress(operator)
- {
+ function testDelegateToBySignature_WithContractWallet_BadSignature(
+ address operator,
+ uint96 ethAmount,
+ uint96 eigenAmount
+ ) public fuzzedAddress(operator) {
address staker = cheats.addr(PRIVATE_KEY);
// deploy ERC1271WalletMock for staker to use
@@ -275,28 +241,42 @@ contract DelegationTests is EigenLayerTestHelper {
cheats.stopPrank();
staker = address(wallet);
- _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
-
- uint256 nonceBefore = delegation.nonces(staker);
+ _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
- bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash));
+ uint256 nonceBefore = delegation.stakerNonce(staker);
- (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash);
- // mess up the signature by flipping v's parity
- v = (v == 27 ? 28 : 27);
+ bytes32 structHash = keccak256(
+ abi.encode(delegation.STAKER_DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.domainSeparator(), structHash));
- bytes memory signature = abi.encodePacked(r, s, v);
-
- cheats.expectRevert(bytes("DelegationManager.delegateToBySignature: ERC1271 signature verification failed"));
- delegation.delegateToBySignature(staker, operator, type(uint256).max, signature);
+ bytes memory signature;
+ {
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash);
+ // mess up the signature by flipping v's parity
+ v = (v == 27 ? 28 : 27);
+ signature = abi.encodePacked(r, s, v);
+ }
+
+ cheats.expectRevert(
+ bytes("EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed")
+ );
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
+ signature: signature,
+ expiry: type(uint256).max
+ });
+ delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0));
}
/// @notice tries delegating using a wallet that does not comply with EIP 1271
- function testDelegateToBySignature_WithContractWallet_NonconformingWallet(address operator, uint96 ethAmount, uint96 eigenAmount, uint8 v, bytes32 r, bytes32 s)
- public
- fuzzedAddress(operator)
- {
+ function testDelegateToBySignature_WithContractWallet_NonconformingWallet(
+ address operator,
+ uint96 ethAmount,
+ uint96 eigenAmount,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) public fuzzedAddress(operator) {
address staker = cheats.addr(PRIVATE_KEY);
// deploy non ERC1271-compliant wallet for staker to use
@@ -305,95 +285,68 @@ contract DelegationTests is EigenLayerTestHelper {
cheats.stopPrank();
staker = address(wallet);
- _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
+ _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
cheats.assume(staker != operator);
bytes memory signature = abi.encodePacked(r, s, v);
cheats.expectRevert();
- delegation.delegateToBySignature(staker, operator, type(uint256).max, signature);
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
+ signature: signature,
+ expiry: type(uint256).max
+ });
+ delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0));
}
/// @notice tests delegation to EigenLayer via an ECDSA signatures with invalid signature
/// @param operator is the operator being delegated to.
function testDelegateToByInvalidSignature(
- address operator,
- uint96 ethAmount,
- uint96 eigenAmount,
+ address operator,
+ uint96 ethAmount,
+ uint96 eigenAmount,
uint8 v,
bytes32 r,
bytes32 s
- )
- public
- fuzzedAddress(operator)
- fuzzedAmounts(ethAmount, eigenAmount)
- {
+ ) public fuzzedAddress(operator) fuzzedAmounts(ethAmount, eigenAmount) {
address staker = cheats.addr(PRIVATE_KEY);
- _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
+ _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount);
bytes memory signature = abi.encodePacked(r, s, v);
-
- cheats.expectRevert();
- delegation.delegateToBySignature(staker, operator, type(uint256).max, signature);
- }
-
- /// @notice registers a fixed address as a delegate, delegates to it from a second address,
- /// and checks that the delegate's voteWeights increase properly
- /// @param operator is the operator being delegated to.
- /// @param staker is the staker delegating stake to the operator.
- function testDelegationMultipleStrategies(uint8 numStratsToAdd, address operator, address staker)
- public
- fuzzedAddress(operator)
- fuzzedAddress(staker)
- {
- cheats.assume(staker != operator);
- cheats.assume(numStratsToAdd > 0 && numStratsToAdd <= 20);
- uint96 operatorEthWeightBefore = voteWeigher.weightOfOperator(operator, 0);
- uint96 operatorEigenWeightBefore = voteWeigher.weightOfOperator(operator, 1);
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
- _testDepositStrategies(staker, 1e18, numStratsToAdd);
-
- // add strategies to voteWeigher
- uint96 multiplier = 1e18;
- for (uint16 i = 0; i < numStratsToAdd; ++i) {
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](
- 1
- );
- ethStratsAndMultipliers[0].strategy = strategies[i];
- ethStratsAndMultipliers[0].multiplier = multiplier;
- cheats.startPrank(voteWeigher.serviceManager().owner());
- voteWeigher.addStrategiesConsideredAndMultipliers(0, ethStratsAndMultipliers);
- cheats.stopPrank();
- }
-
- _testDepositEigen(staker, 1e18);
- _testDelegateToOperator(staker, operator);
- uint96 operatorEthWeightAfter = voteWeigher.weightOfOperator(operator, 0);
- uint96 operatorEigenWeightAfter = voteWeigher.weightOfOperator(operator, 1);
- assertTrue(
- operatorEthWeightAfter > operatorEthWeightBefore, "testDelegation: operatorEthWeight did not increase!"
- );
- assertTrue(
- operatorEigenWeightAfter > operatorEigenWeightBefore, "testDelegation: operatorEthWeight did not increase!"
- );
+ cheats.expectRevert();
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
+ signature: signature,
+ expiry: type(uint256).max
+ });
+ delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0));
}
/// @notice This function tests to ensure that a delegation contract
/// 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);
+ 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
/// @param operator is the operator being delegated to.
function testRegisterAsOperatorMultipleTimes(address operator) public fuzzedAddress(operator) {
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(operator, operatorDetails);
cheats.expectRevert(bytes("DelegationManager.registerAsOperator: operator has already registered"));
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
+ _testRegisterAsOperator(operator, operatorDetails);
}
/// @notice This function tests to ensure that a staker cannot delegate to an unregistered operator
@@ -403,13 +356,13 @@ contract DelegationTests is EigenLayerTestHelper {
_testDepositStrategies(getOperatorAddress(1), 1e18, 1);
_testDepositEigen(getOperatorAddress(1), 1e18);
- cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate"));
+ cheats.expectRevert(bytes("DelegationManager._delegate: operator is not registered in EigenLayer"));
cheats.startPrank(getOperatorAddress(1));
- delegation.delegateTo(delegate);
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry;
+ delegation.delegateTo(delegate, signatureWithExpiry, bytes32(0));
cheats.stopPrank();
}
-
/// @notice This function tests to ensure that a delegation contract
/// cannot be intitialized multiple times, test with different caller addresses
function testCannotInitMultipleTimesDelegation(address _attacker) public {
@@ -417,38 +370,64 @@ 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);
+ delegation.initialize(
+ _attacker,
+ eigenLayerPauserReg,
+ 0,
+ 0, // minWithdrawalDelayBLocks
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ );
}
- /// @notice This function tests that the delegationTerms cannot be set to address(0)
- function testCannotSetDelegationTermsZeroAddress() public{
- cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate"));
- delegation.registerAsOperator(IDelegationTerms(address(0)));
+ /// @notice This function tests that the earningsReceiver cannot be set to address(0)
+ function testCannotSetEarningsReceiverToZeroAddress() public {
+ cheats.expectRevert(
+ bytes("DelegationManager._setOperatorDetails: cannot set `earningsReceiver` to zero address")
+ );
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: address(0),
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ string memory emptyStringForMetadataURI;
+ delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
}
/// @notice This function tests to ensure that an address can only call registerAsOperator() once
- function testCannotRegisterAsOperatorTwice(address _operator, address _dt) public fuzzedAddress(_operator) fuzzedAddress(_dt) {
+ function testCannotRegisterAsOperatorTwice(
+ address _operator,
+ address _dt
+ ) public fuzzedAddress(_operator) fuzzedAddress(_dt) {
vm.assume(_dt != address(0));
vm.startPrank(_operator);
- delegation.registerAsOperator(IDelegationTerms(_dt));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: msg.sender,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ string memory emptyStringForMetadataURI;
+ delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
vm.expectRevert("DelegationManager.registerAsOperator: operator has already registered");
- delegation.registerAsOperator(IDelegationTerms(_dt));
+ delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
cheats.stopPrank();
}
- /// @notice This function checks that you can only delegate to an address that is already registered.
- function testDelegateToInvalidOperator(address _staker, address _unregisteredOperator) public fuzzedAddress(_staker) {
+ /// @notice this function checks that you can only delegate to an address that is already registered.
+ function testDelegateToInvalidOperator(
+ address _staker,
+ address _unregisteredoperator
+ ) public fuzzedAddress(_staker) {
vm.startPrank(_staker);
- cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate"));
- delegation.delegateTo(_unregisteredOperator);
- cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate"));
- delegation.delegateTo(_staker);
+ cheats.expectRevert(bytes("DelegationManager._delegate: operator is not registered in EigenLayer"));
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry;
+ delegation.delegateTo(_unregisteredoperator, signatureWithExpiry, bytes32(0));
+ cheats.expectRevert(bytes("DelegationManager._delegate: operator is not registered in EigenLayer"));
+ delegation.delegateTo(_staker, signatureWithExpiry, bytes32(0));
cheats.stopPrank();
-
}
- function testUndelegate_SigP_Version(address _operator,address _staker,address _dt) public {
-
+ function testUndelegate_SigP_Version(address _operator, address _staker, address _dt) public {
vm.assume(_operator != address(0));
vm.assume(_staker != address(0));
vm.assume(_operator != _staker);
@@ -456,41 +435,1071 @@ contract DelegationTests is EigenLayerTestHelper {
vm.assume(_operator != address(eigenLayerProxyAdmin));
vm.assume(_staker != address(eigenLayerProxyAdmin));
- //setup delegation
+ // setup delegation
vm.prank(_operator);
- delegation.registerAsOperator(IDelegationTerms(_dt));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: _dt,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ string memory emptyStringForMetadataURI;
+ delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
vm.prank(_staker);
- delegation.delegateTo(_operator);
-
- //operators cannot undelegate from themselves
- vm.prank(address(strategyManager));
- cheats.expectRevert(bytes("DelegationManager.undelegate: operators cannot undelegate from themselves"));
- delegation.undelegate(_operator);
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry;
+ delegation.delegateTo(_operator, signatureWithExpiry, bytes32(0));
- //_staker cannot undelegate themselves
- vm.prank(_staker);
- cheats.expectRevert();
- delegation.undelegate(_operator);
-
- //_operator cannot undelegate themselves
+ // operators cannot undelegate from themselves
vm.prank(_operator);
- cheats.expectRevert();
+ cheats.expectRevert(bytes("DelegationManager.undelegate: operators cannot be undelegated"));
delegation.undelegate(_operator);
- //assert still delegated
+ // assert still delegated
assertTrue(delegation.isDelegated(_staker));
- assertFalse(delegation.isNotDelegated(_staker));
assertTrue(delegation.isOperator(_operator));
- //strategyManager can undelegate _staker
- vm.prank(address(strategyManager));
+ // _staker *can* undelegate themselves
+ vm.prank(_staker);
delegation.undelegate(_staker);
- assertFalse(delegation.isDelegated(_staker));
- assertTrue(delegation.isNotDelegated(_staker));
+ // assert undelegated
+ assertTrue(!delegation.isDelegated(_staker));
+ assertTrue(delegation.isOperator(_operator));
}
- function _testRegisterAdditionalOperator(uint256 index, uint32 _serveUntil) internal {
+ /**************************************
+ *
+ * Withdrawals Tests with StrategyManager, using actual SM contract instead of Mock to test
+ *
+ **************************************/
+
+
+ // function testQueueWithdrawalRevertsMismatchedSharesAndStrategyArrayLength() external {
+ // IStrategy[] memory strategyArray = new IStrategy[](1);
+ // uint256[] memory shareAmounts = new uint256[](2);
+
+ // {
+ // strategyArray[0] = eigenPodManagerMock.beaconChainETHStrategy();
+ // shareAmounts[0] = 1;
+ // shareAmounts[1] = 1;
+ // }
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: strategyArray,
+ // shares: shareAmounts,
+ // withdrawer: address(this)
+ // });
+
+ // cheats.expectRevert(bytes("DelegationManager.queueWithdrawal: input length mismatch"));
+ // delegationManager.queueWithdrawals(params);
+ // }
+
+ // function testQueueWithdrawalRevertsWithZeroAddressWithdrawer() external {
+ // IStrategy[] memory strategyArray = new IStrategy[](1);
+ // uint256[] memory shareAmounts = new uint256[](1);
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: strategyArray,
+ // shares: shareAmounts,
+ // withdrawer: address(0)
+ // });
+
+ // cheats.expectRevert(bytes("DelegationManager.queueWithdrawal: must provide valid withdrawal address"));
+ // delegationManager.queueWithdrawals(params);
+ // }
+
+ // function testQueueWithdrawal_ToSelf(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // )
+ // public
+ // returns (
+ // IDelegationManager.Withdrawal memory /* queuedWithdrawal */,
+ // IERC20[] memory /* tokensArray */,
+ // bytes32 /* withdrawalRoot */
+ // )
+ // {
+ // _setUpWithdrawalTests();
+ // StrategyBase strategy = strategyMock;
+ // IERC20 token = strategy.underlyingToken();
+
+ // // filtering of fuzzed inputs
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+
+ // _tempStrategyStorage = strategy;
+
+ // _depositIntoStrategySuccessfully(strategy, /*staker*/ address(this), depositAmount);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // bytes32 withdrawalRoot
+ // ) = _setUpWithdrawalStructSingleStrat(
+ // /*staker*/ address(this),
+ // /*withdrawer*/ address(this),
+ // token,
+ // _tempStrategyStorage,
+ // withdrawalAmount
+ // );
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(/*staker*/ address(this), _tempStrategyStorage);
+ // uint256 nonceBefore = delegationManager.cumulativeWithdrawalsQueued(/*staker*/ address(this));
+
+ // require(!delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRootPendingBefore is true!");
+
+ // {
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalQueued(
+ // withdrawalRoot,
+ // withdrawal
+ // );
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: withdrawal.strategies,
+ // shares: withdrawal.shares,
+ // withdrawer: address(this)
+ // });
+ // delegationManager.queueWithdrawals(params);
+ // }
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(/*staker*/ address(this), _tempStrategyStorage);
+ // uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(/*staker*/ address(this));
+
+ // require(delegationManager.pendingWithdrawals(withdrawalRoot), "pendingWithdrawalsAfter is false!");
+ // require(sharesAfter == sharesBefore - withdrawalAmount, "sharesAfter != sharesBefore - withdrawalAmount");
+ // require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
+
+ // return (withdrawal, tokensArray, withdrawalRoot);
+ // }
+
+ // function testQueueWithdrawal_ToSelf_TwoStrategies(
+ // uint256[2] memory depositAmounts,
+ // uint256[2] memory withdrawalAmounts
+ // )
+ // public
+ // returns (
+ // IDelegationManager.Withdrawal memory /* withdrawal */,
+ // bytes32 /* withdrawalRoot */
+ // )
+ // {
+ // _setUpWithdrawalTests();
+ // // filtering of fuzzed inputs
+ // cheats.assume(withdrawalAmounts[0] != 0 && withdrawalAmounts[0] < depositAmounts[0]);
+ // cheats.assume(withdrawalAmounts[1] != 0 && withdrawalAmounts[1] < depositAmounts[1]);
+ // address staker = address(this);
+
+ // IStrategy[] memory strategies = new IStrategy[](2);
+ // strategies[0] = IStrategy(strategyMock);
+ // strategies[1] = IStrategy(strategyMock2);
+
+ // IERC20[] memory tokens = new IERC20[](2);
+ // tokens[0] = strategyMock.underlyingToken();
+ // tokens[1] = strategyMock2.underlyingToken();
+
+ // uint256[] memory amounts = new uint256[](2);
+ // amounts[0] = withdrawalAmounts[0];
+ // amounts[1] = withdrawalAmounts[1];
+
+ // _depositIntoStrategySuccessfully(strategies[0], staker, depositAmounts[0]);
+ // _depositIntoStrategySuccessfully(strategies[1], staker, depositAmounts[1]);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // bytes32 withdrawalRoot
+ // ) = _setUpWithdrawalStruct_MultipleStrategies(
+ // /* staker */ staker,
+ // /* withdrawer */ staker,
+ // strategies,
+ // amounts
+ // );
+
+ // uint256[] memory sharesBefore = new uint256[](2);
+ // sharesBefore[0] = strategyManager.stakerStrategyShares(staker, strategies[0]);
+ // sharesBefore[1] = strategyManager.stakerStrategyShares(staker, strategies[1]);
+ // uint256 nonceBefore = delegationManager.cumulativeWithdrawalsQueued(staker);
+
+ // require(!delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRootPendingBefore is true!");
+
+ // {
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalQueued(
+ // withdrawalRoot,
+ // withdrawal
+ // );
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: withdrawal.strategies,
+ // shares: withdrawal.shares,
+ // withdrawer: staker
+ // });
+
+ // delegationManager.queueWithdrawals(
+ // params
+ // );
+ // }
+
+ // uint256[] memory sharesAfter = new uint256[](2);
+ // sharesAfter[0] = strategyManager.stakerStrategyShares(staker, strategies[0]);
+ // sharesAfter[1] = strategyManager.stakerStrategyShares(staker, strategies[1]);
+ // uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(staker);
+
+ // require(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRootPendingAfter is false!");
+ // require(
+ // sharesAfter[0] == sharesBefore[0] - withdrawalAmounts[0],
+ // "Strat1: sharesAfter != sharesBefore - withdrawalAmount"
+ // );
+ // require(
+ // sharesAfter[1] == sharesBefore[1] - withdrawalAmounts[1],
+ // "Strat2: sharesAfter != sharesBefore - withdrawalAmount"
+ // );
+ // require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
+
+ // return (withdrawal, withdrawalRoot);
+ // }
+
+ // function testQueueWithdrawalPartiallyWithdraw(uint128 amount) external {
+ // testQueueWithdrawal_ToSelf(uint256(amount) * 2, amount);
+ // require(!delegationManager.isDelegated(address(this)), "should still be delegated failed");
+ // }
+
+ // function testQueueWithdrawal_ToDifferentAddress(
+ // address withdrawer,
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external filterFuzzedAddressInputs(withdrawer) {
+ // _setUpWithdrawalTests();
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // address staker = address(this);
+
+ // _depositIntoStrategySuccessfully(strategyMock, staker, depositAmount);
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // ,
+ // bytes32 withdrawalRoot
+ // ) = _setUpWithdrawalStructSingleStrat(
+ // staker,
+ // withdrawer,
+ // /*token*/ strategyMock.underlyingToken(),
+ // strategyMock,
+ // withdrawalAmount
+ // );
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategyMock);
+ // uint256 nonceBefore = delegationManager.cumulativeWithdrawalsQueued(staker);
+
+ // require(!delegationManager.pendingWithdrawals(withdrawalRoot), "pendingWithdrawalsBefore is true!");
+
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalQueued(
+ // withdrawalRoot,
+ // withdrawal
+ // );
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: withdrawal.strategies,
+ // shares: withdrawal.shares,
+ // withdrawer: withdrawer
+ // });
+
+ // delegationManager.queueWithdrawals(
+ // params
+ // );
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategyMock);
+ // uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(staker);
+
+ // require(delegationManager.pendingWithdrawals(withdrawalRoot), "pendingWithdrawalsAfter is false!");
+ // require(sharesAfter == sharesBefore - withdrawalAmount, "sharesAfter != sharesBefore - amount");
+ // require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
+ // }
+
+ // function testCompleteQueuedWithdrawalRevertsWhenAttemptingReentrancy(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // // replace dummyStrat with Reenterer contract
+ // reenterer = new Reenterer();
+ // strategyMock = StrategyBase(address(reenterer));
+
+ // // whitelist the strategy for deposit
+ // cheats.startPrank(strategyManager.owner());
+ // IStrategy[] memory _strategy = new IStrategy[](1);
+ // _strategy[0] = strategyMock;
+ // strategyManager.addStrategiesToDepositWhitelist(_strategy);
+ // cheats.stopPrank();
+
+ // _tempStakerStorage = address(this);
+ // IStrategy strategy = strategyMock;
+
+ // reenterer.prepareReturnData(abi.encode(depositAmount));
+
+
+ // IStrategy[] memory strategyArray = new IStrategy[](1);
+ // IERC20[] memory tokensArray = new IERC20[](1);
+ // uint256[] memory shareAmounts = new uint256[](1);
+ // {
+ // strategyArray[0] = strategy;
+ // shareAmounts[0] = withdrawalAmount;
+ // tokensArray[0] = mockToken;
+ // }
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // /* tokensArray */,
+ // /* withdrawalRoot */
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+
+ // address targetToUse = address(strategyManager);
+ // uint256 msgValueToUse = 0;
+ // bytes memory calldataToUse = abi.encodeWithSelector(
+ // DelegationManager.completeQueuedWithdrawal.selector,
+ // withdrawal,
+ // tokensArray,
+ // middlewareTimesIndex,
+ // receiveAsTokens
+ // );
+ // reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+ // }
+
+ // function testCompleteQueuedWithdrawalRevertsWhenNotCallingFromWithdrawerAddress(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // _tempStakerStorage = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(_tempStakerStorage));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+
+ // cheats.startPrank(address(123456));
+ // cheats.expectRevert(
+ // bytes(
+ // "DelegationManager.completeQueuedAction: only withdrawer can complete action"
+ // )
+ // );
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+ // cheats.stopPrank();
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(_tempStakerStorage));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+ // function testCompleteQueuedWithdrawalRevertsWhenTryingToCompleteSameWithdrawal2X(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // _tempStakerStorage = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // bytes32 withdrawalRoot
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(_tempStakerStorage));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalCompleted(withdrawalRoot);
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(_tempStakerStorage));
+
+ // require(sharesAfter == sharesBefore + withdrawalAmount, "sharesAfter != sharesBefore + withdrawalAmount");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // sharesBefore = sharesAfter;
+ // balanceBefore = balanceAfter;
+
+ // cheats.expectRevert(
+ // bytes(
+ // "DelegationManager.completeQueuedAction: action is not in queue"
+ // )
+ // );
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // balanceAfter = token.balanceOf(address(_tempStakerStorage));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+ // function testCompleteQueuedWithdrawalRevertsWhenWithdrawalDelayBlocksHasNotPassed(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // _tempStakerStorage = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // uint256 valueToSet = 1;
+ // // set the `withdrawalDelayBlocks` variable
+ // cheats.startPrank(strategyManager.owner());
+ // uint256 previousValue = delegationManager.withdrawalDelayBlocks();
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalDelayBlocksSet(previousValue, valueToSet);
+ // delegationManager.setWithdrawalDelayBlocks(valueToSet);
+ // cheats.stopPrank();
+ // require(
+ // delegationManager.withdrawalDelayBlocks() == valueToSet,
+ // "delegationManager.withdrawalDelayBlocks() != valueToSet"
+ // );
+
+ // cheats.expectRevert(
+ // bytes("DelegationManager.completeQueuedAction: withdrawalDelayBlocks period has not yet passed")
+ // );
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, /* middlewareTimesIndex */ 0, /* receiveAsTokens */ false);
+ // }
+
+ // function testCompleteQueuedWithdrawalRevertsWhenWithdrawalDoesNotExist() external {
+ // uint256 withdrawalAmount = 1e18;
+ // IStrategy strategy = strategyMock;
+ // IERC20 token = strategy.underlyingToken();
+
+ // (IDelegationManager.Withdrawal memory withdrawal, IERC20[] memory tokensArray, ) = _setUpWithdrawalStructSingleStrat(
+ // /*staker*/ address(this),
+ // /*withdrawer*/ address(this),
+ // token,
+ // strategy,
+ // withdrawalAmount
+ // );
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(_tempStakerStorage));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+
+ // cheats.expectRevert(bytes("DelegationManager.completeQueuedAction: action is not in queue"));
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(_tempStakerStorage));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+ // function testCompleteQueuedWithdrawalRevertsWhenWithdrawalsPaused(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // _tempStakerStorage = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(_tempStakerStorage));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+
+ // // pause withdrawals
+ // cheats.startPrank(pauser);
+ // delegationManager.pause(2 ** PAUSED_EXIT_WITHDRAWAL_QUEUE);
+ // cheats.stopPrank();
+
+ // cheats.expectRevert(bytes("Pausable: index is paused"));
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(_tempStakerStorage));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+ // function testCompleteQueuedWithdrawalFailsWhenTokensInputLengthMismatch(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // _tempStakerStorage = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(_tempStakerStorage));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = true;
+ // // mismatch tokens array by setting tokens array to empty array
+ // tokensArray = new IERC20[](0);
+
+ // cheats.expectRevert(bytes("DelegationManager.completeQueuedAction: input length mismatch"));
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(_tempStakerStorage));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+ // function testCompleteQueuedWithdrawalWithNonzeroWithdrawalDelayBlocks(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount,
+ // uint16 valueToSet
+ // ) external {
+ // // filter fuzzed inputs to allowed *and nonzero* amounts
+ // cheats.assume(valueToSet <= delegationManager.MAX_WITHDRAWAL_DELAY_BLOCKS() && valueToSet != 0);
+ // cheats.assume(depositAmount != 0 && withdrawalAmount != 0);
+ // cheats.assume(depositAmount >= withdrawalAmount);
+ // address staker = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+
+ // // set the `withdrawalDelayBlocks` variable
+ // cheats.startPrank(delegationManager.owner());
+ // uint256 previousValue = delegationManager.withdrawalDelayBlocks();
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalDelayBlocksSet(previousValue, valueToSet);
+ // delegationManager.setWithdrawalDelayBlocks(valueToSet);
+ // cheats.stopPrank();
+ // require(
+ // delegationManager.withdrawalDelayBlocks() == valueToSet,
+ // "strategyManager.withdrawalDelayBlocks() != valueToSet"
+ // );
+
+ // cheats.expectRevert(
+ // bytes("DelegationManager.completeQueuedAction: withdrawalDelayBlocks period has not yet passed")
+ // );
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(staker));
+
+ // // roll block number forward to one block before the withdrawal should be completeable and attempt again
+ // uint256 originalBlockNumber = block.number;
+ // cheats.roll(originalBlockNumber + valueToSet - 1);
+ // cheats.expectRevert(
+ // bytes("DelegationManager.completeQueuedAction: withdrawalDelayBlocks period has not yet passed")
+ // );
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // // roll block number forward to the block at which the withdrawal should be completeable, and complete it
+ // cheats.roll(originalBlockNumber + valueToSet);
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(staker));
+
+ // require(sharesAfter == sharesBefore + withdrawalAmount, "sharesAfter != sharesBefore + withdrawalAmount");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+
+ // function testCompleteQueuedWithdrawal_ReceiveAsTokensMarkedFalse(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // address staker = address(this);
+
+ // // testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // bytes32 withdrawalRoot
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceBefore = token.balanceOf(address(staker));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = false;
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalCompleted(withdrawalRoot);
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
+ // uint256 balanceAfter = token.balanceOf(address(staker));
+
+ // require(sharesAfter == sharesBefore + withdrawalAmount, "sharesAfter != sharesBefore + withdrawalAmount");
+ // require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
+ // }
+
+ // function testCompleteQueuedWithdrawal_ReceiveAsTokensMarkedTrue(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
+ // address staker = address(this);
+
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // bytes32 withdrawalRoot
+ // ) = testQueueWithdrawal_ToSelf(depositAmount, withdrawalAmount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ // uint256 balanceBefore = token.balanceOf(address(staker));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = true;
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalCompleted(withdrawalRoot);
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
+ // uint256 balanceAfter = token.balanceOf(address(staker));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore + withdrawalAmount, "balanceAfter != balanceBefore + withdrawalAmount");
+ // if (depositAmount == withdrawalAmount) {
+ // // Since receiving tokens instead of shares, if withdrawal amount is entire deposit, then strategy will be removed
+ // // with sharesAfter being 0
+ // require(
+ // !_isDepositedStrategy(staker, strategy),
+ // "Strategy still part of staker's deposited strategies"
+ // );
+ // require(sharesAfter == 0, "staker shares is not 0");
+ // }
+ // }
+
+ // function testCompleteQueuedWithdrawalFullyWithdraw(uint256 amount) external {
+ // address staker = address(this);
+ // (
+ // IDelegationManager.Withdrawal memory withdrawal,
+ // IERC20[] memory tokensArray,
+ // bytes32 withdrawalRoot
+ // ) = testQueueWithdrawal_ToSelf(amount, amount);
+
+ // IStrategy strategy = withdrawal.strategies[0];
+ // IERC20 token = tokensArray[0];
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ // uint256 balanceBefore = token.balanceOf(address(staker));
+
+ // uint256 middlewareTimesIndex = 0;
+ // bool receiveAsTokens = true;
+ // cheats.expectEmit(true, true, true, true, address(delegationManager));
+ // emit WithdrawalCompleted(withdrawalRoot);
+ // delegationManager.completeQueuedWithdrawal(withdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
+ // uint256 balanceAfter = token.balanceOf(address(staker));
+
+ // require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
+ // require(balanceAfter == balanceBefore + amount, "balanceAfter != balanceBefore + withdrawalAmount");
+ // require(
+ // !_isDepositedStrategy(staker, strategy),
+ // "Strategy still part of staker's deposited strategies"
+ // );
+ // require(sharesAfter == 0, "staker shares is not 0");
+ // }
+
+ // function test_removeSharesRevertsWhenShareAmountIsZero(uint256 depositAmount) external {
+ // _setUpWithdrawalTests();
+ // address staker = address(this);
+ // uint256 withdrawalAmount = 0;
+
+ // _depositIntoStrategySuccessfully(strategyMock, staker, depositAmount);
+
+ // (IDelegationManager.Withdrawal memory withdrawal, , ) = _setUpWithdrawalStructSingleStrat(
+ // /*staker*/ address(this),
+ // /*withdrawer*/ address(this),
+ // mockToken,
+ // strategyMock,
+ // withdrawalAmount
+ // );
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: withdrawal.strategies,
+ // shares: withdrawal.shares,
+ // withdrawer: staker
+ // });
+
+ // cheats.expectRevert(bytes("StrategyManager._removeShares: shareAmount should not be zero!"));
+ // delegationManager.queueWithdrawals(
+ // params
+ // );
+ // }
+
+ // function test_removeSharesRevertsWhenShareAmountIsTooLarge(
+ // uint256 depositAmount,
+ // uint256 withdrawalAmount
+ // ) external {
+ // _setUpWithdrawalTests();
+ // cheats.assume(depositAmount > 0 && withdrawalAmount > depositAmount);
+ // address staker = address(this);
+
+ // _depositIntoStrategySuccessfully(strategyMock, staker, depositAmount);
+
+ // (IDelegationManager.Withdrawal memory withdrawal, , ) = _setUpWithdrawalStructSingleStrat(
+ // /*staker*/ address(this),
+ // /*withdrawer*/ address(this),
+ // mockToken,
+ // strategyMock,
+ // withdrawalAmount
+ // );
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: withdrawal.strategies,
+ // shares: withdrawal.shares,
+ // withdrawer: address(this)
+ // });
+
+ // cheats.expectRevert(bytes("StrategyManager._removeShares: shareAmount too high"));
+ // delegationManager.queueWithdrawals(
+ // params
+ // );
+ // }
+
+ // /**
+ // * Testing queueWithdrawal of 3 strategies, fuzzing the deposit and withdraw amounts. if the withdrawal amounts == deposit amounts
+ // * then the strategy should be removed from the staker StrategyList
+ // */
+ // function test_removeStrategyFromStakerStrategyList(uint256[3] memory depositAmounts, uint256[3] memory withdrawalAmounts) external {
+ // _setUpWithdrawalTests();
+ // // filtering of fuzzed inputs
+ // cheats.assume(withdrawalAmounts[0] > 0 && withdrawalAmounts[0] <= depositAmounts[0]);
+ // cheats.assume(withdrawalAmounts[1] > 0 && withdrawalAmounts[1] <= depositAmounts[1]);
+ // cheats.assume(withdrawalAmounts[2] > 0 && withdrawalAmounts[2] <= depositAmounts[2]);
+ // address staker = address(this);
+
+ // // Setup input params
+ // IStrategy[] memory strategies = new IStrategy[](3);
+ // strategies[0] = strategyMock;
+ // strategies[1] = strategyMock2;
+ // strategies[2] = strategyMock3;
+ // uint256[] memory amounts = new uint256[](3);
+ // amounts[0] = withdrawalAmounts[0];
+ // amounts[1] = withdrawalAmounts[1];
+ // amounts[2] = withdrawalAmounts[2];
+
+ // _depositIntoStrategySuccessfully(strategies[0], staker, depositAmounts[0]);
+ // _depositIntoStrategySuccessfully(strategies[1], staker, depositAmounts[1]);
+ // _depositIntoStrategySuccessfully(strategies[2], staker, depositAmounts[2]);
+
+ // ( ,bytes32 withdrawalRoot) = _setUpWithdrawalStruct_MultipleStrategies(
+ // /* staker */ staker,
+ // /* withdrawer */ staker,
+ // strategies,
+ // amounts
+ // );
+ // require(!delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRootPendingBefore is true!");
+ // delegationManager.cumulativeWithdrawalsQueued(staker);
+ // uint256[] memory sharesBefore = new uint256[](3);
+ // sharesBefore[0] = strategyManager.stakerStrategyShares(staker, strategies[0]);
+ // sharesBefore[1] = strategyManager.stakerStrategyShares(staker, strategies[1]);
+ // sharesBefore[2] = strategyManager.stakerStrategyShares(staker, strategies[2]);
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: strategies,
+ // shares: amounts,
+ // withdrawer: address(this)
+ // });
+
+ // delegationManager.queueWithdrawals(
+ // params
+ // );
+
+ // uint256[] memory sharesAfter = new uint256[](3);
+ // sharesAfter[0] = strategyManager.stakerStrategyShares(staker, strategies[0]);
+ // sharesAfter[1] = strategyManager.stakerStrategyShares(staker, strategies[1]);
+ // sharesAfter[2] = strategyManager.stakerStrategyShares(staker, strategies[2]);
+ // require(sharesBefore[0] == sharesAfter[0] + withdrawalAmounts[0], "Strat1: sharesBefore != sharesAfter + withdrawalAmount");
+ // if (depositAmounts[0] == withdrawalAmounts[0]) {
+ // require(!_isDepositedStrategy(staker, strategies[0]), "Strategy still part of staker's deposited strategies");
+ // }
+ // require(sharesBefore[1] == sharesAfter[1] + withdrawalAmounts[1], "Strat2: sharesBefore != sharesAfter + withdrawalAmount");
+ // if (depositAmounts[1] == withdrawalAmounts[1]) {
+ // require(!_isDepositedStrategy(staker, strategies[1]), "Strategy still part of staker's deposited strategies");
+ // }
+ // require(sharesBefore[2] == sharesAfter[2] + withdrawalAmounts[2], "Strat3: sharesBefore != sharesAfter + withdrawalAmount");
+ // if (depositAmounts[2] == withdrawalAmounts[2]) {
+ // require(!_isDepositedStrategy(staker, strategies[2]), "Strategy still part of staker's deposited strategies");
+ // }
+ // }
+
+ // // ensures that when the staker and withdrawer are different and a withdrawal is completed as shares (i.e. not as tokens)
+ // // that the shares get added back to the right operator
+ // function test_completingWithdrawalAsSharesAddsSharesToCorrectOperator() external {
+ // address staker = address(this);
+ // address withdrawer = address(1000);
+ // address operator_for_staker = address(1001);
+ // address operator_for_withdrawer = address(1002);
+
+ // // register operators
+ // bytes32 salt;
+ // IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ // earningsReceiver: operator_for_staker,
+ // delegationApprover: address(0),
+ // stakerOptOutWindowBlocks: 0
+ // });
+ // testRegisterAsOperator(operator_for_staker, operatorDetails, emptyStringForMetadataURI);
+ // testRegisterAsOperator(operator_for_withdrawer, operatorDetails, emptyStringForMetadataURI);
+
+ // // delegate from the `staker` and withdrawer to the operators
+ // ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ // cheats.startPrank(staker);
+ // delegationManager.delegateTo(operator_for_staker, approverSignatureAndExpiry, salt);
+ // cheats.stopPrank();
+ // cheats.startPrank(withdrawer);
+ // delegationManager.delegateTo(operator_for_withdrawer, approverSignatureAndExpiry, salt);
+ // cheats.stopPrank();
+
+ // // Setup input params
+ // IStrategy[] memory strategies = new IStrategy[](3);
+ // strategies[0] = strategyMock;
+ // strategies[1] = delegationManager.beaconChainETHStrategy();
+ // strategies[2] = strategyMock3;
+ // uint256[] memory amounts = new uint256[](3);
+ // amounts[0] = 1e18;
+ // amounts[1] = 2e18;
+ // amounts[2] = 3e18;
+
+ // (IDelegationManager.Withdrawal memory withdrawal, ) = _setUpWithdrawalStruct_MultipleStrategies({
+ // staker: staker,
+ // withdrawer: withdrawer,
+ // strategyArray: strategies,
+ // shareAmounts: amounts
+ // });
+
+ // // give both the operators a bunch of delegated shares, so we can decrement them when queuing the withdrawal
+ // cheats.startPrank(address(delegationManager.strategyManager()));
+ // for (uint256 i = 0; i < strategies.length; ++i) {
+ // delegationManager.increaseDelegatedShares(staker, strategies[i], amounts[i]);
+ // delegationManager.increaseDelegatedShares(withdrawer, strategies[i], amounts[i]);
+ // }
+ // cheats.stopPrank();
+
+ // IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ // params[0] = IDelegationManager.QueuedWithdrawalParams({
+ // strategies: strategies,
+ // shares: amounts,
+ // withdrawer: withdrawer
+ // });
+
+ // // queue the withdrawal
+ // cheats.startPrank(staker);
+ // delegationManager.queueWithdrawals(params);
+ // cheats.stopPrank();
+
+ // for (uint256 i = 0; i < strategies.length; ++i) {
+ // require(delegationManager.operatorShares(operator_for_staker, strategies[i]) == 0,
+ // "staker operator shares incorrect after queueing");
+ // require(delegationManager.operatorShares(operator_for_withdrawer, strategies[i]) == amounts[i],
+ // "withdrawer operator shares incorrect after queuing");
+ // }
+
+ // // complete the withdrawal
+ // cheats.startPrank(withdrawer);
+ // IERC20[] memory tokens;
+ // delegationManager.completeQueuedWithdrawal(
+ // withdrawal,
+ // tokens,
+ // 0 /*middlewareTimesIndex*/,
+ // false /*receiveAsTokens*/
+ // );
+ // cheats.stopPrank();
+
+ // for (uint256 i = 0; i < strategies.length; ++i) {
+ // if (strategies[i] != delegationManager.beaconChainETHStrategy()) {
+ // require(delegationManager.operatorShares(operator_for_staker, strategies[i]) == 0,
+ // "staker operator shares incorrect after completing withdrawal");
+ // require(delegationManager.operatorShares(operator_for_withdrawer, strategies[i]) == 2 * amounts[i],
+ // "withdrawer operator shares incorrect after completing withdrawal");
+ // } else {
+ // require(delegationManager.operatorShares(operator_for_staker, strategies[i]) == amounts[i],
+ // "staker operator beaconChainETHStrategy shares incorrect after completing withdrawal");
+ // require(delegationManager.operatorShares(operator_for_withdrawer, strategies[i]) == amounts[i],
+ // "withdrawer operator beaconChainETHStrategy shares incorrect after completing withdrawal");
+ // }
+ // }
+ // }
+
+ // /**
+ // * Setup DelegationManager and StrategyManager contracts for testing instead of using StrategyManagerMock
+ // * since we need to test the actual contracts together for the withdrawal queueing tests
+ // */
+ // function _setUpWithdrawalTests() internal {
+ // delegationManagerImplementation = new DelegationManager(strategyManager, slasherMock, eigenPodManagerMock);
+ // cheats.startPrank(eigenLayerProxyAdmin.owner());
+ // eigenLayerProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(delegationManager))), address(delegationManagerImplementation));
+ // cheats.stopPrank();
+
+
+ // strategyImplementation = new StrategyBase(strategyManager);
+ // mockToken = new ERC20Mock();
+ // strategyMock = StrategyBase(
+ // address(
+ // new TransparentUpgradeableProxy(
+ // address(strategyImplementation),
+ // address(eigenLayerProxyAdmin),
+ // abi.encodeWithSelector(StrategyBase.initialize.selector, mockToken, pauserRegistry)
+ // )
+ // )
+ // );
+ // strategyMock2 = StrategyBase(
+ // address(
+ // new TransparentUpgradeableProxy(
+ // address(strategyImplementation),
+ // address(eigenLayerProxyAdmin),
+ // abi.encodeWithSelector(StrategyBase.initialize.selector, mockToken, pauserRegistry)
+ // )
+ // )
+ // );
+ // strategyMock3 = StrategyBase(
+ // address(
+ // new TransparentUpgradeableProxy(
+ // address(strategyImplementation),
+ // address(eigenLayerProxyAdmin),
+ // abi.encodeWithSelector(StrategyBase.initialize.selector, mockToken, pauserRegistry)
+ // )
+ // )
+ // );
+
+ // // whitelist the strategy for deposit
+ // cheats.startPrank(strategyManager.owner());
+ // IStrategy[] memory _strategies = new IStrategy[](3);
+ // _strategies[0] = strategyMock;
+ // _strategies[1] = strategyMock2;
+ // _strategies[2] = strategyMock3;
+ // strategyManager.addStrategiesToDepositWhitelist(_strategies);
+ // cheats.stopPrank();
+
+ // require(delegationManager.strategyManager() == strategyManager,
+ // "constructor / initializer incorrect, strategyManager set wrong");
+ // }
+
+ // function _depositIntoStrategySuccessfully(
+ // IStrategy strategy,
+ // address staker,
+ // uint256 amount
+ // ) internal {
+ // IERC20 token = strategy.underlyingToken();
+ // // IStrategy strategy = strategyMock;
+
+ // // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
+ // cheats.assume(amount != 0);
+ // // filter out zero address because the mock ERC20 we are using will revert on using it
+ // cheats.assume(staker != address(0));
+ // // filter out the strategy itself from fuzzed inputs
+ // cheats.assume(staker != address(strategy));
+ // // sanity check / filter
+ // cheats.assume(amount <= token.balanceOf(address(this)));
+ // cheats.assume(amount >= 1);
+
+ // uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ // uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
+
+ // // needed for expecting an event with the right parameters
+ // uint256 expectedShares = strategy.underlyingToShares(amount);
+
+ // cheats.startPrank(staker);
+ // cheats.expectEmit(true, true, true, true, address(strategyManager));
+ // emit Deposit(staker, token, strategy, expectedShares);
+ // uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount);
+ // cheats.stopPrank();
+
+ // uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
+ // uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
+
+ // require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares");
+ // if (sharesBefore == 0) {
+ // require(
+ // stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1,
+ // "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1"
+ // );
+ // require(
+ // strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) == strategy,
+ // "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy"
+ // );
+ // }
+ // }
+
+ // /**
+ // * @notice internal function to help check if a strategy is part of list of deposited strategies for a staker
+ // * Used to check if removed correctly after withdrawing all shares for a given strategy
+ // */
+ // function _isDepositedStrategy(address staker, IStrategy strategy) internal view returns (bool) {
+ // uint256 stakerStrategyListLength = strategyManager.stakerStrategyListLength(staker);
+ // for (uint256 i = 0; i < stakerStrategyListLength; ++i) {
+ // if (strategyManager.stakerStrategyList(staker, i) == strategy) {
+ // return true;
+ // }
+ // }
+ // return false;
+ // }
+
+ function _testRegisterAdditionalOperator(uint256 index) internal {
address sender = getOperatorAddress(index);
//register as both ETH and EIGEN operator
@@ -498,21 +1507,24 @@ contract DelegationTests is EigenLayerTestHelper {
uint256 eigenToDeposit = 1e10;
_testDepositWeth(sender, wethToDeposit);
_testDepositEigen(sender, eigenToDeposit);
- _testRegisterAsOperator(sender, IDelegationTerms(sender));
-
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: sender,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(sender, operatorDetails);
cheats.startPrank(sender);
- //whitelist the serviceManager to slash the operator
- slasher.optIntoSlashing(address(serviceManager));
-
- voteWeigher.registerOperator(sender, _serveUntil);
-
cheats.stopPrank();
}
-
// registers the operator if they are not already registered, and deposits "WETH" + "EIGEN" on behalf of the staker.
- function _registerOperatorAndDepositFromStaker(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) internal {
+ function _registerOperatorAndDepositFromStaker(
+ address operator,
+ address staker,
+ uint96 ethAmount,
+ uint96 eigenAmount
+ ) internal {
cheats.assume(staker != operator);
// if first deposit amount to base strategy is too small, it will revert. ignore that case here.
@@ -520,12 +1532,17 @@ contract DelegationTests is EigenLayerTestHelper {
cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18);
if (!delegation.isOperator(operator)) {
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(operator, operatorDetails);
}
//making additional deposits to the strategies
- assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate");
+ assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
_testDepositWeth(staker, ethAmount);
_testDepositEigen(staker, eigenAmount);
}
-}
\ No newline at end of file
+}
diff --git a/src/test/DelegationFaucet.t.sol b/src/test/DelegationFaucet.t.sol
new file mode 100644
index 0000000000..e3f04fa75a
--- /dev/null
+++ b/src/test/DelegationFaucet.t.sol
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "script/whitelist/delegationFaucet/DelegationFaucet.sol";
+import "script/whitelist/ERC20PresetMinterPauser.sol";
+
+import "src/test/EigenLayerTestHelper.t.sol";
+
+contract DelegationFaucetTests is EigenLayerTestHelper {
+ // EigenLayer contracts
+ DelegationFaucet delegationFaucet;
+
+ // M2 testing/mock contracts
+ ERC20PresetMinterPauser public stakeToken;
+ StrategyBase public stakeTokenStrat;
+
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
+ uint256 public constant DEFAULT_AMOUNT = 100e18;
+ address owner = cheats.addr(1000);
+
+ /// @notice Emitted when a queued withdrawal is completed
+ event WithdrawalCompleted(bytes32 withdrawalRoot);
+
+ function setUp() public virtual override {
+ EigenLayerDeployer.setUp();
+
+ // Deploy ERC20 stakeToken, StrategyBase, and add StrategyBase to whitelist
+ stakeToken = new ERC20PresetMinterPauser("StakeToken", "STK");
+ stakeTokenStrat = StrategyBase(
+ address(
+ new TransparentUpgradeableProxy(
+ address(baseStrategyImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(StrategyBase.initialize.selector, stakeToken, eigenLayerPauserReg)
+ )
+ )
+ );
+ cheats.startPrank(strategyManager.strategyWhitelister());
+ IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+ _strategy[0] = stakeTokenStrat;
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
+ cheats.stopPrank();
+
+ // Deploy DelegationFaucet, grant it admin/mint/pauser roles, etc.
+ delegationFaucet = new DelegationFaucet(strategyManager, delegation, stakeToken, stakeTokenStrat);
+ targetContract(address(delegationFaucet));
+ stakeToken.grantRole(MINTER_ROLE, address(delegationFaucet));
+ }
+
+ /**
+ * @notice Assertions in test
+ * - Checks staker contract is deployed
+ * - Checks token supply before/after minting
+ * - Checks token balances are updated correctly for staker and strategy contracts
+ * @param _operatorIndex is the index of the operator to use from the test-data/operators.json file
+ * @param _depositAmount is the amount of stakeToken to mint to the staker and deposit into the strategy
+ */
+ function test_mintDepositAndDelegate_CheckBalancesAndDeploys(uint8 _operatorIndex, uint256 _depositAmount) public {
+ cheats.assume(_operatorIndex < 15 && _depositAmount < DEFAULT_AMOUNT);
+ if (_depositAmount == 0) {
+ // Passing 0 as amount param defaults the amount to DEFAULT_AMOUNT constant
+ _depositAmount = DEFAULT_AMOUNT;
+ }
+ // Setup Operator
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ _registerOperator(operator);
+
+ // Mint token to Staker, deposit minted amount into strategy, and delegate to operator
+ uint256 supplyBefore = stakeToken.totalSupply();
+ uint256 stratBalanceBefore = stakeToken.balanceOf(address(stakeTokenStrat));
+ assertTrue(
+ !Address.isContract(stakerContract),
+ "test_mintDepositAndDelegate_CheckBalancesAndDeploys: staker contract shouldn't be deployed"
+ );
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), _depositAmount);
+ assertTrue(
+ Address.isContract(stakerContract),
+ "test_mintDepositAndDelegate_CheckBalancesAndDeploys: staker contract not deployed"
+ );
+ uint256 supplyAfter = stakeToken.totalSupply();
+ uint256 stratBalanceAfter = stakeToken.balanceOf(address(stakeTokenStrat));
+ // Check token supply and balances
+ assertEq(
+ supplyAfter,
+ supplyBefore + _depositAmount,
+ "test_mintDepositAndDelegate_CheckBalancesAndDeploys: token supply not updated correctly"
+ );
+ assertEq(
+ stratBalanceAfter,
+ stratBalanceBefore + _depositAmount,
+ "test_mintDepositAndDelegate_CheckBalancesAndDeploys: strategy balance not updated correctly"
+ );
+ assertEq(
+ stakeToken.balanceOf(stakerContract),
+ 0,
+ "test_mintDepositAndDelegate_CheckBalancesAndDeploys: staker balance should be 0"
+ );
+ }
+
+ /**
+ * @notice Check the before/after values for strategy shares and operator shares
+ * @param _operatorIndex is the index of the operator to use from the test-data/operators.json file
+ * @param _depositAmount is the amount of stakeToken to mint to the staker and deposit into the strategy
+ */
+ function test_mintDepositAndDelegate_StrategyAndOperatorShares(
+ uint8 _operatorIndex,
+ uint256 _depositAmount
+ ) public {
+ cheats.assume(_operatorIndex < 15 && _depositAmount < DEFAULT_AMOUNT);
+ if (_depositAmount == 0) {
+ _depositAmount = DEFAULT_AMOUNT;
+ }
+ // Setup Operator
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ _registerOperator(operator);
+
+ // Mint token to Staker, deposit minted amount into strategy, and delegate to operator
+ uint256 stakerSharesBefore = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 operatorSharesBefore = delegation.operatorShares(operator, stakeTokenStrat);
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), _depositAmount);
+
+ uint256 stakerSharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 operatorSharesAfter = delegation.operatorShares(operator, stakeTokenStrat);
+ assertTrue(
+ delegation.delegatedTo(stakerContract) == operator,
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: delegated address not set appropriately"
+ );
+ assertTrue(
+ delegation.isDelegated(stakerContract),
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: delegated status not set appropriately"
+ );
+ assertEq(
+ stakerSharesAfter,
+ stakerSharesBefore + _depositAmount,
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: staker shares not updated correctly"
+ );
+ assertEq(
+ operatorSharesAfter,
+ operatorSharesBefore + _depositAmount,
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: operator shares not updated correctly"
+ );
+ }
+
+ /**
+ * @notice Invariant test the before/after values for strategy shares and operator shares from multiple runs
+ */
+ /// forge-config: default.invariant.runs = 5
+ /// forge-config: default.invariant.depth = 20
+ function invariant_test_mintDepositAndDelegate_StrategyAndOperatorShares() public {
+ // Setup Operator
+ address operator = getOperatorAddress(0);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ _registerOperator(operator);
+
+ // Mint token to Staker, deposit minted amount into strategy, and delegate to operator
+ uint256 stakerSharesBefore = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 operatorSharesBefore = delegation.operatorShares(operator, stakeTokenStrat);
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), DEFAULT_AMOUNT);
+
+ uint256 stakerSharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 operatorSharesAfter = delegation.operatorShares(operator, stakeTokenStrat);
+ assertTrue(
+ delegation.delegatedTo(stakerContract) == operator,
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: delegated address not set appropriately"
+ );
+ assertTrue(
+ delegation.isDelegated(stakerContract),
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: delegated status not set appropriately"
+ );
+ assertEq(
+ stakerSharesAfter,
+ stakerSharesBefore + DEFAULT_AMOUNT,
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: staker shares not updated correctly"
+ );
+ assertEq(
+ operatorSharesAfter,
+ operatorSharesBefore + DEFAULT_AMOUNT,
+ "test_mintDepositAndDelegate_StrategyAndOperatorShares: operator shares not updated correctly"
+ );
+ }
+
+ /**
+ * @param _operatorIndex is the index of the operator to use from the test-data/operators.json file
+ */
+ function test_mintDepositAndDelegate_RevertsIf_UnregisteredOperator(uint8 _operatorIndex) public {
+ cheats.assume(_operatorIndex < 15);
+ address operator = getOperatorAddress(_operatorIndex);
+ // Unregistered operator should revert
+ cheats.expectRevert("DelegationFaucet: Operator not registered");
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), DEFAULT_AMOUNT);
+ }
+
+ function test_depositIntoStrategy_IncreaseShares(uint8 _operatorIndex, uint256 _depositAmount) public {
+ cheats.assume(_operatorIndex < 15 && _depositAmount < DEFAULT_AMOUNT);
+ if (_depositAmount == 0) {
+ _depositAmount = DEFAULT_AMOUNT;
+ }
+ // Setup Operator
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ _registerOperator(operator);
+
+ // Mint token to Staker, deposit minted amount into strategy, and delegate to operator
+ uint256 stakerSharesBefore = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 operatorSharesBefore = delegation.operatorShares(operator, stakeTokenStrat);
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), DEFAULT_AMOUNT);
+
+ uint256 stakerSharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 operatorSharesAfter = delegation.operatorShares(operator, stakeTokenStrat);
+ assertTrue(
+ delegation.delegatedTo(stakerContract) == operator,
+ "test_mintDepositAndDelegate_IncreaseShares: delegated address not set appropriately"
+ );
+ assertTrue(
+ delegation.isDelegated(stakerContract),
+ "test_mintDepositAndDelegate_IncreaseShares: delegated status not set appropriately"
+ );
+ assertEq(
+ stakerSharesAfter,
+ stakerSharesBefore + DEFAULT_AMOUNT,
+ "test_mintDepositAndDelegate_IncreaseShares: staker shares not updated correctly"
+ );
+ assertEq(
+ operatorSharesAfter,
+ operatorSharesBefore + DEFAULT_AMOUNT,
+ "test_mintDepositAndDelegate_IncreaseShares: operator shares not updated correctly"
+ );
+
+ // Deposit more into strategy
+ stakerSharesBefore = stakerSharesAfter;
+ operatorSharesBefore = operatorSharesAfter;
+ delegationFaucet.depositIntoStrategy(stakerContract, stakeTokenStrat, stakeToken, _depositAmount);
+ stakerSharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ operatorSharesAfter = delegation.operatorShares(operator, stakeTokenStrat);
+
+ assertEq(
+ stakerSharesAfter,
+ stakerSharesBefore + _depositAmount,
+ "test_mintDepositAndDelegate_IncreasesShares: staker shares not updated correctly"
+ );
+ assertEq(
+ operatorSharesAfter,
+ operatorSharesBefore + _depositAmount,
+ "test_mintDepositAndDelegate_IncreasesShares: operator shares not updated correctly"
+ );
+ }
+
+ function test_queueWithdrawal_StakeTokenWithdraw(uint8 _operatorIndex, uint256 _withdrawAmount) public {
+ cheats.assume(_operatorIndex < 15 && 0 < _withdrawAmount && _withdrawAmount < DEFAULT_AMOUNT);
+ // Setup Operator
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ _registerOperator(operator);
+
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), DEFAULT_AMOUNT);
+
+ uint256 operatorSharesBefore = delegation.operatorShares(operator, stakeTokenStrat);
+ uint256 stakerSharesBefore = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 nonceBefore = delegation.cumulativeWithdrawalsQueued(/*staker*/ stakerContract);
+
+ // Queue withdrawal
+ (
+ IDelegationManager.Withdrawal memory queuedWithdrawal,
+ , /*tokensArray is unused in this test*/
+ /*withdrawalRoot is unused in this test*/
+ ) = _setUpQueuedWithdrawalStructSingleStrat(
+ /*staker*/ stakerContract,
+ /*withdrawer*/ stakerContract,
+ stakeToken,
+ stakeTokenStrat,
+ _withdrawAmount
+ );
+ IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ params[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: queuedWithdrawal.strategies,
+ shares: queuedWithdrawal.shares,
+ withdrawer: stakerContract
+ });
+
+ delegationFaucet.queueWithdrawal(
+ stakerContract,
+ params
+ );
+ uint256 operatorSharesAfter = delegation.operatorShares(operator, stakeTokenStrat);
+ uint256 stakerSharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 nonceAfter = delegation.cumulativeWithdrawalsQueued(/*staker*/ stakerContract);
+
+ assertEq(
+ operatorSharesBefore,
+ operatorSharesAfter + _withdrawAmount,
+ "test_queueWithdrawal_WithdrawStakeToken: operator shares not updated correctly"
+ );
+ // Withdrawal queued, but not withdrawn as of yet
+ assertEq(
+ stakerSharesBefore,
+ stakerSharesAfter + _withdrawAmount,
+ "test_queueWithdrawal_WithdrawStakeToken: staker shares not updated correctly"
+ );
+ assertEq(
+ nonceBefore,
+ nonceAfter - 1,
+ "test_queueWithdrawal_WithdrawStakeToken: staker withdrawal nonce not updated"
+ );
+ }
+
+ function test_completeQueuedWithdrawal_ReceiveAsTokensMarkedFalse(
+ uint8 _operatorIndex,
+ uint256 _withdrawAmount
+ ) public {
+ test_queueWithdrawal_StakeTokenWithdraw(_operatorIndex, _withdrawAmount);
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ // assertion before values
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 balanceBefore = stakeToken.balanceOf(address(stakerContract));
+
+ // Set completeQueuedWithdrawal params
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ IERC20[] memory tokensArray = new IERC20[](1);
+ uint256[] memory shareAmounts = new uint256[](1);
+ {
+ strategyArray[0] = stakeTokenStrat;
+ shareAmounts[0] = _withdrawAmount;
+ tokensArray[0] = stakeToken;
+ }
+
+ IDelegationManager.Withdrawal memory queuedWithdrawal;
+ {
+ uint256 nonce = delegation.cumulativeWithdrawalsQueued(stakerContract);
+
+ queuedWithdrawal = IDelegationManager.Withdrawal({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ staker: stakerContract,
+ withdrawer: stakerContract,
+ nonce: (nonce - 1),
+ startBlock: uint32(block.number),
+ delegatedTo: strategyManager.delegation().delegatedTo(stakerContract)
+ });
+ }
+ cheats.expectEmit(true, true, true, true, address(delegation));
+ emit WithdrawalCompleted(delegation.calculateWithdrawalRoot(queuedWithdrawal));
+ uint256 middlewareTimesIndex = 0;
+ bool receiveAsTokens = false;
+ delegationFaucet.completeQueuedWithdrawal(
+ stakerContract,
+ queuedWithdrawal,
+ tokensArray,
+ middlewareTimesIndex,
+ receiveAsTokens
+ );
+ // assertion after values
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 balanceAfter = stakeToken.balanceOf(address(stakerContract));
+ assertEq(
+ sharesBefore + _withdrawAmount,
+ sharesAfter,
+ "test_completeQueuedWithdrawal_ReceiveAsTokensMarkedFalse: staker shares not updated correctly"
+ );
+ assertEq(
+ balanceBefore,
+ balanceAfter,
+ "test_completeQueuedWithdrawal_ReceiveAsTokensMarkedFalse: stakerContract balance not updated correctly"
+ );
+ }
+
+ function test_completeQueuedWithdrawal_ReceiveAsTokensMarkedTrue(
+ uint8 _operatorIndex,
+ uint256 _withdrawAmount
+ ) public {
+ test_queueWithdrawal_StakeTokenWithdraw(_operatorIndex, _withdrawAmount);
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ // assertion before values
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 balanceBefore = stakeToken.balanceOf(address(stakerContract));
+
+ // Set completeQueuedWithdrawal params
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ IERC20[] memory tokensArray = new IERC20[](1);
+ uint256[] memory shareAmounts = new uint256[](1);
+ {
+ strategyArray[0] = stakeTokenStrat;
+ shareAmounts[0] = _withdrawAmount;
+ tokensArray[0] = stakeToken;
+ }
+
+ IDelegationManager.Withdrawal memory queuedWithdrawal;
+ {
+ uint256 nonce = delegation.cumulativeWithdrawalsQueued(stakerContract);
+
+ queuedWithdrawal = IDelegationManager.Withdrawal({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ staker: stakerContract,
+ withdrawer: stakerContract,
+ nonce: (nonce - 1),
+ startBlock: uint32(block.number),
+ delegatedTo: strategyManager.delegation().delegatedTo(stakerContract)
+ });
+ }
+ cheats.expectEmit(true, true, true, true, address(delegation));
+ emit WithdrawalCompleted(delegation.calculateWithdrawalRoot(queuedWithdrawal));
+ uint256 middlewareTimesIndex = 0;
+ bool receiveAsTokens = true;
+ delegationFaucet.completeQueuedWithdrawal(
+ stakerContract,
+ queuedWithdrawal,
+ tokensArray,
+ middlewareTimesIndex,
+ receiveAsTokens
+ );
+ // assertion after values
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(stakerContract, stakeTokenStrat);
+ uint256 balanceAfter = stakeToken.balanceOf(address(stakerContract));
+ assertEq(
+ sharesBefore,
+ sharesAfter,
+ "test_completeQueuedWithdrawal_ReceiveAsTokensMarkedTrue: staker shares not updated correctly"
+ );
+ assertEq(
+ balanceBefore + _withdrawAmount,
+ balanceAfter,
+ "test_completeQueuedWithdrawal_ReceiveAsTokensMarkedTrue: stakerContract balance not updated correctly"
+ );
+ }
+
+ function test_transfer_TransfersERC20(uint8 _operatorIndex, address _to, uint256 _transferAmount) public {
+ cheats.assume(_operatorIndex < 15);
+ // Setup Operator
+ address operator = getOperatorAddress(_operatorIndex);
+ address stakerContract = delegationFaucet.getStaker(operator);
+ _registerOperator(operator);
+
+ // Mint token to Staker, deposit minted amount into strategy, and delegate to operator
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegationFaucet.mintDepositAndDelegate(operator, signatureWithExpiry, bytes32(0), DEFAULT_AMOUNT);
+
+ ERC20PresetMinterPauser mockToken = new ERC20PresetMinterPauser("MockToken", "MTK");
+ mockToken.mint(stakerContract, _transferAmount);
+
+ uint256 stakerBalanceBefore = mockToken.balanceOf(stakerContract);
+ uint256 toBalanceBefore = mockToken.balanceOf(_to);
+ delegationFaucet.transfer(stakerContract, address(mockToken), _to, _transferAmount);
+ uint256 stakerBalanceAfter = mockToken.balanceOf(stakerContract);
+ uint256 toBalanceAfter = mockToken.balanceOf(_to);
+ assertEq(
+ stakerBalanceBefore,
+ stakerBalanceAfter + _transferAmount,
+ "test_transfer_TransfersERC20: staker balance not updated correctly"
+ );
+ assertEq(
+ toBalanceBefore + _transferAmount,
+ toBalanceAfter,
+ "test_transfer_TransfersERC20: to balance not updated correctly"
+ );
+ }
+
+ function _registerOperator(address _operator) internal {
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: _operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(_operator, operatorDetails);
+ }
+
+ function _setUpQueuedWithdrawalStructSingleStrat(
+ address staker,
+ address withdrawer,
+ IERC20 token,
+ IStrategy strategy,
+ uint256 shareAmount
+ )
+ internal
+ view
+ returns (
+ IDelegationManager.Withdrawal memory queuedWithdrawal,
+ IERC20[] memory tokensArray,
+ bytes32 withdrawalRoot
+ )
+ {
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ tokensArray = new IERC20[](1);
+ uint256[] memory shareAmounts = new uint256[](1);
+ strategyArray[0] = strategy;
+ tokensArray[0] = token;
+ shareAmounts[0] = shareAmount;
+ queuedWithdrawal = IDelegationManager.Withdrawal({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ staker: staker,
+ withdrawer: withdrawer,
+ nonce: delegation.cumulativeWithdrawalsQueued(staker),
+ startBlock: uint32(block.number),
+ delegatedTo: strategyManager.delegation().delegatedTo(staker)
+ });
+ // calculate the withdrawal root
+ withdrawalRoot = delegation.calculateWithdrawalRoot(queuedWithdrawal);
+ return (queuedWithdrawal, tokensArray, withdrawalRoot);
+ }
+}
diff --git a/src/test/DepositWithdraw.t.sol b/src/test/DepositWithdraw.t.sol
index 25dfff4938..50fb7b8d32 100644
--- a/src/test/DepositWithdraw.t.sol
+++ b/src/test/DepositWithdraw.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "./EigenLayerTestHelper.t.sol";
import "../contracts/core/StrategyManagerStorage.sol";
@@ -18,217 +18,6 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
return _testDepositWeth(getOperatorAddress(0), amountToDeposit);
}
- function testPreventSlashing() public {
- //use preexisting helper function to set up a withdrawal
- address middleware = address(0xdeadbeef);
- address staker = getOperatorAddress(0);
- uint256 depositAmount = 1 ether;
- IStrategy strategy = wethStrat;
- IStrategy[] memory strategyArray = new IStrategy[](1);
- strategyArray[0] = strategy;
-
- //invalid token
- IERC20[] memory tokensArray = new IERC20[](1);
- tokensArray[0] = IERC20(address(0));
-
- uint256[] memory shareAmounts = new uint256[](1);
- shareAmounts[0] = depositAmount - 1 gwei; //leave some shares behind so we don't get undelegation issues
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
- address withdrawer = staker;
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- ( ,queuedWithdrawal) = _createQueuedWithdrawal(staker,
- true,
- depositAmount,
- strategyArray,
- shareAmounts,
- strategyIndexes,
- withdrawer
- );
-
- cheats.startPrank(staker);
- //opt in staker to restake for the two middlewares we are using
- slasher.optIntoSlashing(middleware);
- cheats.stopPrank();
-
- //move ahead a block after queuing the withdrawal
- cheats.roll(2);
-
- cheats.startPrank(middleware);
- // stake update with updateBlock = 2, serveUntilBlock = 5
- uint32 serveUntilBlock = 5;
- slasher.recordFirstStakeUpdate(staker, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.roll(6);
-
- // freeze the staker
- cheats.startPrank(middleware);
- slasher.freezeOperator(staker);
- cheats.stopPrank();
-
- // attempt to slash - reverts
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert("StrategyBase.withdraw: Can only withdraw the strategy token");
- strategyManager.slashQueuedWithdrawal(address(slasher), queuedWithdrawal, tokensArray, emptyUintArray);
- cheats.stopPrank();
-
- //staker is unfrozen at a future date
- address[] memory addressArray = new address[](1);
- addressArray[0] = staker;
- cheats.startPrank(slasher.owner());
- slasher.resetFrozenStatus(addressArray);
- cheats.stopPrank();
-
- // staker can still withdraw shares
- cheats.startPrank(staker);
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, 0, false);
- cheats.stopPrank();
- }
-
- function testWithdrawalSequences() public {
- //use preexisting helper function to set up a withdrawal
- address middleware = address(0xdeadbeef);
- address middleware_2 = address(0x009849);
- address staker = getOperatorAddress(0);
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- uint256 depositAmount = 1 ether;
- IStrategy strategy = wethStrat;
- IERC20 underlyingToken = weth;
- IStrategy[] memory strategyArray = new IStrategy[](1);
- strategyArray[0] = strategy;
- IERC20[] memory tokensArray = new IERC20[](1);
- tokensArray[0] = underlyingToken;
- {
- uint256[] memory shareAmounts = new uint256[](1);
- shareAmounts[0] = depositAmount - 1 gwei; //leave some shares behind so we don't get undelegation issues
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
- address withdrawer = staker;
-
- {
- assertTrue(!delegation.isDelegated(staker), "_createQueuedWithdrawal: staker is already delegated");
- _testRegisterAsOperator(staker, IDelegationTerms(staker));
- assertTrue(
- delegation.isDelegated(staker), "_createQueuedWithdrawal: staker isn't delegated when they should be"
- );
-
- //make deposit in WETH strategy
- uint256 amountDeposited = _testDepositWeth(staker, depositAmount);
- // We can't withdraw more than we deposit
- if (shareAmounts[0] > amountDeposited) {
- cheats.expectRevert("StrategyManager._removeShares: shareAmount too high");
- }
- }
-
-
- cheats.startPrank(staker);
- //opt in staker to restake for the two middlewares we are using
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_2);
- cheats.stopPrank();
-
- cheats.startPrank(middleware);
- // first stake update with updateBlock = 1, serveUntilBlock = 5
-
- uint32 serveUntilBlock = 5;
- slasher.recordFirstStakeUpdate(staker, serveUntilBlock);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexBlock(staker, 0) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 0) == 5, "middleware serveUntil update incorrect");
-
-
- cheats.startPrank(middleware_2);
- // first stake update with updateBlock = 1, serveUntilBlock = 6
- slasher.recordFirstStakeUpdate(staker, serveUntilBlock+1);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexBlock(staker, 1) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 1) == 6, "middleware serveUntil update incorrect");
- //check old entry has not changed
- require(slasher.getMiddlewareTimesIndexBlock(staker, 0) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 0) == 5, "middleware serveUntil update incorrect");
-
- //move ahead a block before queuing the withdrawal
- cheats.roll(2);
- //cheats.startPrank(staker);
- //queue the withdrawal
- ( ,queuedWithdrawal) = _createOnlyQueuedWithdrawal(staker,
- true,
- depositAmount,
- strategyArray,
- tokensArray,
- shareAmounts,
- strategyIndexes,
- withdrawer
- );
-
- }
- //Because the staker has queued a withdrawal both currently staked middlewares must issued an update as required for the completion of the withdrawal
- //to be realistic we move ahead a block before updating middlewares
- cheats.roll(3);
-
- cheats.startPrank(middleware);
- // stake update with updateBlock = 3, serveUntilBlock = 7
- uint32 serveUntilBlock = 7;
- uint32 updateBlock = 3;
- uint256 insertAfter = 1;
- slasher.recordStakeUpdate(staker, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexBlock(staker, 2) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 2) == 7, "middleware serveUntil update incorrect");
-
- cheats.startPrank(middleware_2);
- // stake update with updateBlock = 3, serveUntilBlock = 10
- slasher.recordStakeUpdate(staker, updateBlock, serveUntilBlock+3, insertAfter);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexBlock(staker, 3) == 3, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 3) == 10, "middleware serveUntil update incorrect");
-
- cheats.startPrank(middleware);
- // stake update with updateBlock = 3, serveUntilBlock = 7
- serveUntilBlock = 7;
- updateBlock = 3;
- insertAfter = 2;
- slasher.recordStakeUpdate(staker, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexBlock(staker, 4) == 3, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 4) == 10, "middleware serveUntil update incorrect");
-
- //move timestamp to 6, one middleware is past serveUntilBlock but the second middleware is still using the restaked funds.
- cheats.warp(8);
- //Also move the current block ahead one
- cheats.roll(4);
-
- cheats.startPrank(staker);
- //when called with the correct middlewareTimesIndex the call reverts
-
- slasher.getMiddlewareTimesIndexBlock(staker, 3);
-
-
- {
- uint256 correctMiddlewareTimesIndex = 4;
- cheats.expectRevert("StrategyManager.completeQueuedWithdrawal: shares pending withdrawal are still slashable");
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, correctMiddlewareTimesIndex, false);
- }
-
- //When called with a stale index the call should also revert.
- {
- uint256 staleMiddlewareTimesIndex = 2;
- cheats.expectRevert("StrategyManager.completeQueuedWithdrawal: shares pending withdrawal are still slashable");
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, staleMiddlewareTimesIndex, false);
- }
-
-
- }
-
/// @notice deploys 'numStratsToAdd' strategies using '_testAddStrategy' and then deposits '1e18' to each of them from 'getOperatorAddress(0)'
/// @param numStratsToAdd is the number of strategies being added and deposited into
@@ -262,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"));
@@ -298,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();
@@ -311,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"));
@@ -323,8 +115,8 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
/**
* @notice Modified from existing _createQueuedWithdrawal, skips delegation and deposit steps so that we can isolate the withdrawal step
* @notice Creates a queued withdrawal from `staker`, queues a withdrawal using
- * `strategyManager.queueWithdrawal(strategyIndexes, strategyArray, tokensArray, shareAmounts, withdrawer)`
- * @notice After initiating a queued withdrawal, this test checks that `strategyManager.canCompleteQueuedWithdrawal` immediately returns the correct
+ * `delegation.queueWithdrawal(strategyIndexes, strategyArray, tokensArray, shareAmounts, withdrawer)`
+ * @notice After initiating a queued withdrawal, this test checks that `delegation.canCompleteQueuedWithdrawal` immediately returns the correct
* response depending on whether `staker` is delegated or not.
* @param staker The address to initiate the queued withdrawal
* @param amountToDeposit The amount of WETH to deposit
@@ -336,34 +128,39 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
IStrategy[] memory strategyArray,
IERC20[] memory /*tokensArray*/,
uint256[] memory shareAmounts,
- uint256[] memory strategyIndexes,
+ uint256[] memory /*strategyIndexes*/,
address withdrawer
)
- internal returns(bytes32 withdrawalRoot, IStrategyManager.QueuedWithdrawal memory queuedWithdrawal)
+ internal returns(bytes32 withdrawalRoot, IDelegationManager.Withdrawal memory queuedWithdrawal)
{
require(amountToDeposit >= shareAmounts[0], "_createQueuedWithdrawal: sanity check failed");
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
+ queuedWithdrawal = IDelegationManager.Withdrawal({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ staker: staker,
withdrawer: withdrawer,
- nonce: uint96(strategyManager.numWithdrawalsQueued(staker))
+ nonce: delegation.cumulativeWithdrawalsQueued(staker),
+ delegatedTo: delegation.delegatedTo(staker),
+ startBlock: uint32(block.number)
});
- queuedWithdrawal = IStrategyManager.QueuedWithdrawal({
+
+ IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ params[0] = IDelegationManager.QueuedWithdrawalParams({
strategies: strategyArray,
shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- delegatedAddress: delegation.delegatedTo(staker),
- withdrawalStartBlock: uint32(block.number)
+ withdrawer: withdrawer
});
-
+ bytes32[] memory withdrawalRoots = new bytes32[](1);
//queue the withdrawal
cheats.startPrank(staker);
- withdrawalRoot = strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, withdrawer, true);
+ withdrawalRoots = delegation.queueWithdrawals(params);
cheats.stopPrank();
- return (withdrawalRoot, queuedWithdrawal);
+ return (withdrawalRoots[0], queuedWithdrawal);
}
@@ -484,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();
}
@@ -529,8 +327,12 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
uint64 amountToDeposit = 1e12;
// shadow-fork mainnet
- uint256 forkId = cheats.createFork("mainnet");
- cheats.selectFork(forkId);
+ try cheats.createFork("mainnet") returns (uint256 forkId) {
+ cheats.selectFork(forkId);
+ // If RPC_MAINNET ENV not set, default to this mainnet RPC endpoint
+ } catch {
+ cheats.createSelectFork("https://eth.llamarpc.com");
+ }
// cast mainnet stETH address to IERC20 interface
// IERC20 steth = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84);
@@ -564,21 +366,16 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
- {
- address[] memory initialOracleSignersArray = new address[](0);
- beaconChainOracle = new BeaconChainOracle(eigenLayerReputedMultisig, initialBeaconChainOracleThreshold, initialOracleSignersArray);
- }
-
ethPOSDeposit = new ETHPOSDepositMock();
- pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, REQUIRED_BALANCE_WEI);
+ pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, GOERLI_GENESIS_TIME);
eigenPodBeacon = new UpgradeableBeacon(address(pod));
// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
- DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher);
+ DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
StrategyManager strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
Slasher slasherImplementation = new Slasher(strategyManager, delegation);
- EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher);
+ EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher, delegation);
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delegation))),
@@ -587,7 +384,10 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
DelegationManager.initialize.selector,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
- 0/*initialPausedStatus*/
+ 0 /*initialPausedStatus*/,
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -598,8 +398,7 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
eigenLayerReputedMultisig,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
- 0/*initialPausedStatus*/,
- 0/*withdrawalDelayBlocks*/
+ 0/*initialPausedStatus*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -617,8 +416,7 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
address(eigenPodManagerImplementation),
abi.encodeWithSelector(
EigenPodManager.initialize.selector,
- type(uint256).max,
- beaconChainOracle,
+ beaconChainOracleAddress,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0/*initialPausedStatus*/
@@ -653,8 +451,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();
}
@@ -697,10 +496,11 @@ 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;
}
-}
\ No newline at end of file
+}
diff --git a/src/test/EigenLayerDeployer.t.sol b/src/test/EigenLayerDeployer.t.sol
index 5885ae214a..5912edb24c 100644
--- a/src/test/EigenLayerDeployer.t.sol
+++ b/src/test/EigenLayerDeployer.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
@@ -12,7 +12,6 @@ import "../contracts/core/DelegationManager.sol";
import "../contracts/interfaces/IETHPOSDeposit.sol";
import "../contracts/interfaces/IBeaconChainOracle.sol";
-import "../contracts/interfaces/IVoteWeigher.sol";
import "../contracts/core/StrategyManager.sol";
import "../contracts/strategies/StrategyBase.sol";
@@ -21,21 +20,19 @@ import "../contracts/core/Slasher.sol";
import "../contracts/pods/EigenPod.sol";
import "../contracts/pods/EigenPodManager.sol";
import "../contracts/pods/DelayedWithdrawalRouter.sol";
-import "../contracts/pods/BeaconChainOracle.sol";
import "../contracts/permissions/PauserRegistry.sol";
-
import "./utils/Operators.sol";
import "./mocks/LiquidStakingToken.sol";
import "./mocks/EmptyContract.sol";
import "./mocks/ETHDepositMock.sol";
+import "./mocks/BeaconChainOracleMock.sol";
import "forge-std/Test.sol";
contract EigenLayerDeployer is Operators {
-
Vm cheats = Vm(HEVM_ADDRESS);
// EigenLayer contracts
@@ -50,7 +47,6 @@ contract EigenLayerDeployer is Operators {
IDelayedWithdrawalRouter public delayedWithdrawalRouter;
IETHPOSDeposit public ethPOSDeposit;
IBeacon public eigenPodBeacon;
- IBeaconChainOracle public beaconChainOracle;
// testing/mock contracts
IERC20 public eigenToken;
@@ -77,9 +73,14 @@ contract EigenLayerDeployer is Operators {
uint256 public constant eigenTotalSupply = 1000e18;
uint256 nonce = 69;
uint256 public gasLimit = 750000;
+ IStrategy[] public initializeStrategiesToSetDelayBlocks;
+ uint256[] public initializeWithdrawalDelayBlocks;
+ uint256 minWithdrawalDelayBlocks = 0;
uint32 PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS = 7 days / 12 seconds;
- uint256 REQUIRED_BALANCE_WEI = 31 ether;
+ uint256 REQUIRED_BALANCE_WEI = 32 ether;
uint64 MAX_PARTIAL_WTIHDRAWAL_AMOUNT_GWEI = 1 ether / 1e9;
+ uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9;
+ uint64 GOERLI_GENESIS_TIME = 1616508000;
address pauser;
address unpauser;
@@ -104,15 +105,12 @@ contract EigenLayerDeployer is Operators {
address operationsMultisig;
address executorMultisig;
-
uint256 public initialBeaconChainOracleThreshold = 3;
- string internal goerliDeploymentConfig = vm.readFile("script/output/M1_deployment_goerli_2023_3_23.json");
-
+ string internal goerliDeploymentConfig = vm.readFile("script/output/goerli/M1_deployment_goerli_2023_3_23.json");
// addresses excluded from fuzzing due to abnormal behavior. TODO: @Sidu28 define this better and give it a clearer name
- mapping (address => bool) fuzzedAddressMapping;
-
+ mapping(address => bool) fuzzedAddressMapping;
//ensures that a passed in address is not set to true in the fuzzedAddressMapping
modifier fuzzedAddress(address addr) virtual {
@@ -128,11 +126,15 @@ contract EigenLayerDeployer is Operators {
//performs basic deployment before each test
// for fork tests run: forge test -vv --fork-url https://eth-goerli.g.alchemy.com/v2/demo -vv
function setUp() public virtual {
- if(vm.envUint("CHAIN_ID") == 31337) {
+ try vm.envUint("CHAIN_ID") returns (uint256 chainId) {
+ if (chainId == 31337) {
+ _deployEigenLayerContractsLocal();
+ } else if (chainId == 5) {
+ _deployEigenLayerContractsGoerli();
+ }
+ // If CHAIN_ID ENV is not set, assume local deployment on 31337
+ } catch {
_deployEigenLayerContractsLocal();
-
- }else if(vm.envUint("CHAIN_ID") == 5) {
- _deployEigenLayerContractsGoerli();
}
fuzzedAddressMapping[address(0)] = true;
@@ -151,7 +153,7 @@ contract EigenLayerDeployer is Operators {
eigenLayerProxyAdmin = ProxyAdmin(eigenLayerProxyAdminAddress);
emptyContract = new EmptyContract();
-
+
//deploy pauser registry
eigenLayerPauserReg = PauserRegistry(eigenLayerPauserRegAddress);
@@ -161,23 +163,21 @@ contract EigenLayerDeployer is Operators {
eigenPodManager = EigenPodManager(eigenPodManagerAddress);
delayedWithdrawalRouter = DelayedWithdrawalRouter(delayedWithdrawalRouterAddress);
- address[] memory initialOracleSignersArray = new address[](0);
- beaconChainOracle = new BeaconChainOracle(eigenLayerReputedMultisig, initialBeaconChainOracleThreshold, initialOracleSignersArray);
+ beaconChainOracleAddress = address(new BeaconChainOracleMock());
ethPOSDeposit = new ETHPOSDepositMock();
- pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, REQUIRED_BALANCE_WEI);
+ pod = new EigenPod(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
eigenPodBeacon = new UpgradeableBeacon(address(pod));
-
-
//simple ERC20 (**NOT** WETH-like!), used in a test strategy
- weth = new ERC20PresetFixedSupply(
- "weth",
- "WETH",
- wethInitialSupply,
- address(this)
- );
+ weth = new ERC20PresetFixedSupply("weth", "WETH", wethInitialSupply, address(this));
// deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it
baseStrategyImplementation = new StrategyBase(strategyManager);
@@ -191,12 +191,7 @@ contract EigenLayerDeployer is Operators {
)
);
- eigenToken = new ERC20PresetFixedSupply(
- "eigen",
- "EIGEN",
- wethInitialSupply,
- address(this)
- );
+ eigenToken = new ERC20PresetFixedSupply("eigen", "EIGEN", wethInitialSupply, address(this));
// deploy upgradeable proxy that points to StrategyBase implementation and initialize it
eigenStrat = StrategyBase(
@@ -244,19 +239,28 @@ contract EigenLayerDeployer is Operators {
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
- address[] memory initialOracleSignersArray = new address[](0);
- beaconChainOracle = new BeaconChainOracle(eigenLayerReputedMultisig, initialBeaconChainOracleThreshold, initialOracleSignersArray);
-
ethPOSDeposit = new ETHPOSDepositMock();
- pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, REQUIRED_BALANCE_WEI);
+ pod = new EigenPod(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
eigenPodBeacon = new UpgradeableBeacon(address(pod));
// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
- DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher);
+ DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
StrategyManager strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
Slasher slasherImplementation = new Slasher(strategyManager, delegation);
- EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher);
+ EigenPodManager eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegation
+ );
DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
@@ -267,7 +271,10 @@ contract EigenLayerDeployer is Operators {
DelegationManager.initialize.selector,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
- 0/*initialPausedStatus*/
+ 0 /*initialPausedStatus*/,
+ minWithdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -278,8 +285,7 @@ contract EigenLayerDeployer is Operators {
eigenLayerReputedMultisig,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
- 0/*initialPausedStatus*/,
- 0/*withdrawalDelayBlocks*/
+ 0 /*initialPausedStatus*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -289,7 +295,7 @@ contract EigenLayerDeployer is Operators {
Slasher.initialize.selector,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
- 0/*initialPausedStatus*/
+ 0 /*initialPausedStatus*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -297,11 +303,10 @@ contract EigenLayerDeployer is Operators {
address(eigenPodManagerImplementation),
abi.encodeWithSelector(
EigenPodManager.initialize.selector,
- type(uint256).max, // maxPods
- beaconChainOracle,
+ beaconChainOracleAddress,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
- 0/*initialPausedStatus*/
+ 0 /*initialPausedStatus*/
)
);
uint256 initPausedStatus = 0;
@@ -309,20 +314,17 @@ contract EigenLayerDeployer is Operators {
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
address(delayedWithdrawalRouterImplementation),
- abi.encodeWithSelector(DelayedWithdrawalRouter.initialize.selector,
- eigenLayerReputedMultisig,
- eigenLayerPauserReg,
- initPausedStatus,
- withdrawalDelayBlocks)
+ abi.encodeWithSelector(
+ DelayedWithdrawalRouter.initialize.selector,
+ eigenLayerReputedMultisig,
+ eigenLayerPauserReg,
+ initPausedStatus,
+ withdrawalDelayBlocks
+ )
);
//simple ERC20 (**NOT** WETH-like!), used in a test strategy
- weth = new ERC20PresetFixedSupply(
- "weth",
- "WETH",
- wethInitialSupply,
- address(this)
- );
+ weth = new ERC20PresetFixedSupply("weth", "WETH", wethInitialSupply, address(this));
// deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it
baseStrategyImplementation = new StrategyBase(strategyManager);
@@ -336,12 +338,7 @@ contract EigenLayerDeployer is Operators {
)
);
- eigenToken = new ERC20PresetFixedSupply(
- "eigen",
- "EIGEN",
- wethInitialSupply,
- address(this)
- );
+ eigenToken = new ERC20PresetFixedSupply("eigen", "EIGEN", wethInitialSupply, address(this));
// deploy upgradeable proxy that points to StrategyBase implementation and initialize it
eigenStrat = StrategyBase(
@@ -358,16 +355,15 @@ contract EigenLayerDeployer is Operators {
}
function _setAddresses(string memory config) internal {
- eigenLayerProxyAdminAddress = stdJson.readAddress(config, ".addresses.eigenLayerProxyAdmin");
+ eigenLayerProxyAdminAddress = stdJson.readAddress(config, ".addresses.eigenLayerProxyAdmin");
eigenLayerPauserRegAddress = stdJson.readAddress(config, ".addresses.eigenLayerPauserReg");
delegationAddress = stdJson.readAddress(config, ".addresses.delegation");
strategyManagerAddress = stdJson.readAddress(config, ".addresses.strategyManager");
slasherAddress = stdJson.readAddress(config, ".addresses.slasher");
- eigenPodManagerAddress = stdJson.readAddress(config, ".addresses.eigenPodManager");
+ eigenPodManagerAddress = stdJson.readAddress(config, ".addresses.eigenPodManager");
delayedWithdrawalRouterAddress = stdJson.readAddress(config, ".addresses.delayedWithdrawalRouter");
emptyContractAddress = stdJson.readAddress(config, ".addresses.emptyContract");
operationsMultisig = stdJson.readAddress(config, ".parameters.operationsMultisig");
executorMultisig = stdJson.readAddress(config, ".parameters.executorMultisig");
}
-
-}
\ No newline at end of file
+}
diff --git a/src/test/EigenLayerTestHelper.t.sol b/src/test/EigenLayerTestHelper.t.sol
index 632a2f437f..5bc4296114 100644
--- a/src/test/EigenLayerTestHelper.t.sol
+++ b/src/test/EigenLayerTestHelper.t.sol
@@ -1,11 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../test/EigenLayerDeployer.t.sol";
-
+import "../contracts/interfaces/ISignatureUtils.sol";
contract EigenLayerTestHelper is EigenLayerDeployer {
-
uint8 durationToInit = 2;
uint256 public SECP256K1N_MODULUS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint256 public SECP256K1N_MODULUS_HALF = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;
@@ -25,16 +24,18 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
function _testInitiateDelegation(
uint8 operatorIndex,
- uint256 amountEigenToDeposit,
- uint256 amountEthToDeposit
- )
- public returns (uint256 amountEthStaked, uint256 amountEigenStaked)
- {
-
+ uint256 amountEigenToDeposit,
+ uint256 amountEthToDeposit
+ ) public returns (uint256 amountEthStaked, uint256 amountEigenStaked) {
address operator = getOperatorAddress(operatorIndex);
-
+
//setting up operator's delegation terms
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(operator, operatorDetails);
for (uint256 i; i < stakers.length; i++) {
//initialize weth, eigen and eth balances for staker
@@ -54,7 +55,6 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
delegation.operatorShares(operator, eigenStrat) - operatorEigenSharesBefore == amountEigenToDeposit
);
assertTrue(delegation.operatorShares(operator, wethStrat) - operatorWETHSharesBefore == amountEthToDeposit);
-
}
amountEthStaked += delegation.operatorShares(operator, wethStrat);
amountEigenStaked += delegation.operatorShares(operator, eigenStrat);
@@ -63,19 +63,24 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
}
/**
- * @notice Register 'sender' as an operator, setting their 'DelegationTerms' contract in DelegationManager to 'dt', verifies
+ * @notice Register 'sender' as an operator, setting their 'OperatorDetails' in DelegationManager to 'operatorDetails', verifies
* that the storage of DelegationManager contract is updated appropriately
- *
+ *
* @param sender is the address being registered as an operator
- * @param dt is the sender's DelegationTerms contract
+ * @param operatorDetails is the `sender`'s OperatorDetails struct
*/
- function _testRegisterAsOperator(address sender, IDelegationTerms dt) internal {
+ function _testRegisterAsOperator(
+ address sender,
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) internal {
cheats.startPrank(sender);
- delegation.registerAsOperator(dt);
+ string memory emptyStringForMetadataURI;
+ delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
assertTrue(delegation.isOperator(sender), "testRegisterAsOperator: sender is not a operator");
assertTrue(
- delegation.delegationTerms(sender) == dt, "_testRegisterAsOperator: delegationTerms not set appropriately"
+ keccak256(abi.encode(delegation.operatorDetails(sender))) == keccak256(abi.encode(operatorDetails)),
+ "_testRegisterAsOperator: operatorDetails not set appropriately"
);
assertTrue(delegation.isDelegated(sender), "_testRegisterAsOperator: sender not marked as actively delegated");
@@ -114,11 +119,7 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
uint256 amountToDeposit,
IERC20 underlyingToken,
IStrategy stratToDepositTo
- )
- internal
- returns (uint256 amountDeposited)
- {
-
+ ) internal returns (uint256 amountDeposited) {
// deposits will revert when amountToDeposit is 0
cheats.assume(amountToDeposit > 0);
@@ -126,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();
}
@@ -153,12 +155,12 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
if (operatorSharesBefore == 0) {
// check that strategy is appropriately added to dynamic array of all of sender's strategies
assertTrue(
- strategyManager.stakerStrategyList(sender, strategyManager.stakerStrategyListLength(sender) - 1)
- == stratToDepositTo,
+ strategyManager.stakerStrategyList(sender, strategyManager.stakerStrategyListLength(sender) - 1) ==
+ stratToDepositTo,
"_testDepositToStrategy: stakerStrategyList array updated incorrectly"
);
}
-
+
// check that the shares out match the expected amount out
assertEq(
strategyManager.stakerStrategyShares(sender, stratToDepositTo) - operatorSharesBefore,
@@ -170,15 +172,14 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
}
/**
- * @notice tries to delegate from 'staker' to 'operator', verifies that staker has at least some shares
+ * @notice tries to delegate from 'staker' to 'operator', verifies that staker has at least some shares
* delegatedShares update correctly for 'operator' and delegated status is updated correctly for 'staker'
* @param staker the staker address to delegate from
* @param operator the operator address to delegate to
*/
function _testDelegateToOperator(address staker, address operator) internal {
//staker-specific information
- (IStrategy[] memory delegateStrategies, uint256[] memory delegateShares) =
- strategyManager.getDeposits(staker);
+ (IStrategy[] memory delegateStrategies, uint256[] memory delegateShares) = strategyManager.getDeposits(staker);
uint256 numStrats = delegateShares.length;
assertTrue(numStrats != 0, "_testDelegateToOperator: delegating from address with no deposits");
@@ -188,17 +189,15 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
}
cheats.startPrank(staker);
- delegation.delegateTo(operator);
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry;
+ delegation.delegateTo(operator, signatureWithExpiry, bytes32(0));
cheats.stopPrank();
assertTrue(
delegation.delegatedTo(staker) == operator,
"_testDelegateToOperator: delegated address not set appropriately"
);
- assertTrue(
- delegation.isDelegated(staker),
- "_testDelegateToOperator: delegated status not set appropriately"
- );
+ assertTrue(delegation.isDelegated(staker), "_testDelegateToOperator: delegated status not set appropriately");
for (uint256 i = 0; i < numStrats; ++i) {
uint256 operatorSharesBefore = inititalSharesInStrats[i];
@@ -211,9 +210,9 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
}
/**
- * @notice deploys 'numStratsToAdd' strategies contracts and initializes them to treat `underlyingToken` as their underlying token
+ * @notice deploys 'numStratsToAdd' strategies contracts and initializes them to treat `underlyingToken` as their underlying token
* and then deposits 'amountToDeposit' to each of them from 'sender'
- *
+ *
* @param sender address that is depositing into the strategies
* @param amountToDeposit amount being deposited
* @param numStratsToAdd number of strategies that are being deployed and deposited into
@@ -223,16 +222,14 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
IERC20 underlyingToken = weth;
cheats.assume(numStratsToAdd > 0 && numStratsToAdd <= 20);
- IStrategy[] memory stratsToDepositTo = new IStrategy[](
- numStratsToAdd
- );
+ IStrategy[] memory stratsToDepositTo = new IStrategy[](numStratsToAdd);
for (uint8 i = 0; i < numStratsToAdd; ++i) {
stratsToDepositTo[i] = StrategyBase(
address(
new TransparentUpgradeableProxy(
address(baseStrategyImplementation),
address(eigenLayerProxyAdmin),
- abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg)
+ abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg)
)
)
);
@@ -251,7 +248,6 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
}
}
-
/**
* @notice Creates a queued withdrawal from `staker`. Begins by registering the staker as a delegate (if specified), then deposits `amountToDeposit`
* into the WETH strategy, and then queues a withdrawal using
@@ -270,32 +266,32 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
uint256[] memory shareAmounts,
uint256[] memory strategyIndexes,
address withdrawer
- )
- internal returns(bytes32 withdrawalRoot, IStrategyManager.QueuedWithdrawal memory queuedWithdrawal)
- {
+ ) internal returns (bytes32 withdrawalRoot, IDelegationManager.Withdrawal memory queuedWithdrawal) {
require(amountToDeposit >= shareAmounts[0], "_createQueuedWithdrawal: sanity check failed");
// we do this here to ensure that `staker` is delegated if `registerAsOperator` is true
if (registerAsOperator) {
assertTrue(!delegation.isDelegated(staker), "_createQueuedWithdrawal: staker is already delegated");
- _testRegisterAsOperator(staker, IDelegationTerms(staker));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: staker,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(staker, operatorDetails);
assertTrue(
- delegation.isDelegated(staker), "_createQueuedWithdrawal: staker isn't delegated when they should be"
+ delegation.isDelegated(staker),
+ "_createQueuedWithdrawal: staker isn't delegated when they should be"
);
}
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: withdrawer,
- nonce: uint96(strategyManager.numWithdrawalsQueued(staker))
- });
-
- queuedWithdrawal = IStrategyManager.QueuedWithdrawal({
+ queuedWithdrawal = IDelegationManager.Withdrawal({
strategies: strategyArray,
shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- delegatedAddress: delegation.delegatedTo(staker),
- withdrawalStartBlock: uint32(block.number)
+ staker: staker,
+ withdrawer: withdrawer,
+ nonce: delegation.cumulativeWithdrawalsQueued(staker),
+ delegatedTo: delegation.delegatedTo(staker),
+ startBlock: uint32(block.number)
});
{
@@ -308,23 +304,22 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
}
//queue the withdrawal
- // TODO: check with 'undelegateIfPossible' = false, rather than just true
- withdrawalRoot = _testQueueWithdrawal(staker, strategyIndexes, strategyArray, shareAmounts, withdrawer, true);
+ withdrawalRoot = _testQueueWithdrawal(staker, strategyIndexes, strategyArray, shareAmounts, withdrawer);
return (withdrawalRoot, queuedWithdrawal);
}
- /**
- * Helper for ECDSA signatures: combines V and S into VS - if S is greater than SECP256K1N_MODULUS_HALF, then we
- * get the modulus, so that the leading bit of s is always 0. Then we set the leading
- * bit to be either 0 or 1 based on the value of v, which is either 27 or 28
- */
- function getVSfromVandS(uint8 v, bytes32 s) internal view returns(bytes32) {
+ /**
+ * Helper for ECDSA signatures: combines V and S into VS - if S is greater than SECP256K1N_MODULUS_HALF, then we
+ * get the modulus, so that the leading bit of s is always 0. Then we set the leading
+ * bit to be either 0 or 1 based on the value of v, which is either 27 or 28
+ */
+ function getVSfromVandS(uint8 v, bytes32 s) internal view returns (bytes32) {
if (uint256(s) > SECP256K1N_MODULUS_HALF) {
s = bytes32(SECP256K1N_MODULUS - uint256(s));
}
bytes32 vs = s;
- if(v == 28) {
+ if (v == 28) {
vs = bytes32(uint256(s) ^ (1 << 255));
}
@@ -335,55 +330,42 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
/// and checks that the operator's voteWeights increase properly
/// @param operator is the operator being delegated to.
/// @param staker is the staker delegating stake to the operator.
- /// @param voteWeigher is the VoteWeigher-type contract to consult for stake weight changes
- function _testDelegation(address operator, address staker, uint256 ethAmount, uint256 eigenAmount, IVoteWeigher voteWeigher)
- internal
- {
+ /// @param ethAmount is the amount of ETH to deposit into the operator's strategy.
+ /// @param eigenAmount is the amount of EIGEN to deposit into the operator's strategy.
+ function _testDelegation(
+ address operator,
+ address staker,
+ uint256 ethAmount,
+ uint256 eigenAmount
+ ) internal {
if (!delegation.isOperator(operator)) {
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(operator, operatorDetails);
}
- uint256[3] memory amountsBefore;
- amountsBefore[0] = voteWeigher.weightOfOperator(operator, 0);
- amountsBefore[1] = voteWeigher.weightOfOperator(operator, 1);
- amountsBefore[2] = delegation.operatorShares(operator, wethStrat);
+ uint256 amountBefore = delegation.operatorShares(operator, wethStrat);
//making additional deposits to the strategies
- assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate");
+ assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
_testDepositWeth(staker, ethAmount);
_testDepositEigen(staker, eigenAmount);
_testDelegateToOperator(staker, operator);
assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
- (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) =
- strategyManager.getDeposits(staker);
-
- {
- uint256 stakerEthWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[0]);
- uint256 stakerEigenWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[1]);
+ (/*IStrategy[] memory updatedStrategies*/, uint256[] memory updatedShares) = strategyManager.getDeposits(staker);
- uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(operator, 0);
- uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(operator, 1);
+ IStrategy _strat = wethStrat;
+ // IStrategy _strat = strategyManager.stakerStrategyList(staker, 0);
+ assertTrue(address(_strat) != address(0), "stakerStrategyList not updated correctly");
- assertTrue(
- operatorEthWeightAfter - amountsBefore[0] == stakerEthWeight,
- "testDelegation: operatorEthWeight did not increment by the right amount"
- );
- assertTrue(
- operatorEigenWeightAfter - amountsBefore[1] == stakerEigenWeight,
- "Eigen weights did not increment by the right amount"
- );
- }
- {
- IStrategy _strat = wethStrat;
- // IStrategy _strat = strategyManager.stakerStrategyList(staker, 0);
- assertTrue(address(_strat) != address(0), "stakerStrategyList not updated correctly");
-
- assertTrue(
- delegation.operatorShares(operator, _strat) - updatedShares[0] == amountsBefore[2],
- "ETH operatorShares not updated correctly"
- );
- }
+ assertTrue(
+ delegation.operatorShares(operator, _strat) - updatedShares[0] == amountBefore,
+ "ETH operatorShares not updated correctly"
+ );
}
/**
@@ -393,7 +375,6 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
* @param tokensArray is the array of tokens to withdraw from said strategies
* @param shareAmounts is the array of shares to be withdrawn from said strategies
* @param delegatedTo is the address the staker has delegated their shares to
- * @param withdrawerAndNonce is a struct containing the withdrawer and the nonce of the withdrawal
* @param withdrawalStartBlock the block number of the original queued withdrawal
* @param middlewareTimesIndex index in the middlewareTimes array used to queue this withdrawal
*/
@@ -404,17 +385,15 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
IERC20[] memory tokensArray,
uint256[] memory shareAmounts,
address delegatedTo,
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce,
+ address withdrawer,
+ uint256 nonce,
uint32 withdrawalStartBlock,
uint256 middlewareTimesIndex
- )
- internal
- {
- cheats.startPrank(withdrawerAndNonce.withdrawer);
+ ) internal {
+ cheats.startPrank(withdrawer);
for (uint256 i = 0; i < strategyArray.length; i++) {
- sharesBefore.push(strategyManager.stakerStrategyShares(withdrawerAndNonce.withdrawer, strategyArray[i]));
-
+ sharesBefore.push(strategyManager.stakerStrategyShares(withdrawer, strategyArray[i]));
}
// emit log_named_uint("strategies", strategyArray.length);
// emit log_named_uint("tokens", tokensArray.length);
@@ -424,22 +403,22 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
// emit log_named_address("delegatedAddress", delegatedTo);
// emit log("************************************************************************************************");
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal = IStrategyManager.QueuedWithdrawal({
+ IDelegationManager.Withdrawal memory queuedWithdrawal = IDelegationManager.Withdrawal({
strategies: strategyArray,
shares: shareAmounts,
- depositor: depositor,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: withdrawalStartBlock,
- delegatedAddress: delegatedTo
+ staker: depositor,
+ withdrawer: withdrawer,
+ nonce: nonce,
+ startBlock: withdrawalStartBlock,
+ delegatedTo: delegatedTo
});
// complete the queued withdrawal
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, false);
+ delegation.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, false);
for (uint256 i = 0; i < strategyArray.length; i++) {
require(
- strategyManager.stakerStrategyShares(withdrawerAndNonce.withdrawer, strategyArray[i])
- == sharesBefore[i] + shareAmounts[i],
+ strategyManager.stakerStrategyShares(withdrawer, strategyArray[i]) == sharesBefore[i] + shareAmounts[i],
"_testCompleteQueuedWithdrawalShares: withdrawer shares not incremented"
);
}
@@ -453,7 +432,6 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
* @param tokensArray is the array of tokens to withdraw from said strategies
* @param shareAmounts is the array of shares to be withdrawn from said strategies
* @param delegatedTo is the address the staker has delegated their shares to
- * @param withdrawerAndNonce is a struct containing the withdrawer and the nonce of the withdrawal
* @param withdrawalStartBlock the block number of the original queued withdrawal
* @param middlewareTimesIndex index in the middlewareTimes array used to queue this withdrawal
*/
@@ -463,38 +441,39 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
IERC20[] memory tokensArray,
uint256[] memory shareAmounts,
address delegatedTo,
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce,
+ address withdrawer,
+ uint256 nonce,
uint32 withdrawalStartBlock,
uint256 middlewareTimesIndex
- )
- internal
- {
- cheats.startPrank(withdrawerAndNonce.withdrawer);
+ ) internal {
+ cheats.startPrank(withdrawer);
for (uint256 i = 0; i < strategyArray.length; i++) {
- balanceBefore.push(strategyArray[i].underlyingToken().balanceOf(withdrawerAndNonce.withdrawer));
+ balanceBefore.push(strategyArray[i].underlyingToken().balanceOf(withdrawer));
priorTotalShares.push(strategyArray[i].totalShares());
strategyTokenBalance.push(strategyArray[i].underlyingToken().balanceOf(address(strategyArray[i])));
}
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal = IStrategyManager.QueuedWithdrawal({
+
+ IDelegationManager.Withdrawal memory queuedWithdrawal = IDelegationManager.Withdrawal({
strategies: strategyArray,
shares: shareAmounts,
- depositor: depositor,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: withdrawalStartBlock,
- delegatedAddress: delegatedTo
+ staker: depositor,
+ withdrawer: withdrawer,
+ nonce: nonce,
+ startBlock: withdrawalStartBlock,
+ delegatedTo: delegatedTo
});
// complete the queued withdrawal
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, true);
+ delegation.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, true);
for (uint256 i = 0; i < strategyArray.length; i++) {
//uint256 strategyTokenBalance = strategyArray[i].underlyingToken().balanceOf(address(strategyArray[i]));
- uint256 tokenBalanceDelta = strategyTokenBalance[i] * shareAmounts[i] / priorTotalShares[i];
+ uint256 tokenBalanceDelta = (strategyTokenBalance[i] * shareAmounts[i]) / priorTotalShares[i];
+ // filter out unrealistic case, where the withdrawer is the strategy contract itself
+ cheats.assume(withdrawer != address(strategyArray[i]));
require(
- strategyArray[i].underlyingToken().balanceOf(withdrawerAndNonce.withdrawer)
- == balanceBefore[i] + tokenBalanceDelta,
+ strategyArray[i].underlyingToken().balanceOf(withdrawer) == balanceBefore[i] + tokenBalanceDelta,
"_testCompleteQueuedWithdrawalTokens: withdrawer balance not incremented"
);
}
@@ -503,26 +482,24 @@ contract EigenLayerTestHelper is EigenLayerDeployer {
function _testQueueWithdrawal(
address depositor,
- uint256[] memory strategyIndexes,
+ uint256[] memory /*strategyIndexes*/,
IStrategy[] memory strategyArray,
uint256[] memory shareAmounts,
- address withdrawer,
- bool undelegateIfPossible
- )
- internal
- returns (bytes32)
- {
+ address withdrawer
+ ) internal returns (bytes32) {
cheats.startPrank(depositor);
- bytes32 withdrawalRoot = strategyManager.queueWithdrawal(
- strategyIndexes,
- strategyArray,
- shareAmounts,
- withdrawer,
- // TODO: make this an input
- undelegateIfPossible
- );
+ IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+
+ params[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ withdrawer: withdrawer
+ });
+
+ bytes32[] memory withdrawalRoots = new bytes32[](1);
+ withdrawalRoots = delegation.queueWithdrawals(params);
cheats.stopPrank();
- return withdrawalRoot;
+ return withdrawalRoots[0];
}
-}
\ No newline at end of file
+}
diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol
index 80a942f07c..eb703d088d 100644
--- a/src/test/EigenPod.t.sol
+++ b/src/test/EigenPod.t.sol
@@ -1,34 +1,33 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../contracts/interfaces/IEigenPod.sol";
-import "../contracts/interfaces/IBLSPublicKeyCompendium.sol";
-import "../contracts/middleware/BLSPublicKeyCompendium.sol";
import "../contracts/pods/DelayedWithdrawalRouter.sol";
import "./utils/ProofParsing.sol";
import "./EigenLayerDeployer.t.sol";
-import "./mocks/MiddlewareRegistryMock.sol";
-import "./mocks/ServiceManagerMock.sol";
import "../contracts/libraries/BeaconChainProofs.sol";
import "./mocks/BeaconChainOracleMock.sol";
-
+import "./harnesses/EigenPodHarness.sol";
contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
using BytesLib for bytes;
+ using BeaconChainProofs for *;
+
uint256 internal constant GWEI_TO_WEI = 1e9;
+ uint64 public constant DENEB_FORK_TIMESTAMP_GOERLI = 1705473120;
- bytes pubkey = hex"88347ed1c492eedc97fc8c506a35d44d81f27a0c7a1c661b35913cfd15256c0cccbd34a83341f505c7de2983292f2cab";
+
+ bytes pubkey =
+ hex"88347ed1c492eedc97fc8c506a35d44d81f27a0c7a1c661b35913cfd15256c0cccbd34a83341f505c7de2983292f2cab";
uint40 validatorIndex0 = 0;
uint40 validatorIndex1 = 1;
- //hash tree root of list of validators
- bytes32 validatorTreeRoot;
- //hash tree root of individual validator container
- bytes32 validatorRoot;
address podOwner = address(42000094993494);
+ bool public IS_DENEB;
+
Vm cheats = Vm(HEVM_ADDRESS);
DelegationManager public delegation;
IStrategyManager public strategyManager;
@@ -36,28 +35,36 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
PauserRegistry public pauserReg;
ProxyAdmin public eigenLayerProxyAdmin;
- IBLSPublicKeyCompendium public blsPkCompendium;
IEigenPodManager public eigenPodManager;
IEigenPod public podImplementation;
IDelayedWithdrawalRouter public delayedWithdrawalRouter;
IETHPOSDeposit public ethPOSDeposit;
IBeacon public eigenPodBeacon;
- IBeaconChainOracleMock public beaconChainOracle;
- MiddlewareRegistryMock public generalReg1;
- ServiceManagerMock public generalServiceManager1;
+ EPInternalFunctions public podInternalFunctionTester;
+
+ BeaconChainOracleMock public beaconChainOracle;
address[] public slashingContracts;
address pauser = address(69);
address unpauser = address(489);
address podManagerAddress = 0x212224D2F2d262cd093eE13240ca4873fcCBbA3C;
address podAddress = address(123);
uint256 stakeAmount = 32e18;
- mapping (address => bool) fuzzedAddressMapping;
+ mapping(address => bool) fuzzedAddressMapping;
bytes signature;
bytes32 depositDataRoot;
bytes32[] withdrawalFields;
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;
+ uint64 internal constant SECONDS_PER_SLOT = 12;
+
+ // bytes validatorPubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
// EIGENPODMANAGER EVENTS
/// @notice Emitted to notify the update of the beaconChainOracle address
@@ -69,10 +76,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
/// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
- /// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue`
- event MaxPodsUpdated(uint256 previousValue, uint256 newValue);
-
-
// EIGENPOD EVENTS
/// @notice Emitted when an ETH validator stakes via this eigenPod
event EigenPodStaked(bytes pubkey);
@@ -80,14 +83,24 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
/// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
event ValidatorRestaked(uint40 validatorIndex);
- /// @notice Emitted when an ETH validator is proven to have a balance less than `REQUIRED_BALANCE_GWEI` in the beacon chain
- event ValidatorOvercommitted(uint40 validatorIndex);
-
+ /// @notice Emitted when an ETH validator's balance is updated in EigenLayer
+ event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newBalanceGwei);
+
/// @notice Emitted when an ETH validator is prove to have withdrawn from the beacon chain
- event FullWithdrawalRedeemed(uint40 validatorIndex, address indexed recipient, uint64 withdrawalAmountGwei);
+ event FullWithdrawalRedeemed(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address indexed recipient,
+ uint64 withdrawalAmountGwei
+ );
/// @notice Emitted when a partial withdrawal claim is successfully redeemed
- event PartialWithdrawalRedeemed(uint40 validatorIndex, address indexed recipient, uint64 partialWithdrawalAmountGwei);
+ event PartialWithdrawalRedeemed(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address indexed recipient,
+ uint64 partialWithdrawalAmountGwei
+ );
/// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
@@ -102,16 +115,14 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
/// @notice event for the claiming of delayedWithdrawals
event DelayedWithdrawalsClaimed(address recipient, uint256 amountClaimed, uint256 delayedWithdrawalsCompleted);
+ /// @notice Emitted when ETH that was previously received via the `receive` fallback is withdrawn
+ event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn);
modifier fuzzedAddress(address addr) virtual {
cheats.assume(fuzzedAddressMapping[addr] == false);
_;
}
-
- uint32 WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds;
- uint256 REQUIRED_BALANCE_WEI = 31 ether;
-
//performs basic deployment before each test
function setUp() public {
// deploy proxy admin for ability to upgrade proxy contracts
@@ -120,15 +131,18 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
// deploy pauser registry
address[] memory pausers = new address[](1);
pausers[0] = pauser;
- pauserReg= new PauserRegistry(pausers, unpauser);
+ pauserReg = new PauserRegistry(pausers, unpauser);
- blsPkCompendium = new BLSPublicKeyCompendium();
+ /// weird workaround: check commit before this -- the call to `upgradeAndCall` the DelegationManager breaks without performing this step!
+ /// seems to be foundry bug. the revert is ultimately for 'TransparentUpgradeableProxy: admin cannot fallback to proxy target', i.e.
+ /// the simulated caller is somehow the ProxyAdmin itself.
+ EmptyContract emptyContract = new EmptyContract();
/**
* First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are
* not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code.
*/
- EmptyContract emptyContract = new EmptyContract();
+ emptyContract = new EmptyContract();
delegation = DelegationManager(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
@@ -144,11 +158,13 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
ethPOSDeposit = new ETHPOSDepositMock();
podImplementation = new EigenPod(
- ethPOSDeposit,
- delayedWithdrawalRouter,
- IEigenPodManager(podManagerAddress),
- REQUIRED_BALANCE_WEI
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ IEigenPodManager(podManagerAddress),
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
);
+
eigenPodBeacon = new UpgradeableBeacon(address(podImplementation));
// this contract is deployed later to keep its address the same (for these tests)
@@ -157,10 +173,20 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
);
// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
- DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher);
- StrategyManager strategyManagerImplementation = new StrategyManager(delegation, IEigenPodManager(podManagerAddress), slasher);
+ DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ StrategyManager strategyManagerImplementation = new StrategyManager(
+ delegation,
+ IEigenPodManager(podManagerAddress),
+ slasher
+ );
Slasher slasherImplementation = new Slasher(strategyManager, delegation);
- EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher);
+ EigenPodManager eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegation
+ );
//ensuring that the address of eigenpodmanager doesn't change
bytes memory code = address(eigenPodManager).code;
@@ -168,7 +194,9 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenPodManager = IEigenPodManager(podManagerAddress);
beaconChainOracle = new BeaconChainOracleMock();
- DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(IEigenPodManager(podManagerAddress));
+ DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(
+ IEigenPodManager(podManagerAddress)
+ );
address initialOwner = address(this);
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
@@ -179,7 +207,10 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
DelegationManager.initialize.selector,
initialOwner,
pauserReg,
- 0/*initialPausedStatus*/
+ 0 /*initialPausedStatus*/,
+ WITHDRAWAL_DELAY_BLOCKS,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
)
);
eigenLayerProxyAdmin.upgradeAndCall(
@@ -190,19 +221,13 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
initialOwner,
initialOwner,
pauserReg,
- 0/*initialPausedStatus*/,
- 0/*withdrawalDelayBlocks*/
+ 0 /*initialPausedStatus*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(slasher))),
address(slasherImplementation),
- abi.encodeWithSelector(
- Slasher.initialize.selector,
- initialOwner,
- pauserReg,
- 0/*initialPausedStatus*/
- )
+ abi.encodeWithSelector(Slasher.initialize.selector, initialOwner, pauserReg, 0 /*initialPausedStatus*/)
);
// TODO: add `cheats.expectEmit` calls for initialization events
eigenLayerProxyAdmin.upgradeAndCall(
@@ -210,11 +235,10 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
address(eigenPodManagerImplementation),
abi.encodeWithSelector(
EigenPodManager.initialize.selector,
- type(uint256).max, // maxPods
beaconChainOracle,
initialOwner,
pauserReg,
- 0/*initialPausedStatus*/
+ 0 /*initialPausedStatus*/
)
);
uint256 initPausedStatus = 0;
@@ -222,16 +246,16 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
address(delayedWithdrawalRouterImplementation),
- abi.encodeWithSelector(DelayedWithdrawalRouter.initialize.selector, initialOwner, pauserReg, initPausedStatus, withdrawalDelayBlocks)
- );
- generalServiceManager1 = new ServiceManagerMock(slasher);
-
- generalReg1 = new MiddlewareRegistryMock(
- generalServiceManager1,
- strategyManager
+ abi.encodeWithSelector(
+ DelayedWithdrawalRouter.initialize.selector,
+ initialOwner,
+ pauserReg,
+ initPausedStatus,
+ withdrawalDelayBlocks
+ )
);
- cheats.deal(address(podOwner), 5*stakeAmount);
+ cheats.deal(address(podOwner), 5 * stakeAmount);
fuzzedAddressMapping[address(0)] = true;
fuzzedAddressMapping[address(eigenLayerProxyAdmin)] = true;
@@ -239,8 +263,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
fuzzedAddressMapping[address(eigenPodManager)] = true;
fuzzedAddressMapping[address(delegation)] = true;
fuzzedAddressMapping[address(slasher)] = true;
- fuzzedAddressMapping[address(generalServiceManager1)] = true;
- fuzzedAddressMapping[address(generalReg1)] = true;
}
function testStaking() public {
@@ -255,320 +277,737 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
function testWithdrawBeforeRestaking() public {
testStaking();
IEigenPod pod = eigenPodManager.getPod(podOwner);
+
+ //simulate that hasRestaked is set to false, so that we can test withdrawBeforeRestaking for pods deployed before M2 activation
+ cheats.store(address(pod), bytes32(uint256(52)), bytes32(uint256(1)));
require(pod.hasRestaked() == false, "Pod should not be restaked");
// simulate a withdrawal
cheats.deal(address(pod), stakeAmount);
cheats.startPrank(podOwner);
cheats.expectEmit(true, true, true, true, address(delayedWithdrawalRouter));
- emit DelayedWithdrawalCreated(podOwner, podOwner, stakeAmount, delayedWithdrawalRouter.userWithdrawalsLength(podOwner));
+ emit DelayedWithdrawalCreated(
+ podOwner,
+ podOwner,
+ stakeAmount,
+ delayedWithdrawalRouter.userWithdrawalsLength(podOwner)
+ );
+
+ uint timestampBeforeTx = pod.mostRecentWithdrawalTimestamp();
+
pod.withdrawBeforeRestaking();
+
require(_getLatestDelayedWithdrawalAmount(podOwner) == stakeAmount, "Payment amount should be stake amount");
- require(pod.mostRecentWithdrawalBlockNumber() == uint64(block.number), "Most recent withdrawal block number not updated");
+ require(
+ pod.mostRecentWithdrawalTimestamp() == uint64(block.timestamp),
+ "Most recent withdrawal block number not updated"
+ );
+ require(
+ pod.mostRecentWithdrawalTimestamp() > timestampBeforeTx,
+ "Most recent withdrawal block number not updated"
+ );
}
- function testWithdrawBeforeRestakingAfterRestaking() public {
- // ./solidityProofGen "ValidatorFieldsProof" 61336 true "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "withdrawalCredentialAndBalanceProof_61336.json"
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61336.json");
- IEigenPod pod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ function testDeployEigenPodWithoutActivateRestaking() public {
+ // ./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");
+
+ IEigenPod newPod = eigenPodManager.getPod(podOwner);
- cheats.expectRevert(bytes("EigenPod.hasNeverRestaked: restaking is enabled"));
cheats.startPrank(podOwner);
- pod.withdrawBeforeRestaking();
+ cheats.expectEmit(true, true, true, true, address(newPod));
+ emit EigenPodStaked(pubkey);
+ eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
+ cheats.stopPrank();
+
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+ bytes[] memory proofsArray = new bytes[](1);
+ proofsArray[0] = abi.encodePacked(getWithdrawalCredentialProof());
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(getLatestBlockRoot());
+
+ //this simulates that hasRestaking is set to false, as would be the case for deployed pods that have not yet restaked prior to M2
+ cheats.store(address(newPod), bytes32(uint256(52)), bytes32(uint256(0)));
+
+ cheats.startPrank(podOwner);
+ cheats.warp(GOERLI_GENESIS_TIME);
+ cheats.expectRevert(bytes("EigenPod.hasEnabledRestaking: restaking is not enabled"));
+ newPod.verifyWithdrawalCredentials(
+ GOERLI_GENESIS_TIME,
+ stateRootProofStruct,
+ validatorIndices,
+ proofsArray,
+ validatorFieldsArray
+ );
+ cheats.stopPrank();
+ }
+
+ function testWithdrawNonBeaconChainETHBalanceWei() public {
+ IEigenPod pod = testDeployAndVerifyNewEigenPod();
+
+ cheats.deal(address(podOwner), 10 ether);
+ emit log_named_address("Pod:", address(pod));
+
+ uint256 balanceBeforeDeposit = pod.nonBeaconChainETHBalanceWei();
+
+ (bool sent, ) = payable(address(pod)).call{value: 1 ether}("");
+
+ require(sent == true, "not sent");
+
+ uint256 balanceAfterDeposit = pod.nonBeaconChainETHBalanceWei();
+
+ require(
+ balanceBeforeDeposit < balanceAfterDeposit
+ && (balanceAfterDeposit - balanceBeforeDeposit) == 1 ether,
+ "increment checks"
+ );
+
+ cheats.startPrank(podOwner, podOwner);
+ cheats.expectEmit(true, true, true, true, address(pod));
+ emit NonBeaconChainETHWithdrawn(podOwner, 1 ether);
+ pod.withdrawNonBeaconChainETHBalanceWei(
+ podOwner,
+ 1 ether
+ );
+
+ uint256 balanceAfterWithdrawal = pod.nonBeaconChainETHBalanceWei();
+
+ require(
+ balanceAfterWithdrawal < balanceAfterDeposit
+ && balanceAfterWithdrawal == balanceBeforeDeposit,
+ "decrement checks"
+ );
+
cheats.stopPrank();
}
function testWithdrawFromPod() public {
+ IEigenPod pod = eigenPodManager.getPod(podOwner);
cheats.startPrank(podOwner);
+
+ cheats.expectEmit(true, true, true, true, address(pod));
+ emit EigenPodStaked(pubkey);
+
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();
- IEigenPod pod = eigenPodManager.getPod(podOwner);
cheats.deal(address(pod), stakeAmount);
+ // this is testing if pods deployed before M2 that do not have hasRestaked initialized to true, will revert
+ cheats.store(address(pod), bytes32(uint256(52)), bytes32(uint256(1)));
+
cheats.startPrank(podOwner);
uint256 userWithdrawalsLength = delayedWithdrawalRouter.userWithdrawalsLength(podOwner);
// cheats.expectEmit(true, true, true, true, address(delayedWithdrawalRouter));
- cheats.expectEmit(true, true, true, true);
+ //cheats.expectEmit(true, true, true, true);
emit DelayedWithdrawalCreated(podOwner, podOwner, stakeAmount, userWithdrawalsLength);
pod.withdrawBeforeRestaking();
cheats.stopPrank();
require(address(pod).balance == 0, "Pod balance should be 0");
}
- function testAttemptedWithdrawalAfterVerifyingWithdrawalCredentials() public {
- testDeployAndVerifyNewEigenPod();
- IEigenPod pod = eigenPodManager.getPod(podOwner);
- cheats.startPrank(podOwner);
- cheats.expectRevert(bytes("EigenPod.hasNeverRestaked: restaking is enabled"));
- IEigenPod(pod).withdrawBeforeRestaking();
- cheats.stopPrank();
- }
-
function testFullWithdrawalProof() public {
- setJSON("./src/test/test-data/fullWithdrawalProof.json");
- BeaconChainProofs.WithdrawalProofs memory proofs = _getWithdrawalProof();
- withdrawalFields = getWithdrawalFields();
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ BeaconChainProofs.WithdrawalProof memory proofs = _getWithdrawalProof();
+ bytes32 beaconStateRoot = getBeaconStateRoot();
+ withdrawalFields = getWithdrawalFields();
validatorFields = getValidatorFields();
Relayer relay = new Relayer();
+ relay.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs);
+ }
+
+ function testFullWithdrawalProofWithWrongIndices(
+ uint64 wrongBlockRootIndex,
+ uint64 wrongWithdrawalIndex,
+ uint64 wrongHistoricalSummariesIndex
+ ) public {
+ uint256 BLOCK_ROOTS_TREE_HEIGHT = 13;
+ uint256 WITHDRAWALS_TREE_HEIGHT = 4;
+ uint256 HISTORICAL_SUMMARIES_TREE_HEIGHT = 24;
+ cheats.assume(wrongBlockRootIndex > 2 ** BLOCK_ROOTS_TREE_HEIGHT);
+ cheats.assume(wrongWithdrawalIndex > 2 ** WITHDRAWALS_TREE_HEIGHT);
+ cheats.assume(wrongHistoricalSummariesIndex > 2 ** HISTORICAL_SUMMARIES_TREE_HEIGHT);
+
+ Relayer relay = new Relayer();
+
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
bytes32 beaconStateRoot = getBeaconStateRoot();
- relay.verifyWithdrawalProofs(beaconStateRoot, proofs, withdrawalFields);
+ validatorFields = getValidatorFields();
+ withdrawalFields = getWithdrawalFields();
+
+ {
+ BeaconChainProofs.WithdrawalProof memory wrongProofs = _getWithdrawalProof();
+ wrongProofs.blockRootIndex = wrongBlockRootIndex;
+ cheats.expectRevert(bytes("BeaconChainProofs.verifyWithdrawal: blockRootIndex is too large"));
+ relay.verifyWithdrawal(beaconStateRoot, withdrawalFields, wrongProofs);
+ }
+ {
+ BeaconChainProofs.WithdrawalProof memory wrongProofs = _getWithdrawalProof();
+ wrongProofs.withdrawalIndex = wrongWithdrawalIndex;
+ cheats.expectRevert(bytes("BeaconChainProofs.verifyWithdrawal: withdrawalIndex is too large"));
+ relay.verifyWithdrawal(beaconStateRoot, withdrawalFields, wrongProofs);
+ }
+
+ {
+ BeaconChainProofs.WithdrawalProof memory wrongProofs = _getWithdrawalProof();
+ wrongProofs.historicalSummaryIndex = wrongHistoricalSummariesIndex;
+ cheats.expectRevert(bytes("BeaconChainProofs.verifyWithdrawal: historicalSummaryIndex is too large"));
+ relay.verifyWithdrawal(beaconStateRoot, withdrawalFields, wrongProofs);
+ }
}
/// @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 61336 has proven their withdrawalcreds
- // ./solidityProofGen "ValidatorFieldsProof" 61336 true "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "withdrawalCredentialAndBalanceProof_61336.json"
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61336.json");
+ //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);
- // ./solidityProofGen "WithdrawalFieldsProof" 61336 2262 "data/slot_43222/oracle_capella_beacon_state_43300.ssz" "data/slot_43222/capella_block_header_43222.json" "data/slot_43222/capella_block_43222.json" fullWithdrawalProof.json
- setJSON("./src/test/test-data/fullWithdrawalProof.json");
- BeaconChainProofs.WithdrawalProofs memory withdrawalProofs = _getWithdrawalProof();
- bytes memory validatorFieldsProof = abi.encodePacked(getValidatorProof());
- withdrawalFields = getWithdrawalFields();
- validatorFields = getValidatorFields();
- bytes32 newBeaconStateRoot = getBeaconStateRoot();
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
+ //./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
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ return _proveWithdrawalForPod(newPod);
+ }
- uint64 restakedExecutionLayerGweiBefore = newPod.restakedExecutionLayerGwei();
- uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
- uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - newPod.REQUIRED_BALANCE_GWEI()) * uint64(GWEI_TO_WEI);
- uint40 validatorIndex = uint40(Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
- cheats.deal(address(newPod), leftOverBalanceWEI);
-
- uint256 delayedWithdrawalRouterContractBalanceBefore = address(delayedWithdrawalRouter).balance;
- cheats.expectEmit(true, true, true, true, address(newPod));
- emit FullWithdrawalRedeemed(validatorIndex, podOwner, withdrawalAmountGwei);
- newPod.verifyAndProcessWithdrawal(withdrawalProofs, validatorFieldsProof, validatorFields, withdrawalFields, 0, 0);
- require(newPod.restakedExecutionLayerGwei() - restakedExecutionLayerGweiBefore == newPod.REQUIRED_BALANCE_GWEI(),
- "restakedExecutionLayerGwei has not been incremented correctly");
- require(address(delayedWithdrawalRouter).balance - delayedWithdrawalRouterContractBalanceBefore == leftOverBalanceWEI,
- "pod delayed withdrawal balance hasn't been updated correctly");
+ /**
+ * @notice this test is to ensure that a full withdrawal can be made once a validator has processed their first full withrawal
+ * This is specifically for the case where a validator has redeposited into their exited validator and needs to prove another withdrawal
+ * to get their funds out
+ */
+ function testWithdrawAfterFullWithdrawal() external {
+ _deployInternalFunctionTester();
+ IEigenPod pod = testFullWithdrawalFlow();
+
+ // ./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_1SlotAdvanced.json" true
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest_1SlotAdvanced.json");
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(getLatestBlockRoot());
+
+ withdrawalFields = getWithdrawalFields();
+ uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
+ withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
+ );
+ uint64 leftOverBalanceWEI = uint64(
+ withdrawalAmountGwei - pod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()
+ ) * uint64(GWEI_TO_WEI);
+ cheats.deal(address(pod), leftOverBalanceWEI);
+ {
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](
+ 1
+ );
+ withdrawalProofsArray[0] = _getWithdrawalProof();
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ validatorFieldsProofArray[0] = abi.encodePacked(getValidatorProof());
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ withdrawalFieldsArray[0] = withdrawalFields;
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ pod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
+ }
+ }
- cheats.roll(block.number + WITHDRAWAL_DELAY_BLOCKS + 1);
- uint podOwnerBalanceBefore = address(podOwner).balance;
- delayedWithdrawalRouter.claimDelayedWithdrawals(podOwner, 1);
- require(address(podOwner).balance - podOwnerBalanceBefore == leftOverBalanceWEI, "Pod owner balance hasn't been updated correctly");
- return newPod;
+ function testProvingFullWithdrawalForTheSameSlotFails() external {
+ IEigenPod pod = testFullWithdrawalFlow();
+
+ {
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](
+ 1
+ );
+ withdrawalProofsArray[0] = _getWithdrawalProof();
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ validatorFieldsProofArray[0] = abi.encodePacked(getValidatorProof());
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ withdrawalFieldsArray[0] = withdrawalFields;
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ cheats.expectRevert(
+ bytes("EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp")
+ );
+ pod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
+ }
}
/// @notice This test is to ensure that the partial withdrawal flow works correctly
- function testPartialWithdrawalFlow() public returns(IEigenPod) {
+ function testPartialWithdrawalFlow() public returns (IEigenPod) {
//this call is to ensure that validator 61068 has proven their withdrawalcreds
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
+ // ./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);
- //generate partialWithdrawalProofs.json with:
- // ./solidityProofGen "WithdrawalFieldsProof" 61068 656 "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "data/slot_58000/capella_block_header_58000.json" "data/slot_58000/capella_block_58000.json" "partialWithdrawalProof.json"
- setJSON("./src/test/test-data/partialWithdrawalProof.json");
- BeaconChainProofs.WithdrawalProofs memory withdrawalProofs = _getWithdrawalProof();
- bytes memory validatorFieldsProof = abi.encodePacked(getValidatorProof());
-
- withdrawalFields = getWithdrawalFields();
+ //generate partialWithdrawalProofs.json with:
+ // ./solidityProofGen "WithdrawalFieldsProof" 302913 146 8092 true true "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" "partialWithdrawalProof_Latest.json" false
+ setJSON("./src/test/test-data/partialWithdrawalProof_Latest.json");
+ withdrawalFields = getWithdrawalFields();
validatorFields = getValidatorFields();
- bytes32 newBeaconStateRoot = getBeaconStateRoot();
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
-
- uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
- uint64 slot = Endian.fromLittleEndianUint64(withdrawalProofs.slotRoot);
- uint40 validatorIndex = uint40(Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
+ BeaconChainProofs.WithdrawalProof memory withdrawalProofs = _getWithdrawalProof();
+ bytes memory validatorFieldsProof = abi.encodePacked(getValidatorProof());
- cheats.deal(address(newPod), stakeAmount);
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(getLatestBlockRoot());
+ uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
+ withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
+ );
+ uint40 validatorIndex = uint40(
+ Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX])
+ );
- uint256 delayedWithdrawalRouterContractBalanceBefore = address(delayedWithdrawalRouter).balance;
- cheats.expectEmit(true, true, true, true, address(newPod));
- emit PartialWithdrawalRedeemed(validatorIndex, podOwner, withdrawalAmountGwei);
- newPod.verifyAndProcessWithdrawal(withdrawalProofs, validatorFieldsProof, validatorFields, withdrawalFields, 0, 0);
- require(newPod.provenPartialWithdrawal(validatorIndex, slot), "provenPartialWithdrawal should be true");
- withdrawalAmountGwei = uint64(withdrawalAmountGwei*GWEI_TO_WEI);
- require(address(delayedWithdrawalRouter).balance - delayedWithdrawalRouterContractBalanceBefore == withdrawalAmountGwei,
- "pod delayed withdrawal balance hasn't been updated correctly");
+ cheats.deal(address(newPod), stakeAmount);
+ {
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](
+ 1
+ );
+ withdrawalProofsArray[0] = withdrawalProofs;
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ validatorFieldsProofArray[0] = validatorFieldsProof;
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = validatorFields;
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ withdrawalFieldsArray[0] = withdrawalFields;
+
+ uint256 delayedWithdrawalRouterContractBalanceBefore = address(delayedWithdrawalRouter).balance;
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ //cheats.expectEmit(true, true, true, true, address(newPod));
+ // cheats.expectEmit(validatorIndex, _computeTimestampAtSlot(Endian.fromLittleEndianUint64(withdrawalProofs.slotRoot)), podOwner, withdrawalAmountGwei, address(newPod));
+ emit PartialWithdrawalRedeemed(
+ validatorIndex,
+ _computeTimestampAtSlot(Endian.fromLittleEndianUint64(withdrawalProofs.slotRoot)),
+ podOwner,
+ withdrawalAmountGwei
+ );
+ newPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
+ require(
+ newPod.provenWithdrawal(
+ validatorFields[0],
+ _computeTimestampAtSlot(Endian.fromLittleEndianUint64(withdrawalProofs.slotRoot))
+ ),
+ "provenPartialWithdrawal should be true"
+ );
+ withdrawalAmountGwei = uint64(withdrawalAmountGwei * GWEI_TO_WEI);
+ require(
+ address(delayedWithdrawalRouter).balance - delayedWithdrawalRouterContractBalanceBefore ==
+ withdrawalAmountGwei,
+ "pod delayed withdrawal balance hasn't been updated correctly"
+ );
+ }
cheats.roll(block.number + WITHDRAWAL_DELAY_BLOCKS + 1);
- uint podOwnerBalanceBefore = address(podOwner).balance;
+ uint256 podOwnerBalanceBefore = address(podOwner).balance;
delayedWithdrawalRouter.claimDelayedWithdrawals(podOwner, 1);
- require(address(podOwner).balance - podOwnerBalanceBefore == withdrawalAmountGwei, "Pod owner balance hasn't been updated correctly");
+ require(
+ address(podOwner).balance - podOwnerBalanceBefore == withdrawalAmountGwei,
+ "Pod owner balance hasn't been updated correctly"
+ );
return newPod;
}
/// @notice verifies that multiple partial withdrawals can be made before a full withdrawal
- function testProvingMultipleWithdrawalsForSameSlot(/*uint256 numPartialWithdrawals*/) public {
+ function testProvingMultiplePartialWithdrawalsForSameSlot() public /*uint256 numPartialWithdrawals*/ {
IEigenPod newPod = testPartialWithdrawalFlow();
- BeaconChainProofs.WithdrawalProofs memory withdrawalProofs = _getWithdrawalProof();
+ BeaconChainProofs.WithdrawalProof memory withdrawalProofs = _getWithdrawalProof();
bytes memory validatorFieldsProof = abi.encodePacked(getValidatorProof());
- withdrawalFields = getWithdrawalFields();
+ withdrawalFields = getWithdrawalFields();
validatorFields = getValidatorFields();
- cheats.expectRevert(bytes("EigenPod._processPartialWithdrawal: partial withdrawal has already been proven for this slot"));
- newPod.verifyAndProcessWithdrawal(withdrawalProofs, validatorFieldsProof, validatorFields, withdrawalFields, 0, 0);
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](1);
+ withdrawalProofsArray[0] = withdrawalProofs;
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ validatorFieldsProofArray[0] = validatorFieldsProof;
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = validatorFields;
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ withdrawalFieldsArray[0] = withdrawalFields;
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ cheats.expectRevert(
+ bytes("EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp")
+ );
+ newPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
}
/// @notice verifies that multiple full withdrawals for a single validator fail
- function testDoubleFullWithdrawal() public {
- IEigenPod newPod = testFullWithdrawalFlow();
- BeaconChainProofs.WithdrawalProofs memory withdrawalProofs = _getWithdrawalProof();
+ function testDoubleFullWithdrawal() public returns (IEigenPod newPod) {
+ newPod = testFullWithdrawalFlow();
+ uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
+ withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
+ );
+ uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) *
+ uint64(GWEI_TO_WEI);
+ cheats.deal(address(newPod), leftOverBalanceWEI);
+
+ BeaconChainProofs.WithdrawalProof memory withdrawalProofs = _getWithdrawalProof();
bytes memory validatorFieldsProof = abi.encodePacked(getValidatorProof());
- withdrawalFields = getWithdrawalFields();
+ withdrawalFields = getWithdrawalFields();
validatorFields = getValidatorFields();
- cheats.expectRevert(bytes("EigenPod.verifyBeaconChainFullWithdrawal: VALIDATOR_STATUS is WITHDRAWN or invalid VALIDATOR_STATUS"));
- newPod.verifyAndProcessWithdrawal(withdrawalProofs, validatorFieldsProof, validatorFields, withdrawalFields, 0, 0);
+
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](1);
+ withdrawalProofsArray[0] = withdrawalProofs;
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ validatorFieldsProofArray[0] = validatorFieldsProof;
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = validatorFields;
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ withdrawalFieldsArray[0] = withdrawalFields;
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ cheats.expectRevert(
+ bytes("EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp")
+ );
+ newPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
+
+ return newPod;
}
- function testDeployAndVerifyNewEigenPod() public returns(IEigenPod) {
- // ./solidityProofGen "ValidatorFieldsProof" 61068 false "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "withdrawalCredentialAndBalanceProof_61068.json"
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
+ function testDeployAndVerifyNewEigenPod() public returns (IEigenPod) {
+ // ./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");
return _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
}
- // //test freezing operator after a beacon chain slashing event
+ // test freezing operator after a beacon chain slashing event
function testUpdateSlashedBeaconBalance() public {
+ _deployInternalFunctionTester();
//make initial deposit
- // ./solidityProofGen "ValidatorFieldsProof" 61511 true "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json");
+ // ./solidityProofGen "BalanceUpdateProof" 302913 false 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_notOverCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
_testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
IEigenPod newPod = eigenPodManager.getPod(podOwner);
- // ./solidityProofGen "ValidatorFieldsProof" 61511 false "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json");
+ cheats.warp(GOERLI_GENESIS_TIME);
+ // ./solidityProofGen "BalanceUpdateProof" 302913 true 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_overCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json");
_proveOverCommittedStake(newPod);
-
- uint256 beaconChainETHShares = strategyManager.stakerStrategyShares(podOwner, strategyManager.beaconChainETHStrategy());
- require(beaconChainETHShares == 0, "strategyManager shares not updated correctly");
- }
+ uint64 newValidatorBalance = _getValidatorUpdatedBalance();
+ int256 beaconChainETHShares = eigenPodManager.podOwnerShares(podOwner);
+ require(
+ beaconChainETHShares == int256((newValidatorBalance) * GWEI_TO_WEI),
+ "eigenPodManager shares not updated correctly"
+ );
+ }
+
+ /// @notice Similar test done in EP unit test
//test deploying an eigen pod with mismatched withdrawal credentials between the proof and the actual pod's address
function testDeployNewEigenPodWithWrongWithdrawalCreds(address wrongWithdrawalAddress) public {
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
+ // ./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");
cheats.startPrank(podOwner);
+ IEigenPod newPod;
+ newPod = eigenPodManager.getPod(podOwner);
+ cheats.expectEmit(true, true, true, true, address(newPod));
+ emit EigenPodStaked(pubkey);
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();
- IEigenPod newPod;
- newPod = eigenPodManager.getPod(podOwner);
+
// make sure that wrongWithdrawalAddress is not set to actual pod address
cheats.assume(wrongWithdrawalAddress != address(newPod));
validatorFields = getValidatorFields();
validatorFields[1] = abi.encodePacked(bytes1(uint8(1)), bytes11(0), wrongWithdrawalAddress).toBytes32(0);
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- uint64 blockNumber = 1;
+ uint64 timestamp = 0;
+
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = validatorFields;
+ bytes[] memory proofsArray = new bytes[](1);
+ proofsArray[0] = abi.encodePacked(getWithdrawalCredentialProof());
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(validatorIndex0);
+ cheats.startPrank(podOwner);
+ cheats.warp(timestamp);
+ if (!newPod.hasRestaked()) {
+ newPod.activateRestaking();
+ }
+ // set oracle block root
+ _setOracleBlockRoot();
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ cheats.warp(timestamp += 1);
cheats.expectRevert(bytes("EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"));
- newPod.verifyWithdrawalCredentialsAndBalance(blockNumber, validatorIndex0, proofs, validatorFields);
+ newPod.verifyWithdrawalCredentials(
+ timestamp,
+ stateRootProofStruct,
+ validatorIndices,
+ proofsArray,
+ validatorFieldsArray
+ );
+ cheats.stopPrank();
}
+ //ensures that a validator proving WC after they have exited the beacon chain is allowed to
+ //prove their WC and process a withdrawal
+ function testProveWithdrawalCredentialsAfterValidatorExit() public {
+ // ./solidityProofGen -newBalance=0 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913_exited.json"
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913_exited.json");
+ emit log("hello");
- //test that when withdrawal credentials are verified more than once, it reverts
- function testDeployNewEigenPodWithActiveValidator() public {
- // ./solidityProofGen "ValidatorFieldsProof" 61068 false "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "withdrawalCredentialAndBalanceProof_61068.json"
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
- IEigenPod pod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
-
- uint64 blockNumber = 1;
- uint40 validatorIndex = uint40(getValidatorIndex());
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- validatorFields = getValidatorFields();
- cheats.expectRevert(bytes("EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials"));
- pod.verifyWithdrawalCredentialsAndBalance(blockNumber, validatorIndex, proofs, validatorFields);
+ IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ //./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
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _proveWithdrawalForPod(newPod);
}
- function testVerifyWithdrawalCredentialsWithInadequateBalance() public {
- // ./solidityProofGen "ValidatorFieldsProof" 61068 false "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "withdrawalCredentialAndBalanceProof_61068.json"
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- validatorFields = getValidatorFields();
- bytes32 newBeaconStateRoot = getBeaconStateRoot();
- uint40 validatorIndex = uint40(getValidatorIndex());
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
+ function testVerifyWithdrawalCredsFromNonPodOwnerAddress(address nonPodOwnerAddress) public {
+ // nonPodOwnerAddress must be different from podOwner
+ cheats.assume(nonPodOwnerAddress != podOwner);
+ // ./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");
+ cheats.startPrank(podOwner);
+ IEigenPod newPod = eigenPodManager.getPod(podOwner);
+ cheats.expectEmit(true, true, true, true, address(newPod));
+ emit EigenPodStaked(pubkey);
- cheats.startPrank(podOwner);
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();
- IEigenPod newPod = eigenPodManager.getPod(podOwner);
- uint64 blockNumber = 1;
- //set the validator balance to less than REQUIRED_BALANCE_WEI
- proofs.balanceRoot = bytes32(0);
- cheats.expectRevert(bytes("EigenPod.verifyCorrectWithdrawalCredentials: ETH validator's balance must be greater than or equal to the restaked balance per validator"));
- newPod.verifyWithdrawalCredentialsAndBalance(blockNumber, validatorIndex, proofs, validatorFields);
- }
+ uint64 timestamp = 1;
- function testProveOverComittedStakeOnWithdrawnValidator() public {
- // ./solidityProofGen "ValidatorFieldsProof" 61511 true "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json");
- _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
- IEigenPod newPod = eigenPodManager.getPod(podOwner);
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+ bytes[] memory proofsArray = new bytes[](1);
+ proofsArray[0] = abi.encodePacked(getWithdrawalCredentialProof());
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(validatorIndex0);
- // ./solidityProofGen "ValidatorFieldsProof" 61511 false "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- //setJSON("./src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json");
- emit log_named_address("podOwner", podOwner);
- validatorFields = getValidatorFields();
- uint40 validatorIndex = uint40(getValidatorIndex());
- bytes32 newBeaconStateRoot = getBeaconStateRoot();
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- //set slashed status to false, and balance to 0
- proofs.balanceRoot = bytes32(0);
- validatorFields[3] = bytes32(0);
- cheats.expectRevert(bytes("EigenPod.verifyOvercommittedStake: Validator must be slashed to be overcommitted"));
- newPod.verifyOvercommittedStake(validatorIndex, proofs, validatorFields, 0, uint64(block.number));
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+ cheats.startPrank(nonPodOwnerAddress);
+ cheats.expectRevert(bytes("EigenPod.onlyEigenPodOwner: not podOwner"));
+ newPod.verifyWithdrawalCredentials(
+ timestamp,
+ stateRootProofStruct,
+ validatorIndices,
+ proofsArray,
+ validatorFieldsArray
+ );
+ cheats.stopPrank();
}
- function getBeaconChainETHShares(address staker) internal view returns(uint256) {
- return strategyManager.stakerStrategyShares(staker, strategyManager.beaconChainETHStrategy());
+ function testBalanceProofWithWrongTimestamp(uint64 timestamp) public {
+ cheats.assume(timestamp > GOERLI_GENESIS_TIME);
+ // ./solidityProofGen "BalanceUpdateProof" 302913 false 0 "data/withdrawal_proof_goerli/goerli_slot_6399999.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_notOverCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+ IEigenPod newPod = testDeployAndVerifyNewEigenPod();
+
+ // ./solidityProofGen "BalanceUpdateProof" 302913 true 0 "data/withdrawal_proof_goerli/goerli_slot_6399999.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_overCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json");
+ // prove overcommitted balance
+ cheats.warp(timestamp);
+ _proveOverCommittedStake(newPod);
+
+
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
+
+ bytes memory proof = abi.encodePacked(getBalanceUpdateProof());
+ bytes[] memory proofs = new bytes[](1);
+ proofs[0] = proof;
+
+ bytes32 newLatestBlockRoot = getLatestBlockRoot();
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(newLatestBlockRoot);
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ cheats.expectRevert(bytes("EigenPod.verifyBalanceUpdate: Validators balance has already been updated for this timestamp"));
+ newPod.verifyBalanceUpdates(uint64(block.timestamp - 1), validatorIndices, stateRootProofStruct, proofs, validatorFieldsArray);
}
// // 3. Single withdrawal credential
// // Test: Owner proves an withdrawal credential.
- // // Expected Behaviour: beaconChainETH shares should increment by REQUIRED_BALANCE_WEI
// // validator status should be marked as ACTIVE
function testProveSingleWithdrawalCredential() public {
- // get beaconChainETH shares
- uint256 beaconChainETHBefore = getBeaconChainETHShares(podOwner);
-
- // ./solidityProofGen "ValidatorFieldsProof" 61068 false "data/slot_58000/oracle_capella_beacon_state_58100.ssz" "withdrawalCredentialAndBalanceProof_61068.json"
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
+ // ./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");
IEigenPod pod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
- uint40 validatorIndex = uint40(getValidatorIndex());
+ bytes32 validatorPubkeyHash = getValidatorPubkeyHash();
- uint256 beaconChainETHAfter = getBeaconChainETHShares(pod.podOwner());
- assertTrue(beaconChainETHAfter - beaconChainETHBefore == pod.REQUIRED_BALANCE_WEI());
- assertTrue(pod.validatorStatus(validatorIndex) == IEigenPod.VALIDATOR_STATUS.ACTIVE);
+ assertTrue(
+ pod.validatorStatus(validatorPubkeyHash) == IEigenPod.VALIDATOR_STATUS.ACTIVE,
+ "wrong validator status"
+ );
}
- // // 5. Prove overcommitted balance
- // // Setup: Run (3).
- // // Test: Watcher proves an overcommitted balance for validator from (3).
- // // Expected Behaviour: beaconChainETH shares should decrement by REQUIRED_BALANCE_WEI
- // // validator status should be marked as OVERCOMMITTED
+ // 5. Prove overcommitted balance
+ // Setup: Run (3).
+ // Test: Watcher proves an overcommitted balance for validator from (3).
+ // validator status should be marked as OVERCOMMITTED
function testProveOverCommittedBalance() public {
- // ./solidityProofGen "ValidatorFieldsProof" 61511 true "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json");
+ _deployInternalFunctionTester();
+ // ./solidityProofGen "BalanceUpdateProof" 302913 false 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_notOverCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
// get beaconChainETH shares
- uint256 beaconChainETHBefore = getBeaconChainETHShares(podOwner);
+ int256 beaconChainETHBefore = eigenPodManager.podOwnerShares(podOwner);
+
+ bytes32 validatorPubkeyHash = getValidatorPubkeyHash();
+ uint256 validatorRestakedBalanceBefore = newPod
+ .validatorPubkeyHashToInfo(validatorPubkeyHash)
+ .restakedBalanceGwei;
- // ./solidityProofGen "ValidatorFieldsProof" 61511 false "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json");
+ // ./solidityProofGen "BalanceUpdateProof" 302913 true 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_overCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json");
// prove overcommitted balance
+ cheats.warp(GOERLI_GENESIS_TIME);
_proveOverCommittedStake(newPod);
- uint40 validatorIndex = uint40(getValidatorIndex());
+ uint256 validatorRestakedBalanceAfter = newPod
+ .validatorPubkeyHashToInfo(validatorPubkeyHash)
+ .restakedBalanceGwei;
- assertTrue(beaconChainETHBefore - getBeaconChainETHShares(podOwner) == newPod.REQUIRED_BALANCE_WEI(), "BeaconChainETHShares not updated");
- assertTrue(newPod.validatorStatus(validatorIndex) == IEigenPod.VALIDATOR_STATUS.OVERCOMMITTED, "validator status not set correctly");
+ uint64 newValidatorBalance = _getValidatorUpdatedBalance();
+ int256 shareDiff = beaconChainETHBefore - eigenPodManager.podOwnerShares(podOwner);
+ assertTrue(
+ eigenPodManager.podOwnerShares(podOwner) ==
+ int256(newValidatorBalance * GWEI_TO_WEI),
+ "hysterisis not working"
+ );
+ assertTrue(
+ beaconChainETHBefore - eigenPodManager.podOwnerShares(podOwner) == shareDiff,
+ "BeaconChainETHShares not updated"
+ );
+ assertTrue(
+ int256(validatorRestakedBalanceBefore) - int256(validatorRestakedBalanceAfter) ==
+ shareDiff / int256(GWEI_TO_WEI),
+ "validator restaked balance not updated"
+ );
+ }
+
+ function testVerifyUndercommittedBalance() public {
+ _deployInternalFunctionTester();
+ // ./solidityProofGen "BalanceUpdateProof" 302913 false 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_notOverCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+ IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ // get beaconChainETH shares
+ int256 beaconChainETHBefore = eigenPodManager.podOwnerShares(podOwner);
+ bytes32 validatorPubkeyHash = getValidatorPubkeyHash();
+ uint256 validatorRestakedBalanceBefore = newPod
+ .validatorPubkeyHashToInfo(validatorPubkeyHash)
+ .restakedBalanceGwei;
+
+ // ./solidityProofGen "BalanceUpdateProof" 302913 true 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_overCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json");
+ // prove overcommitted balance
+ cheats.warp(GOERLI_GENESIS_TIME);
+ _proveOverCommittedStake(newPod);
+
+ cheats.warp(block.timestamp + 1);
+ // ./solidityProofGen "BalanceUpdateProof" 302913 false 100 "data/withdrawal_proof_goerli/goerli_slot_6399999.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_notOverCommitted_302913_incrementedBlockBy100.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_notOverCommitted_302913_incrementedBlockBy100.json");
+ _proveUnderCommittedStake(newPod);
+
+ uint256 validatorRestakedBalanceAfter = newPod
+ .validatorPubkeyHashToInfo(validatorPubkeyHash)
+ .restakedBalanceGwei;
+
+ int256 shareDiff = beaconChainETHBefore - eigenPodManager.podOwnerShares(podOwner);
+
+ assertTrue(
+ eigenPodManager.podOwnerShares(podOwner) ==
+ int256((MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * GWEI_TO_WEI),
+ "hysterisis not working"
+ );
+ assertTrue(
+ beaconChainETHBefore - eigenPodManager.podOwnerShares(podOwner) == shareDiff,
+ "BeaconChainETHShares not updated"
+ );
+ assertTrue(
+ int256(uint256(validatorRestakedBalanceBefore)) - int256(uint256(validatorRestakedBalanceAfter)) ==
+ shareDiff / int256(GWEI_TO_WEI),
+ "validator restaked balance not updated"
+ );
}
function testDeployingEigenPodRevertsWhenPaused() external {
@@ -595,15 +1034,25 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
cheats.stopPrank();
}
+ function testCreatePodIfItReturnsPodAddress() external {
+ cheats.startPrank(podOwner);
+ address _podAddress = eigenPodManager.createPod();
+ cheats.stopPrank();
+ IEigenPod pod = eigenPodManager.getPod(podOwner);
+ require(_podAddress == address(pod), "invalid pod address");
+ }
+
function testStakeOnEigenPodFromNonPodManagerAddress(address nonPodManager) external fuzzedAddress(nonPodManager) {
cheats.assume(nonPodManager != address(eigenPodManager));
cheats.startPrank(podOwner);
+ IEigenPod newPod = eigenPodManager.getPod(podOwner);
+ cheats.expectEmit(true, true, true, true, address(newPod));
+ emit EigenPodStaked(pubkey);
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();
- IEigenPod newPod = eigenPodManager.getPod(podOwner);
- cheats.deal(nonPodManager, stakeAmount);
+ cheats.deal(nonPodManager, stakeAmount);
cheats.startPrank(nonPodManager);
cheats.expectRevert(bytes("EigenPod.onlyEigenPodManager: not eigenPodManager"));
@@ -615,6 +1064,9 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
cheats.assume(nonPodOwner != podOwner);
testStaking();
IEigenPod pod = eigenPodManager.getPod(podOwner);
+
+ // this is testing if pods deployed before M2 that do not have hasRestaked initialized to true, will revert
+ cheats.store(address(pod), bytes32(uint256(52)), bytes32(0));
require(pod.hasRestaked() == false, "Pod should not be restaked");
//simulate a withdrawal
@@ -622,8 +1074,9 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
cheats.expectRevert(bytes("EigenPod.onlyEigenPodOwner: not podOwner"));
pod.withdrawBeforeRestaking();
}
-
+ /* test deprecated since this is checked on the EigenPodManager level, rather than the EigenPod level
+ TODO: @Sidu28 - check whether we have adequate coverage of the correct function
function testWithdrawRestakedBeaconChainETHRevertsWhenPaused() external {
// pause the contract
cheats.startPrank(pauser);
@@ -632,19 +1085,19 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
address recipient = address(this);
uint256 amount = 1e18;
- cheats.startPrank(address(eigenPodManager.strategyManager()));
+ IEigenPod eigenPod = eigenPodManager.getPod(podOwner);
+ cheats.startPrank(address(eigenPodManager));
cheats.expectRevert(bytes("Pausable: index is paused"));
- eigenPodManager.withdrawRestakedBeaconChainETH(podOwner, recipient, amount);
+ eigenPod.withdrawRestakedBeaconChainETH(recipient, amount);
cheats.stopPrank();
}
+ */
function testVerifyCorrectWithdrawalCredentialsRevertsWhenPaused() external {
- setJSON("./src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json");
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- validatorFields = getValidatorFields();
+ // ./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");
bytes32 newBeaconStateRoot = getBeaconStateRoot();
- uint40 validatorIndex = uint40(getValidatorIndex());
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(newBeaconStateRoot);
IEigenPod newPod = eigenPodManager.getPod(podOwner);
@@ -653,50 +1106,114 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
emit EigenPodStaked(pubkey);
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();
- uint64 blockNumber = 1;
+ uint64 timestamp = 1;
// pause the contract
cheats.startPrank(pauser);
eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_CREDENTIALS);
cheats.stopPrank();
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+
+ bytes[] memory proofsArray = new bytes[](1);
+ proofsArray[0] = abi.encodePacked(getWithdrawalCredentialProof());
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
+
+ cheats.startPrank(podOwner);
cheats.expectRevert(bytes("EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"));
- newPod.verifyWithdrawalCredentialsAndBalance(blockNumber, validatorIndex, proofs, validatorFields);
+ newPod.verifyWithdrawalCredentials(
+ timestamp,
+ stateRootProofStruct,
+ validatorIndices,
+ proofsArray,
+ validatorFieldsArray
+ );
+ cheats.stopPrank();
}
function testVerifyOvercommittedStakeRevertsWhenPaused() external {
- // ./solidityProofGen "ValidatorFieldsProof" 61511 true "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json");
+ // ./solidityProofGen "BalanceUpdateProof" 302913 false 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_notOverCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
- // ./solidityProofGen "ValidatorFieldsProof" 61511 false "data/slot_209635/oracle_capella_beacon_state_209635.ssz" "withdrawalCredentialAndBalanceProof_61511.json"
- setJSON("./src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json");
- validatorFields = getValidatorFields();
- uint40 validatorIndex = uint40(getValidatorIndex());
+ // ./solidityProofGen "BalanceUpdateProof" 302913 true 0 "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "balanceUpdateProof_overCommitted_302913.json"
+ setJSON("./src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json");
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
+
+ bytes[] memory proofs = new bytes[](1);
+ proofs[0] = abi.encodePacked(getBalanceUpdateProof());
+
bytes32 newBeaconStateRoot = getBeaconStateRoot();
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
-
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(newBeaconStateRoot);
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
// pause the contract
cheats.startPrank(pauser);
- eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_OVERCOMMITTED);
+ eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE);
cheats.stopPrank();
cheats.expectRevert(bytes("EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"));
- newPod.verifyOvercommittedStake(validatorIndex, proofs, validatorFields, 0, 0);
+ newPod.verifyBalanceUpdates(0, validatorIndices, stateRootProofStruct, proofs, validatorFieldsArray);
}
-
function _proveOverCommittedStake(IEigenPod newPod) internal {
- validatorFields = getValidatorFields();
- uint40 validatorIndex = uint40(getValidatorIndex());
- bytes32 newBeaconStateRoot = getBeaconStateRoot();
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- cheats.expectEmit(true, true, true, true, address(newPod));
- emit ValidatorOvercommitted(validatorIndex);
- newPod.verifyOvercommittedStake(validatorIndex, proofs, validatorFields, 0, uint64(block.number));
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
+
+ bytes[] memory proofs = new bytes[](1);
+ proofs[0] = abi.encodePacked(getBalanceUpdateProof());
+
+ bytes32 newLatestBlockRoot = getLatestBlockRoot();
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(newLatestBlockRoot);
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+ newPod.verifyBalanceUpdates(
+ uint64(block.timestamp),
+ validatorIndices,
+ stateRootProofStruct,
+ proofs,
+ validatorFieldsArray
+ );
+ }
+
+ function _proveUnderCommittedStake(IEigenPod newPod) internal {
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
+
+ bytes[] memory proofs = new bytes[](1);
+ proofs[0] = abi.encodePacked(getBalanceUpdateProof());
+
+ bytes32 newLatestBlockRoot = getLatestBlockRoot();
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(newLatestBlockRoot);
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ newPod.verifyBalanceUpdates(
+ uint64(block.timestamp),
+ validatorIndices,
+ stateRootProofStruct,
+ proofs,
+ validatorFieldsArray
+ );
+ require(newPod.validatorPubkeyHashToInfo(getValidatorPubkeyHash()).status == IEigenPod.VALIDATOR_STATUS.ACTIVE);
+ }
+
+ function _getValidatorUpdatedBalance() internal returns (uint64) {
+ bytes32[] memory validatorFieldsToGet = getValidatorFields();
+ return validatorFieldsToGet.getEffectiveBalanceGwei();
}
function testStake(bytes calldata _pubkey, bytes calldata _signature, bytes32 _depositDataRoot) public {
@@ -717,13 +1234,11 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
}
/// @notice Test that the Merkle proof verification fails when the proof length is 0
- function testVerifyInclusionSha256FailsForEmptyProof(
- bytes32 root,
- bytes32 leaf,
- uint256 index
- ) public {
+ function testVerifyInclusionSha256FailsForEmptyProof(bytes32 root, bytes32 leaf, uint256 index) public {
bytes memory emptyProof = new bytes(0);
- cheats.expectRevert(bytes("Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32"));
+ cheats.expectRevert(
+ bytes("Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32")
+ );
Merkle.verifyInclusionSha256(emptyProof, root, leaf, index);
}
@@ -735,22 +1250,21 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
bytes memory proof
) public {
cheats.assume(proof.length % 32 != 0);
- cheats.expectRevert(bytes("Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32"));
+ cheats.expectRevert(
+ bytes("Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32")
+ );
Merkle.verifyInclusionSha256(proof, root, leaf, index);
}
/// @notice Test that the Merkle proof verification fails when the proof length is empty
- function testVerifyInclusionKeccakFailsForEmptyProof(
- bytes32 root,
- bytes32 leaf,
- uint256 index
- ) public {
+ function testVerifyInclusionKeccakFailsForEmptyProof(bytes32 root, bytes32 leaf, uint256 index) public {
bytes memory emptyProof = new bytes(0);
- cheats.expectRevert(bytes("Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32"));
+ cheats.expectRevert(
+ bytes("Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32")
+ );
Merkle.verifyInclusionKeccak(emptyProof, root, leaf, index);
}
-
/// @notice Test that the Merkle proof verification fails when the proof length is not a multiple of 32
function testVerifyInclusionKeccakFailsForNonMultipleOf32ProofLength(
bytes32 root,
@@ -759,45 +1273,24 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
bytes memory proof
) public {
cheats.assume(proof.length % 32 != 0);
- cheats.expectRevert(bytes("Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32"));
+ cheats.expectRevert(
+ bytes("Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32")
+ );
Merkle.verifyInclusionKeccak(proof, root, leaf, index);
}
// verifies that the `numPod` variable increments correctly on a succesful call to the `EigenPod.stake` function
- function test_incrementNumPodsOnStake(bytes calldata _pubkey, bytes calldata _signature, bytes32 _depositDataRoot) public {
+ function test_incrementNumPodsOnStake(
+ bytes calldata _pubkey,
+ bytes calldata _signature,
+ bytes32 _depositDataRoot
+ ) public {
uint256 numPodsBefore = EigenPodManager(address(eigenPodManager)).numPods();
testStake(_pubkey, _signature, _depositDataRoot);
uint256 numPodsAfter = EigenPodManager(address(eigenPodManager)).numPods();
require(numPodsAfter == numPodsBefore + 1, "numPods did not increment correctly");
}
- // verifies that the `maxPods` variable is enforced on the `EigenPod.stake` function
- function test_maxPodsEnforcementOnStake(bytes calldata _pubkey, bytes calldata _signature, bytes32 _depositDataRoot) public {
- // set pod limit to current number of pods
- cheats.startPrank(unpauser);
- EigenPodManager(address(eigenPodManager)).setMaxPods(EigenPodManager(address(eigenPodManager)).numPods());
- cheats.stopPrank();
-
- cheats.startPrank(podOwner);
- cheats.expectRevert("EigenPodManager._deployPod: pod limit reached");
- eigenPodManager.stake{value: 32 ether}(_pubkey, _signature, _depositDataRoot);
- cheats.stopPrank();
-
- // set pod limit to *one more than* current number of pods
- cheats.startPrank(unpauser);
- EigenPodManager(address(eigenPodManager)).setMaxPods(EigenPodManager(address(eigenPodManager)).numPods() + 1);
- cheats.stopPrank();
-
- IEigenPod newPod = eigenPodManager.getPod(podOwner);
-
- cheats.startPrank(podOwner);
- // successful call
- cheats.expectEmit(true, true, true, true, address(newPod));
- emit EigenPodStaked(_pubkey);
- eigenPodManager.stake{value: 32 ether}(_pubkey, _signature, _depositDataRoot);
- cheats.stopPrank();
- }
-
// verifies that the `numPod` variable increments correctly on a succesful call to the `EigenPod.createPod` function
function test_incrementNumPodsOnCreatePod() public {
uint256 numPodsBefore = EigenPodManager(address(eigenPodManager)).numPods();
@@ -812,64 +1305,161 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenPodManager.createPod();
}
- // verifies that the `maxPods` variable is enforced on the `EigenPod.createPod` function
- function test_maxPodsEnforcementOnCreatePod() public {
- // set pod limit to current number of pods
- cheats.startPrank(unpauser);
- uint256 previousValue = EigenPodManager(address(eigenPodManager)).maxPods();
- uint256 newValue = EigenPodManager(address(eigenPodManager)).numPods();
- cheats.expectEmit(true, true, true, true, address(eigenPodManager));
- emit MaxPodsUpdated(previousValue, newValue);
- EigenPodManager(address(eigenPodManager)).setMaxPods(newValue);
- cheats.stopPrank();
+ function test_validatorPubkeyToInfo() external {
+ bytes memory _pubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
- cheats.expectRevert("EigenPodManager._deployPod: pod limit reached");
- eigenPodManager.createPod();
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ IEigenPod pod = eigenPodManager.getPod(podOwner);
- // set pod limit to *one more than* current number of pods
- cheats.startPrank(unpauser);
- previousValue = EigenPodManager(address(eigenPodManager)).maxPods();
- newValue = EigenPodManager(address(eigenPodManager)).numPods() + 1;
- cheats.expectEmit(true, true, true, true, address(eigenPodManager));
- emit MaxPodsUpdated(previousValue, newValue);
- EigenPodManager(address(eigenPodManager)).setMaxPods(newValue);
- cheats.stopPrank();
+ IEigenPod.ValidatorInfo memory info1 = pod.validatorPubkeyToInfo(_pubkey);
+ IEigenPod.ValidatorInfo memory info2 = pod.validatorPubkeyHashToInfo(getValidatorPubkeyHash());
- // successful call
- eigenPodManager.createPod();
+ require(info1.validatorIndex == info2.validatorIndex, "validatorIndex does not match");
+ require(info1.restakedBalanceGwei > 0, "restakedBalanceGwei is 0");
+ require(info1.restakedBalanceGwei == info2.restakedBalanceGwei, "restakedBalanceGwei does not match");
+ require(info1.mostRecentBalanceUpdateTimestamp == info2.mostRecentBalanceUpdateTimestamp, "mostRecentBalanceUpdateTimestamp does not match");
+ require(info1.status == info2.status, "status does not match");
}
- function test_setMaxPods(uint256 newValue) public {
- cheats.startPrank(unpauser);
- uint256 previousValue = EigenPodManager(address(eigenPodManager)).maxPods();
- cheats.expectEmit(true, true, true, true, address(eigenPodManager));
- emit MaxPodsUpdated(previousValue, newValue);
- EigenPodManager(address(eigenPodManager)).setMaxPods(newValue);
- cheats.stopPrank();
+ function test_validatorStatus() external {
+ bytes memory _pubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
- require(EigenPodManager(address(eigenPodManager)).maxPods() == newValue, "maxPods value not set correctly");
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
+ IEigenPod pod = eigenPodManager.getPod(podOwner);
+
+ IEigenPod.VALIDATOR_STATUS status1 = pod.validatorStatus(_pubkey);
+ IEigenPod.VALIDATOR_STATUS status2 = pod.validatorStatus(getValidatorPubkeyHash());
+
+ require(status1 == status2, "status does not match");
}
- function test_setMaxPods_RevertsWhenNotCalledByUnpauser(address notUnpauser) public fuzzedAddress(notUnpauser) {
- cheats.assume(notUnpauser != unpauser);
- uint256 newValue = 0;
- cheats.startPrank(notUnpauser);
- cheats.expectRevert("msg.sender is not permissioned as unpauser");
- EigenPodManager(address(eigenPodManager)).setMaxPods(newValue);
- cheats.stopPrank();
+ /* TODO: reimplement similar tests
+ function testQueueBeaconChainETHWithdrawalWithoutProvingFullWithdrawal() external {
+ // ./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);
+ uint256 shareAmount = 32e18;
+ // expect revert from underflow
+ cheats.expectRevert();
+ _testQueueWithdrawal(podOwner, shareAmount);
+ }
+
+ function testQueueBeaconChainETHWithdrawal() external {
+ IEigenPod pod = testFullWithdrawalFlow();
+
+ bytes32 validatorPubkeyHash = getValidatorPubkeyHash();
+
+ uint256 withdrawableRestakedExecutionLayerGweiBefore = pod.withdrawableRestakedExecutionLayerGwei();
+
+ uint256 shareAmount = (pod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) * GWEI_TO_WEI;
+ _verifyEigenPodBalanceSharesInvariant(podOwner, pod, validatorPubkeyHash);
+ _testQueueWithdrawal(podOwner, shareAmount);
+ _verifyEigenPodBalanceSharesInvariant(podOwner, pod, validatorPubkeyHash);
+
+ require(withdrawableRestakedExecutionLayerGweiBefore - pod.withdrawableRestakedExecutionLayerGwei() == shareAmount/int256(GWEI_TO_WEI),
+ "withdrawableRestakedExecutionLayerGwei not decremented correctly");
+ }
+*/
+ function _verifyEigenPodBalanceSharesInvariant(
+ address podowner,
+ IEigenPod pod,
+ bytes32 validatorPubkeyHash
+ ) internal view {
+ int256 shares = eigenPodManager.podOwnerShares(podowner);
+ uint64 withdrawableRestakedExecutionLayerGwei = pod.withdrawableRestakedExecutionLayerGwei();
+
+ EigenPod.ValidatorInfo memory info = pod.validatorPubkeyHashToInfo(validatorPubkeyHash);
+
+ uint64 validatorBalanceGwei = info.restakedBalanceGwei;
+ require(
+ shares / int256(GWEI_TO_WEI) ==
+ int256(uint256(validatorBalanceGwei)) + int256(uint256(withdrawableRestakedExecutionLayerGwei)),
+ "EigenPod invariant violated: sharesInSM != withdrawableRestakedExecutionLayerGwei"
+ );
+ }
+
+ function _proveWithdrawalForPod(IEigenPod newPod) internal returns (IEigenPod) {
+ BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(getLatestBlockRoot());
+ uint64 restakedExecutionLayerGweiBefore = newPod.withdrawableRestakedExecutionLayerGwei();
+
+ withdrawalFields = getWithdrawalFields();
+ 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);
+ emit log_named_uint("leftOverBalanceWEI", leftOverBalanceWEI);
+ emit log_named_uint("address(newPod)", address(newPod).balance);
+ emit log_named_uint("withdrawalAmountGwei", withdrawalAmountGwei);
+
+ uint256 delayedWithdrawalRouterContractBalanceBefore = address(delayedWithdrawalRouter).balance;
+ {
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](
+ 1
+ );
+ withdrawalProofsArray[0] = _getWithdrawalProof();
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ validatorFieldsProofArray[0] = abi.encodePacked(getValidatorProof());
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ withdrawalFieldsArray[0] = withdrawalFields;
+
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ newPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
+ }
+ require(
+ newPod.withdrawableRestakedExecutionLayerGwei() - restakedExecutionLayerGweiBefore ==
+ newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(),
+ "restakedExecutionLayerGwei has not been incremented correctly"
+ );
+ require(
+ address(delayedWithdrawalRouter).balance - delayedWithdrawalRouterContractBalanceBefore ==
+ leftOverBalanceWEI,
+ "pod delayed withdrawal balance hasn't been updated correctly"
+ );
+ require(
+ newPod.validatorPubkeyHashToInfo(getValidatorPubkeyHash()).restakedBalanceGwei == 0,
+ "balance not reset correctly"
+ );
+
+ cheats.roll(block.number + WITHDRAWAL_DELAY_BLOCKS + 1);
+ uint256 podOwnerBalanceBefore = address(podOwner).balance;
+ delayedWithdrawalRouter.claimDelayedWithdrawals(podOwner, 1);
+ require(
+ address(podOwner).balance - podOwnerBalanceBefore == leftOverBalanceWEI,
+ "Pod owner balance hasn't been updated correctly"
+ );
+ return newPod;
}
// simply tries to register 'sender' as a delegate, setting their 'DelegationTerms' contract in DelegationManager to 'dt'
// verifies that the storage of DelegationManager contract is updated appropriately
- function _testRegisterAsOperator(address sender, IDelegationTerms dt) internal {
+ function _testRegisterAsOperator(
+ address sender,
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) internal {
cheats.startPrank(sender);
-
- delegation.registerAsOperator(dt);
+ string memory emptyStringForMetadataURI;
+ delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
assertTrue(delegation.isOperator(sender), "testRegisterAsOperator: sender is not a delegate");
- assertTrue(
- delegation.delegationTerms(sender) == dt, "_testRegisterAsOperator: delegationTerms not set appropriately"
- );
+ // TODO: FIX THIS
+ // assertTrue(
+ // delegation.delegationTerms(sender) == dt, "_testRegisterAsOperator: delegationTerms not set appropriately"
+ // );
assertTrue(delegation.isDelegated(sender), "_testRegisterAsOperator: sender not marked as actively delegated");
cheats.stopPrank();
@@ -877,8 +1467,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
function _testDelegateToOperator(address sender, address operator) internal {
//delegator-specific information
- (IStrategy[] memory delegateStrategies, uint256[] memory delegateShares) =
- strategyManager.getDeposits(sender);
+ (IStrategy[] memory delegateStrategies, uint256[] memory delegateShares) = strategyManager.getDeposits(sender);
uint256 numStrats = delegateShares.length;
assertTrue(numStrats > 0, "_testDelegateToOperator: delegating from address with no deposits");
@@ -888,17 +1477,15 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
}
cheats.startPrank(sender);
- delegation.delegateTo(operator);
+ IDelegationManager.SignatureWithExpiry memory signatureWithExpiry;
+ delegation.delegateTo(operator, signatureWithExpiry, bytes32(0));
cheats.stopPrank();
assertTrue(
delegation.delegatedTo(sender) == operator,
"_testDelegateToOperator: delegated address not set appropriately"
);
- assertTrue(
- delegation.isDelegated(sender),
- "_testDelegateToOperator: delegated status not set appropriately"
- );
+ assertTrue(delegation.isDelegated(sender), "_testDelegateToOperator: delegated status not set appropriately");
for (uint256 i = 0; i < numStrats; ++i) {
uint256 operatorSharesBefore = inititalSharesInStrats[i];
@@ -909,36 +1496,32 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
);
}
}
- function _testDelegation(address operator, address staker)
- internal
- {
+
+ function _testDelegation(address operator, address staker) internal {
if (!delegation.isOperator(operator)) {
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _testRegisterAsOperator(operator, operatorDetails);
}
//making additional deposits to the strategies
- assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate");
+ assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
_testDelegateToOperator(staker, operator);
assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate");
IStrategy[] memory updatedStrategies;
uint256[] memory updatedShares;
- (updatedStrategies, updatedShares) =
- strategyManager.getDeposits(staker);
+ (updatedStrategies, updatedShares) = strategyManager.getDeposits(staker);
}
- function _testDeployAndVerifyNewEigenPod(address _podOwner, bytes memory _signature, bytes32 _depositDataRoot)
- internal returns (IEigenPod)
- {
- // (beaconStateRoot, beaconStateMerkleProofForValidators, validatorContainerFields, validatorMerkleProof, validatorTreeRoot, validatorRoot) =
- // getInitialDepositProof(validatorIndex);
-
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = _getValidatorFieldsAndBalanceProof();
- validatorFields = getValidatorFields();
- bytes32 newBeaconStateRoot = getBeaconStateRoot();
- uint40 validatorIndex = uint40(getValidatorIndex());
- BeaconChainOracleMock(address(beaconChainOracle)).setBeaconChainStateRoot(newBeaconStateRoot);
-
+ function _testDeployAndVerifyNewEigenPod(
+ address _podOwner,
+ bytes memory _signature,
+ bytes32 _depositDataRoot
+ ) internal returns (IEigenPod) {
IEigenPod newPod = eigenPodManager.getPod(_podOwner);
cheats.startPrank(_podOwner);
@@ -947,62 +1530,107 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenPodManager.stake{value: stakeAmount}(pubkey, _signature, _depositDataRoot);
cheats.stopPrank();
- uint64 blockNumber = 1;
- cheats.expectEmit(true, true, true, true, address(newPod));
- emit ValidatorRestaked(validatorIndex);
- newPod.verifyWithdrawalCredentialsAndBalance(blockNumber, validatorIndex, proofs, validatorFields);
+ return _verifyWithdrawalCredentials(newPod, _podOwner);
+ }
+
+ function _verifyWithdrawalCredentials(IEigenPod newPod, address _podOwner) internal returns (IEigenPod) {
+ _deployInternalFunctionTester();
+ uint64 timestamp = 0;
+ // cheats.expectEmit(true, true, true, true, address(newPod));
+ // emit ValidatorRestaked(validatorIndex);
+
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ validatorFieldsArray[0] = getValidatorFields();
+
+ bytes[] memory proofsArray = new bytes[](1);
+ proofsArray[0] = abi.encodePacked(getWithdrawalCredentialProof());
+
+ uint40[] memory validatorIndices = new uint40[](1);
+ validatorIndices[0] = uint40(getValidatorIndex());
- IStrategy beaconChainETHStrategy = strategyManager.beaconChainETHStrategy();
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+
+ int256 beaconChainETHSharesBefore = eigenPodManager.podOwnerShares(_podOwner);
+
+ cheats.startPrank(_podOwner);
+ cheats.warp(timestamp);
+ if (newPod.hasRestaked() == false) {
+ newPod.activateRestaking();
+ }
+ //set the oracle block root
+ _setOracleBlockRoot();
+
+ emit log_named_bytes32(
+ "restaking activated",
+ BeaconChainOracleMock(address(beaconChainOracle)).mockBeaconChainStateRoot()
+ );
- uint256 beaconChainETHShares = strategyManager.stakerStrategyShares(_podOwner, beaconChainETHStrategy);
- require(beaconChainETHShares == REQUIRED_BALANCE_WEI, "strategyManager shares not updated correctly");
+ cheats.warp(timestamp += 1);
+ newPod.verifyWithdrawalCredentials(
+ timestamp,
+ stateRootProofStruct,
+ validatorIndices,
+ proofsArray,
+ validatorFieldsArray
+ );
+ cheats.stopPrank();
+
+ int256 beaconChainETHSharesAfter = eigenPodManager.podOwnerShares(_podOwner);
+ uint256 effectiveBalance = uint256(Endian.fromLittleEndianUint64(validatorFieldsArray[0][2]) * GWEI_TO_WEI);
+
+ emit log_named_uint("effectiveBalance", effectiveBalance);
+ emit log_named_uint("MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR", MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR * GWEI_TO_WEI);
+ emit log_named_uint("beaconChainETHSharesAfter", uint256(beaconChainETHSharesAfter));
+ emit log_named_uint("beaconChainETHSharesBefore", uint256(beaconChainETHSharesBefore));
+
+ if(effectiveBalance < MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR * GWEI_TO_WEI){
+ require(
+ (beaconChainETHSharesAfter - beaconChainETHSharesBefore) == int256(effectiveBalance),
+ "eigenPodManager shares not updated correctly"
+ );
+ } else {
+ emit log("here)");
+ require(
+ (beaconChainETHSharesAfter - beaconChainETHSharesBefore) == int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR * GWEI_TO_WEI)),
+ "eigenPodManager shares not updated correctly"
+ );
+ emit log("here)");
+
+ }
return newPod;
}
+ /* TODO: reimplement similar tests
function _testQueueWithdrawal(
- address depositor,
- uint256[] memory strategyIndexes,
- IStrategy[] memory strategyArray,
- uint256[] memory shareAmounts,
- bool undelegateIfPossible
+ address _podOwner,
+ uint256 amountWei
)
internal
returns (bytes32)
{
- cheats.startPrank(depositor);
-
- //make a call with depositor aka podOwner also as withdrawer.
- bytes32 withdrawalRoot = strategyManager.queueWithdrawal(
- strategyIndexes,
- strategyArray,
- shareAmounts,
- depositor,
- // TODO: make this an input
- undelegateIfPossible
+ //make a call from _podOwner to queue the withdrawal
+ cheats.startPrank(_podOwner);
+ bytes32 withdrawalRoot = eigenPodManager.queueWithdrawal(
+ amountWei,
+ _podOwner
);
-
cheats.stopPrank();
return withdrawalRoot;
}
-
+*/
function _getLatestDelayedWithdrawalAmount(address recipient) internal view returns (uint256) {
- return delayedWithdrawalRouter.userDelayedWithdrawalByIndex(recipient, delayedWithdrawalRouter.userWithdrawalsLength(recipient) - 1).amount;
+ return
+ delayedWithdrawalRouter
+ .userDelayedWithdrawalByIndex(recipient, delayedWithdrawalRouter.userWithdrawalsLength(recipient) - 1)
+ .amount;
}
- function _getValidatorFieldsAndBalanceProof() internal returns (BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory) {
-
- bytes32 balanceRoot = getBalanceRoot();
- BeaconChainProofs.ValidatorFieldsAndBalanceProofs memory proofs = BeaconChainProofs.ValidatorFieldsAndBalanceProofs(
- abi.encodePacked(getWithdrawalCredentialProof()),
- abi.encodePacked(getValidatorBalanceProof()),
- balanceRoot
- );
-
- return proofs;
+ function _getStateRootProof() internal returns (BeaconChainProofs.StateRootProof memory) {
+ return BeaconChainProofs.StateRootProof(getBeaconStateRoot(), abi.encodePacked(getStateRootProof()));
}
/// @notice this function just generates a valid proof so that we can test other functionalities of the withdrawal flow
- function _getWithdrawalProof() internal returns(BeaconChainProofs.WithdrawalProofs memory) {
+ function _getWithdrawalProof() internal returns (BeaconChainProofs.WithdrawalProof memory) {
IEigenPod newPod = eigenPodManager.getPod(podOwner);
//make initial deposit
@@ -1012,73 +1640,237 @@ 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 beaconStateRoot = getBeaconStateRoot();
- //set beaconStateRoot
- beaconChainOracle.setBeaconChainStateRoot(beaconStateRoot);
- bytes32 blockHeaderRoot = getBlockHeaderRoot();
- bytes32 blockBodyRoot = getBlockBodyRoot();
+ bytes32 blockRoot = getBlockRoot();
bytes32 slotRoot = getSlotRoot();
- bytes32 blockNumberRoot = getBlockNumberRoot();
+ bytes32 timestampRoot = getTimestampRoot();
bytes32 executionPayloadRoot = getExecutionPayloadRoot();
-
-
-
- uint256 withdrawalIndex = getWithdrawalIndex();
- uint256 blockHeaderRootIndex = getBlockHeaderRootIndex();
-
-
- BeaconChainProofs.WithdrawalProofs memory proofs = BeaconChainProofs.WithdrawalProofs(
- abi.encodePacked(getBlockHeaderProof()),
- abi.encodePacked(getWithdrawalProof()),
- abi.encodePacked(getSlotProof()),
- abi.encodePacked(getExecutionPayloadProof()),
- abi.encodePacked(getBlockNumberProof()),
- uint64(blockHeaderRootIndex),
- uint64(withdrawalIndex),
- blockHeaderRoot,
- blockBodyRoot,
- slotRoot,
- blockNumberRoot,
- executionPayloadRoot
- );
- return proofs;
+ return
+ BeaconChainProofs.WithdrawalProof(
+ abi.encodePacked(withdrawalProof),
+ abi.encodePacked(getSlotProof()),
+ abi.encodePacked(getExecutionPayloadProof()),
+ abi.encodePacked(timestampProof),
+ abi.encodePacked(getHistoricalSummaryProof()),
+ uint64(getBlockRootIndex()),
+ uint64(getHistoricalSummaryIndex()),
+ uint64(getWithdrawalIndex()),
+ blockRoot,
+ slotRoot,
+ timestampRoot,
+ executionPayloadRoot
+ );
}
}
+
- function _getValidatorFieldsProof() internal returns(BeaconChainProofs.ValidatorFieldsProof memory) {
- IEigenPod newPod = eigenPodManager.getPod(podOwner);
-
- //make initial deposit
- cheats.startPrank(podOwner);
- cheats.expectEmit(true, true, true, true, address(newPod));
- emit EigenPodStaked(pubkey);
- eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
- cheats.stopPrank();
-
- {
- bytes32 beaconStateRoot = getBeaconStateRoot();
- //set beaconStateRoot
- beaconChainOracle.setBeaconChainStateRoot(beaconStateRoot);
- uint256 validatorIndex = getValidatorIndex();
- BeaconChainProofs.ValidatorFieldsProof memory proofs = BeaconChainProofs.ValidatorFieldsProof(
- abi.encodePacked(getValidatorProof()),
- uint40(validatorIndex)
- );
- return proofs;
- }
+ function _setOracleBlockRoot() internal {
+ bytes32 latestBlockRoot = getLatestBlockRoot();
+ //set beaconStateRoot
+ beaconChainOracle.setOracleBlockRootAtTimestamp(latestBlockRoot);
}
- }
+ function _computeTimestampAtSlot(uint64 slot) internal pure returns (uint64) {
+ return uint64(GOERLI_GENESIS_TIME + slot * SECONDS_PER_SLOT);
+ }
+ function _deployInternalFunctionTester() internal {
+ podInternalFunctionTester = new EPInternalFunctions(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ IEigenPodManager(podManagerAddress),
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
+ }
+}
- contract Relayer is Test {
- function verifyWithdrawalProofs(
+contract Relayer is Test {
+ function verifyWithdrawal(
bytes32 beaconStateRoot,
- BeaconChainProofs.WithdrawalProofs calldata proofs,
- bytes32[] calldata withdrawalFields
+ bytes32[] calldata withdrawalFields,
+ BeaconChainProofs.WithdrawalProof calldata proofs
) public view {
- BeaconChainProofs.verifyWithdrawalProofs(beaconStateRoot, proofs, withdrawalFields);
+ BeaconChainProofs.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs, type(uint64).max);
}
- }
\ No newline at end of file
+}
+
+
+//TODO: Integration Tests from old EPM unit tests:
+ // queues a withdrawal of "beacon chain ETH shares" from this address to itself
+ // fuzzed input amountGwei is sized-down, since it must be in GWEI and gets sized-up to be WEI
+// TODO: reimplement similar test
+ // function testQueueWithdrawalBeaconChainETHToSelf(uint128 amountGwei)
+ // public returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory, bytes32 /*withdrawalRoot*/)
+ // {
+ // // scale fuzzed amount up to be a whole amount of GWEI
+ // uint256 amount = uint256(amountGwei) * 1e9;
+ // address staker = address(this);
+ // address withdrawer = staker;
+
+ // testRestakeBeaconChainETHSuccessfully(staker, amount);
+
+ // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) =
+ // _createQueuedWithdrawal(staker, amount, withdrawer);
+
+ // return (queuedWithdrawal, withdrawalRoot);
+ // }
+// TODO: reimplement similar test
+ // function testQueueWithdrawalBeaconChainETHToDifferentAddress(address withdrawer, uint128 amountGwei)
+ // public
+ // filterFuzzedAddressInputs(withdrawer)
+ // returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory, bytes32 /*withdrawalRoot*/)
+ // {
+ // // scale fuzzed amount up to be a whole amount of GWEI
+ // uint256 amount = uint256(amountGwei) * 1e9;
+ // address staker = address(this);
+
+ // testRestakeBeaconChainETHSuccessfully(staker, amount);
+
+ // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) =
+ // _createQueuedWithdrawal(staker, amount, withdrawer);
+
+ // return (queuedWithdrawal, withdrawalRoot);
+ // }
+// TODO: reimplement similar test
+
+ // function testQueueWithdrawalBeaconChainETHFailsNonWholeAmountGwei(uint256 nonWholeAmount) external {
+ // // this also filters out the zero case, which will revert separately
+ // cheats.assume(nonWholeAmount % GWEI_TO_WEI != 0);
+ // cheats.expectRevert(bytes("EigenPodManager._queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei"));
+ // eigenPodManager.queueWithdrawal(nonWholeAmount, address(this));
+ // }
+
+ // function testQueueWithdrawalBeaconChainETHFailsZeroAmount() external {
+ // cheats.expectRevert(bytes("EigenPodManager._queueWithdrawal: amount must be greater than zero"));
+ // eigenPodManager.queueWithdrawal(0, address(this));
+ // }
+
+// TODO: reimplement similar test
+ // function testCompleteQueuedWithdrawal() external {
+ // address staker = address(this);
+ // uint256 withdrawalAmount = 1e18;
+
+ // // withdrawalAmount is converted to GWEI here
+ // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) =
+ // testQueueWithdrawalBeaconChainETHToSelf(uint128(withdrawalAmount / 1e9));
+
+ // IEigenPod eigenPod = eigenPodManager.getPod(staker);
+ // uint256 eigenPodBalanceBefore = address(eigenPod).balance;
+
+ // uint256 middlewareTimesIndex = 0;
+
+ // // actually complete the withdrawal
+ // cheats.startPrank(staker);
+ // cheats.expectEmit(true, true, true, true, address(eigenPodManager));
+ // emit BeaconChainETHWithdrawalCompleted(
+ // queuedWithdrawal.podOwner,
+ // queuedWithdrawal.shares,
+ // queuedWithdrawal.nonce,
+ // queuedWithdrawal.delegatedAddress,
+ // queuedWithdrawal.withdrawer,
+ // withdrawalRoot
+ // );
+ // eigenPodManager.completeQueuedWithdrawal(queuedWithdrawal, middlewareTimesIndex);
+ // cheats.stopPrank();
+
+ // // TODO: make EigenPodMock do something so we can verify that it gets called appropriately?
+ // uint256 eigenPodBalanceAfter = address(eigenPod).balance;
+
+ // // verify that the withdrawal root does bit exist after queuing
+ // require(!eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
+ // }
+
+// TODO: reimplement similar test
+ // // creates a queued withdrawal of "beacon chain ETH shares", from `staker`, of `amountWei`, "to" the `withdrawer`
+ // function _createQueuedWithdrawal(address staker, uint256 amountWei, address withdrawer)
+ // internal
+ // returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot)
+ // {
+ // // create the struct, for reference / to return
+ // queuedWithdrawal = IEigenPodManager.BeaconChainQueuedWithdrawal({
+ // shares: amountWei,
+ // podOwner: staker,
+ // nonce: eigenPodManager.cumulativeWithdrawalsQueued(staker),
+ // startBlock: uint32(block.number),
+ // delegatedTo: delegationManagerMock.delegatedTo(staker),
+ // withdrawer: withdrawer
+ // });
+
+ // // verify that the withdrawal root does not exist before queuing
+ // require(!eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
+
+ // // get staker nonce and shares before queuing
+ // uint256 nonceBefore = eigenPodManager.cumulativeWithdrawalsQueued(staker);
+ // int256 sharesBefore = eigenPodManager.podOwnerShares(staker);
+
+ // // actually create the queued withdrawal, and check for event emission
+ // cheats.startPrank(staker);
+
+ // cheats.expectEmit(true, true, true, true, address(eigenPodManager));
+ // emit BeaconChainETHWithdrawalQueued(
+ // queuedWithdrawal.podOwner,
+ // queuedWithdrawal.shares,
+ // queuedWithdrawal.nonce,
+ // queuedWithdrawal.delegatedAddress,
+ // queuedWithdrawal.withdrawer,
+ // eigenPodManager.calculateWithdrawalRoot(queuedWithdrawal)
+ // );
+ // withdrawalRoot = eigenPodManager.queueWithdrawal(amountWei, withdrawer);
+ // cheats.stopPrank();
+
+ // // verify that the withdrawal root does exist after queuing
+ // require(eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is false!");
+
+ // // verify that staker nonce incremented correctly and shares decremented correctly
+ // uint256 nonceAfter = eigenPodManager.cumulativeWithdrawalsQueued(staker);
+ // int256 sharesAfter = eigenPodManager.podOwnerShares(staker);
+ // require(nonceAfter == nonceBefore + 1, "nonce did not increment correctly on queuing withdrawal");
+ // require(sharesAfter + amountWei == sharesBefore, "shares did not decrement correctly on queuing withdrawal");
+
+ // return (queuedWithdrawal, withdrawalRoot);
+ // }
+
+ //Integration Test
+ // function testFullWithdrawalProofWithWrongWithdrawalFields(bytes32[] memory wrongWithdrawalFields) public {
+ // Relayer relay = new Relayer();
+ // uint256 WITHDRAWAL_FIELD_TREE_HEIGHT = 2;
+
+ // setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ // BeaconChainProofs.WithdrawalProof memory proofs = _getWithdrawalProof();
+ // bytes32 beaconStateRoot = getBeaconStateRoot();
+ // cheats.assume(wrongWithdrawalFields.length != 2 ** WITHDRAWAL_FIELD_TREE_HEIGHT);
+ // validatorFields = getValidatorFields();
+
+ // cheats.expectRevert(bytes("BeaconChainProofs.verifyWithdrawal: withdrawalFields has incorrect length"));
+ // relay.verifyWithdrawal(beaconStateRoot, wrongWithdrawalFields, proofs);
+ // }
+
+ // // Integration Test
+ // function testMismatchedWithdrawalProofInputs(uint64 numValidators, uint64 numValidatorProofs) external {
+ // cheats.assume(numValidators < numValidatorProofs && numValidatorProofs < 5);
+ // setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ // bytes[] memory validatorFieldsProofArray = new bytes[](numValidatorProofs);
+ // for (uint256 index = 0; index < numValidators; index++) {
+ // validatorFieldsProofArray[index] = abi.encodePacked(getValidatorProof());
+ // }
+ // bytes32[][] memory validatorFieldsArray = new bytes32[][](numValidators);
+ // for (uint256 index = 0; index < validatorFieldsArray.length; index++) {
+ // validatorFieldsArray[index] = getValidatorFields();
+ // }
+ // BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();
+ // BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](1);
+
+ // withdrawalProofsArray[0] = _getWithdrawalProof();
+
+ // bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+ // withdrawalFieldsArray[0] = withdrawalFields;
+
+ // cheats.expectRevert(bytes("EigenPod.verifyAndProcessWithdrawals: inputs must be same length"));
+ // pod.verifyAndProcessWithdrawals(0, stateRootProofStruct, withdrawalProofsArray, validatorFieldsProofArray, validatorFieldsArray, withdrawalFieldsArray);
+ // }
diff --git a/src/test/Pausable.t.sol b/src/test/Pausable.t.sol
index 8ab03ada59..2aea7a9965 100644
--- a/src/test/Pausable.t.sol
+++ b/src/test/Pausable.t.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "./EigenLayerTestHelper.t.sol";
diff --git a/src/test/Registration.t.sol b/src/test/Registration.t.sol
deleted file mode 100644
index 13ec593023..0000000000
--- a/src/test/Registration.t.sol
+++ /dev/null
@@ -1,124 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./EigenLayerTestHelper.t.sol";
-
-import "@openzeppelin/contracts/utils/math/Math.sol";
-
-import "../contracts/libraries/BytesLib.sol";
-
-import "./mocks/MiddlewareVoteWeigherMock.sol";
-import "./mocks/ServiceManagerMock.sol";
-import "./mocks/PublicKeyCompendiumMock.sol";
-import "./mocks/StrategyManagerMock.sol";
-
-import "../../src/contracts/middleware/BLSRegistry.sol";
-import "../../src/contracts/middleware/BLSPublicKeyCompendium.sol";
-
-
-
-contract RegistrationTests is EigenLayerTestHelper {
-
- BLSRegistry public dlRegImplementation;
- BLSPublicKeyCompendiumMock public pubkeyCompendium;
-
- BLSPublicKeyCompendiumMock public pubkeyCompendiumImplementation;
- BLSRegistry public dlReg;
- ProxyAdmin public dataLayrProxyAdmin;
-
- ServiceManagerMock public dlsm;
- StrategyManagerMock public strategyManagerMock;
-
- function setUp() public virtual override {
- EigenLayerDeployer.setUp();
- initializeMiddlewares();
- }
-
-
- function initializeMiddlewares() public {
- dataLayrProxyAdmin = new ProxyAdmin();
- fuzzedAddressMapping[address(dataLayrProxyAdmin)] = true;
-
- pubkeyCompendium = new BLSPublicKeyCompendiumMock();
-
- strategyManagerMock = new StrategyManagerMock();
- strategyManagerMock.setAddresses(delegation, eigenPodManager, slasher);
-
- dlsm = new ServiceManagerMock(slasher);
-
- dlReg = BLSRegistry(
- address(new TransparentUpgradeableProxy(address(emptyContract), address(dataLayrProxyAdmin), ""))
- );
-
- dlRegImplementation = new BLSRegistry(
- strategyManagerMock,
- dlsm,
- 2,
- pubkeyCompendium
- );
-
- uint256[] memory _quorumBips = new uint256[](2);
- // split 60% ETH quorum, 40% EIGEN quorum
- _quorumBips[0] = 6000;
- _quorumBips[1] = 4000;
-
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1);
- ethStratsAndMultipliers[0].strategy = wethStrat;
- ethStratsAndMultipliers[0].multiplier = 1e18;
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1);
- eigenStratsAndMultipliers[0].strategy = eigenStrat;
- eigenStratsAndMultipliers[0].multiplier = 1e18;
-
- dataLayrProxyAdmin.upgradeAndCall(
- TransparentUpgradeableProxy(payable(address(dlReg))),
- address(dlRegImplementation),
- abi.encodeWithSelector(BLSRegistry.initialize.selector, address(this), false, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers)
- );
-
- }
-
-
- function testRegisterOperator(address operator, uint32 operatorIndex, string calldata socket) public fuzzedAddress(operator) {
- cheats.assume(operatorIndex < 15);
- BN254.G1Point memory pk = getOperatorPubkeyG1(operatorIndex);
-
- //register as both ETH and EIGEN operator
- uint256 wethToDeposit = 1e18;
- uint256 eigenToDeposit = 1e18;
- _testDepositWeth(operator, wethToDeposit);
- _testDepositEigen(operator, eigenToDeposit);
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
-
- cheats.startPrank(operator);
- slasher.optIntoSlashing(address(dlsm));
- pubkeyCompendium.registerPublicKey(pk);
- dlReg.registerOperator(1, pk, socket);
- cheats.stopPrank();
-
- bytes32 pubkeyHash = BN254.hashG1Point(pk);
-
- (uint32 toBlockNumber, uint32 index) = dlReg.pubkeyHashToIndexHistory(pubkeyHash,0);
-
- assertTrue(toBlockNumber == 0, "block number set when it shouldn't be");
- assertTrue(index == 0, "index has been set incorrectly");
- assertTrue(dlReg.operatorList(0) == operator, "incorrect operator added");
- }
-
- function testDeregisterOperator(address operator, uint32 operatorIndex, string calldata socket) public fuzzedAddress(operator) {
- cheats.assume(operatorIndex < 15);
- BN254.G1Point memory pk = getOperatorPubkeyG1(operatorIndex);
-
- testRegisterOperator(operator, operatorIndex, socket);
- cheats.startPrank(operator);
- dlReg.deregisterOperator(pk, 0);
- cheats.stopPrank();
-
- bytes32 pubkeyHash = BN254.hashG1Point(pk);
- (uint32 toBlockNumber, /*uint32 index*/) = dlReg.pubkeyHashToIndexHistory(pubkeyHash,0);
- assertTrue(toBlockNumber == block.number, "toBlockNumber has been set incorrectly");
- }
-
-
-}
\ No newline at end of file
diff --git a/src/test/SigP/BeaconProxy.sol b/src/test/SigP/BeaconProxy.sol
deleted file mode 100644
index 4e796deb03..0000000000
--- a/src/test/SigP/BeaconProxy.sol
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-License-Identifier: MIT
-// OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol)
-
-pragma solidity ^0.8.0;
-
-import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
-import "@openzeppelin/contracts/proxy/Proxy.sol";
-import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
-
-/**
- * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
- *
- * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
- * conflict with the storage layout of the implementation behind the proxy.
- *
- * _Available since v3.4._
- */
-contract BeaconProxyNEW is Proxy, ERC1967Upgrade {
- /**
- * @dev Initializes the proxy with `beacon`.
- *
- * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
- * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity
- * constructor.
- *
- * Requirements:
- *
- * - `beacon` must be a contract with the interface {IBeacon}.
- */
- constructor(address beacon, bytes memory data) payable {
- _upgradeBeaconToAndCall(beacon, data, false);
- }
-
- /**
- * @dev Returns the current beacon address.
- */
- function _beacon() internal view virtual returns (address) {
- return _getBeacon();
- }
-
- /**
- * @dev Returns the current implementation address of the associated beacon.
- */
- function _implementation() internal view virtual override returns (address) {
- return IBeacon(_getBeacon()).implementation();
- }
-
- /**
- * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}.
- *
- * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon.
- *
- * Requirements:
- *
- * - `beacon` must be a contract.
- * - The implementation returned by `beacon` must be a contract.
- */
- function _setBeacon(address beacon, bytes memory data) internal virtual {
- _upgradeBeaconToAndCall(beacon, data, false);
- }
-}
diff --git a/src/test/SigP/DelegationTerms.sol b/src/test/SigP/DelegationTerms.sol
deleted file mode 100644
index dec54629f6..0000000000
--- a/src/test/SigP/DelegationTerms.sol
+++ /dev/null
@@ -1,46 +0,0 @@
-pragma solidity ^0.8.9;
-
-import "../../contracts/strategies/StrategyBase.sol";
-import "../../contracts/interfaces/IDelegationManager.sol";
-
-
-contract SigPDelegationTerms is IDelegationTerms {
- uint256 public paid;
- bytes public isDelegationWithdrawn;
- bytes public isDelegationReceived;
-
-
- function payForService(IERC20 /*token*/, uint256 /*amount*/) external payable {
- paid = 1;
- }
-
- function onDelegationWithdrawn(
- address /*delegator*/,
- IStrategy[] memory /*stakerStrats*/,
- uint256[] memory /*stakerShares*/
- ) external returns(bytes memory) {
- isDelegationWithdrawn = bytes("withdrawn");
- bytes memory _isDelegationWithdrawn = isDelegationWithdrawn;
- return _isDelegationWithdrawn;
- }
-
- // function onDelegationReceived(
- // address delegator,
- // uint256[] memory stakerShares
- // ) external;
-
- function onDelegationReceived(
- address /*delegator*/,
- IStrategy[] memory /*stakerStrats*/,
- uint256[] memory /*stakerShares*/
- ) external returns(bytes memory) {
- // revert("test");
- isDelegationReceived = bytes("received");
- bytes memory _isDelegationReceived = isDelegationReceived;
- return _isDelegationReceived;
- }
-
- function delegate() external {
- isDelegationReceived = bytes("received");
- }
-}
diff --git a/src/test/SigP/EigenPodManagerNEW.sol b/src/test/SigP/EigenPodManagerNEW.sol
deleted file mode 100644
index 095284c152..0000000000
--- a/src/test/SigP/EigenPodManagerNEW.sol
+++ /dev/null
@@ -1,241 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity ^0.8.9;
-
-import "@openzeppelin/contracts/utils/Create2.sol";
-import "./BeaconProxy.sol";
-import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
-import "@openzeppelin/contracts/access/Ownable.sol";
-
-import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
-import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
-
-import "../../contracts/interfaces/IStrategyManager.sol";
-import "../../contracts/interfaces/IDelegationManager.sol";
-import "../../contracts/interfaces/IEigenPodManager.sol";
-import "../../contracts/interfaces/IETHPOSDeposit.sol";
-import "../../contracts/interfaces/IEigenPod.sol";
-import "../../contracts/interfaces/IBeaconChainOracle.sol";
-
-// import "forge-std/Test.sol";
-
-/**
- * @title The contract used for creating and managing EigenPods
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice The main functionalities are:
- * - creating EigenPods
- * - staking for new validators on EigenPods
- * - keeping track of the balances of all validators of EigenPods, and their stake in EigenLayer
- * - withdrawing eth when withdrawals are initiated
- */
-contract EigenPodManagerNEW is Initializable, OwnableUpgradeable, IEigenPodManager {
- function getBeaconChainStateRoot(uint64 slot) external view returns(bytes32) {}
-
- function pause(uint256 newPausedStatus) external {}
-
- function pauseAll() external {}
-
- function paused() external view returns (uint256) {}
-
- function paused(uint8 index) external view returns (bool) {}
-
- function setPauserRegistry(IPauserRegistry newPauserRegistry) external {}
-
- function pauserRegistry() external view returns (IPauserRegistry) {}
-
- function unpause(uint256 newPausedStatus) external {}
-
- function ownerToPod(address podOwner) external view returns(IEigenPod) {}
-
-
- //TODO: change this to constant in prod
- IETHPOSDeposit internal immutable ethPOS;
- /// @notice Beacon proxy to which the EigenPods point
- IBeacon public immutable eigenPodBeacon;
-
- /// @notice EigenLayer's StrategyManager contract
- IStrategyManager public immutable strategyManager;
-
- /// @notice EigenLayer's Slasher contract
- ISlasher public immutable slasher;
-
- /// @notice Oracle contract that provides updates to the beacon chain's state
- IBeaconChainOracle public beaconChainOracle;
-
- /// @notice Pod owner to the amount of penalties they have paid that are still in this contract
- mapping(address => uint256) public podOwnerToUnwithdrawnPaidPenalties;
-
- /// @notice Emitted to notify the update of the beaconChainOracle address
- event BeaconOracleUpdated(address indexed newOracleAddress);
-
- /// @notice Emitted to notify the deployment of an EigenPod
- event PodDeployed(address indexed eigenPod, address indexed podOwner);
-
- /// @notice Emitted to notify a deposit of beacon chain ETH recorded in the manager
- event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
-
- /// @notice Emitted when an EigenPod pays penalties, on behalf of its owner
- event PenaltiesPaid(address indexed podOwner, uint256 amountPaid);
-
- modifier onlyEigenPod(address podOwner) {
- require(address(getPod(podOwner)) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");
- _;
- }
-
- modifier onlyStrategyManager {
- require(msg.sender == address(strategyManager), "EigenPodManager.onlyStrategyManager: not strategyManager");
- _;
- }
-
- constructor(IETHPOSDeposit _ethPOS, IBeacon _eigenPodBeacon, IStrategyManager _strategyManager, ISlasher _slasher) {
- ethPOS = _ethPOS;
- eigenPodBeacon = _eigenPodBeacon;
- strategyManager = _strategyManager;
- slasher = _slasher;
- _disableInitializers();
-
- }
-
- function initialize(IBeaconChainOracle _beaconChainOracle, address initialOwner) public initializer {
- _updateBeaconChainOracle(_beaconChainOracle);
- _transferOwnership(initialOwner);
- }
-
- /**
- * @notice Creates an EigenPod for the sender.
- * @dev Function will revert if the `msg.sender` already has an EigenPod.
- */
- function createPod() external {
- require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
- //deploy a pod if the sender doesn't have one already
- IEigenPod pod = _deployPod();
-
- emit PodDeployed(address(pod), msg.sender);
- }
-
- /**
- * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
- * Also creates an EigenPod for the sender if they don't have one already.
- * @param pubkey The 48 bytes public key of the beacon chain validator.
- * @param signature The validator's signature of the deposit data.
- * @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
- */
- function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable {
- IEigenPod pod = getPod(msg.sender);
- if(!hasPod(msg.sender)) {
- //deploy a pod if the sender doesn't have one already
- pod = _deployPod();
- }
- pod.stake{value: msg.value}(pubkey, signature, depositDataRoot);
- }
-
- /**
- * @notice Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod.
- * @param podOwner The owner of the pod whose balance must be deposited.
- * @param amount The amount of ETH to 'deposit' (i.e. be credited to the podOwner).
- * @dev Callable only by the podOwner's EigenPod contract.
- */
- function restakeBeaconChainETH(address podOwner, uint256 amount) external onlyEigenPod(podOwner) {
- strategyManager.depositBeaconChainETH(podOwner, amount);
- emit BeaconChainETHDeposited(podOwner, amount);
- }
-
- /**
- * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
- * balance of a validator is lower than how much stake they have committed to EigenLayer
- * @param podOwner The owner of the pod whose balance must be removed.
- * @param amount The amount of beacon chain ETH to decrement from the podOwner's shares in the strategyManager.
- * @dev Callable only by the podOwner's EigenPod contract.
- */
- function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external onlyEigenPod(podOwner) {
- strategyManager.recordOvercommittedBeaconChainETH(podOwner, beaconChainETHStrategyIndex, amount);
- }
-
- /**
- * @notice Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain.
- * @param podOwner The owner of the pod whose balance must be withdrawn.
- * @param recipient The recipient of the withdrawn ETH.
- * @param amount The amount of ETH to withdraw.
- * @dev Callable only by the StrategyManager contract.
- */
- function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount) external onlyStrategyManager {
- getPod(podOwner).withdrawRestakedBeaconChainETH(recipient, amount);
- }
-
- /**
- * @notice Records receiving ETH from the `PodOwner`'s EigenPod, paid in order to fullfill the EigenPod's penalties to EigenLayer
- * @param podOwner The owner of the pod whose balance is being sent.
- * @dev Callable only by the podOwner's EigenPod contract.
- */
- function payPenalties(address podOwner) external payable onlyEigenPod(podOwner) {
- podOwnerToUnwithdrawnPaidPenalties[podOwner] += msg.value;
- emit PenaltiesPaid(podOwner, msg.value);
- }
-
- /**
- * @notice Withdraws paid penalties of the `podOwner`'s EigenPod, to the `recipient` address
- * @param recipient The recipient of withdrawn ETH.
- * @param amount The amount of ETH to withdraw.
- * @dev Callable only by the strategyManager.owner().
- */
- function withdrawPenalties(address podOwner, address recipient, uint256 amount) external {
- require(msg.sender == Ownable(address(strategyManager)).owner(), "EigenPods.withdrawPenalties: only strategyManager owner");
- podOwnerToUnwithdrawnPaidPenalties[podOwner] -= amount;
- // transfer penalties from pod to `recipient`
- Address.sendValue(payable(recipient), amount);
- }
-
- /**
- * @notice Updates the oracle contract that provides the beacon chain state root
- * @param newBeaconChainOracle is the new oracle contract being pointed to
- * @dev Callable only by the owner of this contract (i.e. governance)
- */
- function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner {
- _updateBeaconChainOracle(newBeaconChainOracle);
- }
-
-
- // INTERNAL FUNCTIONS
- function _deployPod() internal returns (IEigenPod) {
- IEigenPod pod =
- IEigenPod(
- Create2.deploy(
- 0,
- bytes32(uint256(uint160(msg.sender))),
- // set the beacon address to the eigenPodBeacon and initialize it
- abi.encodePacked(
- type(BeaconProxyNEW).creationCode,
- abi.encode(eigenPodBeacon, abi.encodeWithSelector(IEigenPod.initialize.selector, IEigenPodManager(address(this)), msg.sender))
- )
- )
- );
- return pod;
- }
-
- function _updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) internal {
- beaconChainOracle = newBeaconChainOracle;
- emit BeaconOracleUpdated(address(newBeaconChainOracle));
- }
-
- // VIEW FUNCTIONS
- /// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
- function getPod(address podOwner) public view returns (IEigenPod) {
- return IEigenPod(
- Create2.computeAddress(
- bytes32(uint256(uint160(podOwner))), //salt
- keccak256(abi.encodePacked(
- type(BeaconProxyNEW).creationCode,
- abi.encode(eigenPodBeacon, abi.encodeWithSelector(IEigenPod.initialize.selector, IEigenPodManager(address(this)), podOwner))
- )) //bytecode
- ));
- }
-
- /// @notice Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
- function hasPod(address podOwner) public view returns (bool) {
- return address(getPod(podOwner)).code.length > 0;
- }
-
- function getBeaconChainStateRoot() external view returns(bytes32) {
- // return beaconChainOracle.getBeaconChainStateRoot();
- }
-}
\ No newline at end of file
diff --git a/src/test/Slasher.t.sol b/src/test/Slasher.t.sol
deleted file mode 100644
index 3ed511f78f..0000000000
--- a/src/test/Slasher.t.sol
+++ /dev/null
@@ -1,338 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./EigenLayerDeployer.t.sol";
-import "./EigenLayerTestHelper.t.sol";
-import "../contracts/operators/MerkleDelegationTerms.sol";
-
-contract SlasherTests is EigenLayerTestHelper {
- ISlasher instance;
- uint256 constant HEAD = 0;
- address middleware = address(0xdeadbeef);
- address middleware_2 = address(0x009849);
- address middleware_3 = address(0x001000);
- address middleware_4 = address(0x002000);
- MerkleDelegationTerms delegationTerms;
-
- //performs basic deployment before each test
- function setUp() public override {
- super.setUp();
- delegationTerms = new MerkleDelegationTerms();
- }
-
- /**
- * @notice this function tests the slashing process by first freezing
- * the operator and then calling the strategyManager.slashShares()
- * to actually enforce the slashing conditions.
- */
- function testSlashing() public {
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
-
- // hardcoded inputs
- address[2] memory accounts = [acct_0, acct_1];
- uint256[2] memory depositAmounts;
- uint256 amountToDeposit = 1e18;
- address _operator = operator;
- strategyArray[0] = wethStrat;
- tokensArray[0] = weth;
-
- // have `_operator` make deposits in WETH strategy
- _testDepositWeth(_operator, amountToDeposit);
- // register `_operator` as an operator
- _testRegisterAsOperator(_operator, IDelegationTerms(_operator));
-
- // make deposit in WETH strategy from each of `accounts`, then delegate them to `_operator`
- for (uint256 i = 0; i < accounts.length; i++) {
- depositAmounts[i] = _testDepositWeth(accounts[i], amountToDeposit);
- _testDelegateToOperator(accounts[i], _operator);
- }
-
- uint256[] memory shareAmounts = new uint256[](1);
- shareAmounts[0] = depositAmounts[0];
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- cheats.startPrank(_operator);
- slasher.optIntoSlashing(address(this));
- cheats.stopPrank();
-
- slasher.freezeOperator(_operator);
-
- uint256 prev_shares = delegation.operatorShares(_operator, strategyArray[0]);
-
- strategyManager.slashShares(_operator, acct_0, strategyArray, tokensArray, strategyIndexes, shareAmounts);
-
- require(
- delegation.operatorShares(_operator, strategyArray[0]) + shareAmounts[0] == prev_shares,
- "Malicious Operator slashed by incorrect amount"
- );
- }
-
- /**
- * @notice testing ownable permissions for slashing functions
- * addPermissionedContracts(), removePermissionedContracts()
- * and resetFrozenStatus().
- */
- function testOnlyOwnerFunctions(address incorrectCaller, address inputAddr)
- public
- fuzzedAddress(incorrectCaller)
- fuzzedAddress(inputAddr)
- {
- cheats.assume(incorrectCaller != slasher.owner());
- cheats.startPrank(incorrectCaller);
- address[] memory addressArray = new address[](1);
- addressArray[0] = inputAddr;
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- slasher.resetFrozenStatus(addressArray);
- cheats.stopPrank();
- }
-
-
- function testRecursiveCallRevert() public {
- //Register and opt into slashing with operator
- cheats.startPrank(operator);
- delegation.registerAsOperator(delegationTerms);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_2);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
- //these calls come from middlewares, we need more than 1 middleware to trigger the if clause on line 179
- // we need more than 2 middlewares to trigger the incorrect insertAfter being supplied by getCorrectValueForInsertAfter()
- cheats.startPrank(middleware);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_2);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_3);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware);
-
- //we cannot add updateBlocks in the future so skip ahead to block 5.
- cheats.roll(5);
- //convert the middleware node address to a node number
- uint256 insertAfter = uint256(uint160(middleware));
-
- //Force fallbackRoutine to occur by specifying the wrong insertAfter. This then loops until we revert
- slasher.recordStakeUpdate(operator,5, serveUntilBlock, insertAfter);
-
-
- }
-
- function testRecordFirstStakeUpdate() public {
-
- //Register and opt into slashing with operator
- cheats.startPrank(operator);
- delegation.registerAsOperator(delegationTerms);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
-
- cheats.startPrank(middleware_2);
- //unapproved slasher calls should fail
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware);
- //valid conditions should succeed
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
-
- //repeated calls to FirstStakeUpdate from the same middleware should fail.
- cheats.expectRevert("Slasher.recordFirstStakeUpdate: Appending middleware unsuccessful");
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_3);
- //sequential calls from different approved slashing middlewares should succeed
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- }
-
- function testRecordStakeUpdate() public {
- ///Register and opt into slashing with operator
- cheats.startPrank(operator);
- delegation.registerAsOperator(delegationTerms);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
- //convert the middleware node address to a node number
- uint256 insertAfter = uint256(uint160(middleware));
-
- cheats.startPrank(middleware);
-
- //calling before first stake update should fail
- cheats.expectRevert("Slasher.recordStakeUpdate: Removing middleware unsuccessful");
- slasher.recordStakeUpdate(operator,1, serveUntilBlock, insertAfter);
-
- //set up first stake update
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
-
- cheats.expectRevert("Slasher.recordStakeUpdate: cannot provide update for future block");
- slasher.recordStakeUpdate(operator,5, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_2);
- //calling from unapproved middleware should fail
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordStakeUpdate(operator,1, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
-
- cheats.startPrank(middleware);
- //should succeed when given the correct settings
- cheats.roll(5);
- slasher.recordStakeUpdate(operator,3, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testOrderingRecordStakeUpdateVuln() public {
- ///Register and opt into slashing with operator
- cheats.startPrank(operator);
- delegation.registerAsOperator(delegationTerms);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
- //convert the middleware node address to a node number
- uint256 insertAfter = uint256(uint160(middleware));
-
- cheats.startPrank(middleware);
- //set up first stake update so sizeOf check on line179 is equal to 1
- //uint256 sizeOf = slasher.sizeOfOperatorList(operator);
- uint256 sizeOf = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- require(sizeOf + 1 == slasher.operatorWhitelistedContractsLinkedListSize(operator));
- cheats.stopPrank();
-
- //calling recordStakeUpdate() before recordFirstStakeUpdate() for middleware_3 should fail
- cheats.startPrank(middleware_3);
- sizeOf = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- cheats.expectRevert("Slasher.recordStakeUpdate: Caller is not the list entrant");
- slasher.recordStakeUpdate(operator,1, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
- }
-
- function testOnlyRegisteredForService(address _slasher, uint32 _serveUntilBlock) public fuzzedAddress(_slasher) {
- cheats.prank(operator);
- delegation.registerAsOperator(delegationTerms);
-
- //slasher cannot call stake update unless operator has oped in
- cheats.prank(_slasher);
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordFirstStakeUpdate(operator, _serveUntilBlock);
-
- cheats.prank(_slasher);
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordStakeUpdate(operator, 1,_serveUntilBlock,1);
-
- cheats.prank(_slasher);
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, _serveUntilBlock);
- }
-
- function testOptIn(address _operator, address _slasher) public fuzzedAddress(_slasher) fuzzedAddress(_operator) {
-
- //cannot opt in until registered as operator
- cheats.prank(_operator);
- cheats.expectRevert("Slasher.optIntoSlashing: msg.sender is not a registered operator");
- slasher.optIntoSlashing(_slasher);
-
- //can opt in after registered as operator
- cheats.startPrank(_operator);
- delegation.registerAsOperator(delegationTerms);
- slasher.optIntoSlashing(_slasher);
- cheats.stopPrank();
- }
-
- function testFreezeOperator() public {
- cheats.prank(operator);
- delegation.registerAsOperator(delegationTerms);
-
- //cannot freeze until operator has oped in
- cheats.prank(middleware);
- cheats.expectRevert("Slasher.freezeOperator: msg.sender does not have permission to slash this operator");
- slasher.freezeOperator(operator);
-
- cheats.prank(operator);
- slasher.optIntoSlashing(middleware);
-
- //can freeze after operator has oped in
- cheats.prank(middleware);
- slasher.freezeOperator(operator);
-
- bool frozen = slasher.isFrozen(operator);
- require(frozen,"operator should be frozen");
- }
-
- function testResetFrozenOperator(address _attacker) public fuzzedAddress(_attacker) {
- cheats.assume(_attacker != slasher.owner());
-
- cheats.prank(operator);
- delegation.registerAsOperator(delegationTerms);
-
- cheats.prank(operator);
- slasher.optIntoSlashing(middleware);
-
- cheats.prank(middleware);
- slasher.freezeOperator(operator);
-
- address[] memory frozenAddresses = new address[](1);
- frozenAddresses[0] = operator;
-
- //no other address can unfreeze
- cheats.prank(_attacker);
- cheats.expectRevert("Ownable: caller is not the owner");
- slasher.resetFrozenStatus(frozenAddresses);
-
- //owner can unfreeze
- cheats.prank(slasher.owner());
- slasher.resetFrozenStatus(frozenAddresses);
-
- bool frozen = slasher.isFrozen(operator);
- require(!frozen,"operator should be unfrozen");
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility() public {
- ///Register and opt into slashing with operator
- cheats.startPrank(operator);
- delegation.registerAsOperator(delegationTerms);
- slasher.optIntoSlashing(middleware);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = 10;
-
- //stake update
- cheats.prank(middleware);
- slasher.recordFirstStakeUpdate(operator,serveUntilBlock);
-
- console.log("serveUntilBlock",slasher.getMiddlewareTimesIndexServeUntilBlock(operator,0));
- console.log("contractCanSlashOperatorUntil",slasher.contractCanSlashOperatorUntilBlock(operator,middleware));
-
- //middle can slash
- require(slasher.canSlash(operator,middleware),"middlewre should be able to slash");
-
- //revoke slashing
- cheats.prank(middleware);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator,serveUntilBlock);
-
- cheats.roll(serveUntilBlock);
- //middleware can no longer slash
- require(!slasher.canSlash(operator,middleware),"middlewre should no longer be able to slash");
- }
-}
diff --git a/src/test/Strategy.t.sol b/src/test/Strategy.t.sol
index 2a776cdea8..3139e92a2a 100644
--- a/src/test/Strategy.t.sol
+++ b/src/test/Strategy.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "./EigenLayerTestHelper.t.sol";
import "../contracts/core/StrategyManagerStorage.sol";
diff --git a/src/test/Whitelister.t.sol b/src/test/Whitelister.t.sol
deleted file mode 100644
index 759c3fe920..0000000000
--- a/src/test/Whitelister.t.sol
+++ /dev/null
@@ -1,333 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../src/contracts/interfaces/IStrategyManager.sol";
-import "../../src/contracts/interfaces/IStrategy.sol";
-import "../../src/contracts/interfaces/IDelegationManager.sol";
-import "../../src/contracts/strategies/StrategyBase.sol";
-import "../../src/contracts/middleware/BLSRegistry.sol";
-
-import "../../src/test/mocks/ServiceManagerMock.sol";
-import "../../src/test/mocks/PublicKeyCompendiumMock.sol";
-import "../../src/test/mocks/MiddlewareVoteWeigherMock.sol";
-
-
-
-import "../../script/whitelist/ERC20PresetMinterPauser.sol";
-
-import "../../script/whitelist/Staker.sol";
-import "../../script/whitelist/Whitelister.sol";
-
-import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-import "@openzeppelin/contracts/access/Ownable.sol";
-import "@openzeppelin/contracts/utils/Create2.sol";
-
-import "./EigenLayerTestHelper.t.sol";
-import "./Delegation.t.sol";
-
-import "forge-std/Test.sol";
-
-contract WhitelisterTests is EigenLayerTestHelper {
-
- ERC20PresetMinterPauser dummyToken;
- IStrategy dummyStrat;
- IStrategy dummyStratImplementation;
- Whitelister whiteLister;
-
- BLSRegistry blsRegistry;
- BLSRegistry blsRegistryImplementation;
-
-
- ServiceManagerMock dummyServiceManager;
- BLSPublicKeyCompendiumMock dummyCompendium;
- MiddlewareRegistryMock dummyReg;
-
-
-
- modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) {
- cheats.assume(ethAmount >= 0 && ethAmount <= 1e18);
- cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18);
- _;
- }
-
- uint256 AMOUNT;
-
- // packed info used to help handle stack-too-deep errors
- struct DataForTestWithdrawal {
- IStrategy[] delegatorStrategies;
- uint256[] delegatorShares;
- IStrategyManager.WithdrawerAndNonce withdrawerAndNonce;
- }
-
- function setUp() public virtual override{
- EigenLayerDeployer.setUp();
-
- emptyContract = new EmptyContract();
-
- dummyCompendium = new BLSPublicKeyCompendiumMock();
- blsRegistry = BLSRegistry(
- address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
- );
-
- dummyToken = new ERC20PresetMinterPauser("dummy staked ETH", "dsETH");
- dummyStratImplementation = new StrategyBase(strategyManager);
- dummyStrat = StrategyBase(
- address(
- new TransparentUpgradeableProxy(
- address(dummyStratImplementation),
- address(eigenLayerProxyAdmin),
- abi.encodeWithSelector(StrategyBase.initialize.selector, dummyToken, eigenLayerPauserReg)
- )
- )
- );
-
- whiteLister = new Whitelister(strategyManager, delegation, dummyToken, dummyStrat, blsRegistry);
- whiteLister.transferOwnership(theMultiSig);
- AMOUNT = whiteLister.DEFAULT_AMOUNT();
-
- dummyToken.grantRole(keccak256("MINTER_ROLE"), address(whiteLister));
- dummyToken.grantRole(keccak256("PAUSER_ROLE"), address(whiteLister));
-
- dummyToken.grantRole(keccak256("MINTER_ROLE"), theMultiSig);
- dummyToken.grantRole(keccak256("PAUSER_ROLE"), theMultiSig);
-
- dummyToken.revokeRole(keccak256("MINTER_ROLE"), address(this));
- dummyToken.revokeRole(keccak256("PAUSER_ROLE"), address(this));
-
-
- dummyServiceManager = new ServiceManagerMock(slasher);
- blsRegistryImplementation = new BLSRegistry(strategyManager, dummyServiceManager, 2, dummyCompendium);
-
- uint256[] memory _quorumBips = new uint256[](2);
- // split 60% ETH quorum, 40% EIGEN quorum
- _quorumBips[0] = 6000;
- _quorumBips[1] = 4000;
-
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1);
- ethStratsAndMultipliers[0].strategy = wethStrat;
- ethStratsAndMultipliers[0].multiplier = 1e18;
- VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers =
- new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1);
- eigenStratsAndMultipliers[0].strategy = eigenStrat;
- eigenStratsAndMultipliers[0].multiplier = 1e18;
-
-
- cheats.startPrank(eigenLayerProxyAdmin.owner());
- eigenLayerProxyAdmin.upgradeAndCall(
- TransparentUpgradeableProxy(payable(address(blsRegistry))),
- address(blsRegistryImplementation),
- abi.encodeWithSelector(BLSRegistry.initialize.selector, address(whiteLister), true, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers)
- );
- cheats.stopPrank();
-
-
- dummyReg = new MiddlewareRegistryMock(
- dummyServiceManager,
- strategyManager
- );
-
- fuzzedAddressMapping[address(whiteLister)] = true;
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.strategyWhitelister());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
- }
-
- function testWhitelistingOperator(address operator) public fuzzedAddress(operator) {
- cheats.startPrank(operator);
- IDelegationTerms dt = IDelegationTerms(address(89));
- delegation.registerAsOperator(dt);
- cheats.stopPrank();
-
- cheats.startPrank(theMultiSig);
- whiteLister.whitelist(operator);
- cheats.stopPrank();
-
- assertTrue(blsRegistry.whitelisted(operator) == true, "operator not added to whitelist");
- }
-
- function testWhitelistDepositIntoStrategy(address operator, uint256 depositAmount) external fuzzedAddress(operator) {
- cheats.assume(depositAmount < AMOUNT);
- testWhitelistingOperator(operator);
-
- cheats.startPrank(theMultiSig);
- address staker = whiteLister.getStaker(operator);
- dummyToken.mint(staker, AMOUNT);
-
- whiteLister.depositIntoStrategy(staker, dummyStrat, dummyToken, depositAmount);
- cheats.stopPrank();
- }
-
- function testCallStakerFromNonWhitelisterAddress(address nonWhitelister, bytes memory data) external fuzzedAddress(nonWhitelister) {
- testWhitelistingOperator(operator);
- address staker = whiteLister.getStaker(operator);
-
- cheats.startPrank(nonWhitelister);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- Staker(staker).callAddress(address(strategyManager), data);
- }
-
- function testNonWhitelistedOperatorRegistration(BN254.G1Point memory pk, string memory socket ) external {
- cheats.startPrank(operator);
- IDelegationTerms dt = IDelegationTerms(address(89));
- delegation.registerAsOperator(dt);
- cheats.stopPrank();
-
- cheats.expectRevert(bytes("BLSRegistry._registerOperator: not whitelisted"));
- blsRegistry.registerOperator(1, pk, socket);
- }
-
-
-
- function testWhitelistQueueWithdrawal(
- address operator
- )
- public fuzzedAddress(operator)
- {
-
- address staker = whiteLister.getStaker(operator);
- cheats.assume(staker!=operator);
- _testRegisterAsOperator(operator, IDelegationTerms(operator));
-
- {
- cheats.startPrank(theMultiSig);
- whiteLister.whitelist(operator);
- cheats.stopPrank();
-
- cheats.startPrank(operator);
- slasher.optIntoSlashing(address(dummyServiceManager));
- dummyReg.registerOperator(operator, uint32(block.timestamp) + 3 days);
- cheats.stopPrank();
- }
-
- // packed data structure to deal with stack-too-deep issues
- DataForTestWithdrawal memory dataForTestWithdrawal;
- uint256 expectedTokensOut;
-
- // scoped block to deal with stack-too-deep issues
- {
- //delegator-specific information
- (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) =
- strategyManager.getDeposits(staker);
- dataForTestWithdrawal.delegatorStrategies = delegatorStrategies;
- dataForTestWithdrawal.delegatorShares = delegatorShares;
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce =
- IStrategyManager.WithdrawerAndNonce({
- withdrawer: staker,
- // harcoded nonce value
- nonce: 0
- }
- );
- dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce;
- // find the expected amount out
- expectedTokensOut = delegatorStrategies[0].sharesToUnderlying(delegatorShares[0]);
- emit log_named_uint("expectedTokensOut", expectedTokensOut);
- }
-
- uint256[] memory strategyIndexes = new uint256[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- {
- // hardcoded values
- strategyIndexes[0] = 0;
- tokensArray[0] = dummyToken;
- }
-
- _testQueueWithdrawal(
- staker,
- dataForTestWithdrawal.delegatorStrategies,
- dataForTestWithdrawal.delegatorShares,
- strategyIndexes
- );
-
- {
- uint256 balanceBeforeWithdrawal = dummyToken.balanceOf(staker);
-
- _testCompleteQueuedWithdrawal(
- staker,
- dataForTestWithdrawal.delegatorStrategies,
- tokensArray,
- dataForTestWithdrawal.delegatorShares,
- operator,
- dataForTestWithdrawal.withdrawerAndNonce,
- uint32(block.number),
- 1
- );
- emit log_named_uint("Balance Before Withdrawal", balanceBeforeWithdrawal);
- emit log_named_uint("Balance After Withdrawal", dummyToken.balanceOf(staker));
-
- require(dummyToken.balanceOf(staker) == balanceBeforeWithdrawal + expectedTokensOut, "balance not incremented as expected");
-
- }
- }
-
- function _testQueueWithdrawal(
- address staker,
- IStrategy[] memory strategyArray,
- uint256[] memory shareAmounts,
- uint256[] memory strategyIndexes
- )
- internal
- {
- cheats.startPrank(theMultiSig);
- whiteLister.queueWithdrawal(
- staker,
- strategyIndexes,
- strategyArray,
- shareAmounts,
- staker,
- true
- );
- cheats.stopPrank();
- }
-
- function _testCompleteQueuedWithdrawal(
- address staker,
- IStrategy[] memory strategyArray,
- IERC20[] memory tokensArray,
- uint256[] memory shareAmounts,
- address delegatedTo,
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce,
- uint32 withdrawalStartBlock,
- uint256 middlewareTimesIndex
- )
- internal
- {
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal = IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: withdrawalStartBlock,
- delegatedAddress: delegatedTo
- });
-
- // emit log("*******************COMPLETE***************************");
- // emit log_named_address("delegatedAddress", delegatedTo);
- // emit log_named_uint("withdrawalStartBlock", withdrawalStartBlock);
- // emit log_named_uint("withdrawerAndNonce.Nonce", withdrawerAndNonce.nonce);
- // emit log_named_address("withdrawerAndNonce.Adress", withdrawerAndNonce.withdrawer);
- // emit log_named_address("depositor", staker);
- // emit log("***********************************************************************");
-
- cheats.startPrank(theMultiSig);
- whiteLister.completeQueuedWithdrawal(staker, queuedWithdrawal, tokensArray, middlewareTimesIndex, true);
- cheats.stopPrank();
- }
-
- function testWhitelistTransfer(address operator, address receiver) public fuzzedAddress(receiver) {
- address staker = whiteLister.getStaker(operator);
-
- testWhitelistQueueWithdrawal(operator);
-
- cheats.startPrank(theMultiSig);
-
- whiteLister.transfer(staker, address(dummyToken), receiver, AMOUNT);
- cheats.stopPrank();
- require(dummyToken.balanceOf(receiver) == AMOUNT, "receiver hasn't received tokens");
- }
-}
\ No newline at end of file
diff --git a/src/test/WithdrawalMigration.t.sol b/src/test/WithdrawalMigration.t.sol
new file mode 100644
index 0000000000..500031031b
--- /dev/null
+++ b/src/test/WithdrawalMigration.t.sol
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../test/EigenLayerTestHelper.t.sol";
+import "src/test/utils/Utils.sol";
+import "./mocks/ERC20Mock.sol";
+
+///@notice This set of tests shadow forks the contracts from M1, queues withdrawals, and tests the migration functionality
+contract WithdrawalMigrationTests is EigenLayerTestHelper, Utils {
+ // Pointers to M1 contracts
+ address private constant _M1_EIGENLAYER_PROXY_ADMIN = 0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444;
+ address private constant _M1_STRATEGY_MANAGER = 0x858646372CC42E1A627fcE94aa7A7033e7CF075A;
+ address private constant _M1_DELEGATION_MANAGER = 0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A;
+ address private constant _M1_EIGENPOD_MANAGER = 0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338;
+ address private constant _M1_SLASHER = 0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd;
+ address private constant _M1_UNPAUSER = 0x369e6F597e22EaB55fFb173C6d9cD234BD699111;
+ address private constant _M1_PAUSER_REGISTRY = 0x0c431C66F4dE941d089625E5B423D00707977060;
+ address private constant _M1_CBETH_STRATEGY = 0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc;
+ address private constant _CBETH_ADDRESS = 0xBe9895146f7AF43049ca1c1AE358B0541Ea49704;
+
+ // Block to fork from, must be before M1 upgrade
+ uint256 private constant _M1_BLOCK_FORK = 18322601;
+
+ IM1StrategyManager m1StrategyManager = IM1StrategyManager(_M1_STRATEGY_MANAGER);
+ ProxyAdmin m1EigenLayerProxyAdmin = ProxyAdmin(_M1_EIGENLAYER_PROXY_ADMIN);
+ DelegationManager m1DelegationManager = DelegationManager(_M1_DELEGATION_MANAGER);
+ IStrategy beaconChainETHStrategy;
+ IStrategy cbETHStrategy = IStrategy(_M1_CBETH_STRATEGY);
+ IERC20 cbETH = IERC20(_CBETH_ADDRESS);
+
+ function setUp() public override {
+ vm.createSelectFork(cheats.envString("RPC_MAINNET"), _M1_BLOCK_FORK);
+ beaconChainETHStrategy = m1StrategyManager.beaconChainETHStrategy();
+ // Unpause strategyManager
+ cheats.prank(_M1_UNPAUSER);
+ m1StrategyManager.unpause(0);
+ }
+
+ function testMigrateWithdrawalBeaconChainETHStrategy(uint128 amountGwei) public {
+ // Queue a withdrawal
+ (
+ bytes32 withdrawalRootSM,
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal
+ ) = _queueWithdrawalBeaconChainETH(amountGwei);
+
+ // Assert that withdrawal is queued
+ assertTrue(m1StrategyManager.withdrawalRootPending(withdrawalRootSM));
+
+ // Upgrade, migrate, and check
+ _upgradeAndMigrate(withdrawalRootSM, queuedWithdrawal);
+ }
+
+ function testMigrateQueuedWithdrawalStrategy(
+ uint256 withdrawalAmount,
+ uint256 depositAmount,
+ bool undelegateIfPossible
+ ) public {
+ depositAmount = bound(
+ depositAmount,
+ 1e3,
+ IMaxDepositAmount(address(cbETHStrategy)).maxTotalDeposits() - cbETH.balanceOf(address(cbETHStrategy))
+ );
+ withdrawalAmount = bound(withdrawalAmount, 1, cbETHStrategy.underlyingToShares(depositAmount));
+
+ // Deal tokens
+ deal(address(cbETH), address(this), depositAmount);
+ cbETH.approve(address(m1StrategyManager), depositAmount);
+
+ // Queue withdrawal
+ (
+ bytes32 withdrawalRootSM,
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal
+ ) = _queueWithdrawalTokenStrategy(address(this), depositAmount, withdrawalAmount, undelegateIfPossible);
+
+ // Assert that withdrawal is queued
+ assertTrue(m1StrategyManager.withdrawalRootPending(withdrawalRootSM));
+
+ // Upgrade, migrate, and check
+ _upgradeAndMigrate(withdrawalRootSM, queuedWithdrawal);
+ }
+
+ function _upgradeAndMigrate(
+ bytes32 withdrawalRootSM,
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal
+ ) internal {
+ // Upgrade M1 Contracts
+ _upgradeM1Contracts();
+
+ // Format withdrawal struct as represented in delegationManager
+ IDelegationManager.Withdrawal memory migratedWithdrawal = IDelegationManager.Withdrawal({
+ staker: queuedWithdrawal.staker,
+ delegatedTo: queuedWithdrawal.delegatedAddress,
+ withdrawer: queuedWithdrawal.withdrawerAndNonce.withdrawer,
+ nonce: queuedWithdrawal.withdrawerAndNonce.nonce,
+ startBlock: queuedWithdrawal.withdrawalStartBlock,
+ strategies: queuedWithdrawal.strategies,
+ shares: queuedWithdrawal.shares
+ });
+ bytes32 withdrawalRootDM = m1DelegationManager.calculateWithdrawalRoot(migratedWithdrawal);
+ uint256 nonceBefore = m1DelegationManager.cumulativeWithdrawalsQueued(queuedWithdrawal.staker);
+
+ // Migrate withdrawal
+ cheats.expectEmit(true, true, true, true);
+ emit WithdrawalQueued(withdrawalRootDM, migratedWithdrawal);
+ cheats.expectEmit(true, true, true, true);
+ emit WithdrawalMigrated(withdrawalRootSM, withdrawalRootDM);
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal[]
+ memory queuedWithdrawals = new IStrategyManager.DeprecatedStruct_QueuedWithdrawal[](1);
+ queuedWithdrawals[0] = queuedWithdrawal;
+ m1DelegationManager.migrateQueuedWithdrawals(queuedWithdrawals);
+
+ // Assertions
+ assertFalse(m1StrategyManager.withdrawalRootPending(withdrawalRootSM));
+ assertTrue(m1DelegationManager.pendingWithdrawals(withdrawalRootDM));
+ uint256 nonceAfter = m1DelegationManager.cumulativeWithdrawalsQueued(queuedWithdrawal.staker);
+ assertTrue(nonceAfter == nonceBefore + 1);
+ }
+
+ function testMigrateMultipleQueuedWithdrawals() public {
+ // Get a beaconChain queued withdrawal
+ (
+ bytes32 withdrawalRootSM1,
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal1
+ ) = _queueWithdrawalBeaconChainETH(1e9);
+
+ // Get a tokenStrategy queued withdrawal
+ deal(address(cbETH), address(this), 1e19);
+ cbETH.approve(address(m1StrategyManager), 1e19);
+ (
+ bytes32 withdrawalRootSM2,
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal2
+ ) = _queueWithdrawalTokenStrategy(address(this), 1e19, 5e18, false);
+
+ // Assert Pending Withdrawals
+ assertTrue(m1StrategyManager.withdrawalRootPending(withdrawalRootSM1));
+ assertTrue(m1StrategyManager.withdrawalRootPending(withdrawalRootSM2));
+
+ // Upgrade M1 Contracts
+ _upgradeM1Contracts();
+
+ // Format withdrawal structs as represented in delegationManager
+ IDelegationManager.Withdrawal memory migratedWithdrawal1 = IDelegationManager.Withdrawal({
+ staker: queuedWithdrawal1.staker,
+ delegatedTo: queuedWithdrawal1.delegatedAddress,
+ withdrawer: queuedWithdrawal1.withdrawerAndNonce.withdrawer,
+ nonce: queuedWithdrawal1.withdrawerAndNonce.nonce,
+ startBlock: queuedWithdrawal1.withdrawalStartBlock,
+ strategies: queuedWithdrawal1.strategies,
+ shares: queuedWithdrawal1.shares
+ });
+
+ IDelegationManager.Withdrawal memory migratedWithdrawal2 = IDelegationManager.Withdrawal({
+ staker: queuedWithdrawal2.staker,
+ delegatedTo: queuedWithdrawal2.delegatedAddress,
+ withdrawer: queuedWithdrawal2.withdrawerAndNonce.withdrawer,
+ nonce: queuedWithdrawal2.withdrawerAndNonce.nonce,
+ startBlock: queuedWithdrawal2.withdrawalStartBlock,
+ strategies: queuedWithdrawal2.strategies,
+ shares: queuedWithdrawal2.shares
+ });
+
+ bytes32 withdrawalRootDM1 = m1DelegationManager.calculateWithdrawalRoot(migratedWithdrawal1);
+ bytes32 withdrawalRootDM2 = m1DelegationManager.calculateWithdrawalRoot(migratedWithdrawal2);
+
+ uint256 nonceBefore = m1DelegationManager.cumulativeWithdrawalsQueued(queuedWithdrawal1.staker);
+
+ // Migrate withdrawals
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal[]
+ memory queuedWithdrawals = new IStrategyManager.DeprecatedStruct_QueuedWithdrawal[](2);
+ queuedWithdrawals[0] = queuedWithdrawal1;
+ queuedWithdrawals[1] = queuedWithdrawal2;
+ m1DelegationManager.migrateQueuedWithdrawals(queuedWithdrawals);
+
+ // Assertions
+ assertFalse(m1StrategyManager.withdrawalRootPending(withdrawalRootSM1));
+ assertFalse(m1StrategyManager.withdrawalRootPending(withdrawalRootSM2));
+ assertTrue(m1DelegationManager.pendingWithdrawals(withdrawalRootDM1));
+ assertTrue(m1DelegationManager.pendingWithdrawals(withdrawalRootDM2));
+ uint256 nonceAfter = m1DelegationManager.cumulativeWithdrawalsQueued(queuedWithdrawal1.staker);
+ assertTrue(nonceAfter == nonceBefore + 2);
+ }
+
+ function _queueWithdrawalTokenStrategy(
+ address staker,
+ uint256 depositAmount,
+ uint256 withdrawalAmount,
+ bool undelegateIfPossible
+ ) internal returns (bytes32, IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory) {
+ // Deposit
+ _depositIntoTokenStrategy(staker, depositAmount);
+
+ // Setup payload
+ (
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal,
+ /*IERC20[] memory tokensArray*/,
+ bytes32 withdrawalRootSM
+ ) = _setUpQueuedWithdrawalStructSingleStrat(
+ /*staker*/ address(this),
+ /*withdrawer*/ address(this),
+ cbETH,
+ cbETHStrategy,
+ withdrawalAmount
+ );
+
+ uint256[] memory strategyIndexes = new uint256[](1);
+ strategyIndexes[0] = 0;
+ m1StrategyManager.queueWithdrawal(
+ strategyIndexes,
+ queuedWithdrawal.strategies,
+ queuedWithdrawal.shares,
+ /*withdrawer*/ address(this),
+ undelegateIfPossible
+ );
+
+ return (withdrawalRootSM, queuedWithdrawal);
+ }
+
+ function _depositIntoTokenStrategy(address staker, uint256 amount) internal {
+ // filter out zero case since it will revert with "m1StrategyManager._addShares: shares should not be zero!"
+ cheats.assume(amount != 0);
+ // sanity check / filter
+ cheats.assume(amount <= cbETH.balanceOf(address(this)));
+
+ uint256 sharesBefore = m1StrategyManager.stakerStrategyShares(staker, cbETHStrategy);
+ uint256 stakerStrategyListLengthBefore = m1StrategyManager.stakerStrategyListLength(staker);
+
+ cheats.startPrank(staker);
+ uint256 shares = m1StrategyManager.depositIntoStrategy(cbETHStrategy, cbETH, amount);
+ cheats.stopPrank();
+
+ uint256 sharesAfter = m1StrategyManager.stakerStrategyShares(staker, cbETHStrategy);
+ uint256 stakerStrategyListLengthAfter = m1StrategyManager.stakerStrategyListLength(staker);
+
+ require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares");
+ if (sharesBefore == 0) {
+ require(
+ stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1"
+ );
+ require(
+ m1StrategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) == cbETHStrategy,
+ "m1StrategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy"
+ );
+ }
+ }
+
+ function _queueWithdrawalBeaconChainETH(
+ uint128 amountGwei
+ ) internal returns (bytes32, IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory) {
+ // scale fuzzed amount up to be a whole amount of GWEI
+ uint256 amount = uint256(amountGwei) * 1e9;
+ address staker = address(this);
+ address withdrawer = staker;
+ IStrategy strategy = beaconChainETHStrategy;
+ IERC20 dummyToken;
+
+ _depositBeaconChainETH(staker, amount);
+
+ bool undelegateIfPossible = false;
+ (
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal /*IERC20[] memory tokensArray*/,
+ ,
+ bytes32 withdrawalRootSM
+ ) = _setUpQueuedWithdrawalStructSingleStrat(staker, withdrawer, dummyToken, strategy, amount);
+
+ uint256[] memory strategyIndexes = new uint256[](1);
+ strategyIndexes[0] = 0;
+ m1StrategyManager.queueWithdrawal(
+ strategyIndexes,
+ queuedWithdrawal.strategies,
+ queuedWithdrawal.shares,
+ withdrawer,
+ undelegateIfPossible
+ );
+
+ return (withdrawalRootSM, queuedWithdrawal);
+ }
+
+ function _depositBeaconChainETH(address staker, uint256 amount) internal {
+ // filter out zero case since it will revert with "m1StrategyManager._addShares: shares should not be zero!"
+ cheats.assume(amount != 0);
+ uint256 sharesBefore = m1StrategyManager.stakerStrategyShares(staker, beaconChainETHStrategy);
+
+ cheats.startPrank(address(m1StrategyManager.eigenPodManager()));
+ m1StrategyManager.depositBeaconChainETH(staker, amount);
+ cheats.stopPrank();
+
+ uint256 sharesAfter = m1StrategyManager.stakerStrategyShares(staker, beaconChainETHStrategy);
+ require(sharesAfter == sharesBefore + amount, "sharesAfter != sharesBefore + amount");
+ }
+
+ function _setUpQueuedWithdrawalStructSingleStrat(
+ address staker,
+ address withdrawer,
+ IERC20 token,
+ IStrategy strategy,
+ uint256 shareAmount
+ )
+ internal
+ view
+ returns (
+ IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal,
+ IERC20[] memory tokensArray,
+ bytes32 withdrawalRoot
+ )
+ {
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ tokensArray = new IERC20[](1);
+ uint256[] memory shareAmounts = new uint256[](1);
+ strategyArray[0] = strategy;
+ tokensArray[0] = token;
+ shareAmounts[0] = shareAmount;
+ IStrategyManager.DeprecatedStruct_WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager
+ .DeprecatedStruct_WithdrawerAndNonce({
+ withdrawer: withdrawer,
+ nonce: uint96(m1StrategyManager.numWithdrawalsQueued(staker))
+ });
+ queuedWithdrawal = IStrategyManager.DeprecatedStruct_QueuedWithdrawal({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ staker: staker,
+ withdrawerAndNonce: withdrawerAndNonce,
+ withdrawalStartBlock: uint32(block.number),
+ delegatedAddress: m1StrategyManager.delegation().delegatedTo(staker)
+ });
+ // calculate the withdrawal root
+ withdrawalRoot = m1StrategyManager.calculateWithdrawalRoot(queuedWithdrawal);
+ return (queuedWithdrawal, tokensArray, withdrawalRoot);
+ }
+
+ function _upgradeM1Contracts() internal {
+ // Deploy Implementation Contracts
+ StrategyManager strategyManagerImplementation = new StrategyManager(
+ IDelegationManager(_M1_DELEGATION_MANAGER),
+ IEigenPodManager(_M1_EIGENPOD_MANAGER),
+ ISlasher(_M1_SLASHER)
+ );
+ DelegationManager delegationManagerImplementation = new DelegationManager(
+ IStrategyManager(_M1_STRATEGY_MANAGER),
+ ISlasher(_M1_SLASHER),
+ IEigenPodManager(_M1_EIGENPOD_MANAGER)
+ );
+ // Upgrade
+ cheats.startPrank(m1EigenLayerProxyAdmin.owner());
+ m1EigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(m1StrategyManager))),
+ address(strategyManagerImplementation)
+ );
+ m1EigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(m1DelegationManager))),
+ address(delegationManagerImplementation)
+ );
+ cheats.stopPrank();
+ }
+
+ // Events
+ event WithdrawalMigrated(bytes32 oldWithdrawalRoot, bytes32 newWithdrawalRoot);
+ event WithdrawalQueued(bytes32 withdrawalRoot, IDelegationManager.Withdrawal withdrawal);
+}
+
+interface IM1StrategyManager is IStrategyManager, IPausable {
+ function depositBeaconChainETH(address staker, uint256 amount) external;
+
+ function withdrawalRootPending(bytes32 withdrawalRoot) external view returns (bool);
+
+ function numWithdrawalsQueued(address staker) external view returns (uint256);
+
+ function beaconChainETHStrategy() external view returns (IStrategy);
+
+ function queueWithdrawal(
+ uint256[] calldata strategyIndexes,
+ IStrategy[] calldata strategies,
+ uint256[] calldata shares,
+ address withdrawer,
+ bool undelegateIfPossible
+ ) external returns (bytes32);
+
+ function strategyWhitelister() external view returns (address);
+
+ function stakerStrategyList(address staker, uint256 index) external view returns (IStrategy);
+}
+
+interface IMaxDepositAmount {
+ function maxPerDeposit() external view returns (uint256);
+
+ function maxTotalDeposits() external view returns (uint256);
+}
diff --git a/src/test/Withdrawals.t.sol b/src/test/Withdrawals.t.sol
index 257671ce2c..f8f780e7a7 100644
--- a/src/test/Withdrawals.t.sol
+++ b/src/test/Withdrawals.t.sol
@@ -1,94 +1,57 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "@openzeppelin/contracts/utils/math/Math.sol";
-
-import "./mocks/MiddlewareRegistryMock.sol";
-import "./mocks/ServiceManagerMock.sol";
-import "./Delegation.t.sol";
-
-contract WithdrawalTests is DelegationTests {
+import "../test/EigenLayerTestHelper.t.sol";
+contract WithdrawalTests is EigenLayerTestHelper {
// packed info used to help handle stack-too-deep errors
struct DataForTestWithdrawal {
IStrategy[] delegatorStrategies;
uint256[] delegatorShares;
- IStrategyManager.WithdrawerAndNonce withdrawerAndNonce;
+ address withdrawer;
+ uint96 nonce;
}
- MiddlewareRegistryMock public generalReg1;
- ServiceManagerMock public generalServiceManager1;
-
- MiddlewareRegistryMock public generalReg2;
- ServiceManagerMock public generalServiceManager2;
-
- function initializeGeneralMiddlewares() public {
- generalServiceManager1 = new ServiceManagerMock(slasher);
-
- generalReg1 = new MiddlewareRegistryMock(
- generalServiceManager1,
- strategyManager
- );
-
- generalServiceManager2 = new ServiceManagerMock(slasher);
+ bytes32 defaultOperatorId = bytes32(uint256(0));
- generalReg2 = new MiddlewareRegistryMock(
- generalServiceManager2,
- strategyManager
- );
+ function setUp() public virtual override {
+ EigenLayerDeployer.setUp();
}
//This function helps with stack too deep issues with "testWithdrawal" test
function testWithdrawalWrapper(
- address operator,
- address depositor,
- address withdrawer,
- uint96 ethAmount,
- uint96 eigenAmount,
- bool withdrawAsTokens,
- bool RANDAO
- )
- public
- fuzzedAddress(operator)
- fuzzedAddress(depositor)
- fuzzedAddress(withdrawer)
- {
- cheats.assume(depositor != operator);
- cheats.assume(ethAmount >= 1 && ethAmount <= 1e18);
- cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18);
-
- initializeGeneralMiddlewares();
+ address operator,
+ address depositor,
+ uint96 ethAmount,
+ uint96 eigenAmount,
+ bool withdrawAsTokens,
+ bool RANDAO
+ ) public fuzzedAddress(operator) fuzzedAddress(depositor) {
+ cheats.assume(depositor != operator);
+ cheats.assume(ethAmount >= 1 && ethAmount <= 1e18);
+ cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18);
- if(RANDAO) {
- _testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens);
- }
- else{
- _testWithdrawalWithStakeUpdate(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens);
- }
+ address withdrawer = depositor;
+ if (RANDAO) {
+ _testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens);
+ } else {
+ _testWithdrawalWithStakeUpdate(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens);
}
+ }
/// @notice test staker's ability to undelegate/withdraw from an operator.
/// @param operator is the operator being delegated to.
/// @param depositor is the staker delegating stake to the operator.
function _testWithdrawalAndDeregistration(
- address operator,
- address depositor,
- address withdrawer,
- uint96 ethAmount,
- uint96 eigenAmount,
- bool withdrawAsTokens
- )
- internal
- {
-
- testDelegation(operator, depositor, ethAmount, eigenAmount);
-
- cheats.startPrank(operator);
- slasher.optIntoSlashing(address(generalServiceManager1));
- cheats.stopPrank();
-
- generalReg1.registerOperator(operator, uint32(block.timestamp) + 3 days);
+ address operator,
+ address depositor,
+ address withdrawer,
+ uint96 ethAmount,
+ uint96 eigenAmount,
+ bool withdrawAsTokens
+ ) internal {
+ _initiateDelegation(operator, depositor, ethAmount, eigenAmount);
address delegatedTo = delegation.delegatedTo(depositor);
@@ -98,19 +61,14 @@ contract WithdrawalTests is DelegationTests {
// scoped block to deal with stack-too-deep issues
{
//delegator-specific information
- (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) =
- strategyManager.getDeposits(depositor);
+ (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = strategyManager.getDeposits(
+ depositor
+ );
dataForTestWithdrawal.delegatorStrategies = delegatorStrategies;
dataForTestWithdrawal.delegatorShares = delegatorShares;
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce =
- IStrategyManager.WithdrawerAndNonce({
- withdrawer: withdrawer,
- // harcoded nonce value
- nonce: 0
- }
- );
- dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce;
+ dataForTestWithdrawal.withdrawer = withdrawer;
+ // harcoded nonce value
+ dataForTestWithdrawal.nonce = 0;
}
uint256[] memory strategyIndexes = new uint256[](2);
@@ -131,22 +89,20 @@ contract WithdrawalTests is DelegationTests {
strategyIndexes,
dataForTestWithdrawal.delegatorStrategies,
dataForTestWithdrawal.delegatorShares,
- withdrawer,
- true
+ withdrawer
);
uint32 queuedWithdrawalBlock = uint32(block.number);
-
+
//now withdrawal block time is before deregistration
cheats.warp(uint32(block.timestamp) + 2 days);
cheats.roll(uint32(block.timestamp) + 2 days);
-
- generalReg1.deregisterOperator(operator);
+
{
//warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point
cheats.warp(uint32(block.timestamp) + 4 days);
cheats.roll(uint32(block.timestamp) + 4 days);
- uint256 middlewareTimeIndex = 1;
+ uint256 middlewareTimeIndex = 1;
if (withdrawAsTokens) {
_testCompleteQueuedWithdrawalTokens(
depositor,
@@ -154,7 +110,8 @@ contract WithdrawalTests is DelegationTests {
tokensArray,
dataForTestWithdrawal.delegatorShares,
delegatedTo,
- dataForTestWithdrawal.withdrawerAndNonce,
+ dataForTestWithdrawal.withdrawer,
+ dataForTestWithdrawal.nonce,
queuedWithdrawalBlock,
middlewareTimeIndex
);
@@ -165,7 +122,8 @@ contract WithdrawalTests is DelegationTests {
tokensArray,
dataForTestWithdrawal.delegatorShares,
delegatedTo,
- dataForTestWithdrawal.withdrawerAndNonce,
+ dataForTestWithdrawal.withdrawer,
+ dataForTestWithdrawal.nonce,
queuedWithdrawalBlock,
middlewareTimeIndex
);
@@ -177,32 +135,23 @@ contract WithdrawalTests is DelegationTests {
/// @param operator is the operator being delegated to.
/// @param depositor is the staker delegating stake to the operator.
function _testWithdrawalWithStakeUpdate(
- address operator,
- address depositor,
- address withdrawer,
- uint96 ethAmount,
- uint96 eigenAmount,
- bool withdrawAsTokens
- )
- public
- {
- testDelegation(operator, depositor, ethAmount, eigenAmount);
-
- cheats.startPrank(operator);
- slasher.optIntoSlashing(address(generalServiceManager1));
- slasher.optIntoSlashing(address(generalServiceManager2));
- cheats.stopPrank();
+ address operator,
+ address depositor,
+ address withdrawer,
+ uint96 ethAmount,
+ uint96 eigenAmount,
+ bool withdrawAsTokens
+ ) public {
+ _initiateDelegation(operator, depositor, ethAmount, eigenAmount);
// emit log_named_uint("Linked list element 1", uint256(uint160(address(generalServiceManager1))));
// emit log_named_uint("Linked list element 2", uint256(uint160(address(generalServiceManager2))));
// emit log("________________________________________________________________");
- generalReg1.registerOperator(operator, uint32(block.timestamp) + 5 days);
// emit log_named_uint("Middleware 1 Update Block", uint32(block.number));
cheats.warp(uint32(block.timestamp) + 1 days);
cheats.roll(uint32(block.number) + 1);
- generalReg2.registerOperator(operator, uint32(block.timestamp) + 5 days);
// emit log_named_uint("Middleware 2 Update Block", uint32(block.number));
address delegatedTo = delegation.delegatedTo(depositor);
@@ -213,19 +162,14 @@ contract WithdrawalTests is DelegationTests {
// scoped block to deal with stack-too-deep issues
{
//delegator-specific information
- (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) =
- strategyManager.getDeposits(depositor);
+ (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = strategyManager.getDeposits(
+ depositor
+ );
dataForTestWithdrawal.delegatorStrategies = delegatorStrategies;
dataForTestWithdrawal.delegatorShares = delegatorShares;
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce =
- IStrategyManager.WithdrawerAndNonce({
- withdrawer: withdrawer,
- // harcoded nonce value
- nonce: 0
- }
- );
- dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce;
+ dataForTestWithdrawal.withdrawer = withdrawer;
+ // harcoded nonce value
+ dataForTestWithdrawal.nonce = 0;
}
uint256[] memory strategyIndexes = new uint256[](2);
@@ -246,31 +190,27 @@ contract WithdrawalTests is DelegationTests {
strategyIndexes,
dataForTestWithdrawal.delegatorStrategies,
dataForTestWithdrawal.delegatorShares,
- dataForTestWithdrawal.withdrawerAndNonce.withdrawer,
- true
+ dataForTestWithdrawal.withdrawer
);
uint32 queuedWithdrawalBlock = uint32(block.number);
-
+
//now withdrawal block time is before deregistration
cheats.warp(uint32(block.timestamp) + 2 days);
cheats.roll(uint32(block.number) + 2);
-
- uint256 prevElement = uint256(uint160(address(generalServiceManager2)));
- generalReg1.propagateStakeUpdate(operator, uint32(block.number), prevElement);
+ // uint256 prevElement = uint256(uint160(address(generalServiceManager2)));
cheats.warp(uint32(block.timestamp) + 1 days);
cheats.roll(uint32(block.number) + 1);
- prevElement = uint256(uint160(address(generalServiceManager1)));
- generalReg2.propagateStakeUpdate(operator, uint32(block.number), prevElement);
-
+ // prevElement = uint256(uint160(address(generalServiceManager1)));
+
{
//warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point
cheats.warp(uint32(block.timestamp) + 4 days);
cheats.roll(uint32(block.number) + 4);
- uint256 middlewareTimeIndex = 3;
+ uint256 middlewareTimeIndex = 3;
if (withdrawAsTokens) {
_testCompleteQueuedWithdrawalTokens(
depositor,
@@ -278,7 +218,8 @@ contract WithdrawalTests is DelegationTests {
tokensArray,
dataForTestWithdrawal.delegatorShares,
delegatedTo,
- dataForTestWithdrawal.withdrawerAndNonce,
+ dataForTestWithdrawal.withdrawer,
+ dataForTestWithdrawal.nonce,
queuedWithdrawalBlock,
middlewareTimeIndex
);
@@ -289,7 +230,8 @@ contract WithdrawalTests is DelegationTests {
tokensArray,
dataForTestWithdrawal.delegatorShares,
delegatedTo,
- dataForTestWithdrawal.withdrawerAndNonce,
+ dataForTestWithdrawal.withdrawer,
+ dataForTestWithdrawal.nonce,
queuedWithdrawalBlock,
middlewareTimeIndex
);
@@ -301,66 +243,43 @@ contract WithdrawalTests is DelegationTests {
// @param operator is the operator being delegated to.
// @param staker is the staker delegating stake to the operator.
function testRedelegateAfterWithdrawal(
- address operator,
- address depositor,
- address withdrawer,
- uint96 ethAmount,
- uint96 eigenAmount,
- bool withdrawAsShares
- )
- public
- fuzzedAddress(operator)
- fuzzedAddress(depositor)
- fuzzedAddress(withdrawer)
- {
+ address operator,
+ address depositor,
+ uint96 ethAmount,
+ uint96 eigenAmount,
+ bool withdrawAsShares
+ ) 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);
//warps past fraudproof time interval
cheats.warp(block.timestamp + 7 days + 1);
- testDelegation(operator, depositor, ethAmount, eigenAmount);
+ _initiateDelegation(operator, depositor, ethAmount, eigenAmount);
}
- /// @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)
- {
+ // Helper function to begin a delegation
+ function _initiateDelegation(
+ address operator,
+ address staker,
+ uint96 ethAmount,
+ uint96 eigenAmount
+ ) internal fuzzedAddress(operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) {
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);
+ // base strategy will revert if these amounts are too small on first deposit
+ cheats.assume(ethAmount >= 1);
+ cheats.assume(eigenAmount >= 2);
- uint256[] memory strategyIndexes = new uint256[](2);
- strategyIndexes[0] = 0;
- strategyIndexes[1] = 1;
-
- IERC20[] memory tokensArray = new IERC20[](2);
- tokensArray[0] = weth;
- tokensArray[0] = eigenToken;
+ _testDelegation(operator, staker, ethAmount, eigenAmount);
+ }
- //initiating queued withdrawal
- cheats.expectRevert(
- bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing")
- );
- _testQueueWithdrawal(staker, strategyIndexes, updatedStrategies, updatedShares, staker, true);
+ modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) {
+ cheats.assume(ethAmount >= 0 && ethAmount <= 1e18);
+ cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18);
+ _;
}
}
+
diff --git a/src/test/events/IAVSDirectoryEvents.sol b/src/test/events/IAVSDirectoryEvents.sol
new file mode 100644
index 0000000000..a4f0a29626
--- /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);
+}
diff --git a/src/test/events/IDelegationManagerEvents.sol b/src/test/events/IDelegationManagerEvents.sol
new file mode 100644
index 0000000000..53eae90b6a
--- /dev/null
+++ b/src/test/events/IDelegationManagerEvents.sol
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/contracts/interfaces/IDelegationManager.sol";
+
+interface IDelegationManagerEvents {
+ // @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
+ event OperatorRegistered(address indexed operator, IDelegationManager.OperatorDetails operatorDetails);
+
+ // @notice Emitted when an operator updates their OperatorDetails to @param newOperatorDetails
+ event OperatorDetailsModified(address indexed operator, IDelegationManager.OperatorDetails newOperatorDetails);
+
+ /**
+ * @notice Emitted when @param operator 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 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 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 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
+ event OperatorSharesIncreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
+
+ /// @notice Emitted whenever an operator's shares are decreased for a given strategy
+ event OperatorSharesDecreased(address indexed operator, address staker, IStrategy strategy, uint256 shares);
+
+ // @notice Emitted when @param staker delegates to @param operator.
+ event StakerDelegated(address indexed staker, address indexed operator);
+
+ // @notice Emitted when @param staker undelegates from @param operator.
+ event StakerUndelegated(address indexed staker, address indexed operator);
+
+ /// @notice Emitted when @param staker is undelegated via a call not originating from the staker themself
+ event StakerForceUndelegated(address indexed staker, address indexed operator);
+
+ /**
+ * @notice Emitted when a new withdrawal is queued.
+ * @param withdrawalRoot Is the hash of the `withdrawal`.
+ * @param withdrawal Is the withdrawal itself.
+ */
+ event WithdrawalQueued(bytes32 withdrawalRoot, IDelegationManager.Withdrawal withdrawal);
+
+ /// @notice Emitted when a queued withdrawal is completed
+ event WithdrawalCompleted(bytes32 withdrawalRoot);
+
+ /// @notice Emitted when a queued withdrawal is *migrated* from the StrategyManager to the DelegationManager
+ event WithdrawalMigrated(bytes32 oldWithdrawalRoot, bytes32 newWithdrawalRoot);
+
+ /// @notice Emitted when the `strategyWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
+ event StrategyWithdrawalDelayBlocksSet(IStrategy strategy, uint256 previousValue, uint256 newValue);
+}
diff --git a/src/test/events/IEigenPodEvents.sol b/src/test/events/IEigenPodEvents.sol
new file mode 100644
index 0000000000..20d37557f6
--- /dev/null
+++ b/src/test/events/IEigenPodEvents.sol
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+interface IEigenPodEvents {
+ /// @notice Emitted when an ETH validator stakes via this eigenPod
+ event EigenPodStaked(bytes pubkey);
+
+ /// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
+ event ValidatorRestaked(uint40 validatorIndex);
+
+ /// @notice Emitted when an ETH validator's balance is proven to be updated. Here newValidatorBalanceGwei
+ // is the validator's balance that is credited on EigenLayer.
+ event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newValidatorBalanceGwei);
+
+ /// @notice Emitted when an ETH validator is prove to have withdrawn from the beacon chain
+ event FullWithdrawalRedeemed(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address indexed recipient,
+ uint64 withdrawalAmountGwei
+ );
+
+ /// @notice Emitted when a partial withdrawal claim is successfully redeemed
+ event PartialWithdrawalRedeemed(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address indexed recipient,
+ uint64 partialWithdrawalAmountGwei
+ );
+
+ /// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
+ event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
+
+ /// @notice Emitted when podOwner enables restaking
+ event RestakingActivated(address indexed podOwner);
+
+ /// @notice Emitted when ETH is received via the `receive` fallback
+ event NonBeaconChainETHReceived(uint256 amountReceived);
+
+ /// @notice Emitted when ETH that was previously received via the `receive` fallback is withdrawn
+ event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn);
+}
diff --git a/src/test/events/IEigenPodManagerEvents.sol b/src/test/events/IEigenPodManagerEvents.sol
new file mode 100644
index 0000000000..50caf34bf6
--- /dev/null
+++ b/src/test/events/IEigenPodManagerEvents.sol
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+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 the balance of an EigenPod is updated
+ event PodSharesUpdated(address indexed podOwner, int256 sharesDelta);
+}
diff --git a/src/test/events/IStrategyManagerEvents.sol b/src/test/events/IStrategyManagerEvents.sol
new file mode 100644
index 0000000000..e0727d069b
--- /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);
+}
diff --git a/src/test/harnesses/BytesArrayBitmapsWrapper.sol b/src/test/harnesses/BytesArrayBitmapsWrapper.sol
deleted file mode 100644
index 58a246df72..0000000000
--- a/src/test/harnesses/BytesArrayBitmapsWrapper.sol
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../contracts/libraries/BytesArrayBitmaps.sol";
-
-import "forge-std/Test.sol";
-
-// wrapper around the BytesArrayBitmaps library that exposes the internal functions
-contract BytesArrayBitmapsWrapper is Test {
- function bytesArrayToBitmap(bytes calldata bytesArray) external pure returns (uint256) {
- return BytesArrayBitmaps.bytesArrayToBitmap(bytesArray);
- }
-
- function orderedBytesArrayToBitmap(bytes calldata orderedBytesArray) external pure returns (uint256) {
- return BytesArrayBitmaps.orderedBytesArrayToBitmap(orderedBytesArray);
- }
-
- function isArrayStrictlyAscendingOrdered(bytes calldata bytesArray) external pure returns (bool) {
- return BytesArrayBitmaps.isArrayStrictlyAscendingOrdered(bytesArray);
- }
-
- function bitmapToBytesArray(uint256 bitmap) external pure returns (bytes memory bytesArray) {
- return BytesArrayBitmaps.bitmapToBytesArray(bitmap);
- }
-
- function orderedBytesArrayToBitmap_Yul(bytes calldata orderedBytesArray) external pure returns (uint256) {
- return BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(orderedBytesArray);
- }
-
- function bytesArrayToBitmap_Yul(bytes calldata bytesArray) external pure returns (uint256) {
- return BytesArrayBitmaps.bytesArrayToBitmap_Yul(bytesArray);
- }
-}
\ No newline at end of file
diff --git a/src/test/harnesses/EigenPodHarness.sol b/src/test/harnesses/EigenPodHarness.sol
new file mode 100644
index 0000000000..50582dfe77
--- /dev/null
+++ b/src/test/harnesses/EigenPodHarness.sol
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../../contracts/pods/EigenPod.sol";
+import "forge-std/Test.sol";
+
+contract EPInternalFunctions is EigenPod, Test {
+
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IDelayedWithdrawalRouter _delayedWithdrawalRouter,
+ IEigenPodManager _eigenPodManager,
+ uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ uint64 _GENESIS_TIME
+ ) EigenPod(
+ _ethPOS,
+ _delayedWithdrawalRouter,
+ _eigenPodManager,
+ _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ _GENESIS_TIME
+ ) {}
+
+ function getActiveValidatorCount() public view returns (uint256) {
+ return activeValidatorCount;
+ }
+
+ function setActiveValidatorCount(uint _count) public {
+ activeValidatorCount = _count;
+ }
+
+ function verifyWithdrawalCredentials(
+ uint64 oracleTimestamp,
+ bytes32 beaconStateRoot,
+ uint40 validatorIndex,
+ bytes calldata validatorFieldsProof,
+ bytes32[] calldata validatorFields
+ ) public returns (uint256) {
+ return _verifyWithdrawalCredentials(
+ oracleTimestamp,
+ beaconStateRoot,
+ validatorIndex,
+ validatorFieldsProof,
+ validatorFields
+ );
+ }
+
+ function verifyAndProcessWithdrawal(
+ bytes32 beaconStateRoot,
+ BeaconChainProofs.WithdrawalProof calldata withdrawalProof,
+ bytes calldata validatorFieldsProof,
+ bytes32[] calldata validatorFields,
+ bytes32[] calldata withdrawalFields
+ ) public returns (IEigenPod.VerifiedWithdrawal memory) {
+ return _verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalProof,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+ }
+
+ function processFullWithdrawal(
+ uint40 validatorIndex,
+ bytes32 validatorPubkeyHash,
+ uint64 withdrawalHappenedTimestamp,
+ address recipient,
+ uint64 withdrawalAmountGwei,
+ ValidatorInfo memory validatorInfo
+ ) public returns(IEigenPod.VerifiedWithdrawal memory) {
+ return _processFullWithdrawal(
+ validatorIndex,
+ validatorPubkeyHash,
+ withdrawalHappenedTimestamp,
+ recipient,
+ withdrawalAmountGwei,
+ validatorInfo
+ );
+ }
+
+ function processPartialWithdrawal(
+ uint40 validatorIndex,
+ uint64 withdrawalHappenedTimestamp,
+ address recipient,
+ uint64 withdrawalAmountGwei
+ ) public returns(IEigenPod.VerifiedWithdrawal memory) {
+ return _processPartialWithdrawal(
+ validatorIndex,
+ withdrawalHappenedTimestamp,
+ recipient,
+ withdrawalAmountGwei
+ );
+ }
+
+ function verifyBalanceUpdate(
+ uint64 oracleTimestamp,
+ uint40 validatorIndex,
+ bytes32 beaconStateRoot,
+ bytes calldata validatorFieldsProofs,
+ bytes32[] calldata validatorFields,
+ uint64 mostRecentBalanceUpdateTimestamp
+ )
+ public returns (int256)
+ {
+ bytes32 pkhash = validatorFields[0];
+ _validatorPubkeyHashToInfo[pkhash].mostRecentBalanceUpdateTimestamp = mostRecentBalanceUpdateTimestamp;
+ return _verifyBalanceUpdate(
+ oracleTimestamp,
+ validatorIndex,
+ beaconStateRoot,
+ validatorFieldsProofs,
+ validatorFields
+ );
+ }
+
+ function setValidatorStatus(bytes32 pkhash, VALIDATOR_STATUS status) public {
+ _validatorPubkeyHashToInfo[pkhash].status = status;
+ }
+
+ function setValidatorRestakedBalance(bytes32 pkhash, uint64 restakedBalanceGwei) public {
+ _validatorPubkeyHashToInfo[pkhash].restakedBalanceGwei = restakedBalanceGwei;
+ }
+
+ function setMostRecentWithdrawalTimestamp(uint64 _mostRecentWithdrawalTimestamp) public {
+ mostRecentWithdrawalTimestamp = _mostRecentWithdrawalTimestamp;
+ }
+ }
diff --git a/src/test/harnesses/EigenPodManagerWrapper.sol b/src/test/harnesses/EigenPodManagerWrapper.sol
new file mode 100644
index 0000000000..15eec05fdc
--- /dev/null
+++ b/src/test/harnesses/EigenPodManagerWrapper.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "../../contracts/pods/EigenPodManager.sol";
+
+///@notice This contract exposed the internal `_calculateChangeInDelegatableShares` function for testing
+contract EigenPodManagerWrapper is EigenPodManager {
+
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IBeacon _eigenPodBeacon,
+ IStrategyManager _strategyManager,
+ ISlasher _slasher,
+ IDelegationManager _delegationManager
+ ) EigenPodManager(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {}
+
+ function calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) external pure returns (int256) {
+ return _calculateChangeInDelegatableShares(sharesBefore, sharesAfter);
+ }
+
+ function setPodAddress(address owner, IEigenPod pod) external {
+ ownerToPod[owner] = pod;
+ }
+}
diff --git a/src/test/harnesses/PausableHarness.sol b/src/test/harnesses/PausableHarness.sol
index 53ebc2ef7c..7a0e37fdbb 100644
--- a/src/test/harnesses/PausableHarness.sol
+++ b/src/test/harnesses/PausableHarness.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../../contracts/permissions/Pausable.sol";
@@ -8,4 +8,4 @@ contract PausableHarness is Pausable {
function initializePauser(IPauserRegistry _pauserRegistry, uint256 initPausedStatus) external {
_initializePauser(_pauserRegistry, initPausedStatus);
}
-}
\ No newline at end of file
+}
diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol
new file mode 100644
index 0000000000..96e0e478d6
--- /dev/null
+++ b/src/test/integration/IntegrationBase.t.sol
@@ -0,0 +1,890 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "forge-std/Test.sol";
+
+import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+import "src/test/integration/IntegrationDeployer.t.sol";
+import "src/test/integration/TimeMachine.t.sol";
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/users/User_M1.t.sol";
+
+abstract contract IntegrationBase is IntegrationDeployer {
+
+ using Strings for *;
+
+ uint numStakers = 0;
+ uint numOperators = 0;
+
+ // Lists of stakers/operators created before the m2 upgrade
+ //
+ // When we call _upgradeEigenLayerContracts, we iterate over
+ // these lists and migrate perform the standard migration actions
+ // for each user
+ User[] stakersToMigrate;
+ User[] operatorsToMigrate;
+
+ /**
+ * Gen/Init methods:
+ */
+
+ /**
+ * @dev Create a new user according to configured random variants.
+ * This user is ready to deposit into some strategies and has some underlying token balances
+ */
+ function _newRandomStaker() internal returns (User, IStrategy[] memory, uint[] memory) {
+ string memory stakerName;
+
+ User staker;
+ IStrategy[] memory strategies;
+ uint[] memory tokenBalances;
+
+ if (forkType == MAINNET && !isUpgraded) {
+ stakerName = string.concat("- M1_Staker", numStakers.toString());
+
+ (staker, strategies, tokenBalances) = _randUser(stakerName);
+
+ stakersToMigrate.push(staker);
+ } else {
+ stakerName = string.concat("- Staker", numStakers.toString());
+
+ (staker, strategies, tokenBalances) = _randUser(stakerName);
+ }
+
+ assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "_newRandomStaker: failed to award token balances");
+
+ numStakers++;
+ return (staker, strategies, tokenBalances);
+ }
+
+ /**
+ * @dev Create a new operator according to configured random variants.
+ * This user will immediately deposit their randomized assets into eigenlayer.
+ * @notice If forktype is mainnet and not upgraded, then the operator will only randomize LSTs assets and deposit them
+ * as ETH podowner shares are not available yet.
+ */
+ function _newRandomOperator() internal returns (User, IStrategy[] memory, uint[] memory) {
+ User operator;
+ IStrategy[] memory strategies;
+ uint[] memory tokenBalances;
+
+ if (forkType == MAINNET && !isUpgraded) {
+ string memory operatorName = string.concat("- M1_Operator", numOperators.toString());
+
+ // Create an operator for M1. We omit native ETH because we want to
+ // check staker/operator shares, and we don't award native ETH shares in M1
+ (operator, strategies, tokenBalances) = _randUser_NoETH(operatorName);
+
+ User_M1(payable(address(operator))).depositIntoEigenlayer_M1(strategies, tokenBalances);
+ uint[] memory addedShares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_Snap_Added_StakerShares(operator, strategies, addedShares, "_newRandomOperator: failed to add delegatable shares");
+
+ operatorsToMigrate.push(operator);
+ } else {
+ string memory operatorName = string.concat("- Operator", numOperators.toString());
+
+ (operator, strategies, tokenBalances) = _randUser(operatorName);
+
+ uint[] memory addedShares = _calculateExpectedShares(strategies, tokenBalances);
+
+ operator.registerAsOperator();
+ operator.depositIntoEigenlayer(strategies, tokenBalances);
+
+ assert_Snap_Added_StakerShares(operator, strategies, addedShares, "_newRandomOperator: failed to add delegatable shares");
+ assert_Snap_Added_OperatorShares(operator, strategies, addedShares, "_newRandomOperator: failed to award shares to operator");
+ assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered");
+ }
+
+ numOperators++;
+ return (operator, strategies, tokenBalances);
+ }
+
+ /// @dev If we're on mainnet, upgrade contracts to M2 and migrate stakers/operators
+ function _upgradeEigenLayerContracts() internal {
+ if (forkType == MAINNET) {
+ require(!isUpgraded, "_upgradeEigenLayerContracts: already performed m2 upgrade");
+
+ emit log("_upgradeEigenLayerContracts: upgrading mainnet to m2");
+ _upgradeMainnetContracts();
+
+ emit log("===Migrating Stakers/Operators===");
+
+ // Enable restaking for stakers' pods
+ for (uint i = 0; i < stakersToMigrate.length; i++) {
+ stakersToMigrate[i].activateRestaking();
+ }
+
+ // Register operators with DelegationManager
+ for (uint i = 0; i < operatorsToMigrate.length; i++) {
+ operatorsToMigrate[i].registerAsOperator();
+ }
+
+ emit log("======");
+
+ isUpgraded = true;
+ emit log("_upgradeEigenLayerContracts: m2 upgrade complete");
+ } else if (forkType == HOLESKY) {
+ require(!isUpgraded, "_upgradeEigenLayerContracts: already performed m2 upgrade");
+
+ emit log("_upgradeEigenLayerContracts: upgrading holesky to m2");
+ _upgradeHoleskyContracts();
+
+ isUpgraded = true;
+ emit log("_upgradeEigenLayerContracts: m2 upgrade complete");
+ }
+ }
+
+ /**
+ * Common assertions:
+ */
+
+ function assert_HasNoDelegatableShares(User user, string memory err) internal {
+ (IStrategy[] memory strategies, uint[] memory shares) =
+ delegationManager.getDelegatableShares(address(user));
+
+ assertEq(strategies.length, 0, err);
+ assertEq(strategies.length, shares.length, "assert_HasNoDelegatableShares: return length mismatch");
+ }
+
+ function assert_HasUnderlyingTokenBalances(
+ User user,
+ IStrategy[] memory strategies,
+ uint[] memory expectedBalances,
+ string memory err
+ ) internal {
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ uint expectedBalance = expectedBalances[i];
+ uint tokenBalance;
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ tokenBalance = address(user).balance;
+ } else {
+ tokenBalance = strat.underlyingToken().balanceOf(address(user));
+ }
+
+ assertApproxEqAbs(expectedBalance, tokenBalance, 1, err);
+ // assertEq(expectedBalance, tokenBalance, err);
+ }
+ }
+
+ function assert_HasNoUnderlyingTokenBalance(User user, IStrategy[] memory strategies, string memory err) internal {
+ assert_HasUnderlyingTokenBalances(user, strategies, new uint[](strategies.length), err);
+ }
+
+ function assert_HasExpectedShares(
+ User user,
+ IStrategy[] memory strategies,
+ uint[] memory expectedShares,
+ string memory err
+ ) internal {
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ uint actualShares;
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ // This method should only be used for tests that handle positive
+ // balances. Negative balances are an edge case that require
+ // the own tests and helper methods.
+ int shares = eigenPodManager.podOwnerShares(address(user));
+ if (shares < 0) {
+ revert("assert_HasExpectedShares: negative shares");
+ }
+
+ actualShares = uint(shares);
+ } else {
+ actualShares = strategyManager.stakerStrategyShares(address(user), strat);
+ }
+
+ assertApproxEqAbs(expectedShares[i], actualShares, 1, err);
+ }
+ }
+
+ function assert_HasOperatorShares(
+ User user,
+ IStrategy[] memory strategies,
+ uint[] memory expectedShares,
+ string memory err
+ ) internal {
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ uint actualShares = delegationManager.operatorShares(address(user), strat);
+
+ assertApproxEqAbs(expectedShares[i], actualShares, 1, err);
+ }
+ }
+
+ /// @dev Asserts that ALL of the `withdrawalRoots` is in `delegationManager.pendingWithdrawals`
+ function assert_AllWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal {
+ for (uint i = 0; i < withdrawalRoots.length; i++) {
+ assert_WithdrawalPending(withdrawalRoots[i], err);
+ }
+ }
+
+ /// @dev Asserts that NONE of the `withdrawalRoots` is in `delegationManager.pendingWithdrawals`
+ function assert_NoWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal {
+ for (uint i = 0; i < withdrawalRoots.length; i++) {
+ assert_WithdrawalNotPending(withdrawalRoots[i], err);
+ }
+ }
+
+ /// @dev Asserts that the hash of each withdrawal corresponds to the provided withdrawal root
+ function assert_WithdrawalPending(bytes32 withdrawalRoot, string memory err) internal {
+ assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), err);
+ }
+
+ function assert_WithdrawalNotPending(bytes32 withdrawalRoot, string memory err) internal {
+ assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), err);
+ }
+
+ function assert_ValidWithdrawalHashes(
+ IDelegationManager.Withdrawal[] memory withdrawals,
+ bytes32[] memory withdrawalRoots,
+ string memory err
+ ) internal {
+ for (uint i = 0; i < withdrawals.length; i++) {
+ assert_ValidWithdrawalHash(withdrawals[i], withdrawalRoots[i], err);
+ }
+ }
+
+ function assert_ValidWithdrawalHash(
+ IDelegationManager.Withdrawal memory withdrawal,
+ bytes32 withdrawalRoot,
+ string memory err
+ ) internal {
+ assertEq(withdrawalRoot, delegationManager.calculateWithdrawalRoot(withdrawal), err);
+ }
+
+ /*******************************************************************************
+ SNAPSHOT ASSERTIONS
+ TIME TRAVELERS ONLY BEYOND THIS POINT
+ *******************************************************************************/
+
+ /// Snapshot assertions for delegationManager.operatorShares:
+
+ /// @dev Check that the operator has `addedShares` additional operator shares
+ // for each strategy since the last snapshot
+ function assert_Snap_Added_OperatorShares(
+ User operator,
+ IStrategy[] memory strategies,
+ uint[] memory addedShares,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getOperatorShares(operator, strategies);
+ // Use timewarp to get previous operator shares
+ uint[] memory prevShares = _getPrevOperatorShares(operator, strategies);
+
+ // For each strategy, check (prev + added == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1, err);
+ }
+ }
+
+ /// @dev Check that the operator has `removedShares` fewer operator shares
+ /// for each strategy since the last snapshot
+ function assert_Snap_Removed_OperatorShares(
+ User operator,
+ IStrategy[] memory strategies,
+ uint[] memory removedShares,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getOperatorShares(operator, strategies);
+ // Use timewarp to get previous operator shares
+ uint[] memory prevShares = _getPrevOperatorShares(operator, strategies);
+
+ // For each strategy, check (prev - removed == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertEq(prevShares[i] - removedShares[i], curShares[i], err);
+ }
+ }
+
+ /// @dev Check that the operator's shares in ALL strategies have not changed
+ /// since the last snapshot
+ function assert_Snap_Unchanged_OperatorShares(
+ User operator,
+ string memory err
+ ) internal {
+ IStrategy[] memory strategies = allStrats;
+
+ uint[] memory curShares = _getOperatorShares(operator, strategies);
+ // Use timewarp to get previous operator shares
+ uint[] memory prevShares = _getPrevOperatorShares(operator, strategies);
+
+ // For each strategy, check (prev == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertEq(prevShares[i], curShares[i], err);
+ }
+ }
+
+ function assert_Snap_Delta_OperatorShares(
+ User operator,
+ IStrategy[] memory strategies,
+ int[] memory shareDeltas,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getOperatorShares(operator, strategies);
+ // Use timewarp to get previous operator shares
+ uint[] memory prevShares = _getPrevOperatorShares(operator, strategies);
+
+ // For each strategy, check (prev + added == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ uint expectedShares;
+ if (shareDeltas[i] < 0) {
+ expectedShares = prevShares[i] - uint(-shareDeltas[i]);
+ } else {
+ expectedShares = prevShares[i] + uint(shareDeltas[i]);
+ }
+ assertEq(expectedShares, curShares[i], err);
+ }
+ }
+
+ /// Snapshot assertions for strategyMgr.stakerStrategyShares and eigenPodMgr.podOwnerShares:
+
+ /// @dev Check that the staker has `addedShares` additional delegatable shares
+ /// for each strategy since the last snapshot
+ function assert_Snap_Added_StakerShares(
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory addedShares,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getStakerShares(staker, strategies);
+ // Use timewarp to get previous staker shares
+ uint[] memory prevShares = _getPrevStakerShares(staker, strategies);
+
+ // For each strategy, check (prev + added == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1, err);
+ }
+ }
+
+ /// @dev Check that the staker has `removedShares` fewer delegatable shares
+ /// for each strategy since the last snapshot
+ function assert_Snap_Removed_StakerShares(
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory removedShares,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getStakerShares(staker, strategies);
+ // Use timewarp to get previous staker shares
+ uint[] memory prevShares = _getPrevStakerShares(staker, strategies);
+
+ // For each strategy, check (prev - removed == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertEq(prevShares[i] - removedShares[i], curShares[i], err);
+ }
+ }
+
+ /// @dev Check that the staker's delegatable shares in ALL strategies have not changed
+ /// since the last snapshot
+ function assert_Snap_Unchanged_StakerShares(
+ User staker,
+ string memory err
+ ) internal {
+ IStrategy[] memory strategies = allStrats;
+
+ uint[] memory curShares = _getStakerShares(staker, strategies);
+ // Use timewarp to get previous staker shares
+ uint[] memory prevShares = _getPrevStakerShares(staker, strategies);
+
+ // For each strategy, check (prev == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertEq(prevShares[i], curShares[i], err);
+ }
+ }
+
+ function assert_Snap_Removed_StrategyShares(
+ IStrategy[] memory strategies,
+ uint[] memory removedShares,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getTotalStrategyShares(strategies);
+
+ // Use timewarp to get previous strategy shares
+ uint[] memory prevShares = _getPrevTotalStrategyShares(strategies);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ // Ignore BeaconChainETH strategy since it doesn't keep track of global strategy shares
+ if (strategies[i] == BEACONCHAIN_ETH_STRAT) {
+ continue;
+ }
+ uint prevShare = prevShares[i];
+ uint curShare = curShares[i];
+
+ assertEq(prevShare - removedShares[i], curShare, err);
+ }
+ }
+
+ function assert_Snap_Unchanged_StrategyShares(
+ IStrategy[] memory strategies,
+ string memory err
+ ) internal {
+ uint[] memory curShares = _getTotalStrategyShares(strategies);
+
+ // Use timewarp to get previous strategy shares
+ uint[] memory prevShares = _getPrevTotalStrategyShares(strategies);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ uint prevShare = prevShares[i];
+ uint curShare = curShares[i];
+
+ assertEq(prevShare, curShare, err);
+ }
+ }
+
+ function assert_Snap_Delta_StakerShares(
+ User staker,
+ IStrategy[] memory strategies,
+ int[] memory shareDeltas,
+ string memory err
+ ) internal {
+ int[] memory curShares = _getStakerSharesInt(staker, strategies);
+ // Use timewarp to get previous staker shares
+ int[] memory prevShares = _getPrevStakerSharesInt(staker, strategies);
+
+ // For each strategy, check (prev + added == cur)
+ for (uint i = 0; i < strategies.length; i++) {
+ assertEq(prevShares[i] + shareDeltas[i], curShares[i], err);
+ }
+ }
+
+ /// Snapshot assertions for underlying token balances:
+
+ /// @dev Check that the staker has `addedTokens` additional underlying tokens
+ // since the last snapshot
+ function assert_Snap_Added_TokenBalances(
+ User staker,
+ IERC20[] memory tokens,
+ uint[] memory addedTokens,
+ string memory err
+ ) internal {
+ uint[] memory curTokenBalances = _getTokenBalances(staker, tokens);
+ // Use timewarp to get previous token balances
+ uint[] memory prevTokenBalances = _getPrevTokenBalances(staker, tokens);
+
+ for (uint i = 0; i < tokens.length; i++) {
+ uint prevBalance = prevTokenBalances[i];
+ uint curBalance = curTokenBalances[i];
+
+ assertEq(prevBalance + addedTokens[i], curBalance, err);
+ }
+ }
+
+ /// @dev Check that the staker has `removedTokens` fewer underlying tokens
+ // since the last snapshot
+ function assert_Snap_Removed_TokenBalances(
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory removedTokens,
+ string memory err
+ ) internal {
+ IERC20[] memory tokens = _getUnderlyingTokens(strategies);
+
+ uint[] memory curTokenBalances = _getTokenBalances(staker, tokens);
+ // Use timewarp to get previous token balances
+ uint[] memory prevTokenBalances = _getPrevTokenBalances(staker, tokens);
+
+ for (uint i = 0; i < tokens.length; i++) {
+ uint prevBalance = prevTokenBalances[i];
+ uint curBalance = curTokenBalances[i];
+
+ assertEq(prevBalance - removedTokens[i], curBalance, err);
+ }
+ }
+
+ /// @dev Check that the staker's underlying token balance for ALL tokens have
+ /// not changed since the last snapshot
+ function assert_Snap_Unchanged_TokenBalances(
+ User staker,
+ string memory err
+ ) internal {
+ IERC20[] memory tokens = allTokens;
+
+ uint[] memory curTokenBalances = _getTokenBalances(staker, tokens);
+ // Use timewarp to get previous token balances
+ uint[] memory prevTokenBalances = _getPrevTokenBalances(staker, tokens);
+
+ for (uint i = 0; i < tokens.length; i++) {
+ assertEq(prevTokenBalances[i], curTokenBalances[i], err);
+ }
+ }
+
+ /// Other snapshot assertions:
+
+ function assert_Snap_Added_QueuedWithdrawals(
+ User staker,
+ IDelegationManager.Withdrawal[] memory withdrawals,
+ string memory err
+ ) internal {
+ uint curQueuedWithdrawals = _getCumulativeWithdrawals(staker);
+ // Use timewarp to get previous cumulative withdrawals
+ uint prevQueuedWithdrawals = _getPrevCumulativeWithdrawals(staker);
+
+ assertEq(prevQueuedWithdrawals + withdrawals.length, curQueuedWithdrawals, err);
+ }
+
+ function assert_Snap_Added_QueuedWithdrawal(
+ User staker,
+ IDelegationManager.Withdrawal memory /*withdrawal*/,
+ string memory err
+ ) internal {
+ uint curQueuedWithdrawal = _getCumulativeWithdrawals(staker);
+ // Use timewarp to get previous cumulative withdrawals
+ uint prevQueuedWithdrawal = _getPrevCumulativeWithdrawals(staker);
+
+ assertEq(prevQueuedWithdrawal + 1, curQueuedWithdrawal, err);
+ }
+
+ /*******************************************************************************
+ UTILITY METHODS
+ *******************************************************************************/
+
+ function _randWithdrawal(
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) internal returns (IStrategy[] memory, uint[] memory) {
+ uint stratsToWithdraw = _randUint({ min: 1, max: strategies.length });
+
+ IStrategy[] memory withdrawStrats = new IStrategy[](stratsToWithdraw);
+ uint[] memory withdrawShares = new uint[](stratsToWithdraw);
+
+ for (uint i = 0; i < stratsToWithdraw; i++) {
+ uint sharesToWithdraw;
+
+ if (strategies[i] == BEACONCHAIN_ETH_STRAT) {
+ // For native eth, withdraw a random amount of gwei (at least 1)
+ uint portion = _randUint({ min: 1, max: shares[i] / GWEI_TO_WEI });
+ portion *= GWEI_TO_WEI;
+
+ sharesToWithdraw = shares[i] - portion;
+ } else {
+ // For LSTs, withdraw a random amount of shares (at least 1)
+ uint portion = _randUint({ min: 1, max: shares[i] });
+
+ sharesToWithdraw = shares[i] - portion;
+ }
+
+ withdrawStrats[i] = strategies[i];
+ withdrawShares[i] = sharesToWithdraw;
+ }
+
+ return (withdrawStrats, withdrawShares);
+ }
+
+ /**
+ * Helpful getters:
+ */
+ function _randBalanceUpdate(
+ User staker,
+ IStrategy[] memory strategies
+ ) internal returns (int[] memory, int[] memory, int[] memory) {
+
+ int[] memory tokenDeltas = new int[](strategies.length);
+ int[] memory stakerShareDeltas = new int[](strategies.length);
+ int[] memory operatorShareDeltas = new int[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ // TODO - could choose and set a "next updatable validator" at random here
+ uint40 validator = staker.getUpdatableValidator();
+ uint64 beaconBalanceGwei = beaconChain.balanceOfGwei(validator);
+
+ // For native eth, add or remove a random amount of Gwei - minimum 1
+ // and max of the current beacon chain balance
+ int64 deltaGwei = int64(int(_randUint({ min: 1, max: beaconBalanceGwei })));
+ bool addTokens = _randBool();
+ deltaGwei = addTokens ? deltaGwei : -deltaGwei;
+
+ tokenDeltas[i] = int(deltaGwei) * int(GWEI_TO_WEI);
+
+ // stakerShareDeltas[i] = _calculateSharesDelta(newPodBalanceGwei, oldPodBalanceGwei);
+ stakerShareDeltas[i] = _calcNativeETHStakerShareDelta(staker, validator, beaconBalanceGwei, deltaGwei);
+ operatorShareDeltas[i] = _calcNativeETHOperatorShareDelta(staker, stakerShareDeltas[i]);
+
+ emit log_named_uint("current beacon balance (gwei): ", beaconBalanceGwei);
+ // emit log_named_uint("current validator pod balance (gwei): ", oldPodBalanceGwei);
+ emit log_named_int("beacon balance delta (gwei): ", deltaGwei);
+ emit log_named_int("staker share delta (gwei): ", stakerShareDeltas[i] / int(GWEI_TO_WEI));
+ emit log_named_int("operator share delta (gwei): ", operatorShareDeltas[i] / int(GWEI_TO_WEI));
+ } else {
+ // For LSTs, mint a random token amount
+ uint portion = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE });
+ StdCheats.deal(address(strat.underlyingToken()), address(staker), portion);
+
+ int delta = int(portion);
+ tokenDeltas[i] = delta;
+ stakerShareDeltas[i] = int(strat.underlyingToShares(uint(delta)));
+ operatorShareDeltas[i] = int(strat.underlyingToShares(uint(delta)));
+ }
+ }
+ return (tokenDeltas, stakerShareDeltas, operatorShareDeltas);
+ }
+
+ function _calcNativeETHStakerShareDelta(
+ User staker,
+ uint40 validatorIndex,
+ uint64 beaconBalanceGwei,
+ int64 deltaGwei
+ ) internal view returns (int) {
+ uint64 oldPodBalanceGwei =
+ staker
+ .pod()
+ .validatorPubkeyHashToInfo(beaconChain.pubkeyHash(validatorIndex))
+ .restakedBalanceGwei;
+
+ uint64 newPodBalanceGwei = _calcPodBalance(beaconBalanceGwei, deltaGwei);
+
+ return (int(uint(newPodBalanceGwei)) - int(uint(oldPodBalanceGwei))) * int(GWEI_TO_WEI);
+ }
+
+ function _calcPodBalance(uint64 beaconBalanceGwei, int64 deltaGwei) internal pure returns (uint64) {
+ uint64 podBalanceGwei;
+ if (deltaGwei < 0) {
+ podBalanceGwei = beaconBalanceGwei - uint64(uint(int(-deltaGwei)));
+ } else {
+ podBalanceGwei = beaconBalanceGwei + uint64(uint(int(deltaGwei)));
+ }
+
+ if (podBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
+ podBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ }
+
+ return podBalanceGwei;
+ }
+
+ function _calcNativeETHOperatorShareDelta(User staker, int shareDelta) internal view returns (int) {
+ int curPodOwnerShares = eigenPodManager.podOwnerShares(address(staker));
+ int newPodOwnerShares = curPodOwnerShares + shareDelta;
+
+ if (curPodOwnerShares <= 0) {
+ // if the shares started negative and stayed negative, then there cannot have been an increase in delegateable shares
+ if (newPodOwnerShares <= 0) {
+ return 0;
+ // if the shares started negative and became positive, then the increase in delegateable shares is the ending share amount
+ } else {
+ return newPodOwnerShares;
+ }
+ } else {
+ // if the shares started positive and became negative, then the decrease in delegateable shares is the starting share amount
+ if (newPodOwnerShares <= 0) {
+ return (-curPodOwnerShares);
+ // if the shares started positive and stayed positive, then the change in delegateable shares
+ // is the difference between starting and ending amounts
+ } else {
+ return (newPodOwnerShares - curPodOwnerShares);
+ }
+ }
+ }
+
+ /// @dev For some strategies/underlying token balances, calculate the expected shares received
+ /// from depositing all tokens
+ function _calculateExpectedShares(IStrategy[] memory strategies, uint[] memory tokenBalances) internal returns (uint[] memory) {
+ uint[] memory expectedShares = new uint[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ uint tokenBalance = tokenBalances[i];
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ expectedShares[i] = tokenBalance;
+ } else {
+ expectedShares[i] = strat.underlyingToShares(tokenBalance);
+ }
+ }
+
+ return expectedShares;
+ }
+
+ /// @dev For some strategies/underlying token balances, calculate the expected shares received
+ /// from depositing all tokens
+ function _calculateExpectedTokens(IStrategy[] memory strategies, uint[] memory shares) internal returns (uint[] memory) {
+ uint[] memory expectedTokens = new uint[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ expectedTokens[i] = shares[i];
+ } else {
+ expectedTokens[i] = strat.sharesToUnderlying(shares[i]);
+ }
+ }
+
+ return expectedTokens;
+ }
+
+ function _getWithdrawalHashes(
+ IDelegationManager.Withdrawal[] memory withdrawals
+ ) internal view returns (bytes32[] memory) {
+ bytes32[] memory withdrawalRoots = new bytes32[](withdrawals.length);
+
+ for (uint i = 0; i < withdrawals.length; i++) {
+ withdrawalRoots[i] = delegationManager.calculateWithdrawalRoot(withdrawals[i]);
+ }
+
+ return withdrawalRoots;
+ }
+
+ /// @dev Converts a list of strategies to underlying tokens
+ function _getUnderlyingTokens(IStrategy[] memory strategies) internal view returns (IERC20[] memory) {
+ IERC20[] memory tokens = new IERC20[](strategies.length);
+
+ for (uint i = 0; i < tokens.length; i++) {
+ IStrategy strat = strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ tokens[i] = NATIVE_ETH;
+ } else {
+ tokens[i] = strat.underlyingToken();
+ }
+ }
+
+ return tokens;
+ }
+
+ modifier timewarp() {
+ uint curState = timeMachine.warpToLast();
+ _;
+ 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,
+ IStrategy[] memory strategies
+ ) internal timewarp() returns (uint[] memory) {
+ return _getOperatorShares(operator, strategies);
+ }
+
+ /// @dev Looks up each strategy and returns a list of the operator's shares
+ function _getOperatorShares(User operator, IStrategy[] memory strategies) internal view returns (uint[] memory) {
+ uint[] memory curShares = new uint[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ curShares[i] = delegationManager.operatorShares(address(operator), strategies[i]);
+ }
+
+ return curShares;
+ }
+
+ /// @dev Uses timewarp modifier to get staker shares at the last snapshot
+ function _getPrevStakerShares(
+ User staker,
+ IStrategy[] memory strategies
+ ) internal timewarp() returns (uint[] memory) {
+ return _getStakerShares(staker, strategies);
+ }
+
+ /// @dev Looks up each strategy and returns a list of the staker's shares
+ function _getStakerShares(User staker, IStrategy[] memory strategies) internal view returns (uint[] memory) {
+ uint[] memory curShares = new uint[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ // This method should only be used for tests that handle positive
+ // balances. Negative balances are an edge case that require
+ // the own tests and helper methods.
+ int shares = eigenPodManager.podOwnerShares(address(staker));
+ if (shares < 0) {
+ revert("_getStakerShares: negative shares");
+ }
+
+ curShares[i] = uint(shares);
+ } else {
+ curShares[i] = strategyManager.stakerStrategyShares(address(staker), strat);
+ }
+ }
+
+ return curShares;
+ }
+
+ /// @dev Uses timewarp modifier to get staker shares at the last snapshot
+ function _getPrevStakerSharesInt(
+ User staker,
+ IStrategy[] memory strategies
+ ) internal timewarp() returns (int[] memory) {
+ return _getStakerSharesInt(staker, strategies);
+ }
+
+ /// @dev Looks up each strategy and returns a list of the staker's shares
+ function _getStakerSharesInt(User staker, IStrategy[] memory strategies) internal view returns (int[] memory) {
+ int[] memory curShares = new int[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ curShares[i] = eigenPodManager.podOwnerShares(address(staker));
+ } else {
+ curShares[i] = int(strategyManager.stakerStrategyShares(address(staker), strat));
+ }
+ }
+
+ return curShares;
+ }
+
+ function _getPrevCumulativeWithdrawals(User staker) internal timewarp() returns (uint) {
+ return _getCumulativeWithdrawals(staker);
+ }
+
+ function _getCumulativeWithdrawals(User staker) internal view returns (uint) {
+ return delegationManager.cumulativeWithdrawalsQueued(address(staker));
+ }
+
+ function _getPrevTokenBalances(User staker, IERC20[] memory tokens) internal timewarp() returns (uint[] memory) {
+ return _getTokenBalances(staker, tokens);
+ }
+
+ function _getTokenBalances(User staker, IERC20[] memory tokens) internal view returns (uint[] memory) {
+ uint[] memory balances = new uint[](tokens.length);
+
+ for (uint i = 0; i < tokens.length; i++) {
+ if (tokens[i] == NATIVE_ETH) {
+ balances[i] = address(staker).balance;
+ } else {
+ balances[i] = tokens[i].balanceOf(address(staker));
+ }
+ }
+
+ return balances;
+ }
+
+ function _getPrevTotalStrategyShares(IStrategy[] memory strategies) internal timewarp() returns (uint[] memory) {
+ return _getTotalStrategyShares(strategies);
+ }
+
+ function _getTotalStrategyShares(IStrategy[] memory strategies) internal view returns (uint[] memory) {
+ uint[] memory shares = new uint[](strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ if (strategies[i] != BEACONCHAIN_ETH_STRAT) {
+ shares[i] = strategies[i].totalShares();
+ }
+ // BeaconChainETH strategy doesn't keep track of global strategy shares, so we ignore
+ }
+
+ return shares;
+ }
+}
\ No newline at end of file
diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol
new file mode 100644
index 0000000000..9ca5e3fd69
--- /dev/null
+++ b/src/test/integration/IntegrationChecks.t.sol
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/IntegrationBase.t.sol";
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/users/User_M1.t.sol";
+
+/// @notice Contract that provides utility functions to reuse common test blocks & checks
+contract IntegrationCheckUtils is IntegrationBase {
+
+ function check_Deposit_State(
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) internal {
+ /// Deposit into strategies:
+ // For each of the assets held by the staker (either StrategyManager or EigenPodManager),
+ // the staker calls the relevant deposit function, depositing all held assets.
+ //
+ // ... check that all underlying tokens were transferred to the correct destination
+ // and that the staker now has the expected amount of delegated shares in each strategy
+ assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens");
+ assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expect shares in each strategy after depositing");
+ }
+
+
+ function check_Deposit_State_PartialDeposit(User staker, IStrategy[] memory strategies, uint[] memory shares, uint[] memory tokenBalances) internal {
+ /// Deposit into strategies:
+ // For each of the assets held by the staker (either StrategyManager or EigenPodManager),
+ // the staker calls the relevant deposit function, depositing some subset of held assets
+ //
+ // ... check that some underlying tokens were transferred to the correct destination
+ // and that the staker now has the expected amount of delegated shares in each strategy
+ assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should have transferred some underlying tokens");
+ assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing");
+ }
+
+ function check_Delegation_State(
+ User staker,
+ User operator,
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) internal {
+ /// Delegate to an operator:
+ //
+ // ... check that the staker is now delegated to the operator, and that the operator
+ // was awarded the staker shares
+ assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated");
+ assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator");
+ assert_HasExpectedShares(staker, strategies, shares, "staker should still have expected shares after delegating");
+ assert_Snap_Unchanged_StakerShares(staker, "staker shares should be unchanged after delegating");
+ assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares");
+ }
+
+ function check_QueuedWithdrawal_State(
+ User staker,
+ User operator,
+ IStrategy[] memory strategies,
+ uint[] memory shares,
+ IDelegationManager.Withdrawal[] memory withdrawals,
+ bytes32[] memory withdrawalRoots
+ ) internal {
+ // The staker will queue one or more withdrawals for the selected strategies and shares
+ //
+ // ... check that each withdrawal was successfully enqueued, that the returned roots
+ // match the hashes of each withdrawal, and that the staker and operator have
+ // reduced shares.
+ assertEq(withdrawalRoots.length, 1, "check_QueuedWithdrawal_State: should only have 1 withdrawal root after queueing");
+ assert_AllWithdrawalsPending(withdrawalRoots,
+ "check_QueuedWithdrawal_State: staker withdrawals should now be pending");
+ assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots,
+ "check_QueuedWithdrawal_State: calculated withdrawals should match returned roots");
+ assert_Snap_Added_QueuedWithdrawals(staker, withdrawals,
+ "check_QueuedWithdrawal_State: staker should have increased nonce by withdrawals.length");
+ assert_Snap_Removed_OperatorShares(operator, strategies, shares,
+ "check_QueuedWithdrawal_State: failed to remove operator shares");
+ assert_Snap_Removed_StakerShares(staker, strategies, shares,
+ "check_QueuedWithdrawal_State: failed to remove staker shares");
+ }
+
+ function check_Undelegate_State(
+ User staker,
+ User operator,
+ IDelegationManager.Withdrawal[] memory withdrawals,
+ bytes32[] memory withdrawalRoots,
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) internal {
+ /// Undelegate from an operator
+ //
+ // ... check that the staker is undelegated, all strategies from which the staker is deposited are unqeuued,
+ // that the returned root matches the hashes for each strategy and share amounts, and that the staker
+ // and operator have reduced shares
+ assertFalse(delegationManager.isDelegated(address(staker)),
+ "check_Undelegate_State: staker should not be delegated");
+ assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots,
+ "check_Undelegate_State: calculated withdrawl should match returned root");
+ assert_AllWithdrawalsPending(withdrawalRoots,
+ "check_Undelegate_State: stakers withdrawal should now be pending");
+ assert_Snap_Added_QueuedWithdrawals(staker, withdrawals,
+ "check_Undelegate_State: staker should have increased nonce by withdrawals.length");
+ assert_Snap_Removed_OperatorShares(operator, strategies, shares,
+ "check_Undelegate_State: failed to remove operator shares");
+ assert_Snap_Removed_StakerShares(staker, strategies, shares,
+ "check_Undelegate_State: failed to remove staker shares");
+ }
+
+ /**
+ * @notice Overloaded function to check the state after a withdrawal as tokens, accepting a non-user type for the operator.
+ * @param staker The staker who completed the withdrawal.
+ * @param operator The operator address, which can be a non-user type like address(0).
+ * @param withdrawal The details of the withdrawal that was completed.
+ * @param strategies The strategies from which the withdrawal was made.
+ * @param shares The number of shares involved in the withdrawal.
+ * @param tokens The tokens received after the withdrawal.
+ * @param expectedTokens The expected tokens to be received after the withdrawal.
+ */
+ function check_Withdrawal_AsTokens_State(
+ User staker,
+ User operator,
+ IDelegationManager.Withdrawal memory withdrawal,
+ IStrategy[] memory strategies,
+ uint[] memory shares,
+ IERC20[] memory tokens,
+ uint[] memory expectedTokens
+ ) internal {
+ // Common checks
+ assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
+ assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens");
+ assert_Snap_Unchanged_StakerShares(staker, "staker shares should not have changed");
+ assert_Snap_Removed_StrategyShares(strategies, shares, "strategies should have total shares decremented");
+
+ // Checks specific to an operator that the Staker has delegated to
+ if (operator != User(payable(0))) {
+ if (operator != staker) {
+ assert_Snap_Unchanged_TokenBalances(User(operator), "operator token balances should not have changed");
+ }
+ assert_Snap_Unchanged_OperatorShares(User(operator), "operator shares should not have changed");
+ }
+ }
+
+ function check_Withdrawal_AsShares_State(
+ User staker,
+ User operator,
+ IDelegationManager.Withdrawal memory withdrawal,
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) internal {
+ // Common checks applicable to both user and non-user operator types
+ assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
+ assert_Snap_Unchanged_TokenBalances(staker, "staker should not have any change in underlying token balances");
+ assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should have received expected shares");
+ assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged");
+
+ // Additional checks or handling for the non-user operator scenario
+ if (operator != User(User(payable(0)))) {
+ if (operator != staker) {
+ assert_Snap_Unchanged_TokenBalances(User(operator), "operator should not have any change in underlying token balances");
+ }
+ assert_Snap_Added_OperatorShares(User(operator), withdrawal.strategies, withdrawal.shares, "operator should have received shares");
+ }
+ }
+
+ /// @notice Difference from above is that operator shares do not increase since staker is not delegated
+ function check_Withdrawal_AsShares_Undelegated_State(
+ User staker,
+ User operator,
+ IDelegationManager.Withdrawal memory withdrawal,
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) internal {
+ /// Complete withdrawal(s):
+ // The staker will complete the withdrawal as shares
+ //
+ // ... check that the withdrawal is not pending, that the token balances of the staker and operator are unchanged,
+ // that the withdrawer received the expected shares, and that that the total shares of each o
+ // strategy withdrawn remains unchanged
+ assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
+ assert_Snap_Unchanged_TokenBalances(staker, "staker should not have any change in underlying token balances");
+ assert_Snap_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances");
+ assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should have received expected shares");
+ assert_Snap_Unchanged_OperatorShares(operator, "operator should have shares unchanged");
+ assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged");
+ }
+}
diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol
new file mode 100644
index 0000000000..3849e92f61
--- /dev/null
+++ b/src/test/integration/IntegrationDeployer.t.sol
@@ -0,0 +1,1037 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+// Imports
+import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
+import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
+import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+import "forge-std/Test.sol";
+
+import "src/contracts/core/DelegationManager.sol";
+import "src/contracts/core/StrategyManager.sol";
+import "src/contracts/core/Slasher.sol";
+import "src/contracts/strategies/StrategyBase.sol";
+import "src/contracts/strategies/StrategyBaseTVLLimits.sol";
+import "src/contracts/pods/EigenPodManager.sol";
+import "src/contracts/pods/EigenPod.sol";
+import "src/contracts/pods/DelayedWithdrawalRouter.sol";
+import "src/contracts/permissions/PauserRegistry.sol";
+
+import "src/test/mocks/EmptyContract.sol";
+import "src/test/mocks/ETHDepositMock.sol";
+import "src/test/integration/mocks/BeaconChainOracleMock.t.sol";
+import "src/test/integration/mocks/BeaconChainMock.t.sol";
+
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/users/User_M1.t.sol";
+
+import "script/utils/ExistingDeploymentParser.sol";
+
+abstract contract IntegrationDeployer is ExistingDeploymentParser {
+
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ // Fork ids for specific fork tests
+ bool isUpgraded;
+ uint256 mainnetForkBlock = 19_280_000;
+ uint256 mainnetForkId;
+ uint256 holeskyForkBLock = 1_213_950;
+ uint256 holeskyForkId;
+ uint64 constant DENEB_FORK_TIMESTAMP = 1705473120;
+
+
+ TimeMachine public timeMachine;
+
+ // Lists of strategies used in the system
+ //
+ // When we select random user assets, we use the `assetType` to determine
+ // which of these lists to select user assets from.
+ IStrategy[] lstStrats;
+ IStrategy[] ethStrats; // only has one strat tbh
+ IStrategy[] allStrats; // just a combination of the above 2 lists
+ IERC20[] allTokens; // `allStrats`, but contains all of the underlying tokens instead
+
+ // If a token is in this mapping, then we will ignore this LST as it causes issues with reading balanceOf
+ mapping(address => bool) public tokensNotTested;
+
+ // Mock Contracts to deploy
+ ETHPOSDepositMock ethPOSDeposit;
+ BeaconChainOracleMock public beaconChainOracle;
+ BeaconChainMock public beaconChain;
+
+ // Admin Addresses
+ address eigenLayerReputedMultisig = address(this); // admin address
+ address constant pauser = address(555);
+ address constant unpauser = address(556);
+
+ // Randomness state vars
+ bytes32 random;
+ // After calling `_configRand`, these are the allowed "variants" on users that will
+ // be returned from `_randUser`.
+ bytes assetTypes;
+ bytes userTypes;
+ // Set only once in setUp, if FORK_MAINNET env is set
+ uint forkType;
+
+ // Constants
+ uint64 constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9;
+
+ IStrategy constant BEACONCHAIN_ETH_STRAT = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+ IERC20 constant NATIVE_ETH = IERC20(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+
+ uint constant MIN_BALANCE = 1e6;
+ uint constant MAX_BALANCE = 5e6;
+ uint constant GWEI_TO_WEI = 1e9;
+
+ // Paused Constants
+ // DelegationManager
+ uint8 internal constant PAUSED_NEW_DELEGATION = 0;
+ uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1;
+ uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
+ // StrategyManager
+ uint8 internal constant PAUSED_DEPOSITS = 0;
+ // EigenpodManager
+ uint8 internal constant PAUSED_NEW_EIGENPODS = 0;
+ uint8 internal constant PAUSED_WITHDRAW_RESTAKED_ETH = 1;
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_CREDENTIALS = 2;
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE = 3;
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_WITHDRAWAL = 4;
+ uint8 internal constant PAUSED_NON_PROOF_WITHDRAWALS = 5;
+ // DelayedWithdrawalRouter
+ uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;
+
+ // Flags
+ uint constant FLAG = 1;
+
+ /// @dev Asset flags
+ /// These are used with _configRand to determine what assets are given
+ /// to a user when they are created.
+ uint constant NO_ASSETS = (FLAG << 0); // will have no assets
+ uint constant HOLDS_LST = (FLAG << 1); // will hold some random amount of LSTs
+ uint constant HOLDS_ETH = (FLAG << 2); // will hold some random amount of ETH
+ uint constant HOLDS_ALL = (FLAG << 3); // will hold every LST and ETH
+
+ /// @dev User contract flags
+ /// These are used with _configRand to determine what User contracts can be deployed
+ uint constant DEFAULT = (FLAG << 0);
+ uint constant ALT_METHODS = (FLAG << 1);
+
+ /// @dev Shadow Fork flags
+ /// These are used for upgrade integration testing.
+ uint constant LOCAL = (FLAG << 0);
+ uint constant MAINNET = (FLAG << 1);
+ uint constant HOLESKY = (FLAG << 2);
+
+ // /// @dev Withdrawal flags
+ // /// These are used with _configRand to determine how a user conducts a withdrawal
+ // uint constant FULL_WITHDRAW_SINGLE = (FLAG << 0); // stakers will withdraw all assets using a single queued withdrawal
+ // uint constant FULL_WITHDRAW_MULTI = (FLAG << 1); // stakers will withdraw all assets using multiple queued withdrawals
+ // uint constant PART_WITHDRAW_SINGLE = (FLAG << 2); // stakers will withdraw some, but not all assets
+
+ /// Note: Thought about the following flags (but did not implement) -
+ ///
+ /// WithdrawerType (SELF_WITHDRAWER, OTHER_WITHDRAWER)
+ /// - especially with EPM share handling, this felt like it deserved its own test rather than a fuzzy state
+ /// CompletionType (AS_TOKENS, AS_SHARES)
+ /// - same reason as above
+ ///
+ /// WithdrawalMethod (QUEUE_WITHDRAWAL, UNDELEGATE, REDELEGATE)
+ /// - could still do this!
+ /// - This would trigger staker.queueWithdrawals to use either `queueWithdrawals` or `undelegate` under the hood
+ /// - "redelegate" would be like the above, but adding a new `delegateTo` step after undelegating
+
+ mapping(uint => string) assetTypeToStr;
+ mapping(uint => string) userTypeToStr;
+ mapping(uint => string) forkTypeToStr;
+
+ constructor () {
+ assetTypeToStr[NO_ASSETS] = "NO_ASSETS";
+ assetTypeToStr[HOLDS_LST] = "HOLDS_LST";
+ assetTypeToStr[HOLDS_ETH] = "HOLDS_ETH";
+ assetTypeToStr[HOLDS_ALL] = "HOLDS_ALL";
+
+ userTypeToStr[DEFAULT] = "DEFAULT";
+ userTypeToStr[ALT_METHODS] = "ALT_METHODS";
+
+ forkTypeToStr[LOCAL] = "LOCAL";
+ forkTypeToStr[MAINNET] = "MAINNET";
+ forkTypeToStr[HOLESKY] = "HOLESKY";
+
+ address stETH_Holesky = 0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034;
+ address stETH_Mainnet = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
+ address OETH_Mainnet = 0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3;
+ address osETH_Holesky = 0xF603c5A3F774F05d4D848A9bB139809790890864;
+ address osETH_Mainnet = 0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38;
+ address cbETH_Holesky = 0x8720095Fa5739Ab051799211B146a2EEE4Dd8B37;
+ tokensNotTested[stETH_Holesky] = true;
+ tokensNotTested[stETH_Mainnet] = true;
+ tokensNotTested[OETH_Mainnet] = true;
+ tokensNotTested[osETH_Holesky] = true;
+ tokensNotTested[osETH_Mainnet] = true;
+ tokensNotTested[cbETH_Holesky] = true;
+ }
+
+ /**
+ * @dev Anyone who wants to test using this contract in a separate repo via submodules may have to
+ * override this function to set the correct paths for the deployment info files.
+ *
+ * This setUp function will account for specific --fork-url flags and deploy/upgrade contracts accordingly.
+ * Note that forkIds are also created so you can make explicit fork tests using cheats.selectFork(forkId)
+ */
+ function setUp() public virtual {
+ isUpgraded = false;
+
+ /**
+ * env FOUNDRY_PROFILE=forktest forge t --mc Integration
+ *
+ * Running foundry like this will trigger the fork test profile,
+ * lowering fuzz runs and using a remote RPC to test against mainnet state
+ */
+ bool forkMainnet =
+ _hash("forktest") ==
+ _hash(cheats.envOr(string("FOUNDRY_PROFILE"), string("default")));
+
+ if (forkMainnet) {
+ emit log("setUp: running tests against mainnet fork");
+ emit log_named_string("- using RPC url", cheats.rpcUrl("mainnet"));
+ emit log_named_uint("- forking at block", mainnetForkBlock);
+
+ cheats.createSelectFork(cheats.rpcUrl("mainnet"), mainnetForkBlock);
+ forkType = MAINNET;
+ } else {
+ emit log("setUp: running tests locally");
+
+ forkType = LOCAL;
+ }
+
+ _deployOrFetchContracts();
+ }
+
+ function _setUpLocal() public virtual {
+ // Deploy ProxyAdmin
+ eigenLayerProxyAdmin = new ProxyAdmin();
+ executorMultisig = address(eigenLayerProxyAdmin.owner());
+
+ // Deploy PauserRegistry
+ address[] memory pausers = new address[](1);
+ pausers[0] = pauser;
+ eigenLayerPauserReg = new PauserRegistry(pausers, unpauser);
+
+ // Deploy mocks
+ EmptyContract emptyContract = new EmptyContract();
+ ethPOSDeposit = new ETHPOSDepositMock();
+ beaconChainOracle = new BeaconChainOracleMock();
+
+ /**
+ * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are
+ * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code.
+ */
+ delegationManager = DelegationManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ strategyManager = StrategyManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ slasher = Slasher(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ eigenPodManager = EigenPodManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ delayedWithdrawalRouter = DelayedWithdrawalRouter(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+ avsDirectory = AVSDirectory(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+
+ // Deploy EigenPod Contracts
+ eigenPodImplementation = new EigenPod(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ 0
+ );
+
+ eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
+
+ // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
+ delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher);
+ slasherImplementation = new Slasher(strategyManager, delegationManager);
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegationManager
+ );
+ delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
+ avsDirectoryImplementation = new AVSDirectory(delegationManager);
+
+ // 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))),
+ address(delegationManagerImplementation),
+ abi.encodeWithSelector(
+ DelegationManager.initialize.selector,
+ eigenLayerReputedMultisig, // initialOwner
+ eigenLayerPauserReg,
+ 0 /* initialPausedStatus */,
+ withdrawalDelayBlocks,
+ initializeStrategiesToSetDelayBlocks,
+ initializeWithdrawalDelayBlocks
+ )
+ );
+ // StrategyManager
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation),
+ abi.encodeWithSelector(
+ StrategyManager.initialize.selector,
+ eigenLayerReputedMultisig, //initialOwner
+ eigenLayerReputedMultisig, //initial whitelister
+ eigenLayerPauserReg,
+ 0 // initialPausedStatus
+ )
+ );
+ // Slasher
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ address(slasherImplementation),
+ abi.encodeWithSelector(
+ Slasher.initialize.selector,
+ eigenLayerReputedMultisig,
+ eigenLayerPauserReg,
+ 0 // initialPausedStatus
+ )
+ );
+ // EigenPodManager
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation),
+ abi.encodeWithSelector(
+ EigenPodManager.initialize.selector,
+ address(beaconChainOracle),
+ eigenLayerReputedMultisig, // initialOwner
+ eigenLayerPauserReg,
+ 0 // initialPausedStatus
+ )
+ );
+ // Delayed Withdrawal Router
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ address(delayedWithdrawalRouterImplementation),
+ abi.encodeWithSelector(
+ DelayedWithdrawalRouter.initialize.selector,
+ eigenLayerReputedMultisig, // initialOwner
+ eigenLayerPauserReg,
+ 0, // initialPausedStatus
+ withdrawalDelayBlocks
+ )
+ );
+ // AVSDirectory
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ address(avsDirectoryImplementation),
+ abi.encodeWithSelector(
+ AVSDirectory.initialize.selector,
+ eigenLayerReputedMultisig, // initialOwner
+ eigenLayerPauserReg,
+ 0 // initialPausedStatus
+ )
+ );
+
+ // Create base strategy implementation and deploy a few strategies
+ baseStrategyImplementation = new StrategyBase(strategyManager);
+
+ _newStrategyAndToken("Strategy1Token", "str1", 10e50, address(this)); // initialSupply, owner
+ _newStrategyAndToken("Strategy2Token", "str2", 10e50, address(this)); // initialSupply, owner
+ _newStrategyAndToken("Strategy3Token", "str3", 10e50, address(this)); // initialSupply, owner
+
+ ethStrats.push(BEACONCHAIN_ETH_STRAT);
+ allStrats.push(BEACONCHAIN_ETH_STRAT);
+ allTokens.push(NATIVE_ETH);
+
+ // Create time machine and set block timestamp forward so we can create EigenPod proofs in the past
+ timeMachine = new TimeMachine();
+ timeMachine.setProofGenStartTime(2 hours);
+ // Create mock beacon chain / proof gen interface
+ beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle, eigenPodManager);
+
+ //set deneb fork timestamp
+ eigenPodManager.setDenebForkTimestamp(type(uint64).max);
+ }
+
+ /**
+ * @notice deploy current implementation contracts and upgrade the existing proxy EigenLayer contracts
+ * on Mainnet. Setup for integration tests on mainnet fork.
+ *
+ * Note that beacon chain oracle and eth deposit contracts are mocked and pointed to different addresses for these tests.
+ */
+ function _upgradeMainnetContracts() public virtual {
+ cheats.startPrank(address(executorMultisig));
+
+ ethPOSDeposit = new ETHPOSDepositMock();
+ ETHPOSDepositAddress = address(ethPOSDeposit); // overwrite for upgrade checks later
+
+ // Deploy EigenPod Contracts
+ eigenPodImplementation = new EigenPod(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ 0
+ );
+ eigenPodBeacon.upgradeTo(address(eigenPodImplementation));
+ // Deploy AVSDirectory, contract has not been deployed on mainnet yet
+ avsDirectory = AVSDirectory(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+
+ // First, deploy the *implementation* contracts, using the *proxy contracts* as inputs
+ delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher);
+ slasherImplementation = new Slasher(strategyManager, delegationManager);
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegationManager
+ );
+ delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
+ avsDirectoryImplementation = new AVSDirectory(delegationManager);
+
+ // Second, upgrade the proxy contracts to point to the implementations
+ // DelegationManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delegationManager))),
+ address(delegationManagerImplementation)
+ );
+ // StrategyManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation)
+ );
+ // Slasher
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ address(slasherImplementation)
+ );
+ // EigenPodManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation)
+ );
+ // Delayed Withdrawal Router
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ address(delayedWithdrawalRouterImplementation)
+ );
+ // AVSDirectory, upgrade and initalized
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ address(avsDirectoryImplementation),
+ abi.encodeWithSelector(
+ AVSDirectory.initialize.selector,
+ executorMultisig,
+ eigenLayerPauserReg,
+ 0 // initialPausedStatus
+ )
+ );
+
+ // Create base strategy implementation and deploy a few strategies
+ baseStrategyImplementation = new StrategyBase(strategyManager);
+
+ // Upgrade All deployed strategy contracts to new base strategy
+ for (uint i = 0; i < numStrategiesDeployed; i++) {
+ // Upgrade existing strategy
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i]))),
+ address(baseStrategyImplementation)
+ );
+ }
+
+ // Third, unpause core contracts
+ delegationManager.unpause(0);
+ eigenPodManager.unpause(0);
+ strategyManager.unpause(0);
+
+ eigenPodManager.updateBeaconChainOracle(beaconChainOracle);
+ timeMachine.setProofGenStartTime(0);
+ beaconChain.setNextTimestamp(timeMachine.proofGenStartTime());
+
+ if (eigenPodManager.denebForkTimestamp() == type(uint64).max) {
+ //set deneb fork timestamp if not set
+ eigenPodManager.setDenebForkTimestamp(DENEB_FORK_TIMESTAMP);
+ }
+ cheats.stopPrank();
+
+ ethStrats.push(BEACONCHAIN_ETH_STRAT);
+ allStrats.push(BEACONCHAIN_ETH_STRAT);
+ allTokens.push(NATIVE_ETH);
+ }
+
+ /**
+ * @notice deploy current implementation contracts and upgrade the existing proxy EigenLayer contracts
+ * on Holesky. Setup for integration tests on Holesky fork.
+ *
+ * Note that beacon chain oracle and eth deposit contracts are mocked and pointed to different addresses for these tests.
+ */
+ function _upgradeHoleskyContracts() public virtual {
+ cheats.startPrank(address(executorMultisig));
+
+ ethPOSDeposit = new ETHPOSDepositMock();
+ ETHPOSDepositAddress = address(ethPOSDeposit); // overwrite for upgrade checks later
+
+ // Deploy EigenPod Contracts
+ eigenPodImplementation = new EigenPod(
+ ethPOSDeposit,
+ delayedWithdrawalRouter,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ 0
+ );
+ eigenPodBeacon.upgradeTo(address(eigenPodImplementation));
+ // Deploy AVSDirectory, contract has not been deployed on mainnet yet
+ avsDirectory = AVSDirectory(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+
+ // First, deploy the *implementation* contracts, using the *proxy contracts* as inputs
+ delegationManagerImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager);
+ strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher);
+ slasherImplementation = new Slasher(strategyManager, delegationManager);
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSDeposit,
+ eigenPodBeacon,
+ strategyManager,
+ slasher,
+ delegationManager
+ );
+ delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
+ avsDirectoryImplementation = new AVSDirectory(delegationManager);
+
+ // Second, upgrade the proxy contracts to point to the implementations
+ // DelegationManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delegationManager))),
+ address(delegationManagerImplementation)
+ );
+ // StrategyManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(strategyManager))),
+ address(strategyManagerImplementation)
+ );
+ // Slasher
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(slasher))),
+ address(slasherImplementation)
+ );
+ // EigenPodManager
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerImplementation)
+ );
+ // Delayed Withdrawal Router
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))),
+ address(delayedWithdrawalRouterImplementation)
+ );
+ // AVSDirectory, upgrade and initalized
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(avsDirectory))),
+ address(avsDirectoryImplementation),
+ abi.encodeWithSelector(
+ AVSDirectory.initialize.selector,
+ executorMultisig,
+ eigenLayerPauserReg,
+ 0 // initialPausedStatus
+ )
+ );
+
+ // Create base strategy implementation and deploy a few strategies
+ baseStrategyImplementation = new StrategyBase(strategyManager);
+
+ // Upgrade All deployed strategy contracts to new base strategy
+ for (uint i = 0; i < numStrategiesDeployed; i++) {
+ // Upgrade existing strategy
+ eigenLayerProxyAdmin.upgrade(
+ TransparentUpgradeableProxy(payable(address(deployedStrategyArray[i]))),
+ address(baseStrategyImplementation)
+ );
+ }
+
+ // Third, unpause core contracts
+ delegationManager.unpause(0);
+ eigenPodManager.unpause(0);
+ strategyManager.unpause(0);
+
+ eigenPodManager.updateBeaconChainOracle(beaconChainOracle);
+ timeMachine.setProofGenStartTime(0);
+ beaconChain.setNextTimestamp(timeMachine.proofGenStartTime());
+
+ if (eigenPodManager.denebForkTimestamp() == type(uint64).max) {
+ //set deneb fork timestamp if not set
+ eigenPodManager.setDenebForkTimestamp(DENEB_FORK_TIMESTAMP);
+ }
+ cheats.stopPrank();
+
+ ethStrats.push(BEACONCHAIN_ETH_STRAT);
+ allStrats.push(BEACONCHAIN_ETH_STRAT);
+ allTokens.push(NATIVE_ETH);
+ }
+
+ /// @dev Deploy a strategy and its underlying token, push to global lists of tokens/strategies, and whitelist
+ /// strategy in strategyManager
+ function _newStrategyAndToken(string memory tokenName, string memory tokenSymbol, uint initialSupply, address owner) internal {
+ IERC20 underlyingToken = new ERC20PresetFixedSupply(tokenName, tokenSymbol, initialSupply, owner);
+ StrategyBase strategy = StrategyBase(
+ address(
+ new TransparentUpgradeableProxy(
+ address(baseStrategyImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg)
+ )
+ )
+ );
+
+ // Whitelist strategy
+ IStrategy[] memory strategies = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
+ strategies[0] = strategy;
+
+ if (forkType == MAINNET) {
+ cheats.prank(strategyManager.strategyWhitelister());
+ IStrategyManager_DeprecatedM1(address(strategyManager)).addStrategiesToDepositWhitelist(strategies);
+ cheats.prank(eigenLayerPauserReg.unpauser());
+ StrategyBaseTVLLimits(address(strategy)).setTVLLimits(type(uint256).max, type(uint256).max);
+ } else {
+ cheats.prank(strategyManager.strategyWhitelister());
+ strategyManager.addStrategiesToDepositWhitelist(strategies, _thirdPartyTransfersForbiddenValues);
+ }
+
+ // Add to lstStrats and allStrats
+ lstStrats.push(strategy);
+ allStrats.push(strategy);
+ allTokens.push(underlyingToken);
+ }
+
+ function _configRand(
+ uint24 _randomSeed,
+ uint _assetTypes,
+ uint _userTypes
+ ) internal {
+ // Using uint24 for the seed type so that if a test fails, it's easier
+ // to manually use the seed to replay the same test.
+ emit log_named_uint("_configRand: set random seed to: ", _randomSeed);
+ random = keccak256(abi.encodePacked(_randomSeed));
+
+ // Convert flag bitmaps to bytes of set bits for easy use with _randUint
+ assetTypes = _bitmapToBytes(_assetTypes);
+ userTypes = _bitmapToBytes(_userTypes);
+
+ emit log("_configRand: Users will be initialized with these asset types:");
+ for (uint i = 0; i < assetTypes.length; i++) {
+ emit log(assetTypeToStr[uint(uint8(assetTypes[i]))]);
+ }
+
+ emit log("_configRand: these User contracts will be initialized:");
+ for (uint i = 0; i < userTypes.length; i++) {
+ emit log(userTypeToStr[uint(uint8(userTypes[i]))]);
+ }
+
+ assertTrue(assetTypes.length != 0, "_configRand: no asset types selected");
+ assertTrue(userTypes.length != 0, "_configRand: no user types selected");
+ }
+
+ /**
+ * Depending on the forkType, either deploy contracts locally or parse existing contracts
+ * from network.
+ *
+ * Note: for non-LOCAL forktypes, upgrade of contracts will be peformed after user initialization.
+ */
+ function _deployOrFetchContracts() internal {
+ emit log_named_string("_deployOrFetchContracts using fork for test", forkTypeToStr[forkType]);
+
+ if (forkType == LOCAL) {
+ _setUpLocal();
+ // Set Upgraded as local setup deploys most up to date contracts
+ isUpgraded = true;
+ } else if (forkType == MAINNET) {
+ // cheats.selectFork(mainnetForkId);
+ string memory deploymentInfoPath = "script/configs/mainnet/Mainnet_current_deployment.config.json";
+ _parseDeployedContracts(deploymentInfoPath);
+
+ // Unpause to enable deposits and withdrawals for initializing random user state
+ cheats.prank(eigenLayerPauserReg.unpauser());
+ strategyManager.unpause(0);
+
+ // Add deployed strategies to lstStrats and allStrats
+ for (uint i; i < deployedStrategyArray.length; i++) {
+ IStrategy strategy = IStrategy(deployedStrategyArray[i]);
+
+ if (tokensNotTested[address(strategy.underlyingToken())]) {
+ continue;
+ }
+
+ // Add to lstStrats and allStrats
+ lstStrats.push(strategy);
+ allStrats.push(strategy);
+ allTokens.push(strategy.underlyingToken());
+ }
+
+ // Create time machine and set block timestamp forward so we can create EigenPod proofs in the past
+ timeMachine = new TimeMachine();
+ beaconChainOracle = new BeaconChainOracleMock();
+ // Create mock beacon chain / proof gen interface
+ beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle, eigenPodManager);
+ } else if (forkType == HOLESKY) {
+ revert("_deployOrFetchContracts - holesky tests currently broken sorry");
+ // cheats.selectFork(holeskyForkId);
+ string memory deploymentInfoPath = "script/configs/holesky/Holesky_current_deployment.config.json";
+ _parseDeployedContracts(deploymentInfoPath);
+
+ // Add deployed strategies to lstStrats and allStrats
+ for (uint i; i < deployedStrategyArray.length; i++) {
+ IStrategy strategy = IStrategy(deployedStrategyArray[i]);
+
+ if (tokensNotTested[address(strategy.underlyingToken())]) {
+ continue;
+ }
+
+ // Add to lstStrats and allStrats
+ lstStrats.push(strategy);
+ allStrats.push(strategy);
+ allTokens.push(strategy.underlyingToken());
+ }
+
+ // Update deposit contract to be a mock
+ ethPOSDeposit = new ETHPOSDepositMock();
+ eigenPodImplementation = new EigenPod(
+ ethPOSDeposit,
+ eigenPodImplementation.delayedWithdrawalRouter(),
+ eigenPodImplementation.eigenPodManager(),
+ eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(),
+ 0
+ );
+ // Create time machine and set block timestamp forward so we can create EigenPod proofs in the past
+ timeMachine = new TimeMachine();
+ beaconChainOracle = new BeaconChainOracleMock();
+ // Create mock beacon chain / proof gen interface
+ beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle, eigenPodManager);
+
+ cheats.startPrank(executorMultisig);
+ eigenPodBeacon.upgradeTo(address(eigenPodImplementation));
+ eigenPodManager.updateBeaconChainOracle(beaconChainOracle);
+ cheats.stopPrank();
+
+ } else {
+ revert("_deployOrFetchContracts: unimplemented forkType");
+ }
+ }
+
+ /**
+ * @dev Create a new User with a random config using the range defined in `_configRand`
+ *
+ * Assets are pulled from `strategies` based on a random staker/operator `assetType`
+ */
+ function _randUser(string memory name) internal returns (User, IStrategy[] memory, uint[] memory) {
+ // For the new user, select what type of assets they'll have and whether
+ // they'll use `xWithSignature` methods.
+ //
+ // The values selected here are in the ranges configured via `_configRand`
+ uint assetType = _randAssetType();
+ uint userType = _randUserType();
+
+ // Deploy new User contract
+ User user = _genRandUser(name, userType);
+
+ // For the specific asset selection we made, get a random assortment of
+ // strategies and deal the user some corresponding underlying token balances
+ (IStrategy[] memory strategies, uint[] memory tokenBalances) = _dealRandAssets(user, assetType);
+
+ _printUserInfo(name, assetType, userType, strategies, tokenBalances);
+ return (user, strategies, tokenBalances);
+ }
+
+ /// @dev Create a new user without native ETH. See _randUser above for standard usage
+ function _randUser_NoETH(string memory name) internal returns (User, IStrategy[] memory, uint[] memory) {
+ // For the new user, select what type of assets they'll have and whether
+ // they'll use `xWithSignature` methods.
+ //
+ // The values selected here are in the ranges configured via `_configRand`
+ uint userType = _randUserType();
+
+ // Pick the user's asset distribution, removing "native ETH" as an option
+ // I'm sorry if this eventually leads to a bug that's really hard to track down
+ uint assetType = _randAssetType();
+ if (assetType == HOLDS_ETH) {
+ assetType = NO_ASSETS;
+ } else if (assetType == HOLDS_ALL) {
+ assetType = HOLDS_LST;
+ }
+
+ // Deploy new User contract
+ User user = _genRandUser(name, userType);
+
+ // For the specific asset selection we made, get a random assortment of
+ // strategies and deal the user some corresponding underlying token balances
+ (IStrategy[] memory strategies, uint[] memory tokenBalances) = _dealRandAssets(user, assetType);
+
+ _printUserInfo(name, assetType, userType, strategies, tokenBalances);
+ return (user, strategies, tokenBalances);
+ }
+
+ function _genRandUser(string memory name, uint userType) internal returns (User) {
+ // Create User contract based on userType:
+ User user;
+ if (forkType == LOCAL) {
+ user = new User(name);
+
+ if (userType == DEFAULT) {
+ user = new User(name);
+ } else if (userType == ALT_METHODS) {
+ // User will use nonstandard methods like:
+ // `delegateToBySignature` and `depositIntoStrategyWithSignature`
+ user = User(new User_AltMethods(name));
+ } else {
+ revert("_randUser: unimplemented userType");
+ }
+ } else if (forkType == MAINNET) {
+ if (userType == DEFAULT) {
+ user = User(new User_M1(name));
+ } else if (userType == ALT_METHODS) {
+ // User will use nonstandard methods like:
+ // `delegateToBySignature` and `depositIntoStrategyWithSignature`
+ user = User(new User_M1_AltMethods(name));
+ } else {
+ revert("_randUser: unimplemented userType");
+ }
+
+ } else if (forkType == HOLESKY) {
+ // User deployment for Holesky is exact same as holesky.
+ // Current Holesky deployment is up to date and no deprecated interfaces have been added.
+
+ user = new User(name);
+
+ if (userType == DEFAULT) {
+ user = new User(name);
+ } else if (userType == ALT_METHODS) {
+ // User will use nonstandard methods like:
+ // `delegateToBySignature` and `depositIntoStrategyWithSignature`
+ user = User(new User_AltMethods(name));
+ } else {
+ revert("_randUser: unimplemented userType");
+ }
+ } else {
+ revert("_randUser: unimplemented forkType");
+ }
+
+ return user;
+ }
+
+ /// @dev For a given `assetType`, select a random assortment of strategies and assets
+ /// NO_ASSETS - return will be empty
+ /// HOLDS_LST - `strategies` will be a random subset of initialized strategies
+ /// `tokenBalances` will be the user's balances in each token
+ /// HOLDS_ETH - `strategies` will only contain BEACONCHAIN_ETH_STRAT, and
+ /// `tokenBalances` will contain the user's eth balance
+ /// HOLDS_ALL - `strategies` will contain ALL initialized strategies AND BEACONCHAIN_ETH_STRAT, and
+ /// `tokenBalances` will contain random token/eth balances accordingly
+ function _dealRandAssets(User user, uint assetType) internal returns (IStrategy[] memory, uint[] memory) {
+ IStrategy[] memory strategies;
+ uint[] memory tokenBalances;
+ if (assetType == NO_ASSETS) {
+ strategies = new IStrategy[](0);
+ tokenBalances = new uint[](0);
+ } else if (assetType == HOLDS_LST) {
+ assetType = HOLDS_LST;
+ // Select a random number of assets
+ uint numAssets = _randUint({ min: 1, max: lstStrats.length });
+ strategies = new IStrategy[](numAssets);
+ tokenBalances = new uint[](numAssets);
+
+ // For each asset, award the user a random balance of the underlying token
+ for (uint i = 0; i < numAssets; i++) {
+ IStrategy strat = lstStrats[i];
+ IERC20 underlyingToken = strat.underlyingToken();
+ uint balance = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE });
+
+ StdCheats.deal(address(underlyingToken), address(user), balance);
+ tokenBalances[i] = balance;
+ strategies[i] = strat;
+ }
+ } else if (assetType == HOLDS_ETH) {
+ strategies = new IStrategy[](1);
+ tokenBalances = new uint[](1);
+
+ // Award the user with a random multiple of 32 ETH
+ uint amount = 32 ether * _randUint({ min: 1, max: 3 });
+ cheats.deal(address(user), amount);
+
+ strategies[0] = BEACONCHAIN_ETH_STRAT;
+ tokenBalances[0] = amount;
+ } else if (assetType == HOLDS_ALL) {
+ uint numLSTs = lstStrats.length;
+ strategies = new IStrategy[](numLSTs + 1);
+ tokenBalances = new uint[](numLSTs + 1);
+
+ // For each LST, award the user a random balance of the underlying token
+ for (uint i = 0; i < numLSTs; i++) {
+ IStrategy strat = lstStrats[i];
+ IERC20 underlyingToken = strat.underlyingToken();
+ uint balance = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE });
+
+ StdCheats.deal(address(underlyingToken), address(user), balance);
+ tokenBalances[i] = balance;
+ strategies[i] = strat;
+ }
+
+ // Award the user with a random multiple of 32 ETH
+ uint amount = 32 ether * _randUint({ min: 1, max: 3 });
+ cheats.deal(address(user), amount);
+
+ // Add BEACONCHAIN_ETH_STRAT and eth balance
+ strategies[numLSTs] = BEACONCHAIN_ETH_STRAT;
+ tokenBalances[numLSTs] = amount;
+ } else {
+ revert("_dealRandAssets: assetType unimplemented");
+ }
+
+ return (strategies, tokenBalances);
+ }
+
+ /// @dev By default will have a assetType of HOLDS_LST
+ function _dealRandAssets_M1(User user) internal returns (IStrategy[] memory, uint[] memory) {
+ // Select a random number of assets
+ uint numAssets = _randUint({ min: 1, max: lstStrats.length });
+
+ IStrategy[] memory strategies = new IStrategy[](numAssets);
+ uint[] memory tokenBalances = new uint[](numAssets);
+
+ // For each asset, award the user a random balance of the underlying token
+ for (uint i = 0; i < numAssets; i++) {
+ IStrategy strat = lstStrats[i];
+ IERC20 underlyingToken = strat.underlyingToken();
+ uint balance = _randUint({ min: MIN_BALANCE, max: MAX_BALANCE });
+
+ StdCheats.deal(address(underlyingToken), address(user), balance);
+ tokenBalances[i] = balance;
+ strategies[i] = strat;
+ }
+
+ return (strategies, tokenBalances);
+ }
+
+ /// @dev Uses `random` to return a random uint, with a range given by `min` and `max` (inclusive)
+ /// @return `min` <= result <= `max`
+ function _randUint(uint min, uint max) internal returns (uint) {
+ uint range = max - min + 1;
+
+ // calculate the number of bits needed for the range
+ uint bitsNeeded = 0;
+ uint tempRange = range;
+ while (tempRange > 0) {
+ bitsNeeded++;
+ tempRange >>= 1;
+ }
+
+ // create a mask for the required number of bits
+ // and extract the value from the hash
+ uint mask = (1 << bitsNeeded) - 1;
+ uint value = uint(random) & mask;
+
+ // in case value is out of range, wrap around or retry
+ while (value >= range) {
+ value = (value - range) & mask;
+ }
+
+ // Hash `random` with itself so the next value we generate is different
+ random = keccak256(abi.encodePacked(random));
+ return min + value;
+ }
+
+ function _randBool() internal returns (bool) {
+ return _randUint({ min: 0, max: 1 }) == 0;
+ }
+
+ function _randAssetType() internal returns (uint) {
+ uint idx = _randUint({ min: 0, max: assetTypes.length - 1 });
+ uint assetType = uint(uint8(assetTypes[idx]));
+
+ return assetType;
+ }
+
+ function _randUserType() internal returns (uint) {
+ uint idx = _randUint({ min: 0, max: userTypes.length - 1 });
+ uint userType = uint(uint8(userTypes[idx]));
+
+ return userType;
+ }
+
+ /**
+ * @dev Converts a bitmap into an array of bytes
+ * @dev Each byte in the input is processed as indicating a single bit to flip in the bitmap
+ */
+ function _bitmapToBytes(uint bitmap) internal pure returns (bytes memory bytesArray) {
+ for (uint i = 0; i < 256; ++i) {
+ // Mask for i-th bit
+ uint mask = uint(1 << i);
+
+ // emit log_named_uint("mask: ", mask);
+
+ // If the i-th bit is flipped, add a byte to the return array
+ if (bitmap & mask != 0) {
+ bytesArray = bytes.concat(bytesArray, bytes1(uint8(1 << i)));
+ }
+ }
+ return bytesArray;
+ }
+
+ function _printUserInfo(
+ string memory name,
+ uint assetType,
+ uint userType,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) internal {
+
+ emit log("===Created User===");
+ emit log_named_string("Name", name);
+ emit log_named_string("assetType", assetTypeToStr[assetType]);
+ emit log_named_string("userType", userTypeToStr[userType]);
+ emit log_named_string("forkType", forkTypeToStr[forkType]);
+
+ emit log_named_uint("num assets: ", strategies.length);
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ emit log_named_string("token name: ", "Native ETH");
+ emit log_named_uint("token balance: ", tokenBalances[i]);
+ } else {
+ IERC20 underlyingToken = strat.underlyingToken();
+
+ emit log_named_string("token name: ", IERC20Metadata(address(underlyingToken)).name());
+ emit log_named_uint("token balance: ", tokenBalances[i]);
+ }
+ }
+
+ emit log("==================");
+ }
+
+ /// @dev Helper because solidity syntax is exhausting
+ function _hash(string memory s) internal pure returns (bytes32) {
+ return keccak256(abi.encodePacked(s));
+ }
+}
diff --git a/src/test/integration/README.md b/src/test/integration/README.md
new file mode 100644
index 0000000000..992d2a3267
--- /dev/null
+++ b/src/test/integration/README.md
@@ -0,0 +1,115 @@
+## EigenLayer Core Integration Testing
+
+### What the Hell?
+
+Good question.
+
+This folder contains the integration framework and tests for Eigenlayer core, which orchestrates the deployment of all EigenLayer core contracts to fuzz high-level user flows across multiple user and asset types, and supports time-travelling state lookups to quickly compare past and present states (please try to avoid preventing your own birth).
+
+**If you want to know how to run the tests**:
+
+* Local: `forge t --mc Integration`
+* Mainnet fork tests: `env FOUNDRY_PROFILE=forktest forge t --mc Integration`
+
+Note that for mainnet fork tests, you'll need to set the `RPC_MAINNET` environment variable to your RPC provider of choice!
+
+**If you want to know where the tests are**, take a look at `/tests`. We're doing one test contract per top-level flow, and defining multiple test functions for variants on that flow.
+
+e.g. if you're testing the flow "deposit into strategies -> delegate to operator -> queue withdrawal -> complete withdrawal", that's it's own test contract. For variants where withdrawals are completed "as tokens" vs "as shares," those are their own functions inside that contract.
+
+Looking at the current tests is a good place to start.
+
+**If you want to know how we're fuzzing these flows**, take a look at how we're using the `_configRand` method at the start of each test, which accepts bitmaps for the types of users and assets you want to spawn during the test.
+
+During the test, the config passed into `_configRand` will randomly generate only the values you configure:
+* `assetTypes` affect the assets granted to Users when they are first created. You can use this to ensure your flows and assertions work when users are holding only LSTs, native ETH, or some combination.
+* `userTypes` affect the actual `User` contract being deployed. The `DEFAULT` flag deploys the base `User` contract, while `ALT_METHODS` deploys a version that derives from the same contract, but overrides some methods to use "functionWithSignature" and other variants.
+
+Here's an example:
+
+```solidity
+function testFuzz_deposit_delegate_EXAMPLE(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params.
+ // `_randomSeed` will be the starting seed for all random lookups.
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ // Because of the `assetTypes` flags above, this will create two Users for our test,
+ // each of which holds some random assortment of LSTs.
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+
+ // Because of the `userTypes` flags above, this user might be using either:
+ // - `strategyManager.depositIntoStrategy`
+ // - `strategyManager.depositIntoStrategyWithSignature`
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ // assertions go here
+
+ // Because of the `userTypes` flags above, this user might be using either:
+ // - `delegation.delegateTo`
+ // - `delegation.delegateToBySignature`
+ staker.delegateTo(operator);
+ // assertions go here
+}
+```
+
+**If you want to know about the time travel**, there's a few things to note:
+
+The main feature we're using is foundry's `cheats.snapshot()` and `cheats.revertTo(snapshot)` to zip around in time. You can look at the [Cheatcodes Reference](https://book.getfoundry.sh/cheatcodes/#cheatcodes-interface) to get some idea, but the docs aren't actually correct. The best thing to do is look through our tests and see how it's being used. If you see an assertion called `assert_Snap_...`, that's using the `TimeMachine` under the hood.
+
+Speaking of, the `TimeMachine` is a global contract that controls the time, fate, and destiny of all who use it.
+* `Users` use the `TimeMachine` to snapshot chain state *before* every action they perform. (see the [`User.createSnapshot`](https://github.com/layr-labs/eigenlayer-contracts/blob/c5193f7bff00903a4323be2a1500cbf7137a83e9/src/test/integration/User.t.sol#L43-L46) modifier).
+* `IntegrationBase` uses a `timewarp` modifier to quickly fetch state "from before the last user action". These are leveraged within various `assert_Snap_XYZ` methods to allow the test to quickly compare previous and current values. ([example assertion method](https://github.com/layr-labs/eigenlayer-contracts/blob/c99e847709852d7246c73b7d72d44bba368b760e/src/test/integration/IntegrationBase.t.sol#L146-L148))
+
+This means that tests can perform user actions with very little setup or "reading prior state", and perform all the important assertions after each action. For example:
+
+```solidity
+function testFuzz_deposit_delegate_EXAMPLE(uint24 _random) public {
+ // ... test setup goes above here
+
+ // This snapshots state before the deposit.
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ // This checks the staker's shares from before `depositIntoEigenlayer`, and compares
+ // them to their shares after `depositIntoEigenlayer`.
+ assert_Snap_AddedStakerShares(staker, strategies, expectedShares, "failed to award staker shares");
+
+ // This snapshots state before delegating.
+ staker.delegateTo(operator);
+ // This checks the operator's `operatorShares` before the staker delegated to them, and
+ // compares those shares to the `operatorShares` after the staker delegated.
+ assert_Snap_AddedOperatorShares(operator, strategies, expectedShares, "failed to award operator shares");
+}
+```
+
+### Additional Important Concepts
+
+* Most testing logic and checks are performed at the test level. `IntegrationBase` has primarily helpers and a few sanity checks, but the current structure exists to make it clear what's being tested by reading the test itself.
+* Minimal logic/assertions/cheats used in User contract. These are for carrying out user behaviors, only. Exception:
+ * User methods snapshot state before performing actions
+* Top-level error messages are passed into helper assert methods so that it's always clear where an error came from
+* User contract should have an interface as similar as possible to the contract interfaces, so it feels like calling an EigenLayer method rather than some weird abstraction. Exceptions for things like:
+ * `user.depositIntoEigenLayer(strats, tokenBalances)` - because this deposits all strategies/shares and may touch either Smgr or Emgr
+
+### What needs to be done?
+
+* Suggest or PR cleanup if you have ideas. Currently, the `IntegrationDeployer` contract is pretty messy.
+* Coordinate in Slack to pick out some user flows to write tests for!
+
+#### Reduce RPC spam for fork tests
+
+Currently our mainnet fork tests spam whatever RPC we use. We can improve this in the future - apparently the meta is:
+
+> Use an anvil node to fork the network, you can write a script to make some changes to the forked network for setup etc, then fork your local node in your test.
+> Effectively you just setup an anvil node with the command
+`anvil -f RPC_URL `
+You can use `anvil -h` for more info on what it can do.
+
+> Then in your test you use the vm.createSelectFork command in your setup with the argument to point to your local anvil node which is basically a copy of the rpc you set it up as.
+> If you want to do some setup before running your tests you can write a script file and broadcast the setup transactions to your local anvil node (make sure to use one of the private keys anvil gives you)
\ No newline at end of file
diff --git a/src/test/integration/TimeMachine.t.sol b/src/test/integration/TimeMachine.t.sol
new file mode 100644
index 0000000000..7158f9db88
--- /dev/null
+++ b/src/test/integration/TimeMachine.t.sol
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "forge-std/Test.sol";
+
+contract TimeMachine is Test {
+
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ bool pastExists = false;
+ uint lastSnapshot;
+
+ uint64 public proofGenStartTime;
+
+ function createSnapshot() public returns (uint) {
+ uint snapshot = cheats.snapshot();
+ lastSnapshot = snapshot;
+ pastExists = true;
+ return snapshot;
+ }
+
+ function warpToLast() public returns (uint curState) {
+ // Safety check to make sure createSnapshot is called before attempting to warp
+ // so we don't accidentally prevent our own births
+ assertTrue(pastExists, "Global.warpToPast: invalid usage, past does not exist");
+
+ curState = cheats.snapshot();
+ cheats.revertTo(lastSnapshot);
+ return curState;
+ }
+
+ function warpToPresent(uint curState) public {
+ cheats.revertTo(curState);
+ }
+
+ /// @dev Sets the timestamp we use for proof gen to now,
+ /// then sets block timestamp to now + secondsAgo.
+ ///
+ /// This means we can create mock proofs using an oracle time
+ /// of `proofGenStartTime`.
+ function setProofGenStartTime(uint secondsAgo) public {
+ proofGenStartTime = uint64(block.timestamp);
+ cheats.warp(block.timestamp + secondsAgo);
+ }
+}
diff --git a/src/test/integration/deprecatedInterfaces/mainnet/BeaconChainProofs.sol b/src/test/integration/deprecatedInterfaces/mainnet/BeaconChainProofs.sol
new file mode 100644
index 0000000000..89af420137
--- /dev/null
+++ b/src/test/integration/deprecatedInterfaces/mainnet/BeaconChainProofs.sol
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: BUSL-1.1
+
+pragma solidity ^0.8.12;
+
+import "src/contracts/libraries/Merkle.sol";
+import "src/contracts/libraries/Endian.sol";
+
+// DEPRECATED BeaconChainProofs at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928
+//Utility library for parsing and PHASE0 beacon chain block headers
+//SSZ Spec: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization
+//BeaconBlockHeader Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
+//BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate
+library BeaconChainProofs_DeprecatedM1 {
+ // constants are the number of fields and the heights of the different merkle trees used in merkleizing beacon chain containers
+ uint256 internal constant NUM_BEACON_BLOCK_HEADER_FIELDS = 5;
+ uint256 internal constant BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT = 3;
+
+ uint256 internal constant NUM_BEACON_BLOCK_BODY_FIELDS = 11;
+ uint256 internal constant BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT = 4;
+
+ uint256 internal constant NUM_BEACON_STATE_FIELDS = 21;
+ uint256 internal constant BEACON_STATE_FIELD_TREE_HEIGHT = 5;
+
+ uint256 internal constant NUM_ETH1_DATA_FIELDS = 3;
+ uint256 internal constant ETH1_DATA_FIELD_TREE_HEIGHT = 2;
+
+ uint256 internal constant NUM_VALIDATOR_FIELDS = 8;
+ uint256 internal constant VALIDATOR_FIELD_TREE_HEIGHT = 3;
+
+ uint256 internal constant NUM_EXECUTION_PAYLOAD_HEADER_FIELDS = 15;
+ uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT = 4;
+
+
+ uint256 internal constant NUM_EXECUTION_PAYLOAD_FIELDS = 15;
+ uint256 internal constant EXECUTION_PAYLOAD_FIELD_TREE_HEIGHT = 4;
+
+
+ // HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24
+ uint256 internal constant HISTORICAL_ROOTS_TREE_HEIGHT = 24;
+
+ // HISTORICAL_BATCH is root of state_roots and block_root, so number of leaves = 2^1
+ uint256 internal constant HISTORICAL_BATCH_TREE_HEIGHT = 1;
+
+ // SLOTS_PER_HISTORICAL_ROOT = 2**13, so tree height is 13
+ uint256 internal constant STATE_ROOTS_TREE_HEIGHT = 13;
+ uint256 internal constant BLOCK_ROOTS_TREE_HEIGHT = 13;
+
+
+ uint256 internal constant NUM_WITHDRAWAL_FIELDS = 4;
+ // tree height for hash tree of an individual withdrawal container
+ uint256 internal constant WITHDRAWAL_FIELD_TREE_HEIGHT = 2;
+
+ uint256 internal constant VALIDATOR_TREE_HEIGHT = 40;
+ //refer to the eigenlayer-cli proof library. Despite being the same dimensions as the validator tree, the balance tree is merkleized differently
+ uint256 internal constant BALANCE_TREE_HEIGHT = 38;
+
+ // MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4
+ uint256 internal constant WITHDRAWALS_TREE_HEIGHT = 4;
+
+ //in beacon block body
+ uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9;
+
+ // in beacon block header
+ uint256 internal constant STATE_ROOT_INDEX = 3;
+ uint256 internal constant PROPOSER_INDEX_INDEX = 1;
+ uint256 internal constant SLOT_INDEX = 0;
+ uint256 internal constant BODY_ROOT_INDEX = 4;
+ // in beacon state
+ uint256 internal constant STATE_ROOTS_INDEX = 6;
+ uint256 internal constant BLOCK_ROOTS_INDEX = 5;
+ uint256 internal constant HISTORICAL_ROOTS_INDEX = 7;
+ uint256 internal constant ETH_1_ROOT_INDEX = 8;
+ uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11;
+ uint256 internal constant BALANCE_INDEX = 12;
+ uint256 internal constant EXECUTION_PAYLOAD_HEADER_INDEX = 24;
+ uint256 internal constant HISTORICAL_BATCH_STATE_ROOT_INDEX = 1;
+
+ // in validator
+ uint256 internal constant VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX = 1;
+ uint256 internal constant VALIDATOR_BALANCE_INDEX = 2;
+ uint256 internal constant VALIDATOR_SLASHED_INDEX = 3;
+ uint256 internal constant VALIDATOR_WITHDRAWABLE_EPOCH_INDEX = 7;
+
+ // in execution payload header
+ uint256 internal constant BLOCK_NUMBER_INDEX = 6;
+ uint256 internal constant WITHDRAWALS_ROOT_INDEX = 14;
+
+ //in execution payload
+ uint256 internal constant WITHDRAWALS_INDEX = 14;
+
+ // in withdrawal
+ uint256 internal constant WITHDRAWAL_VALIDATOR_INDEX_INDEX = 1;
+ uint256 internal constant WITHDRAWAL_VALIDATOR_AMOUNT_INDEX = 3;
+
+ //In historicalBatch
+ uint256 internal constant HISTORICALBATCH_STATEROOTS_INDEX = 1;
+
+ //Misc Constants
+ uint256 internal constant SLOTS_PER_EPOCH = 32;
+
+ bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
+
+
+
+ struct WithdrawalProofs {
+ bytes blockHeaderProof;
+ bytes withdrawalProof;
+ bytes slotProof;
+ bytes executionPayloadProof;
+ bytes blockNumberProof;
+ uint64 blockHeaderRootIndex;
+ uint64 withdrawalIndex;
+ bytes32 blockHeaderRoot;
+ bytes32 blockBodyRoot;
+ bytes32 slotRoot;
+ bytes32 blockNumberRoot;
+ bytes32 executionPayloadRoot;
+ }
+
+ struct ValidatorFieldsAndBalanceProofs {
+ bytes validatorFieldsProof;
+ bytes validatorBalanceProof;
+ bytes32 balanceRoot;
+ }
+
+ struct ValidatorFieldsProof {
+ bytes validatorProof;
+ uint40 validatorIndex;
+ }
+
+ /**
+ *
+ * @notice This function is parses the balanceRoot to get the uint64 balance of a validator. During merkleization of the
+ * beacon state balance tree, four uint64 values (making 32 bytes) are grouped together and treated as a single leaf in the merkle tree. Thus the
+ * validatorIndex mod 4 is used to determine which of the four uint64 values to extract from the balanceRoot.
+ * @param validatorIndex is the index of the validator being proven for.
+ * @param balanceRoot is the combination of 4 validator balances being proven for.
+ * @return The validator's balance, in Gwei
+ */
+ function getBalanceFromBalanceRoot(uint40 validatorIndex, bytes32 balanceRoot) internal pure returns (uint64) {
+ uint256 bitShiftAmount = (validatorIndex % 4) * 64;
+ bytes32 validatorBalanceLittleEndian = bytes32((uint256(balanceRoot) << bitShiftAmount));
+ uint64 validatorBalance = Endian.fromLittleEndianUint64(validatorBalanceLittleEndian);
+ return validatorBalance;
+ }
+
+ /**
+ * @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root
+ * @param validatorIndex the index of the proven validator
+ * @param beaconStateRoot is the beacon chain state root to be proven against.
+ * @param proof is the data used in proving the validator's fields
+ * @param validatorFields the claimed fields of the validator
+ */
+ function verifyValidatorFields(
+ uint40 validatorIndex,
+ bytes32 beaconStateRoot,
+ bytes calldata proof,
+ bytes32[] calldata validatorFields
+ ) internal view {
+
+ require(validatorFields.length == 2**VALIDATOR_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length");
+
+ /**
+ * Note: the length of the validator merkle proof is BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1.
+ * There is an additional layer added by hashing the root with the length of the validator list
+ */
+ require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");
+ uint256 index = (VALIDATOR_TREE_ROOT_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex);
+ // merkleize the validatorFields to get the leaf to prove
+ bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields);
+
+ // verify the proof of the validatorRoot against the beaconStateRoot
+ require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, validatorRoot, index), "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof");
+ }
+
+ /**
+ * @notice This function verifies merkle proofs of the balance of a certain validator against a beacon chain state root
+ * @param validatorIndex the index of the proven validator
+ * @param beaconStateRoot is the beacon chain state root to be proven against.
+ * @param proof is the proof of the balance against the beacon chain state root
+ * @param balanceRoot is the serialized balance used to prove the balance of the validator (refer to `getBalanceFromBalanceRoot` above for detailed explanation)
+ */
+ function verifyValidatorBalance(
+ uint40 validatorIndex,
+ bytes32 beaconStateRoot,
+ bytes calldata proof,
+ bytes32 balanceRoot
+ ) internal view {
+ require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");
+
+ /**
+ * the beacon state's balance list is a list of uint64 values, and these are grouped together in 4s when merkleized.
+ * Therefore, the index of the balance of a validator is validatorIndex/4
+ */
+ uint256 balanceIndex = uint256(validatorIndex/4);
+ balanceIndex = (BALANCE_INDEX << (BALANCE_TREE_HEIGHT + 1)) | balanceIndex;
+
+ require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, balanceRoot, balanceIndex), "BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof");
+ }
+
+ /**
+ * @notice This function verifies the slot and the withdrawal fields for a given withdrawal
+ * @param beaconStateRoot is the beacon chain state root to be proven against.
+ * @param proofs is the provided set of merkle proofs
+ * @param withdrawalFields is the serialized withdrawal container to be proven
+ */
+ function verifyWithdrawalProofs(
+ bytes32 beaconStateRoot,
+ WithdrawalProofs calldata proofs,
+ bytes32[] calldata withdrawalFields
+ ) internal view {
+ require(withdrawalFields.length == 2**WITHDRAWAL_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalFields has incorrect length");
+
+ require(proofs.blockHeaderRootIndex < 2**BLOCK_ROOTS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: blockRootIndex is too large");
+ require(proofs.withdrawalIndex < 2**WITHDRAWALS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalIndex is too large");
+
+ // verify the block header proof length
+ require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT),
+ "BeaconChainProofs.verifyWithdrawalProofs: blockHeaderProof has incorrect length");
+ require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
+ "BeaconChainProofs.verifyWithdrawalProofs: withdrawalProof has incorrect length");
+ require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyWithdrawalProofs: executionPayloadProof has incorrect length");
+ require(proofs.slotProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyWithdrawalProofs: slotProof has incorrect length");
+ require(proofs.blockNumberProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT),
+ "BeaconChainProofs.verifyWithdrawalProofs: blockNumberProof has incorrect length");
+
+
+ /**
+ * Computes the block_header_index relative to the beaconStateRoot. It concatenates the indexes of all the
+ * intermediate root indexes from the bottom of the sub trees (the block header container) to the top of the tree
+ */
+ uint256 blockHeaderIndex = BLOCK_ROOTS_INDEX << (BLOCK_ROOTS_TREE_HEIGHT) | uint256(proofs.blockHeaderRootIndex);
+ // Verify the blockHeaderRoot against the beaconStateRoot
+ require(Merkle.verifyInclusionSha256(proofs.blockHeaderProof, beaconStateRoot, proofs.blockHeaderRoot, blockHeaderIndex),
+ "BeaconChainProofs.verifyWithdrawalProofs: Invalid block header merkle proof");
+
+ //Next we verify the slot against the blockHeaderRoot
+ require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof");
+
+ // Next we verify the executionPayloadRoot against the blockHeaderRoot
+ uint256 executionPayloadIndex = BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)| EXECUTION_PAYLOAD_INDEX ;
+ require(Merkle.verifyInclusionSha256(proofs.executionPayloadProof, proofs.blockHeaderRoot, proofs.executionPayloadRoot, executionPayloadIndex),
+ "BeaconChainProofs.verifyWithdrawalProofs: Invalid executionPayload merkle proof");
+
+ // Next we verify the blockNumberRoot against the executionPayload root
+ require(Merkle.verifyInclusionSha256(proofs.blockNumberProof, proofs.executionPayloadRoot, proofs.blockNumberRoot, BLOCK_NUMBER_INDEX),
+ "BeaconChainProofs.verifyWithdrawalProofs: Invalid blockNumber merkle proof");
+
+ /**
+ * Next we verify the withdrawal fields against the blockHeaderRoot:
+ * First we compute the withdrawal_index relative to the blockHeaderRoot by concatenating the indexes of all the
+ * intermediate root indexes from the bottom of the sub trees (the withdrawal container) to the top, the blockHeaderRoot.
+ * Then we calculate merkleize the withdrawalFields container to calculate the the withdrawalRoot.
+ * Finally we verify the withdrawalRoot against the executionPayloadRoot.
+ */
+ uint256 withdrawalIndex = WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1) | uint256(proofs.withdrawalIndex);
+ bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
+ require(Merkle.verifyInclusionSha256(proofs.withdrawalProof, proofs.executionPayloadRoot, withdrawalRoot, withdrawalIndex),
+ "BeaconChainProofs.verifyWithdrawalProofs: Invalid withdrawal merkle proof");
+ }
+
+}
diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IBeaconChainOracle.sol b/src/test/integration/deprecatedInterfaces/mainnet/IBeaconChainOracle.sol
new file mode 100644
index 0000000000..2bb2c818e2
--- /dev/null
+++ b/src/test/integration/deprecatedInterfaces/mainnet/IBeaconChainOracle.sol
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+/**
+ * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928
+ * @title Interface for the BeaconStateOracle contract.
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ */
+interface IBeaconChainOracle_DeprecatedM1 {
+ /// @notice Largest blockNumber that has been confirmed by the oracle.
+ function latestConfirmedOracleBlockNumber() external view returns(uint64);
+ /// @notice Mapping: Beacon Chain blockNumber => the Beacon Chain state root at the specified blockNumber.
+ /// @dev This will return `bytes32(0)` if the state root at the specified blockNumber is not yet confirmed.
+ function beaconStateRootAtBlockNumber(uint64 blockNumber) external view returns(bytes32);
+
+ /// @notice Mapping: address => whether or not the address is in the set of oracle signers.
+ function isOracleSigner(address _oracleSigner) external view returns(bool);
+
+ /// @notice Mapping: Beacon Chain blockNumber => oracle signer address => whether or not the oracle signer has voted on the state root at the blockNumber.
+ function hasVoted(uint64 blockNumber, address oracleSigner) external view returns(bool);
+
+ /// @notice Mapping: Beacon Chain blockNumber => state root => total number of oracle signer votes for the state root at the blockNumber.
+ function stateRootVotes(uint64 blockNumber, bytes32 stateRoot) external view returns(uint256);
+
+ /// @notice Total number of members of the set of oracle signers.
+ function totalOracleSigners() external view returns(uint256);
+
+ /**
+ * @notice Number of oracle signers that must vote for a state root in order for the state root to be confirmed.
+ * Adjustable by this contract's owner through use of the `setThreshold` function.
+ * @dev We note that there is an edge case -- when the threshold is adjusted downward, if a state root already has enough votes to meet the *new* threshold,
+ * the state root must still receive one additional vote from an oracle signer to be confirmed. This behavior is intended, to minimize unexpected root confirmations.
+ */
+ function threshold() external view returns(uint256);
+
+ /**
+ * @notice Owner-only function used to modify the value of the `threshold` variable.
+ * @param _threshold Desired new value for the `threshold` variable. Function will revert if this is set to zero.
+ */
+ function setThreshold(uint256 _threshold) external;
+
+ /**
+ * @notice Owner-only function used to add a signer to the set of oracle signers.
+ * @param _oracleSigners Array of address to be added to the set.
+ * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already in the set of oracle signers.
+ */
+ function addOracleSigners(address[] memory _oracleSigners) external;
+
+ /**
+ * @notice Owner-only function used to remove a signer from the set of oracle signers.
+ * @param _oracleSigners Array of address to be removed from the set.
+ * @dev Function will have no effect on the i-th input address if `_oracleSigners[i]`is already not in the set of oracle signers.
+ */
+ function removeOracleSigners(address[] memory _oracleSigners) external;
+
+ /**
+ * @notice Called by a member of the set of oracle signers to assert that the Beacon Chain state root is `stateRoot` at `blockNumber`.
+ * @dev The state root will be finalized once the total number of votes *for this exact state root at this exact blockNumber* meets the `threshold` value.
+ * @param blockNumber The Beacon Chain blockNumber of interest.
+ * @param stateRoot The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`.
+ */
+ function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external;
+}
diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IDelayedWithdrawalRouter.sol b/src/test/integration/deprecatedInterfaces/mainnet/IDelayedWithdrawalRouter.sol
new file mode 100644
index 0000000000..8abb54a86f
--- /dev/null
+++ b/src/test/integration/deprecatedInterfaces/mainnet/IDelayedWithdrawalRouter.sol
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+/// @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928
+interface IDelayedWithdrawalRouter_DeprecatedM1 {
+ // struct used to pack data into a single storage slot
+ struct DelayedWithdrawal {
+ uint224 amount;
+ uint32 blockCreated;
+ }
+
+ // struct used to store a single users delayedWithdrawal data
+ struct UserDelayedWithdrawals {
+ uint256 delayedWithdrawalsCompleted;
+ DelayedWithdrawal[] delayedWithdrawals;
+ }
+
+ /**
+ * @notice Creates an delayed withdrawal for `msg.value` to the `recipient`.
+ * @dev Only callable by the `podOwner`'s EigenPod contract.
+ */
+ function createDelayedWithdrawal(address podOwner, address recipient) external payable;
+
+ /**
+ * @notice Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period.
+ * @param recipient The address to claim delayedWithdrawals for.
+ * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming.
+ */
+ function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfWithdrawalsToClaim) external;
+
+ /**
+ * @notice Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period.
+ * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming.
+ */
+ function claimDelayedWithdrawals(uint256 maxNumberOfWithdrawalsToClaim) external;
+
+ /// @notice Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
+ function setWithdrawalDelayBlocks(uint256 newValue) external;
+
+ /// @notice Getter function for the mapping `_userWithdrawals`
+ function userWithdrawals(address user) external view returns (UserDelayedWithdrawals memory);
+
+ /// @notice Getter function to get all delayedWithdrawals of the `user`
+ function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory);
+
+ /// @notice Getter function to get all delayedWithdrawals that are currently claimable by the `user`
+ function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory);
+
+ /// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
+ function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (DelayedWithdrawal memory);
+
+ /// @notice Getter function for fetching the length of the delayedWithdrawals array of a specific user
+ function userWithdrawalsLength(address user) external view returns (uint256);
+
+ /// @notice Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable
+ function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool);
+
+ /**
+ * @notice Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner,
+ * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
+ */
+ function withdrawalDelayBlocks() external view returns (uint256);
+}
diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol
new file mode 100644
index 0000000000..72a698e847
--- /dev/null
+++ b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "./BeaconChainProofs.sol";
+import "./IEigenPodManager.sol";
+import "./IBeaconChainOracle.sol";
+
+/**
+ * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928
+ * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ * @notice The main functionalities are:
+ * - creating new ETH validators with their withdrawal credentials pointed to this contract
+ * - proving from beacon chain state roots that withdrawal credentials are pointed to this contract
+ * - proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials
+ * pointed to this contract
+ * - updating aggregate balances in the EigenPodManager
+ * - withdrawing eth when withdrawals are initiated
+ * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
+ * to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts
+ */
+interface IEigenPod_DeprecatedM1 {
+ enum VALIDATOR_STATUS {
+ INACTIVE, // doesnt exist
+ ACTIVE, // staked on ethpos and withdrawal credentials are pointed to the EigenPod
+ OVERCOMMITTED, // proven to be overcommitted to EigenLayer
+ WITHDRAWN // withdrawn from the Beacon Chain
+ }
+
+ // this struct keeps track of PartialWithdrawalClaims
+ struct PartialWithdrawalClaim {
+ PARTIAL_WITHDRAWAL_CLAIM_STATUS status;
+ // block at which the PartialWithdrawalClaim was created
+ uint32 creationBlockNumber;
+ // last block (inclusive) in which the PartialWithdrawalClaim can be fraudproofed
+ uint32 fraudproofPeriodEndBlockNumber;
+ // amount of ETH -- in Gwei -- to be withdrawn until completion of this claim
+ uint64 partialWithdrawalAmountGwei;
+ }
+
+ enum PARTIAL_WITHDRAWAL_CLAIM_STATUS {
+ REDEEMED,
+ PENDING,
+ FAILED
+ }
+
+ /// @notice The amount of eth, in gwei, that is restaked per validator
+ function REQUIRED_BALANCE_GWEI() external view returns(uint64);
+
+ /// @notice The amount of eth, in wei, that is restaked per validator
+ function REQUIRED_BALANCE_WEI() external view returns(uint256);
+
+ /// @notice this is a mapping of validator indices to a Validator struct containing pertinent info about the validator
+ function validatorStatus(uint40 validatorIndex) external view returns(VALIDATOR_STATUS);
+
+ /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
+ function restakedExecutionLayerGwei() external view returns(uint64);
+
+ /// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
+ function initialize(address owner) external;
+
+ /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
+ function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
+
+ /**
+ * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
+ * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
+ * @dev Called during withdrawal or slashing.
+ * @dev Note that this function is marked as non-reentrant to prevent the recipient calling back into it
+ */
+ function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external;
+
+ /// @notice The single EigenPodManager for EigenLayer
+ function eigenPodManager() external view returns (IEigenPodManager_DeprecatedM1);
+
+ /// @notice The owner of this EigenPod
+ function podOwner() external view returns (address);
+
+ /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
+ function hasRestaked() external view returns (bool);
+
+ /// @notice block number of the most recent withdrawal
+ function mostRecentWithdrawalBlockNumber() external view returns (uint64);
+
+
+ ///@notice mapping that tracks proven partial withdrawals
+ function provenPartialWithdrawal(uint40 validatorIndex, uint64 slot) external view returns (bool);
+
+ /**
+ * @notice This function verifies that the withdrawal credentials of the podOwner are pointed to
+ * this contract. It also verifies the current (not effective) balance of the validator. It verifies the provided proof of the ETH validator against the beacon chain state
+ * root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
+ * @param oracleBlockNumber is the Beacon Chain blockNumber whose state root the `proof` will be proven against.
+ * @param validatorIndex is the index of the validator being proven, refer to consensus specs
+ * @param proofs is the bytes that prove the ETH validator's balance and withdrawal credentials against a beacon chain state root
+ * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
+ * for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
+ */
+ function verifyWithdrawalCredentialsAndBalance(
+ uint64 oracleBlockNumber,
+ uint40 validatorIndex,
+ BeaconChainProofs_DeprecatedM1.ValidatorFieldsAndBalanceProofs memory proofs,
+ bytes32[] calldata validatorFields
+ ) external;
+
+ /**
+ * @notice This function records an overcommitment of stake to EigenLayer on behalf of a certain ETH validator.
+ * If successful, the overcommitted balance is penalized (available for withdrawal whenever the pod's balance allows).
+ * The ETH validator's shares in the enshrined beaconChainETH strategy are also removed from the StrategyManager and undelegated.
+ * @param oracleBlockNumber The oracleBlockNumber whose state root the `proof` will be proven against.
+ * Must be within `VERIFY_OVERCOMMITTED_WINDOW_BLOCKS` of the current block.
+ * @param validatorIndex is the index of the validator being proven, refer to consensus specs
+ * @param proofs is the proof of the validator's balance and validatorFields in the balance tree and the balanceRoot to prove for
+ * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
+ * the StrategyManager in case it must be removed from the list of the podOwners strategies
+ * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
+ * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
+ */
+ function verifyOvercommittedStake(
+ uint40 validatorIndex,
+ BeaconChainProofs_DeprecatedM1.ValidatorFieldsAndBalanceProofs calldata proofs,
+ bytes32[] calldata validatorFields,
+ uint256 beaconChainETHStrategyIndex,
+ uint64 oracleBlockNumber
+ ) external;
+
+ /**
+ * @notice This function records a full withdrawal on behalf of one of the Ethereum validators for this EigenPod
+ * @param withdrawalProofs is the information needed to check the veracity of the block number and withdrawal being proven
+ * @param validatorFieldsProof is the proof of the validator's fields in the validator tree
+ * @param withdrawalFields are the fields of the withdrawal being proven
+ * @param validatorFields are the fields of the validator being proven
+ * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
+ * the EigenPodManager to the StrategyManager in case it must be removed from the podOwner's list of strategies
+ */
+ function verifyAndProcessWithdrawal(
+ BeaconChainProofs_DeprecatedM1.WithdrawalProofs calldata withdrawalProofs,
+ bytes calldata validatorFieldsProof,
+ bytes32[] calldata validatorFields,
+ bytes32[] calldata withdrawalFields,
+ uint256 beaconChainETHStrategyIndex,
+ uint64 oracleBlockNumber
+ ) external;
+
+ /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
+ function withdrawBeforeRestaking() external;
+}
diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol
new file mode 100644
index 0000000000..d5795bf5d4
--- /dev/null
+++ b/src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "./IStrategyManager.sol";
+import "./IEigenPod.sol";
+import "./IBeaconChainOracle.sol";
+import "src/contracts/interfaces/IPausable.sol";
+
+/**
+ * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928
+ * @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials pointed to EigenLayer.
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ */
+
+interface IEigenPodManager_DeprecatedM1 is IPausable {
+ /**
+ * @notice Creates an EigenPod for the sender.
+ * @dev Function will revert if the `msg.sender` already has an EigenPod.
+ */
+ function createPod() external;
+
+ /**
+ * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
+ * Also creates an EigenPod for the sender if they don't have one already.
+ * @param pubkey The 48 bytes public key of the beacon chain validator.
+ * @param signature The validator's signature of the deposit data.
+ * @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
+ */
+ function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
+
+ /**
+ * @notice Deposits/Restakes beacon chain ETH in EigenLayer on behalf of the owner of an EigenPod.
+ * @param podOwner The owner of the pod whose balance must be deposited.
+ * @param amount The amount of ETH to 'deposit' (i.e. be credited to the podOwner).
+ * @dev Callable only by the podOwner's EigenPod contract.
+ */
+ function restakeBeaconChainETH(address podOwner, uint256 amount) external;
+
+ /**
+ * @notice Removes beacon chain ETH from EigenLayer on behalf of the owner of an EigenPod, when the
+ * balance of a validator is lower than how much stake they have committed to EigenLayer
+ * @param podOwner The owner of the pod whose balance must be removed.
+ * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy for the pod owner for the callback to
+ * the StrategyManager in case it must be removed from the list of the podOwner's strategies
+ * @param amount The amount of ETH to remove.
+ * @dev Callable only by the podOwner's EigenPod contract.
+ */
+ function recordOvercommittedBeaconChainETH(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 amount) external;
+
+ /**
+ * @notice Withdraws ETH from an EigenPod. The ETH must have first been withdrawn from the beacon chain.
+ * @param podOwner The owner of the pod whose balance must be withdrawn.
+ * @param recipient The recipient of the withdrawn ETH.
+ * @param amount The amount of ETH to withdraw.
+ * @dev Callable only by the StrategyManager contract.
+ */
+ function withdrawRestakedBeaconChainETH(address podOwner, address recipient, uint256 amount) external;
+
+ /**
+ * @notice Updates the oracle contract that provides the beacon chain state root
+ * @param newBeaconChainOracle is the new oracle contract being pointed to
+ * @dev Callable only by the owner of this contract (i.e. governance)
+ */
+ function updateBeaconChainOracle(IBeaconChainOracle_DeprecatedM1 newBeaconChainOracle) external;
+
+ /// @notice Returns the address of the `podOwner`'s EigenPod if it has been deployed.
+ function ownerToPod(address podOwner) external view returns(IEigenPod_DeprecatedM1);
+
+ /// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
+ function getPod(address podOwner) external view returns(IEigenPod_DeprecatedM1);
+
+ /// @notice Oracle contract that provides updates to the beacon chain's state
+ function beaconChainOracle() external view returns(IBeaconChainOracle_DeprecatedM1);
+
+ /// @notice Returns the Beacon Chain state root at `blockNumber`. Reverts if the Beacon Chain state root at `blockNumber` has not yet been finalized.
+ function getBeaconChainStateRoot(uint64 blockNumber) external view returns(bytes32);
+
+ /// @notice EigenLayer's StrategyManager contract
+ function strategyManager() external view returns(IStrategyManager_DeprecatedM1);
+
+ /// @notice EigenLayer's Slasher contract
+ function slasher() external view returns(ISlasher);
+
+ function hasPod(address podOwner) external view returns (bool);
+}
diff --git a/src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol b/src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol
new file mode 100644
index 0000000000..be5745683c
--- /dev/null
+++ b/src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/contracts/interfaces/IStrategy.sol";
+import "src/contracts/interfaces/ISlasher.sol";
+import "src/contracts/interfaces/IDelegationManager.sol";
+
+/**
+ * @notice DEPRECATED INTERFACE at commit hash https://github.com/Layr-Labs/eigenlayer-contracts/tree/0139d6213927c0a7812578899ddd3dda58051928
+ * @title Interface for the primary entrypoint for funds into EigenLayer.
+ * @author Layr Labs, Inc.
+ * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
+ * @notice See the `StrategyManager` contract itself for implementation details.
+ */
+interface IStrategyManager_DeprecatedM1 {
+ // packed struct for queued withdrawals; helps deal with stack-too-deep errors
+ struct WithdrawerAndNonce {
+ address withdrawer;
+ uint96 nonce;
+ }
+
+ /**
+ * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
+ * In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or `completeQueuedWithdrawal`,
+ * the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked against the
+ * stored hash in order to confirm the integrity of the submitted data.
+ */
+ struct QueuedWithdrawal {
+ IStrategy[] strategies;
+ uint256[] shares;
+ address depositor;
+ WithdrawerAndNonce withdrawerAndNonce;
+ uint32 withdrawalStartBlock;
+ address delegatedAddress;
+ }
+
+ /**
+ * @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
+ * @param strategy is the specified strategy where deposit is to be made,
+ * @param token is the denomination in which the deposit is to be made,
+ * @param amount is the amount of token to be deposited in the strategy by the depositor
+ * @return shares The amount of new shares in the `strategy` created as part of the action.
+ * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
+ * @dev Cannot be called by an address that is 'frozen' (this function will revert if the `msg.sender` is frozen).
+ *
+ * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
+ * where the token balance and corresponding strategy shares are not in sync upon reentrancy.
+ */
+ function depositIntoStrategy(IStrategy strategy, IERC20 token, uint256 amount)
+ external
+ returns (uint256 shares);
+
+
+ /**
+ * @notice Deposits `amount` of beaconchain ETH into this contract on behalf of `staker`
+ * @param staker is the entity that is restaking in eigenlayer,
+ * @param amount is the amount of beaconchain ETH being restaked,
+ * @dev Only callable by EigenPodManager.
+ */
+ function depositBeaconChainETH(address staker, uint256 amount) external;
+
+ /**
+ * @notice Records an overcommitment event on behalf of a staker. The staker's beaconChainETH shares are decremented by `amount`.
+ * @param overcommittedPodOwner is the pod owner to be slashed
+ * @param beaconChainETHStrategyIndex is the index of the beaconChainETHStrategy in case it must be removed,
+ * @param amount is the amount to decrement the slashedAddress's beaconChainETHStrategy shares
+ * @dev Only callable by EigenPodManager.
+ */
+ function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount)
+ external;
+
+ /**
+ * @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
+ * who must sign off on the action.
+ * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
+ * purely to help one address deposit 'for' another.
+ * @param strategy is the specified strategy where deposit is to be made,
+ * @param token is the denomination in which the deposit is to be made,
+ * @param amount is the amount of token to be deposited in the strategy by the depositor
+ * @param staker the staker that the deposited assets will be credited to
+ * @param expiry the timestamp at which the signature expires
+ * @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward
+ * following EIP-1271 if the `staker` is a contract
+ * @return shares The amount of new shares in the `strategy` created as part of the action.
+ * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
+ * @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically those
+ * targeting stakers who may be attempting to undelegate.
+ * @dev Cannot be called on behalf of a staker that is 'frozen' (this function will revert if the `staker` is frozen).
+ *
+ * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended. This can lead to attack vectors
+ * where the token balance and corresponding strategy shares are not in sync upon reentrancy
+ */
+ function depositIntoStrategyWithSignature(
+ IStrategy strategy,
+ IERC20 token,
+ uint256 amount,
+ address staker,
+ uint256 expiry,
+ bytes memory signature
+ )
+ external
+ returns (uint256 shares);
+
+ /// @notice Returns the current shares of `user` in `strategy`
+ function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares);
+
+ /**
+ * @notice Get all details on the depositor's deposits and corresponding shares
+ * @return (depositor's strategies, shares in these strategies)
+ */
+ function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory);
+
+ /// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
+ function stakerStrategyListLength(address staker) external view returns (uint256);
+
+ /**
+ * @notice Called by a staker to queue a withdrawal of the given amount of `shares` from each of the respective given `strategies`.
+ * @dev Stakers will complete their withdrawal by calling the 'completeQueuedWithdrawal' function.
+ * User shares are decreased in this function, but the total number of shares in each strategy remains the same.
+ * The total number of shares is decremented in the 'completeQueuedWithdrawal' function instead, which is where
+ * the funds are actually sent to the user through use of the strategies' 'withdrawal' function. This ensures
+ * that the value per share reported by each strategy will remain consistent, and that the shares will continue
+ * to accrue gains during the enforced withdrawal waiting period.
+ * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
+ * for which `msg.sender` is withdrawing 100% of their shares
+ * @param strategies The Strategies to withdraw from
+ * @param shares The amount of shares to withdraw from each of the respective Strategies in the `strategies` array
+ * @param withdrawer The address that can complete the withdrawal and will receive any withdrawn funds or shares upon completing the withdrawal
+ * @param undelegateIfPossible If this param is marked as 'true' *and the withdrawal will result in `msg.sender` having no shares in any Strategy,*
+ * then this function will also make an internal call to `undelegate(msg.sender)` to undelegate the `msg.sender`.
+ * @return The 'withdrawalRoot' of the newly created Queued Withdrawal
+ * @dev Strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
+ * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
+ * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
+ * `stakerStrategyList` to lowest index
+ * @dev Note that if the withdrawal includes shares in the enshrined 'beaconChainETH' strategy, then it must *only* include shares in this strategy, and
+ * `withdrawer` must match the caller's address. The first condition is because slashing of queued withdrawals cannot be guaranteed
+ * for Beacon Chain ETH (since we cannot trigger a withdrawal from the beacon chain through a smart contract) and the second condition is because shares in
+ * the enshrined 'beaconChainETH' strategy technically represent non-fungible positions (deposits to the Beacon Chain, each pointed at a specific EigenPod).
+ */
+ function queueWithdrawal(
+ uint256[] calldata strategyIndexes,
+ IStrategy[] calldata strategies,
+ uint256[] calldata shares,
+ address withdrawer,
+ bool undelegateIfPossible
+ )
+ external returns(bytes32);
+
+ /**
+ * @notice Used to complete the specified `queuedWithdrawal`. The function caller must match `queuedWithdrawal.withdrawer`
+ * @param queuedWithdrawal The QueuedWithdrawal to complete.
+ * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies` array
+ * of the `queuedWithdrawal`. This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
+ * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
+ * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves
+ * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
+ * will simply be transferred to the caller directly.
+ * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
+ */
+ function completeQueuedWithdrawal(
+ QueuedWithdrawal calldata queuedWithdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ )
+ external;
+
+ /**
+ * @notice Used to complete the specified `queuedWithdrawals`. The function caller must match `queuedWithdrawals[...].withdrawer`
+ * @param queuedWithdrawals The QueuedWithdrawals to complete.
+ * @param tokens Array of tokens for each QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
+ * @param middlewareTimesIndexes One index to reference per QueuedWithdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
+ * @param receiveAsTokens If true, the shares specified in the queued withdrawal will be withdrawn from the specified strategies themselves
+ * and sent to the caller, through calls to `queuedWithdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
+ * will simply be transferred to the caller directly.
+ * @dev Array-ified version of `completeQueuedWithdrawal`
+ * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw`
+ */
+ function completeQueuedWithdrawals(
+ QueuedWithdrawal[] calldata queuedWithdrawals,
+ IERC20[][] calldata tokens,
+ uint256[] calldata middlewareTimesIndexes,
+ bool[] calldata receiveAsTokens
+ )
+ external;
+
+ /**
+ * @notice Slashes the shares of a 'frozen' operator (or a staker delegated to one)
+ * @param slashedAddress is the frozen address that is having its shares slashed
+ * @param recipient is the address that will receive the slashed funds, which could e.g. be a harmed party themself,
+ * or a MerkleDistributor-type contract that further sub-divides the slashed funds.
+ * @param strategies Strategies to slash
+ * @param shareAmounts The amount of shares to slash in each of the provided `strategies`
+ * @param tokens The tokens to use as input to the `withdraw` function of each of the provided `strategies`
+ * @param strategyIndexes is a list of the indices in `stakerStrategyList[msg.sender]` that correspond to the strategies
+ * for which `msg.sender` is withdrawing 100% of their shares
+ * @param recipient The slashed funds are withdrawn as tokens to this address.
+ * @dev strategies are removed from `stakerStrategyList` by swapping the last entry with the entry to be removed, then
+ * popping off the last entry in `stakerStrategyList`. The simplest way to calculate the correct `strategyIndexes` to input
+ * is to order the strategies *for which `msg.sender` is withdrawing 100% of their shares* from highest index in
+ * `stakerStrategyList` to lowest index
+ */
+ function slashShares(
+ address slashedAddress,
+ address recipient,
+ IStrategy[] calldata strategies,
+ IERC20[] calldata tokens,
+ uint256[] calldata strategyIndexes,
+ uint256[] calldata shareAmounts
+ )
+ external;
+
+ /**
+ * @notice Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one)
+ * @param recipient The funds in the slashed withdrawal are withdrawn as tokens to this address.
+ * @param queuedWithdrawal The previously queued withdrawal to be slashed
+ * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `strategies`
+ * array of the `queuedWithdrawal`.
+ * @param indicesToSkip Optional input parameter -- indices in the `strategies` array to skip (i.e. not call the 'withdraw' function on). This input exists
+ * so that, e.g., if the slashed QueuedWithdrawal contains a malicious strategy in the `strategies` array which always reverts on calls to its 'withdraw' function,
+ * then the malicious strategy can be skipped (with the shares in effect "burned"), while the non-malicious strategies are still called as normal.
+ */
+ function slashQueuedWithdrawal(address recipient, QueuedWithdrawal calldata queuedWithdrawal, IERC20[] calldata tokens, uint256[] calldata indicesToSkip)
+ external;
+
+ /// @notice Returns the keccak256 hash of `queuedWithdrawal`.
+ function calculateWithdrawalRoot(
+ QueuedWithdrawal memory queuedWithdrawal
+ )
+ external
+ pure
+ returns (bytes32);
+
+ /**
+ * @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into
+ * @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already)
+ */
+ function addStrategiesToDepositWhitelist(IStrategy[] calldata strategiesToWhitelist) external;
+
+ /**
+ * @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
+ * @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it)
+ */
+ function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external;
+
+ /// @notice Returns the single, central Delegation contract of EigenLayer
+ function delegation() external view returns (IDelegationManager);
+
+ /// @notice Returns the single, central Slasher contract of EigenLayer
+ function slasher() external view returns (ISlasher);
+
+ /// @notice returns the enshrined, virtual 'beaconChainETH' Strategy
+ function beaconChainETHStrategy() external view returns (IStrategy);
+
+ /// @notice Returns the number of blocks that must pass between the time a withdrawal is queued and the time it can be completed
+ function withdrawalDelayBlocks() external view returns (uint256);
+
+
+
+ /// VIEW FUNCTIONS FOR PUBLIC VARIABLES, NOT IN ORIGINAL INTERFACE
+ function withdrawalRootPending(bytes32 withdrawalRoot) external view returns (bool);
+
+ function numWithdrawalsQueued(address staker) external view returns (uint256);
+}
diff --git a/src/test/integration/mocks/BeaconChainMock.t.sol b/src/test/integration/mocks/BeaconChainMock.t.sol
new file mode 100644
index 0000000000..ec56198500
--- /dev/null
+++ b/src/test/integration/mocks/BeaconChainMock.t.sol
@@ -0,0 +1,817 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "forge-std/Test.sol";
+
+import "src/contracts/libraries/BeaconChainProofs.sol";
+import "src/contracts/libraries/Merkle.sol";
+import "src/contracts/pods/EigenPodManager.sol";
+
+import "src/test/integration/TimeMachine.t.sol";
+import "src/test/integration/mocks/BeaconChainOracleMock.t.sol";
+
+struct CredentialsProofs {
+ uint64 oracleTimestamp;
+ BeaconChainProofs.StateRootProof stateRootProof;
+ uint40[] validatorIndices;
+ bytes[] validatorFieldsProofs;
+ bytes32[][] validatorFields;
+}
+
+struct BeaconWithdrawal {
+ uint64 oracleTimestamp;
+ BeaconChainProofs.StateRootProof stateRootProof;
+ BeaconChainProofs.WithdrawalProof[] withdrawalProofs;
+ bytes[] validatorFieldsProofs;
+ bytes32[][] validatorFields;
+ bytes32[][] withdrawalFields;
+}
+
+struct BalanceUpdate {
+ uint64 oracleTimestamp;
+ BeaconChainProofs.StateRootProof stateRootProof;
+ uint40[] validatorIndices;
+ bytes[] validatorFieldsProofs;
+ bytes32[][] validatorFields;
+}
+
+contract BeaconChainMock is Test {
+
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ struct Validator {
+ bytes32 pubkeyHash;
+ uint40 validatorIndex;
+ bytes withdrawalCreds;
+ uint64 effectiveBalanceGwei;
+ }
+
+ uint40 nextValidatorIndex = 0;
+ uint64 public nextTimestamp;
+
+ // Sequential list of created Validators
+ // Validator[] validators;
+ // mapping(uint40 => Validator) validators;
+
+ mapping(uint40 => Validator) validators;
+
+ BeaconChainOracleMock oracle;
+ EigenPodManager eigenPodManager;
+
+ /// @dev All withdrawals are processed with index == 0
+ uint64 constant WITHDRAWAL_INDEX = 0;
+ uint constant GWEI_TO_WEI = 1e9;
+
+ constructor(TimeMachine timeMachine, BeaconChainOracleMock beaconChainOracle, EigenPodManager _eigenPodManager) {
+ nextTimestamp = timeMachine.proofGenStartTime();
+ oracle = beaconChainOracle;
+ eigenPodManager = _eigenPodManager;
+ }
+
+ /**
+ * @dev Processes a deposit for a new validator and returns the
+ * information needed to prove withdrawal credentials.
+ *
+ * For now, this returns empty proofs that will pass in the oracle,
+ * but in the future this should use FFI to return a valid proof.
+ */
+ function newValidator(
+ uint balanceWei,
+ bytes memory withdrawalCreds
+ ) public returns (uint40, CredentialsProofs memory) {
+ emit log_named_uint("- BeaconChain.newValidator with balance: ", balanceWei);
+
+ // These checks mimic the checks made in the beacon chain deposit contract
+ //
+ // We sanity-check them here because this contract sorta acts like the
+ // deposit contract and this ensures we only create validators that could
+ // exist IRL
+ require(balanceWei >= 1 ether, "BeaconChainMock.newValidator: deposit value too low");
+ require(balanceWei % 1 gwei == 0, "BeaconChainMock.newValidator: value not multiple of gwei");
+ uint depositAmount = balanceWei / GWEI_TO_WEI;
+ require(depositAmount <= type(uint64).max, "BeaconChainMock.newValidator: deposit value too high");
+
+ // Create unique index for new validator
+ uint40 validatorIndex = nextValidatorIndex;
+ nextValidatorIndex++;
+
+ // Create new validator and record in state
+ Validator memory validator = Validator({
+ pubkeyHash: keccak256(abi.encodePacked(validatorIndex)),
+ validatorIndex: validatorIndex,
+ withdrawalCreds: withdrawalCreds,
+ effectiveBalanceGwei: uint64(depositAmount)
+ });
+ validators[validatorIndex] = validator;
+
+ return (validator.validatorIndex, _genCredentialsProof(validator));
+ }
+
+ /**
+ * @dev Exit a validator from the beacon chain, given its validatorIndex
+ * The passed-in validatorIndex should correspond to a validator created
+ * via `newValidator` above.
+ *
+ * This method will return the exit proofs needed to process eigenpod withdrawals.
+ * Additionally, it will send the withdrawal amount to the validator's withdrawal
+ * destination.
+ */
+ function exitValidator(uint40 validatorIndex) public returns (BeaconWithdrawal memory) {
+ emit log_named_uint("- BeaconChain.exitValidator: ", validatorIndex);
+
+ Validator memory validator = validators[validatorIndex];
+
+ // Get the withdrawal amount and destination
+ uint amountToWithdraw = validator.effectiveBalanceGwei * GWEI_TO_WEI;
+ address destination = _toAddress(validator.withdrawalCreds);
+
+ // Generate exit proofs for a full exit
+ BeaconWithdrawal memory withdrawal = _genExitProof(validator);
+
+ // Update state - set validator balance to zero and send balance to withdrawal destination
+ validators[validatorIndex].effectiveBalanceGwei = 0;
+ cheats.deal(destination, destination.balance + amountToWithdraw);
+
+ return withdrawal;
+ }
+
+ /**
+ * Note: `delta` is expected to be a raw token amount. This method will convert the delta to Gwei
+ */
+ function updateBalance(uint40 validatorIndex, int delta) public returns (BalanceUpdate memory) {
+ delta /= int(GWEI_TO_WEI);
+
+ emit log_named_uint("- BeaconChain.updateBalance for validator: ", validatorIndex);
+ emit log_named_int("- BeaconChain.updateBalance delta gwei: ", delta);
+
+ // Apply delta and update validator balance in state
+ uint64 newBalance;
+ if (delta <= 0) {
+ newBalance = validators[validatorIndex].effectiveBalanceGwei - uint64(uint(-delta));
+ } else {
+ newBalance = validators[validatorIndex].effectiveBalanceGwei + uint64(uint(delta));
+ }
+ validators[validatorIndex].effectiveBalanceGwei = newBalance;
+
+ // Generate balance update proof
+ Validator memory validator = validators[validatorIndex];
+ BalanceUpdate memory update = _genBalanceUpdateProof(validator);
+
+ return update;
+ }
+
+ function setNextTimestamp(uint64 timestamp) public {
+ nextTimestamp = timestamp;
+ }
+
+ function balanceOfGwei(uint40 validatorIndex) public view returns (uint64) {
+ return validators[validatorIndex].effectiveBalanceGwei;
+ }
+
+ function pubkeyHash(uint40 validatorIndex) public view returns (bytes32) {
+ return validators[validatorIndex].pubkeyHash;
+ }
+
+ /**
+ * INTERNAL/HELPER METHODS:
+ */
+
+ /**
+ * @dev For a new validator, generate the beacon chain block root and merkle proof
+ * needed to prove withdrawal credentials to an EigenPod.
+ *
+ * The generated block root is sent to the `BeaconChainOracleMock`, and can be
+ * queried using `proof.oracleTimestamp` to validate the generated proof.
+ */
+ function _genCredentialsProof(Validator memory validator) internal returns (CredentialsProofs memory) {
+ CredentialsProofs memory proof;
+
+ proof.validatorIndices = new uint40[](1);
+ proof.validatorIndices[0] = validator.validatorIndex;
+
+ // Create validatorFields for the new validator
+ proof.validatorFields = new bytes32[][](1);
+ proof.validatorFields[0] = new bytes32[](2 ** BeaconChainProofs.VALIDATOR_FIELD_TREE_HEIGHT);
+ proof.validatorFields[0][BeaconChainProofs.VALIDATOR_PUBKEY_INDEX] = validator.pubkeyHash;
+ proof.validatorFields[0][BeaconChainProofs.VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX] =
+ bytes32(validator.withdrawalCreds);
+ proof.validatorFields[0][BeaconChainProofs.VALIDATOR_BALANCE_INDEX] =
+ _toLittleEndianUint64(validator.effectiveBalanceGwei);
+
+ // Calculate beaconStateRoot using validator index and an empty proof:
+ proof.validatorFieldsProofs = new bytes[](1);
+ proof.validatorFieldsProofs[0] = new bytes(VAL_FIELDS_PROOF_LEN);
+ bytes32 validatorRoot = Merkle.merkleizeSha256(proof.validatorFields[0]);
+ uint index = _calcValProofIndex(validator.validatorIndex);
+
+ bytes32 beaconStateRoot = Merkle.processInclusionProofSha256({
+ proof: proof.validatorFieldsProofs[0],
+ leaf: validatorRoot,
+ index: index
+ });
+
+ // Calculate blockRoot using beaconStateRoot and an empty proof:
+ bytes memory blockRootProof = new bytes(BLOCKROOT_PROOF_LEN);
+ bytes32 blockRoot = Merkle.processInclusionProofSha256({
+ proof: blockRootProof,
+ leaf: beaconStateRoot,
+ index: BeaconChainProofs.STATE_ROOT_INDEX
+ });
+
+ proof.stateRootProof = BeaconChainProofs.StateRootProof({
+ beaconStateRoot: beaconStateRoot,
+ proof: blockRootProof
+ });
+
+ // Send the block root to the oracle and increment timestamp:
+ proof.oracleTimestamp = uint64(nextTimestamp);
+
+ oracle.setBlockRoot(nextTimestamp, blockRoot);
+ nextTimestamp++;
+
+ return proof;
+ }
+
+ /**
+ * @dev Generates the proofs and roots needed to prove a validator's exit from
+ * the beacon chain.
+ *
+ * The generated beacon block root is sent to `BeaconChainOracleMock`, and can
+ * be queried using `withdrawal.oracleTimestamp` to validate the generated proof.
+ *
+ * Since a withdrawal proof requires proving multiple leaves in the same tree, this
+ * method uses `_genConvergentProofs` to calculate proofs and roots for intermediate
+ * subtrees, while retaining the information needed to supply an eigenpod with a proof.
+ *
+ * The overall merkle tree being proven looks like this:
+ *
+ * - beaconBlockRoot (submitted to oracle at end)
+ * -- beaconStateRoot
+ * ---- validatorFieldsRoot
+ * ---- blockRoot (from historical summaries)
+ * -------- slotRoot
+ * -------- executionPayloadRoot
+ * ---------------- timestampRoot
+ * ---------------- withdrawalFieldsRoot
+ *
+ * This method first generates proofs for the lowest leaves, and uses the resulting
+ * intermediate hashes to generate proofs for higher leaves. Eventually, all of these
+ * roots are calculated and the final beaconBlockRoot can be calculated and sent to the
+ * oracle.
+ */
+ function _genExitProof(Validator memory validator) internal returns (BeaconWithdrawal memory) {
+ BeaconWithdrawal memory withdrawal;
+ uint64 withdrawalEpoch = uint64(block.timestamp);
+
+ // Get a new, unique timestamp for queries to the oracle
+ withdrawal.oracleTimestamp = uint64(nextTimestamp);
+ nextTimestamp++;
+
+ // Initialize proof arrays
+ BeaconChainProofs.WithdrawalProof memory withdrawalProof = _initWithdrawalProof({
+ withdrawalEpoch: withdrawalEpoch,
+ withdrawalIndex: WITHDRAWAL_INDEX,
+ oracleTimestamp: withdrawal.oracleTimestamp
+ });
+
+ // Calculate withdrawalFields and record the validator's index and withdrawal amount
+ withdrawal.withdrawalFields = new bytes32[][](1);
+ withdrawal.withdrawalFields[0] = new bytes32[](2 ** BeaconChainProofs.WITHDRAWAL_FIELD_TREE_HEIGHT);
+ withdrawal.withdrawalFields[0][BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX] =
+ _toLittleEndianUint64(validator.validatorIndex);
+ withdrawal.withdrawalFields[0][BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX] =
+ _toLittleEndianUint64(validator.effectiveBalanceGwei);
+
+ {
+ /**
+ * Generate proofs then root for subtree:
+ *
+ * executionPayloadRoot
+ * - timestampRoot (withdrawalProof.timestampProof)
+ * - withdrawalFieldsRoot (withdrawalProof.withdrawalProof)
+ */
+ withdrawalProof.executionPayloadRoot = _genExecPayloadProofs({
+ withdrawalProof: withdrawalProof,
+ withdrawalRoot: Merkle.merkleizeSha256(withdrawal.withdrawalFields[0])
+ });
+ }
+
+ {
+ /**
+ * Generate proofs then root for subtree:
+ *
+ * blockRoot (historical summaries)
+ * - slotRoot (withdrawalProof.slotProof)
+ * - executionPayloadRoot (withdrawalProof.executionPayloadProof)
+ */
+ withdrawalProof.blockRoot = _genBlockRootProofs({
+ withdrawalProof: withdrawalProof
+ });
+ }
+
+ // validatorFields
+ withdrawal.validatorFields = new bytes32[][](1);
+ withdrawal.validatorFields[0] = new bytes32[](2 ** BeaconChainProofs.VALIDATOR_FIELD_TREE_HEIGHT);
+ withdrawal.validatorFields[0][BeaconChainProofs.VALIDATOR_PUBKEY_INDEX] = validator.pubkeyHash;
+ withdrawal.validatorFields[0][BeaconChainProofs.VALIDATOR_WITHDRAWABLE_EPOCH_INDEX] =
+ _toLittleEndianUint64(withdrawalEpoch);
+
+ withdrawal.validatorFieldsProofs = new bytes[](1);
+ withdrawal.validatorFieldsProofs[0] = new bytes(VAL_FIELDS_PROOF_LEN);
+
+ {
+ /**
+ * Generate proofs then root for subtree:
+ *
+ * beaconStateRoot
+ * - validatorFieldsRoot (withdrawal.validatorFieldsProofs[0])
+ * - blockRoot (historical summaries) (withdrawalProof.historicalSummaryBlockRootProof)
+ */
+ withdrawal.stateRootProof.beaconStateRoot = _genBeaconStateRootProofs({
+ withdrawalProof: withdrawalProof,
+ validatorFieldsProof: withdrawal.validatorFieldsProofs[0],
+ validatorIndex: validator.validatorIndex,
+ validatorRoot: Merkle.merkleizeSha256(withdrawal.validatorFields[0])
+ });
+ }
+
+ withdrawal.withdrawalProofs = new BeaconChainProofs.WithdrawalProof[](1);
+ withdrawal.withdrawalProofs[0] = withdrawalProof;
+
+ // Calculate beaconBlockRoot using beaconStateRoot and an empty proof:
+ withdrawal.stateRootProof.proof = new bytes(BLOCKROOT_PROOF_LEN);
+ bytes32 beaconBlockRoot = Merkle.processInclusionProofSha256({
+ proof: withdrawal.stateRootProof.proof,
+ leaf: withdrawal.stateRootProof.beaconStateRoot,
+ index: BeaconChainProofs.STATE_ROOT_INDEX
+ });
+
+ // Send the block root to the oracle
+ oracle.setBlockRoot(withdrawal.oracleTimestamp, beaconBlockRoot);
+ return withdrawal;
+ }
+
+ function _genBalanceUpdateProof(Validator memory validator) internal returns (BalanceUpdate memory) {
+ BalanceUpdate memory update;
+
+ update.validatorIndices = new uint40[](1);
+ update.validatorIndices[0] = validator.validatorIndex;
+
+ // Create validatorFields showing the balance update
+ update.validatorFields = new bytes32[][](1);
+ update.validatorFields[0] = new bytes32[](2 ** BeaconChainProofs.VALIDATOR_FIELD_TREE_HEIGHT);
+ update.validatorFields[0][BeaconChainProofs.VALIDATOR_PUBKEY_INDEX] = validator.pubkeyHash;
+ update.validatorFields[0][BeaconChainProofs.VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX] =
+ bytes32(validator.withdrawalCreds);
+ update.validatorFields[0][BeaconChainProofs.VALIDATOR_BALANCE_INDEX] =
+ _toLittleEndianUint64(validator.effectiveBalanceGwei);
+
+ // Calculate beaconStateRoot using validator index and an empty proof:
+ update.validatorFieldsProofs = new bytes[](1);
+ update.validatorFieldsProofs[0] = new bytes(VAL_FIELDS_PROOF_LEN);
+ bytes32 validatorRoot = Merkle.merkleizeSha256(update.validatorFields[0]);
+ uint index = _calcValProofIndex(validator.validatorIndex);
+
+ bytes32 beaconStateRoot = Merkle.processInclusionProofSha256({
+ proof: update.validatorFieldsProofs[0],
+ leaf: validatorRoot,
+ index: index
+ });
+
+ // Calculate blockRoot using beaconStateRoot and an empty proof:
+ bytes memory blockRootProof = new bytes(BLOCKROOT_PROOF_LEN);
+ bytes32 blockRoot = Merkle.processInclusionProofSha256({
+ proof: blockRootProof,
+ leaf: beaconStateRoot,
+ index: BeaconChainProofs.STATE_ROOT_INDEX
+ });
+
+ update.stateRootProof = BeaconChainProofs.StateRootProof({
+ beaconStateRoot: beaconStateRoot,
+ proof: blockRootProof
+ });
+
+ // Send the block root to the oracle and increment timestamp:
+ update.oracleTimestamp = uint64(nextTimestamp);
+ oracle.setBlockRoot(nextTimestamp, blockRoot);
+ nextTimestamp++;
+
+ return update;
+ }
+
+ /**
+ * @dev Generates converging merkle proofs for timestampRoot and withdrawalRoot
+ * under the executionPayloadRoot.
+ *
+ * `withdrawalProof.timestampProof` and `withdrawalProof.withdrawalProof` are
+ * directly updated here.
+ *
+ * @return executionPayloadRoot
+ */
+ function _genExecPayloadProofs(
+ BeaconChainProofs.WithdrawalProof memory withdrawalProof,
+ bytes32 withdrawalRoot
+ ) internal view returns (bytes32) {
+
+ uint withdrawalProofIndex =
+ (BeaconChainProofs.WITHDRAWALS_INDEX << (BeaconChainProofs.WITHDRAWALS_TREE_HEIGHT + 1)) |
+ uint(withdrawalProof.withdrawalIndex);
+
+ /**
+ * Generate merkle proofs for timestampRoot and withdrawalRoot
+ * that converge at or before executionPayloadRoot.
+ *
+ * timestampProof length: 4
+ * withdrawalProof length: 9
+ */
+ _genConvergentProofs({
+ shortProof: withdrawalProof.timestampProof,
+ shortIndex: BeaconChainProofs.TIMESTAMP_INDEX,
+ shortLeaf: withdrawalProof.timestampRoot,
+ longProof: withdrawalProof.withdrawalProof,
+ longIndex: withdrawalProofIndex,
+ longLeaf: withdrawalRoot
+ });
+
+ // Use generated proofs to calculate tree root and verify both proofs
+ // result in the same root:
+ bytes32 execPayloadRoot = Merkle.processInclusionProofSha256({
+ proof: withdrawalProof.timestampProof,
+ leaf: withdrawalProof.timestampRoot,
+ index: BeaconChainProofs.TIMESTAMP_INDEX
+ });
+
+ bytes32 expectedRoot = Merkle.processInclusionProofSha256({
+ proof: withdrawalProof.withdrawalProof,
+ leaf: withdrawalRoot,
+ index: withdrawalProofIndex
+ });
+
+ require(execPayloadRoot == expectedRoot, "_genExecPayloadProofs: mismatched roots");
+
+ return execPayloadRoot;
+ }
+
+ /**
+ * @dev Generates converging merkle proofs for slotRoot and executionPayloadRoot
+ * under the block root (historical summaries).
+ *
+ * `withdrawalProof.slotProof` and `withdrawalProof.executionPayloadProof` are
+ * directly updated here.
+ *
+ * @return historical summary block root
+ */
+ function _genBlockRootProofs(
+ BeaconChainProofs.WithdrawalProof memory withdrawalProof
+ ) internal view returns (bytes32) {
+
+ uint slotRootIndex = BeaconChainProofs.SLOT_INDEX;
+ uint execPayloadIndex =
+ (BeaconChainProofs.BODY_ROOT_INDEX << BeaconChainProofs.BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT) |
+ BeaconChainProofs.EXECUTION_PAYLOAD_INDEX;
+
+ /**
+ * Generate merkle proofs for slotRoot and executionPayloadRoot
+ * that converge at or before block root.
+ *
+ * slotProof length: 3
+ * executionPayloadProof length: 7
+ */
+ _genConvergentProofs({
+ shortProof: withdrawalProof.slotProof,
+ shortIndex: slotRootIndex,
+ shortLeaf: withdrawalProof.slotRoot,
+ longProof: withdrawalProof.executionPayloadProof,
+ longIndex: execPayloadIndex,
+ longLeaf: withdrawalProof.executionPayloadRoot
+ });
+
+ // Use generated proofs to calculate tree root and verify both proofs
+ // result in the same root:
+ bytes32 blockRoot = Merkle.processInclusionProofSha256({
+ proof: withdrawalProof.slotProof,
+ leaf: withdrawalProof.slotRoot,
+ index: slotRootIndex
+ });
+
+ bytes32 expectedRoot = Merkle.processInclusionProofSha256({
+ proof: withdrawalProof.executionPayloadProof,
+ leaf: withdrawalProof.executionPayloadRoot,
+ index: execPayloadIndex
+ });
+
+ require(blockRoot == expectedRoot, "_genBlockRootProofs: mismatched roots");
+
+ return blockRoot;
+ }
+
+ /**
+ * @dev Generates converging merkle proofs for block root and validatorRoot
+ * under the beaconStateRoot.
+ *
+ * `withdrawalProof.historicalSummaryBlockRootProof` and `validatorFieldsProof` are
+ * directly updated here.
+ *
+ * @return beaconStateRoot
+ */
+ function _genBeaconStateRootProofs(
+ BeaconChainProofs.WithdrawalProof memory withdrawalProof,
+ bytes memory validatorFieldsProof,
+ uint40 validatorIndex,
+ bytes32 validatorRoot
+ ) internal view returns (bytes32) {
+ uint blockHeaderIndex = _calcBlockHeaderIndex(withdrawalProof);
+ uint validatorProofIndex = _calcValProofIndex(validatorIndex);
+
+ /**
+ * Generate merkle proofs for validatorRoot and blockRoot
+ * that converge at or before beaconStateRoot.
+ *
+ * historicalSummaryBlockRootProof length: 44
+ * validatorFieldsProof length: 46
+ */
+ _genConvergentProofs({
+ shortProof: withdrawalProof.historicalSummaryBlockRootProof,
+ shortIndex: blockHeaderIndex,
+ shortLeaf: withdrawalProof.blockRoot,
+ longProof: validatorFieldsProof,
+ longIndex: validatorProofIndex,
+ longLeaf: validatorRoot
+ });
+
+ // Use generated proofs to calculate tree root and verify both proofs
+ // result in the same root:
+ bytes32 beaconStateRoot = Merkle.processInclusionProofSha256({
+ proof: withdrawalProof.historicalSummaryBlockRootProof,
+ leaf: withdrawalProof.blockRoot,
+ index: blockHeaderIndex
+ });
+
+ bytes32 expectedRoot = Merkle.processInclusionProofSha256({
+ proof: validatorFieldsProof,
+ leaf: validatorRoot,
+ index: validatorProofIndex
+ });
+
+ require(beaconStateRoot == expectedRoot, "_genBeaconStateRootProofs: mismatched roots");
+
+ return beaconStateRoot;
+ }
+
+ /**
+ * @dev Generates converging merkle proofs given two leaves and empty proofs.
+ * Basics:
+ * - `shortProof` and `longProof` start as empty proofs initialized to the correct
+ * length for their respective paths.
+ * - At the end of the method, `shortProof` and `longProof` are still entirely empty
+ * EXCEPT at the point where the proofs would normally converge under the root hash.
+ * - At this point, `shortProof` will be assigned the current hash for the `longLeaf` proof
+ * ... and `longProof` will be assigned the current hash for the `shortLeaf` proof
+ *
+ * Steps:
+ * 1. Because the beacon chain has trees and leaves at varying heights, this method
+ * first calculates the root of the longer proof's subtree so that the remaining
+ * proof length is the same for both leaves.
+ * 2. This method simultaneously computes each leaf's remaining proof step-by-step,
+ * performing effectively the same steps as `Merkle.processInclusionProof256`.
+ * 3. At each step, we check to see if the current indices represent sibling leaves.
+ * 4. If `shortIndex` and `longIndex` are siblings:
+ * - longProof[longProof_i] = curShortHash
+ * - shortProof[shortProof_i] = curLongHash
+ *
+ * ... Once we've found this convergence and placed each sibling's current hash in
+ * its opposing sibling's proof, we're done!
+ * @param shortProof An empty proof initialized to the correct length for the shorter proof path
+ * @param shortIndex The index of the
+ */
+ function _genConvergentProofs(
+ bytes memory shortProof,
+ uint shortIndex,
+ bytes32 shortLeaf,
+ bytes memory longProof,
+ uint longIndex,
+ bytes32 longLeaf
+ ) internal view {
+ require(longProof.length >= shortProof.length, "_genConvergentProofs: invalid input");
+
+ bytes32[1] memory curShortHash = [shortLeaf];
+ bytes32[1] memory curLongHash = [longLeaf];
+
+ // Calculate root of long subtree
+ uint longProofOffset = longProof.length - shortProof.length;
+ for (uint i = 32; i <= longProofOffset; i += 32) {
+ if (longIndex % 2 == 0) {
+ assembly {
+ mstore(0x00, mload(curLongHash))
+ mstore(0x20, mload(add(longProof, i)))
+ }
+ } else {
+ assembly {
+ mstore(0x00, mload(add(longProof, i)))
+ mstore(0x20, mload(curLongHash))
+ }
+ }
+
+ // Compute hash and divide index
+ assembly {
+ if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, curLongHash, 0x20)) {
+ revert(0, 0)
+ }
+ longIndex := div(longIndex, 2)
+ }
+ }
+
+ {
+ // Now that we've calculated the longest sub-tree, continue merklizing both trees simultaneously.
+ // When we reach two leaf indices s.t. A is even and B == A + 1, or vice versa, we know we have
+ // found the point where the two sub-trees converge.
+ uint longProof_i = 32 + longProofOffset;
+ uint shortProof_i = 32;
+ bool foundConvergence;
+ for (; longProof_i <= longProof.length; ) {
+ if (_areSiblings(longIndex, shortIndex)) {
+ foundConvergence = true;
+ assembly {
+ mstore(add(longProof, longProof_i), mload(curShortHash))
+ mstore(add(shortProof, shortProof_i), mload(curLongHash))
+ }
+
+ break;
+ }
+
+ // Compute next hash for longProof
+ {
+ if (longIndex % 2 == 0) {
+ assembly {
+ mstore(0x00, mload(curLongHash))
+ mstore(0x20, mload(add(longProof, longProof_i)))
+ }
+ } else {
+ assembly {
+ mstore(0x00, mload(add(longProof, longProof_i)))
+ mstore(0x20, mload(curLongHash))
+ }
+ }
+
+ // Compute hash and divide index
+ assembly {
+ if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, curLongHash, 0x20)) {
+ revert(0, 0)
+ }
+ longIndex := div(longIndex, 2)
+ }
+ }
+
+ // Compute next hash for shortProof
+ {
+ if (shortIndex % 2 == 0) {
+ assembly {
+ mstore(0x00, mload(curShortHash))
+ mstore(0x20, mload(add(shortProof, shortProof_i)))
+ }
+ } else {
+ assembly {
+ mstore(0x00, mload(add(shortProof, shortProof_i)))
+ mstore(0x20, mload(curShortHash))
+ }
+ }
+
+ // Compute hash and divide index
+ assembly {
+ if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, curShortHash, 0x20)) {
+ revert(0, 0)
+ }
+ shortIndex := div(shortIndex, 2)
+ }
+ }
+
+ longProof_i += 32;
+ shortProof_i += 32;
+ }
+
+ require(foundConvergence, "proofs did not converge!");
+ }
+ }
+
+ /**
+ * PROOF LENGTHS, MISC CONSTANTS, AND OTHER HELPERS:
+ */
+
+ uint immutable BLOCKROOT_PROOF_LEN = 32 * BeaconChainProofs.BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT;
+ uint immutable VAL_FIELDS_PROOF_LEN = 32 * (
+ (BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1) + BeaconChainProofs.BEACON_STATE_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
+ );
+ uint immutable SLOT_PROOF_LEN = 32 * (
+ BeaconChainProofs.BEACON_BLOCK_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 +
+ BeaconChainProofs.HISTORICAL_SUMMARIES_TREE_HEIGHT +
+ BeaconChainProofs.BLOCK_ROOTS_TREE_HEIGHT + 2
+ );
+
+ uint immutable HIST_SUMMARIES_PROOF_INDEX = BeaconChainProofs.HISTORICAL_SUMMARIES_INDEX << (
+ BeaconChainProofs.HISTORICAL_SUMMARIES_TREE_HEIGHT + 1 +
+ BeaconChainProofs.BLOCK_ROOTS_TREE_HEIGHT + 1
+ );
+
+ function _initWithdrawalProof(
+ uint64 withdrawalEpoch,
+ uint64 withdrawalIndex,
+ uint64 oracleTimestamp
+ ) internal view returns (BeaconChainProofs.WithdrawalProof memory) {
+ uint256 withdrawalProofLength;
+ uint256 timestampProofLength;
+ if (block.timestamp > eigenPodManager.denebForkTimestamp()) {
+ withdrawalProofLength = WITHDRAWAL_PROOF_LEN_DENEB;
+ timestampProofLength = TIMESTAMP_PROOF_LEN_DENEB;
+ } else {
+ withdrawalProofLength = WITHDRAWAL_PROOF_LEN_CAPELLA;
+ timestampProofLength = TIMESTAMP_PROOF_LEN_CAPELLA;
+ }
+ return BeaconChainProofs.WithdrawalProof({
+ withdrawalProof: new bytes(withdrawalProofLength),
+ slotProof: new bytes(SLOT_PROOF_LEN),
+ executionPayloadProof: new bytes(EXECPAYLOAD_PROOF_LEN),
+ timestampProof: new bytes(timestampProofLength),
+ historicalSummaryBlockRootProof: new bytes(HISTSUMMARY_PROOF_LEN),
+ blockRootIndex: 0,
+ historicalSummaryIndex: 0,
+ withdrawalIndex: withdrawalIndex,
+ blockRoot: bytes32(0),
+ slotRoot: _toLittleEndianUint64(withdrawalEpoch * BeaconChainProofs.SLOTS_PER_EPOCH),
+ timestampRoot: _toLittleEndianUint64(oracleTimestamp),
+ executionPayloadRoot: bytes32(0)
+ });
+ }
+
+ function _calcBlockHeaderIndex(BeaconChainProofs.WithdrawalProof memory withdrawalProof) internal view returns (uint) {
+ return
+ HIST_SUMMARIES_PROOF_INDEX |
+ (uint(withdrawalProof.historicalSummaryIndex) << (BeaconChainProofs.BLOCK_ROOTS_TREE_HEIGHT + 1)) |
+ (BeaconChainProofs.BLOCK_SUMMARY_ROOT_INDEX << BeaconChainProofs.BLOCK_ROOTS_TREE_HEIGHT) |
+ uint(withdrawalProof.blockRootIndex);
+ }
+
+ function _calcValProofIndex(uint40 validatorIndex) internal pure returns (uint) {
+ return
+ (BeaconChainProofs.VALIDATOR_TREE_ROOT_INDEX << (BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1)) |
+ uint(validatorIndex);
+ }
+
+ /// @dev Returns true if a and b are sibling indices in the same sub-tree.
+ ///
+ /// i.e. the indices belong two child nodes that share a parent:
+ /// [A, B] or [B, A]
+ function _areSiblings(uint a, uint b) internal pure returns (bool) {
+ return
+ (a % 2 == 0 && b == a + 1) || (b % 2 == 0 && a == b + 1);
+ }
+
+ /// @dev Opposite of Endian.fromLittleEndianUint64
+ function _toLittleEndianUint64(uint64 num) internal pure returns (bytes32) {
+ uint256 lenum;
+
+ // Rearrange the bytes from big-endian to little-endian format
+ lenum |= uint256((num & 0xFF) << 56);
+ lenum |= uint256((num & 0xFF00) << 40);
+ lenum |= uint256((num & 0xFF0000) << 24);
+ lenum |= uint256((num & 0xFF000000) << 8);
+ lenum |= uint256((num & 0xFF00000000) >> 8);
+ lenum |= uint256((num & 0xFF0000000000) >> 24);
+ lenum |= uint256((num & 0xFF000000000000) >> 40);
+ lenum |= uint256((num & 0xFF00000000000000) >> 56);
+
+ // Shift the little-endian bytes to the end of the bytes32 value
+ return bytes32(lenum << 192);
+ }
+
+ /// @dev Helper to convert 32-byte withdrawal credentials to an address
+ function _toAddress(bytes memory withdrawalCreds) internal pure returns (address a) {
+ bytes32 creds = bytes32(withdrawalCreds);
+ uint160 mask = type(uint160).max;
+
+ assembly { a := and(creds, mask) }
+ }
+}
diff --git a/src/test/integration/mocks/BeaconChainOracleMock.t.sol b/src/test/integration/mocks/BeaconChainOracleMock.t.sol
new file mode 100644
index 0000000000..b5a401c1f3
--- /dev/null
+++ b/src/test/integration/mocks/BeaconChainOracleMock.t.sol
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/contracts/interfaces/IBeaconChainOracle.sol";
+
+contract BeaconChainOracleMock is IBeaconChainOracle {
+
+ mapping(uint64 => bytes32) blockRoots;
+
+ function timestampToBlockRoot(uint timestamp) public view returns (bytes32) {
+ return blockRoots[uint64(timestamp)];
+ }
+
+ function setBlockRoot(uint64 timestamp, bytes32 blockRoot) public {
+ blockRoots[timestamp] = blockRoot;
+ }
+}
diff --git a/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol
new file mode 100644
index 0000000000..1c20e1807a
--- /dev/null
+++ b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/IntegrationChecks.t.sol";
+import "src/test/integration/users/User.t.sol";
+
+contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils {
+
+ function testFuzz_delegate_deposit_queue_completeAsShares(uint24 _random) public {
+ // Configure the random parameters for the test
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+ // Create a staker and an operator with a nonzero balance and corresponding strategies
+ (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ // 1. Delegate to operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, new uint256[](strategies.length)); // Initial shares are zero
+
+ // 2. Deposit into strategy
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ // Check that the deposit increased operator shares the staker is delegated to
+ check_Deposit_State(staker, strategies, shares);
+ assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares");
+
+ // 3. Queue Withdrawal
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 4. Complete Queued Withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+ for (uint i = 0; i < withdrawals.length; i++) {
+ staker.completeWithdrawalAsShares(withdrawals[i]);
+ check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, shares);
+ }
+ }
+
+ function testFuzz_delegate_deposit_queue_completeAsTokens(uint24 _random) public {
+ // Configure the random parameters for the test
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ // Create a staker and an operator with a nonzero balance and corresponding strategies
+ (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ // 1. Delegate to operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, new uint256[](strategies.length)); // Initial shares are zero
+
+ // 2. Deposit into strategy
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ // Check that the deposit increased operator shares the staker is delegated to
+ check_Deposit_State(staker, strategies, shares);
+ assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares");
+
+ // 3. Queue Withdrawal
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 4. Complete Queued Withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+ for (uint i = 0; i < withdrawals.length; i++) {
+ uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
+ check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+ }
+}
diff --git a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol
new file mode 100644
index 0000000000..5129d07108
--- /dev/null
+++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/IntegrationChecks.t.sol";
+import "src/test/integration/users/User.t.sol";
+
+contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils {
+
+ /*******************************************************************************
+ FULL WITHDRAWALS
+ *******************************************************************************/
+
+ /// Generates a random staker and operator. The staker:
+ /// 1. deposits all assets into strategies
+ /// 2. delegates to an operator
+ /// 3. queues a withdrawal for a ALL shares
+ /// 4. completes the queued withdrawal as tokens
+ function testFuzz_deposit_delegate_queue_completeAsTokens(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random subset of valid strategies (StrategyManager and/or EigenPodManager)
+ //
+ // ... check that the staker has no delegatable shares and isn't currently delegated
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ // 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Queue Withdrawals
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _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]);
+ check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+
+ // Check final state:
+ assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should still be delegated to operator");
+ assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares");
+ assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token balances");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ /// Generates a random staker and operator. The staker:
+ /// 1. deposits all assets into strategies
+ /// 2. delegates to an operator
+ /// 3. queues a withdrawal for a ALL shares
+ /// 4. completes the queued withdrawal as shares
+ function testFuzz_deposit_delegate_queue_completeAsShares(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random subset of valid strategies (StrategyManager and/or EigenPodManager)
+ //
+ // ... check that the staker has no delegatable shares and isn't currently delegated
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ // 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Queue Withdrawals
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ for (uint256 i = 0; i < withdrawals.length; i++) {
+ staker.completeWithdrawalAsShares(withdrawals[i]);
+ check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, shares);
+ }
+
+ // Check final state:
+ assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should still be delegated to operator");
+ assert_HasExpectedShares(staker, strategies, shares, "staker should have all original shares");
+ assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ /*******************************************************************************
+ RANDOM WITHDRAWALS
+ *******************************************************************************/
+
+ /// Generates a random staker and operator. The staker:
+ /// 1. deposits all assets into strategies
+ /// 2. delegates to an operator
+ /// 3. queues a withdrawal for a random subset of shares
+ /// 4. completes the queued withdrawal as tokens
+ function testFuzz_deposit_delegate_queueRand_completeAsTokens(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random subset of valid strategies (StrategyManager and/or EigenPodManager)
+ //
+ // ... check that the staker has no delegatable shares and isn't currently delegated
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ // 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Queue Withdrawals
+ // Randomly select one or more assets to withdraw
+ (
+ IStrategy[] memory withdrawStrats,
+ uint[] memory withdrawShares
+ ) = _randWithdrawal(strategies, shares);
+
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawals, withdrawalRoots);
+
+ // 4. Complete withdrawals
+ // Fast forward to when we can complete the withdrawal
+ _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]);
+ check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares, tokens, expectedTokens);
+ }
+
+ // Check final state:
+ assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should still be delegated to operator");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ /// Generates a random staker and operator. The staker:
+ /// 1. deposits all assets into strategies
+ /// 2. delegates to an operator
+ /// 3. queues a withdrawal for a random subset of shares
+ /// 4. completes the queued withdrawal as shares
+ function testFuzz_deposit_delegate_queueRand_completeAsShares(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random subset of valid strategies (StrategyManager and/or EigenPodManager)
+ //
+ // ... check that the staker has no delegatable shares and isn't currently delegated
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ // 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Queue Withdrawals
+ // Randomly select one or more assets to withdraw
+ (
+ IStrategy[] memory withdrawStrats,
+ uint[] memory withdrawShares
+ ) = _randWithdrawal(strategies, shares);
+
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawals, withdrawalRoots);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ for (uint i = 0; i < withdrawals.length; i++) {
+ staker.completeWithdrawalAsShares(withdrawals[i]);
+ check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares);
+ }
+
+ // Check final state:
+ assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should still be delegated to operator");
+ assert_HasExpectedShares(staker, strategies, shares, "staker should have all original shares");
+ assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ /*******************************************************************************
+ UNHAPPY PATH TESTS
+ *******************************************************************************/
+
+ /// Generates a random staker and operator. The staker:
+ /// 1. deposits all assets into strategies
+ /// --- registers as an operator
+ /// 2. delegates to an operator
+ ///
+ /// ... we check that the final step fails
+ function testFuzz_deposit_delegate_revert_alreadyDelegated(uint24 _random) public {
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: NO_ASSETS | HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create a staker and operator
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ // 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Register staker as an operator
+ staker.registerAsOperator();
+ assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated");
+
+ // 3. Attempt to delegate to an operator
+ // This should fail as the staker is already delegated to themselves.
+ cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated");
+ staker.delegateTo(operator);
+ }
+}
diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol
new file mode 100644
index 0000000000..e5bc6b91d8
--- /dev/null
+++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/IntegrationChecks.t.sol";
+
+contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUtils {
+ /// Randomly generates a user with different held assets. Then:
+ /// 1. deposit into strategy
+ /// 2. delegate to an operator
+ /// 3. undelegates from the operator
+ /// 4. complete queued withdrawal as shares
+ /// 5. delegate to a new operator
+ /// 5. queueWithdrawal
+ /// 7. complete their queued withdrawal as tokens
+ function testFuzz_deposit_delegate_reDelegate_completeAsTokens(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator1, ,) = _newRandomOperator();
+ (User operator2, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator1);
+ check_Delegation_State(staker, operator1, strategies, shares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal as shares
+ // Fast forward to when we can complete the withdrawal
+ _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);
+ }
+
+ // 5. Delegate to a new operator
+ staker.delegateTo(operator2);
+ check_Delegation_State(staker, operator2, strategies, shares);
+ assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1");
+
+ // 6. Queue Withdrawal
+ withdrawals = staker.queueWithdrawals(strategies, shares);
+ withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 7. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete withdrawals
+ for (uint i = 0; i < withdrawals.length; i++) {
+ uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
+ check_Withdrawal_AsTokens_State(staker, operator2, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares, tokens, expectedTokens);
+ }
+ }
+
+ function testFuzz_deposit_delegate_reDelegate_completeAsShares(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator1, ,) = _newRandomOperator();
+ (User operator2, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator1);
+ check_Delegation_State(staker, operator1, strategies, shares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal as shares
+ // Fast forward to when we can complete the withdrawal
+ _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);
+ }
+
+ // 5. Delegate to a new operator
+ staker.delegateTo(operator2);
+ check_Delegation_State(staker, operator2, strategies, shares);
+ assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1");
+
+ // 6. Queue Withdrawal
+ withdrawals = staker.queueWithdrawals(strategies, shares);
+ withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 7. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete all but last withdrawal as tokens
+ for (uint i = 0; i < withdrawals.length - 1; i++) {
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
+ uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
+ check_Withdrawal_AsTokens_State(staker, staker, withdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+
+ // Complete last withdrawal as shares
+ 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 {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST, // not holding ETH since we can only deposit 32 ETH multiples
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator1, ,) = _newRandomOperator();
+ (User operator2, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ {
+ // Divide shares by 2 in new array to do deposits after redelegate
+ uint[] memory numTokensToDeposit = new uint[](tokenBalances.length);
+ uint[] memory numTokensRemaining = new uint[](tokenBalances.length);
+ for (uint i = 0; i < shares.length; i++) {
+ numTokensToDeposit[i] = tokenBalances[i] / 2;
+ numTokensRemaining[i] = tokenBalances[i] - numTokensToDeposit[i];
+ }
+ uint[] memory halfShares = _calculateExpectedShares(strategies, numTokensToDeposit);
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, numTokensToDeposit);
+ check_Deposit_State_PartialDeposit(staker, strategies, halfShares, numTokensRemaining);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator1);
+ check_Delegation_State(staker, operator1, strategies, halfShares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, halfShares);
+
+ // 4. Complete withdrawal as shares
+ // Fast forward to when we can complete the withdrawal
+ _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);
+ }
+
+ // 5. Delegate to a new operator
+ staker.delegateTo(operator2);
+ check_Delegation_State(staker, operator2, strategies, halfShares);
+ assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1");
+
+ // 6. Deposit into Strategies
+ uint[] memory sharesAdded = _calculateExpectedShares(strategies, numTokensRemaining);
+ staker.depositIntoEigenlayer(strategies, numTokensRemaining);
+ tokenBalances = _calculateExpectedTokens(strategies, shares);
+ check_Deposit_State(staker, strategies, sharesAdded);
+ }
+
+ {
+ // 7. Queue Withdrawal
+ shares = _calculateExpectedShares(strategies, tokenBalances);
+ IDelegationManager.Withdrawal[] memory newWithdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory newWithdrawalRoots = _getWithdrawalHashes(newWithdrawals);
+ check_QueuedWithdrawal_State(staker, operator2, strategies, shares, newWithdrawals, newWithdrawalRoots);
+
+ // 8. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete withdrawals
+ for (uint i = 0; i < newWithdrawals.length; i++) {
+ uint[] memory expectedTokens = _calculateExpectedTokens(newWithdrawals[i].strategies, newWithdrawals[i].shares);
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(newWithdrawals[i]);
+ check_Withdrawal_AsTokens_State(staker, operator2, newWithdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+ }
+ }
+
+ function testFuzz_deposit_delegate_reDelegate_depositBeforeRedelegate(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST, // not holding ETH since we can only deposit 32 ETH multiples
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator1, ,) = _newRandomOperator();
+ (User operator2, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ {
+ // Divide shares by 2 in new array to do deposits after redelegate
+ uint[] memory numTokensToDeposit = new uint[](tokenBalances.length);
+ uint[] memory numTokensRemaining = new uint[](tokenBalances.length);
+ for (uint i = 0; i < shares.length; i++) {
+ numTokensToDeposit[i] = tokenBalances[i] / 2;
+ numTokensRemaining[i] = tokenBalances[i] - numTokensToDeposit[i];
+ }
+ uint[] memory halfShares = _calculateExpectedShares(strategies, numTokensToDeposit);
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, numTokensToDeposit);
+ check_Deposit_State_PartialDeposit(staker, strategies, halfShares, numTokensRemaining);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator1);
+ check_Delegation_State(staker, operator1, strategies, halfShares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, halfShares);
+
+ // 4. Complete withdrawal as shares
+ // Fast forward to when we can complete the withdrawal
+ _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);
+ }
+
+ // 5. Deposit into Strategies
+ uint[] memory sharesAdded = _calculateExpectedShares(strategies, numTokensRemaining);
+ staker.depositIntoEigenlayer(strategies, numTokensRemaining);
+ tokenBalances = _calculateExpectedTokens(strategies, shares);
+ check_Deposit_State(staker, strategies, sharesAdded);
+
+ // 6. Delegate to a new operator
+ staker.delegateTo(operator2);
+ check_Delegation_State(staker, operator2, strategies, shares);
+ assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1");
+ }
+
+ {
+ // 7. Queue Withdrawal
+ shares = _calculateExpectedShares(strategies, tokenBalances);
+ IDelegationManager.Withdrawal[] memory newWithdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory newWithdrawalRoots = _getWithdrawalHashes(newWithdrawals);
+ check_QueuedWithdrawal_State(staker, operator2, strategies, shares, newWithdrawals, newWithdrawalRoots);
+
+ // 8. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete withdrawals
+ for (uint i = 0; i < newWithdrawals.length; i++) {
+ uint[] memory expectedTokens = _calculateExpectedTokens(newWithdrawals[i].strategies, newWithdrawals[i].shares);
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(newWithdrawals[i]);
+ check_Withdrawal_AsTokens_State(staker, operator2, newWithdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+ }
+ }
+
+ function testFuzz_deposit_delegate_undelegate_withdrawAsTokens_reDelegate_completeAsTokens(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create operators and a staker
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator1, ,) = _newRandomOperator();
+ (User operator2, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory withdrawnTokenBalances = _calculateExpectedTokens(strategies, shares);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator1);
+ check_Delegation_State(staker, operator1, strategies, shares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal as tokens
+ // Fast forward to when we can complete the withdrawal
+ _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]);
+ check_Withdrawal_AsTokens_State(staker, operator1, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares, tokens, expectedTokens);
+ }
+
+ //5. Deposit into Strategies
+ staker.depositIntoEigenlayer(strategies, withdrawnTokenBalances);
+ shares = _calculateExpectedShares(strategies, withdrawnTokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 6. Delegate to a new operator
+ staker.delegateTo(operator2);
+ check_Delegation_State(staker, operator2, strategies, shares);
+ assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1");
+
+ // 7. Queue Withdrawal
+ withdrawals = staker.queueWithdrawals(strategies, shares);
+ withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 8. Complete withdrawal as shares
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete withdrawals as tokens
+ for (uint i = 0; i < withdrawals.length; i++) {
+ uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares);
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
+ check_Withdrawal_AsTokens_State(staker, operator2, withdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+ }
+
+ function testFuzz_deposit_delegate_undelegate_withdrawAsTokens_reDelegate_completeAsShares(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create operators and a staker
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator1, ,) = _newRandomOperator();
+ (User operator2, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory withdrawnTokenBalances = _calculateExpectedTokens(strategies, shares);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator1);
+ check_Delegation_State(staker, operator1, strategies, shares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal as Tokens
+ // Fast forward to when we can complete the withdrawal
+ _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]);
+ check_Withdrawal_AsTokens_State(staker, operator1, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares, tokens, expectedTokens);
+ }
+
+ //5. Deposit into Strategies
+ staker.depositIntoEigenlayer(strategies, withdrawnTokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 6. Delegate to a new operator
+ staker.delegateTo(operator2);
+ check_Delegation_State(staker, operator2, strategies, shares);
+ assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1");
+
+ // 7. Queue Withdrawal
+ shares = _calculateExpectedShares(strategies, withdrawnTokenBalances);
+ withdrawals = staker.queueWithdrawals(strategies, shares);
+ withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 8. Complete withdrawal as shares
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete withdrawals as shares
+ for (uint i = 0; i < withdrawals.length; i++) {
+ staker.completeWithdrawalAsShares(withdrawals[i]);
+ check_Withdrawal_AsShares_State(staker, operator2, withdrawals[i], strategies, shares);
+ }
+ }
+}
diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol
new file mode 100644
index 0000000000..9307eb3583
--- /dev/null
+++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/IntegrationChecks.t.sol";
+
+contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUtils {
+
+ /// Randomly generates a user with different held assets. Then:
+ /// 1. deposit into strategy
+ /// 2. delegate to an operator
+ /// 3. undelegates from the operator
+ /// 4. complete their queued withdrawal as tokens
+ function testFuzz_deposit_undelegate_completeAsTokens(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // Complete withdrawal
+ 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]);
+ check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares, tokens, expectedTokens);
+ }
+
+ // Check Final State
+ assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares");
+ assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token balances");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ /// Randomly generates a user with different held assets. Then:
+ /// 1. deposit into strategy
+ /// 2. delegate to an operator
+ /// 3. undelegates from the operator
+ /// 4. complete their queued withdrawal as shares
+ function testFuzz_deposit_undelegate_completeAsShares(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Undelegate from an operator
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate();
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(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);
+ }
+
+ // Check final state:
+ assert_HasExpectedShares(staker, strategies, shares, "staker should have all original shares");
+ assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ function testFuzz_deposit_delegate_forceUndelegate_completeAsTokens(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Force undelegate
+ IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(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]);
+ check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].shares, tokens, expectedTokens);
+ }
+
+ // Check Final State
+ assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares");
+ assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token balances");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+
+ function testFuzz_deposit_delegate_forceUndelegate_completeAsShares(uint24 _random) public {
+ // When new Users are created, they will choose a random configuration from these params:
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and a staker with:
+ // - some nonzero underlying token balances
+ // - corresponding to a random number of strategies
+ //
+ // ... check that the staker has no deleagatable shares and isn't delegated
+
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit Into Strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ // 3. Force undelegate
+ IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares);
+
+ // 4. Complete withdrawal
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(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);
+ }
+
+ // Check final state:
+ assert_HasExpectedShares(staker, strategies, shares, "staker should have all original shares");
+ assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ }
+}
diff --git a/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol b/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol
new file mode 100644
index 0000000000..53b42ff4a3
--- /dev/null
+++ b/src/test/integration/tests/Deposit_Delegate_UpdateBalance.t.sol
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/IntegrationChecks.t.sol";
+import "src/test/integration/users/User.t.sol";
+
+contract Integration_Deposit_Delegate_UpdateBalance is IntegrationCheckUtils {
+
+ /// Generates a random stake and operator. The staker:
+ /// 1. deposits all assets into strategies
+ /// 2. delegates to an operator
+ /// 3. queues a withdrawal for a ALL shares
+ /// 4. updates their balance randomly
+ /// 5. completes the queued withdrawal as tokens
+ function testFuzz_deposit_delegate_updateBalance_completeAsTokens(uint24 _random) public {
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ /// 0. Create an operator and staker with some underlying assets
+ (
+ User staker,
+ IStrategy[] memory strategies,
+ uint[] memory tokenBalances
+ ) = _newRandomStaker();
+ (User operator, ,) = _newRandomOperator();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+
+ assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing");
+ assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated");
+
+ /// 1. Deposit into strategies
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ /// 2. Delegate to an operator
+ staker.delegateTo(operator);
+ check_Delegation_State(staker, operator, strategies, shares);
+
+ /// 3. Queue withdrawals for ALL shares
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots);
+
+ // Generate a random balance update:
+ // - For LSTs, the tokenDelta is positive tokens minted to the staker
+ // - For ETH, the tokenDelta is a positive or negative change in beacon chain balance
+ (
+ int[] memory tokenDeltas,
+ int[] memory stakerShareDeltas,
+ int[] memory operatorShareDeltas
+ ) = _randBalanceUpdate(staker, strategies);
+
+ // 4. Update LST balance by depositing, and beacon balance by submitting a proof
+ staker.updateBalances(strategies, tokenDeltas);
+ assert_Snap_Delta_StakerShares(staker, strategies, stakerShareDeltas, "staker should have applied deltas correctly");
+ assert_Snap_Delta_OperatorShares(operator, strategies, operatorShareDeltas, "operator should have applied deltas correctly");
+
+ // Fast forward to when we can complete the withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+
+ // 5. Complete queued withdrawals as tokens
+ staker.completeWithdrawalsAsTokens(withdrawals);
+ assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should still be delegated to operator");
+ assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
+ assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed");
+ assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed");
+ }
+}
diff --git a/src/test/integration/tests/Deposit_Queue_Complete.t.sol b/src/test/integration/tests/Deposit_Queue_Complete.t.sol
new file mode 100644
index 0000000000..3260cf32ce
--- /dev/null
+++ b/src/test/integration/tests/Deposit_Queue_Complete.t.sol
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/IntegrationChecks.t.sol";
+
+contract Integration_Deposit_QueueWithdrawal_Complete is IntegrationCheckUtils {
+
+ /// Randomly generates a user with different held assets. Then:
+ /// 1. deposit into strategy
+ /// 2. queueWithdrawal
+ /// 3. completeQueuedWithdrawal"
+ function testFuzz_deposit_queueWithdrawal_completeAsTokens(uint24 _random) public {
+ // Configure the random parameters for the test
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ // Create a staker with a nonzero balance and corresponding strategies
+ (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ // 1. Deposit into strategy
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // Ensure staker is not delegated to anyone post deposit
+ assertFalse(delegationManager.isDelegated(address(staker)), "Staker should not be delegated after deposit");
+
+ // 2. Queue Withdrawal
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+
+ // 3. Complete Queued Withdrawal
+ _rollBlocksForCompleteWithdrawals(strategies);
+ for (uint i = 0; i < withdrawals.length; i++) {
+ uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
+ check_Withdrawal_AsTokens_State(staker, User(payable(0)), withdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+
+ // Ensure staker is still not delegated to anyone post withdrawal completion
+ assertFalse(delegationManager.isDelegated(address(staker)), "Staker should still not be delegated after withdrawal");
+ }
+
+ function testFuzz_deposit_queueWithdrawal_completeAsShares(uint24 _random) public {
+ // Configure the random parameters for the test
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ // Create a staker with a nonzero balance and corresponding strategies
+ (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ // 1. Deposit into strategy
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // Ensure staker is not delegated to anyone post deposit
+ assertFalse(delegationManager.isDelegated(address(staker)), "Staker should not be delegated after deposit");
+
+ // 2. Queue Withdrawal
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+
+ // 3. Complete Queued Withdrawal
+ _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);
+ }
+
+ // Ensure staker is still not delegated to anyone post withdrawal completion
+ assertFalse(delegationManager.isDelegated(address(staker)), "Staker should still not be delegated after withdrawal");
+ }
+}
diff --git a/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol
new file mode 100644
index 0000000000..9da5443a17
--- /dev/null
+++ b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/users/User.t.sol";
+import "src/test/integration/IntegrationChecks.t.sol";
+
+contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationCheckUtils {
+ function testFuzz_deposit_registerOperator_queueWithdrawal_completeAsShares(uint24 _random) public {
+ // Configure the random parameters for the test
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ // Create a staker with a nonzero balance and corresponding strategies
+ (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ // 1. Staker deposits into strategy
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Staker registers as an operator
+ staker.registerAsOperator();
+ assertTrue(delegationManager.isOperator(address(staker)), "Staker should be registered as an operator");
+
+ // 3. Queue Withdrawal
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 4. Complete Queued Withdrawal as Shares
+ _rollBlocksForCompleteWithdrawals(strategies);
+ for (uint i = 0; i < withdrawals.length; i++) {
+ staker.completeWithdrawalAsShares(withdrawals[i]);
+ check_Withdrawal_AsShares_State(staker, staker, withdrawals[i], strategies, shares);
+ }
+ }
+
+ function testFuzz_deposit_registerOperator_queueWithdrawal_completeAsTokens(uint24 _random) public {
+ // Configure the random parameters for the test
+ _configRand({
+ _randomSeed: _random,
+ _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ _userTypes: DEFAULT | ALT_METHODS
+ });
+
+ // Create a staker with a nonzero balance and corresponding strategies
+ (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) = _newRandomStaker();
+ // Upgrade contracts if forkType is not local
+ _upgradeEigenLayerContracts();
+
+ // 1. Staker deposits into strategy
+ staker.depositIntoEigenlayer(strategies, tokenBalances);
+ uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances);
+ check_Deposit_State(staker, strategies, shares);
+
+ // 2. Staker registers as an operator
+ staker.registerAsOperator();
+ assertTrue(delegationManager.isOperator(address(staker)), "Staker should be registered as an operator");
+
+ // 3. Queue Withdrawal
+ IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares);
+ bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
+ check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawals, withdrawalRoots);
+
+ // 4. Complete Queued Withdrawal as Tokens
+ _rollBlocksForCompleteWithdrawals(strategies);
+ for (uint i = 0; i < withdrawals.length; i++) {
+ IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]);
+ uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares);
+
+ check_Withdrawal_AsTokens_State(staker, staker, withdrawals[i], strategies, shares, tokens, expectedTokens);
+ }
+ }
+}
diff --git a/src/test/integration/tests/Upgrade_Setup.t.sol b/src/test/integration/tests/Upgrade_Setup.t.sol
new file mode 100644
index 0000000000..b1b2f2edda
--- /dev/null
+++ b/src/test/integration/tests/Upgrade_Setup.t.sol
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/IntegrationChecks.t.sol";
+
+contract IntegrationMainnetFork_UpgradeSetup is IntegrationCheckUtils {
+
+ // /// @notice Test upgrade setup is correct
+ // /// forge-config: default.fuzz.runs = 1
+ // function test_mainnet_upgrade_setup(uint24 _random) public {
+ // _configRand({
+ // _randomSeed: _random,
+ // _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ // _userTypes: DEFAULT | ALT_METHODS,
+ // _forkTypes: MAINNET
+ // });
+
+ // // // 1. Check proper state pre-upgrade
+ // // _verifyContractPointers();
+ // // _verifyImplementations();
+ // // _verifyContractsInitialized({isInitialDeployment: true});
+ // // _verifyInitializationParams();
+
+ // // 2. Upgrade mainnet contracts
+ // _upgradeEigenLayerContracts();
+ // _parseInitialDeploymentParams("script/configs/mainnet/M2_mainnet_upgrade.config.json");
+
+ // // 2. Verify upgrade setup
+ // _verifyContractPointers();
+ // _verifyImplementations();
+ // _verifyContractsInitialized({isInitialDeployment: true});
+ // _verifyInitializationParams();
+ // }
+
+ // /// @notice Test upgrade setup is correct
+ // /// forge-config: default.fuzz.runs = 1
+ // function test_holesky_upgrade_setup(uint24 _random) public {
+ // _configRand({
+ // _randomSeed: _random,
+ // _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL,
+ // _userTypes: DEFAULT | ALT_METHODS,
+ // _forkTypes: HOLESKY
+ // });
+
+ // // // 1. Check proper state pre-upgrade
+ // // _verifyContractPointers();
+ // // _verifyImplementations();
+ // // _verifyContractsInitialized({isInitialDeployment: true});
+ // // _verifyInitializationParams();
+
+ // // 2. Upgrade holesky contracts
+ // _upgradeEigenLayerContracts();
+ // _parseInitialDeploymentParams("script/configs/holesky/M2_deploy_from_scratch.holesky.config.json");
+
+ // // 3. Verify upgrade setup
+ // _verifyContractPointers();
+ // _verifyImplementations();
+ // _verifyContractsInitialized({isInitialDeployment: true});
+ // _verifyInitializationParams();
+ // }
+
+ /// @notice Ensure contracts point at each other correctly via constructors
+ /// override to remove ethPOSDeposit contract check
+ function _verifyContractPointers() internal virtual override view {
+ // AVSDirectory
+ require(
+ avsDirectory.delegation() == delegationManager,
+ "avsDirectory: delegationManager address not set correctly"
+ );
+ // DelegationManager
+ require(delegationManager.slasher() == slasher, "delegationManager: slasher address not set correctly");
+ require(
+ delegationManager.strategyManager() == strategyManager,
+ "delegationManager: strategyManager address not set correctly"
+ );
+ require(
+ delegationManager.eigenPodManager() == eigenPodManager,
+ "delegationManager: eigenPodManager address not set correctly"
+ );
+ // StrategyManager
+ require(strategyManager.slasher() == slasher, "strategyManager: slasher address not set correctly");
+ require(
+ strategyManager.delegation() == delegationManager,
+ "strategyManager: delegationManager address not set correctly"
+ );
+ require(
+ strategyManager.eigenPodManager() == eigenPodManager,
+ "strategyManager: eigenPodManager address not set correctly"
+ );
+ // EPM
+ require(
+ eigenPodManager.eigenPodBeacon() == eigenPodBeacon,
+ "eigenPodManager: eigenPodBeacon contract address not set correctly"
+ );
+ require(
+ eigenPodManager.strategyManager() == strategyManager,
+ "eigenPodManager: strategyManager contract address not set correctly"
+ );
+ require(eigenPodManager.slasher() == slasher, "eigenPodManager: slasher contract address not set correctly");
+ require(
+ eigenPodManager.delegationManager() == delegationManager,
+ "eigenPodManager: delegationManager contract address not set correctly"
+ );
+ // DelayedWithdrawalRouter
+ require(
+ delayedWithdrawalRouter.eigenPodManager() == eigenPodManager,
+ "delayedWithdrawalRouterContract: eigenPodManager address not set correctly"
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol
new file mode 100644
index 0000000000..341103e4c3
--- /dev/null
+++ b/src/test/integration/users/User.t.sol
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "forge-std/Test.sol";
+
+import "src/contracts/core/DelegationManager.sol";
+import "src/contracts/core/StrategyManager.sol";
+import "src/contracts/pods/EigenPodManager.sol";
+import "src/contracts/pods/EigenPod.sol";
+
+import "src/contracts/interfaces/IDelegationManager.sol";
+import "src/contracts/interfaces/IStrategy.sol";
+
+import "src/test/integration/TimeMachine.t.sol";
+import "src/test/integration/mocks/BeaconChainMock.t.sol";
+import "src/test/integration/mocks/BeaconChainOracleMock.t.sol";
+
+
+interface IUserDeployer {
+ function delegationManager() external view returns (DelegationManager);
+ function strategyManager() external view returns (StrategyManager);
+ function eigenPodManager() external view returns (EigenPodManager);
+ function timeMachine() external view returns (TimeMachine);
+ function beaconChain() external view returns (BeaconChainMock);
+ function beaconChainOracle() external view returns (address);
+}
+
+contract User is Test {
+
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ DelegationManager delegationManager;
+ StrategyManager strategyManager;
+ EigenPodManager eigenPodManager;
+
+ TimeMachine timeMachine;
+
+ /// @dev Native restaker state vars
+
+ BeaconChainMock beaconChain;
+ BeaconChainOracleMock beaconChainOracle;
+ // User's EigenPod and each of their validator indices within that pod
+ EigenPod public pod;
+ uint40[] validators;
+
+ IStrategy constant BEACONCHAIN_ETH_STRAT = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+ IERC20 constant NATIVE_ETH = IERC20(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+ uint constant GWEI_TO_WEI = 1e9;
+
+ string public NAME;
+
+ constructor(string memory name) {
+ IUserDeployer deployer = IUserDeployer(msg.sender);
+
+ delegationManager = deployer.delegationManager();
+ strategyManager = deployer.strategyManager();
+ eigenPodManager = deployer.eigenPodManager();
+ timeMachine = deployer.timeMachine();
+
+ beaconChain = deployer.beaconChain();
+ beaconChainOracle = BeaconChainOracleMock(deployer.beaconChainOracle());
+ _createPod();
+
+ NAME = name;
+ }
+
+ modifier createSnapshot() virtual {
+ timeMachine.createSnapshot();
+ _;
+ }
+
+ receive() external payable {}
+
+ /**
+ * DelegationManager methods:
+ */
+
+ function registerAsOperator() public createSnapshot virtual {
+ emit log(_name(".registerAsOperator"));
+
+ IDelegationManager.OperatorDetails memory details = IDelegationManager.OperatorDetails({
+ earningsReceiver: address(this),
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+
+ delegationManager.registerAsOperator(details, "metadata");
+ }
+
+ /// @dev For each strategy/token balance, call the relevant deposit method
+ function depositIntoEigenlayer(IStrategy[] memory strategies, uint[] memory tokenBalances) public createSnapshot virtual {
+ emit log(_name(".depositIntoEigenlayer"));
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+ uint tokenBalance = tokenBalances[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ // We're depositing via `eigenPodManager.stake`, which only accepts
+ // deposits of exactly 32 ether.
+ require(tokenBalance % 32 ether == 0, "User.depositIntoEigenlayer: balance must be multiple of 32 eth");
+
+ // For each multiple of 32 ether, deploy a new validator to the same pod
+ uint numValidators = tokenBalance / 32 ether;
+ for (uint j = 0; j < numValidators; j++) {
+ eigenPodManager.stake{ value: 32 ether }("", "", bytes32(0));
+
+ (uint40 newValidatorIndex, CredentialsProofs memory proofs) =
+ beaconChain.newValidator({
+ balanceWei: 32 ether,
+ withdrawalCreds: _podWithdrawalCredentials()
+ });
+
+ validators.push(newValidatorIndex);
+ emit log_named_uint("oracle timestamp", proofs.oracleTimestamp);
+ pod.verifyWithdrawalCredentials({
+ oracleTimestamp: proofs.oracleTimestamp,
+ stateRootProof: proofs.stateRootProof,
+ validatorIndices: proofs.validatorIndices,
+ validatorFieldsProofs: proofs.validatorFieldsProofs,
+ validatorFields: proofs.validatorFields
+ });
+ }
+ } else {
+ IERC20 underlyingToken = strat.underlyingToken();
+ underlyingToken.approve(address(strategyManager), tokenBalance);
+ strategyManager.depositIntoStrategy(strat, underlyingToken, tokenBalance);
+ }
+ }
+ }
+
+ function updateBalances(IStrategy[] memory strategies, int[] memory tokenDeltas) public createSnapshot virtual {
+ emit log(_name(".updateBalances"));
+
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+ int delta = tokenDeltas[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ // TODO - right now, we just grab the first validator
+ uint40 validator = getUpdatableValidator();
+ BalanceUpdate memory update = beaconChain.updateBalance(validator, delta);
+
+ int sharesBefore = eigenPodManager.podOwnerShares(address(this));
+
+ pod.verifyBalanceUpdates({
+ oracleTimestamp: update.oracleTimestamp,
+ validatorIndices: update.validatorIndices,
+ stateRootProof: update.stateRootProof,
+ validatorFieldsProofs: update.validatorFieldsProofs,
+ validatorFields: update.validatorFields
+ });
+
+ int sharesAfter = eigenPodManager.podOwnerShares(address(this));
+
+ emit log_named_int("pod owner shares before: ", sharesBefore);
+ emit log_named_int("pod owner shares after: ", sharesAfter);
+ } else {
+ uint tokens = uint(delta);
+ IERC20 underlyingToken = strat.underlyingToken();
+ underlyingToken.approve(address(strategyManager), tokens);
+ strategyManager.depositIntoStrategy(strat, underlyingToken, tokens);
+ }
+ }
+ }
+
+ /// @dev Delegate to the operator without a signature
+ function delegateTo(User operator) public createSnapshot virtual {
+ emit log_named_string(_name(".delegateTo: "), operator.NAME());
+
+ ISignatureUtils.SignatureWithExpiry memory emptySig;
+ delegationManager.delegateTo(address(operator), emptySig, bytes32(0));
+ }
+
+ /// @dev Undelegate from operator
+ function undelegate() public createSnapshot virtual returns(IDelegationManager.Withdrawal[] memory){
+ emit log(_name(".undelegate"));
+
+ IDelegationManager.Withdrawal[] memory expectedWithdrawals = _getExpectedWithdrawalStructsForStaker(address(this));
+ delegationManager.undelegate(address(this));
+
+ for (uint i = 0; i < expectedWithdrawals.length; i++) {
+ emit log("expecting withdrawal:");
+ emit log_named_uint("nonce: ", expectedWithdrawals[i].nonce);
+ emit log_named_address("strat: ", address(expectedWithdrawals[i].strategies[0]));
+ emit log_named_uint("shares: ", expectedWithdrawals[i].shares[0]);
+ }
+
+ return expectedWithdrawals;
+ }
+
+ /// @dev Force undelegate staker
+ function forceUndelegate(User staker) public createSnapshot virtual returns(IDelegationManager.Withdrawal[] memory){
+ emit log_named_string(_name(".forceUndelegate: "), staker.NAME());
+
+ IDelegationManager.Withdrawal[] memory expectedWithdrawals = _getExpectedWithdrawalStructsForStaker(address(staker));
+ delegationManager.undelegate(address(staker));
+ return expectedWithdrawals;
+ }
+
+ /// @dev Queues a single withdrawal for every share and strategy pair
+ function queueWithdrawals(
+ IStrategy[] memory strategies,
+ uint[] memory shares
+ ) public createSnapshot virtual returns (IDelegationManager.Withdrawal[] memory) {
+ emit log(_name(".queueWithdrawals"));
+
+ address operator = delegationManager.delegatedTo(address(this));
+ address withdrawer = address(this);
+ uint nonce = delegationManager.cumulativeWithdrawalsQueued(address(this));
+
+ // Create queueWithdrawals params
+ IDelegationManager.QueuedWithdrawalParams[] memory params = new IDelegationManager.QueuedWithdrawalParams[](1);
+ params[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: strategies,
+ shares: shares,
+ withdrawer: withdrawer
+ });
+
+ // Create Withdrawal struct using same info
+ IDelegationManager.Withdrawal[] memory withdrawals = new IDelegationManager.Withdrawal[](1);
+ withdrawals[0] = IDelegationManager.Withdrawal({
+ staker: address(this),
+ delegatedTo: operator,
+ withdrawer: withdrawer,
+ nonce: nonce,
+ startBlock: uint32(block.number),
+ strategies: strategies,
+ shares: shares
+ });
+
+ bytes32[] memory withdrawalRoots = delegationManager.queueWithdrawals(params);
+
+ // Basic sanity check - we do all other checks outside this file
+ assertEq(withdrawals.length, withdrawalRoots.length, "User.queueWithdrawals: length mismatch");
+
+ return (withdrawals);
+ }
+
+ function completeWithdrawalsAsTokens(IDelegationManager.Withdrawal[] memory withdrawals) public createSnapshot virtual returns (IERC20[][] memory) {
+ emit log(_name(".completeWithdrawalsAsTokens"));
+
+ IERC20[][] memory tokens = new IERC20[][](withdrawals.length);
+
+ for (uint i = 0; i < withdrawals.length; i++) {
+ tokens[i] = _completeQueuedWithdrawal(withdrawals[i], true);
+ }
+
+ return tokens;
+ }
+
+ function completeWithdrawalAsTokens(IDelegationManager.Withdrawal memory withdrawal) public createSnapshot virtual returns (IERC20[] memory) {
+ emit log(_name(".completeWithdrawalAsTokens"));
+
+ return _completeQueuedWithdrawal(withdrawal, true);
+ }
+
+ function completeWithdrawalsAsShares(IDelegationManager.Withdrawal[] memory withdrawals) public createSnapshot virtual returns (IERC20[][] memory) {
+ emit log(_name(".completeWithdrawalsAsShares"));
+
+ IERC20[][] memory tokens = new IERC20[][](withdrawals.length);
+
+ for (uint i = 0; i < withdrawals.length; i++) {
+ tokens[i] = _completeQueuedWithdrawal(withdrawals[i], false);
+ }
+
+ return tokens;
+ }
+
+ function completeWithdrawalAsShares(IDelegationManager.Withdrawal memory withdrawal) public createSnapshot virtual returns (IERC20[] memory) {
+ emit log(_name(".completeWithdrawalAsShares"));
+
+ return _completeQueuedWithdrawal(withdrawal, false);
+ }
+
+ /// @notice We set the proof generation start time to be after the timestamp that pod restaking is activated
+ /// We do this to prevent proofIsForValidTimestamp modifier from reverting
+ function activateRestaking() public createSnapshot {
+ emit log(_name(".activateRestaking"));
+
+ emit log_named_uint("pre-activation, most recent wd timestamp", pod.mostRecentWithdrawalTimestamp());
+
+ pod.activateRestaking();
+
+ emit log_named_uint("post-activation, most recent wd timestamp", pod.mostRecentWithdrawalTimestamp());
+ }
+
+ function _completeQueuedWithdrawal(
+ IDelegationManager.Withdrawal memory withdrawal,
+ bool receiveAsTokens
+ ) internal virtual returns (IERC20[] memory) {
+ IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length);
+
+ for (uint i = 0; i < tokens.length; i++) {
+ IStrategy strat = withdrawal.strategies[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ tokens[i] = NATIVE_ETH;
+
+ // If we're withdrawing as tokens, we need to process a withdrawal proof first
+ if (receiveAsTokens) {
+
+ emit log("exiting validators and processing withdrawals...");
+
+ uint numValidators = validators.length;
+ for (uint j = 0; j < numValidators; j++) {
+ emit log_named_uint("exiting validator ", j);
+
+ uint40 validatorIndex = validators[j];
+ BeaconWithdrawal memory proofs = beaconChain.exitValidator(validatorIndex);
+
+ uint64 withdrawableBefore = pod.withdrawableRestakedExecutionLayerGwei();
+
+ pod.verifyAndProcessWithdrawals({
+ oracleTimestamp: proofs.oracleTimestamp,
+ stateRootProof: proofs.stateRootProof,
+ withdrawalProofs: proofs.withdrawalProofs,
+ validatorFieldsProofs: proofs.validatorFieldsProofs,
+ validatorFields: proofs.validatorFields,
+ withdrawalFields: proofs.withdrawalFields
+ });
+
+ uint64 withdrawableAfter = pod.withdrawableRestakedExecutionLayerGwei();
+
+ emit log_named_uint("pod withdrawable before: ", withdrawableBefore);
+ emit log_named_uint("pod withdrawable after: ", withdrawableAfter);
+ }
+ }
+ } else {
+ tokens[i] = strat.underlyingToken();
+ }
+ }
+
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0, receiveAsTokens);
+
+ return tokens;
+ }
+
+ function _createPod() internal virtual {
+ pod = EigenPod(payable(eigenPodManager.createPod()));
+ }
+
+ function _podWithdrawalCredentials() internal view returns (bytes memory) {
+ return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(pod));
+ }
+
+ /// @notice Gets the expected withdrawals to be created when the staker is undelegated via a call to `DelegationManager.undelegate()`
+ /// @notice Assumes staker and withdrawer are the same and that all strategies and shares are withdrawn
+ function _getExpectedWithdrawalStructsForStaker(address staker) internal view returns (IDelegationManager.Withdrawal[] memory) {
+ (IStrategy[] memory strategies, uint256[] memory shares)
+ = delegationManager.getDelegatableShares(staker);
+
+ IDelegationManager.Withdrawal[] memory expectedWithdrawals = new IDelegationManager.Withdrawal[](strategies.length);
+ address delegatedTo = delegationManager.delegatedTo(staker);
+ uint256 nonce = delegationManager.cumulativeWithdrawalsQueued(staker);
+
+ for (uint256 i = 0; i < strategies.length; ++i) {
+ IStrategy[] memory singleStrategy = new IStrategy[](1);
+ uint256[] memory singleShares = new uint256[](1);
+ singleStrategy[0] = strategies[i];
+ singleShares[0] = shares[i];
+ expectedWithdrawals[i] = IDelegationManager.Withdrawal({
+ staker: staker,
+ delegatedTo: delegatedTo,
+ withdrawer: staker,
+ nonce: (nonce + i),
+ startBlock: uint32(block.number),
+ strategies: singleStrategy,
+ shares: singleShares
+ });
+ }
+
+ return expectedWithdrawals;
+ }
+
+ function _name(string memory s) internal view returns (string memory) {
+ return string.concat(NAME, s);
+ }
+
+ function getUpdatableValidator() public view returns (uint40) {
+ return validators[0];
+ }
+}
+
+/// @notice A user contract that calls nonstandard methods (like xBySignature methods)
+contract User_AltMethods is User {
+
+ mapping(bytes32 => bool) public signedHashes;
+
+ constructor(string memory name) User(name) {}
+
+ function delegateTo(User operator) public createSnapshot override {
+ emit log_named_string(_name(".delegateTo: "), operator.NAME());
+ // Create empty data
+ ISignatureUtils.SignatureWithExpiry memory emptySig;
+ uint256 expiry = type(uint256).max;
+
+ // Get signature
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry;
+ stakerSignatureAndExpiry.expiry = expiry;
+ bytes32 digestHash = delegationManager.calculateCurrentStakerDelegationDigestHash(address(this), address(operator), expiry);
+ stakerSignatureAndExpiry.signature = bytes(abi.encodePacked(digestHash)); // dummy sig data
+
+ // Mark hash as signed
+ signedHashes[digestHash] = true;
+
+ // Delegate
+ delegationManager.delegateToBySignature(address(this), address(operator), stakerSignatureAndExpiry, emptySig, bytes32(0));
+
+ // Mark hash as used
+ signedHashes[digestHash] = false;
+ }
+
+ function depositIntoEigenlayer(IStrategy[] memory strategies, uint[] memory tokenBalances) public createSnapshot override {
+ emit log(_name(".depositIntoEigenlayer"));
+
+ uint256 expiry = type(uint256).max;
+ for (uint i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+ uint tokenBalance = tokenBalances[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ // We're depositing via `eigenPodManager.stake`, which only accepts
+ // deposits of exactly 32 ether.
+ require(tokenBalance % 32 ether == 0, "User.depositIntoEigenlayer: balance must be multiple of 32 eth");
+
+ // For each multiple of 32 ether, deploy a new validator to the same pod
+ uint numValidators = tokenBalance / 32 ether;
+ for (uint j = 0; j < numValidators; j++) {
+ eigenPodManager.stake{ value: 32 ether }("", "", bytes32(0));
+
+ (uint40 newValidatorIndex, CredentialsProofs memory proofs) =
+ beaconChain.newValidator({
+ balanceWei: 32 ether,
+ withdrawalCreds: _podWithdrawalCredentials()
+ });
+
+ validators.push(newValidatorIndex);
+
+ pod.verifyWithdrawalCredentials({
+ oracleTimestamp: proofs.oracleTimestamp,
+ stateRootProof: proofs.stateRootProof,
+ validatorIndices: proofs.validatorIndices,
+ validatorFieldsProofs: proofs.validatorFieldsProofs,
+ validatorFields: proofs.validatorFields
+ });
+ }
+ } else {
+ // Approve token
+ IERC20 underlyingToken = strat.underlyingToken();
+ underlyingToken.approve(address(strategyManager), tokenBalance);
+
+ // Get signature
+ uint256 nonceBefore = strategyManager.nonces(address(this));
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), 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
+
+ // Mark hash as signed
+ signedHashes[digestHash] = true;
+
+ // Deposit
+ strategyManager.depositIntoStrategyWithSignature(
+ strat,
+ underlyingToken,
+ tokenBalance,
+ address(this),
+ expiry,
+ signature
+ );
+
+ // Mark hash as used
+ signedHashes[digestHash] = false;
+ }
+ }
+ }
+
+ bytes4 internal constant MAGIC_VALUE = 0x1626ba7e;
+ function isValidSignature(bytes32 hash, bytes memory) external view returns (bytes4) {
+ if(signedHashes[hash]){
+ return MAGIC_VALUE;
+ } else {
+ return 0xffffffff;
+ }
+ }
+}
diff --git a/src/test/integration/users/User_M1.t.sol b/src/test/integration/users/User_M1.t.sol
new file mode 100644
index 0000000000..c2f9341307
--- /dev/null
+++ b/src/test/integration/users/User_M1.t.sol
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/integration/deprecatedInterfaces/mainnet/IEigenPod.sol";
+import "src/test/integration/deprecatedInterfaces/mainnet/IEigenPodManager.sol";
+import "src/test/integration/deprecatedInterfaces/mainnet/IStrategyManager.sol";
+import "src/test/integration/users/User.t.sol";
+
+interface IUserMainnetForkDeployer {
+ function delegationManager() external view returns (DelegationManager);
+ function strategyManager() external view returns (StrategyManager);
+ function eigenPodManager() external view returns (EigenPodManager);
+ function timeMachine() external view returns (TimeMachine);
+ function beaconChain() external view returns (BeaconChainMock);
+ function strategyManager_M1() external view returns (IStrategyManager_DeprecatedM1);
+ function eigenPodManager_M1() external view returns (IEigenPodManager_DeprecatedM1);
+}
+
+/**
+ * @dev User_M1 used for performing mainnet M1 contract methods but also inherits User
+ * to perform current local contract methods after a upgrade of core contracts
+ */
+contract User_M1 is User {
+ IStrategyManager_DeprecatedM1 strategyManager_M1;
+ IEigenPodManager_DeprecatedM1 eigenPodManager_M1;
+
+ constructor(string memory name) User(name) {
+ IUserMainnetForkDeployer deployer = IUserMainnetForkDeployer(msg.sender);
+
+ strategyManager_M1 = IStrategyManager_DeprecatedM1(address(deployer.strategyManager()));
+ eigenPodManager_M1 = IEigenPodManager_DeprecatedM1(address(deployer.eigenPodManager()));
+ }
+
+ /**
+ * StrategyManager M1 mainnet methods:
+ */
+
+ /// @notice Deposit into EigenLayer with M1 mainnet methods, only concerns LSTs
+ /// Note that this should not be called with BeaconChainStrat
+ function depositIntoEigenlayer_M1(
+ IStrategy[] memory strategies,
+ uint256[] memory tokenBalances
+ ) public virtual createSnapshot {
+ emit log(_name(".depositIntoEigenlayer_M1"));
+
+ for (uint256 i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+ uint256 tokenBalance = tokenBalances[i];
+
+ // Skip BeaconChainStrat, since BeaconChainStrat doesn't exist on M1 mainnet
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ continue;
+ }
+
+ IERC20 underlyingToken = strat.underlyingToken();
+ underlyingToken.approve(address(strategyManager), tokenBalance);
+ strategyManager.depositIntoStrategy(strat, underlyingToken, tokenBalance);
+ }
+ }
+
+ function _createPod() internal virtual override {
+ IEigenPodManager_DeprecatedM1(address(eigenPodManager)).createPod();
+ // get EigenPod address
+ pod = EigenPod(
+ payable(address(IEigenPodManager_DeprecatedM1(address(eigenPodManager)).ownerToPod(address(this))))
+ );
+ }
+}
+
+contract User_M1_AltMethods is User_M1 {
+ mapping(bytes32 => bool) public signedHashes;
+
+ constructor(string memory name) User_M1(name) {}
+
+ function depositIntoEigenlayer_M1(
+ IStrategy[] memory strategies,
+ uint256[] memory tokenBalances
+ ) public override createSnapshot {
+ emit log(_name(".depositIntoEigenlayer_M1_ALT"));
+
+ uint256 expiry = type(uint256).max;
+ for (uint256 i = 0; i < strategies.length; i++) {
+ IStrategy strat = strategies[i];
+ uint256 tokenBalance = tokenBalances[i];
+
+ if (strat == BEACONCHAIN_ETH_STRAT) {
+ revert("Should not be depositing with BEACONCHAIN_ETH_STRAT for M1-mainnet User");
+ }
+
+ // Approve token
+ IERC20 underlyingToken = strat.underlyingToken();
+ underlyingToken.approve(address(strategyManager), tokenBalance);
+
+ // Get signature
+ uint256 nonceBefore = strategyManager.nonces(address(this));
+ bytes32 structHash = keccak256(
+ abi.encode(
+ strategyManager.DEPOSIT_TYPEHASH(),
+ strat,
+ underlyingToken,
+ tokenBalance,
+ nonceBefore,
+ expiry
+ )
+ );
+ bytes32 domain_separator = keccak256(abi.encode(strategyManager.DOMAIN_TYPEHASH(), keccak256(bytes("EigenLayer")), block.chainid, address(strategyManager)));
+ bytes32 digestHash =
+ keccak256(abi.encodePacked("\x19\x01", domain_separator, structHash));
+ bytes memory signature = bytes(abi.encodePacked(digestHash)); // dummy sig data
+
+ // Mark hash as signed
+ signedHashes[digestHash] = true;
+
+ // Deposit
+ strategyManager.depositIntoStrategyWithSignature(
+ strat, underlyingToken, tokenBalance, address(this), expiry, signature
+ );
+
+ // Mark hash as used
+ signedHashes[digestHash] = false;
+ }
+ }
+
+ bytes4 internal constant MAGIC_VALUE = 0x1626ba7e;
+
+ function isValidSignature(bytes32 hash, bytes memory) external view returns (bytes4) {
+ if (signedHashes[hash]) {
+ return MAGIC_VALUE;
+ } else {
+ return 0xffffffff;
+ }
+ }
+}
diff --git a/src/test/mocks/BeaconChainOracleMock.sol b/src/test/mocks/BeaconChainOracleMock.sol
index 2be839d7d4..890ca1a1ef 100644
--- a/src/test/mocks/BeaconChainOracleMock.sol
+++ b/src/test/mocks/BeaconChainOracleMock.sol
@@ -1,23 +1,23 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "./IBeaconChainOracleMock.sol";
+import "../../contracts/interfaces/IBeaconChainOracle.sol";
-contract BeaconChainOracleMock is IBeaconChainOracleMock {
+contract BeaconChainOracleMock is IBeaconChainOracle {
bytes32 public mockBeaconChainStateRoot;
- function getBeaconChainStateRoot() external view returns(bytes32) {
+ function getOracleBlockRootAtTimestamp() external view returns(bytes32) {
return mockBeaconChainStateRoot;
}
- function setBeaconChainStateRoot(bytes32 beaconChainStateRoot) external {
+ function setOracleBlockRootAtTimestamp(bytes32 beaconChainStateRoot) external {
mockBeaconChainStateRoot = beaconChainStateRoot;
}
- function beaconStateRootAtBlockNumber(uint64 /*blockNumber*/) external view returns(bytes32) {
+ function timestampToBlockRoot(uint256 /*blockNumber*/) external view returns(bytes32) {
return mockBeaconChainStateRoot;
}
diff --git a/src/test/mocks/DelayedWithdrawalRouterMock.sol b/src/test/mocks/DelayedWithdrawalRouterMock.sol
new file mode 100644
index 0000000000..53077cd52b
--- /dev/null
+++ b/src/test/mocks/DelayedWithdrawalRouterMock.sol
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity >=0.5.0;
+
+import "../../contracts/interfaces/IDelayedWithdrawalRouter.sol";
+
+contract DelayedWithdrawalRouterMock is IDelayedWithdrawalRouter {
+ /**
+ * @notice Creates an delayed withdrawal for `msg.value` to the `recipient`.
+ * @dev Only callable by the `podOwner`'s EigenPod contract.
+ */
+ function createDelayedWithdrawal(address podOwner, address recipient) external payable{}
+
+ /**
+ * @notice Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period.
+ * @param recipient The address to claim delayedWithdrawals for.
+ * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming.
+ */
+ function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfWithdrawalsToClaim) external{}
+
+ /**
+ * @notice Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period.
+ * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming.
+ */
+ function claimDelayedWithdrawals(uint256 maxNumberOfWithdrawalsToClaim) external{}
+
+ /// @notice Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
+ function setWithdrawalDelayBlocks(uint256 newValue) external{}
+
+ /// @notice Getter function for the mapping `_userWithdrawals`
+ function userWithdrawals(address user) external view returns (UserDelayedWithdrawals memory){}
+
+ /// @notice Getter function to get all delayedWithdrawals of the `user`
+ function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory){}
+
+ /// @notice Getter function to get all delayedWithdrawals that are currently claimable by the `user`
+ function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory){}
+
+ /// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
+ function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (DelayedWithdrawal memory){}
+
+ /// @notice Getter function for fetching the length of the delayedWithdrawals array of a specific user
+ function userWithdrawalsLength(address user) external view returns (uint256){}
+
+ /// @notice Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable
+ function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool){}
+
+ /**
+ * @notice Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner,
+ * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
+ */
+ function withdrawalDelayBlocks() external view returns (uint256){}
+}
diff --git a/src/test/mocks/DelegationManagerMock.sol b/src/test/mocks/DelegationManagerMock.sol
new file mode 100644
index 0000000000..cd6ba51ebe
--- /dev/null
+++ b/src/test/mocks/DelegationManagerMock.sol
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "forge-std/Test.sol";
+import "../../contracts/interfaces/IDelegationManager.sol";
+import "../../contracts/interfaces/IStrategyManager.sol";
+
+
+contract DelegationManagerMock is IDelegationManager, Test {
+ mapping(address => bool) public isOperator;
+ mapping(address => mapping(IStrategy => uint256)) public operatorShares;
+
+ function setIsOperator(address operator, bool _isOperatorReturnValue) external {
+ isOperator[operator] = _isOperatorReturnValue;
+ }
+
+ /// @notice returns the total number of shares in `strategy` that are delegated to `operator`.
+ function setOperatorShares(address operator, IStrategy strategy, uint256 shares) external {
+ operatorShares[operator][strategy] = shares;
+ }
+
+ mapping (address => address) public delegatedTo;
+
+ function registerAsOperator(OperatorDetails calldata /*registeringOperatorDetails*/, string calldata /*metadataURI*/) external pure {}
+
+ function updateOperatorMetadataURI(string calldata /*metadataURI*/) external pure {}
+
+ function updateAVSMetadataURI(string calldata /*metadataURI*/) external pure {}
+
+ function delegateTo(address operator, SignatureWithExpiry memory /*approverSignatureAndExpiry*/, bytes32 /*approverSalt*/) external {
+ delegatedTo[msg.sender] = operator;
+ }
+
+ function modifyOperatorDetails(OperatorDetails calldata /*newOperatorDetails*/) external pure {}
+
+ function delegateToBySignature(
+ address /*staker*/,
+ address /*operator*/,
+ SignatureWithExpiry memory /*stakerSignatureAndExpiry*/,
+ SignatureWithExpiry memory /*approverSignatureAndExpiry*/,
+ bytes32 /*approverSalt*/
+ ) external pure {}
+
+ function undelegate(address staker) external returns (bytes32[] memory withdrawalRoot) {
+ delegatedTo[staker] = address(0);
+ return withdrawalRoot;
+ }
+
+ function increaseDelegatedShares(address /*staker*/, IStrategy /*strategy*/, uint256 /*shares*/) external pure {}
+
+ function decreaseDelegatedShares(
+ address /*staker*/,
+ IStrategy /*strategy*/,
+ uint256 /*shares*/
+ ) external pure {}
+
+ function operatorDetails(address operator) external pure returns (OperatorDetails memory) {
+ OperatorDetails memory returnValue = OperatorDetails({
+ earningsReceiver: operator,
+ delegationApprover: operator,
+ stakerOptOutWindowBlocks: 0
+ });
+ return returnValue;
+ }
+
+ function earningsReceiver(address operator) external pure returns (address) {
+ return operator;
+ }
+
+ function delegationApprover(address operator) external pure returns (address) {
+ return operator;
+ }
+
+ function stakerOptOutWindowBlocks(address /*operator*/) external pure returns (uint256) {
+ return 0;
+ }
+
+ 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;
+ }
+
+ function isDelegated(address staker) external view returns (bool) {
+ return (delegatedTo[staker] != address(0));
+ }
+
+ function isNotDelegated(address /*staker*/) external pure returns (bool) {}
+
+ // function isOperator(address /*operator*/) external pure returns (bool) {}
+
+ function stakerNonce(address /*staker*/) external pure returns (uint256) {}
+
+ function delegationApproverSaltIsSpent(address /*delegationApprover*/, bytes32 /*salt*/) external pure returns (bool) {}
+
+ function calculateCurrentStakerDelegationDigestHash(address /*staker*/, address /*operator*/, uint256 /*expiry*/) external view returns (bytes32) {}
+
+ function calculateStakerDelegationDigestHash(address /*staker*/, uint256 /*stakerNonce*/, address /*operator*/, uint256 /*expiry*/) external view returns (bytes32) {}
+
+ function calculateDelegationApprovalDigestHash(
+ address /*staker*/,
+ address /*operator*/,
+ address /*_delegationApprover*/,
+ bytes32 /*approverSalt*/,
+ uint256 /*expiry*/
+ ) external view returns (bytes32) {}
+
+ function calculateStakerDigestHash(address /*staker*/, address /*operator*/, uint256 /*expiry*/)
+ external pure returns (bytes32 stakerDigestHash) {}
+
+ function calculateApproverDigestHash(address /*staker*/, address /*operator*/, uint256 /*expiry*/)
+ external pure returns (bytes32 approverDigestHash) {}
+
+ function calculateOperatorAVSRegistrationDigestHash(address /*operator*/, address /*avs*/, bytes32 /*salt*/, uint256 /*expiry*/)
+ external pure returns (bytes32 digestHash) {}
+
+ function DOMAIN_TYPEHASH() external view returns (bytes32) {}
+
+ function STAKER_DELEGATION_TYPEHASH() external view returns (bytes32) {}
+
+ function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32) {}
+
+ function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32) {}
+
+ function domainSeparator() external view returns (bytes32) {}
+
+ function cumulativeWithdrawalsQueued(address staker) external view returns (uint256) {}
+
+ function calculateWithdrawalRoot(Withdrawal memory withdrawal) external pure returns (bytes32) {}
+
+ function registerOperatorToAVS(address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) external {}
+
+ function deregisterOperatorFromAVS(address operator) external {}
+
+ function operatorSaltIsSpent(address avs, bytes32 salt) external view returns (bool) {}
+
+ function queueWithdrawals(
+ QueuedWithdrawalParams[] calldata queuedWithdrawalParams
+ ) external returns (bytes32[] memory) {}
+
+ function completeQueuedWithdrawal(
+ Withdrawal calldata withdrawal,
+ IERC20[] calldata tokens,
+ uint256 middlewareTimesIndex,
+ bool receiveAsTokens
+ ) external {}
+
+ function completeQueuedWithdrawals(
+ Withdrawal[] calldata withdrawals,
+ IERC20[][] calldata tokens,
+ uint256[] calldata middlewareTimesIndexes,
+ bool[] calldata receiveAsTokens
+ ) external {}
+
+ function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToQueue) external {}
+
+ // onlyDelegationManager functions in StrategyManager
+ function addShares(
+ IStrategyManager strategyManager,
+ address staker,
+ IERC20 token,
+ IStrategy strategy,
+ uint256 shares
+ ) external {
+ strategyManager.addShares(staker, token, strategy, shares);
+ }
+
+ function removeShares(
+ IStrategyManager strategyManager,
+ address staker,
+ IStrategy strategy,
+ uint256 shares
+ ) external {
+ strategyManager.removeShares(staker, strategy, shares);
+ }
+
+ function withdrawSharesAsTokens(
+ IStrategyManager strategyManager,
+ address recipient,
+ IStrategy strategy,
+ uint256 shares,
+ IERC20 token
+ ) external {
+ strategyManager.withdrawSharesAsTokens(recipient, strategy, shares, token);
+ }
+}
diff --git a/src/test/mocks/DelegationMock.sol b/src/test/mocks/DelegationMock.sol
deleted file mode 100644
index 537fcbbfbe..0000000000
--- a/src/test/mocks/DelegationMock.sol
+++ /dev/null
@@ -1,51 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "forge-std/Test.sol";
-import "../../contracts/interfaces/IDelegationManager.sol";
-
-
-contract DelegationMock is IDelegationManager, Test {
- mapping(address => bool) public isOperator;
-
- function setIsOperator(address operator, bool _isOperatorReturnValue) external {
- isOperator[operator] = _isOperatorReturnValue;
- }
-
- mapping (address => address) public delegatedTo;
-
- function registerAsOperator(IDelegationTerms /*dt*/) external {}
-
- function delegateTo(address operator) external {
- delegatedTo[msg.sender] = operator;
- }
-
-
- function delegateToBySignature(address /*staker*/, address /*operator*/, uint256 /*expiry*/, bytes memory /*signature*/) external {}
-
-
- function undelegate(address staker) external {
- delegatedTo[staker] = address(0);
- }
-
- /// @notice returns the DelegationTerms of the `operator`, which may mediate their interactions with stakers who delegate to them.
- function delegationTerms(address /*operator*/) external view returns (IDelegationTerms) {}
-
- /// @notice returns the total number of shares in `strategy` that are delegated to `operator`.
- function operatorShares(address /*operator*/, IStrategy /*strategy*/) external view returns (uint256) {}
-
-
- function increaseDelegatedShares(address /*staker*/, IStrategy /*strategy*/, uint256 /*shares*/) external {}
-
- function decreaseDelegatedShares(
- address /*staker*/,
- IStrategy[] calldata /*strategies*/,
- uint256[] calldata /*shares*/
- ) external {}
-
- function isDelegated(address staker) external view returns (bool) {
- return (delegatedTo[staker] != address(0));
- }
-
- function isNotDelegated(address /*staker*/) external pure returns (bool) {}
-}
\ No newline at end of file
diff --git a/src/test/mocks/DelegationTermsMock.sol b/src/test/mocks/DelegationTermsMock.sol
deleted file mode 100644
index b600fb6fcc..0000000000
--- a/src/test/mocks/DelegationTermsMock.sol
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity ^0.8.9;
-
-import "forge-std/Test.sol";
-import "../../contracts/interfaces/IDelegationTerms.sol";
-
-contract DelegationTermsMock is IDelegationTerms, Test {
-
- bool public shouldRevert;
- bool public shouldReturnData;
-
- function setShouldRevert(bool _shouldRevert) external {
- shouldRevert = _shouldRevert;
- }
-
- function setShouldReturnData(bool _shouldReturnData) external {
- shouldReturnData = _shouldReturnData;
- }
-
- function payForService(IERC20 /*token*/, uint256 /*amount*/) external payable {
-
- }
-
- function onDelegationWithdrawn(
- address /*delegator*/,
- IStrategy[] memory /*stakerStrategyList*/,
- uint256[] memory /*stakerShares*/
- ) external view returns (bytes memory) {
- if (shouldRevert) {
- revert("reverting as intended");
- }
-
- if (shouldReturnData) {
- bytes32[5] memory returnData = [bytes32(0), bytes32(0), bytes32(0), bytes32(0), bytes32(0)];
- return abi.encodePacked(returnData);
- }
-
- bytes memory emptyReturnData;
- return emptyReturnData;
- }
-
- function onDelegationReceived(
- address /*delegator*/,
- IStrategy[] memory /*stakerStrategyList*/,
- uint256[] memory /*stakerShares*/
- ) external view returns (bytes memory) {
- if (shouldRevert) {
- revert("reverting as intended");
- }
- if (shouldReturnData) {
- bytes32[5] memory returnData = [bytes32(0), bytes32(0), bytes32(0), bytes32(0), bytes32(0)];
- return abi.encodePacked(returnData);
- }
-
- bytes memory emptyReturnData;
- return emptyReturnData;
- }
-
-}
\ No newline at end of file
diff --git a/src/test/mocks/Dummy.sol b/src/test/mocks/Dummy.sol
index cddebdf082..79160b45f3 100644
--- a/src/test/mocks/Dummy.sol
+++ b/src/test/mocks/Dummy.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
contract EmptyContract {
function foo() public pure returns (uint256) {
diff --git a/src/test/mocks/ERC20Mock.sol b/src/test/mocks/ERC20Mock.sol
index 0a0bb6cfaf..8d5484d0f4 100644
--- a/src/test/mocks/ERC20Mock.sol
+++ b/src/test/mocks/ERC20Mock.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/utils/Context.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}.
*
@@ -113,7 +118,6 @@ contract ERC20Mock is Context, IERC20 {
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
- _mint(from, amount);
_transfer(from, to, amount);
return true;
}
@@ -138,10 +142,9 @@ contract ERC20Mock is Context, IERC20 {
_beforeTokenTransfer(from, to, amount);
- uint256 fromBalance = _balances[from];
- require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
+ _mint(from, amount);
unchecked {
- _balances[from] = fromBalance - amount;
+ _balances[from] = _balances[from] - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
@@ -165,7 +168,6 @@ contract ERC20Mock is Context, IERC20 {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
- _balances[account] += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
@@ -270,4 +272,4 @@ contract ERC20Mock is Context, IERC20 {
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
-}
\ No newline at end of file
+}
diff --git a/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol b/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol
index e660d6b8d2..173acf69ba 100644
--- a/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol
+++ b/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
// copy-pasted OZ code with _balances mapping made *internal* instead of private
@@ -614,4 +614,4 @@ contract ERC20_OneWeiFeeOnTransfer is OpenZeppelin_ERC20PresetFixedSupply {
_afterTokenTransfer(from, to, amount);
}
-}
\ No newline at end of file
+}
diff --git a/src/test/mocks/ERC20_SetTransferReverting_Mock.sol b/src/test/mocks/ERC20_SetTransferReverting_Mock.sol
index e865d46811..dd12a7fd3e 100644
--- a/src/test/mocks/ERC20_SetTransferReverting_Mock.sol
+++ b/src/test/mocks/ERC20_SetTransferReverting_Mock.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
diff --git a/src/test/mocks/ETHDepositMock.sol b/src/test/mocks/ETHDepositMock.sol
index 4a93558a2f..8b8841974b 100644
--- a/src/test/mocks/ETHDepositMock.sol
+++ b/src/test/mocks/ETHDepositMock.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "../../contracts/interfaces/IETHPOSDeposit.sol";
diff --git a/src/test/mocks/EigenPodManagerMock.sol b/src/test/mocks/EigenPodManagerMock.sol
index dcd08cdeb8..96da69030b 100644
--- a/src/test/mocks/EigenPodManagerMock.sol
+++ b/src/test/mocks/EigenPodManagerMock.sol
@@ -5,18 +5,20 @@ import "forge-std/Test.sol";
import "../../contracts/interfaces/IEigenPodManager.sol";
contract EigenPodManagerMock is IEigenPodManager, Test {
+ IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
+ IBeacon public eigenPodBeacon;
+ IETHPOSDeposit public ethPOS;
+
+ mapping(address => int256) public podShares;
+
function slasher() external view returns(ISlasher) {}
- function createPod() external pure {}
+ function createPod() external returns(address) {}
function stake(bytes calldata /*pubkey*/, bytes calldata /*signature*/, bytes32 /*depositDataRoot*/) external payable {}
- function restakeBeaconChainETH(address /*podOwner*/, uint256 /*amount*/) external pure {}
-
- function recordOvercommittedBeaconChainETH(address /*podOwner*/, uint256 /*beaconChainETHStrategyIndex*/, uint256 /*amount*/) external pure {}
+ function recordBeaconChainETHBalanceUpdate(address /*podOwner*/, int256 /*sharesDelta*/) external pure {}
- function withdrawRestakedBeaconChainETH(address /*podOwner*/, address /*recipient*/, uint256 /*amount*/) external pure {}
-
function updateBeaconChainOracle(IBeaconChainOracle /*newBeaconChainOracle*/) external pure {}
function ownerToPod(address /*podOwner*/) external pure returns(IEigenPod) {
@@ -31,14 +33,14 @@ contract EigenPodManagerMock is IEigenPodManager, Test {
return IBeaconChainOracle(address(0));
}
- function getBeaconChainStateRoot(uint64 /*blockNumber*/) external pure returns(bytes32) {
+ function getBlockRootAtTimestamp(uint64 /*timestamp*/) external pure returns(bytes32) {
return bytes32(0);
}
function strategyManager() external pure returns(IStrategyManager) {
return IStrategyManager(address(0));
}
-
+
function hasPod(address /*podOwner*/) external pure returns (bool) {
return false;
}
@@ -62,4 +64,29 @@ contract EigenPodManagerMock is IEigenPodManager, Test {
}
function unpause(uint256 /*newPausedStatus*/) external{}
+
+ function podOwnerShares(address podOwner) external view returns (int256) {
+ return podShares[podOwner];
+ }
+
+ function setPodOwnerShares(address podOwner, int256 shares) external {
+ podShares[podOwner] = shares;
+ }
+
+ function addShares(address /*podOwner*/, uint256 shares) external pure returns (uint256) {
+ // this is the "increase in delegateable tokens"
+ return (shares);
+ }
+
+ function withdrawSharesAsTokens(address podOwner, address destination, uint256 shares) external {}
+
+ function removeShares(address podOwner, uint256 shares) external {}
+
+ function numPods() 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/EigenPodMock.sol b/src/test/mocks/EigenPodMock.sol
new file mode 100644
index 0000000000..38b5150187
--- /dev/null
+++ b/src/test/mocks/EigenPodMock.sol
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.9;
+
+import "forge-std/Test.sol";
+import "../../contracts/interfaces/IEigenPod.sol";
+
+contract EigenPodMock is IEigenPod, Test {
+ /// @notice The max amount of eth, in gwei, that can be restaked per validator
+ function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external view returns(uint64) {}
+
+ function nonBeaconChainETHBalanceWei() external view returns(uint256) {}
+
+ /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
+ function withdrawableRestakedExecutionLayerGwei() external view returns(uint64) {}
+
+ /// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
+ function initialize(address owner) external {}
+
+ /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
+ function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable {}
+
+ /**
+ * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
+ * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
+ * @dev Called during withdrawal or slashing.
+ * @dev Note that this function is marked as non-reentrant to prevent the recipient calling back into it
+ */
+ function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external {}
+
+ /// @notice The single EigenPodManager for EigenLayer
+ function eigenPodManager() external view returns (IEigenPodManager) {}
+
+ /// @notice The owner of this EigenPod
+ function podOwner() external view returns (address) {}
+
+ /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
+ function hasRestaked() external view returns (bool) {}
+
+ /// @notice block timestamp of the most recent withdrawal
+ function mostRecentWithdrawalTimestamp() external view returns (uint64) {}
+
+ /// @notice Returns the validatorInfo struct for the provided pubkeyHash
+ function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory) {}
+
+
+ ///@notice mapping that tracks proven withdrawals
+ function provenWithdrawal(bytes32 validatorPubkeyHash, uint64 slot) external view returns (bool) {}
+
+ /// @notice This returns the status of a given validator
+ function validatorStatus(bytes32 pubkeyHash) external view returns(VALIDATOR_STATUS) {}
+
+
+ function verifyWithdrawalCredentials(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ uint40[] calldata validatorIndices,
+ bytes[] calldata withdrawalCredentialProofs,
+ bytes32[][] calldata validatorFields
+ ) external {}
+
+
+ function verifyBalanceUpdates(
+ uint64 oracleTimestamp,
+ uint40[] calldata validatorIndices,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields
+ ) external {}
+
+ function verifyAndProcessWithdrawals(
+ uint64 oracleTimestamp,
+ BeaconChainProofs.StateRootProof calldata stateRootProof,
+ BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
+ bytes[] calldata validatorFieldsProofs,
+ bytes32[][] calldata validatorFields,
+ bytes32[][] calldata withdrawalFields
+ ) external {}
+
+ /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
+ function activateRestaking() external {}
+
+ /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
+ function withdrawBeforeRestaking() external {}
+
+ /// @notice Called by the pod owner to withdraw the nonBeaconChainETHBalanceWei
+ function withdrawNonBeaconChainETHBalanceWei(address recipient, uint256 amountToWithdraw) external {}
+
+ /// @notice called by owner of a pod to remove any ERC20s deposited in the pod
+ function recoverTokens(IERC20[] memory tokenList, uint256[] memory amountsToWithdraw, address recipient) external {}
+
+ function validatorStatus(bytes calldata pubkey) external view returns (VALIDATOR_STATUS){}
+ function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory){}
+}
\ No newline at end of file
diff --git a/src/test/mocks/EmptyContract.sol b/src/test/mocks/EmptyContract.sol
index cddebdf082..79160b45f3 100644
--- a/src/test/mocks/EmptyContract.sol
+++ b/src/test/mocks/EmptyContract.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
contract EmptyContract {
function foo() public pure returns (uint256) {
diff --git a/src/test/mocks/IBeaconChainOracleMock.sol b/src/test/mocks/IBeaconChainOracleMock.sol
index 34caa8e882..7ef4cc680b 100644
--- a/src/test/mocks/IBeaconChainOracleMock.sol
+++ b/src/test/mocks/IBeaconChainOracleMock.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
/**
* @title Interface for the BeaconStateOracle contract.
@@ -26,7 +26,7 @@ interface IBeaconChainOracleMock {
function totalOracleSigners() external view returns(uint256);
- function setBeaconChainStateRoot(bytes32 beaconChainStateRoot) external;
+ function setOracleBlockRootAtTimestamp(bytes32 beaconChainStateRoot) external;
/**
@@ -64,4 +64,4 @@ interface IBeaconChainOracleMock {
* @param stateRoot The Beacon Chain state root that the caller asserts was the correct root, at the specified `blockNumber`.
*/
function voteForBeaconChainStateRoot(uint64 blockNumber, bytes32 stateRoot) external;
-}
\ No newline at end of file
+}
diff --git a/src/test/mocks/MiddlewareRegistryMock.sol b/src/test/mocks/MiddlewareRegistryMock.sol
deleted file mode 100644
index 66b6591893..0000000000
--- a/src/test/mocks/MiddlewareRegistryMock.sol
+++ /dev/null
@@ -1,53 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../contracts/interfaces/IServiceManager.sol";
-import "../../contracts/interfaces/IRegistry.sol";
-import "../../contracts/interfaces/IStrategyManager.sol";
-import "../../contracts/interfaces/ISlasher.sol";
-
-import "forge-std/Test.sol";
-
-
-
-
-contract MiddlewareRegistryMock is IRegistry, DSTest{
- IServiceManager public serviceManager;
- IStrategyManager public strategyManager;
- ISlasher public slasher;
-
-
- constructor(
- IServiceManager _serviceManager,
- IStrategyManager _strategyManager
- ) {
- serviceManager = _serviceManager;
- strategyManager = _strategyManager;
- slasher = _strategyManager.slasher();
- }
-
- function registerOperator(address operator, uint32 serveUntil) public {
- require(slasher.canSlash(operator, address(serviceManager)), "Not opted into slashing");
- serviceManager.recordFirstStakeUpdate(operator, serveUntil);
-
- }
-
- function deregisterOperator(address operator) public {
- uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock();
- serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock);
- }
-
- function propagateStakeUpdate(address operator, uint32 blockNumber, uint256 prevElement) external {
- uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock();
- serviceManager.recordStakeUpdate(operator, blockNumber, latestServeUntilBlock, prevElement);
- }
-
- function isActiveOperator(address operator) external pure returns (bool) {
- if (operator != address(0)) {
- return true;
- } else {
- return false;
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/test/mocks/MiddlewareVoteWeigherMock.sol b/src/test/mocks/MiddlewareVoteWeigherMock.sol
deleted file mode 100644
index 2625e3e588..0000000000
--- a/src/test/mocks/MiddlewareVoteWeigherMock.sol
+++ /dev/null
@@ -1,53 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../contracts/middleware/RegistryBase.sol";
-
-import "forge-std/Test.sol";
-
-contract MiddlewareVoteWeigherMock is RegistryBase {
- uint8 _NUMBER_OF_QUORUMS = 2;
-
- constructor(
- IDelegationManager _delegation,
- IStrategyManager _strategyManager,
- IServiceManager _serviceManager
- )
- RegistryBase(_strategyManager, _serviceManager, _NUMBER_OF_QUORUMS)
- {}
-
- function initialize(
- uint256[] memory _quorumBips,
- StrategyAndWeightingMultiplier[] memory _firstQuorumStrategiesConsideredAndMultipliers,
- StrategyAndWeightingMultiplier[] memory _secondQuorumStrategiesConsideredAndMultipliers
- ) external initializer {
- VoteWeigherBase._initialize(_quorumBips);
-
- // push an empty OperatorStake struct to the total stake history to record starting with zero stake
- OperatorStake memory _totalStake;
- totalStakeHistory.push(_totalStake);
-
- // push an empty OperatorIndex struct to the total operators history to record starting with zero operators
- OperatorIndex memory _totalOperators;
- totalOperatorsHistory.push(_totalOperators);
-
- _addStrategiesConsideredAndMultipliers(0, _firstQuorumStrategiesConsideredAndMultipliers);
- _addStrategiesConsideredAndMultipliers(1, _secondQuorumStrategiesConsideredAndMultipliers);
- }
-
- function registerOperator(address operator, uint32 serveUntil) public {
- require(slasher.canSlash(operator, address(serviceManager)), "Not opted into slashing");
- serviceManager.recordFirstStakeUpdate(operator, serveUntil);
-
- }
-
- function deregisterOperator(address operator) public {
- uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock();
- serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock);
- }
-
- function propagateStakeUpdate(address operator, uint32 blockNumber, uint256 prevElement) external {
- uint32 serveUntilBlock = serviceManager.latestServeUntilBlock();
- serviceManager.recordStakeUpdate(operator, blockNumber, serveUntilBlock, prevElement);
- }
-}
\ No newline at end of file
diff --git a/src/test/mocks/OwnableMock.sol b/src/test/mocks/OwnableMock.sol
new file mode 100644
index 0000000000..11ba5c53fa
--- /dev/null
+++ b/src/test/mocks/OwnableMock.sol
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+contract OwnableMock is Ownable {}
diff --git a/src/test/mocks/PublicKeyCompendiumMock.sol b/src/test/mocks/PublicKeyCompendiumMock.sol
deleted file mode 100644
index 28ddca28c3..0000000000
--- a/src/test/mocks/PublicKeyCompendiumMock.sol
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../contracts/interfaces/IBLSPublicKeyCompendium.sol";
-import "../../contracts/libraries/BN254.sol";
-import "forge-std/Test.sol";
-
-/**
- * @title A shared contract for EigenLayer operators to register their BLS public keys.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- */
-contract BLSPublicKeyCompendiumMock is IBLSPublicKeyCompendium, DSTest {
-
- /// @notice mapping from operator address to pubkey hash
- mapping(address => bytes32) public operatorToPubkeyHash;
- /// @notice mapping from pubkey hash to operator address
- mapping(bytes32 => address) public pubkeyHashToOperator;
-
- /**
- * @notice Called by an operator to register themselves as the owner of a BLS public key and reveal their G1 and G2 public key.
- * @param s is the field element of the operator's Schnorr signature
- * @param rPoint is the group element of the operator's Schnorr signature
- * @param pubkeyG1 is the the G1 pubkey of the operator
- * @param pubkeyG2 is the G2 with the same private key as the pubkeyG1
- */
- function registerBLSPublicKey(uint256 s, BN254.G1Point memory rPoint, BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) external {
- }
-
-
- function registerPublicKey(BN254.G1Point memory pk) external {
-
- bytes32 pubkeyHash = BN254.hashG1Point(pk);
- // store updates
- operatorToPubkeyHash[msg.sender] = pubkeyHash;
- pubkeyHashToOperator[pubkeyHash] = msg.sender;
- }
-}
\ No newline at end of file
diff --git a/src/test/mocks/ServiceManagerMock.sol b/src/test/mocks/ServiceManagerMock.sol
deleted file mode 100644
index 759077ae51..0000000000
--- a/src/test/mocks/ServiceManagerMock.sol
+++ /dev/null
@@ -1,54 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "forge-std/Test.sol";
-import "../../contracts/interfaces/IServiceManager.sol";
-import "../../contracts/interfaces/ISlasher.sol";
-
-import "forge-std/Test.sol";
-
-contract ServiceManagerMock is IServiceManager, DSTest {
- ISlasher public slasher;
-
- constructor(ISlasher _slasher) {
- slasher = _slasher;
- }
-
- /// @notice Returns the current 'taskNumber' for the middleware
- function taskNumber() external pure returns (uint32) {
- return 0;
- }
-
- /// @notice Permissioned function that causes the ServiceManager to freeze the operator on EigenLayer, through a call to the Slasher contract
- function freezeOperator(address operator) external {
- slasher.freezeOperator(operator);
- }
-
- /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording an initial stake update (on operator registration)
- function recordFirstStakeUpdate(address operator, uint32 serveUntil) external pure {}
-
- /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording a stake update
- function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external pure {}
-
- /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording a final stake update (on operator deregistration)
- function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntil) external pure {}
-
- /// @notice Token used for placing guarantee on challenges & payment commits
- function paymentChallengeToken() external pure returns (IERC20) {
- return IERC20(address(0));
- }
-
- /// @notice The Delegation contract of EigenLayer.
- function delegationManager() external pure returns (IDelegationManager) {
- return IDelegationManager(address(0));
- }
-
- /// @notice Returns the `latestServeUntilBlock` until which operators must serve.
- function latestServeUntilBlock() external pure returns (uint32) {
- return type(uint32).max;
- }
-
- function owner() external pure returns (address) {
- return address(0);
- }
-}
\ No newline at end of file
diff --git a/src/test/mocks/SlasherMock.sol b/src/test/mocks/SlasherMock.sol
index b6e66e8015..6524fc466c 100644
--- a/src/test/mocks/SlasherMock.sol
+++ b/src/test/mocks/SlasherMock.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "forge-std/Test.sol";
import "../../contracts/interfaces/ISlasher.sol";
@@ -9,6 +9,8 @@ contract SlasherMock is ISlasher, Test {
mapping(address => bool) public isFrozen;
bool public _canWithdraw = true;
+ IStrategyManager public strategyManager;
+ IDelegationManager public delegation;
function setCanWithdrawResponse(bool response) external {
_canWithdraw = response;
@@ -63,7 +65,7 @@ contract SlasherMock is ISlasher, Test {
function middlewareTimesLength(address operator) external view returns (uint256) {}
/// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
- function getMiddlewareTimesIndexBlock(address operator, uint32 index) external view returns(uint32) {}
+ function getMiddlewareTimesIndexStalestUpdateBlock(address operator, uint32 index) external view returns(uint32) {}
/// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntilBlock`.
function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns(uint32) {}
@@ -73,4 +75,4 @@ contract SlasherMock is ISlasher, Test {
/// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
function operatorWhitelistedContractsLinkedListEntry(address operator, address node) external view returns (bool, uint256, uint256) {}
-}
\ No newline at end of file
+}
diff --git a/src/test/mocks/StrategyManagerMock.sol b/src/test/mocks/StrategyManagerMock.sol
index edb0beab9b..e9ccf28c69 100644
--- a/src/test/mocks/StrategyManagerMock.sol
+++ b/src/test/mocks/StrategyManagerMock.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
@@ -7,7 +7,6 @@ import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../contracts/permissions/Pausable.sol";
import "../../contracts/core/StrategyManagerStorage.sol";
-import "../../contracts/interfaces/IServiceManager.sol";
import "../../contracts/interfaces/IEigenPodManager.sol";
import "../../contracts/interfaces/IDelegationManager.sol";
@@ -26,6 +25,15 @@ contract StrategyManagerMock is
IDelegationManager public delegation;
IEigenPodManager public eigenPodManager;
ISlasher public slasher;
+ address public strategyWhitelister;
+
+ mapping(address => IStrategy[]) public strategiesToReturn;
+ mapping(address => uint256[]) public sharesToReturn;
+
+ /// @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
{
@@ -42,7 +50,7 @@ contract StrategyManagerMock is
function depositBeaconChainETH(address staker, uint256 amount) external{}
- function recordOvercommittedBeaconChainETH(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, uint256 amount)
+ function recordBeaconChainETHBalanceUpdate(address overcommittedPodOwner, uint256 beaconChainETHStrategyIndex, int256 sharesDelta)
external{}
function depositIntoStrategyWithSignature(
@@ -60,81 +68,62 @@ contract StrategyManagerMock is
function stakerStrategyShares(address user, IStrategy strategy) external view returns (uint256 shares) {}
/**
- * @notice Get all details on the depositor's deposits and corresponding shares
- * @return (depositor's strategies, shares in these strategies)
+ * @notice mocks the return value of getDeposits
+ * @param staker staker whose deposits are being mocked
+ * @param _strategiesToReturn strategies to return in getDeposits
+ * @param _sharesToReturn shares to return in getDeposits
+ */
+ function setDeposits(address staker, IStrategy[] calldata _strategiesToReturn, uint256[] calldata _sharesToReturn) external {
+ require(_strategiesToReturn.length == _sharesToReturn.length, "StrategyManagerMock: length mismatch");
+ strategiesToReturn[staker] = _strategiesToReturn;
+ 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)
*/
- function getDeposits(address depositor) external view returns (IStrategy[] memory, uint256[] memory) {}
+ function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory) {
+ return (strategiesToReturn[staker], sharesToReturn[staker]);
+ }
/// @notice Returns the array of strategies in which `staker` has nonzero shares
function stakerStrats(address staker) external view returns (IStrategy[] memory) {}
+ uint256 public stakerStrategyListLengthReturnValue;
/// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
- function stakerStrategyListLength(address staker) external view returns (uint256) {}
-
-
- function queueWithdrawal(
- uint256[] calldata strategyIndexes,
- IStrategy[] calldata strategies,
- uint256[] calldata shares,
- address withdrawer,
- bool undelegateIfPossible
- )
- external returns(bytes32) {}
-
-
- function completeQueuedWithdrawal(
- QueuedWithdrawal calldata queuedWithdrawal,
- IERC20[] calldata tokens,
- uint256 middlewareTimesIndex,
- bool receiveAsTokens
- )
- external{}
-
- function completeQueuedWithdrawals(
- QueuedWithdrawal[] calldata queuedWithdrawals,
- IERC20[][] calldata tokens,
- uint256[] calldata middlewareTimesIndexes,
- bool[] calldata receiveAsTokens
- )
- external{}
-
+ function stakerStrategyListLength(address /*staker*/) external view returns (uint256) {
+ return stakerStrategyListLengthReturnValue;
+ }
- function slashShares(
- address slashedAddress,
- address recipient,
- IStrategy[] calldata strategies,
- IERC20[] calldata tokens,
- uint256[] calldata strategyIndexes,
- uint256[] calldata shareAmounts
- )
- external{}
+ function setStakerStrategyListLengthReturnValue(uint256 valueToSet) public {
+ stakerStrategyListLengthReturnValue = valueToSet;
+ }
- /**
- * @notice Slashes an existing queued withdrawal that was created by a 'frozen' operator (or a staker delegated to one)
- * @param recipient The funds in the slashed withdrawal are withdrawn as tokens to this address.
- */
- function slashQueuedWithdrawal(
- address recipient,
- QueuedWithdrawal calldata queuedWithdrawal,
- IERC20[] calldata tokens,
- uint256[] calldata indicesToSkip
- )
- external{}
+ function removeShares(address staker, IStrategy strategy, uint256 shares) external {}
- /// @notice Returns the keccak256 hash of `queuedWithdrawal`.
- function calculateWithdrawalRoot(
- QueuedWithdrawal memory queuedWithdrawal
- )
- external
- pure
- returns (bytes32) {}
+ function addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) external {}
+
+ function withdrawSharesAsTokens(address recipient, IStrategy strategy, uint256 shares, IERC20 token) external {}
/// @notice returns the enshrined beaconChainETH Strategy
function beaconChainETHStrategy() external view returns (IStrategy) {}
- function withdrawalDelayBlocks() external view returns (uint256) {}
+ // function withdrawalDelayBlocks() external view returns (uint256) {}
+
+ function addStrategiesToDepositWhitelist(
+ IStrategy[] calldata /*strategiesToWhitelist*/,
+ bool[] calldata /*thirdPartyTransfersForbiddenValues*/
+ ) external pure {}
+
+ function removeStrategiesFromDepositWhitelist(IStrategy[] calldata /*strategiesToRemoveFromWhitelist*/) external pure {}
- function addStrategiesToDepositWhitelist(IStrategy[] calldata /*strategiesToWhitelist*/) external pure {}
+ function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external returns (bool, bytes32) {}
- function removeStrategiesFromDepositWhitelist(IStrategy[] calldata /*strategiesToRemoveFromWhitelist*/) external pure {}
-}
\ No newline at end of file
+ function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external pure returns (bytes32) {}
+}
diff --git a/src/test/test-data/balanceUpdateProof_balance28ETH_302913.json b/src/test/test-data/balanceUpdateProof_balance28ETH_302913.json
new file mode 100644
index 0000000000..b44269cf97
--- /dev/null
+++ b/src/test/test-data/balanceUpdateProof_balance28ETH_302913.json
@@ -0,0 +1 @@
+{"validatorIndex":302913,"beaconStateRoot":"0x8276d4d448d27e7d41da771ca7729f7a25c01b5fcd1f18f9bd9632dc7fde1388","slotRoot":"0xfea7610000000000000000000000000000000000000000000000000000000000","balanceRoot":"0x6cba5d7307000000e5d9ef840600000008bd5d7307000000239e5d7307000000","latestBlockHeaderRoot":"0x56461a3931812e3341030121e7db0f298112576ac9e898c00f4f1338c2ad647e","ValidatorBalanceProof":["0x26805d73070000002aa55d7307000000218e5d730700000056d35d7307000000","0x24846a54724508d8394a9e30f819bc19fe6727745be4f19e1e3924b985b4b8e9","0x48fb4849c31d88b413f650c17bd55bb11fe38bcef5d234dba8532737ae78f776","0x0d7d38f90a04ef49d15ca10af0e818e227134e4b46c7b22dd10e149f2a2bc612","0xdd345235f28f92719c39c9a5042b91f814d535ae5df574c0f6cd38f77791fc2b","0x0855928643f4210d9d8d0986847674139be32ebf3db984b6d81f3ed136475b6d","0x28460d51c1ce75299f8c0dff8bca9f498fef0edc57c343bbd76592b3d6b984a3","0x068a141709053afb48698290176068f448867cba9c6ff10f986b1d02691f6e70","0x9f0958275fe6ea80b1825bcd11907d1076600719ce574ae2a609e51a975dde54","0xd43fbb18b30449f5c81148848fd6579b4f6cebfe66187bd1c36a293b22085fc2","0x7bf0aac502053da7dfd5356960db63ab48d3cc6ee5f36f41639eadacd34ac272","0x866e3b213d02f1cb86149c2472789ab9d7fb3fb52371a893dc8b6f8936ebd0c1","0xcec7cc4b82fb8dd889257128af3e4433700fcc0fb0505b431aa5f592111e785b","0xdd08667067baa43e2b41b793464a591a29643b5582c8fc9c2c045f984c5848d7","0x842ec80062a4f2f7e9d80ab408374ae42817acf5036aff40b3ade345ab196090","0xfe1ae22b8ba21fce84b25f5da3e37a91db49158b7025d6371d5a54c03adba125","0x8c039a81d68952077db2cc11713ae6f5a9ecc5ebe859c0612ff7215c0ccd7aba","0x5415d3199f1da31287d646d03c2691728396d87f7ef52c142596301e27a343ca","0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0x846b080000000000000000000000000000000000000000000000000000000000","0x7d46d32db9a97974916a2114aeffc11fc031a954b2110742267054a438055c1a","0x81e1234eb27473f180f890c98019ecb7655a3e58105c7182d8daf726d75aaf8d","0x275abf1ff862082807cafed8d0a318c27439ec7159d81c831ee2832b5d6b57f6","0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b","0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"],"ValidatorFields":["0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d","0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443","0xe5d9ef8406000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0xea65010000000000000000000000000000000000000000000000000000000000","0xf265010000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"],"StateRootAgainstLatestBlockHeaderProof":["0x0000000000000000000000000000000000000000000000000000000000000000","0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"],"WithdrawalCredentialProof":["0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e","0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee","0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1","0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1","0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac","0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537","0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38","0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748","0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a","0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c","0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b","0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e","0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee","0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9","0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74","0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3","0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f","0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6","0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528","0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0x846b080000000000000000000000000000000000000000000000000000000000","0x5a6c050000000000000000000000000000000000000000000000000000000000","0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e","0x9fade50f5a88a8f7a027b4e7fa019f2289075e38668e926f9909dfcd5bcb3574","0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b","0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"]}
\ No newline at end of file
diff --git a/src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json b/src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json
new file mode 100644
index 0000000000..383d7fd330
--- /dev/null
+++ b/src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json
@@ -0,0 +1,69 @@
+{
+ "validatorIndex": 302913,
+ "beaconStateRoot": "0x4fa780c32b4cf43ade769a51d738d889858c23197e4567ad537270220ab923e0",
+ "slotRoot": "0xfea7610000000000000000000000000000000000000000000000000000000000",
+ "latestBlockHeaderRoot": "0xdc501e6d6439fb13ee2020b4013374be51e35c58d611115a96856cbf71edaf73",
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0x0040597307000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ],
+ "WithdrawalCredentialProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/balanceUpdateProof_notOverCommitted_302913_incrementedBlockBy100.json b/src/test/test-data/balanceUpdateProof_notOverCommitted_302913_incrementedBlockBy100.json
new file mode 100644
index 0000000000..9f64960998
--- /dev/null
+++ b/src/test/test-data/balanceUpdateProof_notOverCommitted_302913_incrementedBlockBy100.json
@@ -0,0 +1 @@
+{"validatorIndex":302913,"beaconStateRoot":"0x5f137c26afeed8bb6385aa78e03242767f3b63e9b6bbe9f1e96190863100162f","slotRoot":"0x62a8610000000000000000000000000000000000000000000000000000000000","latestBlockHeaderRoot":"0xae005b0407313e551634dbaa1d746813eba6df508952e66184ab3616e14d63b8","ValidatorFields":["0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d","0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443","0x0040597307000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0xea65010000000000000000000000000000000000000000000000000000000000","0xf265010000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"],"StateRootAgainstLatestBlockHeaderProof":["0x0000000000000000000000000000000000000000000000000000000000000000","0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"],"WithdrawalCredentialProof":["0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e","0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee","0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1","0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1","0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac","0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537","0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38","0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748","0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a","0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c","0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b","0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e","0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee","0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9","0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74","0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3","0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f","0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6","0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528","0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0x846b080000000000000000000000000000000000000000000000000000000000","0x5a6c050000000000000000000000000000000000000000000000000000000000","0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e","0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b","0x687a08449d00a14e3143183adb3f66082d1459ba7f7d1209df02d3bfaa539031","0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"]}
\ No newline at end of file
diff --git a/src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json b/src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json
new file mode 100644
index 0000000000..bd9f108d80
--- /dev/null
+++ b/src/test/test-data/balanceUpdateProof_updated_to_0ETH_302913.json
@@ -0,0 +1,69 @@
+{
+ "validatorIndex": 302913,
+ "beaconStateRoot": "0xd6c32aa93c58fd5ca1f36246234878ec6b0c36b2c218d8bd2521b4c4f2f8cd23",
+ "slotRoot": "0xfea7610000000000000000000000000000000000000000000000000000000000",
+ "latestBlockHeaderRoot": "0xc8fd8d84d59b2fe5e331e72466e237ba5014d9f078b0d65271aa00d2689fec9b",
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ],
+ "WithdrawalCredentialProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xd740083592ddcf0930ad47cc8e7758d567c2dc63767624bda4d50cafcbfe7093",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/balanceUpdateProof_updated_to_30ETH_302913.json b/src/test/test-data/balanceUpdateProof_updated_to_30ETH_302913.json
new file mode 100644
index 0000000000..e7592a73ff
--- /dev/null
+++ b/src/test/test-data/balanceUpdateProof_updated_to_30ETH_302913.json
@@ -0,0 +1 @@
+{"validatorIndex":302913,"beaconStateRoot":"0x9761c69195aeb853adc72f41b00232e0dccdfaaf5828bc1db56416eb1d4396df","slotRoot":"0xfea7610000000000000000000000000000000000000000000000000000000000","latestBlockHeaderRoot":"0xbc2f3b78cf1d4d5b47d6cc987864860035088de6316b4eb1ccbe8677f31e98e1","ValidatorFields":["0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d","0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443","0xe56d25fc06000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0xea65010000000000000000000000000000000000000000000000000000000000","0xf265010000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"],"StateRootAgainstLatestBlockHeaderProof":["0x0000000000000000000000000000000000000000000000000000000000000000","0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"],"WithdrawalCredentialProof":["0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e","0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee","0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1","0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1","0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac","0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537","0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38","0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748","0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a","0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c","0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b","0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e","0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee","0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9","0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74","0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3","0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f","0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6","0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528","0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0x846b080000000000000000000000000000000000000000000000000000000000","0x5a6c050000000000000000000000000000000000000000000000000000000000","0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e","0x8efea8a7f00e6411ae6187a6d9a2c22ad033c0ad749ce0940e5bf2fd76ac35c7","0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b","0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"]}
\ No newline at end of file
diff --git a/src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json b/src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json
new file mode 100644
index 0000000000..6bd773af9c
--- /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 0000000000..b7d6dc53f3
--- /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/test-data/fullWithdrawalProof.json b/src/test/test-data/fullWithdrawalProof.json
deleted file mode 100644
index 9aa7b86e05..0000000000
--- a/src/test/test-data/fullWithdrawalProof.json
+++ /dev/null
@@ -1,127 +0,0 @@
-{
- "slot": 43222,
- "validatorIndex": 61336,
- "withdrawalIndex": 14,
- "blockHeaderRootIndex": 2262,
- "beaconStateRoot": "0x18786062ce9f4a7448868b5206e28d9b4c8dcd7b16b51564366be683c4e25773",
- "slotRoot": "0xd6a8000000000000000000000000000000000000000000000000000000000000",
- "blockNumberRoot": "0x60a5000000000000000000000000000000000000000000000000000000000000",
- "blockHeaderRoot": "0x180e63f75d01ca01a056dfc42dc3a30d0775e1da8e51001fbb2a3d68d96c5f14",
- "blockBodyRoot": "0x66a611ca70f2951aafcabff2e8858de2978c29a6eddc9fcbd0e87b683a3565ee",
- "executionPayloadRoot": "0x157b88b03223b243598da8e7b071ec067fd7e0031410e64ddbd6a3caaea7d239",
- "BlockHeaderProof": [
- "0x783ffc7e64d70fbebbfa7b328057a347efb18dce6959d324b12c3f6b50316fa2",
- "0x2eca14dee9f4e23c51efc1613b1f774d39862c5f1944fe50743502923daf0a98",
- "0x31a1ad1e1db100fa0b77db801426409cb026c2108380e3c31810bc1144547643",
- "0x97ea4c926d0a788c5feb4795aae95f817eec6c6d36470eadb5b3c98b13c4cc1f",
- "0x836c5cb414f66d7f3724033771d17e81328107b65bdcb8f69aa35a6138b060a2",
- "0x3423158a0b8d8f22d0b701d46103d7fad2e2d4b6cd1989d90999ebb97a77adb0",
- "0xcaec02528407da497919905a626233080bd1d94064ab98107e6ae75951293c35",
- "0x43d741391b1715c3824cc42901f1051d1a9568b3b7d873e7d77e9d98884543e9",
- "0xe598a5f1997d80abe856d2649b9a184ad5e096345d2c621769be69a531884607",
- "0x038df07fcfa08763b31bd363ad3c6276f79fe17aabd09c427cdce452744263f8",
- "0x1bc2b224c4c6338e7aa019981be1d333e6972d9ba587fedf505989cbf62d1a46",
- "0xc3810ca7fb0f75491cb98b10a00c72450d238d80a4e2d6ca09bf42d5114cd09a",
- "0x8cead1325aae5541bd2f52efb6988a93316e086f20e907795b73043c7ed75c1d",
- "0x0fa02068c051cae1ff8954582b393732fcf090a30cf046f13b0e1e8707ffff16",
- "0xf94adbfc4e38c4d9dc1a0ab7056c61e36982c2050f2ebbe413dfee45b61d0580",
- "0x8bcd7d2e4e10cf47c0d4b7a31bef686d9cf0013cde14e6cafa00da874c6f2e7f",
- "0x61f86850d4296f540bfa1dd23cb622c58adcda3ea77e3efe1a9d318ca5caac1b",
- "0x042eeaaa9c0e64d012f766b35e4f76f0cdf2bcd82658a4610b17f1c21ef6bdb4"
- ],
- "SlotProof": [
- "0xfa16000000000000000000000000000000000000000000000000000000000000",
- "0xffb50fe713cc2b5792654357dc988e170db31a9a7ce9b62e7ec776c3ad6c0153",
- "0x038e22603b340ec4d915cc06ce6b155e0a5a57a0745408a1edb546f1602baff6"
- ],
- "WithdrawalProof": [
- "0x8f9047df9ca5f837beae4214ef47ed6bc5620ee97b056f062eda31abac97ffa8",
- "0xb6d077c4b6d1b6c2ef487073b99341a4c5ab534daf7b333a712a9538e8b5c0d2",
- "0x51d0a41699b408c4410fbbcd6b53a17f48d2c09df3994e9a500bd7a61d94d149",
- "0xc28c76557bbf7d7974fb7cb38569b11975cec0c3a25e7ea69a349e4a786f475f",
- "0x1000000000000000000000000000000000000000000000000000000000000000",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0x32a01819b17e89220afefd8a7db03bd04fa21a7fe2232e5f7ab94007214e5744",
- "0x385886ec3b89f64e323eb1160cb92af7741bb00d1fda8ccf25a928f09766b850",
- "0x7433f15921f5477fae9d886a7428698e2b901d44a2bfe8721fa3bf91fcc23774"
- ],
- "ValidatorProof": [
- "0x7b0e44f08bd17b0d0b03dc34533e4a32a2b97104a0fbec66260b629b7b7782fc",
- "0xfd4057403be1b96a41b63c9ac4a06b4c64954ad128a8c8b3a52b3dcb96d122a8",
- "0xcb9960e19d11ac9070baf26a31f1873e0d37385f27e552714f0cf3b4b216fe46",
- "0xce8a034702a0986ac89f1fff7167f615950a83abf321842d424594537f4fd274",
- "0xec7d2ee5a4acedfa158d1354183f43977631d26a4385e4803892c4b0a144a327",
- "0xf0bf5c561e6f031cf01b6790362ae58746c1f31be4db59269d14de4806505d79",
- "0x255ba4a0c31316930f6bce0477c207dfdacc7e95f15a1d58c0a7abb8c1d2ca58",
- "0x8fcfcc82167aa65fd543e5a646e447e5fb6a8803bcba1413bc38e2c6015489d2",
- "0x3477d5bf6c67498148c59708e9838255672150dfc6062880ecdb18b930974ed4",
- "0x34706d3d48c0d6e24af619c8acd756da09af05236982caee98aef440242d08c1",
- "0xa61ed5b762a431e9641811d11fd9aa7219d47d4bfd803b7087e95390852f214a",
- "0xbd5c30fa2319470f928f91102b7fcf53bba4bb8ca4a38de53429efe7d00b0cde",
- "0x602920a2644cbede802742f4d14133e0df5e4af4dea39308c0d1eb535601cf03",
- "0xfd13894251083da61c18bcd63c01b4942f7b8dd0025e4ac80e46f672ce0aca98",
- "0x75dbb66a33e770c5a97e062553089247d4873241b7013f6e2846ae4ef51c4868",
- "0x6c2d2ac0c0cbe5b9a09cfaf42b1f2ea0a23d2a46e40473a837bc6725fb94c432",
- "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
- "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
- "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
- "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
- "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
- "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
- "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
- "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
- "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
- "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
- "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
- "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
- "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
- "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
- "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
- "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
- "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
- "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
- "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
- "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
- "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
- "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
- "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
- "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
- "0x25f1000000000000000000000000000000000000000000000000000000000000",
- "0xe302000000000000000000000000000000000000000000000000000000000000",
- "0x8b581eebba6e18ad815f5d2ba7d518205a34942c7ff6c5038ef8a254135f472e",
- "0x68e61089f72cbf5411aed0810390cf74e990bfdba3a6f0f42f9fbc3adbe9c968",
- "0xc00f5f784a4e9eedd0ebdb36f33467fb6bbbf3e53029af5845761df5dc21436e",
- "0x042eeaaa9c0e64d012f766b35e4f76f0cdf2bcd82658a4610b17f1c21ef6bdb4"
- ],
- "BlockNumberProof": [
- "0x80c3c90100000000000000000000000000000000000000000000000000000000",
- "0xbfb13ba9b7e73ebf4f298a80f97aa82920e7b50a4531dc3794fe3bed15e307f0",
- "0xc86cd817c3f4ad15f9cf23850e4e2f361803493dc99f86f030ec4c4285f1ee68",
- "0x5c0db39a5c3aa644e117dd0d6105cdf07bdf981ff0fdbd637c8ef4c5e9387c36"
- ],
- "ExecutionPayloadProof": [
- "0x53a88acc8997cbab934867d28f1631cc46450b4b39657868a0ce3e436c313a0e",
- "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e",
- "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
- "0xa75a515ebc17d68a126374d728848af8cc72b4c307d9964fb051469f4faa9977",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
- "0xa194a21241658a21229f3caf5af2ea38bafef7ecb2b2d9e5beb41a83ef89a7b9"
- ],
- "ValidatorFields": [
- "0x2c58c7f513dab2de353f008ddaf054749e80709b8ec1f397011773c7b29cd950",
- "0x01000000000000000000000085a0c86944b1d7e1119b7c93ad2b771480561ae3",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0xb301000000000000000000000000000000000000000000000000000000000000",
- "0x0c02000000000000000000000000000000000000000000000000000000000000",
- "0x6603000000000000000000000000000000000000000000000000000000000000",
- "0x6604000000000000000000000000000000000000000000000000000000000000"
- ],
- "WithdrawalFields": [
- "0x1601000000000000000000000000000000000000000000000000000000000000",
- "0x98ef000000000000000000000000000000000000000000000000000000000000",
- "0x85a0c86944b1d7e1119b7c93ad2b771480561ae3000000000000000000000000",
- "0xae1d247407000000000000000000000000000000000000000000000000000000"
- ]
-}
\ No newline at end of file
diff --git a/src/test/test-data/fullWithdrawalProof_Latest.json b/src/test/test-data/fullWithdrawalProof_Latest.json
new file mode 100644
index 0000000000..b20a1fa318
--- /dev/null
+++ b/src/test/test-data/fullWithdrawalProof_Latest.json
@@ -0,0 +1,159 @@
+{
+ "slot": 6397852,
+ "validatorIndex": 302913,
+ "historicalSummaryIndex": 146,
+ "withdrawalIndex": 0,
+ "blockHeaderRootIndex": 8092,
+ "beaconStateRoot": "0xe562b064fa5f17412bf13cd3b650f9d84b912f127c3f4c09879864d51a8b4daf",
+ "slotRoot": "0x9c9f610000000000000000000000000000000000000000000000000000000000",
+ "timestampRoot": "0xb06fed6400000000000000000000000000000000000000000000000000000000",
+ "blockHeaderRoot": "0x8b036996f94e940c80c5c692fd0e25467a5d55f1cf92b7808f92090fc7be1d17",
+ "executionPayloadRoot": "0xe628472355543b53917635e60c1f924f111f7a3cd58f2d947e8631b9d9924cb1",
+ "latestBlockHeaderRoot": "0xa81fa0ec796b5f84e6435745245f6d24279a11a74e29666560355507c441332d",
+ "SlotProof": [
+ "0x89c5010000000000000000000000000000000000000000000000000000000000",
+ "0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2",
+ "0xf4e65df697eb85f3ab176ac93b6ad4d96bd6b04bdddcc4f6c98f0fa94effc553"
+ ],
+ "WithdrawalProof": [
+ "0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f",
+ "0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e",
+ "0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6",
+ "0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8",
+ "0x1000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92",
+ "0xac5e32ea973e990d191039e91c7d9fd9830599b8208675f159c3df128228e729",
+ "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"
+ ],
+ "ValidatorProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0xba25997a12576cc460c39cfc6cc87d7dc215b237a0f3c4dc8cb7473125b2e138"
+ ],
+ "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": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ],
+ "HistoricalSummaryProof": [
+ "0x050b5923fe2e470849a7d467e4cbed4803d3a46d516b84552567976960ff4ccc",
+ "0x6ab9b5fc357c793cc5339398016c28ea21f0be4d56a68b554a29766dd312eeeb",
+ "0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529",
+ "0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a",
+ "0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2",
+ "0x301ab0d5d5ada4bd2e859658fc31743b5a502b26bc9b8b162e5a161e21218048",
+ "0x9d2bc97fffd61659313009e67a4c729a10274f2af26914a53bc7af6717da211e",
+ "0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e",
+ "0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f",
+ "0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15",
+ "0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3",
+ "0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf",
+ "0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8",
+ "0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
+ "0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f",
+ "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
+ "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
+ "0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7",
+ "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193",
+ "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
+ "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
+ "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
+ "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
+ "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
+ "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
+ "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x9300000000000000000000000000000000000000000000000000000000000000",
+ "0xd9ed050000000000000000000000000000000000000000000000000000000000",
+ "0xd824a89a9d5dd329a069b394ddc6618c70ed784982061959ac77d58d48e9d7c8",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0x8dc0b81ebe27fb91663a74713252d2eae5deb3b983374afc3c2d2f6b254128f1",
+ "0x09c7a863a253b4163a10916db2781eb35f58c016f0a24b3331ed84af3822c341"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/fullWithdrawalProof_Latest_1SlotAdvanced.json b/src/test/test-data/fullWithdrawalProof_Latest_1SlotAdvanced.json
new file mode 100644
index 0000000000..836639ba31
--- /dev/null
+++ b/src/test/test-data/fullWithdrawalProof_Latest_1SlotAdvanced.json
@@ -0,0 +1,160 @@
+{
+ "slot": 6397852,
+ "validatorIndex": 0,
+ "historicalSummaryIndex": 146,
+ "withdrawalIndex": 0,
+ "blockHeaderRootIndex": 8092,
+ "beaconStateRoot": "0x347e794382bd27d071b471af5047a2d97f8080a0f029e1fb9e3a13665924cdd1",
+ "slotRoot": "0x9c9f610000000000000000000000000000000000000000000000000000000000",
+ "timestampRoot": "0xb16fed6400000000000000000000000000000000000000000000000000000000",
+ "blockHeaderRoot": "0x8b44963e24101b8a5dadf759524d69084f142aaeded6e261e2ae4892c66e3bdf",
+ "blockBodyRoot": "0x8538e5b68a113c7c2cefcbd7b08b23af8f098b03dd1cd40108be7a0bad39f7b4",
+ "executionPayloadRoot": "0x126dc97fb335244643609b61b64ab8694f41a077cfadff97de390e9f708c80be",
+ "latestBlockHeaderRoot": "0x6674590fc92d3ad53a128a56f1b189f6c4111d6bad8976b57fa5858e057f0e07",
+ "SlotProof": [
+ "0x89c5010000000000000000000000000000000000000000000000000000000000",
+ "0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2",
+ "0x3424c924845b7698ec1ee6a4350e03dc5a00dd32f1b2fb21c9d25e17ef61b64f"
+ ],
+ "WithdrawalProof": [
+ "0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f",
+ "0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e",
+ "0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6",
+ "0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8",
+ "0x1000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92",
+ "0x232d0d58faceee6ed21f85527a6d6b54148bfabb0665e580fdd6ec862ab5cc56",
+ "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"
+ ],
+ "ValidatorProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0xce38745b03a4351bbea18f0aadd0001d1c4f1b23b250ed40204591910a77bae2"
+ ],
+ "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": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ],
+ "HistoricalSummaryProof": [
+ "0x050b5923fe2e470849a7d467e4cbed4803d3a46d516b84552567976960ff4ccc",
+ "0x6ab9b5fc357c793cc5339398016c28ea21f0be4d56a68b554a29766dd312eeeb",
+ "0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529",
+ "0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a",
+ "0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2",
+ "0x301ab0d5d5ada4bd2e859658fc31743b5a502b26bc9b8b162e5a161e21218048",
+ "0x9d2bc97fffd61659313009e67a4c729a10274f2af26914a53bc7af6717da211e",
+ "0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e",
+ "0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f",
+ "0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15",
+ "0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3",
+ "0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf",
+ "0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8",
+ "0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
+ "0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f",
+ "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
+ "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
+ "0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7",
+ "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193",
+ "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
+ "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
+ "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
+ "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
+ "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
+ "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
+ "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x9300000000000000000000000000000000000000000000000000000000000000",
+ "0xd9ed050000000000000000000000000000000000000000000000000000000000",
+ "0xd824a89a9d5dd329a069b394ddc6618c70ed784982061959ac77d58d48e9d7c8",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0x8dc0b81ebe27fb91663a74713252d2eae5deb3b983374afc3c2d2f6b254128f1",
+ "0x09c7a863a253b4163a10916db2781eb35f58c016f0a24b3331ed84af3822c341"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/fullWithdrawalProof_Latest_28ETH.json b/src/test/test-data/fullWithdrawalProof_Latest_28ETH.json
new file mode 100644
index 0000000000..1e40c38e43
--- /dev/null
+++ b/src/test/test-data/fullWithdrawalProof_Latest_28ETH.json
@@ -0,0 +1 @@
+{"slot":6397852,"validatorIndex":302913,"historicalSummaryIndex":146,"withdrawalIndex":0,"blockHeaderRootIndex":8092,"beaconStateRoot":"0xc9bc855bfd67190b654dc87cfe6945cdafa1d8bba66c3c2e81748f98c0c0e95b","slotRoot":"0x9c9f610000000000000000000000000000000000000000000000000000000000","timestampRoot":"0xb06fed6400000000000000000000000000000000000000000000000000000000","blockHeaderRoot":"0xbd83e79a38e454ece98a2f9e661792047984889db19234d11143322522e444fb","blockBodyRoot":"0x3e5c78192898e4f24aeceb181e58e4c5f390059ff6eebf0cbee6a096969a59a8","executionPayloadRoot":"0x8478de9992c86ab2617576c4b32267a2de50fd2bd35c8481a3d4a1731c0d5fe4","latestBlockHeaderRoot":"0x9ce41a41052e65ec6a0b3b021dc2f6f98f6060f22d70ac92ade6bcc3d4acca54","SlotProof":["0x89c5010000000000000000000000000000000000000000000000000000000000","0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2","0x1dfeede5317301d17b8e3ea54e055bd0567240cc27f76af3adc823ad7f6b6833"],"WithdrawalProof":["0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f","0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e","0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6","0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8","0x1000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92","0xac5e32ea973e990d191039e91c7d9fd9830599b8208675f159c3df128228e729","0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"],"ValidatorProof":["0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e","0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee","0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1","0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1","0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac","0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537","0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38","0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748","0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a","0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c","0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b","0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e","0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee","0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9","0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74","0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3","0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f","0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6","0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528","0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0x846b080000000000000000000000000000000000000000000000000000000000","0x5a6c050000000000000000000000000000000000000000000000000000000000","0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e","0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b","0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b","0xf91f8669ffcb2c5c46ef5c23e2fe3308154469b55494145b26ca111c173e1184"],"TimestampProof":["0x28a2c80000000000000000000000000000000000000000000000000000000000","0xa749df3368741198702798435eea361b1b1946aa9456587a2be377c8472ea2df","0x251a5e0b34e9eb58a44a231cfb3e817e3cf3f82cde755dc5fe94cbe36e146b5b","0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"],"ExecutionPayloadProof":["0xb6a435ffd17014d1dad214ba466aaa7fba5aa247945d2c29fd53e90d554f4474","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x5ec9aaf0a3571602f4704d4471b9af564caf17e4d22a7c31017293cb95949053","0x0000000000000000000000000000000000000000000000000000000000000000","0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0xc1f7cb289e44e9f711d7d7c05b67b84b8c6dd0394b688922a490cfd3fe216db1"],"ValidatorFields":["0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d","0x0100000000000000000000008e35f095545c56b07c942a4f3b055ef1ec4cb148","0x0040597307000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0xea65010000000000000000000000000000000000000000000000000000000000","0xf265010000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000"],"WithdrawalFields":["0x45cee50000000000000000000000000000000000000000000000000000000000","0x419f040000000000000000000000000000000000000000000000000000000000","0x59b0d71688da01057c08e4c1baa8faa629819c2a000000000000000000000000","0xe5d9ef8406000000000000000000000000000000000000000000000000000000"],"StateRootAgainstLatestBlockHeaderProof":["0x0000000000000000000000000000000000000000000000000000000000000000","0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"],"HistoricalSummaryProof":["0x050b5923fe2e470849a7d467e4cbed4803d3a46d516b84552567976960ff4ccc","0x6ab9b5fc357c793cc5339398016c28ea21f0be4d56a68b554a29766dd312eeeb","0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529","0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a","0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2","0x301ab0d5d5ada4bd2e859658fc31743b5a502b26bc9b8b162e5a161e21218048","0x9d2bc97fffd61659313009e67a4c729a10274f2af26914a53bc7af6717da211e","0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e","0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f","0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15","0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3","0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf","0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8","0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be","0x0000000000000000000000000000000000000000000000000000000000000000","0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c","0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f","0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30","0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1","0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7","0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193","0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1","0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b","0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220","0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f","0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e","0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784","0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb","0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb","0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab","0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x9300000000000000000000000000000000000000000000000000000000000000","0xd9ed050000000000000000000000000000000000000000000000000000000000","0xd824a89a9d5dd329a069b394ddc6618c70ed784982061959ac77d58d48e9d7c8","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x8dc0b81ebe27fb91663a74713252d2eae5deb3b983374afc3c2d2f6b254128f1","0x09c7a863a253b4163a10916db2781eb35f58c016f0a24b3331ed84af3822c341"]}
\ No newline at end of file
diff --git a/src/test/test-data/partialWithdrawalProof.json b/src/test/test-data/partialWithdrawalProof.json
deleted file mode 100644
index 0ad99e716c..0000000000
--- a/src/test/test-data/partialWithdrawalProof.json
+++ /dev/null
@@ -1,127 +0,0 @@
-{
- "slot": 58000,
- "validatorIndex": 61068,
- "withdrawalIndex": 0,
- "blockHeaderRootIndex": 656,
- "beaconStateRoot": "0x7556b22788b3eb50546b644505cb4bbe8fc4d47113eecb08a3d801863b9de672",
- "slotRoot": "0x90e2000000000000000000000000000000000000000000000000000000000000",
- "blockNumberRoot": "0xcedd000000000000000000000000000000000000000000000000000000000000",
- "blockHeaderRoot": "0x403b365874a6d4c21c81f55669a0621a1283e0ae04e4cede98501fef2073e9de",
- "blockBodyRoot": "0x120683bd4829913f7f449c1d2553cd5f16d5a1b03be50db66dddc7be891669d8",
- "executionPayloadRoot": "0x246c1e0160871083826c66aa39cc26b3e76dff5c30ca4a86e4578e3537190ff7",
- "BlockHeaderProof": [
- "0x7d366e6383289c6b3fa604da6ac9c55a6ea2c28ef4660bbd3942303c6cd9a48a",
- "0x9317a329308caaac83b964205503c71ac8b96e822c07dd197517a6c7fbc9480a",
- "0xe08fb0d3a9919f7446e123ba0eb115c1185db84d9793dbee06db92755e2fd533",
- "0xf95108f3a6fe477a043822938b14d440fb3908521bb51fd3bd7fe21922bcd009",
- "0x9f755638087848cdc7da4417b6eb70770f372455330676180e08fa3126140227",
- "0x404e8283c8f835cced515a1103a9d0b4f51815cd1b1fd9961d86cbf0a229d8ad",
- "0x39db56f5a1b6f288369e5fe41e80fbeab3d5857bbd2201c49173569fc100168a",
- "0x247c84d3855bd59bcc864f487f1dece7406127e4c8999cac6abb7b90a38dfed8",
- "0xf76f3958de0d98b331cc4141bae8da8b5efe5a4c5c558e41b5d26c78770a0e0b",
- "0x873a59b38acfa274ad31cebfd90a05c6b3b1c2b391a06685ed861e60579f55c0",
- "0xf9c50805b981d4a1b733ec00a3467e730debfb2793ed66bbe16a6ef97788e5c9",
- "0xa2bbf9d627721df1b38fb58850d2379bf7780856c548bd3fe4eb731ea6916655",
- "0xdd1da78e31063472c8889f278bf203ed29d362dc615073a6d0dc2af95302e341",
- "0xb70c51b37b01fe8c02ab81152374651196b471a0ef71ffefe858ea2b19ea7d06",
- "0x81ee8c21a9d7b67ee31ff8b2f4aeb8c08e02758a6270e5f52445eb1cc5bc12f1",
- "0x0616a07f6e3bf6aa5e42faaf52627f4ff40a4b8e300b2cec71ed5c0fff0f643e",
- "0x32207e3857a785a8d9845666a37c44176af113f8d8137b03c14d2b3f1a469dd1",
- "0xe7467e5a471e40f7969ed8b81a4f5d77af173320e89f2ba7379bd2db7a63cfd6"
- ],
- "SlotProof": [
- "0x8a7c000000000000000000000000000000000000000000000000000000000000",
- "0x4baecac213322c0b3ce4203c523b38ff566b2119f8c80665fb0cb03e7cdcd9f5",
- "0xab2293df236d0af12586b8ec4b4013ecfb6b71f5057867c303a3b05a73f33f16"
- ],
- "WithdrawalProof": [
- "0x4146b4ba1e9c2f10831b53375aae926882a77bcbeeab7321bd6d6c47196fb5fb",
- "0x1c135cd6bbd02e8ea918885d033bdf4ad3320de95215d4f1469544126b31cfdc",
- "0x954fb74dbea94f6a315a4e8d62750476d5631563836e2bf5bac18ba236cdb4a3",
- "0x57836e516d339a2c48916b04fd9c1abdd7b5eb34adcac4ca3fa7a33d3ff913d5",
- "0x1000000000000000000000000000000000000000000000000000000000000000",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0x043b96cba4e5dea1efc7b64838dc5fcf30dd2e8b62adac6df1af0068efe2dc4e",
- "0xdd0dca9d22bdc44e7c593ec402f085425c4ef2fee10a507dc2d9b63c0dcf0db8",
- "0x21d02052d9b892cecf1e13f0749f18013cf7c0b3f012b9275bff5b9743c5c529"
- ],
- "ValidatorProof": [
- "0xba087de35dc4b429ea39111a751664750e61e65c2c036c3ebf4d86e7e5de9340",
- "0xf3bfbc0caa1f8cb89df8a1a04bc18af14714102748965d52e1542b33c94b8ba2",
- "0x0c5a3c12daec8ad96ad6b635dd816b4c4252853260fc5dac3fce29f012f347d8",
- "0x2fd3aa6c99cc05e9fa3473f7cb0b7d737f051134e96be2cebd206825ce636ea2",
- "0x01da838685987c0be55ff085d903f8676048139f2e0aec20d426e336af670449",
- "0x4e4e743ed6cf9c4028e37808bdc202b6cc2f4eecb0f330002c4e41ce181f1def",
- "0xe25e6e55a2bcc935cfc225be988e42253421dc184b1384ceac85fa92228e9782",
- "0xf63336cf1d3b1172dad5513e209d459da726843898d7fcc8b4d2d99e502f2866",
- "0xae999ce50a65a88755ae12cba3616b368eba8f246c263d8154aa0aaa530c9064",
- "0x34706d3d48c0d6e24af619c8acd756da09af05236982caee98aef440242d08c1",
- "0xa61ed5b762a431e9641811d11fd9aa7219d47d4bfd803b7087e95390852f214a",
- "0xbd5c30fa2319470f928f91102b7fcf53bba4bb8ca4a38de53429efe7d00b0cde",
- "0xc655c6a25140072224c98e8b8963fab37d8eb51655265018f62d0860a4d58ad3",
- "0xf00f90e9671a1decbdf18b1ad2247fd9192f1be793818c2c303ee607f43876f0",
- "0xe5ae53bbdf410ecdecbbf58c7b3a4f9ee0885535ef95a3d5a34b69b32da31c07",
- "0x6c2d2ac0c0cbe5b9a09cfaf42b1f2ea0a23d2a46e40473a837bc6725fb94c432",
- "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
- "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
- "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
- "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
- "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
- "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
- "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
- "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
- "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
- "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
- "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
- "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
- "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
- "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
- "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
- "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
- "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
- "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
- "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
- "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
- "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
- "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
- "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
- "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
- "0xc0f1000000000000000000000000000000000000000000000000000000000000",
- "0x8f03000000000000000000000000000000000000000000000000000000000000",
- "0x655ba28a360c2ba8b28ec4ab0f0cc329d0be63fc010e624850f4eb4c6e4e5f2c",
- "0x4257d5a9fa3f4382614a49c599fcf10a01a18d4fd9b81a0af588a2e38a1611af",
- "0x760dd5d4f9e7552bb44ba3cabfc37f7736406b83787878afc9c1788e7b7bbf6c",
- "0xe7467e5a471e40f7969ed8b81a4f5d77af173320e89f2ba7379bd2db7a63cfd6"
- ],
- "BlockNumberProof": [
- "0x80c3c90100000000000000000000000000000000000000000000000000000000",
- "0x4d2f02565540982e5f9a8d1d2e7532adc15d01fa504ce057705a2801faaf4a65",
- "0x40558d1cb53e15f6b55f1c45af033c56a52e1965e23558995b90dbf8c9203e4c",
- "0x8b49718edc12872b06d23846475242b6edaa98806428f7b1623a38c8b69a6362"
- ],
- "ExecutionPayloadProof": [
- "0xad3a87388995595b1b83a82efb06385e567978bbe5706a1ad0241ab4d51cd3ce",
- "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e",
- "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
- "0x3852c03e8d18f79b7258db3d8419c9db06bd0cc25d3b5fecf73ea67218dbe958",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
- "0x012cbf033b1fed2e1af383038f0ef27daf9ee6d9bc4ae24925cfd0e6ceade593"
- ],
- "ValidatorFields": [
- "0xe827e0052f9bbd6b5eec2bc2bc5b1e83208b30feca87ae04388819c83321c183",
- "0x0100000000000000000000001ea692e68a7765de26fc03a6d74ee5b56a7e2b4d",
- "0x0040597307000000000000000000000000000000000000000000000000000000",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
- "0xb201000000000000000000000000000000000000000000000000000000000000",
- "0xc901000000000000000000000000000000000000000000000000000000000000",
- "0xffffffffffffffff000000000000000000000000000000000000000000000000",
- "0xffffffffffffffff000000000000000000000000000000000000000000000000"
- ],
- "WithdrawalFields": [
- "0x2f73030000000000000000000000000000000000000000000000000000000000",
- "0x8cee000000000000000000000000000000000000000000000000000000000000",
- "0x1ea692e68a7765de26fc03a6d74ee5b56a7e2b4d000000000000000000000000",
- "0x2cea020000000000000000000000000000000000000000000000000000000000"
- ]
-}
\ No newline at end of file
diff --git a/src/test/test-data/partialWithdrawalProof_Latest.json b/src/test/test-data/partialWithdrawalProof_Latest.json
new file mode 100644
index 0000000000..0c5160b374
--- /dev/null
+++ b/src/test/test-data/partialWithdrawalProof_Latest.json
@@ -0,0 +1,159 @@
+{
+ "slot": 6397852,
+ "validatorIndex": 302913,
+ "historicalSummaryIndex": 146,
+ "withdrawalIndex": 0,
+ "blockHeaderRootIndex": 8092,
+ "beaconStateRoot": "0xc4ea7f435356d2de29784712dc1fb5597d7d36ec705ddebcbef8bdc4cb4ecaf0",
+ "slotRoot": "0x9c9f610000000000000000000000000000000000000000000000000000000000",
+ "timestampRoot": "0xb06fed6400000000000000000000000000000000000000000000000000000000",
+ "blockHeaderRoot": "0x8d9698a822c4f57d890f9465c046ae8799301ff01daca55fb2e8e347d547ec6d",
+ "executionPayloadRoot": "0xf6e0315572785590a6ee69adab31e5c1e9879264aeead5b7762a384ae7d938d0",
+ "latestBlockHeaderRoot": "0x34e6f6ae9370c1eacc38aad8c5a887983979b79a35c57f09570afd80a879fd69",
+ "SlotProof": [
+ "0x89c5010000000000000000000000000000000000000000000000000000000000",
+ "0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2",
+ "0xe01b2b111680b6b01268c23ee3d7d4d145e98809dd5894cc8d1944bdc19e346e"
+ ],
+ "WithdrawalProof": [
+ "0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f",
+ "0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e",
+ "0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6",
+ "0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8",
+ "0x1000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92",
+ "0xac5e32ea973e990d191039e91c7d9fd9830599b8208675f159c3df128228e729",
+ "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"
+ ],
+ "ValidatorProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x673e299c82563e700195d7d66981bdf521b9609a3b6d22be78f069b359e553e5"
+ ],
+ "TimestampProof": [
+ "0x28a2c80000000000000000000000000000000000000000000000000000000000",
+ "0xa749df3368741198702798435eea361b1b1946aa9456587a2be377c8472ea2df",
+ "0x568268c732e7471701bcaedcae302de5fe87b60cf9e6518696719f9732078b97",
+ "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471"
+ ],
+ "ExecutionPayloadProof": [
+ "0xb6a435ffd17014d1dad214ba466aaa7fba5aa247945d2c29fd53e90d554f4474",
+ "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0x5ec9aaf0a3571602f4704d4471b9af564caf17e4d22a7c31017293cb95949053",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xc1f7cb289e44e9f711d7d7c05b67b84b8c6dd0394b688922a490cfd3fe216db1"
+ ],
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x0100000000000000000000008e35f095545c56b07c942a4f3b055ef1ec4cb148",
+ "0x0040597307000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000"
+ ],
+ "WithdrawalFields": [
+ "0x45cee50000000000000000000000000000000000000000000000000000000000",
+ "0x419f040000000000000000000000000000000000000000000000000000000000",
+ "0x59b0d71688da01057c08e4c1baa8faa629819c2a000000000000000000000000",
+ "0xbd56200000000000000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ],
+ "HistoricalSummaryProof": [
+ "0x050b5923fe2e470849a7d467e4cbed4803d3a46d516b84552567976960ff4ccc",
+ "0x6ab9b5fc357c793cc5339398016c28ea21f0be4d56a68b554a29766dd312eeeb",
+ "0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529",
+ "0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a",
+ "0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2",
+ "0x301ab0d5d5ada4bd2e859658fc31743b5a502b26bc9b8b162e5a161e21218048",
+ "0x9d2bc97fffd61659313009e67a4c729a10274f2af26914a53bc7af6717da211e",
+ "0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e",
+ "0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f",
+ "0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15",
+ "0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3",
+ "0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf",
+ "0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8",
+ "0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
+ "0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f",
+ "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
+ "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
+ "0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7",
+ "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193",
+ "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
+ "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
+ "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
+ "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
+ "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
+ "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
+ "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x9300000000000000000000000000000000000000000000000000000000000000",
+ "0xd9ed050000000000000000000000000000000000000000000000000000000000",
+ "0xd824a89a9d5dd329a069b394ddc6618c70ed784982061959ac77d58d48e9d7c8",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
+ "0x8dc0b81ebe27fb91663a74713252d2eae5deb3b983374afc3c2d2f6b254128f1",
+ "0xe237bc62b6b5269da5f4093c292d0f3bf2cf4d2eb93b4f366dba675c4df9cc62"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json b/src/test/test-data/slashedProofs/balanceUpdateProof_Overcommitted_61511.json
similarity index 88%
rename from src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json
rename to src/test/test-data/slashedProofs/balanceUpdateProof_Overcommitted_61511.json
index bdff2e09f2..942f54cbf3 100644
--- a/src/test/test-data/slashedProofs/overcommittedBalanceProof_61511.json
+++ b/src/test/test-data/slashedProofs/balanceUpdateProof_Overcommitted_61511.json
@@ -1,7 +1,16 @@
{
"validatorIndex": 61511,
"beaconStateRoot": "0xedfa8c363b420fd244477dc823f58f33f5d431f78cdbb7faac607597b5efad94",
+ "slotRoot": "0xe332030000000000000000000000000000000000000000000000000000000000",
"balanceRoot": "0x000000000000000043b2983307000000b9d82430070000006c0cec3007000000",
+ "latestBlockHeaderRoot": "0x44e54c4f1192d7cceaea02be1f3e680b480ff3474e004aaa5c78a8e1543f0d54",
+ "slotProof": [
+ "0x79610ba1d515acf1fc35d9aa230071dfac431ccd2d2d54109df6930baa9e6417",
+ "0x074b37317c897ad7f622e4f3a783a129f8bf1fb60fb0e96dff12f47bc76ed504",
+ "0x98515b0e3e59aaf4758c87e5b204c884f7ab9e874ae1063ebb00b16ae3c4a30d",
+ "0xaa07ea4616f5ee64aae9543c79e7370b2f9cdd82f0b94525cd7bab13d88b5cd7",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
"ValidatorBalanceProof": [
"0x000000000000000068d059730700000000000000000000000000000000000000",
"0x7f5d365ccd831acbbbda52bca33268dd3153d69d29602a4f1b27c28838dd2ccf",
@@ -48,7 +57,24 @@
"0x4092a1ba83f037b714a1d8ce4cbdcbeacc0b4af46535d35327f824c2aa0f6bc6",
"0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
],
- "WithdrawalCredenitalProof": [
+ "ValidatorFields": [
+ "0x15c3c57049dbbd86d6a6cb7cd7637512c5c0ba23b582e3e9b5a9bd71e2fb6146",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0x0076be3707000000000000000000000000000000000000000000000000000000",
+ "0x0100000000000000000000000000000000000000000000000000000000000000",
+ "0x6502000000000000000000000000000000000000000000000000000000000000",
+ "0x6c02000000000000000000000000000000000000000000000000000000000000",
+ "0x0908000000000000000000000000000000000000000000000000000000000000",
+ "0x0428000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "LatestBlockHeaderProof": [
+ "0x8c0d5a3b8b10a624d22494560f6a657d2ef8513f4e05e3d1a9b4e51589ccb3d6",
+ "0x4e70fb71e677e500e00ca1234f5e540aaffa0df03c12a0f92f921e76845187b3",
+ "0xb416255e46f0d0233446299dc4a66f39b045b05aefa97591acc98d9b043d4aa5",
+ "0xaa07ea4616f5ee64aae9543c79e7370b2f9cdd82f0b94525cd7bab13d88b5cd7",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
+ "WithdrawalCredentialProof": [
"0x71bd8878bd97df4ceb7c6b5132d8b6745a6780262d5a0fa9dcb0d6f5c98115e7",
"0x27e6e87163079873e4a956e687e2f5f852775c6c69cc1646d59ff9de87ffdfec",
"0x7d3ec7b5b0603ec79644358dbeea0613df717f2e90271efeee665c25f99806a9",
@@ -95,15 +121,5 @@
"0x9225de1bd47ad6255b27cb59eaaeb7eabacb3cff9e7316ca06933559ef882b86",
"0x4092a1ba83f037b714a1d8ce4cbdcbeacc0b4af46535d35327f824c2aa0f6bc6",
"0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
- ],
- "ValidatorFields": [
- "0x15c3c57049dbbd86d6a6cb7cd7637512c5c0ba23b582e3e9b5a9bd71e2fb6146",
- "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
- "0x0076be3707000000000000000000000000000000000000000000000000000000",
- "0x0100000000000000000000000000000000000000000000000000000000000000",
- "0x6502000000000000000000000000000000000000000000000000000000000000",
- "0x6c02000000000000000000000000000000000000000000000000000000000000",
- "0x0908000000000000000000000000000000000000000000000000000000000000",
- "0x0428000000000000000000000000000000000000000000000000000000000000"
]
}
\ No newline at end of file
diff --git a/src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json b/src/test/test-data/slashedProofs/balanceUpdateProof_notOvercommitted_61511.json
similarity index 85%
rename from src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json
rename to src/test/test-data/slashedProofs/balanceUpdateProof_notOvercommitted_61511.json
index 6a6ac6f5ee..0832a5d260 100644
--- a/src/test/test-data/slashedProofs/notOvercommittedBalanceProof_61511.json
+++ b/src/test/test-data/slashedProofs/balanceUpdateProof_notOvercommitted_61511.json
@@ -1,7 +1,16 @@
{
"validatorIndex": 61511,
- "beaconStateRoot": "0x4befef9ff04af6164fe5cdcde7f5a9df05b9ad9210fb818d2636b36779d5bcff",
+ "beaconStateRoot": "0x4ee7c0f1277c3675d938680918b5b495ba5a9825ded9f007aa8a820548240520",
+ "slotRoot": "0xe332030000000000000000000000000000000000000000000000000000000000",
"balanceRoot": "0x000000000000000043b2983307000000b9d8243007000000e5015b7307000000",
+ "latestBlockHeaderRoot": "0x44e54c4f1192d7cceaea02be1f3e680b480ff3474e004aaa5c78a8e1543f0d54",
+ "slotProof": [
+ "0x79610ba1d515acf1fc35d9aa230071dfac431ccd2d2d54109df6930baa9e6417",
+ "0x074b37317c897ad7f622e4f3a783a129f8bf1fb60fb0e96dff12f47bc76ed504",
+ "0x98515b0e3e59aaf4758c87e5b204c884f7ab9e874ae1063ebb00b16ae3c4a30d",
+ "0x5024e63e2e208d43769955eeeeb3db9dfed9b2d65e965ace30a60096dbeccede",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
"ValidatorBalanceProof": [
"0x000000000000000068d059730700000000000000000000000000000000000000",
"0x7f5d365ccd831acbbbda52bca33268dd3153d69d29602a4f1b27c28838dd2ccf",
@@ -44,11 +53,28 @@
"0x1ff9000000000000000000000000000000000000000000000000000000000000",
"0xe3a4221e582bef1ce66e057107d2423d8a4af2ceef50973ab5c5b9a2c1fba14b",
"0x64b4054633cff8782678efebd075563f7eb509431378ec880a8280c897e13190",
- "0x86d33e3372cac6ef0ff823dc3a3ea92e02ea2bee1600549bdda5a914875a6e10",
+ "0xbdabb1a249ef55a056128614d113e85bb6f6899e7169050dcc88407406f0ace0",
"0x4092a1ba83f037b714a1d8ce4cbdcbeacc0b4af46535d35327f824c2aa0f6bc6",
"0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
],
- "WithdrawalCredenitalProof": [
+ "ValidatorFields": [
+ "0x15c3c57049dbbd86d6a6cb7cd7637512c5c0ba23b582e3e9b5a9bd71e2fb6146",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000",
+ "0x0100000000000000000000000000000000000000000000000000000000000000",
+ "0x6502000000000000000000000000000000000000000000000000000000000000",
+ "0x6c02000000000000000000000000000000000000000000000000000000000000",
+ "0x0908000000000000000000000000000000000000000000000000000000000000",
+ "0x0428000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "LatestBlockHeaderProof": [
+ "0x8c0d5a3b8b10a624d22494560f6a657d2ef8513f4e05e3d1a9b4e51589ccb3d6",
+ "0x4e70fb71e677e500e00ca1234f5e540aaffa0df03c12a0f92f921e76845187b3",
+ "0xb416255e46f0d0233446299dc4a66f39b045b05aefa97591acc98d9b043d4aa5",
+ "0x5024e63e2e208d43769955eeeeb3db9dfed9b2d65e965ace30a60096dbeccede",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
+ "WithdrawalCredentialProof": [
"0x71bd8878bd97df4ceb7c6b5132d8b6745a6780262d5a0fa9dcb0d6f5c98115e7",
"0x27e6e87163079873e4a956e687e2f5f852775c6c69cc1646d59ff9de87ffdfec",
"0x7d3ec7b5b0603ec79644358dbeea0613df717f2e90271efeee665c25f99806a9",
@@ -95,15 +121,5 @@
"0x3626feb0eee255fd0fb5296d2984a905827a482a26234a8632e810d2355eeeaf",
"0x4092a1ba83f037b714a1d8ce4cbdcbeacc0b4af46535d35327f824c2aa0f6bc6",
"0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
- ],
- "ValidatorFields": [
- "0x15c3c57049dbbd86d6a6cb7cd7637512c5c0ba23b582e3e9b5a9bd71e2fb6146",
- "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
- "0x0076be3707000000000000000000000000000000000000000000000000000000",
- "0x0100000000000000000000000000000000000000000000000000000000000000",
- "0x6502000000000000000000000000000000000000000000000000000000000000",
- "0x6c02000000000000000000000000000000000000000000000000000000000000",
- "0x0908000000000000000000000000000000000000000000000000000000000000",
- "0x0428000000000000000000000000000000000000000000000000000000000000"
]
}
\ No newline at end of file
diff --git a/src/test/test-data/slashedProofs/balanceUpdateProof_notOvercommitted_61511_incrementedBlockBy100.json b/src/test/test-data/slashedProofs/balanceUpdateProof_notOvercommitted_61511_incrementedBlockBy100.json
new file mode 100644
index 0000000000..839f6050b4
--- /dev/null
+++ b/src/test/test-data/slashedProofs/balanceUpdateProof_notOvercommitted_61511_incrementedBlockBy100.json
@@ -0,0 +1,125 @@
+{
+ "validatorIndex": 61511,
+ "beaconStateRoot": "0xe9537d65c51eb2c5b09330a10eacac64dce5a1c5298e455dd8705aef2a2a0c90",
+ "slotRoot": "0x4733030000000000000000000000000000000000000000000000000000000000",
+ "balanceRoot": "0x000000000000000043b2983307000000b9d8243007000000e5015b7307000000",
+ "latestBlockHeaderRoot": "0x44e54c4f1192d7cceaea02be1f3e680b480ff3474e004aaa5c78a8e1543f0d54",
+ "slotProof": [
+ "0x79610ba1d515acf1fc35d9aa230071dfac431ccd2d2d54109df6930baa9e6417",
+ "0x074b37317c897ad7f622e4f3a783a129f8bf1fb60fb0e96dff12f47bc76ed504",
+ "0x98515b0e3e59aaf4758c87e5b204c884f7ab9e874ae1063ebb00b16ae3c4a30d",
+ "0x5024e63e2e208d43769955eeeeb3db9dfed9b2d65e965ace30a60096dbeccede",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
+ "ValidatorBalanceProof": [
+ "0x000000000000000068d059730700000000000000000000000000000000000000",
+ "0x7f5d365ccd831acbbbda52bca33268dd3153d69d29602a4f1b27c28838dd2ccf",
+ "0xa8556dd2b415efe9ea077acd637059f02112fe070e26e29bd56a1c1a281f3edb",
+ "0x11e8a9a28b280f0ea331f9f34a963aa493087147001ab12812df39f82258000f",
+ "0x128ef0eb187fb5343e67b9d4c60826cb3ccba55f32169d4d5d0e203e0abf51c8",
+ "0x8de5b7a778b0cc4f3cc5fcfdfe00a2a7b326c6096300fc7a2e132fe7328003a9",
+ "0x9050635d2238a7ddd92f956cb6c444805881635901510604a2ccb2c6d709455d",
+ "0xda56d579f6cecdf3ea6a31550aaba7f1233b07f58db6730ecdd0c929ed445f53",
+ "0x4a7aadbb3b9721060adbe14ce124730853dc8ddadb8fe1608c0784f1ef35e262",
+ "0xce105221b70ce96b926af197858c7f703f78f6f772565e49a977426e3622d527",
+ "0xa73aa71c2dcc22d07a4d636bea5e207b0610206bd095d1715ea8cf394aeca03d",
+ "0x482c8b89f16d57347c143f3e9102b63c4e56d0ef11f7fc68a498407b845ff1ae",
+ "0xef9636e6ce536e011368ef2950aa56b8c2551af78c65e6076a16ff34ae984530",
+ "0x38f4e0d211be2dc4aff4f85c215f042369f2ac780831369a2287eacb6d30104d",
+ "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
+ "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0x1ff9000000000000000000000000000000000000000000000000000000000000",
+ "0xe3a4221e582bef1ce66e057107d2423d8a4af2ceef50973ab5c5b9a2c1fba14b",
+ "0x64b4054633cff8782678efebd075563f7eb509431378ec880a8280c897e13190",
+ "0xbdabb1a249ef55a056128614d113e85bb6f6899e7169050dcc88407406f0ace0",
+ "0x234714ee1e90e736bbabeec9d26875e76b86300d657304aa762e5f5a05d57f55",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
+ "ValidatorFields": [
+ "0x15c3c57049dbbd86d6a6cb7cd7637512c5c0ba23b582e3e9b5a9bd71e2fb6146",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000",
+ "0x0100000000000000000000000000000000000000000000000000000000000000",
+ "0x6502000000000000000000000000000000000000000000000000000000000000",
+ "0x6c02000000000000000000000000000000000000000000000000000000000000",
+ "0x0908000000000000000000000000000000000000000000000000000000000000",
+ "0x0428000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "LatestBlockHeaderProof": [
+ "0x8c0d5a3b8b10a624d22494560f6a657d2ef8513f4e05e3d1a9b4e51589ccb3d6",
+ "0x4e70fb71e677e500e00ca1234f5e540aaffa0df03c12a0f92f921e76845187b3",
+ "0x5a6544be083c70c87ff98d129a5bf434d62c73999c25230c2bff2f2e2d555b21",
+ "0x5024e63e2e208d43769955eeeeb3db9dfed9b2d65e965ace30a60096dbeccede",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ],
+ "WithdrawalCredentialProof": [
+ "0x71bd8878bd97df4ceb7c6b5132d8b6745a6780262d5a0fa9dcb0d6f5c98115e7",
+ "0x27e6e87163079873e4a956e687e2f5f852775c6c69cc1646d59ff9de87ffdfec",
+ "0x7d3ec7b5b0603ec79644358dbeea0613df717f2e90271efeee665c25f99806a9",
+ "0x198cf23e2a594f99cc6fb0bd71728ae43f64d3aeecbbbd73b42e7f9d91ab112b",
+ "0x1a2ae162dcb33b34412a1b76d296d043b6e4a092ac17a31ff688f125a71efc5c",
+ "0x22b7f97d9cf16bb59b5bd032833bc428eba10283db93184e3e8f58e840376c4a",
+ "0x2d7294482a48d1fa33db6d09459c5ef98c5b4f063f1d8abb2968f6ff2553cea1",
+ "0x3cf02972a98b6349570d638662c6c359fd67d51da4ebb84c78a616bf57031be3",
+ "0x91efbc4e73550db8404d9df7819c1a93474a2dac866fe503936751f9c8aa52ce",
+ "0x5e4d2fde7fd736c48b6d997064515f12c5b870573f822571d575ad9ec4330d37",
+ "0x65cdebb1d68f7e5f9f4b367cf30b781ffc2c630afd87821d98dcdf31f8216147",
+ "0x7d35223de45ed1780d9c5fa6d8c4f777b819433c3e585a41d1ebfcb33de180f8",
+ "0xa248de567404ec7a47f3c27b498c2b9e6318d2de870b0e076a4689ccd32f4713",
+ "0xf00f90e9671a1decbdf18b1ad2247fd9192f1be793818c2c303ee607f43876f0",
+ "0xe5ae53bbdf410ecdecbbf58c7b3a4f9ee0885535ef95a3d5a34b69b32da31c07",
+ "0x04c321478671bf8864f28399bfd02a4d534ed2d35a402ae97b5a223483bbaebd",
+ "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
+ "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x1ff9000000000000000000000000000000000000000000000000000000000000",
+ "0x7c0b000000000000000000000000000000000000000000000000000000000000",
+ "0x23453ac8b60d1e35c7b111a0a5a5fb5621007fc454a17d5099d4290c99d7cf5b",
+ "0x3626feb0eee255fd0fb5296d2984a905827a482a26234a8632e810d2355eeeaf",
+ "0x234714ee1e90e736bbabeec9d26875e76b86300d657304aa762e5f5a05d57f55",
+ "0x8ce2046003484702394d963638c95fb59d0f32da8e2060a33918836be89ff074"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json b/src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json
index 0fb60d0f30..117f5deb98 100644
--- a/src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json
+++ b/src/test/test-data/withdrawalCredentialAndBalanceProof_61068.json
@@ -2,6 +2,7 @@
"validatorIndex": 61068,
"beaconStateRoot": "0x9022cd2f5102866cce67dc86f3190719cbf0ff0aff080cab6e975bfa823cc04b",
"balanceRoot": "0xe5015b7307000000e5015b7307000000e5015b7307000000e5015b7307000000",
+ "latestBlockHeaderRoot": "0xb70c51b37b01fe8c02ab81152374651196b471a0ef71ffefe858ea2b19ea7d06",
"ValidatorBalanceProof": [
"0xe5015b73070000003972637307000000e5015b7307000000e5015b7307000000",
"0x0e633cd1b96df1d8901b2a4499fdde621b96a8d34aac9a21048cb23cd26accb1",
@@ -48,7 +49,7 @@
"0x760dd5d4f9e7552bb44ba3cabfc37f7736406b83787878afc9c1788e7b7bbf6c",
"0xe7467e5a471e40f7969ed8b81a4f5d77af173320e89f2ba7379bd2db7a63cfd6"
],
- "WithdrawalCredenitalProof": [
+ "WithdrawalCredentialProof": [
"0xba087de35dc4b429ea39111a751664750e61e65c2c036c3ebf4d86e7e5de9340",
"0xf3bfbc0caa1f8cb89df8a1a04bc18af14714102748965d52e1542b33c94b8ba2",
"0x0c5a3c12daec8ad96ad6b635dd816b4c4252853260fc5dac3fce29f012f347d8",
@@ -105,5 +106,12 @@
"0xc901000000000000000000000000000000000000000000000000000000000000",
"0xffffffffffffffff000000000000000000000000000000000000000000000000",
"0xffffffffffffffff000000000000000000000000000000000000000000000000"
+ ],
+ "LatestBlockHeaderProof": [
+ "0xa787e90f294d84c8f175b8decee03b95aef0bd5c25ff0dc306500fdb537affd5",
+ "0x81ee8c21a9d7b67ee31ff8b2f4aeb8c08e02758a6270e5f52445eb1cc5bc12f1",
+ "0x0616a07f6e3bf6aa5e42faaf52627f4ff40a4b8e300b2cec71ed5c0fff0f643e",
+ "0x740363449ce964e54c36c8ace68b3d3e1b37f7a4099643255db1e8b4bb9e69a0",
+ "0xe7467e5a471e40f7969ed8b81a4f5d77af173320e89f2ba7379bd2db7a63cfd6"
]
}
\ No newline at end of file
diff --git a/src/test/test-data/withdrawalCredentialAndBalanceProof_61336.json b/src/test/test-data/withdrawalCredentialAndBalanceProof_61336.json
index e5dac72250..6240c4649f 100644
--- a/src/test/test-data/withdrawalCredentialAndBalanceProof_61336.json
+++ b/src/test/test-data/withdrawalCredentialAndBalanceProof_61336.json
@@ -1,7 +1,8 @@
{
"validatorIndex": 61336,
- "beaconStateRoot": "0xdba7bbc60e552d76011c8c682954eae0ec1c554bcd293c0588e96916ea0d3b5f",
+ "beaconStateRoot": "0x040086cbfc077b7c4461001a5d59e9cc62603db076a63b9a746d5e8320accf4f",
"balanceRoot": "0xe5015b7307000000000000000000000000000000000000000000000000000000",
+ "latestBlockHeaderRoot": "0xb70c51b37b01fe8c02ab81152374651196b471a0ef71ffefe858ea2b19ea7d06",
"ValidatorBalanceProof": [
"0xc834937207000000c834937207000000c83493720700000048c4927207000000",
"0xb78a61531f51cb2ec7dd495212bd2d6c7ef34d2e7e58d3561bef92901f179715",
@@ -44,11 +45,11 @@
"0xc0f1000000000000000000000000000000000000000000000000000000000000",
"0xa2b940dcdbbb8e6a9940e94ac5cc90a8ee18d7549882d4f3b26406a8770db1df",
"0x1c9f4ae53f01f807ea9c3e70da4793f791e170473ecf849d8eabf644323370c9",
- "0x6750f9ab0181cee5d556dd6f980131497eda153677ad38f658307316982f9d1f",
+ "0xfc4c6267c598928f75ed9a6093b17f3d4a803520ae75403dfe052ae422d0dd75",
"0x760dd5d4f9e7552bb44ba3cabfc37f7736406b83787878afc9c1788e7b7bbf6c",
"0xe7467e5a471e40f7969ed8b81a4f5d77af173320e89f2ba7379bd2db7a63cfd6"
],
- "WithdrawalCredenitalProof": [
+ "WithdrawalCredentialProof": [
"0x7b0e44f08bd17b0d0b03dc34533e4a32a2b97104a0fbec66260b629b7b7782fc",
"0xfd4057403be1b96a41b63c9ac4a06b4c64954ad128a8c8b3a52b3dcb96d122a8",
"0xcb9960e19d11ac9070baf26a31f1873e0d37385f27e552714f0cf3b4b216fe46",
@@ -99,11 +100,18 @@
"ValidatorFields": [
"0x2c58c7f513dab2de353f008ddaf054749e80709b8ec1f397011773c7b29cd950",
"0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
- "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xb301000000000000000000000000000000000000000000000000000000000000",
"0x0c02000000000000000000000000000000000000000000000000000000000000",
"0x6603000000000000000000000000000000000000000000000000000000000000",
"0x6604000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "LatestBlockHeaderProof": [
+ "0xa787e90f294d84c8f175b8decee03b95aef0bd5c25ff0dc306500fdb537affd5",
+ "0x81ee8c21a9d7b67ee31ff8b2f4aeb8c08e02758a6270e5f52445eb1cc5bc12f1",
+ "0x0616a07f6e3bf6aa5e42faaf52627f4ff40a4b8e300b2cec71ed5c0fff0f643e",
+ "0x2df72afcad3355b9ea6965e94642ab99a49f8ba3e8abea7bf49bc594c2c48a90",
+ "0xe7467e5a471e40f7969ed8b81a4f5d77af173320e89f2ba7379bd2db7a63cfd6"
]
}
\ No newline at end of file
diff --git a/src/test/test-data/withdrawal_credential_proof_302913.json b/src/test/test-data/withdrawal_credential_proof_302913.json
new file mode 100644
index 0000000000..b35ae433bd
--- /dev/null
+++ b/src/test/test-data/withdrawal_credential_proof_302913.json
@@ -0,0 +1,115 @@
+{
+ "validatorIndex": 302913,
+ "beaconStateRoot": "0x4678e21381463879490925e8911492b890c0ce7ba347cce57fd8d4e0bb27f04a",
+ "balanceRoot": "0x6cba5d7307000000e5015b730700000008bd5d7307000000239e5d7307000000",
+ "latestBlockHeaderRoot": "0x859816e76b0944c3110a31f26acc301718122e1acfe8fb161df9c0e684515593",
+ "ValidatorBalanceProof": [
+ "0x26805d73070000002aa55d7307000000218e5d730700000056d35d7307000000",
+ "0x24846a54724508d8394a9e30f819bc19fe6727745be4f19e1e3924b985b4b8e9",
+ "0x48fb4849c31d88b413f650c17bd55bb11fe38bcef5d234dba8532737ae78f776",
+ "0x0d7d38f90a04ef49d15ca10af0e818e227134e4b46c7b22dd10e149f2a2bc612",
+ "0xdd345235f28f92719c39c9a5042b91f814d535ae5df574c0f6cd38f77791fc2b",
+ "0x0855928643f4210d9d8d0986847674139be32ebf3db984b6d81f3ed136475b6d",
+ "0x28460d51c1ce75299f8c0dff8bca9f498fef0edc57c343bbd76592b3d6b984a3",
+ "0x068a141709053afb48698290176068f448867cba9c6ff10f986b1d02691f6e70",
+ "0x9f0958275fe6ea80b1825bcd11907d1076600719ce574ae2a609e51a975dde54",
+ "0xd43fbb18b30449f5c81148848fd6579b4f6cebfe66187bd1c36a293b22085fc2",
+ "0x7bf0aac502053da7dfd5356960db63ab48d3cc6ee5f36f41639eadacd34ac272",
+ "0x866e3b213d02f1cb86149c2472789ab9d7fb3fb52371a893dc8b6f8936ebd0c1",
+ "0xcec7cc4b82fb8dd889257128af3e4433700fcc0fb0505b431aa5f592111e785b",
+ "0xdd08667067baa43e2b41b793464a591a29643b5582c8fc9c2c045f984c5848d7",
+ "0x842ec80062a4f2f7e9d80ab408374ae42817acf5036aff40b3ade345ab196090",
+ "0xfe1ae22b8ba21fce84b25f5da3e37a91db49158b7025d6371d5a54c03adba125",
+ "0x8c039a81d68952077db2cc11713ae6f5a9ecc5ebe859c0612ff7215c0ccd7aba",
+ "0x5415d3199f1da31287d646d03c2691728396d87f7ef52c142596301e27a343ca",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x7d46d32db9a97974916a2114aeffc11fc031a954b2110742267054a438055c1a",
+ "0x81e1234eb27473f180f890c98019ecb7655a3e58105c7182d8daf726d75aaf8d",
+ "0xa158c9a519bf3c8d2296ebb6a2df54283c7014e7b84524da0574c95fe600a1fd",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ],
+ "WithdrawalCredentialProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0x3f918c09ea95d520538a81f767de0f1be3f30fc9d599805c68048ef5c23a85e2",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ],
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/withdrawal_credential_proof_302913_30ETHBalance.json b/src/test/test-data/withdrawal_credential_proof_302913_30ETHBalance.json
new file mode 100644
index 0000000000..1157b6224c
--- /dev/null
+++ b/src/test/test-data/withdrawal_credential_proof_302913_30ETHBalance.json
@@ -0,0 +1 @@
+{"validatorIndex":302913,"beaconStateRoot":"0x9761c69195aeb853adc72f41b00232e0dccdfaaf5828bc1db56416eb1d4396df","balanceRoot":"0x6cba5d7307000000e56d25fc0600000008bd5d7307000000239e5d7307000000","latestBlockHeaderRoot":"0xbc2f3b78cf1d4d5b47d6cc987864860035088de6316b4eb1ccbe8677f31e98e1","ValidatorBalanceProof":["0x26805d73070000002aa55d7307000000218e5d730700000056d35d7307000000","0x24846a54724508d8394a9e30f819bc19fe6727745be4f19e1e3924b985b4b8e9","0x48fb4849c31d88b413f650c17bd55bb11fe38bcef5d234dba8532737ae78f776","0x0d7d38f90a04ef49d15ca10af0e818e227134e4b46c7b22dd10e149f2a2bc612","0xdd345235f28f92719c39c9a5042b91f814d535ae5df574c0f6cd38f77791fc2b","0x0855928643f4210d9d8d0986847674139be32ebf3db984b6d81f3ed136475b6d","0x28460d51c1ce75299f8c0dff8bca9f498fef0edc57c343bbd76592b3d6b984a3","0x068a141709053afb48698290176068f448867cba9c6ff10f986b1d02691f6e70","0x9f0958275fe6ea80b1825bcd11907d1076600719ce574ae2a609e51a975dde54","0xd43fbb18b30449f5c81148848fd6579b4f6cebfe66187bd1c36a293b22085fc2","0x7bf0aac502053da7dfd5356960db63ab48d3cc6ee5f36f41639eadacd34ac272","0x866e3b213d02f1cb86149c2472789ab9d7fb3fb52371a893dc8b6f8936ebd0c1","0xcec7cc4b82fb8dd889257128af3e4433700fcc0fb0505b431aa5f592111e785b","0xdd08667067baa43e2b41b793464a591a29643b5582c8fc9c2c045f984c5848d7","0x842ec80062a4f2f7e9d80ab408374ae42817acf5036aff40b3ade345ab196090","0xfe1ae22b8ba21fce84b25f5da3e37a91db49158b7025d6371d5a54c03adba125","0x8c039a81d68952077db2cc11713ae6f5a9ecc5ebe859c0612ff7215c0ccd7aba","0x5415d3199f1da31287d646d03c2691728396d87f7ef52c142596301e27a343ca","0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0x846b080000000000000000000000000000000000000000000000000000000000","0x7d46d32db9a97974916a2114aeffc11fc031a954b2110742267054a438055c1a","0x81e1234eb27473f180f890c98019ecb7655a3e58105c7182d8daf726d75aaf8d","0x738a1fb07361c872216ae655df9a1690d465e6698acd1c9230ebc3fd89412c69","0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b","0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"],"WithdrawalCredentialProof":["0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e","0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee","0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1","0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1","0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac","0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537","0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38","0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748","0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a","0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c","0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b","0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e","0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee","0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9","0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74","0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3","0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f","0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6","0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528","0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3","0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7","0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff","0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5","0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d","0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c","0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327","0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74","0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76","0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f","0x846b080000000000000000000000000000000000000000000000000000000000","0x5a6c050000000000000000000000000000000000000000000000000000000000","0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e","0x8efea8a7f00e6411ae6187a6d9a2c22ad033c0ad749ce0940e5bf2fd76ac35c7","0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b","0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"],"ValidatorFields":["0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d","0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443","0xe56d25fc06000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000","0xea65010000000000000000000000000000000000000000000000000000000000","0xf265010000000000000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000","0xffffffffffffffff000000000000000000000000000000000000000000000000"],"StateRootAgainstLatestBlockHeaderProof":["0x0000000000000000000000000000000000000000000000000000000000000000","0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"]}
\ No newline at end of file
diff --git a/src/test/test-data/withdrawal_credential_proof_302913_exited.json b/src/test/test-data/withdrawal_credential_proof_302913_exited.json
new file mode 100644
index 0000000000..c455ac0d9b
--- /dev/null
+++ b/src/test/test-data/withdrawal_credential_proof_302913_exited.json
@@ -0,0 +1,115 @@
+{
+ "validatorIndex": 302913,
+ "beaconStateRoot": "0xd6c32aa93c58fd5ca1f36246234878ec6b0c36b2c218d8bd2521b4c4f2f8cd23",
+ "balanceRoot": "0x6cba5d7307000000000000000000000008bd5d7307000000239e5d7307000000",
+ "latestBlockHeaderRoot": "0xc8fd8d84d59b2fe5e331e72466e237ba5014d9f078b0d65271aa00d2689fec9b",
+ "ValidatorBalanceProof": [
+ "0x26805d73070000002aa55d7307000000218e5d730700000056d35d7307000000",
+ "0x24846a54724508d8394a9e30f819bc19fe6727745be4f19e1e3924b985b4b8e9",
+ "0x48fb4849c31d88b413f650c17bd55bb11fe38bcef5d234dba8532737ae78f776",
+ "0x0d7d38f90a04ef49d15ca10af0e818e227134e4b46c7b22dd10e149f2a2bc612",
+ "0xdd345235f28f92719c39c9a5042b91f814d535ae5df574c0f6cd38f77791fc2b",
+ "0x0855928643f4210d9d8d0986847674139be32ebf3db984b6d81f3ed136475b6d",
+ "0x28460d51c1ce75299f8c0dff8bca9f498fef0edc57c343bbd76592b3d6b984a3",
+ "0x068a141709053afb48698290176068f448867cba9c6ff10f986b1d02691f6e70",
+ "0x9f0958275fe6ea80b1825bcd11907d1076600719ce574ae2a609e51a975dde54",
+ "0xd43fbb18b30449f5c81148848fd6579b4f6cebfe66187bd1c36a293b22085fc2",
+ "0x7bf0aac502053da7dfd5356960db63ab48d3cc6ee5f36f41639eadacd34ac272",
+ "0x866e3b213d02f1cb86149c2472789ab9d7fb3fb52371a893dc8b6f8936ebd0c1",
+ "0xcec7cc4b82fb8dd889257128af3e4433700fcc0fb0505b431aa5f592111e785b",
+ "0xdd08667067baa43e2b41b793464a591a29643b5582c8fc9c2c045f984c5848d7",
+ "0x842ec80062a4f2f7e9d80ab408374ae42817acf5036aff40b3ade345ab196090",
+ "0xfe1ae22b8ba21fce84b25f5da3e37a91db49158b7025d6371d5a54c03adba125",
+ "0x8c039a81d68952077db2cc11713ae6f5a9ecc5ebe859c0612ff7215c0ccd7aba",
+ "0x5415d3199f1da31287d646d03c2691728396d87f7ef52c142596301e27a343ca",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x7d46d32db9a97974916a2114aeffc11fc031a954b2110742267054a438055c1a",
+ "0x81e1234eb27473f180f890c98019ecb7655a3e58105c7182d8daf726d75aaf8d",
+ "0xa015eda3f293610cf5763cc878497fe3fbdb20309c1ba004f384b467a11db327",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ],
+ "WithdrawalCredentialProof": [
+ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e",
+ "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee",
+ "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1",
+ "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1",
+ "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac",
+ "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537",
+ "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38",
+ "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748",
+ "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a",
+ "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c",
+ "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b",
+ "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e",
+ "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee",
+ "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9",
+ "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74",
+ "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3",
+ "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f",
+ "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xd740083592ddcf0930ad47cc8e7758d567c2dc63767624bda4d50cafcbfe7093",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ],
+ "ValidatorFields": [
+ "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xea65010000000000000000000000000000000000000000000000000000000000",
+ "0xf265010000000000000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000",
+ "0xffffffffffffffff000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/test-data/withdrawal_credential_proof_510257.json b/src/test/test-data/withdrawal_credential_proof_510257.json
new file mode 100644
index 0000000000..4f925b5d2e
--- /dev/null
+++ b/src/test/test-data/withdrawal_credential_proof_510257.json
@@ -0,0 +1,115 @@
+{
+ "validatorIndex": 510257,
+ "beaconStateRoot": "0xffa8d20fba11c926a5b8d9ea4b0738c1fe22d1981177b39cd6a86ed3669702fe",
+ "balanceRoot": "0x0000000000000000e5015b730700000000000000000000000000000000000000",
+ "latestBlockHeaderRoot": "0x359b8fc7ea96256c42c38ec047e0052217c7889bbb46cf928fa75fbe2a718e0b",
+ "ValidatorBalanceProof": [
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
+ "0x6dd4ffb4ca5aa522f9dbe4fc9a0e07d26c61f8d309590c36f6ba2f2e55144e3a",
+ "0x46af8bc21a2d3401a9d413f03c93ecb0b29d5746307f71dc7118bad7dd46ca3f",
+ "0x07b3dda614c6ceb1364c74c9128707e339167bc2875217db87c3169083e60937",
+ "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
+ "0x0368e4efe7928a54c6ef32ec3447b168e69db5ab597f6f5d9ee09528a85e36cb",
+ "0xd0073758e6864c0b3e519a78a765091a5b42652268f7c7146d3c6bdf33676601",
+ "0xddc34931fd2c88c485af67a69b31fe118013f42b48ce81a349f47fb329b2b381",
+ "0x8905afda3b52ca75fed67a6fd8342b2bf7bb1f3bfb8246b5b8730097ca547b1c",
+ "0x9ac6965d7809be3640d5bae20323a5c726a181dd50f05406c547f2392ce3e25a",
+ "0x2a397932c520e69909074a21d4e367678c47b34d5c132c0b868b51df7d4b0b53",
+ "0xb896ba3596c19cb4f1b98b79fe8234299f164762055b7114968b480c4cb0f9bb",
+ "0x36adfe560682ca3335a39ba90eb021c0daf74b32a908203bc66bc9d680819914",
+ "0x5537955b317ce653fc15a7d7a8e0b4e2733d6e2f630f3404437751251aa51bea",
+ "0x6dece44e9d5964318fd5c7b458afe405bbfbb2dc79eb12fe87e65d101da20eb5",
+ "0x8c039a81d68952077db2cc11713ae6f5a9ecc5ebe859c0612ff7215c0ccd7aba",
+ "0x5415d3199f1da31287d646d03c2691728396d87f7ef52c142596301e27a343ca",
+ "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
+ "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x7d46d32db9a97974916a2114aeffc11fc031a954b2110742267054a438055c1a",
+ "0x81e1234eb27473f180f890c98019ecb7655a3e58105c7182d8daf726d75aaf8d",
+ "0xa383de4eac63dd7869d3ef08c5aca0ed832a5fac0603bcc8b5db37297e617ec5",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ],
+ "WithdrawalCredentialProof": [
+ "0x1a7c6cc33e5d44fd12622c350a89467af1dde63b878aada6d92122e7af62be18",
+ "0xeb50c3b01c1ec2a90e7ac51a1472633125fb9cfa1c516dc3a10fa61d10d16726",
+ "0x25da66081ff6b20267ab5cfb4301bc4935b03c09138a0ce56b9e6a476292a64f",
+ "0xe73b742215086105ee3509e12da84334de7c522c8fcfbe0bd506ce9f037c49aa",
+ "0x9951384987ef65d5135e31779d1752ca0a0a907b49f9c040d967b82eac01824a",
+ "0x8f03ed90c033494704345311ee036253524e6a63d90de1320abe06a1dc097d6c",
+ "0x3de8f72a828bf9a4e0105c96b705b983b6acf2a36d18f016089ce2ac2e912377",
+ "0x96cad999eeb82a853ebc7b0240b613c4ad3dc1e4d24bf53820bc99a42fe1b323",
+ "0x2d9c9feea67401140c6361f569491a9a34a67ac4579c48b27ae1f91692daa9ff",
+ "0x55c251dcae98b63839b0986e3724c9840529b10af62f1256b2f124380008035d",
+ "0xe3a61aed717bccf4def4d98d1e548e62bd6a1199d26b7fd90149738e2e91c82f",
+ "0x265ae7041099b690f20ffe0ead1172a213740bf99c6dbd82d239a82ef0d5e97d",
+ "0x2e5ac328c7b8dfd5f0be9ed5311a060a3daa85f0f2b9ac750981233bfc69c1b6",
+ "0xd53273ba4b65d46cefdcf4042a55aba80a1d68bad67dffceb26c1074597e84c9",
+ "0x1257580608fe43f8118c73bcb20bc4556e5034249fd68035b9a84e0e18c09010",
+ "0x71d6e5c378dfd648434c3cd6426d35b5751ae595ff66e76de95b38f4e2c5c08d",
+ "0xbce1ff41e2ec18651cd3dec46adefc600f19fc2237853cbcc9f9e8cba4767d65",
+ "0xfb2b2dc8116aec79c89d6a3bf4631e1d34a531c81e24af8dca50652a39af0fa6",
+ "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528",
+ "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3",
+ "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
+ "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
+ "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
+ "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
+ "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
+ "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
+ "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
+ "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
+ "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
+ "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
+ "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
+ "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
+ "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
+ "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
+ "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
+ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
+ "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
+ "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
+ "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76",
+ "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f",
+ "0x846b080000000000000000000000000000000000000000000000000000000000",
+ "0x5a6c050000000000000000000000000000000000000000000000000000000000",
+ "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e",
+ "0xca8a23905f2088e7d8a9a6d631d265b45c2e839137ac66c86bf80456bbebddba",
+ "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b",
+ "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d"
+ ],
+ "ValidatorFields": [
+ "0xcc3045318f8e5b6ec9441a54183cacde7f180509086f92868ba8942c92a79ca4",
+ "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443",
+ "0xe5015b7307000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x6cde020000000000000000000000000000000000000000000000000000000000",
+ "0x74de020000000000000000000000000000000000000000000000000000000000",
+ "0xf708030000000000000000000000000000000000000000000000000000000000",
+ "0xf709030000000000000000000000000000000000000000000000000000000000"
+ ],
+ "StateRootAgainstLatestBlockHeaderProof": [
+ "0x5e83fd9f12a10bd0aa14d9300f245cdc5ffaabec780453c1638eaf6177dd64f1",
+ "0x336f092b490e7794f3f013536c0fb0b40195fff09689811c1a0887b3bfbc10ae",
+ "0xde1c709ab0e3d3fb72c4e5805bb81fae2b2d55c5da32ed1c0b995e7b5793b930"
+ ]
+}
\ No newline at end of file
diff --git a/src/test/tree/DelegationManagerUnit.tree b/src/test/tree/DelegationManagerUnit.tree
new file mode 100644
index 0000000000..0eabd751a6
--- /dev/null
+++ b/src/test/tree/DelegationManagerUnit.tree
@@ -0,0 +1,189 @@
+.
+├── DelegationManager Tree (*** denotes that integration tests are needed to validate path)
+├── when registerAsOperator is called
+│ ├── given that the caller has already registered as operator
+│ │ └── it should revert
+│ ├── it should call `_setOperatorDetails`
+│ │ ├── given that operatorDetails.earningsReceiver is 0 address
+│ │ │ └── it should revert
+│ │ ├── given operatorDetails.stakerOptOutWindowBlocks is > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS
+│ │ │ └── it should revert
+│ │ ├── given caller has already delegated to an operator
+│ │ │ └── it should revert
+│ │ └── it should emit an OperatorDetailsModified event
+│ └── it should call `_delegate`
+│ ├── given that delegation is paused
+│ │ └── it should revert
+│ ├── it should set the operator delegated to itself and emit a StakerDelegated event
+│ ├── given the caller has delegateable shares
+│ │ └── it should increase the operator's shares and and emit an OperatorSharesIncreased event
+│ └── it should push an operator stake update
+│ └── it should emit an OperatorRegistered event and OperatorMetadataURIUpdated event
+├── when modifyOperatorDetails is called
+│ ├── given caller is not an operator
+│ │ └── it should revert
+│ ├── given operatorDetails.earningsReceiver is 0 address
+│ │ └── it should revert
+│ ├── given operatorDetails.stakerOptOutWindowBlocks is > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS
+│ │ └── it should revert
+│ ├── given operatorDetails.stakerOptOutWindowBlocks is less than the current stakerOptOutWindowBlocks
+│ │ └── it should revert
+│ └── given caller is an operator and provides a valid earningsReceiver and stakerOptOutWindowBlocks
+│ └── it should update the operatorDetails and emit an OperatorDetailsModified event
+├── when updateOperatorMetadataURI is called
+│ ├── given caller is not an operator
+│ │ └── it should revert
+│ └── given caller is an operator
+│ └── it should emit an event
+├── when delegateTo is called
+│ └── it calls _delegate() (internal function) with msg.sender as the staker
+├── when delegateToBySignature is called
+│ ├── given block timestamp is > staker signature expiry
+│ │ └── it should revert
+│ ├── given staker signature verification fails
+│ │ └── it should revert
+│ └── given staker signature verification succeeds
+│ └── it should call _delegate() (internal function)
+├── when _delegate() is called
+│ ├── given that new delegations are paused
+│ │ └── it should revert
+│ ├── given staker is already delegated to an operator
+│ │ └── it should revert
+│ ├── given passed in operator param isn't a registered operator
+│ │ └── it should revert
+│ ├── given operator's delegationApprover is set to zero address OR given caller is the delegationApprover
+│ │ └── it should check delegatable shares and update accordingly (**below logic tree reused elsewhere**)
+│ │ ├── given staker doesn't have delegatable shares
+│ │ │ └── it should set staker delegated to operator, call the StakeRegistry, and emit events
+│ │ └── given staker has delegatable shares
+│ │ ├── given staker has EigenPod shares
+│ │ │ ├── given EigenPod shares are <= 0
+│ │ │ │ └── it should set staker delegated to operator, operator beaconChainStrategy shares unchanged, call the StakeRegistry, and emit events
+│ │ │ └── given EigenPod shares are > 0
+│ │ │ └── it should set staker delegated to operator, increase operator beaconChainStrategy shares, call the StakeRegistry, and emit events
+│ │ ├── given staker has StrategyManager shares
+│ │ │ └── it should set staker delegated to operator, increase operator StrategyManager shares, call the StakeRegistry, and emit events
+│ │ └── given staker has shares in both EigenPod and StrategyManager
+│ │ └── it should set staker delegated to operator, increase operator shares (EPM and SM), call the StakeRegistry, and emit events
+│ └── given operator's delegationApprover is set to nonzero address AND the caller is not the delegationApprover
+│ ├── given the delegationApprover is an EOA
+│ │ ├── given the block timestamp is past the expiry timestamp
+│ │ │ └── it should revert
+│ │ ├── given the delegationApprove salt has already been used
+│ │ │ └── it should revert
+│ │ ├── given the signature verification fails
+│ │ │ └── it should revert
+│ │ └── given the signature verification succeeds
+│ │ └── it should check delegatable shares and update accordingly (**logic tree reused from above**)
+│ └── given the delegationApprover is a contract
+│ ├── given the block timestamp is past the expiry timestamp
+│ │ └── it should revert
+│ ├── given the delegationApprove salt has already been used
+│ │ └── it should revert
+│ ├── given the contract isn't EIP1271 compliant
+│ │ └── it should revert
+│ ├── given the signature verification fails, isValidSignature() does not return EIP1271_MAGICVALUE
+│ │ └── it should revert
+│ └── given the signature verification succeeds, isValidSignature() returns EIP1271_MAGICVALUE
+│ └── it should check delegatable shares and update accordingly (**logic tree reused from above**)
+├── when undelegate is called
+│ ├── given caller is not delegated to an operator
+│ │ └── it should revert
+│ ├── given that the caller is registered as operator
+│ │ └── it should revert
+│ ├── given that staker param is zero address
+│ │ └── it should revert
+│ ├── given msg.sender is neither the staker, operator, or delegationApprover (if applicable)
+│ │ └── it should revert
+│ ├── given the msg.sender is the operator or delegationApprover
+│ │ └── it should emit a StakerForceUndelegated event
+│ └── it should emit a StakerUndelegatedEvent and undelegate the staker
+│ ├── given the staker doesn't have delegateable shares
+│ │ └── it should return a zero withdrawal root
+│ └── given the staker has delegateable shares ***
+│ └── it should call _removeSharesAndQueueWithdrawal
+├── when queueWithdrawals is called ***
+│ ├── given that entering the withdrawal queue is paused
+│ │ └── it should revert
+│ └── it should loop through each withdrawal and call _removeSharesAndQueueWithdrawal
+├── when _removeSharesAndQueueWithdrawal is called
+│ ├── given that the staker is a zero address
+│ │ └── it should revert
+│ ├── given that the length of strategies is 0
+│ │ └── it should revert
+│ └── it should loop through each strategy
+│ ├── given that the staker is delegated to (not zero address)
+│ │ └── it should decrease the operator's shares
+│ ├── given that the strategy is the beacon chain strategy
+│ │ └── it should remove shares from the eigen pod manager
+│ ├── given that the strategy is not the beacon chain eth strategy
+│ │ └── it should remove shares from the strategy manager
+│ ├── given that the staker is delegated to (not zero address)
+│ │ └── it should push a stake update
+│ ├── it should increment the staker's cumulativeWithdrawalsQueued
+│ ├── it should calculate and set the withdrawal root as pending
+│ └── it should emit a WithdrawalQueued event and return the withdrawal root
+├── when completeQueuedWithdrawal OR completeQueuedWithdrawals is called ***
+│ ├── given that the exiting the withdrawal queue is paused
+│ │ └── it should revert
+│ ├── given that the function is reentered
+│ │ └── it should revert
+│ └── it should call _completeQueuedWithdrawal (internal function) for each withdrawal
+├── when _completeQueuedWithdrawal is called ***
+│ ├── given that the withdrawal root is not pending
+│ │ └── it should revert
+│ ├── given that the withdrawal delay blocks period has not elapsed
+│ │ └── it should revert
+│ ├── given that the caller is not the withdrawer
+│ │ └── it should revert
+│ ├── given that receiveAsTokens is true
+│ │ └── given that the tokens and strategies length are not equal
+│ │ └── it should revert
+│ └── given that the above conditions are satisfied
+│ ├── it should delete the withdrawal root from pending withdrawals
+│ ├── given that receiveAsTokens is true
+│ │ └── it should call _withdrawSharesAsTokens for each strategy to withdraw from
+│ ├── given that receiveAsTokens is false
+│ │ ├── it should loop through each strategy to withdraw from
+│ │ ├── given that the strategy is the beaconChainETHStrategy
+│ │ │ ├── it should call addShares on the eigenPodManager with the staker as the original pod owner
+│ │ │ └── given that the staker is delegated to (operator not zero address)
+│ │ │ ├── it should increase the original pod operator's shares
+│ │ │ └── it should push a stake update for the original pod operator
+│ │ ├── given that the strategy is not the beaconChainETHStrategy
+│ │ │ ├── it should call addShares on the strategyManager with the staker as the withdrawer
+│ │ │ └── it should increase the operator's shares with the staker as the withdrawer
+│ │ └── it should push an operator stake update
+│ └── it should emit a WithdrawalCompleted event
+├── when _withdrawSharesAsTokens is called (internal function) ***
+│ ├── given that the strategy is the beaconChainStrategy
+│ │ └── it should call withdrawSharesAsTokens on the eigen pod manager
+│ └── given that the strategy is not the beaconChainStrategy
+│ └── it should call withdrawSharesAsTokens on the strategy manager
+├── when migrate queued withdrawals is called
+│ └── given that the withdrawal succesfully deletes from the strategy manager
+│ ├── it should calculate a new root
+│ │ └── given that the root is already pending
+│ │ └── it should revert
+│ └── it should emit WithdrawalQueued and WithdrawalMigrated events
+├── when increaseDelegatedShares is called
+│ ├── if the caller is not the strategy manager or eigen pod manager
+│ │ └── it should revert
+│ └── given that the staker is delegated
+│ ├── it should increase the operator's share for the staker and its associated strategy
+│ └── it should push an operator stake update
+├── when decreaseDelegatedShares is called
+│ ├── if the caller is not the strategy manager or eigen pod manager
+│ │ └── it should revert
+│ └── given that the staker is delegated
+│ ├── it should increase the operator's share for the staker and its associated strategy
+│ └── it should push an operator stake update
+└── when setStakeRegistry is called
+ ├── given not called by owner
+ │ └── it should revert
+ ├── given existing stakeRegistry address is set
+ │ └── it should revert
+ ├── given new stakeRegistry address is 0
+ │ └── it should revert
+ └── given called by owner, existing address not set, and new address is nonzero
+ └── it should set the new stakeRegistry address and emit event
\ No newline at end of file
diff --git a/src/test/tree/EigenPodManagerUnit.tree b/src/test/tree/EigenPodManagerUnit.tree
new file mode 100644
index 0000000000..b09fdad3f9
--- /dev/null
+++ b/src/test/tree/EigenPodManagerUnit.tree
@@ -0,0 +1,86 @@
+├── EigenPodManager Tree (*** denotes that integration tests are needed to validate path)
+├── when contract is deployed and initialized
+│ └── it should properly set storage
+├── when initialize called again
+│ └── it should revert
+├── when createPod called
+│ ├── given the user has already created a pod
+│ │ └── it should revert
+│ └── given the user has not created a pod
+│ └── it should deploy a pod
+├── when stake is called
+│ ├── given the user has not created a pod
+│ │ └── it should deploy a pod
+│ └── given the user has already created a pod
+│ └── it should call stake on the eigenPod
+├── when updateBeaconChainOracle is called
+│ ├── given the user is not the owner
+│ │ └── it should revert
+│ └── given the user is the owner
+│ └── it should set the beacon chain oracle
+├── when addShares is called
+│ ├── given that the caller is not the delegationManager
+│ │ └── it should revert
+│ ├── given that the podOwner address is 0
+│ │ └── it should revert
+│ ├── given that the shares amount is negative
+│ │ └── it should revert
+│ ├── given that the shares is not a whole gwei amount
+│ │ └── it should revert
+│ └── given that all of the above conditions are satisfied
+│ └── it should update the podOwnerShares
+├── when removeShares is called
+│ ├── given that the caller is not the delegationManager
+│ │ └── it should revert
+│ ├── given that the shares amount is negative
+│ │ └── it should revert
+│ ├── given that the shares is not a whole gwei amount
+│ │ └── it should revert
+│ ├── given that removing shares results in the pod owner having negative shares
+│ │ └── it should revert
+│ └── given that all of the above conditions are satisfied
+│ └── it should update the podOwnerShares
+├── when withdrawSharesAsTokens is called
+│ ├── given that the podOwner is address 0
+│ │ └── it should revert
+│ ├── given that the destination is address 0
+│ │ └── it should revert
+│ ├── given that the shares amount is negative
+│ │ └── it should revert
+│ ├── given that the shares is not a whole gwei amount
+│ │ └── it should revert
+│ ├── given that the current podOwner shares are negative
+│ │ ├── given that the shares to withdraw are larger in magnitude than the shares of the podOwner
+│ │ │ └── it should set the podOwnerShares to 0 and decrement shares to withdraw by the share deficit
+│ │ └── given that the shares to withdraw are smaller in magnitude than shares of the podOwner
+│ │ └── it should increment the podOwner shares by the shares to withdraw
+│ └── given that the pod owner shares are positive
+│ └── it should withdraw restaked ETH from the eigenPod
+├── when shares are adjusted
+│ ├── given that sharesBefore is negative or 0
+│ │ ├── given that sharesAfter is negative or zero
+│ │ │ └── the change in delegateable shares should be 0
+│ │ └── given that sharesAfter is positive
+│ │ └── the change in delegateable shares should be positive
+│ └── given that sharesBefore is positive
+│ ├── given that sharesAfter is negative or zero
+│ │ └── the change in delegateable shares is negative sharesBefore
+│ └── given that sharesAfter is positive
+│ └── the change in delegateable shares is the difference between sharesAfter and sharesBefore
+└── when recordBeaconChainETHBalanceUpdate is called
+ ├── given that the podOwner's eigenPod is not the caller
+ │ └── it should revert
+ ├── given that the podOwner is a zero address
+ │ └── it should revert
+ ├── given that sharesDelta is not a whole gwei amount
+ │ ├── it should revert
+ │ └── given that the shares delta is valid
+ │ └── it should update the podOwnerShares
+ ├── given that the change in delegateable shares is positive ***
+ │ └── it should increase delegated shares on the delegationManager
+ ├── given that the change in delegateable shares is negative ***
+ │ └── it should decrease delegated shares on the delegationManager
+ ├── given that the change in delegateable shares is 0 ***
+ │ └── it should only update the podOwnerShares
+ └── given that the function is reentered ***
+ └── it should revert
\ No newline at end of file
diff --git a/src/test/tree/EigenPodUnit.tree b/src/test/tree/EigenPodUnit.tree
new file mode 100644
index 0000000000..92a2c4e450
--- /dev/null
+++ b/src/test/tree/EigenPodUnit.tree
@@ -0,0 +1,171 @@
+.
+├── EigenPod Tree (*** denotes that integration tests are needed to validate path)
+├── when the contract is deployed and initialized
+│ └── it should properly set storage
+├── when initialize called again
+│ └── it should revert
+├── // Balance Update Tree
+├── when verifyBalanceUpdates is called ***
+│ ├── given that balance updates are paused
+│ │ └── it should revert
+│ ├── given that the indices and proofs are different lengths
+│ │ └── it should revert
+│ ├── given that the oracle timestamp is stale
+│ │ └── it should revert
+│ ├── given that the beacon state root proof is invalid
+│ │ └── it should revert
+│ └── given that the above conditions are satisfied
+│ ├── it should call _verifyBalanceUpdate for each balance update
+│ └── it should record a beaconChainETH balance update in the EPM
+├── when _verifyBalanceUpdate is called (internal function)
+│ ├── given that the most recent balance update timestamp is greater than or equal to the oracle timestamp
+│ │ └── it should revert
+│ ├── given that the validator status is not active
+│ │ └── it should revert
+│ ├── given that the validator withdrawable epoch is less than or equal to the epoch of the oracle timestamp
+│ │ └── given that the validator balance is equal to 0
+│ │ └── it should revert
+│ ├── given that the validator fields proof is not valid
+│ │ └── it should revert
+│ ├── given that the validator balances proof is not valid
+│ │ └── it should revert
+│ └── given that the above conditions are satisfied
+│ ├── given that the validator restaked balance is greater than the max restaked balance per validator
+│ │ └── it should set the new restaked balance to the validator restaked balance
+│ ├── given that the validator restaked balance is less than or equal to the max restaked balance per validator
+│ │ └── it should set the new restaked balance to the validator restaked balance
+│ ├── it should update the _validatorPubkeyHashToInfo mapping with the new restaked balance
+│ └── given that the new restaked balance is not equal to the validator restaked balance
+│ └── it should emit a validator balance updated event and return a non-zero sharesDeltaGwei
+├── // EigenPodManager Caller Tree
+├── when stake is called
+│ ├── given the caller is not the EigenPodManager
+│ │ └── it should revert
+│ ├── given the value staked is not 32 ETH
+│ │ └── it should revert
+│ └── given that all of the above conditions are satisfied
+│ └── it should stake ETH in the beacon chain deposit contract
+├── when withdrawRestakedBeaconChainETH is called - function only relevant when `withdrawableRestakedExecutionLayerGwei` is incremented after a full withdrawal
+│ ├── given that the caller is not the EigenPodManager
+│ │ └── it should revert
+│ ├── given that the amount to withdraw is not a whole Gwei amount
+│ │ └── it should revert
+│ ├── given that the amount to withdraw is greater than the withdrawable restaked execution layer amount
+│ │ └── it should revert
+│ └── given the above conditions are satisfied
+│ └── it should send eth from the pod to the recipient
+├── // EigenPodOwner Caller Tree
+├── when verifyWithdrawalCredentials is called ***
+│ ├── given that the caller is not the eigen pod Owner
+│ │ └── it should revert
+│ ├── given that verify credentials is paused
+│ │ └── it should revert
+│ ├── given that the proof is not valid for the timestamp
+│ │ └── it should revert
+│ ├── given that restaking is not enabled
+│ │ └── it should revert
+│ ├── given that the validator indices, proofs, and validator fields are different lengths
+│ │ └── it should revert
+│ ├── given that the withdrawal credential proof is stale
+│ │ └── it should revert
+│ ├── given that the beacon state root proof is invalid
+│ │ └── it should revert
+│ ├── it should call _verifyWithdrawalCredentials for each validator
+│ └── it should record a beaconChainETH balance update in the EPM
+├── when _verifyWithdrawalCredentials is called (internal function)
+│ ├── given that the validators status is inactive
+│ │ └── it should revert
+│ ├── given that validator's withdrawal credentials does not correspond to the pod withdrawal credentials
+│ │ └── it should revert
+│ ├── given that the validator fields proof is not valid
+│ │ └── it should revert
+│ └── given that all the above conditions are satisfied
+│ ├── given that the validator effective balance is greater than the max restaked balance
+│ │ └── it should set the validator restaked balance to the max restaked balance
+│ ├── given that the validator effective balance is less than or equal to the max restaked balance
+│ │ └── it should set the validator restaked balance to the validator effective balance
+│ ├── it should update the _validatorPubkeyHashToInfo mapping with an active validator and restaked balance
+│ ├── it should emit ValidatorRestaked and ValidatorBalanceUpdated Events
+│ └── It should return the validator's restakedBalance in wei
+├── when withdrawNonBeaconChainETHBalanceWei is called
+│ ├── given that the caller is not the eigen pod owner
+│ │ └── it should revert
+│ ├── given that the amount to withdraw is greater than the non-beacon chain eth balance
+│ │ └── it should revert
+│ └── given the above conditions pass
+│ └── it should emit a non beacon chain eth withdrawn event and send eth to the delayed withdrawal router
+├── when recoverTokens is called
+│ ├── given that the caller is not the eigen pod owner
+│ │ └── it should revert
+│ ├── given that the tokens and amounts to withdraw are different lengths
+│ │ └── it should revert
+│ └── given that the above conditions pass
+│ └── it should transfer tokens to the recipient
+├── when activate restaking is called
+│ ├── given that the eigenpods verify credentials is not paused ***
+│ │ └── it should revert
+│ ├── given that the caller is not the eigen pod owner
+│ │ └── it should revert
+│ ├── given that hasRestaked is true
+│ │ └── it should revert
+│ └── given that all the above conditions pass
+│ └── it should set hasRestaked to true, process a withdrawal of ETH to the delayed withdrawal router, and emit a RestakingActivated event
+├── when withdrawBeforeRestaking is called
+│ ├── given that the caller is not the eigen pod owner
+│ │ └── it should revert
+│ ├── given that has restaked is true
+│ │ └── it should revert
+│ └── given that the above conditions pass
+│ └── it should process a withdrawal of eth to the delayed withdrawal router
+├── // Withdrawal Tree
+├── when verifyAndProcessWithdrawals is called ***
+│ ├── given that verifying withdrawals are paused
+│ │ └── it should revert
+│ ├── given that validatorFields, validatorProofs, withdrawalProofs, withdrawalFields, are different lengths
+│ │ └── it should revert
+│ ├── given that the beacon state root proof is invalid
+│ │ └── it should revert
+│ ├── given that the above conditions are satisfied
+│ ├── it should call _verifyAndProcessWithdrawal
+│ ├── given that the amount of ETH to withdraw immediately is greater than 0
+│ │ └── it should send the ETH to the delayed withdrawal router
+│ └── given that the pod's shares have are not 0
+│ └── it should record a beacon chain balance update in the EPM
+└── when _verifyAndProcessWithdrawal is called (internal function)
+ ├── given that the proof timestamp is stale
+ │ └── it should revert
+ ├── given that the status of the validator is inactive
+ │ └── it should revert
+ ├── given that the withdrawalTimestamp has already been proven for the validator
+ │ └── it should revert
+ ├── given that the withdrawal proof is invalid
+ │ └── it should revert
+ ├── given that the validator fields proof is invalid
+ │ └── it should revert
+ └── given that the above conditions are satisfied
+ ├── it should set the withdrawal timestamp as proven for the validator pubKey
+ ├── given that the epoch of the proof is after the withdrawable epoch
+ │ ├── it should call _processFullWithdrawal
+ │ └── when _processFullWithdrawal is called (internal function)
+ │ ├── given that the withdrawalAmount is greater than the max restaked balance per validator
+ │ │ └── it should set the amount to queue to the max restaked balance per validator
+ │ ├── given that the withdrawalAmount is less than or equal to the max restaked balance per validator
+ │ │ └── it should set the amount to queue to the withdrawal amount
+ │ ├── it should set the amount of ETH to withdraw via the delayed withdrawal router as the difference between the withdrawalAmount and amount to queue
+ │ ├── it should increment withdrawableRestakedExecutionLayerGwei by the amount to queue
+ │ ├── it should update the sharesDelta of the withdrawal as the difference between the amount to queue and the previous restaked balance
+ │ ├── it should update the _validatorPubkeyHashToInfo mapping with a restaked balance of 0 and status as withdrawn
+ │ └── it should emit a FullWithdrawalRedeemed event and return the verified withdrawal struct
+ └── given that the epoch of the proof is before the withdrawable epoch
+ ├── it should call _processPartialWithdrawal
+ └── when _processPartialWithdrawal is called (internal function)
+ ├── it should emit a PartialWithdrawalRedeemed event
+ ├── it should increment the sumOfPartialWithdrawalsClaimedGwei variable
+ └── it should return the verified withdrawal struct
+
+// Tests in Integration
+// Pausing Functionality
+// verifyWithdrawalCredentials (external)
+// verifyBalanceUpdates (external)
+// verifyAndProcessWithdrawals(external)
+// Withdraw restaked beacon chain ETH after withdrawing
\ No newline at end of file
diff --git a/src/test/tree/StrategyManagerUnit.tree b/src/test/tree/StrategyManagerUnit.tree
new file mode 100644
index 0000000000..62340c8b86
--- /dev/null
+++ b/src/test/tree/StrategyManagerUnit.tree
@@ -0,0 +1,111 @@
+├── StrategyManagerUnit.t.sol (*** denotes that integration tests are needed to validate path)
+├── initialize
+| ├── given that initialized is only called once
+│ │ └── it should set the storage variables correctly (owner, strategyWhitelister, pauserRegistry)
+│ └── given that initialize is called again
+│ └── it should revert
+├── depositIntoStrategy()
+│ ├── given that deposits paused
+│ │ └── it should revert
+│ ├── given the function is re-entered
+│ │ └── it should revert
+│ ├── given that the strategy is not whitelisted
+│ │ └── it should revert
+│ ├── given the token safeTransferFrom() reverts
+│ │ └── it should revert
+│ └── given that token safeTransferFrom() succeeds
+│ ├── given the staker has existing shares in strategy (not first deposit)
+│ │ └── it should increase shares, nonce. while stakerStrategyListLength is unchanged
+│ ├── given the staker has no existing shares in strategy (first deposit)
+│ │ └── stakerStrategyListLength increases by 1 and shares increase
+│ ├── given the staker has delegated to a operator ***
+│ │ └── it should deposit successfully with shares increase, including delegated shares
+│ └── given the staker is not delegated
+│ └── it should deposit successfully with shares increase
+├── depositIntoStrategyWithSignature()
+│ ├── given that deposits paused
+│ │ └── it should revert
+│ ├── given the function is re-entered
+│ │ └── it should revert
+│ ├── given the signature expired
+│ │ └── it should revert
+│ ├── given that deposits paused and strategy not whitelisted
+│ │ └── it should revert
+│ ├── given the staker is a EOA
+│ │ ├── given the signature verification fails
+│ │ │ └── it should revert
+│ │ └── given the signature verification succeeds
+│ │ ├── given the token safeTransferFrom reverts
+│ │ │ └── it should revert
+│ │ └── given the token safeTransferFrom succeeds
+│ │ ├── given that the staker has delegated to a operator ***
+│ │ │ └── it should deposit successfully with shares and nonce increase, including delegated shares
+│ │ └── given that the staker is not delegated
+│ │ └── it should deposit successfully with shares and nonce increase
+│ └── given the staker is a contract
+│ ├── given the contract isn't EIP1271 compliant
+│ │ └── it should revert
+│ ├── given the signature verification fails, isValidSignature() return != EIP1271_MAGICVALUE
+│ │ └── it should revert
+│ └── given the signature verification succeeds, isValidSignature() returns EIP1271_MAGICVALUE
+│ ├── given the token safeTransferFrom reverts
+│ │ └── it should revert
+│ └── given the token safeTransferFrom succeeds
+│ ├── given the staker has delegated to a operator ***
+│ │ └── it should deposit successfully with shares and nonce increase, including delegated shares
+│ └── given the staker is not delegated
+│ └── it should deposit successfully with shares and nonce increase
+├── removeShares()
+│ ├── given not called by DelegationManager
+│ │ └── it should revert
+│ ├── given the share amount is 0
+│ │ └── it should revert
+│ ├── given the share amount is too high, higher than deposited amount
+│ │ └── it should revert
+│ ├── given the share amount is equal to the deposited amount
+│ │ └── staker shares should be 0 with decremented stakerStrategyListLength
+│ └── given the share amount is less than the deposited amount
+│ └── staker shares should now be deposited - shares amount, unchanged stakerStrategyListLength
+├── addShares()
+│ ├── given not called by DelegationManager
+│ │ └── it should revert
+│ ├── given the share amount is 0
+│ │ └── it should revert
+│ ├── given the staker is 0 address
+│ │ └── it should revert
+│ ├── given adding shares with 0 existing shares
+│ │ └── it should increase shares and increment stakerStrategyListLength
+│ ├── given adding shares with 0 existing shares and staker has MAX_STAKER_STRATEGY_LIST_LENGTH
+│ │ └── it should revert
+│ └── given the adding shares with > 0 existing shares
+│ └── it should increase shares, unchanged stakerStrategyListLength
+├── withdrawSharesAsTokens()
+│ ├── given not called by DelegationManager
+│ │ └── it should revert
+│ └── given that deposited strategy is called
+│ │ └── it should withdraw tokens from strategy with token balanceOf() update
+├── setStrategyWhitelister()
+│ ├── given not called by owner
+│ │ └── it should revert
+│ └── given called by owner address
+│ └── it should update strategyWhitelister address
+├── addStrategiesToDepositWhitelist()
+│ ├── given not called by strategyWhitelister address
+│ │ └── it should revert
+│ └── given the strategyWhitelister address is called
+│ ├── given adding one single strategy that is already whitelisted
+│ │ └── it should not emit StrategyAddedToDepositWhitelist with mapping still true
+│ ├── given adding one single strategy
+│ │ └── it should whitelist the new strategy with mapping set to true
+│ └── given adding multiple strategies to whitelist
+│ └── it should whitelist all new strategies with mappings set to true
+└── removeStrategiesFromDepositWhitelist()
+ ├── given not called by strategyWhitelister address
+ │ └── it should revert
+ └── given called by strategyWhitelister address
+ ├── given removing one single strategy that is not whitelisted
+ │ └── it shouldn't emit StrategyRemovedFromDepositWhitelist with mapping still false
+ ├── given removing one single strategy
+ │ └── it should de-whitelist the new strategy with mapping set to false
+ └── given removing multiple strategies to whitelist
+ └── it should de-whitelist all specified strategies with mappings set to false
\ 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 0000000000..8876651ca4
--- /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/BeaconChainOracleUnit.t.sol b/src/test/unit/BeaconChainOracleUnit.t.sol
deleted file mode 100644
index 2df0b1df97..0000000000
--- a/src/test/unit/BeaconChainOracleUnit.t.sol
+++ /dev/null
@@ -1,298 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../contracts/pods/BeaconChainOracle.sol";
-
-import "forge-std/Test.sol";
-
-contract BeaconChainOracleUnitTests is Test {
-
- Vm cheats = Vm(HEVM_ADDRESS);
-
- BeaconChainOracle public beaconChainOracle;
-
- address public initialBeaconChainOwner = address(this);
- uint256 public initialBeaconChainOracleThreshold = 2;
- uint256 public minThreshold;
-
- mapping(address => bool) public addressIsExcludedFromFuzzedInputs;
-
- // static values reused across several tests
- uint256 numberPotentialOracleSigners = 16;
- address[] public potentialOracleSigners;
- uint64 public blockNumberToVoteFor = 5151;
- bytes32 public stateRootToVoteFor = bytes32(uint256(987));
-
- modifier filterFuzzedAddressInputs(address fuzzedAddress) {
- cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]);
- _;
- }
-
- function setUp() external {
- address[] memory initialOracleSignersArray = new address[](0);
- beaconChainOracle = new BeaconChainOracle(initialBeaconChainOwner, initialBeaconChainOracleThreshold, initialOracleSignersArray);
- minThreshold = beaconChainOracle.MINIMUM_THRESHOLD();
-
- // set up array for use in testing
- for (uint256 i = 0; i < numberPotentialOracleSigners; ++i) {
- potentialOracleSigners.push(address(uint160(777 + i)));
- }
- }
-
- function testConstructor_RevertsOnThresholdTooLow() external {
- address[] memory initialOracleSignersArray = new address[](0);
- // check that deployment fails when trying to set threshold below `MINIMUM_THRESHOLD`
- cheats.expectRevert(bytes("BeaconChainOracle._setThreshold: cannot set threshold below MINIMUM_THRESHOLD"));
- new BeaconChainOracle(initialBeaconChainOwner, minThreshold - 1, initialOracleSignersArray);
-
- // check that deployment succeeds when trying to set threshold *at* (i.e. equal to) `MINIMUM_THRESHOLD`
- beaconChainOracle = new BeaconChainOracle(initialBeaconChainOwner, minThreshold, initialOracleSignersArray);
- }
-
- function testSetThreshold(uint256 newThreshold) public {
- // filter out disallowed inputs
- cheats.assume(newThreshold >= minThreshold);
-
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.setThreshold(newThreshold);
- cheats.stopPrank();
-
- assertEq(newThreshold, beaconChainOracle.threshold());
- }
-
- function testSetThreshold_RevertsOnThresholdTooLow() external {
- cheats.startPrank(beaconChainOracle.owner());
- cheats.expectRevert(bytes("BeaconChainOracle._setThreshold: cannot set threshold below MINIMUM_THRESHOLD"));
- beaconChainOracle.setThreshold(minThreshold - 1);
- cheats.stopPrank();
-
- // make sure it works *at* (i.e. equal to) the threshold
- testSetThreshold(minThreshold);
- }
-
- function testSetThreshold_RevertsOnCallingFromNotOwner(address notOwner) external {
- cheats.assume(notOwner != beaconChainOracle.owner());
-
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- beaconChainOracle.setThreshold(minThreshold);
- cheats.stopPrank();
- }
-
- function testAddOracleSigner(address signerToAdd) public {
- uint256 totalSignersBefore = beaconChainOracle.totalOracleSigners();
- bool alreadySigner = beaconChainOracle.isOracleSigner(signerToAdd);
-
- address[] memory signerArray = new address[](1);
- signerArray[0] = signerToAdd;
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.addOracleSigners(signerArray);
- cheats.stopPrank();
-
- uint256 totalSignersAfter = beaconChainOracle.totalOracleSigners();
- require(beaconChainOracle.isOracleSigner(signerToAdd), "signer not added");
- if (alreadySigner) {
- require(totalSignersAfter == totalSignersBefore, "totalSigners incremented incorrectly");
- } else {
- require(totalSignersAfter == totalSignersBefore + 1, "totalSigners did not increment correctly");
- }
- }
-
- function testAddOracleSigners(uint8 amountSignersToAdd) public {
- cheats.assume(amountSignersToAdd <= numberPotentialOracleSigners);
- uint256 totalSignersBefore = beaconChainOracle.totalOracleSigners();
-
- // copy array to memory
- address[] memory signerArray = new address[](amountSignersToAdd);
- for (uint256 i = 0; i < amountSignersToAdd; ++i) {
- signerArray[i] = potentialOracleSigners[i];
- }
-
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.addOracleSigners(signerArray);
- cheats.stopPrank();
-
- // check post conditions
- uint256 totalSignersAfter = beaconChainOracle.totalOracleSigners();
- for (uint256 i = 0; i < amountSignersToAdd; ++i) {
- require(beaconChainOracle.isOracleSigner(signerArray[i]), "signer not added");
- }
- require(totalSignersAfter == totalSignersBefore + amountSignersToAdd, "totalSigners did not increment correctly");
- }
-
- function testAddOracleSigners_SignerAlreadyInSet() external {
- address oracleSigner = potentialOracleSigners[0];
- address[] memory signerArray = new address[](1);
- signerArray[0] = oracleSigner;
- testAddOracleSigner(oracleSigner);
-
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.addOracleSigners(signerArray);
- cheats.stopPrank();
-
- require(beaconChainOracle.isOracleSigner(oracleSigner), "signer improperly removed");
- }
-
- function testAddOracleSigners_RevertsOnCallingFromNotOwner(address notOwner) external {
- cheats.assume(notOwner != beaconChainOracle.owner());
- address oracleSigner = potentialOracleSigners[0];
- address[] memory signerArray = new address[](1);
- signerArray[0] = oracleSigner;
-
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- beaconChainOracle.addOracleSigners(signerArray);
- cheats.stopPrank();
-
- require(!beaconChainOracle.isOracleSigner(oracleSigner), "signer improperly added");
- }
-
- function testRemoveOracleSigner(address signerToRemove) public {
- uint256 totalSignersBefore = beaconChainOracle.totalOracleSigners();
- bool alreadySigner = beaconChainOracle.isOracleSigner(signerToRemove);
-
- address[] memory signerArray = new address[](1);
- signerArray[0] = signerToRemove;
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.removeOracleSigners(signerArray);
- cheats.stopPrank();
-
- uint256 totalSignersAfter = beaconChainOracle.totalOracleSigners();
- require(!beaconChainOracle.isOracleSigner(signerToRemove), "signer not removed");
- if (alreadySigner) {
- require(totalSignersAfter == totalSignersBefore - 1, "totalSigners did not decrement correctly");
- } else {
- require(totalSignersAfter == totalSignersBefore, "totalSigners decremented incorrectly");
- }
- }
-
- function testRemoveOracleSigners(uint8 amountSignersToAdd, uint8 amountSignersToRemove) external {
- cheats.assume(amountSignersToAdd <= numberPotentialOracleSigners);
- cheats.assume(amountSignersToRemove <= numberPotentialOracleSigners);
- testAddOracleSigners(amountSignersToAdd);
-
- uint256 totalSignersBefore = beaconChainOracle.totalOracleSigners();
-
- // copy array to memory
- address[] memory signerArray = new address[](amountSignersToRemove);
- for (uint256 i = 0; i < amountSignersToRemove; ++i) {
- signerArray[i] = potentialOracleSigners[i];
- }
-
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.removeOracleSigners(signerArray);
- cheats.stopPrank();
-
- // check post conditions
- uint256 totalSignersAfter = beaconChainOracle.totalOracleSigners();
- for (uint256 i = 0; i < amountSignersToRemove; ++i) {
- require(!beaconChainOracle.isOracleSigner(signerArray[i]), "signer not removed");
- }
- uint256 amountThatShouldHaveBeenRemoved = amountSignersToRemove > amountSignersToAdd ? amountSignersToAdd : amountSignersToRemove;
- require(totalSignersAfter + amountThatShouldHaveBeenRemoved == totalSignersBefore, "totalSigners did not decrement correctly");
- }
-
- function testRemoveOracleSigners_SignerAlreadyNotInSet() external {
- address oracleSigner = potentialOracleSigners[0];
- address[] memory signerArray = new address[](1);
- signerArray[0] = oracleSigner;
-
- cheats.startPrank(beaconChainOracle.owner());
- beaconChainOracle.removeOracleSigners(signerArray);
- cheats.stopPrank();
-
- require(!beaconChainOracle.isOracleSigner(oracleSigner), "signer improperly added");
- }
-
- function testRemoveOracleSigners_RevertsOnCallingFromNotOwner(address notOwner) external {
- cheats.assume(notOwner != beaconChainOracle.owner());
- address oracleSigner = potentialOracleSigners[0];
- address[] memory signerArray = new address[](1);
- signerArray[0] = oracleSigner;
-
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- beaconChainOracle.removeOracleSigners(signerArray);
- cheats.stopPrank();
-
- require(!beaconChainOracle.isOracleSigner(oracleSigner), "signer improperly added");
- }
-
- function testVoteForBeaconChainStateRoot(address oracleSigner, uint64 _blockNumber, bytes32 _stateRoot) public {
- uint256 votesBefore = beaconChainOracle.stateRootVotes(_blockNumber, _stateRoot);
-
- testAddOracleSigner(oracleSigner);
- cheats.startPrank(oracleSigner);
- beaconChainOracle.voteForBeaconChainStateRoot(_blockNumber, _stateRoot);
- cheats.stopPrank();
-
- uint256 votesAfter = beaconChainOracle.stateRootVotes(_blockNumber, _stateRoot);
- require(votesAfter == votesBefore + 1, "votesAfter != votesBefore + 1");
- require(beaconChainOracle.hasVoted(_blockNumber, oracleSigner), "vote not recorded as being cast");
- if (votesAfter == beaconChainOracle.threshold()) {
- assertEq(beaconChainOracle.beaconStateRootAtBlockNumber(_blockNumber), _stateRoot, "state root not confirmed when it should be");
- } else {
- require(beaconChainOracle.beaconStateRootAtBlockNumber(_blockNumber) == bytes32(0), "state root improperly confirmed");
- }
- }
-
- function testVoteForBeaconChainStateRoot_VoteDoesNotCauseConfirmation() public {
- address _oracleSigner = potentialOracleSigners[0];
- testVoteForBeaconChainStateRoot(_oracleSigner, blockNumberToVoteFor, stateRootToVoteFor);
- }
-
- function testVoteForBeaconChainStateRoot_VoteCausesConfirmation(uint64 _blockNumber, bytes32 _stateRoot) public {
- uint64 latestConfirmedOracleBlockNumberBefore = beaconChainOracle.latestConfirmedOracleBlockNumber();
-
- uint256 votesBefore = beaconChainOracle.stateRootVotes(_blockNumber, _stateRoot);
- require(votesBefore == 0, "something is wrong, state root should have zero votes before voting");
-
- for (uint256 i = 0; i < beaconChainOracle.threshold(); ++i) {
- testVoteForBeaconChainStateRoot(potentialOracleSigners[i], _blockNumber, _stateRoot);
- }
-
- assertEq(beaconChainOracle.beaconStateRootAtBlockNumber(_blockNumber), _stateRoot, "state root not confirmed when it should be");
- assertEq(beaconChainOracle.threshold(), beaconChainOracle.stateRootVotes(_blockNumber, _stateRoot), "state root confirmed with incorrect votes");
-
- if (_blockNumber > latestConfirmedOracleBlockNumberBefore) {
- assertEq(_blockNumber, beaconChainOracle.latestConfirmedOracleBlockNumber(), "latestConfirmedOracleBlockNumber did not update appropriately");
- } else {
- assertEq(latestConfirmedOracleBlockNumberBefore, beaconChainOracle.latestConfirmedOracleBlockNumber(), "latestConfirmedOracleBlockNumber updated inappropriately");
- }
- }
-
- function testVoteForBeaconChainStateRoot_VoteCausesConfirmation_latestOracleBlockNumberDoesNotIncrease() external {
- testVoteForBeaconChainStateRoot_VoteCausesConfirmation(blockNumberToVoteFor + 1, stateRootToVoteFor);
- uint64 latestConfirmedOracleBlockNumberBefore = beaconChainOracle.latestConfirmedOracleBlockNumber();
- testVoteForBeaconChainStateRoot_VoteCausesConfirmation(blockNumberToVoteFor, stateRootToVoteFor);
- assertEq(latestConfirmedOracleBlockNumberBefore, beaconChainOracle.latestConfirmedOracleBlockNumber(), "latestConfirmedOracleBlockNumber updated inappropriately");
- }
-
- function testVoteForBeaconChainStateRoot_RevertsWhenCallerHasVoted() external {
- address _oracleSigner = potentialOracleSigners[0];
- testVoteForBeaconChainStateRoot(_oracleSigner, blockNumberToVoteFor, stateRootToVoteFor);
-
- cheats.startPrank(_oracleSigner);
- cheats.expectRevert(bytes("BeaconChainOracle.voteForBeaconChainStateRoot: Signer has already voted"));
- beaconChainOracle.voteForBeaconChainStateRoot(blockNumberToVoteFor, stateRootToVoteFor);
- cheats.stopPrank();
- }
-
- function testVoteForBeaconChainStateRoot_RevertsWhenStateRootAlreadyConfirmed() external {
- address _oracleSigner = potentialOracleSigners[potentialOracleSigners.length - 1];
- testAddOracleSigner(_oracleSigner);
- testVoteForBeaconChainStateRoot_VoteCausesConfirmation(blockNumberToVoteFor, stateRootToVoteFor);
-
- cheats.startPrank(_oracleSigner);
- cheats.expectRevert(bytes("BeaconChainOracle.voteForBeaconChainStateRoot: State root already confirmed"));
- beaconChainOracle.voteForBeaconChainStateRoot(blockNumberToVoteFor, stateRootToVoteFor);
- cheats.stopPrank();
- }
-
- function testVoteForBeaconChainStateRoot_RevertsWhenCallingFromNotOracleSigner(address notOracleSigner) external {
- cheats.startPrank(notOracleSigner);
- cheats.expectRevert(bytes("BeaconChainOracle.onlyOracleSigner: Not an oracle signer"));
- beaconChainOracle.voteForBeaconChainStateRoot(blockNumberToVoteFor, stateRootToVoteFor);
- cheats.stopPrank();
- }
-}
\ No newline at end of file
diff --git a/src/test/unit/BytesArrayBitmapsUnit.t.sol b/src/test/unit/BytesArrayBitmapsUnit.t.sol
deleted file mode 100644
index d107f2122b..0000000000
--- a/src/test/unit/BytesArrayBitmapsUnit.t.sol
+++ /dev/null
@@ -1,155 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../harnesses/BytesArrayBitmapsWrapper.sol";
-// import "../../contracts/libraries/BytesArrayBitmaps.sol";
-
-import "forge-std/Test.sol";
-
-contract BytesArrayBitmapsUnitTests is Test {
- Vm cheats = Vm(HEVM_ADDRESS);
-
- BytesArrayBitmapsWrapper public bytesArrayBitmapsWrapper;
-
- function setUp() public {
- bytesArrayBitmapsWrapper = new BytesArrayBitmapsWrapper();
- }
-
- // ensure that the bitmap encoding of an emtpy bytes array is an emtpy bitmap (function doesn't revert and approriately returns uint256(0))
- function testEmptyArrayEncoding() public view {
- bytes memory emptyBytesArray;
- uint256 returnedBitMap = bytesArrayBitmapsWrapper.bytesArrayToBitmap(emptyBytesArray);
- require(returnedBitMap == 0, "BytesArrayBitmapsUnitTests.testEmptyArrayEncoding: empty array not encoded to empty bitmap");
- }
-
- // ensure that the bitmap encoding of a single uint8 (i.e. a single byte) matches the expected output
- function testSingleByteEncoding(uint8 fuzzedNumber) public view {
- bytes1 singleByte = bytes1(fuzzedNumber);
- bytes memory bytesArray = abi.encodePacked(singleByte);
- uint256 returnedBitMap = bytesArrayBitmapsWrapper.bytesArrayToBitmap(bytesArray);
- uint256 bitMask = uint256(1 << fuzzedNumber);
- require(returnedBitMap == bitMask, "BytesArrayBitmapsUnitTests.testSingleByteEncoding: non-equivalence");
- }
-
- // ensure that the bitmap encoding of a two uint8's (i.e. a two byte array) matches the expected output
- function testTwoByteEncoding(uint8 firstFuzzedNumber, uint8 secondFuzzedNumber) public {
- bytes1 firstSingleByte = bytes1(firstFuzzedNumber);
- bytes1 secondSingleByte = bytes1(secondFuzzedNumber);
- bytes memory bytesArray = abi.encodePacked(firstSingleByte, secondSingleByte);
- if (firstFuzzedNumber == secondFuzzedNumber) {
- cheats.expectRevert(bytes("BytesArrayBitmaps.bytesArrayToBitmap: repeat entry in bytesArray"));
- bytesArrayBitmapsWrapper.bytesArrayToBitmap(bytesArray);
- } else {
- uint256 returnedBitMap = bytesArrayBitmapsWrapper.bytesArrayToBitmap(bytesArray);
- uint256 firstBitMask = uint256(1 << firstFuzzedNumber);
- uint256 secondBitMask = uint256(1 << secondFuzzedNumber);
- uint256 combinedBitMask = firstBitMask | secondBitMask;
- require(returnedBitMap == combinedBitMask, "BytesArrayBitmapsUnitTests.testTwoByteEncoding: non-equivalence");
- }
- }
-
- // ensure that converting bytes array => bitmap => bytes array is returns the original bytes array (i.e. is lossless and artifactless)
- // note that this only works on ordered arrays, because unordered arrays will be returned ordered
- function testBytesArrayToBitmapToBytesArray(bytes memory originalBytesArray) public view {
- // filter down to only ordered inputs
- cheats.assume(bytesArrayBitmapsWrapper.isArrayStrictlyAscendingOrdered(originalBytesArray));
- uint256 bitmap = bytesArrayBitmapsWrapper.bytesArrayToBitmap(originalBytesArray);
- bytes memory returnedBytesArray = bytesArrayBitmapsWrapper.bitmapToBytesArray(bitmap);
- // emit log_named_bytes("originalBytesArray", originalBytesArray);
- // emit log_named_uint("bitmap", bitmap);
- // emit log_named_bytes("returnedBytesArray", returnedBytesArray);
- require(keccak256(abi.encodePacked(originalBytesArray)) == keccak256(abi.encodePacked(returnedBytesArray)),
- "BytesArrayBitmapsUnitTests.testBytesArrayToBitmapToBytesArray: output doesn't match input");
- }
-
- // ensure that converting bytes array => bitmap => bytes array is returns the original bytes array (i.e. is lossless and artifactless)
- // note that this only works on ordered arrays, because unordered arrays will be returned ordered
- function testBytesArrayToBitmapToBytesArray_Yul(bytes memory originalBytesArray) public view {
- // filter down to only ordered inputs
- cheats.assume(bytesArrayBitmapsWrapper.isArrayStrictlyAscendingOrdered(originalBytesArray));
- uint256 bitmap = bytesArrayBitmapsWrapper.bytesArrayToBitmap_Yul(originalBytesArray);
- bytes memory returnedBytesArray = bytesArrayBitmapsWrapper.bitmapToBytesArray(bitmap);
- // emit log_named_bytes("originalBytesArray", originalBytesArray);
- // emit log_named_uint("bitmap", bitmap);
- // emit log_named_bytes("returnedBytesArray", returnedBytesArray);
- require(keccak256(abi.encodePacked(originalBytesArray)) == keccak256(abi.encodePacked(returnedBytesArray)),
- "BytesArrayBitmapsUnitTests.testBytesArrayToBitmapToBytesArray: output doesn't match input");
- }
-
- // ensure that converting bytes array => bitmap => bytes array is returns the original bytes array (i.e. is lossless and artifactless)
- // note that this only works on ordered arrays
- function testBytesArrayToBitmapToBytesArray_OrderedVersion(bytes memory originalBytesArray) public view {
- // filter down to only ordered inputs
- cheats.assume(bytesArrayBitmapsWrapper.isArrayStrictlyAscendingOrdered(originalBytesArray));
- uint256 bitmap = bytesArrayBitmapsWrapper.orderedBytesArrayToBitmap(originalBytesArray);
- bytes memory returnedBytesArray = bytesArrayBitmapsWrapper.bitmapToBytesArray(bitmap);
- require(keccak256(abi.encodePacked(originalBytesArray)) == keccak256(abi.encodePacked(returnedBytesArray)),
- "BytesArrayBitmapsUnitTests.testBytesArrayToBitmapToBytesArray: output doesn't match input");
- }
-
- // ensure that converting bytes array => bitmap => bytes array is returns the original bytes array (i.e. is lossless and artifactless)
- // note that this only works on ordered arrays
- function testBytesArrayToBitmapToBytesArray_OrderedVersion_Yul(bytes memory originalBytesArray) public view {
- // filter down to only ordered inputs
- cheats.assume(bytesArrayBitmapsWrapper.isArrayStrictlyAscendingOrdered(originalBytesArray));
- uint256 bitmap = bytesArrayBitmapsWrapper.orderedBytesArrayToBitmap(originalBytesArray);
- bytes memory returnedBytesArray = bytesArrayBitmapsWrapper.bitmapToBytesArray(bitmap);
- require(keccak256(abi.encodePacked(originalBytesArray)) == keccak256(abi.encodePacked(returnedBytesArray)),
- "BytesArrayBitmapsUnitTests.testBytesArrayToBitmapToBytesArray: output doesn't match input");
- }
-
- // ensure that converting bitmap => bytes array => bitmap is returns the original bitmap (i.e. is lossless and artifactless)
- function testBitMapToBytesArrayToBitmap(uint256 originalBitmap) public view {
- bytes memory bytesArray = bytesArrayBitmapsWrapper.bitmapToBytesArray(originalBitmap);
- uint256 returnedBitMap = bytesArrayBitmapsWrapper.bytesArrayToBitmap(bytesArray);
- require(returnedBitMap == originalBitmap, "BytesArrayBitmapsUnitTests.testBitMapToArrayToBitmap: output doesn't match input");
- }
-
- // testing one function for a specific input. used for comparing gas costs
- function testBytesArrayToBitmap_OrderedVersion_Yul_SpecificInput(/*bytes memory originalBytesArray*/) public {
- bytes memory originalBytesArray =
- abi.encodePacked(bytes1(uint8(5)), bytes1(uint8(6)), bytes1(uint8(7)), bytes1(uint8(8)), bytes1(uint8(9)), bytes1(uint8(10)), bytes1(uint8(11)), bytes1(uint8(12)));
- // bytes memory originalBytesArray = abi.encodePacked(bytes1(uint8(5)));
- uint256 gasLeftBefore = gasleft();
- bytesArrayBitmapsWrapper.orderedBytesArrayToBitmap_Yul(originalBytesArray);
- uint256 gasLeftAfter = gasleft();
- uint256 gasSpent = gasLeftBefore - gasLeftAfter;
- emit log_named_uint("gasSpent", gasSpent);
- }
-
- // testing one function for a specific input. used for comparing gas costs
- function testBytesArrayToBitmap_OrderedVersion_SpecificInput(/*bytes memory originalBytesArray*/) public {
- bytes memory originalBytesArray =
- abi.encodePacked(bytes1(uint8(5)), bytes1(uint8(6)), bytes1(uint8(7)), bytes1(uint8(8)), bytes1(uint8(9)), bytes1(uint8(10)), bytes1(uint8(11)), bytes1(uint8(12)));
- // bytes memory originalBytesArray = abi.encodePacked(bytes1(uint8(5)));
- uint256 gasLeftBefore = gasleft();
- bytesArrayBitmapsWrapper.orderedBytesArrayToBitmap(originalBytesArray);
- uint256 gasLeftAfter = gasleft();
- uint256 gasSpent = gasLeftBefore - gasLeftAfter;
- emit log_named_uint("gasSpent", gasSpent);
- }
-
- // testing one function for a specific input. used for comparing gas costs
- function testBytesArrayToBitmap_SpecificInput(/*bytes memory originalBytesArray*/) public {
- bytes memory originalBytesArray =
- abi.encodePacked(bytes1(uint8(5)), bytes1(uint8(6)), bytes1(uint8(7)), bytes1(uint8(8)), bytes1(uint8(9)), bytes1(uint8(10)), bytes1(uint8(11)), bytes1(uint8(12)));
- // bytes memory originalBytesArray = abi.encodePacked(bytes1(uint8(5)));
- uint256 gasLeftBefore = gasleft();
- bytesArrayBitmapsWrapper.bytesArrayToBitmap(originalBytesArray);
- uint256 gasLeftAfter = gasleft();
- uint256 gasSpent = gasLeftBefore - gasLeftAfter;
- emit log_named_uint("gasSpent", gasSpent);
- }
-
- // testing one function for a specific input. used for comparing gas costs
- function testBytesArrayToBitmap_Yul_SpecificInput(/*bytes memory originalBytesArray*/) public {
- bytes memory originalBytesArray =
- abi.encodePacked(bytes1(uint8(5)), bytes1(uint8(6)), bytes1(uint8(7)), bytes1(uint8(8)), bytes1(uint8(9)), bytes1(uint8(10)), bytes1(uint8(11)), bytes1(uint8(12)));
- // bytes memory originalBytesArray = abi.encodePacked(bytes1(uint8(5)));
- uint256 gasLeftBefore = gasleft();
- bytesArrayBitmapsWrapper.bytesArrayToBitmap_Yul(originalBytesArray);
- uint256 gasLeftAfter = gasleft();
- uint256 gasSpent = gasLeftBefore - gasLeftAfter;
- emit log_named_uint("gasSpent", gasSpent);
- }
-}
\ No newline at end of file
diff --git a/src/test/unit/DelayedWithdrawalRouterUnit.t.sol b/src/test/unit/DelayedWithdrawalRouterUnit.t.sol
index b389ad5e7e..848d1319e0 100644
--- a/src/test/unit/DelayedWithdrawalRouterUnit.t.sol
+++ b/src/test/unit/DelayedWithdrawalRouterUnit.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
@@ -123,7 +123,7 @@ contract DelayedWithdrawalRouterUnitTests is Test {
cheats.assume(recipient != address(0));
IDelayedWithdrawalRouter.UserDelayedWithdrawals memory userWithdrawalsBefore = delayedWithdrawalRouter.userWithdrawals(recipient);
uint224 delayedWithdrawalAmount = 0;
-
+ cheats.assume(recipient != address(0));
address podAddress = address(eigenPodManagerMock.getPod(podOwner));
cheats.deal(podAddress, delayedWithdrawalAmount);
cheats.startPrank(podAddress);
@@ -256,7 +256,7 @@ contract DelayedWithdrawalRouterUnitTests is Test {
require(delayedWithdrawalRouter.getUserDelayedWithdrawals(recipient).length == delayedWithdrawalsCreated, "Incorrect number delayed withdrawals");
cheats.roll(block.number + delayedWithdrawalRouter.withdrawalDelayBlocks() - delayedWithdrawalsToCreate);
- for (uint i = 1; i <= delayedWithdrawalsToCreate; ++i) {
+ for (uint256 i = 1; i <= delayedWithdrawalsToCreate; ++i) {
uint256 length = delayedWithdrawalRouter.getClaimableUserDelayedWithdrawals(recipient).length;
require(length == i, "Incorrect number of claimable delayed withdrawals");
cheats.roll(block.number + 1);
@@ -471,4 +471,4 @@ contract DelayedWithdrawalRouterUnitTests is Test {
_pseudorandomNumber = uint256(keccak256(abi.encode(_pseudorandomNumber)));
return _pseudorandomNumber;
}
-}
\ No newline at end of file
+}
diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol
index b14183611f..6805993b42 100644
--- a/src/test/unit/DelegationUnit.t.sol
+++ b/src/test/unit/DelegationUnit.t.sol
@@ -1,279 +1,3352 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity ^0.8.9;
+pragma solidity ^0.8.12;
-import "forge-std/Test.sol";
+import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
+import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
-import "../mocks/StrategyManagerMock.sol";
+import "src/contracts/core/DelegationManager.sol";
+import "src/contracts/strategies/StrategyBase.sol";
-import "../mocks/SlasherMock.sol";
-import "../EigenLayerTestHelper.t.sol";
-import "../mocks/ERC20Mock.sol";
-import "../mocks/DelegationTermsMock.sol";
-import "../Delegation.t.sol";
+import "src/test/events/IDelegationManagerEvents.sol";
+import "src/test/utils/EigenLayerUnitTestSetup.sol";
-contract DelegationUnitTests is EigenLayerTestHelper {
-
- StrategyManagerMock strategyManagerMock;
- SlasherMock slasherMock;
+/**
+ * @notice Unit testing of the DelegationManager contract. Withdrawals are tightly coupled
+ * with EigenPodManager and StrategyManager and are part of integration tests.
+ * Contracts tested: DelegationManager
+ * Contracts not mocked: StrategyBase, PauserRegistry
+ */
+contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManagerEvents {
+ // Contract under test
DelegationManager delegationManager;
- DelegationTermsMock delegationTermsMock;
DelegationManager delegationManagerImplementation;
+
+ // Mocks
StrategyBase strategyImplementation;
StrategyBase strategyMock;
+ IERC20 mockToken;
+ uint256 mockTokenInitialSupply = 10e50;
+ // Delegation signer
+ uint256 delegationSignerPrivateKey = uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80);
+ uint256 stakerPrivateKey = uint256(123_456_789);
- uint256 GWEI_TO_WEI = 1e9;
+ // empty string reused across many tests
+ string emptyStringForMetadataURI;
- event OnDelegationReceivedCallFailure(IDelegationTerms indexed delegationTerms, bytes32 returnData);
- event OnDelegationWithdrawnCallFailure(IDelegationTerms indexed delegationTerms, bytes32 returnData);
+ // "empty" / zero salt, reused across many tests
+ bytes32 emptySalt;
+ // reused in various tests. in storage to help handle stack-too-deep errors
+ address defaultStaker = cheats.addr(uint256(123_456_789));
+ address defaultOperator = address(this);
+ address defaultApprover = cheats.addr(delegationSignerPrivateKey);
+ address defaultAVS = address(this);
- function setUp() override virtual public{
- EigenLayerDeployer.setUp();
+ // 604800 seconds in week / 12 = 50,400 blocks
+ uint256 minWithdrawalDelayBlocks = 50400;
+ IStrategy[] public initializeStrategiesToSetDelayBlocks;
+ uint256[] public initializeWithdrawalDelayBlocks;
- slasherMock = new SlasherMock();
- delegationTermsMock = new DelegationTermsMock();
- delegationManager = DelegationManager(address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")));
- strategyManagerMock = new StrategyManagerMock();
+ IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
- delegationManagerImplementation = new DelegationManager(strategyManagerMock, slasherMock);
+ // Index for flag that pauses new delegations when set.
+ uint8 internal constant PAUSED_NEW_DELEGATION = 0;
- cheats.startPrank(eigenLayerProxyAdmin.owner());
- eigenLayerProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(delegationManager))), address(delegationManagerImplementation));
- cheats.stopPrank();
-
- delegationManager.initialize(address(this), eigenLayerPauserReg, 0);
+ // Index for flag that pauses queuing new withdrawals when set.
+ uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1;
+
+ // Index for flag that pauses completing existing withdrawals when set.
+ uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
+
+ // 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;
+ mapping(IStrategy => uint256) public delegatedSharesBefore;
+
+ function setUp() public virtual override {
+ // Setup
+ EigenLayerUnitTestSetup.setUp();
- strategyImplementation = new StrategyBase(strategyManager);
+ // 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 mock token and strategy
+ mockToken = new ERC20PresetFixedSupply("Mock Token", "MOCK", mockTokenInitialSupply, address(this));
+ strategyImplementation = new StrategyBase(strategyManagerMock);
strategyMock = StrategyBase(
address(
new TransparentUpgradeableProxy(
address(strategyImplementation),
address(eigenLayerProxyAdmin),
- abi.encodeWithSelector(StrategyBase.initialize.selector, weth, eigenLayerPauserReg)
+ abi.encodeWithSelector(StrategyBase.initialize.selector, mockToken, pauserRegistry)
)
)
);
+ // Exclude delegation manager from fuzzed tests
+ addressIsExcludedFromFuzzedInputs[address(delegationManager)] = true;
+ addressIsExcludedFromFuzzedInputs[defaultApprover] = true;
+
}
- function testReinitializeDelegation() public{
- cheats.expectRevert(bytes("Initializable: contract is already initialized"));
- delegationManager.initialize(address(this), eigenLayerPauserReg, 0);
+ /**
+ * INTERNAL / HELPER FUNCTIONS
+ */
+
+ /**
+ * @notice internal function to deploy mock tokens and strategies and have the staker deposit into them.
+ * Since we are mocking the strategyManager we call strategyManagerMock.setDeposits so that when
+ * DelegationManager calls getDeposits, we can have these share amounts returned.
+ */
+ function _deployAndDepositIntoStrategies(
+ address staker,
+ uint256[] memory sharesAmounts
+ ) 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)),
+ mockTokenInitialSupply,
+ address(this)
+ );
+ strategies[i] = StrategyBase(
+ address(
+ new TransparentUpgradeableProxy(
+ address(strategyImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(StrategyBase.initialize.selector, token, pauserRegistry)
+ )
+ )
+ );
+ }
+ delegationManager.setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
+ strategyManagerMock.setDeposits(staker, strategies, sharesAmounts);
+ return strategies;
}
- function testBadECDSASignatureExpiry(address staker, address operator, uint256 expiry, bytes memory signature) public{
- cheats.assume(expiry < block.timestamp);
- cheats.expectRevert(bytes("DelegationManager.delegateToBySignature: delegation signature expired"));
- delegationManager.delegateToBySignature(staker, operator, expiry, signature);
+ /**
+ * @notice internal function for calculating a signature from the delegationSigner corresponding to `_delegationSignerPrivateKey`, approving
+ * the `staker` to delegate to `operator`, with the specified `salt`, and expiring at `expiry`.
+ */
+ function _getApproverSignature(
+ uint256 _delegationSignerPrivateKey,
+ address staker,
+ address operator,
+ bytes32 salt,
+ uint256 expiry
+ ) internal view returns (ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry) {
+ approverSignatureAndExpiry.expiry = expiry;
+ {
+ bytes32 digestHash = delegationManager.calculateDelegationApprovalDigestHash(
+ staker,
+ operator,
+ delegationManager.delegationApprover(operator),
+ salt,
+ expiry
+ );
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(_delegationSignerPrivateKey, digestHash);
+ approverSignatureAndExpiry.signature = abi.encodePacked(r, s, v);
+ }
+ return approverSignatureAndExpiry;
}
- function testUndelegateFromNonStrategyManagerAddress(address undelegator) public fuzzedAddress(undelegator) {
- cheats.assume(undelegator != address(strategyManagerMock));
- cheats.expectRevert(bytes("onlyStrategyManager"));
- cheats.startPrank(undelegator);
- delegationManager.undelegate(address(this));
+ /**
+ * @notice internal function for calculating a signature from the staker corresponding to `_stakerPrivateKey`, delegating them to
+ * the `operator`, and expiring at `expiry`.
+ */
+ function _getStakerSignature(
+ uint256 _stakerPrivateKey,
+ address operator,
+ uint256 expiry
+ ) internal view returns (ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry) {
+ address staker = cheats.addr(stakerPrivateKey);
+ stakerSignatureAndExpiry.expiry = expiry;
+ {
+ bytes32 digestHash = delegationManager.calculateCurrentStakerDelegationDigestHash(staker, operator, expiry);
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(_stakerPrivateKey, digestHash);
+ stakerSignatureAndExpiry.signature = abi.encodePacked(r, s, v);
+ }
+ return stakerSignatureAndExpiry;
}
- function testUndelegateByOperatorFromThemselves(address operator) public fuzzedAddress(operator) {
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(IDelegationTerms(address(this)));
- cheats.stopPrank();
- cheats.expectRevert(bytes("DelegationManager.undelegate: operators cannot undelegate from themselves"));
-
- cheats.startPrank(address(strategyManagerMock));
- delegationManager.undelegate(operator);
- cheats.stopPrank();
+ // @notice Assumes operator does not have a delegation approver & staker != approver
+ function _delegateToOperatorWhoAcceptsAllStakers(address staker, address operator) internal {
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ cheats.prank(staker);
+ delegationManager.delegateTo(operator, approverSignatureAndExpiry, emptySalt);
}
- function testIncreaseDelegatedSharesFromNonStrategyManagerAddress(address operator, uint256 shares) public fuzzedAddress(operator) {
- cheats.assume(operator != address(strategyManagerMock));
- cheats.expectRevert(bytes("onlyStrategyManager"));
- cheats.startPrank(operator);
- delegationManager.increaseDelegatedShares(operator, strategyMock, shares);
+ function _delegateToOperatorWhoRequiresSig(address staker, address operator, bytes32 salt) internal {
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ operator,
+ salt,
+ expiry
+ );
+ cheats.prank(staker);
+ delegationManager.delegateTo(operator, approverSignatureAndExpiry, salt);
}
- function testDecreaseDelegatedSharesFromNonStrategyManagerAddress(
- address operator,
- IStrategy[] memory strategies,
- uint256[] memory shareAmounts
- ) public fuzzedAddress(operator) {
- cheats.assume(operator != address(strategyManagerMock));
- cheats.expectRevert(bytes("onlyStrategyManager"));
- cheats.startPrank(operator);
- delegationManager.decreaseDelegatedShares(operator, strategies, shareAmounts);
+ function _delegateToOperatorWhoRequiresSig(address staker, address operator) internal {
+ _delegateToOperatorWhoRequiresSig(staker, operator, emptySalt);
}
- function testDelegateWhenOperatorIsFrozen(address operator, address staker) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
+ function _delegateToBySignatureOperatorWhoAcceptsAllStakers(
+ address staker,
+ address caller,
+ address operator,
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry,
+ bytes32 salt
+ ) internal {
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ cheats.prank(caller);
+ delegationManager.delegateToBySignature(
+ staker,
+ operator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ salt
+ );
+ }
+
+ function _delegateToBySignatureOperatorWhoRequiresSig(
+ address staker,
+ address caller,
+ address operator,
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry,
+ bytes32 salt
+ ) internal {
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ operator,
+ salt,
+ expiry
+ );
+ cheats.prank(caller);
+ delegationManager.delegateToBySignature(
+ staker,
+ operator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ salt
+ );
+ }
+
+ 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: defaultApprover,
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(operator, operatorDetails, emptyStringForMetadataURI);
+ }
+
+ function _registerOperatorWith1271DelegationApprover(address operator) internal returns (ERC1271WalletMock) {
+ address delegationSigner = defaultApprover;
+ /**
+ * 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());
+ }
+
+ /**
+ * @notice Using this helper function to fuzz withdrawalAmounts since fuzzing two dynamic sized arrays of equal lengths
+ * reject too many inputs.
+ */
+ function _fuzzWithdrawalAmounts(uint256[] memory depositAmounts) internal view returns (uint256[] memory) {
+ uint256[] memory withdrawalAmounts = new uint256[](depositAmounts.length);
+ for (uint256 i = 0; i < depositAmounts.length; i++) {
+ cheats.assume(depositAmounts[i] > 0);
+ // generate withdrawal amount within range s.t withdrawAmount <= depositAmount
+ withdrawalAmounts[i] = bound(
+ uint256(keccak256(abi.encodePacked(depositAmounts[i]))),
+ 0,
+ depositAmounts[i]
+ );
+ }
+ return withdrawalAmounts;
+ }
+
+ function _setUpQueueWithdrawalsSingleStrat(
+ address staker,
+ address withdrawer,
+ IStrategy strategy,
+ uint256 withdrawalAmount
+ ) internal view returns (
+ IDelegationManager.QueuedWithdrawalParams[] memory,
+ IDelegationManager.Withdrawal memory,
+ bytes32
+ ) {
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ strategyArray[0] = strategy;
+ uint256[] memory withdrawalAmounts = new uint256[](1);
+ withdrawalAmounts[0] = withdrawalAmount;
+
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams = new IDelegationManager.QueuedWithdrawalParams[](1);
+ queuedWithdrawalParams[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: strategyArray,
+ shares: withdrawalAmounts,
+ withdrawer: withdrawer
+ });
+
+ IDelegationManager.Withdrawal memory withdrawal = IDelegationManager.Withdrawal({
+ staker: staker,
+ delegatedTo: delegationManager.delegatedTo(staker),
+ withdrawer: withdrawer,
+ nonce: delegationManager.cumulativeWithdrawalsQueued(staker),
+ startBlock: uint32(block.number),
+ strategies: strategyArray,
+ shares: withdrawalAmounts
+ });
+ bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal);
+
+ return (queuedWithdrawalParams, withdrawal, withdrawalRoot);
+ }
+
+ function _setUpQueueWithdrawals(
+ address staker,
+ address withdrawer,
+ IStrategy[] memory strategies,
+ uint256[] memory withdrawalAmounts
+ ) internal view returns (
+ IDelegationManager.QueuedWithdrawalParams[] memory,
+ IDelegationManager.Withdrawal memory,
+ bytes32
+ ) {
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams = new IDelegationManager.QueuedWithdrawalParams[](1);
+ queuedWithdrawalParams[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: strategies,
+ shares: withdrawalAmounts,
+ withdrawer: withdrawer
+ });
+ IDelegationManager.Withdrawal memory withdrawal = IDelegationManager.Withdrawal({
+ staker: staker,
+ delegatedTo: delegationManager.delegatedTo(staker),
+ withdrawer: withdrawer,
+ nonce: delegationManager.cumulativeWithdrawalsQueued(staker),
+ startBlock: uint32(block.number),
+ strategies: strategies,
+ shares: withdrawalAmounts
+ });
+ bytes32 withdrawalRoot = delegationManager.calculateWithdrawalRoot(withdrawal);
+
+ return (queuedWithdrawalParams, withdrawal, 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 _setUpCompleteQueuedWithdrawalSingleStrat(
+ 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 = _deployAndDepositIntoStrategies(staker, depositAmounts);
+ (
+ 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 = new IERC20[](1);
+ tokens[0] = strategies[0].underlyingToken();
+ 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:
+ * - operator is already a registered operator.
+ * - for each i, withdrawalAmount[i] <= depositAmount[i] (see filterFuzzedDepositWithdrawInputs above)
+ */
+ function _setUpCompleteQueuedWithdrawal(
+ address staker,
+ address withdrawer,
+ uint256[] memory depositAmounts,
+ uint256[] memory withdrawalAmounts
+ ) 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,
+ bytes32 withdrawalRoot
+ ) = _setUpQueueWithdrawals({
+ staker: staker,
+ withdrawer: withdrawer,
+ strategies: strategies,
+ withdrawalAmounts: withdrawalAmounts
+ });
+
+ cheats.prank(staker);
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+
+ return (withdrawal, tokens, withdrawalRoot);
+ }
+}
+
+contract DelegationManagerUnitTests_Initialization_Setters is DelegationManagerUnitTests {
+ function test_initialization() public {
+ assertEq(
+ address(delegationManager.strategyManager()),
+ address(strategyManagerMock),
+ "constructor / initializer incorrect, strategyManager set wrong"
+ );
+ assertEq(
+ address(delegationManager.slasher()),
+ address(slasherMock),
+ "constructor / initializer incorrect, slasher set wrong"
+ );
+ assertEq(
+ address(delegationManager.pauserRegistry()),
+ address(pauserRegistry),
+ "constructor / initializer incorrect, pauserRegistry set wrong"
+ );
+ assertEq(delegationManager.owner(), address(this), "constructor / initializer incorrect, owner set wrong");
+ assertEq(delegationManager.paused(), 0, "constructor / initializer incorrect, paused status set wrong");
+ }
+
+ /// @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,
+ 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[] 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._setStrategyWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
+ );
+ delegationManager = DelegationManager(
+ address(
+ new TransparentUpgradeableProxy(
+ address(delegationManagerImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ DelegationManager.initialize.selector,
+ address(this),
+ pauserRegistry,
+ 0, // 0 is initialPausedStatus
+ minWithdrawalDelayBlocks,
+ strategiesToSetDelayBlocks,
+ withdrawalDelayBlocks
+ )
+ )
+ )
+ );
+ }
+}
+
+contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerUnitTests {
+ function test_registerAsOperator_revert_paused() public {
+ // set the pausing flag
+ cheats.prank(pauser);
+ delegationManager.pause(2 ** PAUSED_NEW_DELEGATION);
+
+ cheats.expectRevert("Pausable: index is paused");
+ delegationManager.registerAsOperator(
+ IDelegationManager.OperatorDetails({
+ earningsReceiver: defaultOperator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ }),
+ emptyStringForMetadataURI
+ );
+ }
+
+ // @notice Verifies that someone cannot successfully call `DelegationManager.registerAsOperator(operatorDetails)` again after registering for the first time
+ function testFuzz_registerAsOperator_revert_cannotRegisterMultipleTimes(
+ address operator,
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) public filterFuzzedAddressInputs(operator) {
+ _filterOperatorDetails(operator, operatorDetails);
+
+ // Register once
cheats.startPrank(operator);
- delegationManager.registerAsOperator(IDelegationTerms(address(this)));
+ delegationManager.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
+
+ // Expect revert when register again
+ cheats.expectRevert("DelegationManager.registerAsOperator: operator has already registered");
+ delegationManager.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
cheats.stopPrank();
+ }
+
+ /**
+ * @notice Verifies that an operator cannot register with `earningsReceiver` set to the zero address
+ * @dev This is an important check since we check `earningsReceiver != address(0)` to check if an address is an operator!
+ */
+ function testFuzz_registerAsOperator_revert_earningsReceiverZeroAddress() public {
+ IDelegationManager.OperatorDetails memory operatorDetails;
+
+ cheats.prank(defaultOperator);
+ cheats.expectRevert("DelegationManager._setOperatorDetails: cannot set `earningsReceiver` to zero address");
+ delegationManager.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
+ }
+
+ /**
+ * @notice Verifies that an operator cannot register with `stakerOptOutWindowBlocks` set larger than `MAX_STAKER_OPT_OUT_WINDOW_BLOCKS`
+ */
+ function testFuzz_registerAsOperator_revert_optOutBlocksTooLarge(
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) public {
+ // 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 *allowed* stakerOptOutWindowBlocks values
+ cheats.assume(operatorDetails.stakerOptOutWindowBlocks > delegationManager.MAX_STAKER_OPT_OUT_WINDOW_BLOCKS());
+
+ cheats.prank(defaultOperator);
+ cheats.expectRevert("DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS");
+ delegationManager.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
+ }
- slasherMock.setOperatorFrozenStatus(operator, true);
- cheats.expectRevert(bytes("DelegationManager._delegate: cannot delegate to a frozen operator"));
+ /**
+ * @notice `operator` registers via calling `DelegationManager.registerAsOperator(operatorDetails, metadataURI)`
+ * Should be able to set any parameters, other than setting their `earningsReceiver` to the zero address or too high value for `stakerOptOutWindowBlocks`
+ * The set parameters should match the desired parameters (correct storage update)
+ * Operator becomes delegated to themselves
+ * Properly emits events – especially the `OperatorRegistered` event, but also `StakerDelegated` & `OperatorDetailsModified` events
+ * Reverts appropriately if operator was already delegated to someone (including themselves, i.e. they were already an operator)
+ * @param operator and @param operatorDetails are fuzzed inputs
+ */
+ function testFuzz_registerAsOperator(
+ address operator,
+ IDelegationManager.OperatorDetails memory operatorDetails,
+ string memory metadataURI
+ ) public filterFuzzedAddressInputs(operator) {
+ _filterOperatorDetails(operator, operatorDetails);
+
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorDetailsModified(operator, operatorDetails);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(operator, operator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorRegistered(operator, operatorDetails);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorMetadataURIUpdated(operator, metadataURI);
+
+ cheats.prank(operator);
+ delegationManager.registerAsOperator(operatorDetails, metadataURI);
+
+ // Storage checks
+ assertEq(
+ operatorDetails.earningsReceiver,
+ delegationManager.earningsReceiver(operator),
+ "earningsReceiver not set correctly"
+ );
+ assertEq(
+ operatorDetails.delegationApprover,
+ delegationManager.delegationApprover(operator),
+ "delegationApprover not set correctly"
+ );
+ assertEq(
+ operatorDetails.stakerOptOutWindowBlocks,
+ delegationManager.stakerOptOutWindowBlocks(operator),
+ "stakerOptOutWindowBlocks not set correctly"
+ );
+ assertEq(delegationManager.delegatedTo(operator), operator, "operator not delegated to self");
+ }
+
+ // @notice Verifies that a staker who is actively delegated to an operator cannot register as an operator (without first undelegating, at least)
+ function testFuzz_registerAsOperator_cannotRegisterWhileDelegated(
+ address staker,
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != defaultOperator);
+ // Staker becomes an operator, so filter against staker's address
+ _filterOperatorDetails(staker, operatorDetails);
+
+ // register *this contract* as an operator
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, emptySalt);
+
+ // expect revert if attempt to register as operator
+ cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated");
+ delegationManager.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
+
cheats.stopPrank();
}
- function testDelegateWhenStakerHasExistingDelegation(address staker, address operator, address operator2) public
- fuzzedAddress(staker)
- fuzzedAddress(operator)
- fuzzedAddress(operator2)
- {
- cheats.assume(operator != operator2);
- cheats.assume(staker != operator);
- cheats.assume(staker != operator2);
+ /**
+ * @notice Verifies that an operator cannot modify their `earningsReceiver` address to set it to the zero address
+ * @dev This is an important check since we check `earningsReceiver != address(0)` to check if an address is an operator!
+ */
+ function test_modifyOperatorParameters_revert_earningsReceiverZeroAddress() public {
+ // register *this contract* as an operator
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: defaultOperator,
+ delegationApprover: address(0),
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(defaultOperator, operatorDetails, emptyStringForMetadataURI);
+
+ operatorDetails.earningsReceiver = address(0);
+ cheats.expectRevert("DelegationManager._setOperatorDetails: cannot set `earningsReceiver` to zero address");
+ delegationManager.modifyOperatorDetails(operatorDetails);
+ }
+
+ /**
+ * @notice Tests that an operator can modify their OperatorDetails by calling `DelegationManager.modifyOperatorDetails`
+ * Should be able to set any parameters, other than setting their `earningsReceiver` to the zero address or too high value for `stakerOptOutWindowBlocks`
+ * The set parameters should match the desired parameters (correct storage update)
+ * Properly emits an `OperatorDetailsModified` event
+ * Reverts appropriately if the caller is not an operator
+ * Reverts if operator tries to decrease their `stakerOptOutWindowBlocks` parameter
+ * @param initialOperatorDetails and @param modifiedOperatorDetails are fuzzed inputs
+ */
+ function testFuzz_modifyOperatorParameters(
+ IDelegationManager.OperatorDetails memory initialOperatorDetails,
+ IDelegationManager.OperatorDetails memory modifiedOperatorDetails
+ ) public {
+ // filter out zero address since people can't set their earningsReceiver address to the zero address (test case verified above)
+ cheats.assume(modifiedOperatorDetails.earningsReceiver != address(0));
+
+ _registerOperator(defaultOperator, initialOperatorDetails, emptyStringForMetadataURI);
+
+ cheats.startPrank(defaultOperator);
+
+ // either it fails for trying to set the stakerOptOutWindowBlocks
+ if (modifiedOperatorDetails.stakerOptOutWindowBlocks > delegationManager.MAX_STAKER_OPT_OUT_WINDOW_BLOCKS()) {
+ cheats.expectRevert(
+ "DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS"
+ );
+ delegationManager.modifyOperatorDetails(modifiedOperatorDetails);
+ // or the transition is allowed,
+ } else if (
+ modifiedOperatorDetails.stakerOptOutWindowBlocks >= initialOperatorDetails.stakerOptOutWindowBlocks
+ ) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorDetailsModified(defaultOperator, modifiedOperatorDetails);
+ delegationManager.modifyOperatorDetails(modifiedOperatorDetails);
+
+ assertEq(
+ modifiedOperatorDetails.earningsReceiver,
+ delegationManager.earningsReceiver(defaultOperator),
+ "earningsReceiver not set correctly"
+ );
+ assertEq(
+ modifiedOperatorDetails.delegationApprover,
+ delegationManager.delegationApprover(defaultOperator),
+ "delegationApprover not set correctly"
+ );
+ assertEq(
+ modifiedOperatorDetails.stakerOptOutWindowBlocks,
+ delegationManager.stakerOptOutWindowBlocks(defaultOperator),
+ "stakerOptOutWindowBlocks not set correctly"
+ );
+ assertEq(delegationManager.delegatedTo(defaultOperator), defaultOperator, "operator not delegated to self");
+ // or else the transition is disallowed
+ } else {
+ cheats.expectRevert("DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be decreased");
+ delegationManager.modifyOperatorDetails(modifiedOperatorDetails);
+ }
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(IDelegationTerms(address(11)));
cheats.stopPrank();
+ }
+
+ // @notice Tests that an address which is not an operator cannot successfully call `updateOperatorMetadataURI`.
+ function test_updateOperatorMetadataUri_notRegistered() public {
+ assertFalse(delegationManager.isOperator(defaultOperator), "bad test setup");
+
+ cheats.prank(defaultOperator);
+ cheats.expectRevert("DelegationManager.updateOperatorMetadataURI: caller must be an operator");
+ delegationManager.updateOperatorMetadataURI(emptyStringForMetadataURI);
+ }
+
+ /**
+ * @notice Verifies that a staker cannot call cannot modify their `OperatorDetails` without first registering as an operator
+ * @dev This is an important check to ensure that our definition of 'operator' remains consistent, in particular for preserving the
+ * invariant that 'operators' are always delegated to themselves
+ */
+ function testFuzz_updateOperatorMetadataUri_revert_notOperator(
+ IDelegationManager.OperatorDetails memory operatorDetails
+ ) public {
+ cheats.expectRevert("DelegationManager.modifyOperatorDetails: caller must be an operator");
+ delegationManager.modifyOperatorDetails(operatorDetails);
+ }
+
+ // @notice Tests that an operator who calls `updateOperatorMetadataURI` will correctly see an `OperatorMetadataURIUpdated` event emitted with their input
+ function testFuzz_UpdateOperatorMetadataURI(string memory metadataURI) public {
+ // register *this contract* as an operator
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // call `updateOperatorMetadataURI` and check for event
+ cheats.prank(defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorMetadataURIUpdated(defaultOperator, metadataURI);
+ delegationManager.updateOperatorMetadataURI(metadataURI);
+ }
+}
- cheats.startPrank(operator2);
- delegationManager.registerAsOperator(IDelegationTerms(address(10)));
+contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests {
+ function test_Revert_WhenPaused() public {
+ // set the pausing flag
+ cheats.prank(pauser);
+ delegationManager.pause(2 ** PAUSED_NEW_DELEGATION);
+
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ cheats.prank(defaultStaker);
+ cheats.expectRevert("Pausable: index is paused");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, emptySalt);
+ }
+
+ /**
+ * @notice Delegates from `staker` to an operator, then verifies that the `staker` cannot delegate to another `operator` (at least without first undelegating)
+ */
+ function testFuzz_Revert_WhenDelegateWhileDelegated(
+ address staker,
+ address operator,
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 salt
+ ) public filterFuzzedAddressInputs(staker) filterFuzzedAddressInputs(operator) {
+ // filter out input since if the staker tries to delegate again after registering as an operator, we will revert earlier than this test is designed to check
+ cheats.assume(staker != operator);
+
+ // delegate from the staker to an operator
+ cheats.assume(operator != address(this));
+ _registerOperatorWithBaseDetails(operator);
+ _delegateToOperatorWhoAcceptsAllStakers(staker, operator);
+
+ // try to delegate again and check that the call reverts
+ cheats.startPrank(staker);
+ cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated");
+ delegationManager.delegateTo(operator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ }
+ // @notice Verifies that `staker` cannot delegate to an unregistered `operator`
+ function testFuzz_Revert_WhenDelegateToUnregisteredOperator(
+ address staker,
+ address operator
+ ) public filterFuzzedAddressInputs(staker) filterFuzzedAddressInputs(operator) {
+ assertFalse(delegationManager.isOperator(operator), "incorrect test input?");
+
+ // try to delegate and check that the call reverts
cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ cheats.expectRevert("DelegationManager._delegate: operator is not registered in EigenLayer");
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ delegationManager.delegateTo(operator, approverSignatureAndExpiry, emptySalt);
cheats.stopPrank();
+ }
+
+ /**
+ * @notice `staker` delegates to an operator who does not require any signature verification (i.e. the operator’s `delegationApprover` address is set to the zero address)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * The function should pass with any `operatorSignature` input (since it should be unused)
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_OperatorWhoAcceptsAllStakers_StrategyManagerShares(
+ address staker,
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 salt,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(staker) {
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // Set staker shares in StrategyManager
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(staker, strategiesToReturn, sharesToReturn);
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- cheats.expectRevert(bytes("DelegationManager._delegate: staker has existing delegation"));
- delegationManager.delegateTo(operator2);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, strategyMock, shares);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ assertTrue(delegationManager.isOperator(defaultOperator), "staker not registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
}
- function testDelegationToUnregisteredOperator(address operator) public{
- cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate"));
- delegationManager.delegateTo(operator);
+ /**
+ * @notice `staker` delegates to an operator who does not require any signature verification (i.e. the operator’s `delegationApprover` address is set to the zero address)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * The function should pass with any `operatorSignature` input (since it should be unused)
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * OperatorSharesIncreased event should only be emitted if beaconShares is > 0. Since a staker can have negative shares nothing should happen in that case
+ */
+ function testFuzz_OperatorWhoAcceptsAllStakers_BeaconChainStrategyShares(
+ address staker,
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 salt,
+ int256 beaconShares
+ ) public filterFuzzedAddressInputs(staker) {
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // Set staker shares in BeaconChainStrategy
+ eigenPodManagerMock.setPodOwnerShares(staker, beaconShares);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(staker, beaconChainETHStrategy);
+ // delegate from the `staker` to the operator
+ cheats.startPrank(staker);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
+ cheats.stopPrank();
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertTrue(delegationManager.isOperator(defaultOperator), "staker not registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
}
- function testDelegationWhenPausedNewDelegationIsSet(address operator, address staker) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.startPrank(pauser);
- delegationManager.pause(1);
+ /**
+ * @notice `staker` delegates to an operator who does not require any signature verification (i.e. the operator’s `delegationApprover` address is set to the zero address)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * Similar to tests above but now with staker who has both EigenPod and StrategyManager shares.
+ */
+ function testFuzz_OperatorWhoAcceptsAllStakers_BeaconChainAndStrategyManagerShares(
+ address staker,
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 salt,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(staker) {
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // Set staker shares in BeaconChainStrategy and StrategyMananger
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(staker, strategiesToReturn, sharesToReturn);
+ eigenPodManagerMock.setPodOwnerShares(staker, beaconShares);
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(staker, beaconChainETHStrategy);
+ // delegate from the `staker` to the operator
+ cheats.startPrank(staker);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, strategyMock, shares);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ assertTrue(delegationManager.isOperator(defaultOperator), "staker not registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ }
+
+ /**
+ * @notice `staker` delegates to a operator who does not require any signature verification similar to test above.
+ * In this scenario, staker doesn't have any delegatable shares and operator shares should not increase. Staker
+ * should still be correctly delegated to the operator after the call.
+ */
+ function testFuzz_OperatorWhoAcceptsAllStakers_ZeroDelegatableShares(
+ address staker,
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry,
+ bytes32 salt
+ ) public filterFuzzedAddressInputs(staker) {
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- delegationManager.delegateTo(operator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+
+ assertTrue(delegationManager.isOperator(defaultOperator), "staker not registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
}
- function testRevertingDelegationReceivedHook(address operator, address staker) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
+ /**
+ * @notice Like `testDelegateToOperatorWhoRequiresECDSASignature` but using an invalid expiry on purpose and checking that reversion occurs
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresECDSASignature_ExpiredDelegationApproverSignature(
+ address staker,
+ bytes32 salt,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // roll to a very late timestamp
+ skip(type(uint256).max / 2);
+ // filter to only *invalid* `expiry` values
+ 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);
- delegationTermsMock.setShouldRevert(true);
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(delegationTermsMock);
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator
+ cheats.startPrank(staker);
+ cheats.expectRevert("DelegationManager._delegate: approver signature expired");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ }
+
+ /**
+ * @notice Like `testDelegateToOperatorWhoRequiresECDSASignature` but undelegating after delegating and trying the same approveSignature
+ * and checking that reversion occurs with the same salt
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresECDSASignature_PreviouslyUsedSalt(
+ address staker,
+ bytes32 salt,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ // staker also must not be the delegationApprover so that signature verification process takes place
+ cheats.assume(staker != defaultOperator);
+ cheats.assume(staker != defaultApprover);
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+ // delegate from the `staker` to the operator, undelegate, and then try to delegate again with same approversalt
+ // to check that call reverts
cheats.startPrank(staker);
- // cheats.expectEmit(true, true, true, true, address(delegationManager));
- cheats.expectEmit(true, true, true, true);
- emit OnDelegationReceivedCallFailure(delegationTermsMock, 0x08c379a000000000000000000000000000000000000000000000000000000000);
- delegationManager.delegateTo(operator);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ delegationManager.undelegate(staker);
+ cheats.expectRevert("DelegationManager._delegate: approverSalt already spent");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
}
- function testRevertingDelegationWithdrawnHook(
- address operator,
- address staker
- ) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
- delegationTermsMock.setShouldRevert(true);
+ /**
+ * @notice Like `testDelegateToOperatorWhoRequiresECDSASignature` but using an incorrect signature on purpose and checking that reversion occurs
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresECDSASignature_WithBadSignature(
+ address staker,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ 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 && staker != defaultApprover);
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(delegationTermsMock);
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // calculate the signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ approverSignatureAndExpiry.expiry = expiry;
+ {
+ bytes32 digestHash = delegationManager.calculateDelegationApprovalDigestHash(
+ staker,
+ defaultOperator,
+ delegationManager.delegationApprover(defaultOperator),
+ emptySalt,
+ expiry
+ );
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(delegationSignerPrivateKey, digestHash);
+ // mess up the signature by flipping v's parity
+ v = (v == 27 ? 28 : 27);
+ approverSignatureAndExpiry.signature = abi.encodePacked(r, s, v);
+ }
+
+ // try to delegate from the `staker` to the operator, and check reversion
+ cheats.startPrank(staker);
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, emptySalt);
cheats.stopPrank();
+ }
+
+ /**
+ * @notice `staker` delegates to an operator who requires signature verification through an EOA (i.e. the operator’s `delegationApprover` address is set to a nonzero EOA)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * The function should pass *only with a valid ECDSA signature from the `delegationApprover`, OR if called by the operator or their delegationApprover themselves
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_OperatorWhoRequiresECDSASignature(
+ address staker,
+ bytes32 salt,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
- (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) =
- strategyManager.getDeposits(staker);
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
- cheats.startPrank(address(strategyManagerMock));
- // cheats.expectEmit(true, true, true, true, address(delegationManager));
- cheats.expectEmit(true, true, true, true);
- emit OnDelegationWithdrawnCallFailure(delegationTermsMock, 0x08c379a000000000000000000000000000000000000000000000000000000000);
- delegationManager.decreaseDelegatedShares(staker, updatedStrategies, updatedShares);
+ if (staker == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
+ }
+
+ /**
+ * @notice `staker` delegates to an operator who requires signature verification through an EOA (i.e. the operator’s `delegationApprover` address is set to a nonzero EOA)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * The function should pass *only with a valid ECDSA signature from the `delegationApprover`, OR if called by the operator or their delegationApprover themselves
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Operator shares should increase by the amount of shares delegated
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_OperatorWhoRequiresECDSASignature_StrategyManagerShares(
+ address staker,
+ bytes32 salt,
+ uint256 expiry,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // Set staker shares in StrategyManager
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(staker, strategiesToReturn, sharesToReturn);
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ // delegate from the `staker` to the operator
+ cheats.startPrank(staker);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+
+ if (staker == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
}
- function testDelegationReceivedHookWithTooMuchReturnData(address operator, address staker) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
- delegationTermsMock.setShouldReturnData(true);
+ /**
+ * @notice `staker` delegates to an operator who requires signature verification through an EOA (i.e. the operator’s `delegationApprover` address is set to a nonzero EOA)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * The function should pass *only with a valid ECDSA signature from the `delegationApprover`, OR if called by the operator or their delegationApprover themselves
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Operator beaconShares should increase by the amount of shares delegated if beaconShares > 0
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_OperatorWhoRequiresECDSASignature_BeaconChainStrategyShares(
+ address staker,
+ bytes32 salt,
+ uint256 expiry,
+ int256 beaconShares
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(delegationTermsMock);
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // Set staker shares in BeaconChainStrategy
+ eigenPodManagerMock.setPodOwnerShares(staker, beaconShares);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(staker, beaconChainETHStrategy);
+ // delegate from the `staker` to the operator
+ cheats.startPrank(staker);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+
+ if (staker == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
+ }
+
+ /**
+ * @notice `staker` delegates to an operator who requires signature verification through an EOA (i.e. the operator’s `delegationApprover` address is set to a nonzero EOA)
+ * via the `staker` calling `DelegationManager.delegateTo`
+ * The function should pass *only with a valid ECDSA signature from the `delegationApprover`, OR if called by the operator or their delegationApprover themselves
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Operator beaconshares should increase by the amount of beaconShares delegated if beaconShares > 0
+ * Operator strategy manager shares should icnrease by amount of shares
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_OperatorWhoRequiresECDSASignature_BeaconChainAndStrategyManagerShares(
+ address staker,
+ bytes32 salt,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // Set staker shares in BeaconChainStrategy and StrategyMananger
+ {
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(staker, strategiesToReturn, sharesToReturn);
+ eigenPodManagerMock.setPodOwnerShares(staker, beaconShares);
+ }
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(staker, beaconChainETHStrategy);
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, strategyMock, shares);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+
+ if (staker == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
}
- function testDelegationWithdrawnHookWithTooMuchReturnData(
- address operator,
- address staker
- ) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
+ /**
+ * @notice delegateTo test with operator's delegationApprover address set to a contract address
+ * and check that reversion occurs when the signature is expired
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresEIP1271Signature_ExpiredDelegationApproverSignature(
+ address staker,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // roll to a very late timestamp
+ skip(type(uint256).max / 2);
+ // filter to only *invalid* `expiry` values
+ 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);
- delegationTermsMock.setShouldReturnData(true);
+ _registerOperatorWithDelegationApprover(defaultOperator);
+ // create the signature struct
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ approverSignatureAndExpiry.expiry = expiry;
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(delegationTermsMock);
+ // try to delegate from the `staker` to the operator, and check reversion
+ cheats.startPrank(staker);
+ cheats.expectRevert("DelegationManager._delegate: approver signature expired");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, emptySalt);
cheats.stopPrank();
+ }
+ /**
+ * @notice delegateTo test with operator's delegationApprover address set to a contract address
+ * and check that reversion occurs when the signature approverSalt is already used.
+ * Performed by delegating to operator, undelegating, and trying to reuse the same signature
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresEIP1271Signature_PreviouslyUsedSalt(
+ address staker,
+ bytes32 salt,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ ERC1271WalletMock wallet = _registerOperatorWith1271DelegationApprover(defaultOperator);
+ cheats.assume(staker != address(wallet) && staker != defaultOperator);
+
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
+ delegationManager.undelegate(staker);
+ // Reusing same signature should revert with salt already being used
+ cheats.expectRevert("DelegationManager._delegate: approverSalt already spent");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+ }
- (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) =
- strategyManager.getDeposits(staker);
+ /**
+ * @notice delegateTo test with operator's delegationApprover address set to a contract address that
+ * is non compliant with EIP1271
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresEIP1271Signature_NonCompliantWallet(
+ address staker,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
- cheats.startPrank(address(strategyManagerMock));
- delegationManager.decreaseDelegatedShares(staker, updatedStrategies, updatedShares);
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ // deploy a ERC1271MaliciousMock contract that will return an incorrect value when called
+ ERC1271MaliciousMock wallet = new ERC1271MaliciousMock();
+
+ // filter fuzzed input, since otherwise we can get a flaky failure here. if the caller itself is the 'delegationApprover'
+ // then we don't even trigger the signature verification call, so we won't get a revert as expected
+ cheats.assume(staker != address(wallet));
+
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: defaultOperator,
+ delegationApprover: address(wallet),
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(defaultOperator, operatorDetails, emptyStringForMetadataURI);
+
+ // create the signature struct
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ approverSignatureAndExpiry.expiry = expiry;
+
+ // try to delegate from the `staker` to the operator, and check reversion
+ cheats.startPrank(staker);
+ // because the ERC1271MaliciousMock contract returns the wrong amount of data, we get a low-level "EvmError: Revert" message here rather than the error message bubbling up
+ cheats.expectRevert();
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, emptySalt);
cheats.stopPrank();
}
- function testDelegationReceivedHookWithNoReturnData(address operator, address staker) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
+ /**
+ * @notice delegateTo test with operator's delegationApprover address set to a contract address that
+ * returns a value other than the EIP1271 "magic bytes" and checking that reversion occurs appropriately
+ */
+ function testFuzz_Revert_WhenOperatorWhoRequiresEIP1271Signature_IsValidSignatureFails(
+ address staker,
+ bytes32 salt,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(delegationTermsMock);
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ // deploy a ERC1271WalletMock contract that will return an incorrect value when called
+ // owner is the 0 address
+ ERC1271WalletMock wallet = new ERC1271WalletMock(address(1));
+
+ // filter fuzzed input, since otherwise we can get a flaky failure here. if the caller itself is the 'delegationApprover'
+ // then we don't even trigger the signature verification call, so we won't get a revert as expected
+ cheats.assume(staker != address(wallet));
+
+ IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
+ earningsReceiver: defaultOperator,
+ delegationApprover: address(wallet),
+ stakerOptOutWindowBlocks: 0
+ });
+ _registerOperator(defaultOperator, operatorDetails, emptyStringForMetadataURI);
+
+ // calculate the delegationSigner's but this is not the correct signature from the wallet contract
+ // since the wallet owner is address(1)
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // try to delegate from the `staker` to the operator, and check reversion
+ cheats.startPrank(staker);
+ // Signature should fail as the wallet will not return EIP1271_MAGICVALUE
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed");
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, emptySalt);
cheats.stopPrank();
+ }
+
+ /**
+ * @notice `staker` delegates to an operator who requires signature verification through an EIP1271-compliant contract (i.e. the operator’s `delegationApprover` address is
+ * set to a nonzero and code-containing address) via the `staker` calling `DelegationManager.delegateTo`
+ * The function uses OZ's ERC1271WalletMock contract, and thus should pass *only when a valid ECDSA signature from the `owner` of the ERC1271WalletMock contract,
+ * OR if called by the operator or their delegationApprover themselves
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_OperatorWhoRequiresEIP1271Signature(
+ address staker,
+ bytes32 salt,
+ uint256 expiry
+ ) public filterFuzzedAddressInputs(staker) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+ // register *this contract* as an operator
+ // filter inputs, since this will fail when the staker is already registered as an operator
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWith1271DelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ staker,
+ defaultOperator,
+ salt,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator
cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(staker, defaultOperator);
+ delegationManager.delegateTo(defaultOperator, approverSignatureAndExpiry, salt);
cheats.stopPrank();
+
+ assertTrue(delegationManager.isDelegated(staker), "staker not delegated correctly");
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address");
+ assertFalse(delegationManager.isOperator(staker), "staker incorrectly registered as operator");
+
+ // check that the nonce incremented appropriately
+ if (staker == defaultOperator || staker == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
}
+}
- function testDelegationWithdrawnHookWithNoReturnData(
- address operator,
- address staker
- ) public fuzzedAddress(operator) fuzzedAddress(staker) {
- cheats.assume(operator != staker);
+contract DelegationManagerUnitTests_delegateToBySignature is DelegationManagerUnitTests {
+ function test_revert_paused() public {
+ // set the pausing flag
+ cheats.prank(pauser);
+ delegationManager.pause(2 ** PAUSED_NEW_DELEGATION);
- cheats.startPrank(operator);
- delegationManager.registerAsOperator(delegationTermsMock);
+ uint256 expiry = type(uint256).max;
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ cheats.expectRevert("Pausable: index is paused");
+ delegationManager.delegateToBySignature(
+ defaultStaker,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ emptySalt
+ );
+ }
+
+ /// @notice Checks that `DelegationManager.delegateToBySignature` reverts if the staker's signature has expired
+ function testFuzz_Revert_WhenStakerSignatureExpired(
+ address staker,
+ address operator,
+ uint256 expiry,
+ bytes memory signature
+ ) public filterFuzzedAddressInputs(staker) filterFuzzedAddressInputs(operator) {
+ expiry = bound(expiry, 0, block.timestamp - 1);
+ cheats.expectRevert("DelegationManager.delegateToBySignature: staker signature expired");
+ ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({
+ signature: signature,
+ expiry: expiry
+ });
+ delegationManager.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, emptySalt);
+ }
+
+ /// @notice Checks that `DelegationManager.delegateToBySignature` reverts if the staker's ECDSA signature verification fails
+ function test_Revert_EOAStaker_WhenStakerSignatureVerificationFails() public {
+ address invalidStaker = address(1000);
+ address caller = address(2000);
+ uint256 expiry = type(uint256).max;
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ // Should revert from invalid signature as staker is not set as the address of signer
+ cheats.startPrank(caller);
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer");
+ // use an empty approver signature input since none is needed / the input is unchecked
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ delegationManager.delegateToBySignature(
+ invalidStaker,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ emptySalt
+ );
cheats.stopPrank();
+ }
- cheats.startPrank(staker);
- delegationManager.delegateTo(operator);
+ /// @notice Checks that `DelegationManager.delegateToBySignature` reverts if the staker's contract signature verification fails
+ function test_Revert_ERC1271Staker_WhenStakerSignatureVerficationFails() public {
+ address staker = address(new ERC1271WalletMock(address(1)));
+ address caller = address(2000);
+ uint256 expiry = type(uint256).max;
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ // Should revert from invalid signature as staker is not set as the address of signer
+ cheats.startPrank(caller);
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed");
+ // use an empty approver signature input since none is needed / the input is unchecked
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ delegationManager.delegateToBySignature(
+ staker,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ emptySalt
+ );
+ cheats.stopPrank();
+ }
+
+ /// @notice Checks that `DelegationManager.delegateToBySignature` reverts when the staker is already delegated
+ function test_Revert_Staker_WhenAlreadyDelegated() public {
+ address staker = cheats.addr(stakerPrivateKey);
+ address caller = address(2000);
+ uint256 expiry = type(uint256).max;
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ // Should revert as `staker` has already delegated to `operator`
+ cheats.startPrank(caller);
+ cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated");
+ // use an empty approver signature input since none is needed / the input is unchecked
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ delegationManager.delegateToBySignature(
+ staker,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ emptySalt
+ );
+ cheats.stopPrank();
+ }
+
+ /// @notice Checks that `delegateToBySignature` reverts when operator is not registered after successful staker signature verification
+ function test_Revert_EOAStaker_OperatorNotRegistered() public {
+ address staker = cheats.addr(stakerPrivateKey);
+ address caller = address(2000);
+ uint256 expiry = type(uint256).max;
+
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ // Should revert as `operator` is not registered
+ cheats.startPrank(caller);
+ cheats.expectRevert("DelegationManager._delegate: operator is not registered in EigenLayer");
+ // use an empty approver signature input since none is needed / the input is unchecked
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry;
+ delegationManager.delegateToBySignature(
+ staker,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ emptySalt
+ );
+ cheats.stopPrank();
+ }
+
+ /**
+ * @notice Checks that `DelegationManager.delegateToBySignature` reverts if the delegationApprover's signature has expired
+ * after successful staker signature verification
+ */
+ function testFuzz_Revert_WhenDelegationApproverSignatureExpired(
+ address caller,
+ uint256 stakerExpiry,
+ uint256 delegationApproverExpiry
+ ) public filterFuzzedAddressInputs(caller) {
+ cheats.assume(caller != defaultOperator);
+
+ // roll to a very late timestamp
+ 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
+ delegationApproverExpiry = bound(delegationApproverExpiry, 0, block.timestamp - 1);
+
+ console.log("timestamp: %s", block.timestamp);
+ console.log(stakerExpiry);
+ console.log(delegationApproverExpiry);
+
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // calculate the delegationSigner's signature
+ ISignatureUtils.SignatureWithExpiry memory approverSignatureAndExpiry = _getApproverSignature(
+ delegationSignerPrivateKey,
+ defaultStaker,
+ defaultOperator,
+ emptySalt,
+ delegationApproverExpiry
+ );
+
+ // calculate the staker signature
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ stakerExpiry
+ );
+
+ // try delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`, and check for reversion
+ cheats.startPrank(caller);
+ cheats.expectRevert("DelegationManager._delegate: approver signature expired");
+ delegationManager.delegateToBySignature(
+ defaultStaker,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ approverSignatureAndExpiry,
+ emptySalt
+ );
cheats.stopPrank();
+ }
+
+ /**
+ * @notice `staker` becomes delegated to an operator who does not require any signature verification (i.e. the operator’s `delegationApprover` address is set to the zero address)
+ * via the `caller` calling `DelegationManager.delegateToBySignature`
+ * The function should pass with any `operatorSignature` input (since it should be unused)
+ * The function should pass only with a valid `stakerSignatureAndExpiry` input
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * BeaconChainStrategy and StrategyManager operator shares should increase for operator
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_EOAStaker_OperatorWhoAcceptsAllStakers(
+ address caller,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(caller) {
+ cheats.assume(expiry >= block.timestamp);
+ cheats.assume(shares > 0);
+
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ emptySalt
+ ),
+ "salt somehow spent too early?"
+ );
+ {
+ // Set staker shares in BeaconChainStrategy and StrategyMananger
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(defaultStaker, strategiesToReturn, sharesToReturn);
+ eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares);
+ }
+
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(defaultStaker, beaconChainETHStrategy);
+ // fetch the staker's current nonce
+ uint256 currentStakerNonce = delegationManager.stakerNonce(defaultStaker);
+ // calculate the staker signature
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(defaultStaker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, defaultStaker, strategyMock, shares);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, defaultStaker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ _delegateToBySignatureOperatorWhoAcceptsAllStakers(
+ defaultStaker,
+ caller,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ emptySalt
+ );
+
+ // Check operator shares increases
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ // check all the delegation status changes
+ assertTrue(delegationManager.isDelegated(defaultStaker), "staker not delegated correctly");
+ assertEq(
+ delegationManager.delegatedTo(defaultStaker),
+ defaultOperator,
+ "staker delegated to the wrong address"
+ );
+ assertFalse(delegationManager.isOperator(defaultStaker), "staker incorrectly registered as operator");
+ // check that the staker nonce incremented appropriately
+ assertEq(
+ delegationManager.stakerNonce(defaultStaker),
+ currentStakerNonce + 1,
+ "staker nonce did not increment"
+ );
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ emptySalt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ }
+
+ /**
+ * @notice `staker` becomes delegated to an operator who requires signature verification through an EOA (i.e. the operator’s `delegationApprover` address is set to a nonzero EOA)
+ * via the `caller` calling `DelegationManager.delegateToBySignature`
+ * The function should pass *only with a valid ECDSA signature from the `delegationApprover`, OR if called by the operator or their delegationApprover themselves
+ * AND with a valid `stakerSignatureAndExpiry` input
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * BeaconChainStrategy and StrategyManager operator shares should increase for operator
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_EOAStaker_OperatorWhoRequiresECDSASignature(
+ address caller,
+ bytes32 salt,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(caller) {
+ // filter to only valid `expiry` values
+ cheats.assume(expiry >= block.timestamp);
+ cheats.assume(shares > 0);
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ {
+ // Set staker shares in BeaconChainStrategy and StrategyMananger
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(defaultStaker, strategiesToReturn, sharesToReturn);
+ eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares);
+ }
+
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(defaultStaker, beaconChainETHStrategy);
+ // fetch the staker's current nonce
+ uint256 currentStakerNonce = delegationManager.stakerNonce(defaultStaker);
+ // calculate the staker signature
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(defaultStaker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, defaultStaker, strategyMock, shares);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, defaultStaker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ _delegateToBySignatureOperatorWhoRequiresSig(
+ defaultStaker,
+ caller,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ salt
+ );
+ {
+ // Check operator shares increases
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ }
+ assertTrue(delegationManager.isDelegated(defaultStaker), "staker not delegated correctly");
+ assertEq(
+ delegationManager.delegatedTo(defaultStaker),
+ defaultOperator,
+ "staker delegated to the wrong address"
+ );
+ assertFalse(delegationManager.isOperator(defaultStaker), "staker incorrectly registered as operator");
+
+ // check that the delegationApprover nonce incremented appropriately
+ if (caller == defaultOperator || caller == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
+
+ // check that the staker nonce incremented appropriately
+ assertEq(
+ delegationManager.stakerNonce(defaultStaker),
+ currentStakerNonce + 1,
+ "staker nonce did not increment"
+ );
+ }
+
+ /**
+ * @notice `staker` becomes delegated to an operatorwho requires signature verification through an EIP1271-compliant contract (i.e. the operator’s `delegationApprover` address is
+ * set to a nonzero and code-containing address) via the `caller` calling `DelegationManager.delegateToBySignature`
+ * The function uses OZ's ERC1271WalletMock contract, and thus should pass *only when a valid ECDSA signature from the `owner` of the ERC1271WalletMock contract,
+ * OR if called by the operator or their delegationApprover themselves
+ * AND with a valid `stakerSignatureAndExpiry` input
+ * Properly emits a `StakerDelegated` event
+ * Staker is correctly delegated after the call (i.e. correct storage update)
+ * Reverts if the staker is already delegated (to the operator or to anyone else)
+ * Reverts if the ‘operator’ is not actually registered as an operator
+ */
+ function testFuzz_EOAStaker_OperatorWhoRequiresEIP1271Signature(
+ address caller,
+ bytes32 salt,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(caller) {
+ cheats.assume(expiry >= block.timestamp);
+ cheats.assume(shares > 0);
+
+ _registerOperatorWith1271DelegationApprover(defaultOperator);
+
+ // verify that the salt hasn't been used before
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too early?"
+ );
+ {
+ // Set staker shares in BeaconChainStrategy and StrategyMananger
+ IStrategy[] memory strategiesToReturn = new IStrategy[](1);
+ strategiesToReturn[0] = strategyMock;
+ uint256[] memory sharesToReturn = new uint256[](1);
+ sharesToReturn[0] = shares;
+ strategyManagerMock.setDeposits(defaultStaker, strategiesToReturn, sharesToReturn);
+ eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares);
+ }
+
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesBefore = delegationManager.operatorShares(defaultStaker, beaconChainETHStrategy);
+ // fetch the staker's current nonce
+ uint256 currentStakerNonce = delegationManager.stakerNonce(defaultStaker);
+ // calculate the staker signature
+ ISignatureUtils.SignatureWithExpiry memory stakerSignatureAndExpiry = _getStakerSignature(
+ stakerPrivateKey,
+ defaultOperator,
+ expiry
+ );
+
+ // delegate from the `staker` to the operator, via having the `caller` call `DelegationManager.delegateToBySignature`
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerDelegated(defaultStaker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, defaultStaker, strategyMock, shares);
+ if (beaconShares > 0) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, defaultStaker, beaconChainETHStrategy, uint256(beaconShares));
+ }
+ _delegateToBySignatureOperatorWhoRequiresSig(
+ defaultStaker,
+ caller,
+ defaultOperator,
+ stakerSignatureAndExpiry,
+ salt
+ );
+
+ {
+ // Check operator shares increases
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, strategyMock);
+ uint256 beaconSharesAfter = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy);
+ if (beaconShares <= 0) {
+ assertEq(
+ beaconSharesBefore,
+ beaconSharesAfter,
+ "operator beaconchain shares should not have increased with negative shares"
+ );
+ } else {
+ assertEq(
+ beaconSharesBefore + uint256(beaconShares),
+ beaconSharesAfter,
+ "operator beaconchain shares not increased correctly"
+ );
+ }
+ assertEq(operatorSharesBefore + shares, operatorSharesAfter, "operator shares not increased correctly");
+ }
+ assertTrue(delegationManager.isDelegated(defaultStaker), "staker not delegated correctly");
+ assertEq(
+ delegationManager.delegatedTo(defaultStaker),
+ defaultOperator,
+ "staker delegated to the wrong address"
+ );
+ assertFalse(delegationManager.isOperator(defaultStaker), "staker incorrectly registered as operator");
+
+ // check that the delegationApprover nonce incremented appropriately
+ if (caller == defaultOperator || caller == delegationManager.delegationApprover(defaultOperator)) {
+ // verify that the salt is still marked as unused (since it wasn't checked or used)
+ assertFalse(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent too incorrectly?"
+ );
+ } else {
+ // verify that the salt is marked as used
+ assertTrue(
+ delegationManager.delegationApproverSaltIsSpent(
+ delegationManager.delegationApprover(defaultOperator),
+ salt
+ ),
+ "salt somehow spent not spent?"
+ );
+ }
+
+ // check that the staker nonce incremented appropriately
+ assertEq(
+ delegationManager.stakerNonce(defaultStaker),
+ currentStakerNonce + 1,
+ "staker nonce did not increment"
+ );
+ }
+
+ /**
+ * @notice Calls same delegateToBySignature test but with the staker address being a ERC1271WalletMock
+ * Generates valid signatures from the staker to delegate to operator `defaultOperator`
+ */
+ function testFuzz_ERC1271Staker_OperatorWhoAcceptsAllStakers(
+ address caller,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(caller) {
+ defaultStaker = address(ERC1271WalletMock(cheats.addr(stakerPrivateKey)));
+ testFuzz_EOAStaker_OperatorWhoAcceptsAllStakers(caller, expiry, beaconShares, shares);
+ }
+
+ /**
+ * @notice Calls same delegateToBySignature test but with the staker address being a ERC1271WalletMock
+ * Generates valid signatures from the staker to delegate to operator `defaultOperator` who has
+ * a delegationApprover address set to a nonzero EOA
+ */
+ function testFuzz_ERC1271Staker_OperatorWhoRequiresECDSASignature(
+ address caller,
+ bytes32 salt,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(caller) {
+ // Call same test but with the staker address being a ERC1271WalletMock
+ defaultStaker = address(ERC1271WalletMock(cheats.addr(stakerPrivateKey)));
+ testFuzz_EOAStaker_OperatorWhoRequiresECDSASignature(caller, salt, expiry, beaconShares, shares);
+ }
+
+ /**
+ * @notice Calls same delegateToBySignature test but with the staker address being a ERC1271WalletMock
+ * Generates valid signatures from the staker to delegate to operator `defaultOperator` who has
+ * a delegationApprover address set to a nonzero ERC1271 compliant contract
+ */
+ function testFuzz_ERC1271Staker_OperatorWhoRequiresEIP1271Signature(
+ address caller,
+ bytes32 salt,
+ uint256 expiry,
+ int256 beaconShares,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(caller) {
+ // Call same test but with the staker address being a ERC1271WalletMock
+ defaultStaker = address(ERC1271WalletMock(cheats.addr(stakerPrivateKey)));
+ testFuzz_EOAStaker_OperatorWhoRequiresEIP1271Signature(caller, salt, expiry, beaconShares, shares);
+ }
+}
+
+contract DelegationManagerUnitTests_ShareAdjustment is DelegationManagerUnitTests {
+ // @notice Verifies that `DelegationManager.increaseDelegatedShares` reverts if not called by the StrategyManager nor EigenPodManager
+ function testFuzz_increaseDelegatedShares_revert_invalidCaller(
+ address invalidCaller,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(invalidCaller) {
+ cheats.assume(invalidCaller != address(strategyManagerMock));
+ cheats.assume(invalidCaller != address(eigenPodManagerMock));
+ cheats.assume(invalidCaller != address(eigenLayerProxyAdmin));
+
+ cheats.expectRevert("DelegationManager: onlyStrategyManagerOrEigenPodManager");
+ delegationManager.increaseDelegatedShares(invalidCaller, strategyMock, shares);
+ }
+
+ // @notice Verifies that there is no change in shares if the staker is not delegated
+ function testFuzz_increaseDelegatedShares_noop(address staker) public {
+ cheats.assume(staker != defaultOperator);
+ _registerOperatorWithBaseDetails(defaultOperator);
+ assertFalse(delegationManager.isDelegated(staker), "bad test setup");
+
+ cheats.prank(address(strategyManagerMock));
+ delegationManager.increaseDelegatedShares(staker, strategyMock, 1);
+ assertEq(delegationManager.operatorShares(defaultOperator, strategyMock), 0, "shares should not have changed");
+ }
+
+ /**
+ * @notice Verifies that `DelegationManager.increaseDelegatedShares` properly increases the delegated `shares` that the operator
+ * who the `staker` is delegated to has in the strategy
+ * @dev Checks that there is no change if the staker is not delegated
+ */
+ function testFuzz_increaseDelegatedShares(
+ address staker,
+ uint256 shares,
+ bool delegateFromStakerToOperator
+ ) 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);
+
+ // Register operator
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // delegate from the `staker` to the operator *if `delegateFromStakerToOperator` is 'true'*
+ if (delegateFromStakerToOperator) {
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ }
+
+ uint256 _delegatedSharesBefore = delegationManager.operatorShares(
+ delegationManager.delegatedTo(staker),
+ strategyMock
+ );
+
+ if (delegationManager.isDelegated(staker)) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesIncreased(defaultOperator, staker, strategyMock, shares);
+ }
+
+ cheats.prank(address(strategyManagerMock));
+ delegationManager.increaseDelegatedShares(staker, strategyMock, shares);
+
+ uint256 delegatedSharesAfter = delegationManager.operatorShares(
+ delegationManager.delegatedTo(staker),
+ strategyMock
+ );
+
+ if (delegationManager.isDelegated(staker)) {
+ assertEq(
+ delegatedSharesAfter,
+ _delegatedSharesBefore + shares,
+ "delegated shares did not increment correctly"
+ );
+ } else {
+ assertEq(delegatedSharesAfter, _delegatedSharesBefore, "delegated shares incremented incorrectly");
+ assertEq(_delegatedSharesBefore, 0, "nonzero shares delegated to zero address!");
+ }
+ }
+
+ // @notice Verifies that `DelegationManager.decreaseDelegatedShares` reverts if not called by the StrategyManager nor EigenPodManager
+ function testFuzz_decreaseDelegatedShares_revert_invalidCaller(
+ address invalidCaller,
+ uint256 shares
+ ) public filterFuzzedAddressInputs(invalidCaller) {
+ cheats.assume(invalidCaller != address(strategyManagerMock));
+ cheats.assume(invalidCaller != address(eigenPodManagerMock));
- (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) =
- strategyManager.getDeposits(staker);
+ cheats.startPrank(invalidCaller);
+ cheats.expectRevert("DelegationManager: onlyStrategyManagerOrEigenPodManager");
+ delegationManager.decreaseDelegatedShares(invalidCaller, strategyMock, shares);
+ }
+
+ // @notice Verifies that there is no change in shares if the staker is not delegated
+ function testFuzz_decreaseDelegatedShares_noop(address staker) public {
+ cheats.assume(staker != defaultOperator);
+ _registerOperatorWithBaseDetails(defaultOperator);
+ assertFalse(delegationManager.isDelegated(staker), "bad test setup");
+
+ cheats.prank(address(strategyManagerMock));
+ delegationManager.decreaseDelegatedShares(staker, strategyMock, 1);
+ assertEq(delegationManager.operatorShares(defaultOperator, strategyMock), 0, "shares should not have changed");
+ }
+
+ /**
+ * @notice Verifies that `DelegationManager.decreaseDelegatedShares` properly decreases the delegated `shares` that the operator
+ * who the `staker` is delegated to has in the strategies
+ * @dev Checks that there is no change if the staker is not delegated
+ */
+ function testFuzz_decreaseDelegatedShares(
+ address staker,
+ IStrategy[] memory strategies,
+ uint128 shares,
+ bool delegateFromStakerToOperator
+ ) public filterFuzzedAddressInputs(staker) {
+ // sanity-filtering on fuzzed input length & staker
+ cheats.assume(strategies.length <= 32);
+ cheats.assume(staker != defaultOperator);
+
+ // Register operator
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ // delegate from the `staker` to the operator *if `delegateFromStakerToOperator` is 'true'*
+ if (delegateFromStakerToOperator) {
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ }
+
+ uint256[] memory sharesInputArray = new uint256[](strategies.length);
+ address delegatedTo = delegationManager.delegatedTo(staker);
+
+ // for each strategy in `strategies`, increase delegated shares by `shares`
+ // noop if the staker is not delegated
cheats.startPrank(address(strategyManagerMock));
- delegationManager.decreaseDelegatedShares(staker, updatedStrategies, updatedShares);
+ for (uint256 i = 0; i < strategies.length; ++i) {
+ delegationManager.increaseDelegatedShares(staker, strategies[i], shares);
+ // store delegated shares in a mapping
+ delegatedSharesBefore[strategies[i]] = delegationManager.operatorShares(delegatedTo, strategies[i]);
+ // also construct an array which we'll use in another loop
+ sharesInputArray[i] = shares;
+ totalSharesForStrategyInArray[address(strategies[i])] += sharesInputArray[i];
+ }
+ cheats.stopPrank();
+
+ bool isDelegated = delegationManager.isDelegated(staker);
+
+ // for each strategy in `strategies`, decrease delegated shares by `shares`
+ {
+ cheats.startPrank(address(strategyManagerMock));
+ address operatorToDecreaseSharesOf = delegationManager.delegatedTo(staker);
+ if (isDelegated) {
+ for (uint256 i = 0; i < strategies.length; ++i) {
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit OperatorSharesDecreased(
+ operatorToDecreaseSharesOf,
+ staker,
+ strategies[i],
+ sharesInputArray[i]
+ );
+ delegationManager.decreaseDelegatedShares(staker, strategies[i], sharesInputArray[i]);
+ }
+ }
+ cheats.stopPrank();
+ }
+
+ // check shares after call to `decreaseDelegatedShares`
+ for (uint256 i = 0; i < strategies.length; ++i) {
+ uint256 delegatedSharesAfter = delegationManager.operatorShares(delegatedTo, strategies[i]);
+
+ if (isDelegated) {
+ assertEq(
+ delegatedSharesAfter + totalSharesForStrategyInArray[address(strategies[i])],
+ delegatedSharesBefore[strategies[i]],
+ "delegated shares did not decrement correctly"
+ );
+ assertEq(delegatedSharesAfter, 0, "nonzero shares delegated to");
+ } else {
+ assertEq(
+ delegatedSharesAfter,
+ delegatedSharesBefore[strategies[i]],
+ "delegated shares decremented incorrectly"
+ );
+ assertEq(delegatedSharesBefore[strategies[i]], 0, "nonzero shares delegated to zero address!");
+ }
+ }
+ }
+}
+
+contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests {
+ // @notice Verifies that undelegating is not possible when the "undelegation paused" switch is flipped
+ function test_undelegate_revert_paused(address staker) public filterFuzzedAddressInputs(staker) {
+ // set the pausing flag
+ cheats.prank(pauser);
+ delegationManager.pause(2 ** PAUSED_ENTER_WITHDRAWAL_QUEUE);
+
+ cheats.prank(staker);
+ cheats.expectRevert("Pausable: index is paused");
+ delegationManager.undelegate(staker);
+ }
+
+ function testFuzz_undelegate_revert_notDelgated(
+ address undelegatedStaker
+ ) public filterFuzzedAddressInputs(undelegatedStaker) {
+ cheats.assume(undelegatedStaker != defaultOperator);
+ assertFalse(delegationManager.isDelegated(undelegatedStaker), "bad test setup");
+
+ cheats.prank(undelegatedStaker);
+ cheats.expectRevert("DelegationManager.undelegate: staker must be delegated to undelegate");
+ delegationManager.undelegate(undelegatedStaker);
+ }
+
+ // @notice Verifies that an operator cannot undelegate from themself (this should always be forbidden)
+ function testFuzz_undelegate_revert_stakerIsOperator(address operator) public filterFuzzedAddressInputs(operator) {
+ _registerOperatorWithBaseDetails(operator);
+
+ cheats.prank(operator);
+ cheats.expectRevert("DelegationManager.undelegate: operators cannot be undelegated");
+ delegationManager.undelegate(operator);
+ }
+
+ /**
+ * @notice verifies that `DelegationManager.undelegate` reverts if trying to undelegate an operator from themselves
+ * @param callFromOperatorOrApprover -- calls from the operator if 'false' and the 'approver' if true
+ */
+ function testFuzz_undelegate_operatorCannotForceUndelegateThemself(
+ address delegationApprover,
+ bool callFromOperatorOrApprover
+ ) public filterFuzzedAddressInputs(delegationApprover) {
+ // register *this contract* as an operator with the default `delegationApprover`
+ _registerOperatorWithDelegationApprover(defaultOperator);
+
+ address caller;
+ if (callFromOperatorOrApprover) {
+ caller = delegationApprover;
+ } else {
+ caller = defaultOperator;
+ }
+
+ // try to call the `undelegate` function and check for reversion
+ cheats.prank(caller);
+ cheats.expectRevert("DelegationManager.undelegate: operators cannot be undelegated");
+ delegationManager.undelegate(defaultOperator);
+ }
+
+ //TODO: verify that this check is even needed
+ function test_undelegate_revert_zeroAddress() public {
+ _registerOperatorWithBaseDetails(defaultOperator);
+ _delegateToOperatorWhoAcceptsAllStakers(address(0), defaultOperator);
+
+ cheats.prank(address(0));
+ cheats.expectRevert("DelegationManager.undelegate: cannot undelegate zero address");
+ delegationManager.undelegate(address(0));
+ }
+
+ /**
+ * @notice Verifies that the `undelegate` function has proper access controls (can only be called by the operator who the `staker` has delegated
+ * to or the operator's `delegationApprover`), or the staker themselves
+ */
+ function testFuzz_undelegate_revert_invalidCaller(
+ address invalidCaller
+ ) public filterFuzzedAddressInputs(invalidCaller) {
+ address staker = address(0x123);
+ // filter out addresses that are actually allowed to call the function
+ cheats.assume(invalidCaller != staker);
+ cheats.assume(invalidCaller != defaultOperator);
+ cheats.assume(invalidCaller != defaultApprover);
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+ _delegateToOperatorWhoRequiresSig(staker, defaultOperator);
+
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("DelegationManager.undelegate: caller cannot undelegate staker");
+ delegationManager.undelegate(staker);
+ }
+
+ /**
+ * Staker is undelegated from an operator, via a call to `undelegate`, properly originating from the staker's address.
+ * Reverts if the staker is themselves an operator (i.e. they are delegated to themselves)
+ * Does nothing if the staker is already undelegated
+ * Properly undelegates the staker, i.e. the staker becomes “delegated to” the zero address, and `isDelegated(staker)` returns ‘false’
+ * Emits a `StakerUndelegated` event
+ */
+ function testFuzz_undelegate_noDelegateableShares(address staker) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != defaultOperator);
+
+ // register *this contract* as an operator and delegate from the `staker` to them
+ _registerOperatorWithBaseDetails(defaultOperator);
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerUndelegated(staker, delegationManager.delegatedTo(staker));
+ cheats.prank(staker);
+ bytes32[] memory withdrawalRoots = delegationManager.undelegate(staker);
+
+ assertEq(withdrawalRoots.length, 0, "withdrawalRoot should be an empty array");
+ assertEq(
+ delegationManager.delegatedTo(staker),
+ address(0),
+ "undelegated staker should be delegated to zero address"
+ );
+ assertFalse(delegationManager.isDelegated(staker), "staker not undelegated");
+ }
+
+ /**
+ * @notice Verifies that the `undelegate` function allows for a force undelegation
+ */
+ function testFuzz_undelegate_forceUndelegation_noDelegateableShares(
+ address staker,
+ bytes32 salt,
+ bool callFromOperatorOrApprover
+ ) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != defaultOperator);
+
+ _registerOperatorWithDelegationApprover(defaultOperator);
+ _delegateToOperatorWhoRequiresSig(staker, defaultOperator, salt);
+
+ address caller;
+ if (callFromOperatorOrApprover) {
+ caller = defaultApprover;
+ } else {
+ caller = defaultOperator;
+ }
+
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerForceUndelegated(staker, defaultOperator);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit StakerUndelegated(staker, defaultOperator);
+ cheats.prank(caller);
+ bytes32[] memory withdrawalRoots = delegationManager.undelegate(staker);
+
+ assertEq(withdrawalRoots.length, 0, "withdrawalRoot should be an empty array");
+ assertEq(
+ delegationManager.delegatedTo(staker),
+ address(0),
+ "undelegated staker should be delegated to zero address"
+ );
+ assertFalse(delegationManager.isDelegated(staker), "staker not undelegated");
+ }
+}
+
+contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTests {
+ function test_Revert_WhenEnterQueueWithdrawalsPaused() public {
+ cheats.prank(pauser);
+ delegationManager.pause(2 ** PAUSED_ENTER_WITHDRAWAL_QUEUE);
+ (IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams, , ) = _setUpQueueWithdrawalsSingleStrat({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ strategy: strategyMock,
+ withdrawalAmount: 100
+ });
+ cheats.expectRevert("Pausable: index is paused");
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+ }
+
+ function test_Revert_WhenQueueWithdrawalParamsLengthMismatch() public {
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ strategyArray[0] = strategyMock;
+ uint256[] memory shareAmounts = new uint256[](2);
+ shareAmounts[0] = 100;
+ shareAmounts[1] = 100;
+
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams = new IDelegationManager.QueuedWithdrawalParams[](1);
+ queuedWithdrawalParams[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ withdrawer: defaultStaker
+ });
+
+ cheats.expectRevert("DelegationManager.queueWithdrawal: input length mismatch");
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+ }
+
+ function test_Revert_WhenNotStakerWithdrawer(address withdrawer) public {
+ cheats.assume(withdrawer != defaultStaker);
+
+ (IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams, , ) = _setUpQueueWithdrawalsSingleStrat({
+ staker: defaultStaker,
+ withdrawer: withdrawer,
+ strategy: strategyMock,
+ withdrawalAmount: 100
+ });
+ cheats.expectRevert("DelegationManager.queueWithdrawal: withdrawer must be staker");
+ cheats.prank(defaultStaker);
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+ }
+
+ function test_Revert_WhenEmptyStrategiesArray() public {
+ IStrategy[] memory strategyArray = new IStrategy[](0);
+ uint256[] memory shareAmounts = new uint256[](0);
+ address withdrawer = defaultOperator;
+
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams = new IDelegationManager.QueuedWithdrawalParams[](1);
+ queuedWithdrawalParams[0] = IDelegationManager.QueuedWithdrawalParams({
+ strategies: strategyArray,
+ shares: shareAmounts,
+ withdrawer: withdrawer
+ });
+
+ cheats.expectRevert("DelegationManager._removeSharesAndQueueWithdrawal: strategies cannot be empty");
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+ }
+
+ /**
+ * @notice Verifies that `DelegationManager.queueWithdrawals` properly queues a withdrawal for the `withdrawer`
+ * from the `strategy` for the `sharesAmount`.
+ * - 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_SingleStrat(
+ address staker,
+ uint256 depositAmount,
+ uint256 withdrawalAmount
+ ) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != defaultOperator);
+ cheats.assume(withdrawalAmount > 0 && withdrawalAmount <= depositAmount);
+ uint256[] memory sharesAmounts = new uint256[](1);
+ sharesAmounts[0] = depositAmount;
+ // sharesAmounts is single element so returns single strategy
+ IStrategy[] memory strategies = _deployAndDepositIntoStrategies(staker, sharesAmounts);
+ _registerOperatorWithBaseDetails(defaultOperator);
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ (
+ IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawalParams,
+ IDelegationManager.Withdrawal memory withdrawal,
+ bytes32 withdrawalRoot
+ ) = _setUpQueueWithdrawalsSingleStrat({
+ staker: staker,
+ withdrawer: staker,
+ strategy: strategies[0],
+ withdrawalAmount: withdrawalAmount
+ });
+ assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker should be delegated to operator");
+ uint256 nonceBefore = delegationManager.cumulativeWithdrawalsQueued(staker);
+ uint256 delegatedSharesBefore = delegationManager.operatorShares(defaultOperator, strategies[0]);
+
+ // queueWithdrawals
+ cheats.prank(staker);
+ cheats.expectEmit(true, true, true, true, address(delegationManager));
+ emit WithdrawalQueued(withdrawalRoot, withdrawal);
+ delegationManager.queueWithdrawals(queuedWithdrawalParams);
+
+ uint256 nonceAfter = delegationManager.cumulativeWithdrawalsQueued(staker);
+ uint256 delegatedSharesAfter = delegationManager.operatorShares(defaultOperator, strategies[0]);
+ assertEq(nonceBefore + 1, nonceAfter, "staker nonce should have incremented");
+ assertEq(delegatedSharesBefore - withdrawalAmount, delegatedSharesAfter, "delegated shares not decreased correctly");
+ }
+
+ /**
+ * @notice Verifies that `DelegationManager.queueWithdrawals` properly queues a withdrawal for the `withdrawer`
+ * with multiple strategies and sharesAmounts. 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_MultipleStrats(
+ address staker,
+ uint256[] memory depositAmounts
+ ) public filterFuzzedAddressInputs(staker){
+ cheats.assume(depositAmounts.length > 0 && depositAmounts.length <= 32);
+ uint256[] memory withdrawalAmounts = _fuzzWithdrawalAmounts(depositAmounts);
+
+ IStrategy[] memory strategies = _deployAndDepositIntoStrategies(staker, depositAmounts);
+ _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 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 {
+ function test_Revert_WhenExitWithdrawalQueuePaused() public {
+ cheats.prank(pauser);
+ delegationManager.pause(2 ** PAUSED_EXIT_WITHDRAWAL_QUEUE);
+ _registerOperatorWithBaseDetails(defaultOperator);
+ (
+ IDelegationManager.Withdrawal memory withdrawal,
+ IERC20[] memory tokens,
+ /* bytes32 withdrawalRoot */
+ ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ depositAmount: 100,
+ withdrawalAmount: 100
+ });
+ _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
+
+ cheats.expectRevert("Pausable: index is paused");
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
+ }
+
+ function test_Revert_WhenInvalidWithdrawalRoot() public {
+ _registerOperatorWithBaseDetails(defaultOperator);
+ (
+ IDelegationManager.Withdrawal memory withdrawal,
+ IERC20[] memory tokens,
+ bytes32 withdrawalRoot
+ ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ depositAmount: 100,
+ withdrawalAmount: 100
+ });
+ _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
+
+ assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending");
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.prank(defaultStaker);
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
+ assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be completed and marked false now");
+
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.expectRevert("DelegationManager._completeQueuedWithdrawal: action is not in queue");
+ cheats.prank(defaultStaker);
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
+ }
+
+ /**
+ * @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 */
+ ) = _setUpCompleteQueuedWithdrawal({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ depositAmounts: depositAmounts,
+ withdrawalAmounts: withdrawalAmounts
+ });
+
+ // 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();
}
-}
\ No newline at end of file
+ function test_Revert_WhenNotCalledByWithdrawer() public {
+ _registerOperatorWithBaseDetails(defaultOperator);
+ (
+ IDelegationManager.Withdrawal memory withdrawal,
+ IERC20[] memory tokens,
+ /*bytes32 withdrawalRoot*/
+ ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ depositAmount: 100,
+ withdrawalAmount: 100
+ });
+ _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
+
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.expectRevert("DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action");
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, false);
+ }
+
+ function test_Revert_WhenTokensArrayLengthMismatch() public {
+ _registerOperatorWithBaseDetails(defaultOperator);
+ (IDelegationManager.Withdrawal memory withdrawal, , ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ staker: defaultStaker,
+ withdrawer: defaultStaker,
+ depositAmount: 100,
+ withdrawalAmount: 100
+ });
+ _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
+
+ IERC20[] memory tokens = new IERC20[](0);
+ cheats.roll(block.number + delegationManager.getWithdrawalDelay(withdrawal.strategies));
+ cheats.expectRevert("DelegationManager._completeQueuedWithdrawal: input length mismatch");
+ cheats.prank(defaultStaker);
+ delegationManager.completeQueuedWithdrawal(withdrawal, tokens, 0 /* middlewareTimesIndex */, true);
+ }
+
+ /**
+ * @notice Verifies that `DelegationManager.completeQueuedWithdrawal` properly completes a queued withdrawal for the `withdrawer`
+ * for a single strategy. Withdraws as tokens so there are no operator shares increase.
+ * - Asserts that the withdrawalRoot is True before `completeQueuedWithdrawal` and False after
+ * - Asserts operatorShares is unchanged after `completeQueuedWithdrawal`
+ * - Checks that event `WithdrawalCompleted` is emitted with withdrawalRoot
+ */
+ function test_completeQueuedWithdrawal_SingleStratWithdrawAsTokens(
+ address staker,
+ uint256 depositAmount,
+ uint256 withdrawalAmount
+ ) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != defaultOperator);
+ cheats.assume(withdrawalAmount > 0 && withdrawalAmount <= depositAmount);
+ _registerOperatorWithBaseDetails(defaultOperator);
+ (
+ IDelegationManager.Withdrawal memory withdrawal,
+ IERC20[] memory tokens,
+ bytes32 withdrawalRoot
+ ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ staker: staker,
+ withdrawer: staker,
+ depositAmount: depositAmount,
+ withdrawalAmount: withdrawalAmount
+ });
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, withdrawal.strategies[0]);
+ assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending");
+
+ // completeQueuedWithdrawal
+ 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);
+
+ uint256 operatorSharesAfter = delegationManager.operatorShares(defaultOperator, withdrawal.strategies[0]);
+ assertEq(operatorSharesAfter, operatorSharesBefore, "operator shares should be unchanged");
+ assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be completed and marked false now");
+ }
+
+ /**
+ * @notice Verifies that `DelegationManager.completeQueuedWithdrawal` properly completes a queued withdrawal for the `withdrawer`
+ * for a single strategy. Withdraws as shares so if the withdrawer is delegated, operator shares increase. In the test case, this only
+ * happens if staker and withdrawer are fuzzed the same address (i.e. staker == withdrawer)
+ * - Asserts that the withdrawalRoot is True before `completeQueuedWithdrawal` and False after
+ * - Asserts if staker == withdrawer, operatorShares increase, otherwise operatorShares are unchanged
+ * - Checks that event `WithdrawalCompleted` is emitted with withdrawalRoot
+ */
+ function test_completeQueuedWithdrawal_SingleStratWithdrawAsShares(
+ address staker,
+ uint256 depositAmount,
+ uint256 withdrawalAmount
+ ) public filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != defaultOperator);
+ cheats.assume(withdrawalAmount > 0 && withdrawalAmount <= depositAmount);
+ _registerOperatorWithBaseDetails(defaultOperator);
+
+ (
+ IDelegationManager.Withdrawal memory withdrawal,
+ IERC20[] memory tokens,
+ bytes32 withdrawalRoot
+ ) = _setUpCompleteQueuedWithdrawalSingleStrat({
+ staker: staker,
+ withdrawer: staker,
+ depositAmount: depositAmount,
+ withdrawalAmount: withdrawalAmount
+ });
+ _delegateToOperatorWhoAcceptsAllStakers(staker, defaultOperator);
+ uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, withdrawal.strategies[0]);
+ assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending");
+
+ // completeQueuedWithdrawal
+ 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]);
+ // 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");
+ }
+}
diff --git a/src/test/unit/EigenPod-PodManagerUnit.t.sol b/src/test/unit/EigenPod-PodManagerUnit.t.sol
new file mode 100644
index 0000000000..ca33ffde05
--- /dev/null
+++ b/src/test/unit/EigenPod-PodManagerUnit.t.sol
@@ -0,0 +1,648 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+
+import "src/contracts/pods/EigenPodManager.sol";
+import "src/contracts/pods/EigenPod.sol";
+import "src/contracts/pods/EigenPodPausingConstants.sol";
+
+import "src/test/utils/EigenLayerUnitTestSetup.sol";
+import "src/test/utils/ProofParsing.sol";
+import "src/test/harnesses/EigenPodManagerWrapper.sol";
+import "src/test/mocks/EigenPodMock.sol";
+import "src/test/mocks/Dummy.sol";
+import "src/test/mocks/ETHDepositMock.sol";
+import "src/test/mocks/DelayedWithdrawalRouterMock.sol";
+import "src/test/mocks/BeaconChainOracleMock.sol";
+import "src/test/mocks/Reenterer.sol";
+import "src/test/events/IEigenPodEvents.sol";
+import "src/test/events/IEigenPodManagerEvents.sol";
+
+contract EigenPod_PodManager_UnitTests is EigenLayerUnitTestSetup {
+ // Contracts Under Test: EigenPodManager & EigenPod
+ EigenPod public eigenPod;
+ EigenPod public podImplementation;
+ IBeacon public eigenPodBeacon;
+ EigenPodManager public eigenPodManager;
+ EigenPodManager public eigenPodManagerImplementation;
+ EigenPodManagerWrapper public eigenPodManagerWrapper; // Implementation contract
+
+ // Mocks
+ IETHPOSDeposit public ethPOSMock;
+ IDelayedWithdrawalRouter public delayedWithdrawalRouterMock;
+ BeaconChainOracleMock beaconChainOracle;
+
+ // Constants
+ uint64 public constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9;
+ uint64 public constant GOERLI_GENESIS_TIME = 1616508000;
+ address public initialOwner = address(this);
+
+ // Owner for which proofs are generated; eigenPod above is owned by this address
+ bytes internal constant beaconProxyBytecode =
+ hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";
+ address public constant podOwner = address(42000094993494);
+
+ address public constant podAddress = address(0x49c486E3f4303bc11C02F952Fe5b08D0AB22D443);
+
+ function setUp() public override virtual {
+ // Setup
+ EigenLayerUnitTestSetup.setUp();
+
+ // Deploy Mocks
+ ethPOSMock = new ETHPOSDepositMock();
+ delayedWithdrawalRouterMock = new DelayedWithdrawalRouterMock();
+ beaconChainOracle = new BeaconChainOracleMock();
+
+ // Deploy proxy contract for EPM
+ EmptyContract emptyContract = new EmptyContract();
+ eigenPodManager = EigenPodManager(
+ address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
+ );
+
+ // Deploy EigenPod Implementation and beacon
+ podImplementation = new EigenPod(
+ ethPOSMock,
+ delayedWithdrawalRouterMock,
+ eigenPodManager,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
+
+ eigenPodBeacon = new UpgradeableBeacon(address(podImplementation));
+
+ // Deploy EigenPodManager implementation
+ eigenPodManagerWrapper = new EigenPodManagerWrapper(
+ ethPOSMock,
+ eigenPodBeacon,
+ strategyManagerMock,
+ slasherMock,
+ delegationManagerMock
+ );
+
+ eigenLayerProxyAdmin.upgradeAndCall(
+ TransparentUpgradeableProxy(payable(address(eigenPodManager))),
+ address(eigenPodManagerWrapper),
+ abi.encodeWithSelector(
+ EigenPodManager.initialize.selector,
+ beaconChainOracle,
+ initialOwner,
+ pauserRegistry,
+ 0 /*initialPausedStatus*/
+ )
+ );
+
+ // Below is a hack to get the eigenPod address that proofs prove against
+
+ // Deploy Proxy same way as EigenPodManager does
+ eigenPod = EigenPod(payable(
+ Create2.deploy(
+ 0,
+ bytes32(uint256(uint160(address(podOwner)))),
+ // set the beacon address to the eigenPodBeacon
+ abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
+ )));
+
+ // Etch the eigenPod code to the address for which proofs are generated
+ bytes memory code = address(eigenPod).code;
+ cheats.etch(podAddress, code);
+ eigenPod = EigenPod(payable(podAddress));
+
+ // Store the eigenPodBeacon address in the eigenPod beacon proxy
+ bytes32 beaconSlot = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
+ cheats.store(address(eigenPod), beaconSlot, bytes32(uint256(uint160(address(eigenPodBeacon)))));
+
+ // Initialize pod
+ eigenPod.initialize(address(podOwner));
+
+ // Set storage in EPM
+ EigenPodManagerWrapper(address(eigenPodManager)).setPodAddress(podOwner, eigenPod);
+
+ eigenPodManager.setDenebForkTimestamp(type(uint64).max);
+ }
+}
+
+contract EigenPod_PodManager_UnitTests_EigenPodPausing is EigenPod_PodManager_UnitTests {
+ /**
+ * 1. verifyBalanceUpdates revert when PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE set
+ * 2. verifyAndProcessWithdrawals revert when PAUSED_EIGENPODS_VERIFY_WITHDRAWAL set
+ * 3. verifyWithdrawalCredentials revert when PAUSED_EIGENPODS_VERIFY_CREDENTIALS set
+ * 4. activateRestaking revert when PAUSED_EIGENPODS_VERIFY_CREDENTIALS set
+ */
+
+ /// @notice Index for flag that pauses creation of new EigenPods when set. See EigenPodManager code for details.
+ uint8 internal constant PAUSED_NEW_EIGENPODS = 0;
+ /// @notice Index for flag that pauses all withdrawal-of-restaked ETH related functionality `function *of the EigenPodManager* when set. See EigenPodManager code for details.
+ uint8 internal constant PAUSED_WITHDRAW_RESTAKED_ETH = 1;
+
+ /// @notice Index for flag that pauses the deposit related functions *of the EigenPods* when set. see EigenPod code for details.
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_CREDENTIALS = 2;
+ /// @notice Index for flag that pauses the `verifyBalanceUpdate` function *of the EigenPods* when set. see EigenPod code for details.
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE = 3;
+ /// @notice Index for flag that pauses the `verifyBeaconChainFullWithdrawal` function *of the EigenPods* when set. see EigenPod code for details.
+ uint8 internal constant PAUSED_EIGENPODS_VERIFY_WITHDRAWAL = 4;
+
+ function test_verifyBalanceUpdates_revert_pausedEigenVerifyBalanceUpdate() public {
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct;
+
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ bytes[] memory proofsArray = new bytes[](1);
+ uint40[] memory validatorIndices = new uint40[](1);
+
+ // pause the contract
+ cheats.prank(address(pauser));
+ eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE);
+
+ cheats.prank(address(podOwner));
+ cheats.expectRevert(bytes("EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"));
+ eigenPod.verifyBalanceUpdates(0, validatorIndices, stateRootProofStruct, proofsArray, validatorFieldsArray);
+ }
+
+ function test_verifyAndProcessWithdrawals_revert_pausedEigenVerifyWithdrawal() public {
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct;
+ BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray;
+
+ bytes[] memory validatorFieldsProofArray = new bytes[](1);
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
+
+ // pause the contract
+ cheats.prank(address(pauser));
+ eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_WITHDRAWAL);
+
+ cheats.prank(address(podOwner));
+ cheats.expectRevert(bytes("EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"));
+ eigenPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofsArray,
+ validatorFieldsProofArray,
+ validatorFieldsArray,
+ withdrawalFieldsArray
+ );
+ }
+
+ function test_verifyWithdrawalCredentials_revert_pausedEigenVerifyCredentials() public {
+ BeaconChainProofs.StateRootProof memory stateRootProofStruct;
+
+ bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
+ bytes[] memory proofsArray = new bytes[](1);
+ uint40[] memory validatorIndices = new uint40[](1);
+
+ // pause the contract
+ cheats.prank(address(pauser));
+ eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_CREDENTIALS);
+
+ cheats.prank(address(podOwner));
+ cheats.expectRevert(bytes("EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"));
+ eigenPod.verifyWithdrawalCredentials(
+ 0,
+ stateRootProofStruct,
+ validatorIndices,
+ proofsArray,
+ validatorFieldsArray
+ );
+ }
+
+ function test_activateRestaking_revert_pausedEigenVerifyCredentials() public {
+ // pause the contract
+ cheats.prank(address(pauser));
+ eigenPodManager.pause(2 ** PAUSED_EIGENPODS_VERIFY_CREDENTIALS);
+
+ cheats.prank(address(podOwner));
+ cheats.expectRevert(bytes("EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"));
+ eigenPod.activateRestaking();
+ }
+}
+
+contract EigenPod_PodManager_UnitTests_EigenPod is EigenPod_PodManager_UnitTests {
+ /**
+ * @notice Tests function calls from EPM to EigenPod
+ * 1. Stake works when pod is deployed
+ * 2. Stake when pod is not deployed -> check that ethPOS deposit contract is correct for this and above test
+ */
+
+ bytes public constant pubkey = hex"88347ed1c492eedc97fc8c506a35d44d81f27a0c7a1c661b35913cfd15256c0cccbd34a83341f505c7de2983292f2cab";
+
+ function test_stake_podAlreadyDeployed(bytes memory signature, bytes32 depositDataRoot) public {
+ uint256 stakeAmount = 32e18;
+
+ uint256 numPods = eigenPodManager.numPods();
+ emit log_named_uint("numPods", numPods);
+
+ cheats.startPrank(podOwner);
+ cheats.deal(podOwner, stakeAmount);
+ eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
+ cheats.stopPrank();
+ }
+
+ function test_stake_podNotDeployed(bytes memory signature, bytes32 depositDataRoot) public {
+ address newPodOwner = address(69696969696);
+
+ uint256 stakeAmount = 32e18;
+
+ cheats.startPrank(newPodOwner);
+ cheats.deal(newPodOwner, stakeAmount);
+ eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
+ cheats.stopPrank();
+ }
+}
+
+contract EigenPod_PodManager_UnitTests_EigenPodManager is EigenPod_PodManager_UnitTests, ProofParsing, IEigenPodEvents {
+ /**
+ * @notice Tests function calls from EigenPod to EigenPodManager
+ * 1. Verify withdrawal credentials and call `recordBeaconChainETHBalanceUpdate` -> assert shares are updated
+ * 2. Do a full withdrawal and call `recordBeaconChainETHBalanceUpdate` -> assert shares are updated
+ * 3. Do a partial withdrawal and call `recordBeaconChainETHBalanceUpdate` -> assert shares are updated
+ * 4. Verify balance updates and call `recordBeaconChainEThBalanceUpdate` -> assert shares are updated
+ * 5. Withdraw restaked beacon chain ETH
+ */
+
+ using BeaconChainProofs for *;
+
+ // Params to verify withdrawal credentials
+ BeaconChainProofs.StateRootProof stateRootProofStruct;
+ uint40[] validatorIndices;
+ bytes[] validatorFieldsProofs;
+ bytes32[][] validatorFields;
+ // BeaconChainProofs.BalanceUpdateProof[] balanceUpdateProof;
+ BeaconChainProofs.WithdrawalProof[] withdrawalProofs;
+ bytes32[][] withdrawalFields;
+
+ function test_verifyWithdrawalCredentials() public {
+ // Arrange: Set up conditions to verify withdrawal credentials
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _setWithdrawalCredentialParams();
+
+ // Set oracle block root and warp time
+ _setOracleBlockRoot();
+ cheats.warp(GOERLI_GENESIS_TIME + 1 days);
+ uint64 oracleTimestamp = uint64(block.timestamp);
+
+ // Save state for checks
+ int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
+
+ // Act: Verify withdrawal credentials and record the balance update
+ cheats.prank(podOwner);
+ eigenPod.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ stateRootProofStruct,
+ validatorIndices,
+ validatorFieldsProofs,
+ validatorFields
+ );
+
+ // Assert: Check that the shares are updated correctly
+ int256 updatedShares = eigenPodManager.podOwnerShares(podOwner);
+ assertTrue(updatedShares != initialShares, "Shares should be updated after verifying withdrawal credentials");
+ assertEq(updatedShares, 32e18, "Shares should be 32ETH in wei after verifying withdrawal credentials");
+ }
+
+ function test_balanceUpdate_negativeSharesDelta() public {
+ // Verify withdrawal credentials
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _verifyWithdrawalCredentials();
+
+ // Set JSON
+ setJSON("src/test/test-data/balanceUpdateProof_balance28ETH_302913.json");
+ bytes32 validatorPubkeyHash = validatorFields[0].getPubkeyHash();
+
+ // Set proof params, oracle block root, and warp time
+ _setBalanceUpdateParams();
+ _setOracleBlockRoot();
+ cheats.warp(GOERLI_GENESIS_TIME);
+ uint64 oracleTimestamp = uint64(block.timestamp);
+
+ // Save state for checks
+ int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
+ uint64 newValidatorBalance = validatorFields[0].getEffectiveBalanceGwei();
+
+ // Verify balance update
+ eigenPod.verifyBalanceUpdates(
+ oracleTimestamp,
+ validatorIndices,
+ stateRootProofStruct,
+ validatorFieldsProofs,
+ validatorFields
+ );
+
+ // Checks
+ int256 updatedShares = eigenPodManager.podOwnerShares(podOwner);
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(validatorPubkeyHash);
+ assertEq(validatorInfo.restakedBalanceGwei, newValidatorBalance, "Restaked balance gwei is incorrect");
+ assertLt(updatedShares - initialShares, 0, "Shares delta should be negative");
+ int256 expectedSharesDiff = (int256(uint256(newValidatorBalance)) - int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR))) * 1e9;
+ assertEq(updatedShares - initialShares, expectedSharesDiff, "Shares delta should be equal to restaked balance");
+ }
+
+ function test_balanceUpdate_positiveSharesDelta() public {
+ // Verify withdrawal credentials
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913_30ETHBalance.json");
+ _verifyWithdrawalCredentials();
+
+ // Set JSON
+ setJSON("src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+ bytes32 validatorPubkeyHash = validatorFields[0].getPubkeyHash();
+
+ // Set proof params, oracle block root, and warp time
+ _setBalanceUpdateParams();
+ _setOracleBlockRoot();
+ cheats.warp(GOERLI_GENESIS_TIME);
+ uint64 oracleTimestamp = uint64(block.timestamp);
+
+ // Save state for checks
+ int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
+ uint64 newValidatorBalance = validatorFields[0].getEffectiveBalanceGwei();
+
+ // Verify balance update
+ eigenPod.verifyBalanceUpdates(
+ oracleTimestamp,
+ validatorIndices,
+ stateRootProofStruct,
+ validatorFieldsProofs,
+ validatorFields
+ );
+
+ // Checks
+ int256 updatedShares = eigenPodManager.podOwnerShares(podOwner);
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(validatorPubkeyHash);
+ 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 {
+ // Verify withdrawal credentials
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913_30ETHBalance.json");
+ _verifyWithdrawalCredentials();
+
+ // Set JSON
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ bytes32 validatorPubkeyHash = validatorFields[0].getPubkeyHash();
+
+ // Set proof params, block root
+ _setWithdrawalProofParams();
+ _setOracleBlockRoot();
+
+ // Save state for checks; deal EigenPod withdrawal router balance
+ uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
+ withdrawalFields[0][BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
+ );
+ uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * 1e9;
+ cheats.deal(address(eigenPod), leftOverBalanceWEI);
+ int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
+
+ // Withdraw
+ eigenPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofs,
+ validatorFieldsProofs,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Checks
+ int256 updatedShares = eigenPodManager.podOwnerShares(podOwner);
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(validatorPubkeyHash);
+ assertEq(validatorInfo.restakedBalanceGwei, 0, "Restaked balance gwei should be 0");
+ assertGt(updatedShares - initialShares, 0, "Shares diff should be positive");
+ int256 expectedSharesDiff = (int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR))*1e9) - initialShares;
+ assertEq(updatedShares - initialShares, expectedSharesDiff, "Shares delta incorrect");
+ assertEq(updatedShares, 32e18, "Shares should be 32e18");
+ assertEq(address(delayedWithdrawalRouterMock).balance, leftOverBalanceWEI, "Incorrect amount sent to delayed withdrawal router");
+ }
+
+ function test_withdrawRestakedBeaconChainETH() public {
+ test_fullWithdrawal_excess32ETH();
+
+ // Deal eigenPod balance - max restaked balance
+ cheats.deal(address(eigenPod), 32 ether);
+
+ cheats.startPrank(address(delegationManagerMock));
+ vm.expectEmit(true, true, true, true);
+ emit RestakedBeaconChainETHWithdrawn(podOwner, 32 ether);
+ eigenPodManager.withdrawSharesAsTokens(
+ podOwner,
+ podOwner,
+ uint256(eigenPodManager.podOwnerShares(podOwner))
+ );
+ cheats.stopPrank();
+
+ // Checks
+ assertEq(address(podOwner).balance, 32 ether, "EigenPod balance should be 0");
+ assertEq(address(eigenPod).balance, 0, "EigenPod balance should be 0");
+ assertEq(eigenPod.withdrawableRestakedExecutionLayerGwei(), 0, "Restaked execution layer gwei should be 0");
+ }
+
+ function test_fullWithdrawal_less32ETH() public {
+ // Verify withdrawal credentials
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _verifyWithdrawalCredentials();
+
+ // Set JSON
+ setJSON("src/test/test-data/fullWithdrawalProof_Latest_28ETH.json");
+ bytes32 validatorPubkeyHash = validatorFields[0].getPubkeyHash();
+
+ // Set proof params, block root
+ _setWithdrawalProofParams();
+ _setOracleBlockRoot();
+
+ // Save State
+ uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
+ withdrawalFields[0][BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
+ );
+ int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
+
+ // Withdraw
+ eigenPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofs,
+ validatorFieldsProofs,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Checks
+ int256 updatedShares = eigenPodManager.podOwnerShares(podOwner);
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(validatorPubkeyHash);
+ assertEq(validatorInfo.restakedBalanceGwei, 0, "Restaked balance gwei is incorrect");
+ assertLt(updatedShares - initialShares, 0, "Shares delta should be negative");
+ int256 expectedSharesDiff = (int256(uint256(withdrawalAmountGwei)) - int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR))) * 1e9;
+ assertEq(updatedShares - initialShares, expectedSharesDiff, "Shares delta incorrect");
+ }
+
+ function test_partialWithdrawal() public {
+ // Set JSON & params
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _verifyWithdrawalCredentials();
+
+ // Set JSON
+ setJSON("./src/test/test-data/partialWithdrawalProof_Latest.json");
+ bytes32 validatorPubkeyHash = validatorFields[0].getPubkeyHash();
+
+ // Set proof params, block root
+ _setWithdrawalProofParams();
+ _setOracleBlockRoot();
+
+ // Assert that partial withdrawal code path will be tested
+ assertLt(withdrawalProofs[0].getWithdrawalEpoch(), validatorFields[0].getWithdrawableEpoch(), "Withdrawal epoch should be less than the withdrawable epoch");
+
+ // Save state for checks; deal EigenPod withdrawal router balance
+ uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
+ withdrawalFields[0][BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
+ );
+ cheats.deal(address(eigenPod), withdrawalAmountGwei * 1e9); // deal full withdrawal amount since it's a partial withdrawal
+ uint64 initialRestakedBalance = (eigenPod.validatorPubkeyHashToInfo(validatorPubkeyHash)).restakedBalanceGwei;
+ int256 initialShares = eigenPodManager.podOwnerShares(podOwner);
+
+ // Withdraw
+ eigenPod.verifyAndProcessWithdrawals(
+ 0,
+ stateRootProofStruct,
+ withdrawalProofs,
+ validatorFieldsProofs,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Checks
+ int256 updatedShares = eigenPodManager.podOwnerShares(podOwner);
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(validatorPubkeyHash);
+ assertEq(validatorInfo.restakedBalanceGwei, initialRestakedBalance, "Restaked balance gwei should be unchanged");
+ assertEq(updatedShares - initialShares, 0, "Shares diff should be 0");
+ assertEq(address(delayedWithdrawalRouterMock).balance, withdrawalAmountGwei * 1e9, "Incorrect amount sent to delayed withdrawal router");
+ }
+
+ // Helper Functions
+ function _getStateRootProof() internal returns (BeaconChainProofs.StateRootProof memory) {
+ return BeaconChainProofs.StateRootProof(getBeaconStateRoot(), abi.encodePacked(getStateRootProof()));
+ }
+
+ function _setOracleBlockRoot() internal {
+ bytes32 latestBlockRoot = getLatestBlockRoot();
+ //set beaconStateRoot
+ beaconChainOracle.setOracleBlockRootAtTimestamp(latestBlockRoot);
+ }
+
+ function _verifyWithdrawalCredentials() internal {
+ _setWithdrawalCredentialParams();
+
+ // Set oracle block root and warp time
+ uint64 oracleTimestamp = 0;
+ _setOracleBlockRoot();
+ cheats.warp(oracleTimestamp+=1);
+
+ // Act: Verify withdrawal credentials and record the balance update
+ cheats.prank(podOwner);
+ eigenPod.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ stateRootProofStruct,
+ validatorIndices,
+ validatorFieldsProofs,
+ validatorFields
+ );
+ }
+
+ function _setWithdrawalCredentialParams() internal {
+ // Reset arrays
+ delete validatorIndices;
+ delete validatorFields;
+ delete validatorFieldsProofs;
+
+ // Set state proof struct
+ stateRootProofStruct = _getStateRootProof();
+
+ // Set validator indices
+ uint40 validatorIndex = uint40(getValidatorIndex());
+ validatorIndices.push(validatorIndex);
+
+ // Set validatorFieldsArray
+ validatorFields.push(getValidatorFields());
+
+ // Set validator fields proof
+ validatorFieldsProofs.push(abi.encodePacked(getWithdrawalCredentialProof())); // Validator fields are proven here
+ }
+
+ function _setBalanceUpdateParams() internal {
+ // Reset arrays
+ delete validatorIndices;
+ delete validatorFields;
+ delete validatorFieldsProofs;
+
+ // Set state proof struct
+ stateRootProofStruct = _getStateRootProof();
+
+ // Set validator indices
+ uint40 validatorIndex = uint40(getValidatorIndex());
+ validatorIndices.push(validatorIndex);
+
+ // Set validatorFieldsArray
+ validatorFields.push(getValidatorFields());
+
+ // Set validator fields proof
+ validatorFieldsProofs.push(abi.encodePacked(getBalanceUpdateProof())); // Validator fields are proven here
+ }
+
+ function _setWithdrawalProofParams() internal {
+ // Reset arrays
+ delete validatorFields;
+ delete validatorFieldsProofs;
+ delete withdrawalFields;
+ delete withdrawalProofs;
+
+ // Set state proof struct
+ stateRootProofStruct = _getStateRootProof();
+
+ // Set validatorFields
+ validatorFields.push(getValidatorFields());
+
+ // Set validator fields proof
+ validatorFieldsProofs.push(abi.encodePacked(getValidatorProof()));
+
+ // Set withdrawal fields
+ withdrawalFields.push(getWithdrawalFields());
+
+ // Set withdrawal proofs
+ withdrawalProofs.push(_getWithdrawalProof());
+ }
+
+ // function _getBalanceUpdateProof() internal returns (BeaconChainProofs.BalanceUpdateProof memory) {
+ // bytes32 balanceRoot = getBalanceRoot();
+ // BeaconChainProofs.BalanceUpdateProof memory proofs = BeaconChainProofs.BalanceUpdateProof(
+ // abi.encodePacked(getValidatorBalanceProof()),
+ // abi.encodePacked(getWithdrawalCredentialProof()), //technically this is to verify validator pubkey in the validator fields, but the WC proof is effectively the same so we use it here again.
+ // balanceRoot
+ // );
+ // return proofs;
+ // }
+
+ /// @notice this function just generates a valid proof so that we can test other functionalities of the withdrawal flow
+ function _getWithdrawalProof() internal returns (BeaconChainProofs.WithdrawalProof memory) {
+ {
+ bytes32 blockRoot = getBlockRoot();
+ bytes32 slotRoot = getSlotRoot();
+ bytes32 timestampRoot = getTimestampRoot();
+ bytes32 executionPayloadRoot = getExecutionPayloadRoot();
+
+ return
+ BeaconChainProofs.WithdrawalProof(
+ abi.encodePacked(getWithdrawalProofCapella()),
+ abi.encodePacked(getSlotProof()),
+ abi.encodePacked(getExecutionPayloadProof()),
+ abi.encodePacked(getTimestampProofCapella()),
+ abi.encodePacked(getHistoricalSummaryProof()),
+ uint64(getBlockRootIndex()),
+ uint64(getHistoricalSummaryIndex()),
+ uint64(getWithdrawalIndex()),
+ blockRoot,
+ slotRoot,
+ timestampRoot,
+ executionPayloadRoot
+ );
+ }
+ }
+}
+
diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol
new file mode 100644
index 0000000000..bc553243cf
--- /dev/null
+++ b/src/test/unit/EigenPodManagerUnit.t.sol
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+
+import "src/contracts/pods/EigenPodManager.sol";
+import "src/contracts/pods/EigenPodPausingConstants.sol";
+
+import "src/test/events/IEigenPodManagerEvents.sol";
+import "src/test/utils/EigenLayerUnitTestSetup.sol";
+import "src/test/harnesses/EigenPodManagerWrapper.sol";
+import "src/test/mocks/EigenPodMock.sol";
+import "src/test/mocks/ETHDepositMock.sol";
+
+contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup {
+ // Contracts Under Test: EigenPodManager
+ EigenPodManager public eigenPodManagerImplementation;
+ EigenPodManager public eigenPodManager;
+
+ using stdStorage for StdStorage;
+
+ // Mocks
+ IETHPOSDeposit public ethPOSMock;
+ IEigenPod public eigenPodMockImplementation;
+ IBeacon public eigenPodBeacon; // Proxy for eigenPodMockImplementation
+
+ // Constants
+ uint256 public constant GWEI_TO_WEI = 1e9;
+ address public defaultStaker = address(this);
+ IEigenPod public defaultPod;
+ address public initialOwner = address(this);
+
+ function setUp() virtual override public {
+ EigenLayerUnitTestSetup.setUp();
+
+ // Deploy Mocks
+ ethPOSMock = new ETHPOSDepositMock();
+ eigenPodMockImplementation = new EigenPodMock();
+ eigenPodBeacon = new UpgradeableBeacon(address(eigenPodMockImplementation));
+
+ // Deploy EPM Implementation & Proxy
+ eigenPodManagerImplementation = new EigenPodManager(
+ ethPOSMock,
+ eigenPodBeacon,
+ strategyManagerMock,
+ slasherMock,
+ delegationManagerMock
+ );
+ eigenPodManager = EigenPodManager(
+ address(
+ new TransparentUpgradeableProxy(
+ address(eigenPodManagerImplementation),
+ address(eigenLayerProxyAdmin),
+ abi.encodeWithSelector(
+ EigenPodManager.initialize.selector,
+ IBeaconChainOracle(address(0)) /*beaconChainOracle*/,
+ initialOwner,
+ pauserRegistry,
+ 0 /*initialPausedStatus*/
+ )
+ )
+ )
+ );
+
+ // Set defaultPod
+ defaultPod = eigenPodManager.getPod(defaultStaker);
+
+ // Exclude the zero address, and the eigenPodManager itself from fuzzed inputs
+ addressIsExcludedFromFuzzedInputs[address(0)] = true;
+ addressIsExcludedFromFuzzedInputs[address(eigenPodManager)] = true;
+ }
+
+ /*******************************************************************************
+ Helper Functions/Modifiers
+ *******************************************************************************/
+
+ function _initializePodWithShares(address podOwner, int256 shares) internal {
+ // Deploy pod
+ IEigenPod deployedPod = _deployAndReturnEigenPodForStaker(podOwner);
+
+ // Set shares
+ cheats.prank(address(deployedPod));
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, shares);
+ }
+
+
+ modifier deployPodForStaker(address staker) {
+ _deployAndReturnEigenPodForStaker(staker);
+ _;
+ }
+
+ function _deployAndReturnEigenPodForStaker(address staker) internal returns (IEigenPod deployedPod) {
+ deployedPod = eigenPodManager.getPod(staker);
+ cheats.prank(staker);
+ eigenPodManager.createPod();
+ return deployedPod;
+ }
+
+ function _checkPodDeployed(address staker, address expectedPod, uint256 numPodsBefore) internal {
+ assertEq(address(eigenPodManager.ownerToPod(staker)), expectedPod, "Expected pod not deployed");
+ assertEq(eigenPodManager.numPods(), numPodsBefore + 1, "Num pods not incremented");
+ }
+}
+
+contract EigenPodManagerUnitTests_Initialization_Setters is EigenPodManagerUnitTests, IEigenPodManagerEvents {
+
+ /*******************************************************************************
+ Initialization Tests
+ *******************************************************************************/
+
+ function test_initialization() public {
+ // Check max pods, beacon chain, owner, and pauser
+ assertEq(address(eigenPodManager.beaconChainOracle()), address(IBeaconChainOracle(address(0))), "Initialization: beacon chain oracle incorrect");
+ assertEq(eigenPodManager.owner(), initialOwner, "Initialization: owner incorrect");
+ assertEq(address(eigenPodManager.pauserRegistry()), address(pauserRegistry), "Initialization: pauser registry incorrect");
+ assertEq(eigenPodManager.paused(), 0, "Initialization: paused value not 0");
+
+ // Check storage variables
+ assertEq(address(eigenPodManager.ethPOS()), address(ethPOSMock), "Initialization: ethPOS incorrect");
+ assertEq(address(eigenPodManager.eigenPodBeacon()), address(eigenPodBeacon), "Initialization: eigenPodBeacon incorrect");
+ assertEq(address(eigenPodManager.strategyManager()), address(strategyManagerMock), "Initialization: strategyManager incorrect");
+ assertEq(address(eigenPodManager.slasher()), address(slasherMock), "Initialization: slasher incorrect");
+ assertEq(address(eigenPodManager.delegationManager()), address(delegationManagerMock), "Initialization: delegationManager incorrect");
+ }
+
+ function test_initialize_revert_alreadyInitialized() public {
+ cheats.expectRevert("Initializable: contract is already initialized");
+ eigenPodManager.initialize(
+ IBeaconChainOracle(address(0)) /*beaconChainOracle*/,
+ initialOwner,
+ pauserRegistry,
+ 0 /*initialPausedStatus*/);
+ }
+
+ /*******************************************************************************
+ Setters
+ *******************************************************************************/
+
+ function testFuzz_updateBeaconChainOracle_revert_notOwner(address notOwner) public filterFuzzedAddressInputs(notOwner) {
+ cheats.assume(notOwner != initialOwner);
+ cheats.prank(notOwner);
+ cheats.expectRevert("Ownable: caller is not the owner");
+ eigenPodManager.updateBeaconChainOracle(IBeaconChainOracle(address(1)));
+ }
+
+ function test_updateBeaconChainOracle() public {
+ // Set new beacon chain oracle
+ IBeaconChainOracle newBeaconChainOracle = IBeaconChainOracle(address(1));
+ cheats.prank(initialOwner);
+ cheats.expectEmit(true, true, true, true);
+ emit BeaconOracleUpdated(address(newBeaconChainOracle));
+ eigenPodManager.updateBeaconChainOracle(newBeaconChainOracle);
+
+ // 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 {
+
+ function test_createPod() public {
+ // Get expected pod address and pods before
+ IEigenPod expectedPod = eigenPodManager.getPod(defaultStaker);
+ uint256 numPodsBefore = eigenPodManager.numPods();
+
+ // Create pod
+ cheats.expectEmit(true, true, true, true);
+ emit PodDeployed(address(expectedPod), defaultStaker);
+ eigenPodManager.createPod();
+
+ // Check pod deployed
+ _checkPodDeployed(defaultStaker, address(defaultPod), numPodsBefore);
+ }
+
+ function test_createPod_revert_alreadyCreated() public deployPodForStaker(defaultStaker) {
+ cheats.expectRevert("EigenPodManager.createPod: Sender already has a pod");
+ eigenPodManager.createPod();
+ }
+}
+
+contract EigenPodManagerUnitTests_StakeTests is EigenPodManagerUnitTests {
+
+ function test_stake_podAlreadyDeployed() deployPodForStaker(defaultStaker) public {
+ // Declare dummy variables
+ bytes memory pubkey = bytes("pubkey");
+ bytes memory sig = bytes("sig");
+ bytes32 depositDataRoot = bytes32("depositDataRoot");
+
+ // Stake
+ eigenPodManager.stake{value: 32 ether}(pubkey, sig, depositDataRoot);
+
+ // Expect pod has 32 ether
+ assertEq(address(defaultPod).balance, 32 ether, "ETH not staked in EigenPod");
+ }
+
+ function test_stake_newPodDeployed() public {
+ // Declare dummy variables
+ bytes memory pubkey = bytes("pubkey");
+ bytes memory sig = bytes("sig");
+ bytes32 depositDataRoot = bytes32("depositDataRoot");
+
+ // Stake
+ eigenPodManager.stake{value: 32 ether}(pubkey, sig, depositDataRoot);
+
+ // Check pod deployed
+ _checkPodDeployed(defaultStaker, address(defaultPod), 0); // staker, defaultPod, numPodsBefore
+
+ // Expect pod has 32 ether
+ assertEq(address(defaultPod).balance, 32 ether, "ETH not staked in EigenPod");
+ }
+}
+
+contract EigenPodManagerUnitTests_ShareUpdateTests is EigenPodManagerUnitTests {
+
+ /*******************************************************************************
+ Add Shares Tests
+ *******************************************************************************/
+
+ function testFuzz_addShares_revert_notDelegationManager(address notDelegationManager) public filterFuzzedAddressInputs(notDelegationManager){
+ cheats.assume(notDelegationManager != address(delegationManagerMock));
+ cheats.prank(notDelegationManager);
+ cheats.expectRevert("EigenPodManager.onlyDelegationManager: not the DelegationManager");
+ eigenPodManager.addShares(defaultStaker, 0);
+ }
+
+ function test_addShares_revert_podOwnerZeroAddress() public {
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.addShares: podOwner cannot be zero address");
+ eigenPodManager.addShares(address(0), 0);
+ }
+
+ function testFuzz_addShares_revert_sharesNegative(int256 shares) public {
+ cheats.assume(shares < 0);
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.addShares: shares cannot be negative");
+ eigenPodManager.addShares(defaultStaker, uint256(shares));
+ }
+
+ function testFuzz_addShares_revert_sharesNotWholeGwei(uint256 shares) public {
+ cheats.assume(int256(shares) >= 0);
+ cheats.assume(shares % GWEI_TO_WEI != 0);
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.addShares: shares must be a whole Gwei amount");
+ eigenPodManager.addShares(defaultStaker, shares);
+ }
+
+ function testFuzz_addShares(uint256 shares) public {
+ // Fuzz inputs
+ cheats.assume(defaultStaker != address(0));
+ shares = shares - (shares % GWEI_TO_WEI); // Round down to nearest Gwei
+ cheats.assume(int256(shares) >= 0);
+
+ // Add shares
+ cheats.prank(address(delegationManagerMock));
+ eigenPodManager.addShares(defaultStaker, shares);
+
+ // Check storage update
+ assertEq(eigenPodManager.podOwnerShares(defaultStaker), int256(shares), "Incorrect number of shares added");
+ }
+
+ /*******************************************************************************
+ Remove Shares Tests
+ ******************************************************************************/
+
+ function testFuzz_removeShares_revert_notDelegationManager(address notDelegationManager) public filterFuzzedAddressInputs(notDelegationManager) {
+ cheats.assume(notDelegationManager != address(delegationManagerMock));
+ cheats.prank(notDelegationManager);
+ cheats.expectRevert("EigenPodManager.onlyDelegationManager: not the DelegationManager");
+ eigenPodManager.removeShares(defaultStaker, 0);
+ }
+
+ function testFuzz_removeShares_revert_sharesNegative(int256 shares) public {
+ cheats.assume(shares < 0);
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.removeShares: shares cannot be negative");
+ eigenPodManager.removeShares(defaultStaker, uint256(shares));
+ }
+
+ function testFuzz_removeShares_revert_sharesNotWholeGwei(uint256 shares) public {
+ cheats.assume(int256(shares) >= 0);
+ cheats.assume(shares % GWEI_TO_WEI != 0);
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.removeShares: shares must be a whole Gwei amount");
+ eigenPodManager.removeShares(defaultStaker, shares);
+ }
+
+ function testFuzz_removeShares_revert_tooManySharesRemoved(uint224 sharesToAdd, uint224 sharesToRemove) public {
+ // Constrain inputs
+ cheats.assume(sharesToRemove > sharesToAdd);
+ uint256 sharesAdded = sharesToAdd * GWEI_TO_WEI;
+ uint256 sharesRemoved = sharesToRemove * GWEI_TO_WEI;
+
+ // Initialize pod with shares
+ _initializePodWithShares(defaultStaker, int256(sharesAdded));
+
+ // Remove shares
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.removeShares: cannot result in pod owner having negative shares");
+ eigenPodManager.removeShares(defaultStaker, sharesRemoved);
+ }
+
+ function testFuzz_removeShares(uint224 sharesToAdd, uint224 sharesToRemove) public {
+ // Constain inputs
+ cheats.assume(sharesToRemove <= sharesToAdd);
+ uint256 sharesAdded = sharesToAdd * GWEI_TO_WEI;
+ uint256 sharesRemoved = sharesToRemove * GWEI_TO_WEI;
+
+ // Initialize pod with shares
+ _initializePodWithShares(defaultStaker, int256(sharesAdded));
+
+ // Remove shares
+ cheats.prank(address(delegationManagerMock));
+ eigenPodManager.removeShares(defaultStaker, sharesRemoved);
+
+ // Check storage
+ assertEq(eigenPodManager.podOwnerShares(defaultStaker), int256(sharesAdded - sharesRemoved), "Incorrect number of shares removed");
+ }
+
+ function testFuzz_removeShares_zeroShares(address podOwner, uint256 shares) public filterFuzzedAddressInputs(podOwner) {
+ // Constrain inputs
+ cheats.assume(podOwner != address(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));
+
+ // Remove shares
+ cheats.prank(address(delegationManagerMock));
+ eigenPodManager.removeShares(podOwner, shares);
+
+ // Check storage update
+ assertEq(eigenPodManager.podOwnerShares(podOwner), 0, "Shares not reset to zero");
+ }
+
+ /*******************************************************************************
+ WithdrawSharesAsTokens Tests
+ ******************************************************************************/
+
+ function test_withdrawSharesAsTokens_revert_podOwnerZeroAddress() public {
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: podOwner cannot be zero address");
+ eigenPodManager.withdrawSharesAsTokens(address(0), address(0), 0);
+ }
+
+ function test_withdrawSharesAsTokens_revert_destinationZeroAddress() public {
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: destination cannot be zero address");
+ eigenPodManager.withdrawSharesAsTokens(defaultStaker, address(0), 0);
+ }
+
+ function testFuzz_withdrawSharesAsTokens_revert_sharesNegative(int256 shares) public {
+ cheats.assume(shares < 0);
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: shares cannot be negative");
+ eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, uint256(shares));
+ }
+
+ function testFuzz_withdrawSharesAsTokens_revert_sharesNotWholeGwei(uint256 shares) public {
+ cheats.assume(int256(shares) >= 0);
+ cheats.assume(shares % GWEI_TO_WEI != 0);
+
+ cheats.prank(address(delegationManagerMock));
+ cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: shares must be a whole Gwei amount");
+ eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, shares);
+ }
+
+ /**
+ * @notice The `withdrawSharesAsTokens` is called in the `completeQueuedWithdrawal` function from the
+ * delegationManager. When a withdrawal is queued in the delegationManager, `removeShares is called`
+ */
+ function test_withdrawSharesAsTokens_reduceEntireDeficit() public {
+ // Shares to initialize & withdraw
+ int256 sharesBeginning = -100e18;
+ uint256 sharesToWithdraw = 101e18;
+
+ // Deploy Pod And initialize with negative shares
+ _initializePodWithShares(defaultStaker, sharesBeginning);
+
+ // Withdraw shares
+ cheats.prank(address(delegationManagerMock));
+ eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, sharesToWithdraw);
+
+ // Check storage update
+ assertEq(eigenPodManager.podOwnerShares(defaultStaker), int256(0), "Shares not reduced to 0");
+ }
+
+ function test_withdrawSharesAsTokens_partialDefecitReduction() public {
+ // Shares to initialize & withdraw
+ int256 sharesBeginning = -100e18;
+ uint256 sharesToWithdraw = 50e18;
+
+ // Deploy Pod And initialize with negative shares
+ _initializePodWithShares(defaultStaker, sharesBeginning);
+
+ // Withdraw shares
+ cheats.prank(address(delegationManagerMock));
+ eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, sharesToWithdraw);
+
+ // Check storage update
+ int256 expectedShares = sharesBeginning + int256(sharesToWithdraw);
+ assertEq(eigenPodManager.podOwnerShares(defaultStaker), expectedShares, "Shares not reduced to expected amount");
+ }
+
+ function test_withdrawSharesAsTokens_withdrawPositive() public {
+ // Shares to initialize & withdraw
+ int256 sharesBeginning = 100e18;
+ uint256 sharesToWithdraw = 50e18;
+
+ // Deploy Pod And initialize with negative shares
+ _initializePodWithShares(defaultStaker, sharesBeginning);
+
+ // Withdraw shares
+ cheats.prank(address(delegationManagerMock));
+ eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, sharesToWithdraw);
+
+ // Check storage remains the same
+ assertEq(eigenPodManager.podOwnerShares(defaultStaker), sharesBeginning, "Shares should not be adjusted");
+ }
+}
+
+contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodManagerUnitTests, IEigenPodManagerEvents {
+
+ function testFuzz_recordBalanceUpdate_revert_notPod(address invalidCaller) public filterFuzzedAddressInputs(invalidCaller) deployPodForStaker(defaultStaker) {
+ cheats.assume(invalidCaller != address(defaultPod));
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("EigenPodManager.onlyEigenPod: not a pod");
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, 0);
+ }
+
+ function test_recordBalanceUpdate_revert_zeroAddress() public {
+ IEigenPod zeroAddressPod = _deployAndReturnEigenPodForStaker(address(0));
+ cheats.prank(address(zeroAddressPod));
+ cheats.expectRevert("EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address");
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(address(0), 0);
+ }
+
+ function testFuzz_recordBalanceUpdate_revert_nonWholeGweiAmount(int256 sharesDelta) public deployPodForStaker(defaultStaker) {
+ cheats.assume(sharesDelta % int256(GWEI_TO_WEI) != 0);
+ cheats.prank(address(defaultPod));
+ cheats.expectRevert("EigenPodManager.recordBeaconChainETHBalanceUpdate: sharesDelta must be a whole Gwei amount");
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, sharesDelta);
+ }
+
+ function testFuzz_recordBalanceUpdateX(int224 sharesBefore, int224 sharesDelta) public {
+ // Constrain inputs
+ int256 scaledSharesBefore = sharesBefore * int256(GWEI_TO_WEI);
+ int256 scaledSharesDelta = sharesDelta * int256(GWEI_TO_WEI);
+
+ // Initialize shares
+ _initializePodWithShares(defaultStaker, scaledSharesBefore);
+
+ // Update balance
+ cheats.expectEmit(true, true, true, true);
+ emit PodSharesUpdated(defaultStaker, scaledSharesDelta);
+ cheats.prank(address(defaultPod));
+ eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, scaledSharesDelta);
+
+ // Check storage
+ assertEq(eigenPodManager.podOwnerShares(defaultStaker), scaledSharesBefore + scaledSharesDelta, "Shares not updated correctly");
+ }
+}
+
+contract EigenPodManagerUnitTests_ShareAdjustmentCalculationTests is EigenPodManagerUnitTests {
+ // Wrapper contract that exposes the internal `_calculateChangeInDelegatableShares` function
+ EigenPodManagerWrapper public eigenPodManagerWrapper;
+
+ function setUp() virtual override public {
+ super.setUp();
+
+ // Upgrade eigenPodManager to wrapper
+ eigenPodManagerWrapper = new EigenPodManagerWrapper(
+ ethPOSMock,
+ eigenPodBeacon,
+ strategyManagerMock,
+ slasherMock,
+ delegationManagerMock
+ );
+ eigenLayerProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(eigenPodManager))), address(eigenPodManagerWrapper));
+ }
+
+ function testFuzz_shareAdjustment_negativeToNegative(int256 sharesBefore, int256 sharesAfter) public {
+ cheats.assume(sharesBefore <= 0);
+ cheats.assume(sharesAfter <= 0);
+
+ int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter);
+ assertEq(sharesDelta, 0, "Shares delta must be 0");
+ }
+
+ function testFuzz_shareAdjustment_negativeToPositive(int256 sharesBefore, int256 sharesAfter) public {
+ cheats.assume(sharesBefore <= 0);
+ cheats.assume(sharesAfter > 0);
+
+ int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter);
+ assertEq(sharesDelta, sharesAfter, "Shares delta must be equal to sharesAfter");
+ }
+
+ function testFuzz_shareAdjustment_positiveToNegative(int256 sharesBefore, int256 sharesAfter) public {
+ cheats.assume(sharesBefore > 0);
+ cheats.assume(sharesAfter <= 0);
+
+ int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter);
+ assertEq(sharesDelta, -sharesBefore, "Shares delta must be equal to the negative of sharesBefore");
+ }
+
+ function testFuzz_shareAdjustment_positiveToPositive(int256 sharesBefore, int256 sharesAfter) public {
+ cheats.assume(sharesBefore > 0);
+ cheats.assume(sharesAfter > 0);
+
+ int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter);
+ assertEq(sharesDelta, sharesAfter - sharesBefore, "Shares delta must be equal to the difference between sharesAfter and sharesBefore");
+ }
+}
diff --git a/src/test/unit/EigenPodUnit.t.sol b/src/test/unit/EigenPodUnit.t.sol
new file mode 100644
index 0000000000..6af23d1bd0
--- /dev/null
+++ b/src/test/unit/EigenPodUnit.t.sol
@@ -0,0 +1,1094 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
+import "@openzeppelin/contracts/utils/Create2.sol";
+
+import "src/contracts/pods/EigenPod.sol";
+
+import "src/test/mocks/ETHDepositMock.sol";
+import "src/test/mocks/DelayedWithdrawalRouterMock.sol";
+import "src/test/mocks/ERC20Mock.sol";
+import "src/test/harnesses/EigenPodHarness.sol";
+import "src/test/utils/ProofParsing.sol";
+import "src/test/utils/EigenLayerUnitTestSetup.sol";
+import "src/test/events/IEigenPodEvents.sol";
+
+contract EigenPodUnitTests is EigenLayerUnitTestSetup {
+ // Contract Under Test: EigenPod
+ 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;
+ uint64 public constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9;
+ // uint64 public constant RESTAKED_BALANCE_OFFSET_GWEI = 75e7;
+ uint64 public constant GOERLI_GENESIS_TIME = 1616508000;
+ // uint64 public constant SECONDS_PER_SLOT = 12;
+
+ bytes internal constant beaconProxyBytecode =
+ hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";
+ address public podOwner = address(this);
+
+ function setUp() public override virtual {
+ // Setup
+ EigenLayerUnitTestSetup.setUp();
+
+ // Deploy mocks
+ ethPOSDepositMock = new ETHPOSDepositMock();
+ delayedWithdrawalRouterMock = new DelayedWithdrawalRouterMock();
+
+ // Deploy EigenPod
+ podImplementation = new EigenPod(
+ ethPOSDepositMock,
+ delayedWithdrawalRouterMock,
+ eigenPodManagerMock,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
+
+ // Deploy Beacon
+ eigenPodBeacon = new UpgradeableBeacon(address(podImplementation));
+
+ // Deploy Proxy same way as EigenPodManager does
+ eigenPod = EigenPod(payable(
+ Create2.deploy(
+ 0,
+ bytes32(uint256(uint160(address(this)))),
+ // set the beacon address to the eigenPodBeacon
+ abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
+ )));
+
+ // Etch the eigenPod code to the address for which proofs are generated
+ bytes memory code = address(eigenPod).code;
+ cheats.etch(podAddress, code);
+ eigenPod = EigenPod(payable(podAddress));
+
+ // Store the eigenPodBeacon address in the eigenPod beacon proxy
+ bytes32 beaconSlot = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
+ cheats.store(address(eigenPod), beaconSlot, bytes32(uint256(uint160(address(eigenPodBeacon)))));
+
+ // Initialize pod
+ eigenPod.initialize(address(this));
+ }
+
+
+ /// @notice Post-M2, all new deployed eigen pods will have restaked set to true
+ modifier hasNotRestaked() {
+ // Write hasRestaked as false. hasRestaked in slot 52
+ bytes32 slot = bytes32(uint256(52));
+ bytes32 value = bytes32(0); // 0 == false
+ cheats.store(address(eigenPod), slot, value);
+ _;
+ }
+}
+
+contract EigenPodUnitTests_Initialization is EigenPodUnitTests, IEigenPodEvents {
+
+ function test_initialization() public {
+ // Check podOwner and restaked
+ assertEq(eigenPod.podOwner(), podOwner, "Pod owner incorrectly set");
+ assertTrue(eigenPod.hasRestaked(), "hasRestaked incorrectly set");
+ // Check immutable storage
+ assertEq(address(eigenPod.ethPOS()), address(ethPOSDepositMock), "EthPOS incorrectly set");
+ assertEq(address(eigenPod.delayedWithdrawalRouter()), address(delayedWithdrawalRouterMock), "DelayedWithdrawalRouter incorrectly set");
+ assertEq(address(eigenPod.eigenPodManager()), address(eigenPodManagerMock), "EigenPodManager incorrectly set");
+ assertEq(eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(), MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Max restaked balance incorrectly set");
+ assertEq(eigenPod.GENESIS_TIME(), GOERLI_GENESIS_TIME, "Goerli genesis time incorrectly set");
+ }
+
+ function test_initialize_revert_alreadyInitialized() public {
+ cheats.expectRevert("Initializable: contract is already initialized");
+ eigenPod.initialize(podOwner);
+ }
+
+ function test_initialize_eventEmitted() public {
+ address newPodOwner = address(0x123);
+
+ // Deploy new pod
+ EigenPod newEigenPod = EigenPod(payable(
+ Create2.deploy(
+ 0,
+ bytes32(uint256(uint160(newPodOwner))),
+ // set the beacon address to the eigenPodBeacon
+ abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
+ )));
+
+ // Expect emit and Initialize new pod
+ vm.expectEmit(true, true, true, true);
+ emit RestakingActivated(newPodOwner);
+ newEigenPod.initialize(newPodOwner);
+ }
+}
+
+contract EigenPodUnitTests_Stake is EigenPodUnitTests, IEigenPodEvents {
+
+ // Beacon chain staking constnats
+ bytes public constant pubkey =
+ hex"88347ed1c492eedc97fc8c506a35d44d81f27a0c7a1c661b35913cfd15256c0cccbd34a83341f505c7de2983292f2cab";
+ bytes public signature;
+ bytes32 public depositDataRoot;
+
+ function testFuzz_stake_revert_notEigenPodManager(address invalidCaller) public {
+ cheats.assume(invalidCaller != address(eigenPodManagerMock));
+ cheats.deal(invalidCaller, 32 ether);
+
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("EigenPod.onlyEigenPodManager: not eigenPodManager");
+ 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);
+
+ cheats.prank(address(eigenPodManagerMock));
+ cheats.expectRevert("EigenPod.stake: must initially stake for any validator with 32 ether");
+ eigenPod.stake{value: value}(pubkey, signature, depositDataRoot);
+ }
+
+ function test_stake() public {
+ cheats.deal(address(eigenPodManagerMock), 32 ether);
+
+ // Expect emit
+ vm.expectEmit(true, true, true, true);
+ emit EigenPodStaked(pubkey);
+
+ // Stake
+ cheats.prank(address(eigenPodManagerMock));
+ eigenPod.stake{value: 32 ether}(pubkey, signature, depositDataRoot);
+
+ // Check eth transferred
+ assertEq(address(ethPOSDepositMock).balance, 32 ether, "Incorrect amount transferred");
+ }
+}
+
+contract EigenPodUnitTests_PodOwnerFunctions is EigenPodUnitTests, IEigenPodEvents {
+
+ /*******************************************************************************
+ Withdraw Non Beacon Chain ETH Tests
+ *******************************************************************************/
+
+ function testFuzz_withdrawNonBeaconChainETH_revert_notPodOwner(address invalidCaller) public {
+ cheats.assume(invalidCaller != podOwner);
+
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("EigenPod.onlyEigenPodOwner: not podOwner");
+ eigenPod.withdrawNonBeaconChainETHBalanceWei(invalidCaller, 1 ether);
+ }
+
+ function test_withdrawNonBeaconChainETH_revert_tooMuchWithdrawn() public {
+ // Send EigenPod 0.9 ether
+ _seedPodWithETH(0.9 ether);
+
+ // Withdraw 1 ether
+ cheats.expectRevert("EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei");
+ eigenPod.withdrawNonBeaconChainETHBalanceWei(podOwner, 1 ether);
+ }
+
+ function testFuzz_withdrawNonBeaconChainETH(uint256 ethAmount) public {
+ _seedPodWithETH(ethAmount);
+ assertEq(eigenPod.nonBeaconChainETHBalanceWei(), ethAmount, "Incorrect amount incremented in receive function");
+
+ cheats.expectEmit(true, true, true, true);
+ emit NonBeaconChainETHWithdrawn(podOwner, ethAmount);
+ eigenPod.withdrawNonBeaconChainETHBalanceWei(podOwner, ethAmount);
+
+ // Checks
+ assertEq(address(eigenPod).balance, 0, "Incorrect amount withdrawn from eigenPod");
+ assertEq(address(delayedWithdrawalRouterMock).balance, ethAmount, "Incorrect amount set to delayed withdrawal router");
+ }
+
+ /*******************************************************************************
+ Recover Tokens Tests
+ *******************************************************************************/
+
+ function testFuzz_recoverTokens_revert_notPodOwner(address invalidCaller) public {
+ cheats.assume(invalidCaller != podOwner);
+
+ IERC20[] memory tokens = new IERC20[](1);
+ tokens[0] = IERC20(address(0x123));
+ uint256[] memory amounts = new uint256[](1);
+ amounts[0] = 1;
+
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("EigenPod.onlyEigenPodOwner: not podOwner");
+ eigenPod.recoverTokens(tokens, amounts, podOwner);
+ }
+
+ function test_recoverTokens_revert_invalidLengths() public {
+ IERC20[] memory tokens = new IERC20[](1);
+ tokens[0] = IERC20(address(0x123));
+ uint256[] memory amounts = new uint256[](2);
+ amounts[0] = 1;
+ amounts[1] = 1;
+
+ cheats.expectRevert("EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length");
+ eigenPod.recoverTokens(tokens, amounts, podOwner);
+ }
+
+ function test_recoverTokens() public {
+ // Deploy dummy token
+ IERC20 dummyToken = new ERC20Mock();
+ dummyToken.transfer(address(eigenPod), 1e18);
+
+ // Recover tokens
+ address recipient = address(0x123);
+ IERC20[] memory tokens = new IERC20[](1);
+ tokens[0] = dummyToken;
+ uint256[] memory amounts = new uint256[](1);
+ amounts[0] = 1e18;
+
+ eigenPod.recoverTokens(tokens, amounts, recipient);
+
+ // Checks
+ assertEq(dummyToken.balanceOf(recipient), 1e18, "Incorrect amount recovered");
+ }
+
+ /*******************************************************************************
+ Activate Restaking Tests
+ *******************************************************************************/
+
+ function testFuzz_activateRestaking_revert_notPodOwner(address invalidCaller) public {
+ cheats.assume(invalidCaller != podOwner);
+
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("EigenPod.onlyEigenPodOwner: not podOwner");
+ eigenPod.activateRestaking();
+ }
+
+ function test_activateRestaking_revert_alreadyRestaked() public {
+ cheats.expectRevert("EigenPod.hasNeverRestaked: restaking is enabled");
+ eigenPod.activateRestaking();
+ }
+
+ function testFuzz_activateRestaking(uint256 ethAmount) public hasNotRestaked {
+ // Seed some ETH
+ _seedPodWithETH(ethAmount);
+
+ // Activate restaking
+ vm.expectEmit(true, true, true, true);
+ emit RestakingActivated(podOwner);
+ eigenPod.activateRestaking();
+
+ // Checks
+ assertTrue(eigenPod.hasRestaked(), "hasRestaked incorrectly set");
+ _assertWithdrawalProcessed(ethAmount);
+ }
+
+ /**
+ * This is a regression test for a bug (EIG-14) found by Hexens. Lets say podOwner sends 32 ETH to the EigenPod,
+ * the nonBeaconChainETHBalanceWei increases by 32 ETH. podOwner calls withdrawBeforeRestaking, which
+ * will simply send the entire ETH balance (32 ETH) to the owner. The owner activates restaking,
+ * creates a validator and verifies the withdrawal credentials, receiving 32 ETH in shares.
+ * They can exit the validator, the pod gets the 32ETH and they can call withdrawNonBeaconChainETHBalanceWei
+ * And simply withdraw the 32ETH because nonBeaconChainETHBalanceWei is 32ETH. This was an issue because
+ * nonBeaconChainETHBalanceWei was never zeroed out in _processWithdrawalBeforeRestaking
+ */
+ function test_regression_validatorBalance_cannotBeRemoved_viaNonBeaconChainETHBalanceWei() external hasNotRestaked {
+ // Assert that the pod has not restaked
+ assertFalse(eigenPod.hasRestaked(), "hasRestaked should be false");
+
+ // Simulate podOwner sending 32 ETH to eigenPod
+ _seedPodWithETH(32 ether);
+ assertEq(eigenPod.nonBeaconChainETHBalanceWei(), 32 ether, "nonBeaconChainETHBalanceWei should be 32 ETH");
+
+ // Pod owner calls withdrawBeforeRestaking, sending 32 ETH to owner
+ eigenPod.withdrawBeforeRestaking();
+ assertEq(address(eigenPod).balance, 0, "eigenPod balance should be 0");
+ assertEq(address(delayedWithdrawalRouterMock).balance, 32 ether, "withdrawal router balance should be 32 ETH");
+
+ // Upgrade from m1 to m2
+
+ // Activate restaking on the pod
+ eigenPod.activateRestaking();
+
+ // Simulate a withdrawal by increasing eth balance without code execution
+ cheats.deal(address(eigenPod), 32 ether);
+
+ // Try calling withdrawNonBeaconChainETHBalanceWei, should fail since `nonBeaconChainETHBalanceWei`
+ // was set to 0 when calling `_processWithdrawalBeforeRestaking` from `activateRestaking`
+ cheats.expectRevert("EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei");
+ eigenPod.withdrawNonBeaconChainETHBalanceWei(podOwner, 32 ether);
+ }
+
+ /*******************************************************************************
+ Withdraw Before Restaking Tests
+ *******************************************************************************/
+
+ function testFuzz_withdrawBeforeRestaking_revert_notPodOwner(address invalidCaller) public filterFuzzedAddressInputs(invalidCaller) {
+ cheats.assume(invalidCaller != podOwner);
+
+ cheats.prank(invalidCaller);
+ cheats.expectRevert("EigenPod.onlyEigenPodOwner: not podOwner");
+ eigenPod.withdrawBeforeRestaking();
+ }
+
+ function test_withdrawBeforeRestaking_revert_alreadyRestaked() public {
+ cheats.expectRevert("EigenPod.hasNeverRestaked: restaking is enabled");
+ eigenPod.withdrawBeforeRestaking();
+ }
+
+ function testFuzz_withdrawBeforeRestaking(uint256 ethAmount) public hasNotRestaked {
+ // Seed some ETH
+ _seedPodWithETH(ethAmount);
+
+ // Withdraw
+ eigenPod.withdrawBeforeRestaking();
+
+ // Checks
+ _assertWithdrawalProcessed(ethAmount);
+ }
+
+ // Helpers
+ function _assertWithdrawalProcessed(uint256 amount) internal {
+ assertEq(eigenPod.mostRecentWithdrawalTimestamp(), uint32(block.timestamp), "Incorrect mostRecentWithdrawalTimestamp");
+ assertEq(eigenPod.nonBeaconChainETHBalanceWei(), 0, "Incorrect nonBeaconChainETHBalanceWei");
+ assertEq(address(delayedWithdrawalRouterMock).balance, amount, "Incorrect amount sent to delayed withdrawal router");
+ }
+
+ function _seedPodWithETH(uint256 ethAmount) internal {
+ cheats.deal(address(this), ethAmount);
+ bool result;
+ bytes memory data;
+ (result, data) = address(eigenPod).call{value: ethAmount}("");
+ }
+}
+
+contract EigenPodHarnessSetup is EigenPodUnitTests {
+ // Harness that exposes internal functions for test
+ EPInternalFunctions public eigenPodHarnessImplementation;
+ EPInternalFunctions public eigenPodHarness;
+
+ function setUp() public virtual override {
+ EigenPodUnitTests.setUp();
+
+ // Deploy EP Harness
+ eigenPodHarnessImplementation = new EPInternalFunctions(
+ ethPOSDepositMock,
+ delayedWithdrawalRouterMock,
+ eigenPodManagerMock,
+ MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ GOERLI_GENESIS_TIME
+ );
+
+ // Upgrade eigenPod to harness
+ UpgradeableBeacon(address(eigenPodBeacon)).upgradeTo(address(eigenPodHarnessImplementation));
+ eigenPodHarness = EPInternalFunctions(payable(eigenPod));
+ }
+}
+
+contract EigenPodUnitTests_VerifyWithdrawalCredentialsTests is EigenPodHarnessSetup, ProofParsing, IEigenPodEvents {
+ using BytesLib for bytes;
+ using BeaconChainProofs for *;
+
+ // Params to _verifyWithdrawalCredentials, can be set in test or helper function
+ uint64 oracleTimestamp;
+ bytes32 beaconStateRoot;
+ uint40 validatorIndex;
+ bytes validatorFieldsProof;
+ bytes32[] validatorFields;
+
+ function test_revert_validatorActive() public {
+ // Set JSON & params
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _setWithdrawalCredentialParams();
+
+ // Set validator status to active
+ eigenPodHarness.setValidatorStatus(validatorFields[0], IEigenPod.VALIDATOR_STATUS.ACTIVE);
+
+ // Expect revert
+ cheats.expectRevert(
+ "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials"
+ );
+ eigenPodHarness.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ beaconStateRoot,
+ validatorIndex,
+ validatorFieldsProof,
+ validatorFields
+ );
+ }
+
+ 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();
+
+ // Change the withdrawal credentials in validatorFields, which is at index 1
+ validatorFields[1] = abi.encodePacked(bytes1(uint8(1)), bytes11(0), wrongWithdrawalAddress).toBytes32(0);
+
+ // Expect revert
+ cheats.expectRevert(
+ "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"
+ );
+ eigenPodHarness.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ beaconStateRoot,
+ validatorIndex,
+ validatorFieldsProof,
+ validatorFields
+ );
+ }
+
+ function test_effectiveBalanceGreaterThan32ETH() public {
+ // Set JSON and params
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ _setWithdrawalCredentialParams();
+
+ // Check that restaked balance greater than 32 ETH
+ uint64 effectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
+ assertGt(effectiveBalanceGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Proof file has an effective balance less than 32 ETH");
+
+ uint activeValidatorCountBefore = eigenPodHarness.getActiveValidatorCount();
+
+ // Verify withdrawal credentials
+ vm.expectEmit(true, true, true, true);
+ emit ValidatorRestaked(validatorIndex);
+ vm.expectEmit(true, true, true, true);
+ emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR);
+ uint256 restakedBalanceWei = eigenPodHarness.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ beaconStateRoot,
+ validatorIndex,
+ validatorFieldsProof,
+ validatorFields
+ );
+
+ // Checks
+ uint activeValidatorCountAfter = eigenPodHarness.getActiveValidatorCount();
+ assertEq(activeValidatorCountAfter, activeValidatorCountBefore + 1, "active validator count should increase when proving withdrawal credentials");
+ assertEq(restakedBalanceWei, uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * uint256(1e9), "Returned restaked balance gwei should be max");
+ _assertWithdrawalCredentialsSet(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR);
+ }
+
+ function test_effectiveBalanceLessThan32ETH() public {
+ // Set JSON and params
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913_30ETHBalance.json");
+ _setWithdrawalCredentialParams();
+
+ // Check that restaked balance less than 32 ETH
+ uint64 effectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
+ assertLt(effectiveBalanceGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Proof file has an effective balance greater than 32 ETH");
+
+ uint activeValidatorCountBefore = eigenPodHarness.getActiveValidatorCount();
+
+ // Verify withdrawal credentials
+ vm.expectEmit(true, true, true, true);
+ emit ValidatorRestaked(validatorIndex);
+ vm.expectEmit(true, true, true, true);
+ emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, effectiveBalanceGwei);
+ uint256 restakedBalanceWei = eigenPodHarness.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ beaconStateRoot,
+ validatorIndex,
+ validatorFieldsProof,
+ validatorFields
+ );
+
+ // Checks
+ uint activeValidatorCountAfter = eigenPodHarness.getActiveValidatorCount();
+ assertEq(activeValidatorCountAfter, activeValidatorCountBefore + 1, "active validator count should increase when proving withdrawal credentials");
+ assertEq(restakedBalanceWei, uint256(effectiveBalanceGwei) * uint256(1e9), "Returned restaked balance gwei incorrect");
+ _assertWithdrawalCredentialsSet(effectiveBalanceGwei);
+ }
+
+ function _assertWithdrawalCredentialsSet(uint256 restakedBalanceGwei) internal {
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]);
+ assertEq(uint8(validatorInfo.status), uint8(IEigenPod.VALIDATOR_STATUS.ACTIVE), "Validator status should be active");
+ assertEq(validatorInfo.validatorIndex, validatorIndex, "Validator index incorrectly set");
+ assertEq(validatorInfo.mostRecentBalanceUpdateTimestamp, oracleTimestamp, "Most recent balance update timestamp incorrectly set");
+ assertEq(validatorInfo.restakedBalanceGwei, restakedBalanceGwei, "Restaked balance gwei not set correctly");
+ }
+
+
+ function _setWithdrawalCredentialParams() public {
+ // Set beacon state root, validatorIndex
+ beaconStateRoot = getBeaconStateRoot();
+ validatorIndex = uint40(getValidatorIndex());
+ validatorFieldsProof = abi.encodePacked(getWithdrawalCredentialProof()); // Validator fields are proven here
+ validatorFields = getValidatorFields();
+
+ // Get an oracle timestamp
+ cheats.warp(GOERLI_GENESIS_TIME + 1 days);
+ oracleTimestamp = uint64(block.timestamp);
+ }
+}
+
+/// @notice In practice, this function should be called after a validator has verified their withdrawal credentials
+contract EigenPodUnitTests_VerifyBalanceUpdateTests is EigenPodHarnessSetup, ProofParsing, IEigenPodEvents {
+ using BeaconChainProofs for *;
+
+ // Params to verifyBalanceUpdate, can be set in test or helper function
+ uint64 oracleTimestamp;
+ uint40 validatorIndex;
+ bytes32 beaconStateRoot;
+ bytes validatorFieldsProof;
+ bytes32[] validatorFields;
+
+ function testFuzz_revert_oracleTimestampStale(uint64 oracleFuzzTimestamp, uint64 mostRecentBalanceUpdateTimestamp) public {
+ // Constain inputs and set proof file
+ cheats.assume(oracleFuzzTimestamp < mostRecentBalanceUpdateTimestamp);
+ setJSON("src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+
+ // Get validator fields and balance update root
+ validatorFields = getValidatorFields();
+ validatorFieldsProof = abi.encodePacked(getBalanceUpdateProof());
+
+ // Balance update reversion
+ cheats.expectRevert(
+ "EigenPod.verifyBalanceUpdate: Validators balance has already been updated for this timestamp"
+ );
+ eigenPodHarness.verifyBalanceUpdate(
+ oracleFuzzTimestamp,
+ 0,
+ bytes32(0),
+ validatorFieldsProof,
+ validatorFields,
+ mostRecentBalanceUpdateTimestamp
+ );
+ }
+
+ function test_revert_validatorInactive() public {
+ // Set proof file
+ setJSON("src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+
+ // Set proof params
+ _setBalanceUpdateParams();
+
+ // Set validator status to inactive
+ eigenPodHarness.setValidatorStatus(validatorFields[0], IEigenPod.VALIDATOR_STATUS.INACTIVE);
+
+ // Balance update reversion
+ cheats.expectRevert(
+ "EigenPod.verifyBalanceUpdate: Validator not active"
+ );
+ eigenPodHarness.verifyBalanceUpdate(
+ oracleTimestamp,
+ validatorIndex,
+ beaconStateRoot,
+ validatorFieldsProof,
+ validatorFields,
+ 0 // Most recent balance update timestamp set to 0
+ );
+ }
+
+ /**
+ * Regression test for a bug that allowed balance updates to be made for withdrawn validators. Thus
+ * the validator's balance could be maliciously proven to be 0 before the validator themselves are
+ * able to prove their withdrawal.
+ */
+ function test_revert_balanceUpdateAfterWithdrawableEpoch() external {
+ // Set Json proof
+ setJSON("src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+
+ // Set proof params
+ _setBalanceUpdateParams();
+
+ // Set effective balance and withdrawable epoch
+ validatorFields[2] = bytes32(uint256(0)); // per consensus spec, slot 2 is effective balance
+ validatorFields[7] = bytes32(uint256(0)); // per consensus spec, slot 7 is withdrawable epoch == 0
+
+ console.log("withdrawable epoch: ", validatorFields.getWithdrawableEpoch());
+ // Expect revert on balance update
+ cheats.expectRevert(bytes("EigenPod.verifyBalanceUpdate: validator is withdrawable but has not withdrawn"));
+ eigenPodHarness.verifyBalanceUpdate(oracleTimestamp, validatorIndex, beaconStateRoot, validatorFieldsProof, validatorFields, 0);
+ }
+
+ /// @notice Rest of tests assume beacon chain proofs are correct; Now we update the validator's balance
+
+ ///@notice Balance of validator is >= 32e9
+ function test_positiveSharesDelta() public {
+ // Set JSON
+ setJSON("src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+
+ // Set proof params
+ _setBalanceUpdateParams();
+
+ // Verify balance update
+ vm.expectEmit(true, true, true, true);
+ emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR);
+ int256 sharesDeltaGwei = eigenPodHarness.verifyBalanceUpdate(
+ oracleTimestamp,
+ validatorIndex,
+ beaconStateRoot,
+ validatorFieldsProof,
+ validatorFields,
+ 0 // Most recent balance update timestamp set to 0
+ );
+
+ // Checks
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]);
+ assertEq(validatorInfo.restakedBalanceGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Restaked balance gwei should be max");
+ assertGt(sharesDeltaGwei, 0, "Shares delta should be positive");
+ assertEq(sharesDeltaGwei, int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR)), "Shares delta should be equal to restaked balance");
+ }
+
+ function test_negativeSharesDelta() public {
+ // Set JSON
+ setJSON("src/test/test-data/balanceUpdateProof_balance28ETH_302913.json");
+
+ // Set proof params
+ _setBalanceUpdateParams();
+ uint64 newValidatorBalance = validatorFields.getEffectiveBalanceGwei();
+
+ // Set balance of validator to max ETH
+ eigenPodHarness.setValidatorRestakedBalance(validatorFields[0], MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR);
+
+ // Verify balance update
+ int256 sharesDeltaGwei = eigenPodHarness.verifyBalanceUpdate(
+ oracleTimestamp,
+ validatorIndex,
+ beaconStateRoot,
+ validatorFieldsProof,
+ validatorFields,
+ 0 // Most recent balance update timestamp set to 0
+ );
+
+ // Checks
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]);
+ assertEq(validatorInfo.restakedBalanceGwei, newValidatorBalance, "Restaked balance gwei should be max");
+ assertLt(sharesDeltaGwei, 0, "Shares delta should be negative");
+ int256 expectedSharesDiff = int256(uint256(newValidatorBalance)) - int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR));
+ assertEq(sharesDeltaGwei, expectedSharesDiff, "Shares delta should be equal to restaked balance");
+ }
+
+ function test_zeroSharesDelta() public {
+ // Set JSON
+ setJSON("src/test/test-data/balanceUpdateProof_notOverCommitted_302913.json");
+
+ // Set proof params
+ _setBalanceUpdateParams();
+
+ // Set previous restaked balance to max restaked balance
+ eigenPodHarness.setValidatorRestakedBalance(validatorFields[0], MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR);
+
+ // Verify balance update
+ int256 sharesDeltaGwei = eigenPodHarness.verifyBalanceUpdate(
+ oracleTimestamp,
+ validatorIndex,
+ beaconStateRoot,
+ validatorFieldsProof,
+ validatorFields,
+ 0 // Most recent balance update timestamp set to 0
+ );
+
+ // Checks
+ assertEq(sharesDeltaGwei, 0, "Shares delta should be 0");
+ }
+
+ function _setBalanceUpdateParams() internal {
+ // Set validator index, beacon state root, balance update proof, and validator fields
+ validatorIndex = uint40(getValidatorIndex());
+ beaconStateRoot = getBeaconStateRoot();
+ validatorFieldsProof = abi.encodePacked(getBalanceUpdateProof());
+ validatorFields = getValidatorFields();
+
+ // Get an oracle timestamp
+ cheats.warp(GOERLI_GENESIS_TIME + 1 days);
+ oracleTimestamp = uint64(block.timestamp);
+
+ // Set validator status to active
+ eigenPodHarness.setValidatorStatus(validatorFields[0], IEigenPod.VALIDATOR_STATUS.ACTIVE);
+ }
+}
+
+contract EigenPodUnitTests_WithdrawalTests is EigenPodHarnessSetup, ProofParsing, IEigenPodEvents {
+ using BeaconChainProofs for *;
+
+ // Params to process withdrawal
+ bytes32 beaconStateRoot;
+ BeaconChainProofs.WithdrawalProof withdrawalToProve;
+ bytes validatorFieldsProof;
+ bytes32[] validatorFields;
+ bytes32[] withdrawalFields;
+
+ // Most recent withdrawal timestamp incremented when withdrawal processed before restaking OR when staking activated
+ function test_verifyAndProcessWithdrawal_revert_staleProof() public hasNotRestaked {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ // Set timestamp to after withdrawal timestamp
+ uint64 timestampOfWithdrawal = Endian.fromLittleEndianUint64(withdrawalToProve.timestampRoot);
+ uint256 newTimestamp = timestampOfWithdrawal + 2500;
+ cheats.warp(newTimestamp);
+
+ // Activate restaking, setting `mostRecentWithdrawalTimestamp`
+ eigenPodHarness.activateRestaking();
+
+ // Expect revert
+ cheats.expectRevert("EigenPod.proofIsForValidTimestamp: beacon chain proof must be at or after mostRecentWithdrawalTimestamp");
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+ }
+
+ function test_verifyAndProcessWithdrawal_revert_statusInactive() public {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ // Set status to inactive
+ eigenPodHarness.setValidatorStatus(validatorFields[0], IEigenPod.VALIDATOR_STATUS.INACTIVE);
+
+ // Expect revert
+ cheats.expectRevert("EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract");
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+ }
+
+ function test_verifyAndProcessWithdrawal_withdrawalAlreadyProcessed() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ // Process withdrawal
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Attempt to process again
+ cheats.expectRevert("EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp");
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+ }
+
+ function test_verifyAndProcessWithdrawal_excess() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ // Process withdrawal
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Verify storage
+ bytes32 validatorPubKeyHash = validatorFields.getPubkeyHash();
+ uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
+ assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
+ }
+
+ // regression test for off-by-one error
+ function test_verifyAndProcessWithdrawal_atLatestWithdrawalTimestamp() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
+ // set the `mostRecentWithdrawalTimestamp` to be equal to the withdrawal timestamp
+ eigenPodHarness.setMostRecentWithdrawalTimestamp(withdrawalTimestamp);
+
+ // Process withdrawal
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Verify storage
+ bytes32 validatorPubKeyHash = validatorFields.getPubkeyHash();
+ assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
+ }
+
+ function test_revert_verifyAndProcessWithdrawal_beforeLatestWithdrawalTimestamp() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
+ // set the `mostRecentWithdrawalTimestamp` to just after the withdrawal timestamp
+ eigenPodHarness.setMostRecentWithdrawalTimestamp(withdrawalTimestamp + 1);
+
+ // Process withdrawal, expect revert
+ cheats.expectRevert("EigenPod.proofIsForValidTimestamp: beacon chain proof must be at or after mostRecentWithdrawalTimestamp");
+ eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+ }
+
+ /// @notice Tests processing a full withdrawal > MAX_RESTAKED_GWEI_PER_VALIDATOR
+ function test_processFullWithdrawal_excess32ETH() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("./src/test/test-data/fullWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ // Get params to check against
+ uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
+ uint40 validatorIndex = uint40(getValidatorIndex());
+ uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
+ assertGt(withdrawalAmountGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Withdrawal amount should be greater than max restaked balance for this test");
+
+ // Process full withdrawal
+ vm.expectEmit(true, true, true, true);
+ emit FullWithdrawalRedeemed(validatorIndex, withdrawalTimestamp, podOwner, withdrawalAmountGwei);
+ IEigenPod.VerifiedWithdrawal memory vw = eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Storage checks in _verifyAndProcessWithdrawal
+ bytes32 validatorPubKeyHash = validatorFields.getPubkeyHash();
+ assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
+
+ // Checks from _processFullWithdrawal
+ assertEq(eigenPod.withdrawableRestakedExecutionLayerGwei(), MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Incorrect withdrawable restaked execution layer gwei");
+ // Excess withdrawal amount is diff between restaked balance and total withdrawal amount
+ uint64 excessWithdrawalAmount = withdrawalAmountGwei - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ assertEq(vw.amountToSendGwei, excessWithdrawalAmount, "Amount to send via router is not correct");
+ assertEq(vw.sharesDeltaGwei, 0, "Shares delta not correct"); // Shares delta is 0 since restaked balance and amount to withdraw were max
+
+ // ValidatorInfo storage update checks
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]);
+ assertEq(uint8(validatorInfo.status), uint8(IEigenPod.VALIDATOR_STATUS.WITHDRAWN), "Validator status should be withdrawn");
+ assertEq(validatorInfo.restakedBalanceGwei, 0, "Restaked balance gwei should be 0");
+ }
+
+ function test_processFullWithdrawal_lessThan32ETH() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("src/test/test-data/fullWithdrawalProof_Latest_28ETH.json");
+ _setWithdrawalProofParams();
+
+ // Get params to check against
+ uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
+ uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
+ assertLt(withdrawalAmountGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Withdrawal amount should be greater than max restaked balance for this test");
+
+ // Process full withdrawal
+ IEigenPod.VerifiedWithdrawal memory vw = eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Storage checks in _verifyAndProcessWithdrawal
+ bytes32 validatorPubKeyHash = validatorFields.getPubkeyHash();
+ assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
+
+ // Checks from _processFullWithdrawal
+ assertEq(eigenPod.withdrawableRestakedExecutionLayerGwei(), withdrawalAmountGwei, "Incorrect withdrawable restaked execution layer gwei");
+ // Excess withdrawal amount should be 0 since balance is < MAX
+ assertEq(vw.amountToSendGwei, 0, "Amount to send via router is not correct");
+ int256 expectedSharesDiff = int256(uint256(withdrawalAmountGwei)) - int256(uint256(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR));
+ assertEq(vw.sharesDeltaGwei, expectedSharesDiff, "Shares delta not correct"); // Shares delta is 0 since restaked balance and amount to withdraw were max
+
+ // ValidatorInfo storage update checks
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]);
+ assertEq(uint8(validatorInfo.status), uint8(IEigenPod.VALIDATOR_STATUS.WITHDRAWN), "Validator status should be withdrawn");
+ assertEq(validatorInfo.restakedBalanceGwei, 0, "Restaked balance gwei should be 0");
+ }
+
+ function test_processPartialWithdrawal() public setWithdrawalCredentialsExcess {
+ // Set JSON & params
+ setJSON("./src/test/test-data/partialWithdrawalProof_Latest.json");
+ _setWithdrawalProofParams();
+
+ // Get params to check against
+ uint64 withdrawalTimestamp = withdrawalToProve.getWithdrawalTimestamp();
+ uint40 validatorIndex = uint40(getValidatorIndex());
+ uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
+
+ // Assert that partial withdrawal code path will be tested
+ assertLt(withdrawalToProve.getWithdrawalEpoch(), validatorFields.getWithdrawableEpoch(), "Withdrawal epoch should be less than the withdrawable epoch");
+
+ // Process partial withdrawal
+ vm.expectEmit(true, true, true, true);
+ emit PartialWithdrawalRedeemed(validatorIndex, withdrawalTimestamp, podOwner, withdrawalAmountGwei);
+ IEigenPod.VerifiedWithdrawal memory vw = eigenPodHarness.verifyAndProcessWithdrawal(
+ beaconStateRoot,
+ withdrawalToProve,
+ validatorFieldsProof,
+ validatorFields,
+ withdrawalFields
+ );
+
+ // Storage checks in _verifyAndProcessWithdrawal
+ bytes32 validatorPubKeyHash = validatorFields.getPubkeyHash();
+ assertTrue(eigenPodHarness.provenWithdrawal(validatorPubKeyHash, withdrawalTimestamp), "Withdrawal not set to proven");
+
+ // Checks from _processPartialWithdrawal
+ assertEq(eigenPod.sumOfPartialWithdrawalsClaimedGwei(), withdrawalAmountGwei, "Incorrect partial withdrawal amount");
+ assertEq(vw.amountToSendGwei, withdrawalAmountGwei, "Amount to send via router is not correct");
+ assertEq(vw.sharesDeltaGwei, 0, "Shares delta should be 0");
+
+ // Assert validator still has same restaked balance and status
+ IEigenPod.ValidatorInfo memory validatorInfo = eigenPodHarness.validatorPubkeyHashToInfo(validatorFields[0]);
+ assertEq(uint8(validatorInfo.status), uint8(IEigenPod.VALIDATOR_STATUS.ACTIVE), "Validator status should be active");
+ assertEq(validatorInfo.restakedBalanceGwei, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, "Restaked balance gwei should be max");
+ }
+
+ function testFuzz_processFullWithdrawal(bytes32 pubkeyHash, uint64 restakedAmount, uint64 withdrawalAmount) public {
+ // Format validatorInfo struct
+ IEigenPod.ValidatorInfo memory validatorInfo = IEigenPod.ValidatorInfo({
+ validatorIndex: 0,
+ restakedBalanceGwei: restakedAmount,
+ mostRecentBalanceUpdateTimestamp: 0,
+ status: IEigenPod.VALIDATOR_STATUS.ACTIVE
+ });
+
+ // Since we're withdrawing using an ACTIVE validator, ensure we have
+ // a validator count to decrement
+ uint activeValidatorCountBefore = 1 + eigenPodHarness.getActiveValidatorCount();
+ eigenPodHarness.setActiveValidatorCount(activeValidatorCountBefore);
+
+ // Process full withdrawal.
+ IEigenPod.VerifiedWithdrawal memory vw = eigenPodHarness.processFullWithdrawal(0, pubkeyHash, 0, podOwner, withdrawalAmount, validatorInfo);
+
+ // Validate that our activeValidatorCount decreased
+ uint activeValidatorCountAfter = eigenPodHarness.getActiveValidatorCount();
+ assertEq(activeValidatorCountAfter, activeValidatorCountBefore - 1, "active validator count should decrease when withdrawing active validator");
+
+ // Get expected amounts based on withdrawalAmount
+ uint64 amountETHToQueue;
+ uint64 amountETHToSend;
+ if (withdrawalAmount > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR){
+ amountETHToQueue = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ amountETHToSend = withdrawalAmount - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
+ } else {
+ amountETHToQueue = withdrawalAmount;
+ amountETHToSend = 0;
+ }
+
+ // Check invariant-> amountToQueue + amountToSend = withdrawalAmount
+ assertEq(vw.amountToSendGwei + eigenPod.withdrawableRestakedExecutionLayerGwei(), withdrawalAmount, "Amount to queue and send must add up to total withdrawal amount");
+
+ // Check amount to queue and send
+ assertEq(vw.amountToSendGwei, amountETHToSend, "Amount to queue is not correct");
+ assertEq(eigenPod.withdrawableRestakedExecutionLayerGwei(), amountETHToQueue, "Incorrect withdrawable restaked execution layer gwei");
+
+ // Check shares delta
+ int256 expectedSharesDelta = int256(uint256(amountETHToQueue)) - int256(uint256(restakedAmount));
+ assertEq(vw.sharesDeltaGwei, expectedSharesDelta, "Shares delta not correct");
+
+ // Storage checks
+ IEigenPod.ValidatorInfo memory validatorInfoAfter = eigenPodHarness.validatorPubkeyHashToInfo(pubkeyHash);
+ assertEq(uint8(validatorInfoAfter.status), uint8(IEigenPod.VALIDATOR_STATUS.WITHDRAWN), "Validator status should be withdrawn");
+ assertEq(validatorInfoAfter.restakedBalanceGwei, 0, "Restaked balance gwei should be 0");
+ }
+
+ function testFuzz_processFullWithdrawal_lessMaxRestakedBalance(bytes32 pubkeyHash, uint64 restakedAmount, uint64 withdrawalAmount) public {
+ withdrawalAmount = uint64(bound(withdrawalAmount, 0, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR));
+ testFuzz_processFullWithdrawal(pubkeyHash, restakedAmount, withdrawalAmount);
+ }
+
+ function testFuzz_processPartialWithdrawal(
+ uint40 validatorIndex,
+ uint64 withdrawalTimestamp,
+ address recipient,
+ uint64 partialWithdrawalAmountGwei
+ ) public {
+ IEigenPod.VerifiedWithdrawal memory vw = eigenPodHarness.processPartialWithdrawal(validatorIndex, withdrawalTimestamp, recipient, partialWithdrawalAmountGwei);
+
+ // Checks
+ assertEq(eigenPod.sumOfPartialWithdrawalsClaimedGwei(), partialWithdrawalAmountGwei, "Incorrect partial withdrawal amount");
+ assertEq(vw.amountToSendGwei, partialWithdrawalAmountGwei, "Amount to send via router is not correct");
+ assertEq(vw.sharesDeltaGwei, 0, "Shares delta should be 0");
+ }
+
+ function _setWithdrawalProofParams() internal {
+ // Set validator index, beacon state root, balance update proof, and validator fields
+ beaconStateRoot = getBeaconStateRoot();
+ validatorFields = getValidatorFields();
+ validatorFieldsProof = abi.encodePacked(getValidatorProof());
+ withdrawalToProve = _getWithdrawalProof();
+ withdrawalFields = getWithdrawalFields();
+ }
+
+ /// @notice this function just generates a valid proof so that we can test other functionalities of the withdrawal flow
+ function _getWithdrawalProof() internal returns (BeaconChainProofs.WithdrawalProof memory) {
+ {
+ bytes32 blockRoot = getBlockRoot();
+ 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(withdrawalProof),
+ abi.encodePacked(getSlotProof()),
+ abi.encodePacked(getExecutionPayloadProof()),
+ abi.encodePacked(timestampProof),
+ abi.encodePacked(getHistoricalSummaryProof()),
+ uint64(getBlockRootIndex()),
+ uint64(getHistoricalSummaryIndex()),
+ uint64(getWithdrawalIndex()),
+ blockRoot,
+ slotRoot,
+ timestampRoot,
+ executionPayloadRoot
+ );
+ }
+ }
+
+ ///@notice Effective balance is > 32 ETH
+ modifier setWithdrawalCredentialsExcess() {
+ // Set JSON and params
+ setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
+ // Set beacon state root, validatorIndex
+ beaconStateRoot = getBeaconStateRoot();
+ uint40 validatorIndex = uint40(getValidatorIndex());
+ validatorFieldsProof = abi.encodePacked(getWithdrawalCredentialProof()); // Validator fields are proven here
+ validatorFields = getValidatorFields();
+
+ // Get an oracle timestamp
+ cheats.warp(GOERLI_GENESIS_TIME + 1 days);
+ uint64 oracleTimestamp = uint64(block.timestamp);
+
+ eigenPodHarness.verifyWithdrawalCredentials(
+ oracleTimestamp,
+ beaconStateRoot,
+ validatorIndex,
+ validatorFieldsProof,
+ validatorFields
+ );
+ _;
+ }
+}
diff --git a/src/test/unit/PausableUnit.t.sol b/src/test/unit/PausableUnit.t.sol
index 4d5bc3a52a..c8f03aa92b 100644
--- a/src/test/unit/PausableUnit.t.sol
+++ b/src/test/unit/PausableUnit.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "forge-std/Test.sol";
@@ -173,4 +173,4 @@ contract PausableUnitTests is Test {
cheats.stopPrank();
}
-}
\ No newline at end of file
+}
diff --git a/src/test/unit/PauserRegistryUnit.t.sol b/src/test/unit/PauserRegistryUnit.t.sol
index c29dde4f15..802c228e80 100644
--- a/src/test/unit/PauserRegistryUnit.t.sol
+++ b/src/test/unit/PauserRegistryUnit.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "forge-std/Test.sol";
@@ -98,4 +98,4 @@ contract PauserRegistryUnitTests is Test {
pauserRegistry.setUnpauser(newUnpauser);
cheats.stopPrank();
}
-}
\ No newline at end of file
+}
diff --git a/src/test/unit/SlasherUnit.t.sol b/src/test/unit/SlasherUnit.t.sol
deleted file mode 100644
index de1b8fe539..0000000000
--- a/src/test/unit/SlasherUnit.t.sol
+++ /dev/null
@@ -1,799 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
-import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
-
-import "forge-std/Test.sol";
-
-import "../../contracts/core/Slasher.sol";
-import "../../contracts/permissions/PauserRegistry.sol";
-import "../../contracts/strategies/StrategyBase.sol";
-
-import "../mocks/DelegationMock.sol";
-import "../mocks/EigenPodManagerMock.sol";
-import "../mocks/StrategyManagerMock.sol";
-import "../mocks/Reenterer.sol";
-import "../mocks/Reverter.sol";
-
-import "../mocks/ERC20Mock.sol";
-
-import "./Utils.sol";
-
-contract SlasherUnitTests is Test, Utils {
-
- Vm cheats = Vm(HEVM_ADDRESS);
-
- uint256 private constant HEAD = 0;
-
- uint256 private constant _NULL = 0;
- uint256 private constant _HEAD = 0;
-
- bool private constant _PREV = false;
- bool private constant _NEXT = true;
-
- uint8 internal constant PAUSED_OPT_INTO_SLASHING = 0;
- uint8 internal constant PAUSED_FIRST_STAKE_UPDATE = 1;
- uint8 internal constant PAUSED_NEW_FREEZING = 2;
-
- uint32 internal constant MAX_CAN_SLASH_UNTIL = type(uint32).max;
-
- ProxyAdmin public proxyAdmin;
- PauserRegistry public pauserRegistry;
-
- Slasher public slasherImplementation;
- Slasher public slasher;
- StrategyManagerMock public strategyManagerMock;
- DelegationMock public delegationMock;
- EigenPodManagerMock public eigenPodManagerMock;
-
- Reenterer public reenterer;
-
- address public pauser = address(555);
- address public unpauser = address(999);
-
- address initialOwner = address(this);
-
- IERC20 public dummyToken;
- StrategyBase public dummyStrat;
-
- uint256[] public emptyUintArray;
-
- // used as transient storage to fix stack-too-deep errors
- uint32 contractCanSlashOperatorUntilBefore;
- uint256 linkedListLengthBefore;
- uint256 middlewareTimesLengthBefore;
- bool nodeExists;
- uint256 prevNode;
- uint256 nextNode;
- ISlasher.MiddlewareDetails middlewareDetailsBefore;
- ISlasher.MiddlewareDetails middlewareDetailsAfter;
-
- mapping(address => bool) public addressIsExcludedFromFuzzedInputs;
-
- modifier filterFuzzedAddressInputs(address fuzzedAddress) {
- cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]);
- _;
- }
-
- function setUp() virtual public {
- proxyAdmin = new ProxyAdmin();
-
- address[] memory pausers = new address[](1);
- pausers[0] = pauser;
- pauserRegistry = new PauserRegistry(pausers, unpauser);
-
- delegationMock = new DelegationMock();
- eigenPodManagerMock = new EigenPodManagerMock();
- strategyManagerMock = new StrategyManagerMock();
- slasherImplementation = new Slasher(strategyManagerMock, delegationMock);
- slasher = Slasher(
- address(
- new TransparentUpgradeableProxy(
- address(slasherImplementation),
- address(proxyAdmin),
- abi.encodeWithSelector(Slasher.initialize.selector, initialOwner, pauserRegistry, 0/*initialPausedStatus*/)
- )
- )
- );
- dummyToken = new ERC20Mock();
- dummyStrat = deployNewStrategy(dummyToken, strategyManagerMock, pauserRegistry, dummyAdmin);
-
- // excude the zero address and the proxyAdmin from fuzzed inputs
- addressIsExcludedFromFuzzedInputs[address(0)] = true;
- addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true;
- }
-
- /**
- * Regression test for SigP's EGN2-01 issue, "Middleware can Deny Withdrawals by Revoking Slashing Prior to Queueing Withdrawal".
- * This test checks that a new queued withdrawal after total deregistration (i.e. queued *after* totally de-registering from all AVSs) can still eventually be completed.
- */
- function testCanCompleteNewQueuedWithdrawalAfterTotalDeregistration(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 serveUntilBlock,
- uint32 withdrawalStartBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX (or one less than max), since the contract will revert in this instance (or our math with overflow, if 1 less).
- cheats.assume(prevServeUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
- cheats.assume(serveUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
-
- // simulate registering to and de-registering from an AVS
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
-
- uint256 middlewareTimesIndex = slasher.middlewareTimesLength(operator) - 1;
- ISlasher.MiddlewareTimes memory middlewareTimes = slasher.operatorToMiddlewareTimes(operator, middlewareTimesIndex);
-
- // emit log_named_uint("middlewareTimes.stalestUpdateBlock", middlewareTimes.stalestUpdateBlock);
- // emit log_named_uint("middlewareTimes.latestServeUntilBlock", middlewareTimes.latestServeUntilBlock);
-
- // uint256 operatorWhitelistedContractsLinkedListSize = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- // emit log_named_uint("operatorWhitelistedContractsLinkedListSize", operatorWhitelistedContractsLinkedListSize);
-
- // filter fuzzed inputs
- // cheats.assume(withdrawalStartBlock >= block.number);
- cheats.assume(withdrawalStartBlock >= middlewareTimes.stalestUpdateBlock);
- cheats.roll(middlewareTimes.latestServeUntilBlock + 1);
-
- require(
- slasher.canWithdraw(operator, withdrawalStartBlock, middlewareTimesIndex),
- "operator cannot complete withdrawal when they should be able to"
- );
- }
-
- /**
- * Test related to SigP's EGN2-01 issue, "Middleware can Deny Withdrawals by Revoking Slashing Prior to Queueing Withdrawal", to ensure that the fix does not degrade performance.
- * This test checks that a *previous* queued withdrawal prior to total deregistration (i.e. queued *before* totally de-registering from all AVSs)
- * can still be withdrawn at the appropriate time, i.e. that a fix to EGN2-01 does not add any delay to existing withdrawals.
- */
- function testCanCompleteExistingQueuedWithdrawalAfterTotalDeregistration(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 serveUntilBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX (or one less than max), since the contract will revert in this instance (or our math with overflow, if 1 less).
- cheats.assume(prevServeUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
- cheats.assume(serveUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
-
- // roll forward 2 blocks
- cheats.roll(block.number + 2);
- // make sure `withdrawalStartBlock` is in past
- uint32 withdrawalStartBlock = uint32(block.number) - 1;
-
- // simulate registering to and de-registering from an AVS
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
-
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
-
- uint256 operatorWhitelistedContractsLinkedListSize = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- require(operatorWhitelistedContractsLinkedListSize == 0, "operatorWhitelistedContractsLinkedListSize != 0");
-
- uint256 middlewareTimesLength = slasher.middlewareTimesLength(operator);
- require(middlewareTimesLength >= 2, "middlewareTimesLength < 2");
- uint256 middlewareTimesIndex = middlewareTimesLength - 2;
-
- ISlasher.MiddlewareTimes memory olderMiddlewareTimes = slasher.operatorToMiddlewareTimes(operator, middlewareTimesIndex);
-
- cheats.roll(olderMiddlewareTimes.latestServeUntilBlock + 1);
-
- require(withdrawalStartBlock < olderMiddlewareTimes.stalestUpdateBlock, "withdrawalStartBlock >= olderMiddlewareTimes.stalestUpdateBlock");
-
- require(
- slasher.canWithdraw(operator, withdrawalStartBlock, middlewareTimesIndex),
- "operator cannot complete withdrawal when they should be able to"
- );
- }
-
- function testCannotReinitialize() public {
- cheats.expectRevert(bytes("Initializable: contract is already initialized"));
- slasher.initialize(initialOwner, pauserRegistry, 0);
- }
-
- function testOptIntoSlashing(address operator, address contractAddress)
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- delegationMock.setIsOperator(operator, true);
-
- cheats.startPrank(operator);
- slasher.optIntoSlashing(contractAddress);
- cheats.stopPrank();
-
- assertEq(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress), MAX_CAN_SLASH_UNTIL);
- require(slasher.canSlash(operator, contractAddress), "contract was not properly granted slashing permission");
- }
-
- function testOptIntoSlashing_RevertsWhenPaused() public {
- address operator = address(this);
- address contractAddress = address(this);
-
- // pause opting into slashing
- cheats.startPrank(pauser);
- slasher.pause(2 ** PAUSED_OPT_INTO_SLASHING);
- cheats.stopPrank();
-
- cheats.startPrank(operator);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- slasher.optIntoSlashing(contractAddress);
- cheats.stopPrank();
- }
-
- function testOptIntoSlashing_RevertsWhenCallerNotOperator(address notOperator) public filterFuzzedAddressInputs(notOperator) {
- require(!delegationMock.isOperator(notOperator), "caller is an operator -- this is assumed false");
- address contractAddress = address(this);
-
- cheats.startPrank(notOperator);
- cheats.expectRevert(bytes("Slasher.optIntoSlashing: msg.sender is not a registered operator"));
- slasher.optIntoSlashing(contractAddress);
- cheats.stopPrank();
- }
-
- function testFreezeOperator(address toBeFrozen, address freezingContract) public
- filterFuzzedAddressInputs(toBeFrozen)
- filterFuzzedAddressInputs(freezingContract)
- {
- testOptIntoSlashing(toBeFrozen, freezingContract);
- cheats.startPrank(freezingContract);
- slasher.freezeOperator(toBeFrozen);
- cheats.stopPrank();
-
- require(slasher.isFrozen(toBeFrozen), "operator not properly frozen");
- }
-
- function testFreezeOperatorTwice(address toBeFrozen, address freezingContract) public {
- testFreezeOperator(toBeFrozen, freezingContract);
- testFreezeOperator(toBeFrozen, freezingContract);
- }
-
- function testFreezeOperator_RevertsWhenPaused(address toBeFrozen, address freezingContract) external
- filterFuzzedAddressInputs(toBeFrozen)
- filterFuzzedAddressInputs(freezingContract)
- {
- testOptIntoSlashing(toBeFrozen, freezingContract);
-
- // pause freezing
- cheats.startPrank(pauser);
- slasher.pause(2 ** PAUSED_NEW_FREEZING);
- cheats.stopPrank();
-
- cheats.startPrank(freezingContract);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- slasher.freezeOperator(toBeFrozen);
- cheats.stopPrank();
- }
-
- function testFreezeOperator_WhenCallerDoesntHaveSlashingPermission(address toBeFrozen, address freezingContract) external
- filterFuzzedAddressInputs(toBeFrozen)
- filterFuzzedAddressInputs(freezingContract)
- {
- cheats.startPrank(freezingContract);
- cheats.expectRevert(bytes("Slasher.freezeOperator: msg.sender does not have permission to slash this operator"));
- slasher.freezeOperator(toBeFrozen);
- cheats.stopPrank();
- }
-
- function testResetFrozenStatus(uint8 numberOfOperators, uint256 pseudorandomInput) external {
- // sanity filtering
- cheats.assume(numberOfOperators <= 16);
-
- address contractAddress = address(this);
-
- address[] memory operatorAddresses = new address[](numberOfOperators);
- bool[] memory operatorFrozen = new bool[](numberOfOperators);
- for (uint256 i = 0; i < numberOfOperators; ++i) {
- address operatorAddress = address(uint160(8888 + i));
- operatorAddresses[i] = operatorAddress;
- testOptIntoSlashing(operatorAddress, contractAddress);
- bool freezeOperator = (pseudorandomInput % 2 == 0) ? false : true;
- pseudorandomInput = uint256(keccak256(abi.encodePacked(pseudorandomInput)));
- operatorFrozen[i] = freezeOperator;
- if (freezeOperator) {
- testFreezeOperator(operatorAddress, contractAddress);
- }
- }
-
- cheats.startPrank(slasher.owner());
- slasher.resetFrozenStatus(operatorAddresses);
- cheats.stopPrank();
-
- for (uint256 i = 0; i < numberOfOperators; ++i) {
- require(!slasher.isFrozen(operatorAddresses[i]), "operator frozen improperly (not unfrozen when should be)");
- }
- }
-
- function testResetFrozenStatus_RevertsWhenCalledByNotOwner(address notOwner) external filterFuzzedAddressInputs(notOwner) {
- // sanity filtering
- cheats.assume(notOwner != slasher.owner());
-
- address[] memory operatorAddresses = new address[](1);
-
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- slasher.resetFrozenStatus(operatorAddresses);
- cheats.stopPrank();
- }
-
- function testRecordFirstStakeUpdate(address operator, address contractAddress, uint32 serveUntilBlock)
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- testOptIntoSlashing(operator, contractAddress);
- _testRecordFirstStakeUpdate(operator, contractAddress, serveUntilBlock);
- }
-
- // internal function corresponding to the bulk of the logic in `testRecordFirstStakeUpdate`, so we can reuse it elsewhere without calling `testOptIntoSlashing`
- function _testRecordFirstStakeUpdate(address operator, address contractAddress, uint32 serveUntilBlock)
- internal
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
-
- linkedListLengthBefore = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- middlewareDetailsBefore = slasher.whitelistedContractDetails(operator, contractAddress);
- contractCanSlashOperatorUntilBefore = slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress);
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructBefore;
- // fetch the most recent struct, if at least one exists (otherwise leave the struct uninitialized)
- middlewareTimesLengthBefore = slasher.middlewareTimesLength(operator);
- if (middlewareTimesLengthBefore != 0) {
- mostRecentMiddlewareTimesStructBefore = slasher.operatorToMiddlewareTimes(operator, middlewareTimesLengthBefore - 1);
- }
-
- cheats.startPrank(contractAddress);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructAfter = slasher.operatorToMiddlewareTimes(operator, slasher.middlewareTimesLength(operator) - 1);
-
- // check that linked list size increased appropriately
- require(slasher.operatorWhitelistedContractsLinkedListSize(operator) == linkedListLengthBefore + 1, "linked list length did not increase when it should!");
- // get the linked list entry for the `contractAddress`
- (nodeExists, prevNode, nextNode) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- // verify that the node exists
- require(nodeExists, "node does not exist");
-
- // if the `serveUntilBlock` time is greater than the previous maximum, then an update must have been pushed and the `latestServeUntilBlock` must be equal to the `serveUntilBlock` input
- if (serveUntilBlock > mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1, "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == serveUntilBlock, "latestServeUntilBlock not updated correctly");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock, "latestServeUntilBlock updated incorrectly");
- }
- // if this is the first MiddlewareTimes struct in the array, then an update must have been pushed and the `stalestUpdateBlock` *must* be equal to the current block
- if (middlewareTimesLengthBefore == 0) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1,
- "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is first list entry");
- // otherwise, we check if the `contractAddress` is the head of the list. If it *is*, then prevNode will be _HEAD, and...
- } else if (prevNode == _HEAD) {
- // if nextNode is _HEAD, then the this indicates that `contractAddress` is actually the only list entry
- if (nextNode != _HEAD) {
- // if nextNode is not the only list entry, then `latestUpdateBlock` should update to a more recent time (if applicable!)
- uint32 nextMiddlewaresLeastRecentUpdateBlock = slasher.whitelistedContractDetails(operator, _uintToAddress(nextNode)).latestUpdateBlock;
- uint32 newValue = (nextMiddlewaresLeastRecentUpdateBlock < block.number) ? nextMiddlewaresLeastRecentUpdateBlock: uint32(block.number);
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == newValue,
- "stalestUpdateBlock not updated correctly -- should have updated to newValue");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is only list entry");
- }
- }
-
- middlewareDetailsAfter = slasher.whitelistedContractDetails(operator, contractAddress);
- require(middlewareDetailsAfter.latestUpdateBlock == block.number,
- "latestUpdateBlock not updated correctly");
- require(middlewareDetailsAfter.contractCanSlashOperatorUntilBlock == middlewareDetailsBefore.contractCanSlashOperatorUntilBlock,
- "contractCanSlashOperatorUntilBlock changed unexpectedly");
- // check that `contractCanSlashOperatorUntilBlock` did not change
- require(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress) == contractCanSlashOperatorUntilBefore, "contractCanSlashOperatorUntilBlock changed unexpectedly");
- }
-
- function testRecordFirstStakeUpdate_RevertsWhenPaused() external {
- address operator = address(this);
- address contractAddress = address(this);
- uint32 serveUntilBlock = 0;
- testOptIntoSlashing(operator, contractAddress);
-
- // pause first stake updates
- cheats.startPrank(pauser);
- slasher.pause(2 ** PAUSED_FIRST_STAKE_UPDATE);
- cheats.stopPrank();
-
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordFirstStakeUpdate_RevertsWhenCallerDoesntHaveSlashingPermission() external {
- address operator = address(this);
- address contractAddress = address(this);
- uint32 serveUntilBlock = 0;
-
- require(!slasher.canSlash(operator, contractAddress), "improper slashing permission has been given");
-
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"));
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordFirstStakeUpdate_RevertsWhenCallerAlreadyInList() external {
- address operator = address(this);
- address contractAddress = address(this);
- uint32 serveUntilBlock = 0;
-
- testRecordFirstStakeUpdate(operator, contractAddress, serveUntilBlock);
-
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Slasher.recordFirstStakeUpdate: Appending middleware unsuccessful"));
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordStakeUpdate(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
- _testRecordStakeUpdate(operator, contractAddress, updateBlock, serveUntilBlock, insertAfter);
- }
-
- // internal function corresponding to the bulk of the logic in `testRecordStakeUpdate`, so we can reuse it elsewhere without calling `testOptIntoSlashing`
- function _testRecordStakeUpdate(
- address operator,
- address contractAddress,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- internal
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out invalid fuzzed inputs. "cannot provide update for future block"
- cheats.assume(updateBlock <= block.number);
-
- linkedListLengthBefore = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- middlewareDetailsBefore = slasher.whitelistedContractDetails(operator, contractAddress);
- contractCanSlashOperatorUntilBefore = slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress);
-
- // filter out invalid fuzzed inputs. "can't push a previous update"
- cheats.assume(updateBlock >= middlewareDetailsBefore.latestUpdateBlock);
-
- // fetch the most recent struct
- middlewareTimesLengthBefore = slasher.middlewareTimesLength(operator);
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructBefore = slasher.operatorToMiddlewareTimes(operator, middlewareTimesLengthBefore - 1);
-
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructAfter = slasher.operatorToMiddlewareTimes(operator, slasher.middlewareTimesLength(operator) - 1);
-
- // check that linked list size remained the same appropriately
- require(slasher.operatorWhitelistedContractsLinkedListSize(operator) == linkedListLengthBefore, "linked list length did increased inappropriately");
- // get the linked list entry for the `contractAddress`
- (nodeExists, prevNode, nextNode) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- // verify that the node exists
- require(nodeExists, "node does not exist");
-
- // if the `serveUntilBlock` time is greater than the previous maximum, then an update must have been pushed and the `latestServeUntilBlock` must be equal to the `serveUntilBlock` input
- if (serveUntilBlock > mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1, "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == serveUntilBlock, "latestServeUntilBlock not updated correctly");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock, "latestServeUntilBlock updated incorrectly");
- }
- // if this is the first MiddlewareTimes struct in the array, then an update must have been pushed and the `stalestUpdateBlock` *must* be equal to the current block
- if (middlewareTimesLengthBefore == 0) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1,
- "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == updateBlock,
- "stalestUpdateBlock not updated correctly -- contractAddress is first list entry");
- // otherwise, we check if the `contractAddress` is the head of the list. If it *is*, then prevNode will be _HEAD, and...
- } else if (prevNode == _HEAD) {
- // if nextNode is _HEAD, then the this indicates that `contractAddress` is actually the only list entry
- if (nextNode != _HEAD) {
- // if nextNode is not the only list entry, then `latestUpdateBlock` should update to a more recent time (if applicable!)
- uint32 nextMiddlewaresLeastRecentUpdateBlock = slasher.whitelistedContractDetails(operator, _uintToAddress(nextNode)).latestUpdateBlock;
- uint32 newValue = (nextMiddlewaresLeastRecentUpdateBlock < updateBlock) ? nextMiddlewaresLeastRecentUpdateBlock: uint32(updateBlock);
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == newValue,
- "stalestUpdateBlock not updated correctly -- should have updated to newValue");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == updateBlock,
- "stalestUpdateBlock not updated correctly -- contractAddress is only list entry");
- }
- }
-
- middlewareDetailsAfter = slasher.whitelistedContractDetails(operator, contractAddress);
- require(middlewareDetailsAfter.latestUpdateBlock == updateBlock,
- "latestUpdateBlock not updated correctly");
- require(middlewareDetailsAfter.contractCanSlashOperatorUntilBlock == middlewareDetailsBefore.contractCanSlashOperatorUntilBlock,
- "contractCanSlashOperatorUntil changed unexpectedly");
- // check that `contractCanSlashOperatorUntilBlock` did not change
- require(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress) == contractCanSlashOperatorUntilBefore, "contractCanSlashOperatorUntilBlock changed unexpectedly");
- }
-
- function testRecordStakeUpdate_MultipleLinkedListEntries(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- address _contractAddress = address(this);
- cheats.assume(contractAddress != _contractAddress);
- testRecordFirstStakeUpdate(operator, _contractAddress, prevServeUntilBlock);
- testRecordStakeUpdate(operator, contractAddress, prevServeUntilBlock, updateBlock, serveUntilBlock, insertAfter);
- }
-
- function testRecordStakeUpdate_RevertsWhenCallerNotAlreadyInList(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- uint32 updateBlock = 0;
-
- testOptIntoSlashing(operator, contractAddress);
-
- cheats.expectRevert(bytes("Slasher.recordStakeUpdate: Removing middleware unsuccessful"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testRecordStakeUpdate_RevertsWhenCallerNotAlreadyInList_MultipleLinkedListEntries(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- address _contractAddress = address(this);
- uint32 updateBlock = 0;
-
- cheats.assume(contractAddress != _contractAddress);
-
- testRecordFirstStakeUpdate(operator, _contractAddress, prevServeUntilBlock);
- testOptIntoSlashing(operator, contractAddress);
-
- cheats.expectRevert(bytes("Slasher.recordStakeUpdate: Caller is not the list entrant"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testRecordStakeUpdate_RevertsWhenCallerCannotSlash(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- uint32 updateBlock = 0;
- cheats.expectRevert(bytes("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
-
- function testRecordStakeUpdate_RevertsWhenUpdateBlockInFuture(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter to appropriate fuzzed inputs (appropriate for causing reverts!)
- cheats.assume(updateBlock > block.number);
-
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
-
- cheats.expectRevert(bytes("Slasher.recordStakeUpdate: cannot provide update for future block"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX, since the contract will revert in this instance.
- cheats.assume(serveUntilBlock != MAX_CAN_SLASH_UNTIL);
-
- testRecordStakeUpdate_MultipleLinkedListEntries(operator, contractAddress, prevServeUntilBlock, updateBlock, serveUntilBlock, insertAfter);
-
- linkedListLengthBefore = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- middlewareDetailsBefore = slasher.whitelistedContractDetails(operator, contractAddress);
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructBefore;
- // fetch the most recent struct, if at least one exists (otherwise leave the struct uninitialized)
- middlewareTimesLengthBefore = slasher.middlewareTimesLength(operator);
- if (middlewareTimesLengthBefore != 0) {
- mostRecentMiddlewareTimesStructBefore = slasher.operatorToMiddlewareTimes(operator, middlewareTimesLengthBefore - 1);
- }
-
- // get the linked list entry for the `contractAddress`
- (nodeExists, prevNode, nextNode) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- require(nodeExists, "node does not exist when it should");
-
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructAfter = slasher.operatorToMiddlewareTimes(operator, slasher.middlewareTimesLength(operator) - 1);
-
- // check that linked list size decrease appropriately
- require(slasher.operatorWhitelistedContractsLinkedListSize(operator) == linkedListLengthBefore - 1, "linked list length did not decrease when it should!");
- // verify that the node no longer exists
- (nodeExists, /*prevNode*/, /*nextNode*/) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- require(!nodeExists, "node exists when it should have been deleted");
-
- // if the `serveUntilBlock` time is greater than the previous maximum, then an update must have been pushed and the `latestServeUntilBlock` must be equal to the `serveUntilBlock` input
- if (serveUntilBlock > mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1, "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == serveUntilBlock, "latestServeUntilBlock not updated correctly");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock, "latestServeUntilBlock updated incorrectly");
- }
- // if this is the first MiddlewareTimes struct in the array, then an update must have been pushed and the `stalestUpdateBlock` *must* be equal to the current block
- if (middlewareTimesLengthBefore == 0) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1,
- "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is first list entry");
- // otherwise, we check if the `contractAddress` is the head of the list. If it *is*, then prevNode will be _HEAD, and...
- } else if (prevNode == _HEAD) {
- // if nextNode is _HEAD, then the this indicates that `contractAddress` is actually the only list entry
- if (nextNode != _HEAD) {
- // if nextNode is not the only list entry, then `latestUpdateBlock` should update to a more recent time (if applicable!)
- uint32 nextMiddlewaresLeastRecentUpdateBlock = slasher.whitelistedContractDetails(operator, _uintToAddress(nextNode)).latestUpdateBlock;
- uint32 newValue = (nextMiddlewaresLeastRecentUpdateBlock < block.number) ? nextMiddlewaresLeastRecentUpdateBlock: uint32(block.number);
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == newValue,
- "stalestUpdateBlock not updated correctly -- should have updated to newValue");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is only list entry");
- }
- }
-
- middlewareDetailsAfter = slasher.whitelistedContractDetails(operator, contractAddress);
- require(middlewareDetailsAfter.latestUpdateBlock == block.number,
- "latestUpdateBlock not updated correctly");
- // check that slashing ability was revoked after `serveUntilBlock`
- require(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress) == serveUntilBlock, "contractCanSlashOperatorUntil not set correctly");
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility_RevertsWhenCallerCannotSlash(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX, since the contract will revert in this instance.
- cheats.assume(serveUntilBlock != MAX_CAN_SLASH_UNTIL);
-
- cheats.expectRevert(bytes("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"));
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility_RevertsWhenCallerNotAlreadyInList(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX, since the contract will revert in this instance.
- cheats.assume(serveUntilBlock != MAX_CAN_SLASH_UNTIL);
-
- testOptIntoSlashing(operator, contractAddress);
-
- cheats.expectRevert(bytes("Slasher.recordLastStakeUpdateAndRevokeSlashingAbility: Removing middleware unsuccessful"));
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility_RevertsWhenServeUntilBlockInputIsMax(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- uint32 serveUntilBlock = MAX_CAN_SLASH_UNTIL;
-
- testOptIntoSlashing(operator, contractAddress);
-
- _testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
- _testRecordStakeUpdate(operator, contractAddress, updateBlock, prevServeUntilBlock, insertAfter);
-
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Slasher._revokeSlashingAbility: serveUntilBlock time must be limited"));
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function _addressToUint(address addr) internal pure returns(uint256) {
- return uint256(uint160(addr));
- }
-
- function _uintToAddress(uint256 x) internal pure returns(address) {
- return address(uint160(x));
- }
-}
\ No newline at end of file
diff --git a/src/test/unit/StrategyBaseTVLLimitsUnit.sol b/src/test/unit/StrategyBaseTVLLimitsUnit.sol
index b2a4ee7f34..6a2b1d8f9e 100644
--- a/src/test/unit/StrategyBaseTVLLimitsUnit.sol
+++ b/src/test/unit/StrategyBaseTVLLimitsUnit.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "./StrategyBaseUnit.t.sol";
@@ -283,4 +283,4 @@ contract StrategyBaseTVLLimitsUnitTests is StrategyBaseUnitTests {
) public virtual override filterToValidDepositAmounts(amountToDeposit) {
StrategyBaseUnitTests.testWithdrawWithPriorTotalSharesAndAmountSharesNotEqual(amountToDeposit, sharesToWithdraw);
}
-}
\ No newline at end of file
+}
diff --git a/src/test/unit/StrategyBaseUnit.t.sol b/src/test/unit/StrategyBaseUnit.t.sol
index 9d33abf949..659acf1c85 100644
--- a/src/test/unit/StrategyBaseUnit.t.sol
+++ b/src/test/unit/StrategyBaseUnit.t.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
@@ -375,4 +375,4 @@ contract StrategyBaseUnitTests is Test {
strategy.withdraw(address(this), underlyingToken, sharesToWithdraw);
cheats.stopPrank();
}
-}
\ No newline at end of file
+}
diff --git a/src/test/unit/StrategyManagerUnit.t.sol b/src/test/unit/StrategyManagerUnit.t.sol
index 00931cb51f..d4793f901c 100644
--- a/src/test/unit/StrategyManagerUnit.t.sol
+++ b/src/test/unit/StrategyManagerUnit.t.sol
@@ -1,294 +1,273 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
-import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
-
-import "forge-std/Test.sol";
-
-import "../../contracts/core/StrategyManager.sol";
-import "../../contracts/strategies/StrategyBase.sol";
-import "../../contracts/permissions/PauserRegistry.sol";
-import "../mocks/DelegationMock.sol";
-import "../mocks/SlasherMock.sol";
-import "../mocks/EigenPodManagerMock.sol";
-import "../mocks/Reenterer.sol";
-import "../mocks/Reverter.sol";
-
-import "../mocks/ERC20Mock.sol";
-
-import "./Utils.sol";
-
-contract StrategyManagerUnitTests is Test, Utils {
-
- Vm cheats = Vm(HEVM_ADDRESS);
-
- uint256 public REQUIRED_BALANCE_WEI = 31 ether;
-
- ProxyAdmin public proxyAdmin;
- PauserRegistry public pauserRegistry;
-
+import "src/contracts/core/StrategyManager.sol";
+import "src/contracts/strategies/StrategyBase.sol";
+import "src/contracts/permissions/PauserRegistry.sol";
+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";
+
+/**
+ * @notice Unit testing of the StrategyManager contract, entire withdrawal tests related to the
+ * DelegationManager are not tested here but callable functions by the DelegationManager are mocked and tested here.
+ * Contracts tested: StrategyManager.sol
+ * Contracts not mocked: StrategyBase, PauserRegistry
+ */
+contract StrategyManagerUnitTests is EigenLayerUnitTestSetup, IStrategyManagerEvents {
StrategyManager public strategyManagerImplementation;
StrategyManager public strategyManager;
- DelegationMock public delegationMock;
- SlasherMock public slasherMock;
- EigenPodManagerMock public eigenPodManagerMock;
+ IERC20 public dummyToken;
+ ERC20_SetTransferReverting_Mock public revertToken;
StrategyBase public dummyStrat;
StrategyBase public dummyStrat2;
-
- IStrategy public beaconChainETHStrategy;
-
- IERC20 public dummyToken;
+ StrategyBase public dummyStrat3;
Reenterer public reenterer;
- uint256 GWEI_TO_WEI = 1e9;
-
- address public pauser = address(555);
- address public unpauser = address(999);
-
address initialOwner = address(this);
-
- uint256[] public emptyUintArray;
-
- // used as transient storage to fix stack-too-deep errors
- IStrategy public _tempStrategyStorage;
- address public _tempStakerStorage;
uint256 public privateKey = 111111;
+ address constant dummyAdmin = address(uint160(uint256(keccak256("DummyAdmin"))));
- mapping(address => bool) public addressIsExcludedFromFuzzedInputs;
-
- modifier filterFuzzedAddressInputs(address fuzzedAddress) {
- cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]);
- _;
- }
-
- /**
- * @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() virtual public {
- proxyAdmin = new ProxyAdmin();
-
- address[] memory pausers = new address[](1);
- pausers[0] = pauser;
- pauserRegistry = new PauserRegistry(pausers, unpauser);
-
- slasherMock = new SlasherMock();
- delegationMock = new DelegationMock();
- eigenPodManagerMock = new EigenPodManagerMock();
- strategyManagerImplementation = new StrategyManager(delegationMock, eigenPodManagerMock, slasherMock);
+ function setUp() public override {
+ EigenLayerUnitTestSetup.setUp();
+ strategyManagerImplementation = new StrategyManager(delegationManagerMock, eigenPodManagerMock, slasherMock);
strategyManager = StrategyManager(
address(
new TransparentUpgradeableProxy(
address(strategyManagerImplementation),
- address(proxyAdmin),
+ address(eigenLayerProxyAdmin),
abi.encodeWithSelector(
StrategyManager.initialize.selector,
initialOwner,
initialOwner,
pauserRegistry,
- 0/*initialPausedStatus*/,
- 0/*withdrawalDelayBlocks*/
+ 0 /*initialPausedStatus*/
)
)
)
);
dummyToken = new ERC20Mock();
- dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
- dummyStrat2 = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+ revertToken = new ERC20_SetTransferReverting_Mock(1000e18, address(this));
+ revertToken.setTransfersRevert(true);
+ dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+ dummyStrat2 = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+ dummyStrat3 = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
// whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](2);
- _strategy[0] = dummyStrat;
- _strategy[1] = dummyStrat2;
- for (uint256 i = 0; i < _strategy.length; ++i) {
+ cheats.prank(strategyManager.owner());
+ IStrategy[] memory _strategies = new IStrategy[](3);
+ _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(_strategy[i]);
+ emit StrategyAddedToDepositWhitelist(_strategies[i]);
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit UpdatedThirdPartyTransfersForbidden(_strategies[i], _thirdPartyTransfersForbiddenValues[i]);
}
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
+ strategyManager.addStrategiesToDepositWhitelist(_strategies, _thirdPartyTransfersForbiddenValues);
- beaconChainETHStrategy = strategyManager.beaconChainETHStrategy();
-
- // excude the zero address, the proxyAdmin and the eigenPodManagerMock from fuzzed inputs
- addressIsExcludedFromFuzzedInputs[address(0)] = true;
- addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true;
- addressIsExcludedFromFuzzedInputs[address(eigenPodManagerMock)] = true;
+ addressIsExcludedFromFuzzedInputs[address(reenterer)] = true;
}
- function testCannotReinitialize() public {
- cheats.expectRevert(bytes("Initializable: contract is already initialized"));
- strategyManager.initialize(initialOwner, initialOwner, pauserRegistry, 0, 0);
- }
+ // INTERNAL / HELPER FUNCTIONS
+ function _deployNewStrategy(
+ IERC20 _token,
+ IStrategyManager _strategyManager,
+ IPauserRegistry _pauserRegistry,
+ address admin
+ ) public returns (StrategyBase) {
+ StrategyBase newStrategy = new StrategyBase(_strategyManager);
+ newStrategy = StrategyBase(address(new TransparentUpgradeableProxy(address(newStrategy), address(admin), "")));
+ newStrategy.initialize(_token, _pauserRegistry);
+ return newStrategy;
+ }
+
+ function _depositIntoStrategySuccessfully(
+ IStrategy strategy,
+ address staker,
+ uint256 amount
+ ) internal filterFuzzedAddressInputs(staker) {
+ IERC20 token = dummyToken;
- function testDepositBeaconChainETHSuccessfully(address staker, uint256 amount) public filterFuzzedAddressInputs(staker) {
// filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
cheats.assume(amount != 0);
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, beaconChainETHStrategy);
+ // filter out zero address because the mock ERC20 we are using will revert on using it
+ cheats.assume(staker != address(0));
+ // sanity check / filter
+ cheats.assume(amount <= token.balanceOf(address(this)));
- cheats.startPrank(address(strategyManager.eigenPodManager()));
- strategyManager.depositBeaconChainETH(staker, amount);
- cheats.stopPrank();
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, beaconChainETHStrategy);
- require(sharesAfter == sharesBefore + amount, "sharesAfter != sharesBefore + amount");
- }
+ // needed for expecting an event with the right parameters
+ uint256 expectedShares = amount;
- function testDepositBeaconChainETHFailsWhenNotCalledByEigenPodManager(address improperCaller) public filterFuzzedAddressInputs(improperCaller) {
- uint256 amount = 1e18;
- address staker = address(this);
+ cheats.prank(staker);
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit Deposit(staker, token, strategy, expectedShares);
+ uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.expectRevert(bytes("StrategyManager.onlyEigenPodManager: not the eigenPodManager"));
- cheats.startPrank(address(improperCaller));
- strategyManager.depositBeaconChainETH(staker, amount);
- cheats.stopPrank();
- }
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
+ uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
- function testDepositBeaconChainETHFailsWhenDepositsPaused() public {
- uint256 amount = 1e18;
- address staker = address(this);
+ assertEq(sharesAfter, sharesBefore + shares, "sharesAfter != sharesBefore + shares");
+ if (sharesBefore == 0) {
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore + 1,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1"
+ );
+ assertEq(
+ address(strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1)),
+ address(strategy),
+ "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy"
+ );
+ }
+ }
- // pause deposits
- cheats.startPrank(pauser);
- strategyManager.pause(1);
- cheats.stopPrank();
+ // internal function for de-duping code. expects success if `expectedRevertMessage` is empty and expiry is valid.
+ function _depositIntoStrategyWithSignature(
+ address staker,
+ uint256 amount,
+ uint256 expiry,
+ string memory expectedRevertMessage
+ ) internal returns (bytes memory) {
+ // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
+ cheats.assume(amount != 0);
+ // sanity check / filter
+ cheats.assume(amount <= dummyToken.balanceOf(address(this)));
- cheats.expectRevert(bytes("Pausable: index is paused"));
- cheats.startPrank(address(eigenPodManagerMock));
- strategyManager.depositBeaconChainETH(staker, amount);
- cheats.stopPrank();
- }
+ uint256 nonceBefore = strategyManager.nonces(staker);
+ bytes memory signature;
- function testDepositBeaconChainETHFailsWhenStakerFrozen() public {
- uint256 amount = 1e18;
- address staker = address(this);
+ {
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, dummyStrat, dummyToken, amount, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
- // freeze the staker
- slasherMock.freezeOperator(staker);
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
- cheats.startPrank(address(eigenPodManagerMock));
- strategyManager.depositBeaconChainETH(staker, amount);
- cheats.stopPrank();
- }
+ signature = abi.encodePacked(r, s, v);
+ }
- function testDepositBeaconChainETHFailsWhenReentering() public {
- uint256 amount = 1e18;
- address staker = address(this);
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat);
- _beaconChainReentrancyTestsSetup();
+ bool expectedRevertMessageIsempty;
+ {
+ string memory emptyString;
+ expectedRevertMessageIsempty =
+ keccak256(abi.encodePacked(expectedRevertMessage)) == keccak256(abi.encodePacked(emptyString));
+ }
+ if (!expectedRevertMessageIsempty) {
+ cheats.expectRevert(bytes(expectedRevertMessage));
+ } else if (expiry < block.timestamp) {
+ cheats.expectRevert("StrategyManager.depositIntoStrategyWithSignature: signature expired");
+ } else {
+ // needed for expecting an event with the right parameters
+ uint256 expectedShares = amount;
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit Deposit(staker, dummyToken, dummyStrat, expectedShares);
+ }
+ uint256 shares = strategyManager.depositIntoStrategyWithSignature(
+ dummyStrat,
+ dummyToken,
+ amount,
+ staker,
+ expiry,
+ signature
+ );
- address targetToUse = address(strategyManager);
- uint256 msgValueToUse = 0;
- bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.depositBeaconChainETH.selector, staker, amount);
- reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat);
+ uint256 nonceAfter = strategyManager.nonces(staker);
- cheats.startPrank(address(reenterer));
- strategyManager.depositBeaconChainETH(staker, amount);
- cheats.stopPrank();
+ if (expiry >= block.timestamp && expectedRevertMessageIsempty) {
+ assertEq(sharesAfter, sharesBefore + shares, "sharesAfter != sharesBefore + shares");
+ assertEq(nonceAfter, nonceBefore + 1, "nonceAfter != nonceBefore + 1");
+ }
+ return signature;
}
- function testRecordOvercommittedBeaconChainETHSuccessfully(uint256 amount_1, uint256 amount_2) public {
- // zero inputs will revert, and cannot reduce more than full amount
- cheats.assume(amount_2 <= amount_1 && amount_1 != 0 && amount_2 != 0);
+ /**
+ * @notice internal function to help check if a strategy is part of list of deposited strategies for a staker
+ * Used to check if removed correctly after withdrawing all shares for a given strategy
+ */
+ function _isDepositedStrategy(address staker, IStrategy strategy) internal view returns (bool) {
+ uint256 stakerStrategyListLength = strategyManager.stakerStrategyListLength(staker);
+ for (uint256 i = 0; i < stakerStrategyListLength; ++i) {
+ if (strategyManager.stakerStrategyList(staker, i) == strategy) {
+ return true;
+ }
+ }
+ return false;
+ }
- address overcommittedPodOwner = address(this);
- uint256 beaconChainETHStrategyIndex = 0;
- testDepositBeaconChainETHSuccessfully(overcommittedPodOwner, amount_1);
+ /**
+ * @notice Deploys numberOfStrategiesToAdd new strategies and adds them to the whitelist
+ */
+ 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);
+ strategyArray[i] = _strategy;
+ assertFalse(strategyManager.strategyIsWhitelistedForDeposit(_strategy), "strategy improperly whitelisted?");
+ }
- uint256 sharesBefore = strategyManager.stakerStrategyShares(overcommittedPodOwner, beaconChainETHStrategy);
+ cheats.prank(strategyManager.strategyWhitelister());
+ for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit StrategyAddedToDepositWhitelist(strategyArray[i]);
+ }
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
- cheats.startPrank(address(eigenPodManagerMock));
- strategyManager.recordOvercommittedBeaconChainETH(overcommittedPodOwner, beaconChainETHStrategyIndex, amount_2);
- cheats.stopPrank();
+ for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
+ assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategyArray[i]), "strategy not whitelisted");
+ }
- uint256 sharesAfter = strategyManager.stakerStrategyShares(overcommittedPodOwner, beaconChainETHStrategy);
- require(sharesAfter == sharesBefore - amount_2, "sharesAfter != sharesBefore - amount");
+ return strategyArray;
}
+}
- function testRecordOvercommittedBeaconChainETHFailsWhenNotCalledByEigenPodManager(address improperCaller) public filterFuzzedAddressInputs(improperCaller) {
- uint256 amount = 1e18;
- address staker = address(this);
- uint256 beaconChainETHStrategyIndex = 0;
-
- testDepositBeaconChainETHSuccessfully(staker, amount);
-
- cheats.expectRevert(bytes("StrategyManager.onlyEigenPodManager: not the eigenPodManager"));
- cheats.startPrank(address(improperCaller));
- strategyManager.recordOvercommittedBeaconChainETH(staker, beaconChainETHStrategyIndex, amount);
- cheats.stopPrank();
+contract StrategyManagerUnitTests_initialize is StrategyManagerUnitTests {
+ function test_CannotReinitialize() public {
+ cheats.expectRevert("Initializable: contract is already initialized");
+ strategyManager.initialize(initialOwner, initialOwner, pauserRegistry, 0);
}
- function testRecordOvercommittedBeaconChainETHFailsWhenReentering() public {
- uint256 amount = 1e18;
- address staker = address(this);
- uint256 beaconChainETHStrategyIndex = 0;
-
- _beaconChainReentrancyTestsSetup();
-
- testDepositBeaconChainETHSuccessfully(staker, amount);
-
- address targetToUse = address(strategyManager);
- uint256 msgValueToUse = 0;
- bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.recordOvercommittedBeaconChainETH.selector, staker, beaconChainETHStrategyIndex, amount);
- reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
-
- cheats.startPrank(address(reenterer));
- strategyManager.recordOvercommittedBeaconChainETH(staker, beaconChainETHStrategyIndex, amount);
- cheats.stopPrank();
+ function test_InitializedStorageProperly() public {
+ assertEq(strategyManager.owner(), initialOwner, "strategyManager.owner() != initialOwner");
+ assertEq(
+ strategyManager.strategyWhitelister(),
+ initialOwner,
+ "strategyManager.strategyWhitelister() != initialOwner"
+ );
+ assertEq(
+ address(strategyManager.pauserRegistry()),
+ address(pauserRegistry),
+ "strategyManager.pauserRegistry() != pauserRegistry"
+ );
}
+}
- function testDepositIntoStrategySuccessfully(address staker, uint256 amount) public filterFuzzedAddressInputs(staker) {
- IERC20 token = dummyToken;
+contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTests {
+ function testFuzz_depositIntoStrategySuccessfully(
+ address staker,
+ uint256 amount
+ ) public filterFuzzedAddressInputs(staker) {
+ IERC20 token = dummyToken;
IStrategy strategy = dummyStrat;
// filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
@@ -307,54 +286,54 @@ contract StrategyManagerUnitTests is Test, Utils {
// needed for expecting an event with the right parameters
uint256 expectedShares = strategy.underlyingToShares(amount);
- cheats.startPrank(staker);
+ cheats.prank(staker);
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit Deposit(staker, token, strategy, expectedShares);
uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
- require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares");
+ assertEq(sharesAfter, sharesBefore + shares, "sharesAfter != sharesBefore + shares");
if (sharesBefore == 0) {
- require(stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1");
- require(strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) == strategy,
- "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy");
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore + 1,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1"
+ );
+ assertEq(
+ address(strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1)),
+ address(strategy),
+ "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy"
+ );
+ } else {
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore"
+ );
}
}
- function testDepositIntoStrategySuccessfullyTwice() public {
+ function test_DepositWhenStrategySharesExist() public {
address staker = address(this);
uint256 amount = 1e18;
- testDepositIntoStrategySuccessfully(staker, amount);
- testDepositIntoStrategySuccessfully(staker, amount);
+ testFuzz_depositIntoStrategySuccessfully(staker, amount);
+ testFuzz_depositIntoStrategySuccessfully(staker, amount);
}
- function testDepositIntoStrategyFailsWhenDepositsPaused() public {
+ function test_Revert_WhenDepositsPaused() public {
uint256 amount = 1e18;
// pause deposits
- cheats.startPrank(pauser);
+ cheats.prank(pauser);
strategyManager.pause(1);
- cheats.stopPrank();
-
- cheats.expectRevert(bytes("Pausable: index is paused"));
- strategyManager.depositIntoStrategy(dummyStrat, dummyToken, amount);
- }
-
- function testDepositIntoStrategyFailsWhenStakerFrozen() public {
- uint256 amount = 1e18;
- address staker = address(this);
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
+ cheats.expectRevert("Pausable: index is paused");
strategyManager.depositIntoStrategy(dummyStrat, dummyToken, amount);
}
- function testDepositIntoStrategyFailsWhenReentering() public {
+ function test_Revert_WhenReentering() public {
uint256 amount = 1e18;
reenterer = new Reenterer();
@@ -362,93 +341,250 @@ contract StrategyManagerUnitTests is Test, Utils {
// 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));
address targetToUse = address(strategyManager);
uint256 msgValueToUse = 0;
- bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.depositIntoStrategy.selector, address(reenterer), dummyToken, amount);
+ bytes memory calldataToUse = abi.encodeWithSelector(
+ StrategyManager.depositIntoStrategy.selector,
+ address(reenterer),
+ dummyToken,
+ amount
+ );
reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
strategyManager.depositIntoStrategy(IStrategy(address(reenterer)), dummyToken, amount);
}
- function testDepositIntoStrategyWithSignatureSuccessfully(uint256 amount, uint256 expiry) public {
- // min shares must be minted on strategy
- cheats.assume(amount >= 1);
-
- address staker = cheats.addr(privateKey);
- // not expecting a revert, so input an empty string
- string memory expectedRevertMessage;
- _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage);
- }
+ function test_Revert_WhenTokenSafeTransferFromReverts() external {
+ // replace 'dummyStrat' with one that uses a reverting token
+ dummyToken = IERC20(address(new Reverter()));
+ dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
- function testDepositIntoStrategyWithSignatureReplay(uint256 amount, uint256 expiry) public {
- // min shares must be minted on strategy
- cheats.assume(amount >= 1);
- cheats.assume(expiry > block.timestamp);
+ // whitelist the strategy for deposit
+ cheats.startPrank(strategyManager.owner());
+ IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
- address staker = cheats.addr(privateKey);
- // not expecting a revert, so input an empty string
- bytes memory signature = _depositIntoStrategyWithSignature(staker, amount, expiry, "");
+ _strategy[0] = dummyStrat;
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
+ cheats.stopPrank();
- cheats.expectRevert(bytes("StrategyManager.depositIntoStrategyWithSignature: signature not from staker"));
- strategyManager.depositIntoStrategyWithSignature(dummyStrat, dummyToken, amount, staker, expiry, signature);
+ address staker = address(this);
+ IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+ IStrategy strategy = dummyStrat;
+ cheats.prank(staker);
+ cheats.expectRevert("Reverter: I am a contract that always reverts");
+ strategyManager.depositIntoStrategy(strategy, token, amount);
}
- // tries depositing using a signature and an EIP 1271 compliant wallet
- function testDepositIntoStrategyWithSignature_WithContractWallet_Successfully(uint256 amount, uint256 expiry) public {
- // min shares must be minted on strategy
- cheats.assume(amount >= 1);
-
- address staker = cheats.addr(privateKey);
+ function test_Revert_WhenTokenDoesNotExist() external {
+ // replace 'dummyStrat' with one that uses a non-existent token
+ dummyToken = IERC20(address(5678));
+ dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
- // deploy ERC1271WalletMock for staker to use
- cheats.startPrank(staker);
- ERC1271WalletMock wallet = new ERC1271WalletMock(staker);
+ // 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, _thirdPartyTransfersForbiddenValues);
cheats.stopPrank();
- staker = address(wallet);
- // not expecting a revert, so input an empty string
- string memory expectedRevertMessage;
- _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage);
+ address staker = address(this);
+ IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+ IStrategy strategy = dummyStrat;
+
+ cheats.prank(staker);
+ cheats.expectRevert("Address: call to non-contract");
+ strategyManager.depositIntoStrategy(strategy, token, amount);
}
- // tries depositing using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature
- function testDepositIntoStrategyWithSignature_WithContractWallet_BadSignature(uint256 amount) public {
- // min shares must be minted on strategy
- cheats.assume(amount >= 1);
+ function test_Revert_WhenStrategyDepositFunctionReverts() external {
+ // replace 'dummyStrat' with one that always reverts
+ dummyStrat = StrategyBase(address(new Reverter()));
- address staker = cheats.addr(privateKey);
- IStrategy strategy = dummyStrat;
+ // 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, _thirdPartyTransfersForbiddenValues);
+ cheats.stopPrank();
+
+ address staker = address(this);
IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+ IStrategy strategy = dummyStrat;
- // deploy ERC1271WalletMock for staker to use
- cheats.startPrank(staker);
- ERC1271WalletMock wallet = new ERC1271WalletMock(staker);
- cheats.stopPrank();
- staker = address(wallet);
+ cheats.prank(staker);
+ cheats.expectRevert("Reverter: I am a contract that always reverts");
+ strategyManager.depositIntoStrategy(strategy, token, amount);
+ }
- // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
- cheats.assume(amount != 0);
- // sanity check / filter
- cheats.assume(amount <= token.balanceOf(address(this)));
+ function test_Revert_WhenStrategyDoesNotExist() external {
+ // replace 'dummyStrat' with one that does not exist
+ dummyStrat = StrategyBase(address(5678));
- uint256 nonceBefore = strategyManager.nonces(staker);
- uint256 expiry = type(uint256).max;
- bytes memory signature;
+ // whitelist the strategy for deposit
+ cheats.startPrank(strategyManager.owner());
+ IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
- {
- bytes32 structHash = keccak256(abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.DOMAIN_SEPARATOR(), structHash));
+ _strategy[0] = dummyStrat;
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
+ cheats.stopPrank();
+
+ address staker = address(this);
+ IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+ IStrategy strategy = dummyStrat;
+
+ cheats.prank(staker);
+ cheats.expectRevert();
+ strategyManager.depositIntoStrategy(strategy, token, amount);
+ }
+
+ function test_Revert_WhenStrategyNotWhitelisted() external {
+ // replace 'dummyStrat' with one that is not whitelisted
+ dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+
+ address staker = address(this);
+ IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+ IStrategy strategy = dummyStrat;
+
+ cheats.prank(staker);
+ cheats.expectRevert("StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted");
+ strategyManager.depositIntoStrategy(strategy, token, amount);
+ }
+
+ function test_addShares_Revert_WhenSharesIsZero() external {
+ // replace dummyStrat with Reenterer contract
+ reenterer = new Reenterer();
+ dummyStrat = StrategyBase(address(reenterer));
+
+ // 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, _thirdPartyTransfersForbiddenValues);
+ cheats.stopPrank();
+
+ address staker = address(this);
+ IStrategy strategy = dummyStrat;
+ IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+
+ reenterer.prepareReturnData(abi.encode(uint256(0)));
+
+ cheats.prank(staker);
+ cheats.expectRevert("StrategyManager._addShares: shares should not be zero!");
+ strategyManager.depositIntoStrategy(strategy, token, amount);
+ }
+}
+
+contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyManagerUnitTests {
+ function test_Revert_WhenSignatureInvalid() public {
+ address staker = cheats.addr(privateKey);
+ IStrategy strategy = dummyStrat;
+ IERC20 token = dummyToken;
+ uint256 amount = 1e18;
+
+ uint256 nonceBefore = strategyManager.nonces(staker);
+ uint256 expiry = block.timestamp;
+ bytes memory signature;
+
+ {
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
+
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
+
+ signature = abi.encodePacked(r, s, v);
+ }
+
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer");
+ // call with `notStaker` as input instead of `staker` address
+ address notStaker = address(3333);
+ strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, notStaker, expiry, signature);
+
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
+ uint256 nonceAfter = strategyManager.nonces(staker);
+
+ assertEq(sharesAfter, sharesBefore, "sharesAfter != sharesBefore");
+ assertEq(nonceAfter, nonceBefore, "nonceAfter != nonceBefore");
+ }
+
+ function testFuzz_DepositSuccessfully(uint256 amount, uint256 expiry) public {
+ // min shares must be minted on strategy
+ cheats.assume(amount >= 1);
+
+ address staker = cheats.addr(privateKey);
+ // not expecting a revert, so input an empty string
+ string memory expectedRevertMessage;
+ _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage);
+ }
+
+ function testFuzz_Revert_SignatureReplay(uint256 amount, uint256 expiry) public {
+ // min shares must be minted on strategy
+ cheats.assume(amount >= 1);
+ cheats.assume(expiry > block.timestamp);
+
+ address staker = cheats.addr(privateKey);
+ // not expecting a revert, so input an empty string
+ bytes memory signature = _depositIntoStrategyWithSignature(staker, amount, expiry, "");
+
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer");
+ strategyManager.depositIntoStrategyWithSignature(dummyStrat, dummyToken, amount, staker, expiry, signature);
+ }
+
+ // tries depositing using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature
+ function testFuzz_Revert_WithContractWallet_BadSignature(uint256 amount) public {
+ // min shares must be minted on strategy
+ cheats.assume(amount >= 1);
+
+ address staker = cheats.addr(privateKey);
+ IStrategy strategy = dummyStrat;
+ IERC20 token = dummyToken;
+
+ // deploy ERC1271WalletMock for staker to use
+ cheats.prank(staker);
+ ERC1271WalletMock wallet = new ERC1271WalletMock(staker);
+ staker = address(wallet);
+
+ // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
+ cheats.assume(amount != 0);
+ // sanity check / filter
+ cheats.assume(amount <= token.balanceOf(address(this)));
+
+ uint256 nonceBefore = strategyManager.nonces(staker);
+ uint256 expiry = type(uint256).max;
+ bytes memory signature;
+
+ {
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
(uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
// mess up the signature by flipping v's parity
@@ -457,12 +593,17 @@ contract StrategyManagerUnitTests is Test, Utils {
signature = abi.encodePacked(r, s, v);
}
- cheats.expectRevert(bytes("StrategyManager.depositIntoStrategyWithSignature: ERC1271 signature verification failed"));
+ cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed");
strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature);
}
// tries depositing using a wallet that does not comply with EIP 1271
- function testDepositIntoStrategyWithSignature_WithContractWallet_NonconformingWallet(uint256 amount, uint8 v, bytes32 r, bytes32 s) public {
+ function testFuzz_Revert_WithContractWallet_NonconformingWallet(
+ uint256 amount,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) public {
// min shares must be minted on strategy
cheats.assume(amount >= 1);
@@ -471,9 +612,8 @@ contract StrategyManagerUnitTests is Test, Utils {
IERC20 token = dummyToken;
// deploy ERC1271WalletMock for staker to use
- cheats.startPrank(staker);
+ cheats.prank(staker);
ERC1271MaliciousMock wallet = new ERC1271MaliciousMock();
- cheats.stopPrank();
staker = address(wallet);
// filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
@@ -488,65 +628,78 @@ contract StrategyManagerUnitTests is Test, Utils {
strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature);
}
- function testDepositIntoStrategyWithSignatureFailsWhenDepositsPaused() public {
- address staker = cheats.addr(privateKey);
-
- // pause deposits
- cheats.startPrank(pauser);
- strategyManager.pause(1);
- cheats.stopPrank();
-
- // not expecting a revert, so input an empty string
- string memory expectedRevertMessage = "Pausable: index is paused";
- _depositIntoStrategyWithSignature(staker, 1e18, type(uint256).max, expectedRevertMessage);
- }
-
- function testDepositIntoStrategyWithSignatureFailsWhenStakerFrozen() public {
+ // Tries depositing without token approval and transfer fails. deposit function should also revert
+ function test_Revert_WithContractWallet_TokenTransferFails() external {
address staker = cheats.addr(privateKey);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
uint256 amount = 1e18;
-
uint256 nonceBefore = strategyManager.nonces(staker);
- uint256 expiry = type(uint256).max;
+ uint256 expiry = block.timestamp + 100;
bytes memory signature;
{
- bytes32 structHash = keccak256(abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.DOMAIN_SEPARATOR(), structHash));
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, dummyStrat, revertToken, amount, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
(uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
signature = abi.encodePacked(r, s, v);
}
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ cheats.expectRevert("ERC20: insufficient allowance");
+ strategyManager.depositIntoStrategyWithSignature(dummyStrat, revertToken, amount, staker, expiry, signature);
+ }
- // freeze the staker
- slasherMock.freezeOperator(staker);
+ // tries depositing using a signature and an EIP 1271 compliant wallet
+ function testFuzz_WithContractWallet_Successfully(uint256 amount, uint256 expiry) public {
+ // min shares must be minted on strategy
+ cheats.assume(amount >= 1);
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
- strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature);
+ address staker = cheats.addr(privateKey);
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 nonceAfter = strategyManager.nonces(staker);
+ // deploy ERC1271WalletMock for staker to use
+ cheats.prank(staker);
+ ERC1271WalletMock wallet = new ERC1271WalletMock(staker);
+ staker = address(wallet);
+
+ // not expecting a revert, so input an empty string
+ string memory expectedRevertMessage;
+ _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage);
+ }
+
+ function test_Revert_WhenDepositsPaused() public {
+ address staker = cheats.addr(privateKey);
+
+ // pause deposits
+ cheats.prank(pauser);
+ strategyManager.pause(1);
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(nonceAfter == nonceBefore, "nonceAfter != nonceBefore");
+ string memory expectedRevertMessage = "Pausable: index is paused";
+ _depositIntoStrategyWithSignature(staker, 1e18, type(uint256).max, expectedRevertMessage);
}
- function testDepositIntoStrategyWithSignatureFailsWhenReentering() public {
+ /**
+ * @notice reenterer contract which is configured as the strategy contract
+ * is configured to call depositIntoStrategy after reenterer.deposit() is called from the
+ * depositIntoStrategyWithSignature() is called from the StrategyManager. Situation is not likely to occur given
+ * the strategy has to be whitelisted but it at least protects from reentrant attacks
+ */
+ function test_Revert_WhenReentering() public {
reenterer = new Reenterer();
// 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);
@@ -559,36 +712,34 @@ contract StrategyManagerUnitTests is Test, Utils {
bytes memory signature;
{
- bytes32 structHash = keccak256(abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.DOMAIN_SEPARATOR(), structHash));
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
(uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
signature = abi.encodePacked(r, s, v);
}
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
-
uint256 shareAmountToReturn = amount;
reenterer.prepareReturnData(abi.encode(shareAmountToReturn));
{
address targetToUse = address(strategyManager);
uint256 msgValueToUse = 0;
- bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.depositIntoStrategy.selector, address(reenterer), dummyToken, amount);
+ bytes memory calldataToUse = abi.encodeWithSelector(
+ StrategyManager.depositIntoStrategy.selector,
+ address(reenterer),
+ dummyToken,
+ amount
+ );
reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
}
-
strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 nonceAfter = strategyManager.nonces(staker);
-
- require(sharesAfter == sharesBefore + shareAmountToReturn, "sharesAfter != sharesBefore + shareAmountToReturn");
- require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
}
- function testDepositIntoStrategyWithSignatureFailsWhenSignatureExpired() public {
+ function test_Revert_WhenSignatureExpired() public {
address staker = cheats.addr(privateKey);
IStrategy strategy = dummyStrat;
IERC20 token = dummyToken;
@@ -601,8 +752,10 @@ contract StrategyManagerUnitTests is Test, Utils {
bytes memory signature;
{
- bytes32 structHash = keccak256(abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.DOMAIN_SEPARATOR(), structHash));
+ bytes32 structHash = keccak256(
+ abi.encode(strategyManager.DEPOSIT_TYPEHASH(), staker, strategy, token, amount, nonceBefore, expiry)
+ );
+ bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash));
(uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
@@ -611,1845 +764,541 @@ contract StrategyManagerUnitTests is Test, Utils {
uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- cheats.expectRevert(bytes("StrategyManager.depositIntoStrategyWithSignature: signature expired"));
+ cheats.expectRevert("StrategyManager.depositIntoStrategyWithSignature: signature expired");
strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature);
uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
uint256 nonceAfter = strategyManager.nonces(staker);
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(nonceAfter == nonceBefore, "nonceAfter != nonceBefore");
+ assertEq(sharesAfter, sharesBefore, "sharesAfter != sharesBefore");
+ assertEq(nonceAfter, nonceBefore, "nonceAfter != nonceBefore");
}
- function testDepositIntoStrategyWithSignatureFailsWhenSignatureInvalid() public {
+ function test_Revert_WhenStrategyNotWhitelisted() external {
+ // replace 'dummyStrat' with one that is not whitelisted
+ dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+ dummyToken = dummyStrat.underlyingToken();
address staker = cheats.addr(privateKey);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
uint256 amount = 1e18;
- uint256 nonceBefore = strategyManager.nonces(staker);
- uint256 expiry = block.timestamp;
- bytes memory signature;
+ string
+ 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);
- {
- bytes32 structHash = keccak256(abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.DOMAIN_SEPARATOR(), structHash));
+ cheats.prank(strategyManager.strategyWhitelister());
+ strategyManager.setThirdPartyTransfersForbidden(dummyStrat, true);
- (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
+ 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);
+ }
+}
- signature = abi.encodePacked(r, s, v);
- }
+contract StrategyManagerUnitTests_removeShares is StrategyManagerUnitTests {
+ /**
+ * @notice Should revert if not called by DelegationManager
+ */
+ function test_Revert_DelegationManagerModifier() external {
+ DelegationManagerMock invalidDelegationManager = new DelegationManagerMock();
+ cheats.expectRevert("StrategyManager.onlyDelegationManager: not the DelegationManager");
+ invalidDelegationManager.removeShares(strategyManager, address(this), dummyStrat, 1);
+ }
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ /**
+ * @notice deposits a single strategy and tests removeShares() function reverts when sharesAmount is 0
+ */
+ function testFuzz_Revert_ZeroShares(
+ address staker,
+ uint256 depositAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply());
+ IStrategy strategy = dummyStrat;
+ _depositIntoStrategySuccessfully(strategy, staker, depositAmount);
+ cheats.expectRevert("StrategyManager._removeShares: shareAmount should not be zero!");
+ delegationManagerMock.removeShares(strategyManager, staker, strategy, 0);
+ }
- cheats.expectRevert(bytes("StrategyManager.depositIntoStrategyWithSignature: signature not from staker"));
- // call with `notStaker` as input instead of `staker` address
- address notStaker = address(3333);
- strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, notStaker, expiry, signature);
+ /**
+ * @notice deposits a single strategy and tests removeShares() function reverts when sharesAmount is
+ * higher than depositAmount
+ */
+ function testFuzz_Revert_ShareAmountTooHigh(
+ address staker,
+ uint256 depositAmount,
+ uint256 removeSharesAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply());
+ cheats.assume(removeSharesAmount > depositAmount);
+ IStrategy strategy = dummyStrat;
+ _depositIntoStrategySuccessfully(strategy, staker, depositAmount);
+ cheats.expectRevert("StrategyManager._removeShares: shareAmount too high");
+ delegationManagerMock.removeShares(strategyManager, staker, strategy, removeSharesAmount);
+ }
+ /**
+ * @notice deposit single strategy and removeShares() for less than the deposited amount
+ * Shares should be updated correctly with stakerStrategyListLength unchanged
+ */
+ function testFuzz_RemoveSharesLessThanDeposit(
+ address staker,
+ uint256 depositAmount,
+ uint256 removeSharesAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply());
+ cheats.assume(removeSharesAmount > 0 && removeSharesAmount < depositAmount);
+ IStrategy strategy = dummyStrat;
+ _depositIntoStrategySuccessfully(strategy, staker, depositAmount);
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ delegationManagerMock.removeShares(strategyManager, staker, strategy, removeSharesAmount);
+ uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 nonceAfter = strategyManager.nonces(staker);
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(nonceAfter == nonceBefore, "nonceAfter != nonceBefore");
+ assertEq(sharesBefore, sharesAfter + removeSharesAmount, "Remove incorrect amount of shares");
+ assertEq(
+ stakerStrategyListLengthBefore,
+ stakerStrategyListLengthAfter,
+ "stakerStrategyListLength shouldn't have changed"
+ );
}
- function testUndelegate() public {
- strategyManager.undelegate();
+ /**
+ * @notice testing removeShares()
+ * deposits 1 strategy and tests it is removed from staker strategy list after removing all shares
+ */
+ function testFuzz_RemovesStakerStrategyListSingleStrat(
+ address staker,
+ uint256 sharesAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ cheats.assume(sharesAmount > 0 && sharesAmount < dummyToken.totalSupply());
+ IStrategy strategy = dummyStrat;
+ _depositIntoStrategySuccessfully(strategy, staker, sharesAmount);
+
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
+ assertEq(sharesBefore, sharesAmount, "Staker has not deposited amount into strategy");
+
+ delegationManagerMock.removeShares(strategyManager, staker, strategy, sharesAmount);
+ uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore - 1,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore - 1"
+ );
+ assertEq(sharesAfter, 0, "sharesAfter != 0");
+ assertFalse(_isDepositedStrategy(staker, strategy), "strategy should not be part of staker strategy list");
}
- function testUndelegateRevertsWithActiveDeposits() public {
- address staker = address(this);
- uint256 amount = 1e18;
+ /**
+ * @notice testing removeShares() function with 3 strategies deposited.
+ * Randomly selects one of the 3 strategies to be fully removed from staker strategy list.
+ * Only callable by DelegationManager
+ */
+ function testFuzz_RemovesStakerStrategyListMultipleStrat(
+ address staker,
+ uint256[3] memory amounts,
+ uint8 randStrategy
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ IStrategy[] memory strategies = new IStrategy[](3);
+ strategies[0] = dummyStrat;
+ strategies[1] = dummyStrat2;
+ strategies[2] = dummyStrat3;
+ for (uint256 i = 0; i < 3; ++i) {
+ amounts[i] = bound(amounts[i], 1, dummyToken.totalSupply() - 1);
+ _depositIntoStrategySuccessfully(strategies[i], staker, amounts[i]);
+ }
+ IStrategy removeStrategy = strategies[randStrategy % 3];
+ uint256 removeAmount = amounts[randStrategy % 3];
- testDepositIntoStrategySuccessfully(staker, amount);
- require(strategyManager.stakerStrategyListLength(staker) != 0, "test broken in some way, length shouldn't be 0");
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
+ uint256[] memory sharesBefore = new uint256[](3);
+ for (uint256 i = 0; i < 3; ++i) {
+ sharesBefore[i] = strategyManager.stakerStrategyShares(staker, strategies[i]);
+ assertEq(sharesBefore[i], amounts[i], "Staker has not deposited amount into strategy");
+ assertTrue(_isDepositedStrategy(staker, strategies[i]), "strategy should be deposited");
+ }
- cheats.expectRevert(bytes("StrategyManager._undelegate: depositor has active deposits"));
- strategyManager.undelegate();
+ delegationManagerMock.removeShares(strategyManager, staker, removeStrategy, removeAmount);
+ uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, removeStrategy);
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore - 1,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore - 1"
+ );
+ assertEq(sharesAfter, 0, "sharesAfter != 0");
+ assertFalse(
+ _isDepositedStrategy(staker, removeStrategy),
+ "strategy should not be part of staker strategy list"
+ );
}
- // fuzzed input amountGwei is sized-down, since it must be in GWEI and gets sized-up to be WEI
- function testQueueWithdrawalBeaconChainETHToSelf(uint128 amountGwei)
- public returns (IStrategyManager.QueuedWithdrawal memory, bytes32 /*withdrawalRoot*/)
- {
- // scale fuzzed amount up to be a whole amount of GWEI
- uint256 amount = uint256(amountGwei) * 1e9;
+ /**
+ * @notice testing removeShares() function with 3 strategies deposited.
+ * Removing Shares could result in removing from staker strategy list if depositAmounts[i] == sharesAmounts[i].
+ * Only callable by DelegationManager
+ */
+ function testFuzz_RemoveShares(uint256[3] memory depositAmounts, uint256[3] memory sharesAmounts) external {
address staker = address(this);
- address withdrawer = staker;
- IStrategy strategy = beaconChainETHStrategy;
- IERC20 token;
+ IStrategy[] memory strategies = new IStrategy[](3);
+ strategies[0] = dummyStrat;
+ strategies[1] = dummyStrat2;
+ strategies[2] = dummyStrat3;
+ uint256[] memory sharesBefore = new uint256[](3);
+ for (uint256 i = 0; i < 3; ++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");
+ assertTrue(_isDepositedStrategy(staker, strategies[i]), "strategy should be deposited");
+ }
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
- testDepositBeaconChainETHSuccessfully(staker, amount);
+ uint256 numPoppedStrategies = 0;
+ uint256[] memory sharesAfter = new uint256[](3);
+ for (uint256 i = 0; i < 3; ++i) {
+ delegationManagerMock.removeShares(strategyManager, staker, strategies[i], sharesAmounts[i]);
+ }
- bool undelegateIfPossible = false;
+ for (uint256 i = 0; i < 3; ++i) {
+ sharesAfter[i] = strategyManager.stakerStrategyShares(staker, strategies[i]);
+ if (sharesAmounts[i] == depositAmounts[i]) {
+ ++numPoppedStrategies;
+ assertFalse(
+ _isDepositedStrategy(staker, strategies[i]),
+ "strategy should not be part of staker strategy list"
+ );
+ assertEq(sharesAfter[i], 0, "sharesAfter != 0");
+ } else {
+ assertTrue(
+ _isDepositedStrategy(staker, strategies[i]),
+ "strategy should be part of staker strategy list"
+ );
+ assertEq(
+ sharesAfter[i],
+ sharesBefore[i] - sharesAmounts[i],
+ "sharesAfter != sharesBefore - sharesAmounts"
+ );
+ }
+ }
+ assertEq(
+ stakerStrategyListLengthBefore - numPoppedStrategies,
+ strategyManager.stakerStrategyListLength(staker),
+ "stakerStrategyListLengthBefore - numPoppedStrategies != strategyManager.stakerStrategyListLength(staker)"
+ );
+ }
+}
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) =
- _setUpQueuedWithdrawalStructSingleStrat(staker, withdrawer, token, strategy, amount);
+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), dummyToken, dummyStrat, 1);
+ }
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 nonceBefore = strategyManager.numWithdrawalsQueued(staker);
+ function testFuzz_Revert_StakerZeroAddress(uint256 amount) external {
+ cheats.expectRevert("StrategyManager._addShares: staker cannot be zero address");
+ delegationManagerMock.addShares(strategyManager, address(0), dummyToken, dummyStrat, amount);
+ }
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
+ 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, dummyToken, dummyStrat, 0);
+ }
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
+ function testFuzz_AppendsStakerStrategyList(
+ address staker,
+ uint256 amount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0) && amount != 0);
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat);
+ assertEq(sharesBefore, 0, "Staker has already deposited into this strategy");
+ assertFalse(_isDepositedStrategy(staker, dummyStrat), "strategy should not be deposited");
- {
- for (uint256 i = 0; i < queuedWithdrawal.strategies.length; ++i) {
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit ShareWithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.strategies[i],
- queuedWithdrawal.shares[i]
- );
- }
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- queuedWithdrawal.delegatedAddress,
- withdrawalRoot
- );
- }
-
- strategyManager.queueWithdrawal(strategyIndexes, queuedWithdrawal.strategies, queuedWithdrawal.shares, withdrawer, undelegateIfPossible);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 nonceAfter = strategyManager.numWithdrawalsQueued(staker);
-
- require(strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is false!");
- require(sharesAfter == sharesBefore - amount, "sharesAfter != sharesBefore - amount");
- require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
-
- return (queuedWithdrawal, withdrawalRoot);
- }
-
- function testQueueWithdrawalBeaconChainETHToDifferentAddress(address withdrawer) external filterFuzzedAddressInputs(withdrawer) {
- // filtering for test flakiness
- cheats.assume(withdrawer != address(this));
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- uint256[] memory strategyIndexes = new uint256[](1);
- bool undelegateIfPossible = false;
-
- {
- strategyArray[0] = strategyManager.beaconChainETHStrategy();
- shareAmounts[0] = REQUIRED_BALANCE_WEI;
- strategyIndexes[0] = 0;
- }
-
- cheats.expectRevert(bytes("StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH to a different address"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, withdrawer, undelegateIfPossible);
- }
-
- function testQueueWithdrawalMultipleStrategiesWithBeaconChain() external {
- testDepositIntoStrategySuccessfully(address(this), REQUIRED_BALANCE_WEI);
-
- IStrategy[] memory strategyArray = new IStrategy[](2);
- uint256[] memory shareAmounts = new uint256[](2);
- uint256[] memory strategyIndexes = new uint256[](2);
- bool undelegateIfPossible = false;
-
- {
- strategyArray[0] = strategyManager.beaconChainETHStrategy();
- shareAmounts[0] = REQUIRED_BALANCE_WEI;
- strategyIndexes[0] = 0;
- strategyArray[1] = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
- shareAmounts[1] = REQUIRED_BALANCE_WEI;
- strategyIndexes[1] = 1;
- }
-
- cheats.expectRevert(bytes("StrategyManager.queueWithdrawal: cannot queue a withdrawal including Beacon Chain ETH and other tokens"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, address(this), undelegateIfPossible);
-
- {
- strategyArray[0] = dummyStrat;
- shareAmounts[0] = 1;
- strategyIndexes[0] = 0;
- strategyArray[1] = strategyManager.beaconChainETHStrategy();
- shareAmounts[1] = REQUIRED_BALANCE_WEI;
- strategyIndexes[1] = 1;
- }
- cheats.expectRevert(bytes("StrategyManager.queueWithdrawal: cannot queue a withdrawal including Beacon Chain ETH and other tokens"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, address(this), undelegateIfPossible);
- }
-
- function testQueueWithdrawalBeaconChainEthNonWholeAmountGwei(uint256 nonWholeAmount) external {
- cheats.assume(nonWholeAmount % GWEI_TO_WEI != 0);
- IStrategy[] memory strategyArray = new IStrategy[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- uint256[] memory strategyIndexes = new uint256[](1);
- bool undelegateIfPossible = false;
-
- {
- strategyArray[0] = strategyManager.beaconChainETHStrategy();
- shareAmounts[0] = REQUIRED_BALANCE_WEI - 1243895959494;
- strategyIndexes[0] = 0;
- }
-
- cheats.expectRevert(bytes("StrategyManager.queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, address(this), undelegateIfPossible);
- }
-
- function testQueueWithdrawalMismatchedIndexAndStrategyArrayLength() external {
- IStrategy[] memory strategyArray = new IStrategy[](1);
- uint256[] memory shareAmounts = new uint256[](2);
- uint256[] memory strategyIndexes = new uint256[](1);
- bool undelegateIfPossible = false;
-
- {
- strategyArray[0] = strategyManager.beaconChainETHStrategy();
- shareAmounts[0] = 1;
- shareAmounts[1] = 1;
- strategyIndexes[0] = 0;
- }
-
- cheats.expectRevert(bytes("StrategyManager.queueWithdrawal: input length mismatch"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, address(this), undelegateIfPossible);
- }
-
- function testQueueWithdrawalWithZeroAddress() external {
- IStrategy[] memory strategyArray = new IStrategy[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- uint256[] memory strategyIndexes = new uint256[](1);
- bool undelegateIfPossible = false;
-
- cheats.expectRevert(bytes("StrategyManager.queueWithdrawal: cannot withdraw to zero address"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, address(0), undelegateIfPossible);
- }
-
- function testQueueWithdrawalWithFrozenAddress(address frozenAddress) external filterFuzzedAddressInputs(frozenAddress) {
- IStrategy[] memory strategyArray = new IStrategy[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- uint256[] memory strategyIndexes = new uint256[](1);
- bool undelegateIfPossible = false;
-
- slasherMock.freezeOperator(frozenAddress);
-
- cheats.startPrank(frozenAddress);
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
- strategyManager.queueWithdrawal(strategyIndexes, strategyArray, shareAmounts, address(0), undelegateIfPossible);
- cheats.stopPrank();
-
- }
-
- function testQueueWithdrawal_ToSelf_NotBeaconChainETH(uint256 depositAmount, uint256 withdrawalAmount, bool undelegateIfPossible) public
- returns (IStrategyManager.QueuedWithdrawal memory /* queuedWithdrawal */, IERC20[] memory /* tokensArray */, bytes32 /* withdrawalRoot */)
- {
- // filtering of fuzzed inputs
- cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
-
- // address staker = address(this);
- _tempStrategyStorage = dummyStrat;
- // IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully(/*staker*/ address(this), depositAmount);
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, bytes32 withdrawalRoot) =
- _setUpQueuedWithdrawalStructSingleStrat(/*staker*/ address(this), /*withdrawer*/ address(this), dummyToken, _tempStrategyStorage, withdrawalAmount);
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(/*staker*/ address(this), _tempStrategyStorage);
- uint256 nonceBefore = strategyManager.numWithdrawalsQueued(/*staker*/ address(this));
-
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
-
- {
- for (uint256 i = 0; i < queuedWithdrawal.strategies.length; ++i) {
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit ShareWithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.strategies[i],
- queuedWithdrawal.shares[i]
- );
- }
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- queuedWithdrawal.delegatedAddress,
- withdrawalRoot
- );
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
- strategyManager.queueWithdrawal(
- strategyIndexes,
- queuedWithdrawal.strategies,
- queuedWithdrawal.shares,
- /*withdrawer*/ address(this),
- undelegateIfPossible
- );
- }
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(/*staker*/ address(this), _tempStrategyStorage);
- uint256 nonceAfter = strategyManager.numWithdrawalsQueued(/*staker*/ address(this));
-
- require(strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is false!");
- require(sharesAfter == sharesBefore - withdrawalAmount, "sharesAfter != sharesBefore - withdrawalAmount");
- require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
-
- return (queuedWithdrawal, tokensArray, withdrawalRoot);
- }
-
- function testQueueWithdrawal_ToSelf_NotBeaconChainETHTwoStrategies(uint256 depositAmount, uint256 withdrawalAmount, bool undelegateIfPossible) public
- returns (IStrategyManager.QueuedWithdrawal memory /* queuedWithdrawal */, IERC20[] memory /* tokensArray */, bytes32 /* withdrawalRoot */)
- {
- // filtering of fuzzed inputs
- cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
-
-
- IStrategy[] memory strategies = new IStrategy[](2);
- strategies[0] = dummyStrat;
- strategies[1] = dummyStrat2;
-
- IERC20[] memory tokens = new IERC20[](2);
- tokens[0] = dummyToken;
- tokens[1] = dummyToken;
-
- uint256[] memory amounts = new uint256[](2);
- amounts[0] = withdrawalAmount;
- amounts[1] = withdrawalAmount;
-
- _depositIntoStrategySuccessfully(dummyStrat, /*staker*/ address(this), depositAmount);
- _depositIntoStrategySuccessfully(dummyStrat2, /*staker*/ address(this), depositAmount);
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) =
- _setUpQueuedWithdrawalStructSingleStrat_MultipleStrategies(/*staker*/ address(this), /*withdrawer*/ address(this), strategies, amounts);
-
- // uint256 sharesBefore = strategyManager.stakerStrategyShares(/*staker*/ address(this), strategies[0]) + strategyManager.stakerStrategyShares(/*staker*/ address(this), strategies[1]);
- // uint256 nonceBefore = strategyManager.numWithdrawalsQueued(/*staker*/ address(this));
-
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
-
- {
- for (uint256 i = 0; i < queuedWithdrawal.strategies.length; ++i) {
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit ShareWithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.strategies[i],
- queuedWithdrawal.shares[i]
- );
- }
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- queuedWithdrawal.delegatedAddress,
- withdrawalRoot
- );
-
- uint256[] memory strategyIndexes = new uint256[](2);
- strategyIndexes[0] = 0;
- strategyIndexes[1] = 0;
- strategyManager.queueWithdrawal(
- strategyIndexes,
- queuedWithdrawal.strategies,
- queuedWithdrawal.shares,
- /*withdrawer*/ address(this),
- undelegateIfPossible
- );
- }
-
- return (queuedWithdrawal, tokens, withdrawalRoot);
- }
-
- function testQueueWithdrawal_ToDifferentAddress_NotBeaconChainETH(address withdrawer, uint256 amount)
- external filterFuzzedAddressInputs(withdrawer)
- {
- address staker = address(this);
- _tempStrategyStorage = dummyStrat;
-
- testDepositIntoStrategySuccessfully(staker, amount);
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) =
- _setUpQueuedWithdrawalStructSingleStrat(staker, withdrawer, /*token*/ dummyToken, _tempStrategyStorage, amount);
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, _tempStrategyStorage);
- uint256 nonceBefore = strategyManager.numWithdrawalsQueued(staker);
-
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
-
- bool undelegateIfPossible = false;
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- {
- for (uint256 i = 0; i < queuedWithdrawal.strategies.length; ++i) {
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit ShareWithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.strategies[i],
- queuedWithdrawal.shares[i]
- );
- }
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalQueued(
- /*staker*/ address(this),
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- queuedWithdrawal.delegatedAddress,
- withdrawalRoot
- );
- }
-
- strategyManager.queueWithdrawal(strategyIndexes, queuedWithdrawal.strategies, queuedWithdrawal.shares, withdrawer, undelegateIfPossible);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, _tempStrategyStorage);
- uint256 nonceAfter = strategyManager.numWithdrawalsQueued(staker);
-
- require(strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is false!");
- require(sharesAfter == sharesBefore - amount, "sharesAfter != sharesBefore - amount");
- require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
- }
-
-
- // TODO: set up delegation for the following three tests and check afterwords
- function testQueueWithdrawal_WithdrawEverything_DontUndelegate(uint256 amount) external {
- // delegate to self
- delegationMock.delegateTo(address(this));
- require(delegationMock.isDelegated(address(this)), "delegation mock setup failed");
- bool undelegateIfPossible = false;
- // deposit and withdraw the same amount, don't undelegate
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(amount, amount, undelegateIfPossible);
- require(delegationMock.isDelegated(address(this)) == !undelegateIfPossible, "undelegation mock failed");
- }
-
- function testQueueWithdrawal_WithdrawEverything_DoUndelegate(uint256 amount) external {
- bool undelegateIfPossible = true;
- // deposit and withdraw the same amount, do undelegate if possible
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(amount, amount, undelegateIfPossible);
- require(delegationMock.isDelegated(address(this)) == !undelegateIfPossible, "undelegation mock failed");
- }
-
- function testQueueWithdrawal_DontWithdrawEverything_MarkUndelegateIfPossibleAsTrue(uint128 amount) external {
- bool undelegateIfPossible = true;
- // deposit and withdraw only half, do undelegate if possible
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(uint256(amount) * 2, amount, undelegateIfPossible);
- require(!delegationMock.isDelegated(address(this)), "undelegation mock failed");
- }
-
- function testQueueWithdrawalFailsWhenStakerFrozen() public {
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = depositAmount;
-
- testDepositIntoStrategySuccessfully(staker, depositAmount);
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) =
- _setUpQueuedWithdrawalStructSingleStrat(staker, /*withdrawer*/ staker, token, strategy, withdrawalAmount);
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 nonceBefore = strategyManager.numWithdrawalsQueued(staker);
-
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!");
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- // bool undelegateIfPossible = false;
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
- strategyManager.queueWithdrawal(strategyIndexes, queuedWithdrawal.strategies, queuedWithdrawal.shares, /*withdrawer*/ staker, /*undelegateIfPossible*/ false);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 nonceAfter = strategyManager.numWithdrawalsQueued(address(this));
-
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is true!");
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(nonceAfter == nonceBefore, "nonceAfter != nonceBefore");
- }
-
- function testCompleteQueuedWithdrawal_ReceiveAsTokensMarkedFalse() external {
- address staker = address(this);
- uint256 withdrawalAmount = 1e18;
- IStrategy strategy = dummyStrat;
-
- {
- uint256 depositAmount = 1e18;
- bool undelegateIfPossible = false;
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
- }
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- {
- strategyArray[0] = strategy;
- shareAmounts[0] = withdrawalAmount;
- tokensArray[0] = dummyToken;
- }
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- {
- uint256 nonce = strategyManager.numWithdrawalsQueued(staker);
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: staker,
- nonce: (uint96(nonce) - 1)
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(staker)
- }
- );
- }
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(staker));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalCompleted(
- queuedWithdrawal.depositor,
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- strategyManager.calculateWithdrawalRoot(queuedWithdrawal)
- );
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(staker));
-
- require(sharesAfter == sharesBefore + withdrawalAmount, "sharesAfter != sharesBefore + withdrawalAmount");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- }
-
- function testCompleteQueuedWithdrawal_ReceiveAsTokensMarkedTrue_NotWithdrawingBeaconChainETH() external {
- address staker = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
- _tempStrategyStorage = dummyStrat;
-
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- {
- strategyArray[0] = _tempStrategyStorage;
- shareAmounts[0] = withdrawalAmount;
- tokensArray[0] = dummyToken;
- }
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- {
- uint256 nonce = strategyManager.numWithdrawalsQueued(staker);
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: staker,
- nonce: (uint96(nonce) - 1)
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(staker)
- }
- );
- }
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, _tempStrategyStorage);
- uint256 balanceBefore = dummyToken.balanceOf(address(staker));
-
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalCompleted(
- queuedWithdrawal.depositor,
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- strategyManager.calculateWithdrawalRoot(queuedWithdrawal)
- );
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, /*middlewareTimesIndex*/ 0, /*receiveAsTokens*/ true);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, _tempStrategyStorage);
- uint256 balanceAfter = dummyToken.balanceOf(address(staker));
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(balanceAfter == balanceBefore + withdrawalAmount, "balanceAfter != balanceBefore + withdrawalAmount");
- }
-
- function testCompleteQueuedWithdrawal_ReceiveAsTokensMarkedTrue_WithdrawingBeaconChainETH() external {
- _tempStakerStorage = address(this);
- uint256 withdrawalAmount = 1e18;
- _tempStrategyStorage = beaconChainETHStrategy;
-
- // withdrawalAmount is converted to GWEI here
- testQueueWithdrawalBeaconChainETHToSelf(uint128(withdrawalAmount / 1e9));
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- {
- strategyArray[0] = _tempStrategyStorage;
- shareAmounts[0] = withdrawalAmount;
- }
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- {
- uint256 nonce = strategyManager.numWithdrawalsQueued(_tempStakerStorage);
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: _tempStakerStorage,
- nonce: (uint96(nonce) - 1)
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: _tempStakerStorage,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(_tempStakerStorage)
- }
- );
- }
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(_tempStakerStorage, _tempStrategyStorage);
- // uint256 balanceBefore = address(this).balance;
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = true;
-
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalCompleted(
- queuedWithdrawal.depositor,
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- strategyManager.calculateWithdrawalRoot(queuedWithdrawal)
- );
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(_tempStakerStorage, _tempStrategyStorage);
- // uint256 balanceAfter = address(this).balance;
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- // require(balanceAfter == balanceBefore + withdrawalAmount, "balanceAfter != balanceBefore + withdrawalAmount");
- // TODO: make EigenPodManagerMock do something so we can verify that it gets called appropriately?
- }
-
- function testCompleteQueuedWithdrawalFailsWhenWithdrawalsPaused() external {
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy strategy = queuedWithdrawal.strategies[0];
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(_tempStakerStorage));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- // pause withdrawals
- cheats.startPrank(pauser);
- strategyManager.pause(2);
- cheats.stopPrank();
-
- cheats.expectRevert(bytes("Pausable: index is paused"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(_tempStakerStorage));
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- }
-
- function testCompleteQueuedWithdrawalFailsWhenDelegatedAddressFrozen() external {
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy strategy = queuedWithdrawal.strategies[0];
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(_tempStakerStorage));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- // freeze the delegatedAddress
- slasherMock.freezeOperator(strategyManager.delegation().delegatedTo(_tempStakerStorage));
-
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(_tempStakerStorage));
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- }
-
- function testCompleteQueuedWithdrawalFailsWhenAttemptingReentrancy() external {
- // replace dummyStrat with Reenterer contract
- reenterer = new Reenterer();
- dummyStrat = StrategyBase(address(reenterer));
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- for (uint256 i = 0; i < _strategy.length; ++i) {
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(_strategy[i]);
- }
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
- IStrategy strategy = dummyStrat;
-
- reenterer.prepareReturnData(abi.encode(depositAmount));
-
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- {
- strategyArray[0] = strategy;
- shareAmounts[0] = withdrawalAmount;
- tokensArray[0] = dummyToken;
- }
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- {
- uint256 nonce = strategyManager.numWithdrawalsQueued(_tempStakerStorage);
-
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: _tempStakerStorage,
- nonce: (uint96(nonce) - 1)
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: _tempStakerStorage,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(_tempStakerStorage)
- }
- );
- }
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- address targetToUse = address(strategyManager);
- uint256 msgValueToUse = 0;
- bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.completeQueuedWithdrawal.selector, queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
- reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
-
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
- }
-
- function testCompleteQueuedWithdrawalFailsWhenWithdrawalDoesNotExist() external {
- _tempStakerStorage = address(this);
- uint256 withdrawalAmount = 1e18;
- IStrategy strategy = dummyStrat;
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- {
- strategyArray[0] = strategy;
- shareAmounts[0] = withdrawalAmount;
- tokensArray[0] = dummyToken;
- }
-
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- IStrategyManager.QueuedWithdrawal memory queuedWithdrawal;
-
- {
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: _tempStakerStorage,
- nonce: 0
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: _tempStakerStorage,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(_tempStakerStorage)
- }
- );
- }
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(_tempStakerStorage));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: withdrawal is not pending"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(_tempStakerStorage));
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- }
-
- function testCompleteQueuedWithdrawalFailsWhenCanWithdrawReturnsFalse() external {
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy strategy = queuedWithdrawal.strategies[0];
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(_tempStakerStorage));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- // prepare mock
- slasherMock.setCanWithdrawResponse(false);
-
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: shares pending withdrawal are still slashable"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(_tempStakerStorage));
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- }
-
- function testUndelegateWithFrozenStaker() public {
- slasherMock.setOperatorFrozenStatus(address(this), true);
- cheats.expectRevert(bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing"));
- cheats.startPrank(address(this));
- strategyManager.undelegate();
- cheats.stopPrank();
- }
-
- function testCompleteQueuedWithdrawalFailsWhenNotCallingFromWithdrawerAddress() external {
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy strategy = queuedWithdrawal.strategies[0];
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(_tempStakerStorage));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- cheats.startPrank(address(123456));
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: only specified withdrawer can complete a queued withdrawal"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
- cheats.stopPrank();
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(_tempStakerStorage));
-
- require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- }
-
- function testCompleteQueuedWithdrawalFailsWhenTryingToCompleteSameWithdrawal2X() external {
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- IStrategy strategy = queuedWithdrawal.strategies[0];
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceBefore = dummyToken.balanceOf(address(_tempStakerStorage));
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalCompleted(
- queuedWithdrawal.depositor,
- queuedWithdrawal.withdrawerAndNonce.nonce,
- queuedWithdrawal.withdrawerAndNonce.withdrawer,
- strategyManager.calculateWithdrawalRoot(queuedWithdrawal)
- );
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(address(this), strategy);
- uint256 balanceAfter = dummyToken.balanceOf(address(_tempStakerStorage));
-
- require(sharesAfter == sharesBefore + withdrawalAmount, "sharesAfter != sharesBefore + withdrawalAmount");
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
-
- // try to complete same withdrawal again
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: withdrawal is not pending"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
- }
-
- function testCompleteQueuedWithdrawalFailsWhenWithdrawalDelayBlocksHasNotPassed() external {
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- uint256 valueToSet = 1;
- // set the `withdrawalDelayBlocks` variable
- cheats.startPrank(strategyManager.owner());
- uint256 previousValue = strategyManager.withdrawalDelayBlocks();
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalDelayBlocksSet(previousValue, valueToSet);
- strategyManager.setWithdrawalDelayBlocks(valueToSet);
- cheats.stopPrank();
- require(strategyManager.withdrawalDelayBlocks() == valueToSet, "strategyManager.withdrawalDelayBlocks() != valueToSet");
-
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
- }
-
- function testCompleteQueuedWithdrawalWithNonzeroWithdrawalDelayBlocks(uint16 valueToSet) external {
- // filter fuzzed inputs to allowed *and nonzero* amounts
- cheats.assume(valueToSet <= strategyManager.MAX_WITHDRAWAL_DELAY_BLOCKS() && valueToSet != 0);
-
- _tempStakerStorage = address(this);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = 1e18;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- uint256 middlewareTimesIndex = 0;
- bool receiveAsTokens = false;
-
- // set the `withdrawalDelayBlocks` variable
- cheats.startPrank(strategyManager.owner());
- uint256 previousValue = strategyManager.withdrawalDelayBlocks();
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalDelayBlocksSet(previousValue, valueToSet);
- strategyManager.setWithdrawalDelayBlocks(valueToSet);
- cheats.stopPrank();
- require(strategyManager.withdrawalDelayBlocks() == valueToSet, "strategyManager.withdrawalDelayBlocks() != valueToSet");
-
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
-
- // roll block number forward to one block before the withdrawal should be completeable and attempt again
- uint256 originalBlockNumber = block.number;
- cheats.roll(originalBlockNumber + valueToSet - 1);
- cheats.expectRevert(bytes("StrategyManager.completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed"));
- strategyManager.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, middlewareTimesIndex, receiveAsTokens);
-
- // roll block number forward to the block at which the withdrawal should be completeable, and complete it
- cheats.roll(originalBlockNumber + valueToSet);
- }
-
- function testSlashSharesNotBeaconChainETHFuzzed(uint64 withdrawalAmount) external {
- // cannot cause share value to increase too drastically
- cheats.assume(withdrawalAmount <= 1e9 || withdrawalAmount == 1e18);
- _tempStakerStorage = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- {
- uint256 depositAmount = 1e18;
- // filter fuzzed input
- cheats.assume(withdrawalAmount != 0 && withdrawalAmount <= depositAmount);
- testDepositIntoStrategySuccessfully(_tempStakerStorage, depositAmount);
- }
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = uint256(withdrawalAmount);
-
- // freeze the staker
- slasherMock.freezeOperator(_tempStakerStorage);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(_tempStakerStorage, strategy);
- uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(_tempStakerStorage);
- uint256 balanceBefore = dummyToken.balanceOf(recipient);
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(_tempStakerStorage, strategy);
- uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(_tempStakerStorage);
- uint256 balanceAfter = dummyToken.balanceOf(recipient);
-
- require(sharesAfter == sharesBefore - uint256(withdrawalAmount), "sharesAfter != sharesBefore - uint256(withdrawalAmount)");
- require(balanceAfter == balanceBefore + uint256(withdrawalAmount), "balanceAfter != balanceBefore + uint256(withdrawalAmount)");
- if (sharesAfter == 0) {
- require(stakerStrategyListLengthAfter == stakerStrategyListLengthBefore - 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore - 1");
- }
- }
-
- function testSlashSharesNotBeaconChainETH_AllShares() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully( staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- // slash the same amount as deposited
- shareAmounts[0] = amount;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
- uint256 balanceBefore = dummyToken.balanceOf(recipient);
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
- uint256 balanceAfter = dummyToken.balanceOf(recipient);
-
- require(sharesAfter == sharesBefore - amount, "sharesAfter != sharesBefore - amount");
- require(balanceAfter == balanceBefore + amount, "balanceAfter != balanceBefore + amount");
- require(sharesAfter == 0, "sharesAfter != 0");
- require(stakerStrategyListLengthAfter == stakerStrategyListLengthBefore - 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore - 1");
- }
-
- function testSlashSharesBeaconChainETH() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = beaconChainETHStrategy;
- IERC20 token;
-
- testDepositBeaconChainETHSuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
- }
-
- function testSlashSharesMixIncludingBeaconChainETH() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully(staker, amount);
- testDepositBeaconChainETHSuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](2);
- IERC20[] memory tokensArray = new IERC20[](2);
- uint256[] memory shareAmounts = new uint256[](2);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount;
- strategyArray[1] = beaconChainETHStrategy;
- tokensArray[1] = token;
- shareAmounts[1] = amount;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](2);
- strategyIndexes[0] = 0;
- // this index is also zero, since the other strategy will be removed!
- strategyIndexes[1] = 0;
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 balanceBefore = dummyToken.balanceOf(recipient);
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 balanceAfter = dummyToken.balanceOf(recipient);
-
- require(sharesAfter == sharesBefore - amount, "sharesAfter != sharesBefore - amount");
- require(balanceAfter == balanceBefore + amount, "balanceAfter != balanceBefore + amount");
- }
-
-
- function testSlashSharesRevertsWhenCalledByNotOwner() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- // recipient is not the owner
- cheats.startPrank(recipient);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
- }
-
- function testSlashSharesRevertsWhenStakerNotFrozen() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount;
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert(bytes("StrategyManager.onlyFrozen: staker has not been frozen"));
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
- }
-
- function testSlashSharesRevertsWhenAttemptingReentrancy() external {
- // replace dummyStrat with Reenterer contract
- reenterer = new Reenterer();
- dummyStrat = StrategyBase(address(reenterer));
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- reenterer.prepareReturnData(abi.encode(amount));
-
- testDepositIntoStrategySuccessfully(staker, amount);
- testDepositBeaconChainETHSuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](2);
- IERC20[] memory tokensArray = new IERC20[](2);
- uint256[] memory shareAmounts = new uint256[](2);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount;
- strategyArray[1] = beaconChainETHStrategy;
- tokensArray[1] = token;
- shareAmounts[1] = amount;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](2);
- strategyIndexes[0] = 0;
- // this index is also zero, since the other strategy will be removed!
- strategyIndexes[1] = 0;
-
- // transfer strategyManager's ownership to the reenterer
- cheats.startPrank(strategyManager.owner());
- strategyManager.transferOwnership(address(reenterer));
- cheats.stopPrank();
-
- // prepare for reentrant call, expecting revert for reentrancy
- address targetToUse = address(strategyManager);
- uint256 msgValueToUse = 0;
- bytes memory calldataToUse =
- abi.encodeWithSelector(StrategyManager.slashShares.selector, slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
- }
-
- function testSlashQueuedWithdrawalNotBeaconChainETH() external {
- address recipient = address(333);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = depositAmount;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) = testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- uint256 balanceBefore = dummyToken.balanceOf(address(recipient));
-
- // slash the delegatedOperator
- slasherMock.freezeOperator(queuedWithdrawal.delegatedAddress);
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashQueuedWithdrawal(recipient, queuedWithdrawal, _arrayWithJustDummyToken(), emptyUintArray);
- cheats.stopPrank();
-
- uint256 balanceAfter = dummyToken.balanceOf(address(recipient));
- require(balanceAfter == balanceBefore + withdrawalAmount, "balanceAfter != balanceBefore + withdrawalAmount");
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is true!");
- }
-
- /// @notice this function is to test for a bug identified in the Code4Rena audit (H-205). This bug essentially
- /// allowed a strategy that is meant to be skipped, to actually be withdrawn from. This is a regression test
- /// to ensure that this bug does not reappear.
- function testSlashQueuedWithdrawalIncrementor() external {
- address recipient = address(333);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = depositAmount;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) =
-testQueueWithdrawal_ToSelf_NotBeaconChainETHTwoStrategies(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- uint256 balanceBefore = dummyToken.balanceOf(address(recipient));
-
- // slash the delegatedOperator
- slasherMock.freezeOperator(queuedWithdrawal.delegatedAddress);
-
- uint256[] memory indicesToSkip = new uint256[](2);
-
- indicesToSkip[0] = 0;
- indicesToSkip[1] = 1;
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashQueuedWithdrawal(recipient, queuedWithdrawal, _arrayWithJustTwoDummyTokens(), indicesToSkip);
- cheats.stopPrank();
-
- uint256 balanceAfter = dummyToken.balanceOf(address(recipient));
-
- /**
- * This check ensures that the strategy has not been withdrawn from. If the incrementor is misplaced inside
- * the else statement (as it was before the fix was made), the withdrawal would have been triggered for the
- * the strategy that we intended to skip, i.e., the check indicesToSkip[indicesToSkipIndex] == i would have
- * failed, triggering the else logic to withdraw from the strategy that was at index 0.
- */
- require(balanceAfter == balanceBefore, "withdrawal should not have been processed");
-
- require(!strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is true!");
- }
-
- function testSlashQueuedWithdrawalFailsWhenNotCallingFromOwnerAddress() external {
- address recipient = address(333);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = depositAmount;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- uint256 balanceBefore = dummyToken.balanceOf(address(recipient));
-
- // slash the delegatedOperator
- slasherMock.freezeOperator(queuedWithdrawal.delegatedAddress);
-
- // recipient is not strategyManager.owner()
- cheats.startPrank(recipient);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- strategyManager.slashQueuedWithdrawal(recipient, queuedWithdrawal, _arrayWithJustDummyToken(), emptyUintArray);
- cheats.stopPrank();
-
- uint256 balanceAfter = dummyToken.balanceOf(address(recipient));
-
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- require(strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is false");
- }
-
- function testSlashQueuedWithdrawalFailsWhenDelegatedAddressNotFrozen() external {
- address recipient = address(333);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = depositAmount;
- bool undelegateIfPossible = false;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, bytes32 withdrawalRoot) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- uint256 balanceBefore = dummyToken.balanceOf(address(recipient));
-
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert(bytes("StrategyManager.onlyFrozen: staker has not been frozen"));
- strategyManager.slashQueuedWithdrawal(recipient, queuedWithdrawal, _arrayWithJustDummyToken(), emptyUintArray);
- cheats.stopPrank();
-
- uint256 balanceAfter = dummyToken.balanceOf(address(recipient));
-
- require(balanceAfter == balanceBefore, "balanceAfter != balanceBefore");
- require(strategyManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingAfter is false");
- }
-
- function testSlashQueuedWithdrawalFailsWhenAttemptingReentrancy() external {
- // replace dummyStrat with Reenterer contract
- reenterer = new Reenterer();
- dummyStrat = StrategyBase(address(reenterer));
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- address staker = address(this);
- address recipient = address(333);
- uint256 depositAmount = 1e18;
- uint256 withdrawalAmount = depositAmount;
- bool undelegateIfPossible = false;
-
- reenterer.prepareReturnData(abi.encode(depositAmount));
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*IERC20[] memory tokensArray*/, /*bytes32 withdrawalRoot*/) =
- testQueueWithdrawal_ToSelf_NotBeaconChainETH(depositAmount, withdrawalAmount, undelegateIfPossible);
-
- // freeze the delegatedAddress
- slasherMock.freezeOperator(strategyManager.delegation().delegatedTo(staker));
-
- // transfer strategyManager's ownership to the reenterer
- cheats.startPrank(strategyManager.owner());
- strategyManager.transferOwnership(address(reenterer));
- cheats.stopPrank();
-
- // prepare for reentrant call, expecting revert for reentrancy
- address targetToUse = address(strategyManager);
- uint256 msgValueToUse = 0;
- bytes memory calldataToUse =
- abi.encodeWithSelector(StrategyManager.slashQueuedWithdrawal.selector, recipient, queuedWithdrawal, _arrayWithJustDummyToken(), emptyUintArray);
- reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call"));
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashQueuedWithdrawal(recipient, queuedWithdrawal, _arrayWithJustDummyToken(), emptyUintArray);
- cheats.stopPrank();
- }
-
- function testSlashQueuedWithdrawalFailsWhenWithdrawalDoesNotExist() external {
- address recipient = address(333);
- uint256 amount = 1e18;
-
- (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, /*bytes32 withdrawalRoot*/) =
- // convert wei to gwei for test input
- testQueueWithdrawalBeaconChainETHToSelf(uint128(amount / 1e9));
-
- // slash the delegatedOperator
- slasherMock.freezeOperator(queuedWithdrawal.delegatedAddress);
-
- // modify the queuedWithdrawal data so the root won't exist
- queuedWithdrawal.shares[0] = (amount * 2);
-
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert(bytes("StrategyManager.slashQueuedWithdrawal: withdrawal is not pending"));
- strategyManager.slashQueuedWithdrawal(recipient, queuedWithdrawal, _arrayWithJustDummyToken(), emptyUintArray);
- cheats.stopPrank();
- }
-
- function test_addSharesRevertsWhenSharesIsZero() external {
- // replace dummyStrat with Reenterer contract
- reenterer = new Reenterer();
- dummyStrat = StrategyBase(address(reenterer));
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
- uint256 amount = 1e18;
-
- reenterer.prepareReturnData(abi.encode(uint256(0)));
-
- cheats.startPrank(staker);
- cheats.expectRevert(bytes("StrategyManager._addShares: shares should not be zero!"));
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
- }
-
- function test_addSharesRevertsWhenDepositWouldExeedMaxArrayLength() external {
- address staker = address(this);
- IERC20 token = dummyToken;
- uint256 amount = 1e18;
- IStrategy strategy = dummyStrat;
-
- // uint256 MAX_STAKER_STRATEGY_LIST_LENGTH = strategyManager.MAX_STAKER_STRATEGY_LIST_LENGTH();
- uint256 MAX_STAKER_STRATEGY_LIST_LENGTH = 32;
-
- // loop that deploys a new strategy and deposits into it
- for (uint256 i = 0; i < MAX_STAKER_STRATEGY_LIST_LENGTH; ++i) {
- cheats.startPrank(staker);
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
-
- dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
- strategy = dummyStrat;
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
- }
-
- require(strategyManager.stakerStrategyListLength(staker) == MAX_STAKER_STRATEGY_LIST_LENGTH,
- "strategyManager.stakerStrategyListLength(staker) != MAX_STAKER_STRATEGY_LIST_LENGTH");
-
- cheats.startPrank(staker);
- cheats.expectRevert(bytes("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"));
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
- }
-
- function test_depositIntoStrategyRevertsWhenTokenSafeTransferFromReverts() external {
- // replace 'dummyStrat' with one that uses a reverting token
- dummyToken = IERC20(address(new Reverter()));
- dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- address staker = address(this);
- IERC20 token = dummyToken;
- uint256 amount = 1e18;
- IStrategy strategy = dummyStrat;
-
- cheats.startPrank(staker);
- cheats.expectRevert();
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
- }
-
- function test_depositIntoStrategyRevertsWhenTokenDoesNotExist() external {
- // replace 'dummyStrat' with one that uses a non-existent token
- dummyToken = IERC20(address(5678));
- dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- address staker = address(this);
- IERC20 token = dummyToken;
- uint256 amount = 1e18;
- IStrategy strategy = dummyStrat;
-
- cheats.startPrank(staker);
- cheats.expectRevert();
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
- }
-
- function test_depositIntoStrategyRevertsWhenStrategyDepositFunctionReverts() external {
- // replace 'dummyStrat' with one that always reverts
- dummyStrat = StrategyBase(
- address(
- new Reverter()
- )
- );
-
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- address staker = address(this);
- IERC20 token = dummyToken;
- uint256 amount = 1e18;
- IStrategy strategy = dummyStrat;
-
- cheats.startPrank(staker);
- cheats.expectRevert();
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
- }
-
- function test_depositIntoStrategyRevertsWhenStrategyDoesNotExist() external {
- // replace 'dummyStrat' with one that does not exist
- dummyStrat = StrategyBase(
- address(5678)
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, dummyStrat, amount);
+ uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat);
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore + 1,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1"
);
+ assertEq(sharesAfter, amount, "sharesAfter != amount");
+ assertTrue(_isDepositedStrategy(staker, dummyStrat), "strategy should be deposited");
+ }
- // whitelist the strategy for deposit
- cheats.startPrank(strategyManager.owner());
- IStrategy[] memory _strategy = new IStrategy[](1);
- _strategy[0] = dummyStrat;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(dummyStrat);
- strategyManager.addStrategiesToDepositWhitelist(_strategy);
- cheats.stopPrank();
-
- address staker = address(this);
- IERC20 token = dummyToken;
- uint256 amount = 1e18;
+ function testFuzz_AddSharesToExistingShares(
+ address staker,
+ uint256 sharesAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0) && 0 < sharesAmount && sharesAmount <= dummyToken.totalSupply());
+ uint256 initialAmount = 1e18;
IStrategy strategy = dummyStrat;
+ _depositIntoStrategySuccessfully(strategy, staker, initialAmount);
+ uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat);
+ assertEq(sharesBefore, initialAmount, "Staker has not deposited amount into strategy");
+ assertTrue(_isDepositedStrategy(staker, strategy), "strategy should be deposited");
- cheats.startPrank(staker);
- cheats.expectRevert();
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, dummyStrat, sharesAmount);
+ uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
+ uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat);
+ assertEq(
+ stakerStrategyListLengthAfter,
+ stakerStrategyListLengthBefore,
+ "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore"
+ );
+ assertEq(sharesAfter, sharesBefore + sharesAmount, "sharesAfter != sharesBefore + sharesAmount");
+ assertTrue(_isDepositedStrategy(staker, strategy), "strategy should be deposited");
}
- function test_depositIntoStrategyRevertsWhenStrategyNotWhitelisted() external {
- // replace 'dummyStrat' with one that is not whitelisted
- dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
-
+ /**
+ * @notice When _addShares() called either by depositIntoStrategy or addShares() results in appending to
+ * stakerStrategyListLength when the staker has MAX_STAKER_STRATEGY_LIST_LENGTH strategies, it should revert
+ */
+ function test_Revert_WhenMaxStrategyListLength() external {
address staker = address(this);
IERC20 token = dummyToken;
uint256 amount = 1e18;
IStrategy strategy = dummyStrat;
+ uint256 MAX_STAKER_STRATEGY_LIST_LENGTH = 32;
- cheats.startPrank(staker);
- cheats.expectRevert("StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted");
- strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
- }
+ // loop that deploys a new strategy and deposits into it
+ for (uint256 i = 0; i < MAX_STAKER_STRATEGY_LIST_LENGTH; ++i) {
+ cheats.startPrank(staker);
+ strategyManager.depositIntoStrategy(strategy, token, amount);
+ cheats.stopPrank();
- function test_removeSharesRevertsWhenShareAmountIsZero() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
+ dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+ strategy = dummyStrat;
- testDepositIntoStrategySuccessfully(staker, amount);
+ // whitelist the strategy for deposit
+ cheats.startPrank(strategyManager.owner());
+ IStrategy[] memory _strategy = new IStrategy[](1);
+ bool[] memory _thirdPartyTransfersForbiddenValues = new bool[](1);
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = 0;
+ _strategy[0] = dummyStrat;
+ strategyManager.addStrategiesToDepositWhitelist(_strategy, _thirdPartyTransfersForbiddenValues);
+ cheats.stopPrank();
+ }
- // freeze the staker
- slasherMock.freezeOperator(staker);
+ assertEq(
+ strategyManager.stakerStrategyListLength(staker),
+ MAX_STAKER_STRATEGY_LIST_LENGTH,
+ "strategyManager.stakerStrategyListLength(staker) != MAX_STAKER_STRATEGY_LIST_LENGTH"
+ );
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
+ cheats.prank(staker);
+ cheats.expectRevert("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH");
+ delegationManagerMock.addShares(strategyManager, staker, dummyToken, strategy, amount);
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert(bytes("StrategyManager._removeShares: shareAmount should not be zero!"));
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
+ cheats.expectRevert("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH");
+ strategyManager.depositIntoStrategy(strategy, token, amount);
}
+}
- function test_removeSharesRevertsWhenShareAmountIsTooLarge() external {
- uint256 amount = 1e18;
- address staker = address(this);
- IStrategy strategy = dummyStrat;
- IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount + 1;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
-
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert(bytes("StrategyManager._removeShares: shareAmount too high"));
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
+contract StrategyManagerUnitTests_withdrawSharesAsTokens is StrategyManagerUnitTests {
+ function test_Revert_DelegationManagerModifier() external {
+ DelegationManagerMock invalidDelegationManager = new DelegationManagerMock();
+ cheats.expectRevert("StrategyManager.onlyDelegationManager: not the DelegationManager");
+ invalidDelegationManager.removeShares(strategyManager, address(this), dummyStrat, 1);
}
- function test_removeStrategyFromStakerStrategyListWorksWithIncorrectIndexInput() external {
- uint256 amount = 1e18;
- address staker = address(this);
+ /**
+ * @notice deposits a single strategy and withdrawSharesAsTokens() function reverts when sharesAmount is
+ * higher than depositAmount
+ */
+ function testFuzz_Revert_ShareAmountTooHigh(
+ address staker,
+ uint256 depositAmount,
+ uint256 sharesAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply() && depositAmount < sharesAmount);
IStrategy strategy = dummyStrat;
IERC20 token = dummyToken;
-
- testDepositIntoStrategySuccessfully(staker, amount);
- testDepositBeaconChainETHSuccessfully(staker, amount);
-
- IStrategy[] memory strategyArray = new IStrategy[](1);
- IERC20[] memory tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = amount;
-
- // freeze the staker
- slasherMock.freezeOperator(staker);
-
- address slashedAddress = address(this);
- address recipient = address(333);
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 1;
-
- // check that we are actually supplying an incorrect index!
- require(strategyManager.stakerStrategyList(staker, strategyIndexes[0]) != strategyArray[0],
- "we want to supply an incorrect index but have supplied a correct one");
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 balanceBefore = dummyToken.balanceOf(recipient);
-
- cheats.startPrank(strategyManager.owner());
- strategyManager.slashShares(slashedAddress, recipient, strategyArray, tokensArray, strategyIndexes, shareAmounts);
- cheats.stopPrank();
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 balanceAfter = dummyToken.balanceOf(recipient);
-
- require(sharesAfter == sharesBefore - amount, "sharesAfter != sharesBefore - amount");
- require(balanceAfter == balanceBefore + amount, "balanceAfter != balanceBefore + amount");
+ _depositIntoStrategySuccessfully(strategy, staker, depositAmount);
+ cheats.expectRevert("StrategyBase.withdraw: amountShares must be less than or equal to totalShares");
+ delegationManagerMock.withdrawSharesAsTokens(strategyManager, staker, strategy, sharesAmount, token);
}
- function testSetWithdrawalDelayBlocks(uint16 valueToSet) external {
- // filter fuzzed inputs to allowed amounts
- cheats.assume(valueToSet <= strategyManager.MAX_WITHDRAWAL_DELAY_BLOCKS());
-
- // set the `withdrawalDelayBlocks` variable
- cheats.startPrank(strategyManager.owner());
- uint256 previousValue = strategyManager.withdrawalDelayBlocks();
+ function testFuzz_SingleStrategyDeposited(
+ address staker,
+ uint256 depositAmount,
+ uint256 sharesAmount
+ ) external filterFuzzedAddressInputs(staker) {
+ cheats.assume(staker != address(0));
+ cheats.assume(sharesAmount > 0 && sharesAmount < dummyToken.totalSupply() && depositAmount >= sharesAmount);
+ IStrategy strategy = dummyStrat;
+ IERC20 token = dummyToken;
+ _depositIntoStrategySuccessfully(strategy, staker, depositAmount);
+ uint256 balanceBefore = token.balanceOf(staker);
+ delegationManagerMock.withdrawSharesAsTokens(strategyManager, staker, strategy, sharesAmount, token);
+ uint256 balanceAfter = token.balanceOf(staker);
+ assertEq(balanceAfter, balanceBefore + sharesAmount, "balanceAfter != balanceBefore + sharesAmount");
+ }
+}
+
+contract StrategyManagerUnitTests_setStrategyWhitelister is StrategyManagerUnitTests {
+ function testFuzz_SetStrategyWhitelister(
+ address newWhitelister
+ ) external filterFuzzedAddressInputs(newWhitelister) {
+ address previousStrategyWhitelister = strategyManager.strategyWhitelister();
cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit WithdrawalDelayBlocksSet(previousValue, valueToSet);
- strategyManager.setWithdrawalDelayBlocks(valueToSet);
- cheats.stopPrank();
- require(strategyManager.withdrawalDelayBlocks() == valueToSet, "strategyManager.withdrawalDelayBlocks() != valueToSet");
+ emit StrategyWhitelisterChanged(previousStrategyWhitelister, newWhitelister);
+ strategyManager.setStrategyWhitelister(newWhitelister);
+ assertEq(
+ strategyManager.strategyWhitelister(),
+ newWhitelister,
+ "strategyManager.strategyWhitelister() != newWhitelister"
+ );
}
- function testSetWithdrawalDelayBlocksRevertsWhenCalledByNotOwner(address notOwner) filterFuzzedAddressInputs(notOwner) external {
+ function testFuzz_Revert_WhenCalledByNotOwner(address notOwner) external filterFuzzedAddressInputs(notOwner) {
cheats.assume(notOwner != strategyManager.owner());
-
- uint256 valueToSet = 1;
- // set the `withdrawalDelayBlocks` variable
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- strategyManager.setWithdrawalDelayBlocks(valueToSet);
- cheats.stopPrank();
+ address newWhitelister = address(this);
+ cheats.prank(notOwner);
+ cheats.expectRevert("Ownable: caller is not the owner");
+ strategyManager.setStrategyWhitelister(newWhitelister);
}
+}
- function testSetWithdrawalDelayBlocksRevertsWhenInputValueTooHigh(uint256 valueToSet) external {
- // filter fuzzed inputs to disallowed amounts
- cheats.assume(valueToSet > strategyManager.MAX_WITHDRAWAL_DELAY_BLOCKS());
+contract StrategyManagerUnitTests_addStrategiesToDepositWhitelist is StrategyManagerUnitTests {
+ function testFuzz_Revert_WhenCalledByNotStrategyWhitelister(
+ address notStrategyWhitelister
+ ) 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;
- // attempt to set the `withdrawalDelayBlocks` variable
- cheats.startPrank(strategyManager.owner());
- cheats.expectRevert(bytes("StrategyManager.setWithdrawalDelay: _withdrawalDelayBlocks too high"));
- strategyManager.setWithdrawalDelayBlocks(valueToSet);
+ cheats.prank(notStrategyWhitelister);
+ cheats.expectRevert("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister");
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
}
- function testSetStrategyWhitelister(address newWhitelister) external {
- address previousStrategyWhitelister = strategyManager.strategyWhitelister();
+ 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 StrategyWhitelisterChanged(previousStrategyWhitelister, newWhitelister);
- strategyManager.setStrategyWhitelister(newWhitelister);
- require(strategyManager.strategyWhitelister() == newWhitelister, "strategyManager.strategyWhitelister() != newWhitelister");
+ emit StrategyAddedToDepositWhitelist(strategy);
+ strategyManager.addStrategiesToDepositWhitelist(strategyArray, thirdPartyTransfersForbiddenValues);
+ assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted");
}
- function testSetStrategyWhitelisterRevertsWhenCalledByNotOwner(address notOwner)
- external filterFuzzedAddressInputs(notOwner)
- {
- cheats.assume(notOwner != strategyManager.owner());
- address newWhitelister = address(this);
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- strategyManager.setStrategyWhitelister(newWhitelister);
- cheats.stopPrank();
- }
-
- function testAddStrategiesToDepositWhitelist(uint8 numberOfStrategiesToAdd) public returns (IStrategy[] memory) {
+ 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);
+ 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, thirdPartyTransfersForbiddenValues);
+ uint256 numLogsAfter = cheats.getRecordedLogs().length;
+ assertEq(numLogsBefore, numLogsAfter, "event emitted when strategy already whitelisted");
+ assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should still be whitelisted");
+ }
+
+ function testFuzz_AddStrategiesToDepositWhitelist(uint8 numberOfStrategiesToAdd) external {
// sanity filtering on fuzzed input
cheats.assume(numberOfStrategiesToAdd <= 16);
+ _addStrategiesToWhitelist(numberOfStrategiesToAdd);
+ }
+}
- IStrategy[] memory strategyArray = new IStrategy[](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);
- strategyArray[i] = _strategy;
- require(!strategyManager.strategyIsWhitelistedForDeposit(_strategy), "strategy improperly whitelisted?");
- }
-
- cheats.startPrank(strategyManager.strategyWhitelister());
- for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit StrategyAddedToDepositWhitelist(strategyArray[i]);
- }
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
- cheats.stopPrank();
+contract StrategyManagerUnitTests_removeStrategiesFromDepositWhitelist is StrategyManagerUnitTests {
+ function testFuzz_Revert_WhenCalledByNotStrategyWhitelister(
+ address notStrategyWhitelister
+ ) external filterFuzzedAddressInputs(notStrategyWhitelister) {
+ cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister());
+ IStrategy[] memory strategyArray = _addStrategiesToWhitelist(1);
- for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
- require(strategyManager.strategyIsWhitelistedForDeposit(strategyArray[i]), "strategy not properly whitelisted");
- }
+ cheats.prank(notStrategyWhitelister);
+ cheats.expectRevert("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister");
+ strategyManager.removeStrategiesFromDepositWhitelist(strategyArray);
+ }
- return strategyArray;
+ /**
+ * @notice testing that mapping is still false and no event emitted
+ */
+ function test_RemoveNonWhitelistedStrategy() external {
+ IStrategy[] memory strategyArray = new IStrategy[](1);
+ IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
+ strategyArray[0] = strategy;
+ assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted");
+ // Make sure event not emitted by checking logs length
+ cheats.recordLogs();
+ uint256 numLogsBefore = cheats.getRecordedLogs().length;
+ strategyManager.removeStrategiesFromDepositWhitelist(strategyArray);
+ uint256 numLogsAfter = cheats.getRecordedLogs().length;
+ assertEq(numLogsBefore, numLogsAfter, "event emitted when strategy already not whitelisted");
+ assertFalse(
+ strategyManager.strategyIsWhitelistedForDeposit(strategy),
+ "strategy still should not be whitelisted"
+ );
}
- function testAddStrategiesToDepositWhitelistRevertsWhenCalledByNotStrategyWhitelister(address notStrategyWhitelister)
- external filterFuzzedAddressInputs(notStrategyWhitelister)
- {
- cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister());
+ /**
+ * @notice testing that strategy is removed from whitelist and event is emitted
+ */
+ function test_RemoveWhitelistedStrategy() external {
IStrategy[] memory strategyArray = new IStrategy[](1);
- IStrategy _strategy = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin);
- strategyArray[0] = _strategy;
+ 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, thirdPartyTransfersForbiddenValues);
+ assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted");
- cheats.startPrank(notStrategyWhitelister);
- cheats.expectRevert(bytes("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"));
- strategyManager.addStrategiesToDepositWhitelist(strategyArray);
- cheats.stopPrank();
+ // Now remove strategy from whitelist
+ cheats.expectEmit(true, true, true, true, address(strategyManager));
+ emit StrategyRemovedFromDepositWhitelist(strategy);
+ strategyManager.removeStrategiesFromDepositWhitelist(strategyArray);
+ assertFalse(
+ strategyManager.strategyIsWhitelistedForDeposit(strategy),
+ "strategy should no longer be whitelisted"
+ );
}
- function testRemoveStrategiesFromDepositWhitelist(uint8 numberOfStrategiesToAdd, uint8 numberOfStrategiesToRemove) external {
+ function testFuzz_RemoveStrategiesFromDepositWhitelist(
+ uint8 numberOfStrategiesToAdd,
+ uint8 numberOfStrategiesToRemove
+ ) external {
// sanity filtering on fuzzed input
cheats.assume(numberOfStrategiesToAdd <= 16);
cheats.assume(numberOfStrategiesToRemove <= 16);
cheats.assume(numberOfStrategiesToRemove <= numberOfStrategiesToAdd);
- IStrategy[] memory strategiesAdded = testAddStrategiesToDepositWhitelist(numberOfStrategiesToAdd);
+ IStrategy[] memory strategiesAdded = _addStrategiesToWhitelist(numberOfStrategiesToAdd);
IStrategy[] memory strategiesToRemove = new IStrategy[](numberOfStrategiesToRemove);
// loop that selectively copies from array to other array
@@ -2457,198 +1306,25 @@ testQueueWithdrawal_ToSelf_NotBeaconChainETHTwoStrategies(depositAmount, withdra
strategiesToRemove[i] = strategiesAdded[i];
}
- cheats.startPrank(strategyManager.strategyWhitelister());
+ cheats.prank(strategyManager.strategyWhitelister());
for (uint256 i = 0; i < strategiesToRemove.length; ++i) {
cheats.expectEmit(true, true, true, true, address(strategyManager));
emit StrategyRemovedFromDepositWhitelist(strategiesToRemove[i]);
}
strategyManager.removeStrategiesFromDepositWhitelist(strategiesToRemove);
- cheats.stopPrank();
for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) {
if (i < numberOfStrategiesToRemove) {
- require(!strategyManager.strategyIsWhitelistedForDeposit(strategiesToRemove[i]), "strategy not properly removed from whitelist");
+ assertFalse(
+ strategyManager.strategyIsWhitelistedForDeposit(strategiesToRemove[i]),
+ "strategy not properly removed from whitelist"
+ );
} else {
- require(strategyManager.strategyIsWhitelistedForDeposit(strategiesAdded[i]), "strategy improperly removed from whitelist?");
- }
- }
- }
-
- function testRemoveStrategiesFromDepositWhitelistRevertsWhenCalledByNotStrategyWhitelister(address notStrategyWhitelister)
- external filterFuzzedAddressInputs(notStrategyWhitelister)
- {
- cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister());
- IStrategy[] memory strategyArray = testAddStrategiesToDepositWhitelist(1);
-
- cheats.startPrank(notStrategyWhitelister);
- cheats.expectRevert(bytes("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"));
- strategyManager.removeStrategiesFromDepositWhitelist(strategyArray);
- cheats.stopPrank();
- }
-
- // INTERNAL / HELPER FUNCTIONS
- function _beaconChainReentrancyTestsSetup() internal {
- // prepare StrategyManager with EigenPodManager and Delegation replaced with a Reenterer contract
- reenterer = new Reenterer();
- strategyManagerImplementation = new StrategyManager(IDelegationManager(address(reenterer)), IEigenPodManager(address(reenterer)), slasherMock);
- strategyManager = StrategyManager(
- address(
- new TransparentUpgradeableProxy(
- address(strategyManagerImplementation),
- address(proxyAdmin),
- abi.encodeWithSelector(StrategyManager.initialize.selector, initialOwner, initialOwner, pauserRegistry, 0, 0)
- )
- )
- );
- }
-
- function _setUpQueuedWithdrawalStructSingleStrat(address staker, address withdrawer, IERC20 token, IStrategy strategy, uint256 shareAmount)
- internal view returns (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, IERC20[] memory tokensArray, bytes32 withdrawalRoot)
- {
- IStrategy[] memory strategyArray = new IStrategy[](1);
- tokensArray = new IERC20[](1);
- uint256[] memory shareAmounts = new uint256[](1);
- strategyArray[0] = strategy;
- tokensArray[0] = token;
- shareAmounts[0] = shareAmount;
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: withdrawer,
- nonce: uint96(strategyManager.numWithdrawalsQueued(staker))
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(staker)
- }
- );
- // calculate the withdrawal root
- withdrawalRoot = strategyManager.calculateWithdrawalRoot(queuedWithdrawal);
- return (queuedWithdrawal, tokensArray, withdrawalRoot);
- }
-
- function _depositIntoStrategySuccessfully(IStrategy strategy, address staker, uint256 amount) internal filterFuzzedAddressInputs(staker) {
- IERC20 token = dummyToken;
-
- // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
- cheats.assume(amount != 0);
- // filter out zero address because the mock ERC20 we are using will revert on using it
- cheats.assume(staker != address(0));
- // sanity check / filter
- cheats.assume(amount <= token.balanceOf(address(this)));
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker);
-
- // needed for expecting an event with the right parameters
- uint256 expectedShares = amount;
-
- cheats.startPrank(staker);
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit Deposit(staker, token, strategy, expectedShares);
- uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount);
- cheats.stopPrank();
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy);
- uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker);
-
- require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares");
- if (sharesBefore == 0) {
- require(stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1");
- require(strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) == strategy,
- "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy");
- }
- }
-
- function _setUpQueuedWithdrawalStructSingleStrat_MultipleStrategies(
- address staker,
- address withdrawer,
- IStrategy[] memory strategyArray,
- uint256[] memory shareAmounts
- )
- internal view returns (IStrategyManager.QueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot)
- {
- IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = IStrategyManager.WithdrawerAndNonce({
- withdrawer: withdrawer,
- nonce: uint96(strategyManager.numWithdrawalsQueued(staker))
- });
- queuedWithdrawal =
- IStrategyManager.QueuedWithdrawal({
- strategies: strategyArray,
- shares: shareAmounts,
- depositor: staker,
- withdrawerAndNonce: withdrawerAndNonce,
- withdrawalStartBlock: uint32(block.number),
- delegatedAddress: strategyManager.delegation().delegatedTo(staker)
+ assertTrue(
+ strategyManager.strategyIsWhitelistedForDeposit(strategiesAdded[i]),
+ "strategy improperly removed from whitelist?"
+ );
}
- );
- // calculate the withdrawal root
- withdrawalRoot = strategyManager.calculateWithdrawalRoot(queuedWithdrawal);
- return (queuedWithdrawal, withdrawalRoot);
- }
-
- function _arrayWithJustDummyToken() internal view returns (IERC20[] memory) {
- IERC20[] memory array = new IERC20[](1);
- array[0] = dummyToken;
- return array;
- }
-
- function _arrayWithJustTwoDummyTokens() internal view returns (IERC20[] memory) {
- IERC20[] memory array = new IERC20[](2);
- array[0] = dummyToken;
- array[1] = dummyToken;
- return array;
- }
-
- // internal function for de-duping code. expects success if `expectedRevertMessage` is empty and expiry is valid.
- function _depositIntoStrategyWithSignature(address staker, uint256 amount, uint256 expiry, string memory expectedRevertMessage) internal returns (bytes memory) {
-
- // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!"
- cheats.assume(amount != 0);
- // sanity check / filter
- cheats.assume(amount <= dummyToken.balanceOf(address(this)));
-
- uint256 nonceBefore = strategyManager.nonces(staker);
- bytes memory signature;
-
- {
- bytes32 structHash = keccak256(abi.encode(strategyManager.DEPOSIT_TYPEHASH(), dummyStrat, dummyToken, amount, nonceBefore, expiry));
- bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.DOMAIN_SEPARATOR(), structHash));
-
- (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash);
-
- signature = abi.encodePacked(r, s, v);
- }
-
- uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat);
-
- bool expectedRevertMessageIsempty;
- {
- string memory emptyString;
- expectedRevertMessageIsempty = keccak256(abi.encodePacked(expectedRevertMessage)) == keccak256(abi.encodePacked(emptyString));
- }
- if (!expectedRevertMessageIsempty) {
- cheats.expectRevert(bytes(expectedRevertMessage));
- } else if (expiry < block.timestamp) {
- cheats.expectRevert("StrategyManager.depositIntoStrategyWithSignature: signature expired");
- } else {
- // needed for expecting an event with the right parameters
- uint256 expectedShares = amount;
- cheats.expectEmit(true, true, true, true, address(strategyManager));
- emit Deposit(staker, dummyToken, dummyStrat, expectedShares);
- }
- uint256 shares = strategyManager.depositIntoStrategyWithSignature(dummyStrat, dummyToken, amount, staker, expiry, signature);
-
- uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat);
- uint256 nonceAfter = strategyManager.nonces(staker);
-
- if (expiry >= block.timestamp && expectedRevertMessageIsempty) {
- require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares");
- require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1");
}
- return signature;
}
-}
\ No newline at end of file
+}
diff --git a/src/test/utils/EigenLayerUnitTestBase.sol b/src/test/utils/EigenLayerUnitTestBase.sol
new file mode 100644
index 0000000000..9a776e9bff
--- /dev/null
+++ b/src/test/utils/EigenLayerUnitTestBase.sol
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
+import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import "src/contracts/permissions/PauserRegistry.sol";
+import "forge-std/Test.sol";
+
+abstract contract EigenLayerUnitTestBase is Test {
+ Vm cheats = Vm(HEVM_ADDRESS);
+
+ PauserRegistry public pauserRegistry;
+ ProxyAdmin public eigenLayerProxyAdmin;
+
+ mapping(address => bool) public addressIsExcludedFromFuzzedInputs;
+
+ address public constant pauser = address(555);
+ address public constant unpauser = address(556);
+
+ // Helper Functions/Modifiers
+ modifier filterFuzzedAddressInputs(address fuzzedAddress) {
+ cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]);
+ _;
+ }
+
+ function setUp() public virtual {
+ address[] memory pausers = new address[](1);
+ pausers[0] = pauser;
+ pauserRegistry = new PauserRegistry(pausers, unpauser);
+ eigenLayerProxyAdmin = new ProxyAdmin();
+
+ addressIsExcludedFromFuzzedInputs[address(pauserRegistry)] = true;
+ addressIsExcludedFromFuzzedInputs[address(eigenLayerProxyAdmin)] = true;
+ }
+}
diff --git a/src/test/utils/EigenLayerUnitTestSetup.sol b/src/test/utils/EigenLayerUnitTestSetup.sol
new file mode 100644
index 0000000000..5c8d86a4d2
--- /dev/null
+++ b/src/test/utils/EigenLayerUnitTestSetup.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity ^0.8.12;
+
+import "src/test/mocks/StrategyManagerMock.sol";
+import "src/test/mocks/DelegationManagerMock.sol";
+import "src/test/mocks/SlasherMock.sol";
+import "src/test/mocks/EigenPodManagerMock.sol";
+import "src/test/utils/EigenLayerUnitTestBase.sol";
+
+abstract contract EigenLayerUnitTestSetup is EigenLayerUnitTestBase {
+ // Declare Mocks
+ StrategyManagerMock strategyManagerMock;
+ DelegationManagerMock public delegationManagerMock;
+ SlasherMock public slasherMock;
+ EigenPodManagerMock public eigenPodManagerMock;
+
+ function setUp() public virtual override {
+ EigenLayerUnitTestBase.setUp();
+ strategyManagerMock = new StrategyManagerMock();
+ delegationManagerMock = new DelegationManagerMock();
+ slasherMock = new SlasherMock();
+ eigenPodManagerMock = new EigenPodManagerMock();
+
+ addressIsExcludedFromFuzzedInputs[address(0)] = true;
+ addressIsExcludedFromFuzzedInputs[address(strategyManagerMock)] = true;
+ addressIsExcludedFromFuzzedInputs[address(delegationManagerMock)] = true;
+ addressIsExcludedFromFuzzedInputs[address(slasherMock)] = true;
+ addressIsExcludedFromFuzzedInputs[address(eigenPodManagerMock)] = true;
+ }
+}
diff --git a/src/test/utils/Operators.sol b/src/test/utils/Operators.sol
index d2284e8763..7c5faabd62 100644
--- a/src/test/utils/Operators.sol
+++ b/src/test/utils/Operators.sol
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "../../contracts/libraries/BN254.sol";
import "forge-std/Test.sol";
import "forge-std/Script.sol";
import "forge-std/StdJson.sol";
@@ -25,49 +24,18 @@ contract Operators is Test {
return stdJson.readAddress(operatorConfigJson, string.concat(operatorPrefix(index), "Address"));
}
- function getOperatorSchnorrSignature(uint256 index) public returns(uint256, BN254.G1Point memory) {
- uint256 s = readUint(operatorConfigJson, index, "SField");
- BN254.G1Point memory pubkey = BN254.G1Point({
- X: readUint(operatorConfigJson, index, "RPoint.X"),
- Y: readUint(operatorConfigJson, index, "RPoint.Y")
- });
- return (s, pubkey);
- }
-
function getOperatorSecretKey(uint256 index) public returns(uint256) {
return readUint(operatorConfigJson, index, "SecretKey");
}
- function getOperatorPubkeyG1(uint256 index) public returns(BN254.G1Point memory) {
- BN254.G1Point memory pubkey = BN254.G1Point({
- X: readUint(operatorConfigJson, index, "PubkeyG1.X"),
- Y: readUint(operatorConfigJson, index, "PubkeyG1.Y")
- });
- return pubkey;
- }
-
- function getOperatorPubkeyG2(uint256 index) public returns(BN254.G2Point memory) {
- BN254.G2Point memory pubkey = BN254.G2Point({
- X: [
- readUint(operatorConfigJson, index, "PubkeyG2.X.A1"),
- readUint(operatorConfigJson, index, "PubkeyG2.X.A0")
- ],
- Y: [
- readUint(operatorConfigJson, index, "PubkeyG2.Y.A1"),
- readUint(operatorConfigJson, index, "PubkeyG2.Y.A0")
- ]
- });
- return pubkey;
- }
-
- function readUint(string memory json, uint256 index, string memory key) public returns (uint) {
+ function readUint(string memory json, uint256 index, string memory key) public returns (uint256) {
return stringToUint(stdJson.readString(json, string.concat(operatorPrefix(index), key)));
}
- function stringToUint(string memory s) public pure returns (uint) {
+ function stringToUint(string memory s) public pure returns (uint256) {
bytes memory b = bytes(s);
- uint result = 0;
- for (uint i = 0; i < b.length; i++) {
+ uint256 result = 0;
+ for (uint256 i = 0; i < b.length; i++) {
if (uint256(uint8(b[i])) >= 48 && uint256(uint8(b[i])) <= 57) {
result = result * 10 + (uint256(uint8(b[i])) - 48);
}
diff --git a/src/test/utils/Owners.sol b/src/test/utils/Owners.sol
index 5f93d50bfc..9c32971abc 100644
--- a/src/test/utils/Owners.sol
+++ b/src/test/utils/Owners.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "forge-std/Test.sol";
import "forge-std/Script.sol";
diff --git a/src/test/utils/ProofParsing.sol b/src/test/utils/ProofParsing.sol
index 1cc4eecc05..fd69678ecc 100644
--- a/src/test/utils/ProofParsing.sol
+++ b/src/test/utils/ProofParsing.sol
@@ -1,30 +1,26 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
-import "../../contracts/libraries/BN254.sol";
import "forge-std/Test.sol";
-import "forge-std/Script.sol";
import "forge-std/StdJson.sol";
-
-contract ProofParsing is Test{
+contract ProofParsing is Test {
string internal proofConfigJson;
string prefix;
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] blockNumberProofs;
-
+ bytes32[5] timestampProofsCapella;
+ bytes32[4] timestampProofsDeneb;
bytes32 slotRoot;
bytes32 executionPayloadRoot;
- bytes32 blockNumberRoots;
function setJSON(string memory path) public {
proofConfigJson = vm.readFile(path);
@@ -38,41 +34,45 @@ contract ProofParsing is Test{
return stdJson.readUint(proofConfigJson, ".validatorIndex");
}
+ function getValidatorPubkeyHash() public returns(bytes32) {
+ return stdJson.readBytes32(proofConfigJson, ".ValidatorFields[0]");
+ }
+
function getWithdrawalIndex() public returns(uint256) {
return stdJson.readUint(proofConfigJson, ".withdrawalIndex");
}
- function getBlockHeaderRootIndex() public returns(uint256) {
+ function getBlockRootIndex() public returns(uint256) {
return stdJson.readUint(proofConfigJson, ".blockHeaderRootIndex");
}
+ function getHistoricalSummaryIndex() public returns(uint256) {
+ return stdJson.readUint(proofConfigJson, ".historicalSummaryIndex");
+ }
+
function getBeaconStateRoot() public returns(bytes32) {
return stdJson.readBytes32(proofConfigJson, ".beaconStateRoot");
}
- function getBlockHeaderRoot() public returns(bytes32) {
+ function getBlockRoot() public returns(bytes32) {
return stdJson.readBytes32(proofConfigJson, ".blockHeaderRoot");
}
- function getBlockBodyRoot() public returns(bytes32) {
- return stdJson.readBytes32(proofConfigJson, ".blockBodyRoot");
- }
-
function getSlotRoot() public returns(bytes32) {
return stdJson.readBytes32(proofConfigJson, ".slotRoot");
}
- function getBalanceRoot() public returns(bytes32) {
- return stdJson.readBytes32(proofConfigJson, ".balanceRoot");
- }
-
- function getBlockNumberRoot() public returns(bytes32) {
- return stdJson.readBytes32(proofConfigJson, ".blockNumberRoot");
+ function getTimestampRoot() public returns(bytes32) {
+ return stdJson.readBytes32(proofConfigJson, ".timestampRoot");
}
function getExecutionPayloadRoot() public returns(bytes32) {
return stdJson.readBytes32(proofConfigJson, ".executionPayloadRoot");
}
+
+ function getLatestBlockRoot() public returns(bytes32) {
+ return stdJson.readBytes32(proofConfigJson, ".latestBlockHeaderRoot");
+ }
function getExecutionPayloadProof () public returns(bytes32[7] memory) {
for (uint i = 0; i < 7; i++) {
prefix = string.concat(".ExecutionPayloadProof[", string.concat(vm.toString(i), "]"));
@@ -81,14 +81,24 @@ contract ProofParsing is Test{
return executionPayloadProof;
}
- function getBlockNumberProof () 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(".BlockNumberProof[", string.concat(vm.toString(i), "]"));
- blockNumberProofs[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ prefix = string.concat(".TimestampProof[", string.concat(vm.toString(i), "]"));
+ timestampProofsDeneb[i] = (stdJson.readBytes32(proofConfigJson, prefix));
}
- return blockNumberProofs;
+ 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), "]"));
@@ -105,12 +115,29 @@ contract ProofParsing is Test{
return slotProof;
}
- function getWithdrawalProof() public returns(bytes32[9] memory) {
+ function getStateRootProof() public returns(bytes32[] memory) {
+ bytes32[] memory stateRootProof = new bytes32[](3);
+ for (uint i = 0; i < 3; i++) {
+ prefix = string.concat(".StateRootAgainstLatestBlockHeaderProof[", string.concat(vm.toString(i), "]"));
+ stateRootProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ }
+ return stateRootProof;
+ }
+
+ 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) {
@@ -120,17 +147,22 @@ contract ProofParsing is Test{
}
return validatorProof;
}
+
+ function getHistoricalSummaryProof() public returns(bytes32[44] memory) {
+ for (uint i = 0; i < 44; i++) {
+ prefix = string.concat(".HistoricalSummaryProof[", string.concat(vm.toString(i), "]"));
+ historicalSummaryProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ }
+ return historicalSummaryProof;
+ }
function getWithdrawalFields() public returns(bytes32[] memory) {
bytes32[] memory withdrawalFields = new bytes32[](4);
for (uint i = 0; i < 4; i++) {
prefix = string.concat(".WithdrawalFields[", string.concat(vm.toString(i), "]"));
- emit log_named_bytes32("prefix", stdJson.readBytes32(proofConfigJson, prefix));
withdrawalFields[i] = (stdJson.readBytes32(proofConfigJson, prefix));
}
- emit log_named_uint("length withdrawal firle", withdrawalFields.length);
return withdrawalFields;
-
}
function getValidatorFields() public returns(bytes32[] memory) {
@@ -142,21 +174,26 @@ contract ProofParsing is Test{
return validatorFields;
}
- function getValidatorBalanceProof() public returns(bytes32[] memory) {
- bytes32[] memory validatorBalanceProof = new bytes32[](44);
- for (uint i = 0; i < 44; i++) {
- prefix = string.concat(".ValidatorBalanceProof[", string.concat(vm.toString(i), "]"));
- validatorBalanceProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
- }
- return validatorBalanceProof;
+ function getBalanceUpdateProof() public returns(bytes32[] memory) {
+ // Balance update proofs are the same as withdrawal credential proofs
+ return getWithdrawalCredentialProof();
}
function getWithdrawalCredentialProof() public returns(bytes32[] memory) {
- bytes32[] memory withdrawalCredenitalProof = new bytes32[](46);
+ bytes32[] memory withdrawalCredentialProof = new bytes32[](46);
+ for (uint i = 0; i < 46; i++) {
+ prefix = string.concat(".WithdrawalCredentialProof[", string.concat(vm.toString(i), "]"));
+ withdrawalCredentialProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ }
+ return withdrawalCredentialProof;
+ }
+
+ function getValidatorFieldsProof() public returns(bytes32[] memory) {
+ bytes32[] memory validatorFieldsProof = new bytes32[](46);
for (uint i = 0; i < 46; i++) {
- prefix = string.concat(".WithdrawalCredenitalProof[", string.concat(vm.toString(i), "]"));
- withdrawalCredenitalProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
+ prefix = string.concat(".ValidatorFieldsProof[", string.concat(vm.toString(i), "]"));
+ validatorFieldsProof[i] = (stdJson.readBytes32(proofConfigJson, prefix));
}
- return withdrawalCredenitalProof;
+ return validatorFieldsProof;
}
-}
\ No newline at end of file
+}
diff --git a/src/test/utils/SignatureCompaction.sol b/src/test/utils/SignatureCompaction.sol
index dd5f0dcef8..c10b2db1de 100644
--- a/src/test/utils/SignatureCompaction.sol
+++ b/src/test/utils/SignatureCompaction.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
diff --git a/src/test/unit/Utils.sol b/src/test/utils/Utils.sol
similarity index 90%
rename from src/test/unit/Utils.sol
rename to src/test/utils/Utils.sol
index 8958cdb857..10c5653eba 100644
--- a/src/test/unit/Utils.sol
+++ b/src/test/utils/Utils.sol
@@ -1,7 +1,7 @@
-pragma solidity =0.8.12;
+pragma solidity ^0.8.12;
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
-import "../../contracts/strategies/StrategyBase.sol";
+import "src/contracts/strategies/StrategyBase.sol";
contract Utils {
address constant dummyAdmin = address(uint160(uint256(keccak256("DummyAdmin"))));
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000000..d19ceaa7d3
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+source .env
+
+operator1address=$(cast w a --private-key $OPERATOR_1)
+operator2address=$(cast w a --private-key $OPERATOR_2)
+operator3address=$(cast w a --private-key $OPERATOR_3)
+operator4address=$(cast w a --private-key $OPERATOR_4)
+
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_1 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "registerAsOperator((address,address,uint32), string)" "($operator1address,0x0000000000000000000000000000000000000000,10)" "https://operator.com/123"
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_2 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "registerAsOperator((address,address,uint32), string)" "($operator2address,0x0000000000000000000000000000000000000000,0)" "https://123qwe.com/opop"
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_3 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "registerAsOperator((address,address,uint32), string)" "($operator3address,0x0000000000000000000000000000000000000000,0)" "https://geezlouise.com/13"
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_4 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "registerAsOperator((address,address,uint32), string)" "($operator4address,0x0000000000000000000000000000000000000000,0)" "https://urmama.com/rich_dank"
+
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_1 0x78697cd4EE4BdE0514fE8a7C61E8cB1A152B5d78 "registerOperatorWithCoordinator(bytes memory, bytes calldata)" "" ""
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_2 0x78697cd4EE4BdE0514fE8a7C61E8cB1A152B5d78 "registerOperatorWithCoordinator(bytes memory, bytes calldata)" "" ""
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_3 0x78697cd4EE4BdE0514fE8a7C61E8cB1A152B5d78 "registerOperatorWithCoordinator(bytes memory, bytes calldata)" "" ""
+# cast send --rpc-url $RPC_URL --private-key $OPERATOR_4 0x78697cd4EE4BdE0514fE8a7C61E8cB1A152B5d78 "registerOperatorWithCoordinator(bytes memory, bytes calldata)" "" ""
+
+cast send --rpc-url $RPC_URL --private-key $OPERATOR_1 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "updateOperatorMetadataURI(string calldata)" "https://operator-metadata.s3.amazonaws.com/operator_1.json"
+cast send --rpc-url $RPC_URL --private-key $OPERATOR_2 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "updateOperatorMetadataURI(string calldata)" "https://operator-metadata.s3.amazonaws.com/operator_2.json"
+cast send --rpc-url $RPC_URL --private-key $OPERATOR_3 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "updateOperatorMetadataURI(string calldata)" "https://operator-metadata.s3.amazonaws.com/operator_3.json"
+cast send --rpc-url $RPC_URL --private-key $OPERATOR_4 0x45b4c4DAE69393f62e1d14C5fe375792DF4E6332 "updateOperatorMetadataURI(string calldata)" "https://operator-metadata.s3.amazonaws.com/operator_4.json"
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000..df93c8da6f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ }
+ }
+
\ No newline at end of file