diff --git a/README.md b/README.md index 673fd0932d..bfe0c0ecdd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ + # 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 (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 reusable across different applications built on top of EigenLayer. diff --git a/certora/specs/core/DelegationManager.spec b/certora/specs/core/DelegationManager.spec index e0d545887e..a24fc29b7f 100644 --- a/certora/specs/core/DelegationManager.spec +++ b/certora/specs/core/DelegationManager.spec @@ -6,6 +6,9 @@ methods { 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); diff --git a/certora/specs/core/StrategyManager.spec b/certora/specs/core/StrategyManager.spec index 9af8f85625..ec7f7b9cd4 100644 --- a/certora/specs/core/StrategyManager.spec +++ b/certora/specs/core/StrategyManager.spec @@ -10,6 +10,9 @@ methods { 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); diff --git a/certora/specs/pods/EigenPodManager.spec b/certora/specs/pods/EigenPodManager.spec index 4d5e1afbe7..b7a2802fac 100644 --- a/certora/specs/pods/EigenPodManager.spec +++ b/certora/specs/pods/EigenPodManager.spec @@ -6,6 +6,9 @@ methods { 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); diff --git a/docs/outdated/AVS-Guide.md b/docs/experimental/AVS-Guide.md similarity index 96% rename from docs/outdated/AVS-Guide.md rename to docs/experimental/AVS-Guide.md index e56d38fb24..7de4c7e34e 100644 --- a/docs/outdated/AVS-Guide.md +++ b/docs/experimental/AVS-Guide.md @@ -1,16 +1,21 @@ [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 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 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) +![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. 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. @@ -47,7 +52,7 @@ In order for any EigenLayer operator to be able to opt-in to an AVS, EigenLayer 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* @@ -70,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: diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index f53455754f..17615d2e15 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -245,9 +245,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg (IStrategy[] memory strategies, uint256[] memory shares) = getDelegatableShares(staker); - // push the operator's new stake to the StakeRegistry - _pushOperatorStakeUpdate(operator); - // emit an event if this action was not initiated by the staker themselves if (msg.sender != staker) { emit StakerForceUndelegated(staker, operator); @@ -607,16 +604,19 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg podOwner: staker, shares: withdrawal.shares[i] }); - currentOperator = delegatedTo[staker]; + address podOwnerOperator = delegatedTo[staker]; // Similar to `isDelegated` logic - if (currentOperator != address(0)) { + if (podOwnerOperator != address(0)) { _increaseOperatorShares({ - operator: currentOperator, + operator: podOwnerOperator, // the 'staker' here is the address receiving new shares staker: staker, strategy: withdrawal.strategies[i], shares: increaseInDelegateableShares }); + + // push the operator's new stake to the StakeRegistry + _pushOperatorStakeUpdate(podOwnerOperator); } } else { strategyManager.addShares(msg.sender, withdrawal.strategies[i], withdrawal.shares[i]); @@ -687,9 +687,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg strategy: strategies[i], shares: shares[i] }); - - // push the operator's new stake to the StakeRegistry - _pushOperatorStakeUpdate(operator); } // Remove active shares from EigenPodManager/StrategyManager @@ -709,6 +706,11 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg unchecked { ++i; } } + // Push the operator's new stake to the StakeRegistry + if (operator != address(0)) { + _pushOperatorStakeUpdate(operator); + } + // Create queue entry and increment withdrawal nonce uint256 nonce = cumulativeWithdrawalsQueued[staker]; cumulativeWithdrawalsQueued[staker]++;