diff --git a/.gitignore b/.gitignore index 84f7c801c..555235f94 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,8 @@ script/output/M1_deployment_data.json script/misc -test.sh \ No newline at end of file +test.sh + +# Surya outputs +InheritanceGraph.png +surya_report.md \ No newline at end of file diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 000000000..497fd271c --- /dev/null +++ b/.solhintignore @@ -0,0 +1 @@ +Slasher.sol \ No newline at end of file diff --git a/README.md b/README.md index 82e56d018..3df359140 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,85 @@ # EigenLayer -

-🚧 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. 🚧 -

-EigenLayer is a set of smart contracts deployed on Ethereum that enable restaking of assets to secure new services. +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. -We recommend starting with the [technical documentation](docs/README.md) to get an overview of the contracts before diving into the code. +## Getting Started -For deployment addresses on both mainnet and Goerli, see [Deployments](#deployments) below. - -## Table of Contents - -* [Installation and Running Tests / Analyzers](#installation-and-running-tests--analyzers) -* [Technical Documentation](docs/README.md) +* [Documentation](#documentation) +* [Building and Running Tests](#building-and-running-tests) * [Deployments](#deployments) - -## Installation and Running Tests / Analyzers - -### Installation - -``` -foundryup +## Documentation -forge install -``` +### Basics -This repository uses Foundry as a smart contract development toolchain. +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) -See the [Foundry Docs](https://book.getfoundry.sh/) for more info on installation and usage. +### Deep Dive -### Natspec Documentation +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. -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. +To get an idea of how users interact with these contracts, check out our integration tests: [/src/test/integration](./src/test/integration/). -To generate the docs, run `npx hardhat docgen` (you may need to run `npm install` first). +## Building and Running Tests -### Run Tests +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: -Prior to running tests, you should set up your environment. At present this repository contains fork tests against ETH mainnet; your environment will use 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. If you don't set the `RPC_MAINNET` key then the test cases will default to LlamaNodes RPC url when fork testing. +``` +foundryup -The main command to run tests is: +forge build +forge test +``` -`forge test -vv` +### Running Fork Tests -### Run Tests on a Fork +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. -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 +1. Install [surya](https://github.com/ConsenSys/surya/) and graphviz: -`surya inheritance ./src/contracts/**/*.sol | dot -Tpng > InheritanceGraph.png` +``` +npm i -g surya -and/or +apt install graphviz +``` -`surya graph ./src/contracts/middleware/*.sol | dot -Tpng > MiddlewareControlFlowGraph.png` +2. Then, run: -and/or +``` +surya inheritance ./src/contracts/**/*.sol | dot -Tpng > InheritanceGraph.png -`surya mdreport surya_report.md ./src/contracts/**/*.sol` +surya mdreport surya_report.md ./src/contracts/**/*.sol +``` ## Deployments -### M1 (Current Mainnet Deployment) +### Current Mainnet Deployment + +The current mainnet deployment is our M1 release, and is from a much older version of this repo. You can view the deployed contract addresses in [Current Mainnet Deployment](#current-mainnet-deployment) below, or check out the [`init-mainnet-deployment`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/mainnet-deployment) branch in "Releases". | Name | Solidity | Proxy | Implementation | Notes | | -------- | -------- | -------- | -------- | -------- | @@ -101,7 +100,9 @@ and/or | 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) | | -### M2 (Current Goerli Testnet Deployment) +### Current Testnet Deployment + +The current testnet deployment is from our M2 beta release, which is a slightly older version of this repo. You can view the deployed contract addresses in [Current Testnet Deployment](#current-testnet-deployment), or check out the [`goerli-m2-deployment`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/goerli-m2-deployment) branch in "Releases". | Name | Solidity | Proxy | Implementation | Notes | | -------- | -------- | -------- | -------- | -------- | @@ -109,6 +110,7 @@ and/or | Strategy: stETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xB613...14da`](https://goerli.etherscan.io/address/0xb613e78e2068d7489bb66419fb1cfa11275d14da) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) | | Strategy: rETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x8799...70b5`](https://goerli.etherscan.io/address/0x879944A8cB437a5f8061361f82A6d4EED59070b5) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) | | EigenPodManager | [`EigenPodManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/EigenPodManager.sol) | [`0xa286...df41`](https://goerli.etherscan.io/address/0xa286b84C96aF280a49Fe1F40B9627C2A2827df41) | [`0xdD09...901b`](https://goerli.etherscan.io/address/0xdD09d95bD25299EDBF4f33d76F84dBc77b0B901b) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) | +| EigenLayerBeaconOracle | [`succinctlabs/EigenLayerBeaconOracle.sol`](https://github.com/succinctlabs/telepathy-contracts/blob/main/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol) | [`0x40B1...9f2c`](https://goerli.etherscan.io/address/0x40B10ddD29a2cfF33DBC420AE5bbDa0649049f2c) | [`0x0a6e...db01`](https://goerli.etherscan.io/address/0x0a6e235c30658dbdb53147fbb199878a4e34db01) | OpenZeppelin UUPS | | EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/EigenPod.sol) | [`0x3093...C9a5`](https://goerli.etherscan.io/address/0x3093F3B560352F896F0e9567019902C9Aff8C9a5) | [`0x86bf...6CcA`](https://goerli.etherscan.io/address/0x86bf376E0C0c9c6D332E13422f35Aca75C106CcA) | - Beacon: [OpenZeppelin BeaconProxy@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol)
- Deployed pods use [UpgradableBeacon@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) | | DelayedWithdrawalRouter | [`DelayedWithdrawalRouter`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x8958...388f`](https://goerli.etherscan.io/address/0x89581561f1F98584F88b0d57c2180fb89225388f) | [`0x6070...27fe`](https://goerli.etherscan.io/address/0x60700ade3Bf48C437a5D01b6Da8d7483cffa27fe) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) | | DelegationManager | [`DelegationManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/DelegationManager.sol) | [`0x1b7b...b0a8`](https://goerli.etherscan.io/address/0x1b7b8F6b258f95Cf9596EabB9aa18B62940Eb0a8) | [`0x9b79...A99d`](https://goerli.etherscan.io/address/0x9b7980a32ceCe2Aa936DD2E43AF74af62581A99d) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) | diff --git a/docs/README.md b/docs/README.md index de41946e1..c85b40229 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,62 +1,102 @@ +[middleware-repo]: https://github.com/Layr-Labs/eigenlayer-middleware/ + ## EigenLayer M2 Docs -**EigenLayer M2** extends the functionality of EigenLayer M1 (which is live on both Goerli and mainnet). M2 is currently on the Goerli testnet, and will eventually be released on mainnet. +**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]. -M1 enables very basic restaking: users that stake ETH natively or with a liquid staking token can opt-in to the M1 smart contracts, which currently support two basic operations: deposits and withdrawals. +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/). -M2 adds several features, the most important of which is the basic support needed to create an AVS. The M2 release includes the first AVS, EigenDA . The other features of M2 support AVSs and pad out existing features of M1. A short list of new features includes: -* Anyone can register as an operator -* Operators can begin providing services to an AVS -* Stakers can delegate their stake to a single operator -* Native ETH restaking is now fully featured, using beacon chain state proofs to validate withdrawal credentials, validator balances, and validator exits -* Proofs are supported by beacon chain headers provided by an oracle (See [`EigenPodManager` docs](./core/EigenPodManager.md) for more info) +#### Contents + +* [System Components](#system-components) +* [Roles and Actors](#roles-and-actors) ### System Components -**EigenPodManager**: +#### EigenPodManager -| File | Type | Proxy? | Goerli | -| -------- | -------- | -------- | -------- | -| [`EigenPodManager.sol`](../src/contracts/pods/EigenPodManager.sol) | Singleton | Transparent proxy | TODO | -| [`EigenPod.sol`](../src/contracts/pods/EigenPod.sol) | Instanced, deployed per-user | Beacon proxy | TODO | -| [`DelayedWithdrawalRouter.sol`](../src/contracts/pods/DelayedWithdrawalRouter.sol) | Singleton | Transparent proxy | TODO | +| 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 certain withdrawals from an `EigenPod`. This is primarily to add a stopgap against a hack being able to instantly withdraw funds (note that most withdrawals are processed via the `DelegationManager`, not the `DelayedWithdrawalRouter`). +* 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**: +#### StrategyManager -| File | Type | Proxy? | Goerli | -| -------- | -------- | -------- | -------- | -| [`StrategyManager.sol`](../src/contracts/core/StrategyManager.sol) | Singleton | Transparent proxy | TODO | -| [`StrategyBaseTVLLimits.sol`](../src/contracts/strategies/StrategyBaseTVLLimits.sol) | 3 instances (for cbETH, rETH, stETH) | Transparent proxy | TODO | +| 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 each of the 3 LST-specific strategies, and manages accounting+interactions between users with restaked LSTs and the `DelegationManager`. -* `StrategyBaseTVLLimits` is deployed as three separate instances, one for each supported LST (cbETH, rETH, and stETH). 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. +* `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**: +#### DelegationManager -| File | Type | Proxy? | Goerli | -| -------- | -------- | -------- | -------- | -| [`DelegationManager.sol`](../src/contracts/core/DelegationManager.sol) | Singleton | Transparent proxy | TODO | +| 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). -**Slasher**: +#### 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. -| File | Type | Proxy? | Goerli | -| -------- | -------- | -------- | -------- | -| [`Slasher.sol`](../src/contracts/core/Slasher.sol) | - | - | - | +*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 now details. -The `Slasher` is deployed, but will remain completely paused/unusable during M2. No contracts interact with it, and its design is not finalized. \ No newline at end of file +*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) \ No newline at end of file diff --git a/docs/RolesAndActors.md b/docs/RolesAndActors.md deleted file mode 100644 index 2431c0e30..000000000 --- a/docs/RolesAndActors.md +++ /dev/null @@ -1,50 +0,0 @@ -## Roles and Actors - -This document describes the different roles and actors that exist in EigenLayer M2, and provides some insight into how they interact with M2's core components. - -### Stakers - -A **Staker** is any party who has assets deposited (or "restaked") into EigenLayer. Currently, these assets can be: -* Native beacon chain ETH (via the EigenPods subsystem) -* Liquid staking tokens: cbETH, rETH, stETH (via the Strategies subsystem) - -Stakers can restake any combination of these. That is, a Staker may hold ALL of these assets, or only one of them. - -Once they've deposited, Stakers can delegate their stake to an Operator via the `DelegationManager`, or they can become an Operator by delegating to themselves. - -*Flows:* -* Depositing into EigenLayer -* Delegating to an Operator -* Withdrawing out of EigenLayer - -*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 - -### Operators - -An **Operator** is a user who helps run the software build on top of EigenLayer. 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:* -* Registering as an operator -* Opting in to a service -* Exiting from a service - -*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) - -### Supporting Roles - -#### Pausers - -TODO - -#### Strategy Whitelister - -TODO - -#### Multisigs and Owners - -TODO - diff --git a/docs/core/DelegationManager.md b/docs/core/DelegationManager.md index f683685b5..9b2972c1e 100644 --- a/docs/core/DelegationManager.md +++ b/docs/core/DelegationManager.md @@ -1,6 +1,6 @@ ## DelegationManager -| File | Type | Proxy? | +| File | Type | Proxy | | -------- | -------- | -------- | | [`DelegationManager.sol`](../../src/contracts/core/DelegationManager.sol) | Singleton | Transparent proxy | diff --git a/docs/core/EigenPodManager.md b/docs/core/EigenPodManager.md index 1fe109fbf..1bfb3c334 100644 --- a/docs/core/EigenPodManager.md +++ b/docs/core/EigenPodManager.md @@ -7,7 +7,7 @@ * Beacon chain proofs * Stake / proof / withdrawal flows --> -| File | Type | Proxy? | +| 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 | @@ -23,7 +23,7 @@ The `EigenPodManager` is the entry point for this process, allowing Stakers to d * `EigenPod.verifyBalanceUpdate`: effective balance * `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/)). +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 diff --git a/docs/core/StrategyManager.md b/docs/core/StrategyManager.md index 26b39171d..0ab265501 100644 --- a/docs/core/StrategyManager.md +++ b/docs/core/StrategyManager.md @@ -1,6 +1,6 @@ ## StrategyManager -| File | Type | Proxy? | +| 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 | diff --git a/docs/outdated/EigenLayer-delegation-flow.md b/docs/outdated/EigenLayer-delegation-flow.md deleted file mode 100644 index 701ef03f4..000000000 --- a/docs/outdated/EigenLayer-delegation-flow.md +++ /dev/null @@ -1,52 +0,0 @@ -# Delegation Flow - -In the delegation flow there are two types of users: Stakers and Operators. Stakers are users who delegate their staked collateral to Operators. Operators receive delegated stakes from Stakers and run services built on top of EigenLayer. 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 - -An Operator can register themselves in the system by calling the registerAsOperator function, providing their OperatorDetails which include the earningsReceiver (the address to receive the operator's earnings), delegationApprover (the address that approves delegations to the operator), and stakerOptOutWindowBlocks (the number of blocks for which a staker can opt out of delegating to the operator). In order to be delegated _to_, an operator must have first called `DelegationManager.registerAsOperator`. Once registered, an operator cannot deregister and is considered permanently delegated to themselves. - -When an operator registers in EigenLayer, the following flow of calls between contracts occurs: - -```mermaid -sequenceDiagram - participant Operator as Operator - participant DelegationManager as DelegationManager - Operator->>DelegationManager: registerAsOperator(operatorDetails, metadataURI) - DelegationManager->>Operator: OperatorRegistered event -``` - -1. The would-be operator calls `DelegationManager.registerAsOperator`, providing their `OperatorDetails` and an (optional) `metadataURI` string as an input. The DelegationManager contract stores the `OperatorDetails` provided by the operator and emits an event containing the `metadataURI`. The `OperatorDetails` help define the terms of the relationship between the operator and any stakers who delegate to them, and the `metadataURI` can provide additional details about the operator. - All of the remaining steps (2 and 3) proceed as outlined in the delegation process below; - -## 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` - -If a staker tries to delegate to someone who has not previously registered as an operator, their transaction will fail. - -In either case, the end result is the same, and the flow of calls between contracts looks identical: - -```mermaid -sequenceDiagram -participant Staker as Staker -participant DelegationManager as DelegationManager -Staker->>DelegationManager: delegateTo(operator, approverSignatureWithExpirhy, approverSalt) -DelegationManager->>Staker: StakerDelegated event -``` - -```mermaid -sequenceDiagram -participant Staker as Staker -participant DelegationManager as DelegationManager -Staker->>DelegationManager: delegateToWithSignature(staker, operator, stakerSignatureWithExpiry, approverSignatureWithExpiry, approverSalt) -DelegationManager->>Staker: StakerDelegated event -``` - -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 diff --git a/docs/outdated/EigenLayer-deposit-flow.md b/docs/outdated/EigenLayer-deposit-flow.md deleted file mode 100644 index 16497c89f..000000000 --- a/docs/outdated/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 a Strategy through the StrategyManager, and depositing "Beacon Chain ETH" (or proof thereof) through the EigenPodManager. - -## Depositing Into a 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.getBlockRootAtTimestamp` (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/outdated/EigenLayer-tech-spec.md b/docs/outdated/EigenLayer-tech-spec.md deleted file mode 100644 index e2805a44e..000000000 --- a/docs/outdated/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 "**optimistically 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 structure 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 their `OperatorDetails` and (optionally) providing a `metadataURI` to help structure and explain 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`. By design, registered as an operator, an address can never "deregister" as an operator in EigenLayer. -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/outdated/EigenLayer-withdrawal-flow.md b/docs/outdated/EigenLayer-withdrawal-flow.md deleted file mode 100644 index 503289ff2..000000000 --- a/docs/outdated/EigenLayer-withdrawal-flow.md +++ /dev/null @@ -1,36 +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. - -## 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. diff --git a/docs/outdated/EigenPods.md b/docs/outdated/EigenPods.md deleted file mode 100644 index 305ea271f..000000000 --- a/docs/outdated/EigenPods.md +++ /dev/null @@ -1,84 +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. The EigenPods subprotocol allows entities that own validators that are a part of Ethereum's consensus to repoint their withdrawal credentials (explained later) to contracts in the EigenPods subprotocol. - -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. - -Any user that wants to participate in native restaking first deploys an EigenPod contract by calling createPod() on the EigenPodManager. This deploys an EigenPod contract which is a BeaconProxy in the Beacon Proxy pattern. The user is called the pod owner of the EigenPod they deploy. - -This flow is live on Mainnet. - -## 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 Ethereum 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 Ethereum 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 effective balance is proven to be greater than or equal to `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`, then the EigenPod will pass the validator's effective balance value through its own hysteresis calculation (see [here](#hysteresis)), which effectively underestimates the effective balance of the validator by 1 ETH. Then a call is made to the EigenPodManager to forward a call to the StrategyManager, crediting the staker with those shares of the virtual beacon chain ETH strategy. - -### Effective Restaked Balance - Hysteresis {#hysteresis} -To convey to EigenLayer that an EigenPod has validator(s) restaked on it, anyone can submit a proof against a beacon chain state root the proves that a validator has their withdrawal credentials pointed to the pod. The proof is verified and the EigenPod calls the EigenPodMananger that calls the StrategyManager which records the validators proven balance run through the hysteresis function worth of ETH in the "beaconChainETH" strategy. Each EigenPod keeps track of all of the validators by the hash of their public key. For each validator, their validator index and current balance in EigenLayer is kept track of. - -### Proofs of Validator Balance Updates -EigenLayer pessimistically assumes the validator has less ETH that they actually have restaked in order for the protocol to have an accurate view of the validator's restaked assets even in the case of an uncorrelated slashing event, for which the penalty is >=1 ETH. -In the case that a validator's balance drops close to or below what is noted in EigenLayer, AVSs need to be notified of that ASAP, in order to get an accurate view of their security. -In the case that a validator's balance, when run through the hysteresis function, is lower or higher than what is restaked on EigenLayer, anyone is allowed to permissionlessly prove the new balance of the validator, triggering an update in EigenLayer. If the proof is valid, the StrategyManager decrements the pod owners beacon chain ETH shares by however much is staked on EigenLayer and adds the new proven stake, i.e., the strategyManager's view of the staker's shares is an accurate representation of the consensus layer as long as timely balance update proofs are submitted. - -### 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 fulfilled 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 2 cases, each handled slightly differently: - -1. If the withdrawn amount is greater than `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`, then `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR` is held for processing through EigenLayer's normal withdrawal path, while the excess amount above `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR` is marked as instantly withdrawable. - -2. If the withdrawn amount is less than `MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR`, then the amount being withdrawn is held for processing through EigenLayer's normal withdrawal path. - -### The EigenPod Invariant -The core complexity of the EigenPods system is to ensure that EigenLayer continuously has an accurate picture of the state of the beacon chain balances repointed to it. In other words, the invariant that governs this system is: -`sum(shares_in_beaconChainETHStrategy) * WEI_TO_GWEI = sum(validator_restakedBalanceGwei) + withdrawableRestakedExecutionLayerGwei` - -Essentially this states that the podOwner's shares in the strategyManager's beaconChainETHStrategy must be equal to sum of all the podOwner's restakedBalanceGwei + any withdrawableRestakedExecutionLayerGwei they may have after proving full withdrawals. - - -![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. - -### verifyWithdrawal -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/outdated/Guaranteed-stake-updates.md b/docs/outdated/Guaranteed-stake-updates.md deleted file mode 100644 index be889a97f..000000000 --- a/docs/outdated/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 its 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/outdated/Middleware-registration-operator-flow.md b/docs/outdated/Middleware-registration-operator-flow.md deleted file mode 100644 index 4faf78bf1..000000000 --- a/docs/outdated/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/src/contracts/libraries/Merkle.sol b/src/contracts/libraries/Merkle.sol index 0f2901705..9da153a5f 100644 --- a/src/contracts/libraries/Merkle.sol +++ b/src/contracts/libraries/Merkle.sol @@ -152,7 +152,7 @@ library Merkle { //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++) { + 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 @@ -160,7 +160,7 @@ library Merkle { //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++) { + 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