You Could've Invented EigenLayer
In this blog post, we will take you through the evolution of the protocol, by covering how EigenLayer's architecture emerged from the initial concept.
This blog is inspired by David Philipsonβs series on account abstraction. Special thanks to Noam Horowitz, Shiva, NashQ, Mike Neuder, and Sina from the community for comments and feedback on the article.
While many people are familiar with the terms restaking and EigenLayer, only a few are aware that our core contracts consist of thousands of lines of code with the following architecture.
Where did the complexity of a seemingly simple idea come from?
In this blog post, we will take you through the evolution of the protocol, by covering how EigenLayer's current complex architecture emerged from the initial concept.
The target audience for this post is individuals who have a basic understanding of smart contracts and have heard of EigenLayer or restaking.
Now, let's dive in.
End Goal: Making building infrastructure easy
First, let's preface with the problem EigenLayer is trying to solve. If you are already familiar with this part, move to later sections.
Developers who build decentralized infrastructures on Ethereum face the challenge of establishing their own economic security. While Ethereum provides economic security for smart contract protocols, infrastructures like bridges or sequencers require their own economic security to enable a distributed network of nodes to reach consensus.
Consensus mechanisms are essential for facilitating interactions among these nodes, whether it's an L1, an oracle network, or a bridge.
Proof of work is energy-intensive, proof of authority is overly centralized, and as a result, proof of stake (PoS) has emerged as the prevailing consensus mechanism for most infrastructure projects.
However, bootstrapping a new PoS network is hard.
Firstly, it is difficult to identify where the stakers (people who provide stake) are located. There is no single place where developers can find stakers.
Secondly, stakers must invest a significant amount of money to obtain a stake in the new network, usually by buying the network's native token, which is generally volatile and hard to get.
Thirdly, stakers must forgo other rewards opportunities, such as the 5% rewards offered by Ethereum.
Lastly, the current security model is undesirable as the cost to corrupt any dApp is simply the cost needed to compromise its weakest infrastructural dependency.
EigenLayer was created to address these issues.
- It serves as a platform connecting stakers and infrastructure developers.
- Stakers can offer economic security using any token.
- Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards.
- Through restaking, EigenLayer pools security instead of fragmenting it.
EigenLayer generalizes the concept of providing economic security.
Goal: Creating a platform connecting stakers and infrastructure developers
EigenLayer is the platform where stakers can provide stake for any infrastructure project, and infrastructure projects can pitch to potential stakers on EigenLayer. The backbone of this platform is enabling stakers to make credible commitments for different infrastructures.
What is this credible commitment?
These commitments are applicable to all PoS systems, not just EigenLayer. L1 stakers generally commit to following the protocol rules and risk losing their stake if they sign conflicting blocks at the same block height.
Infrastructure developers build the infrastructure logic and software, while stakers provide stake to secure the infrastructure. The stake serves as a commitment to the users of the infrastructures. This commitment is for the smooth running of the protocol and against specific misbehaviors.
Conceptually, when a project has $100m stake behind it, it means that if it deviates from its commitment and behaves maliciously, some portion of that $100m will be slashed. For simplicity, "slashed" can be thought of as burning that money.
The higher this number is, the more security and guarantees it provides to its users.
If we permit stakers to allocate stakes to various commitments, we can then create a user-friendly interface on top to make a platform.
We require a trustless and programmable platform to enforce different staker commitments, and Ethereum is the most suitable for this. Additionally, Ethereum holds the largest stake, which aids in bootstrapping the staker-side market.
The goal here is that a staker should be able to secure, for example, a bridge protocol through EigenLayer, on Ethereum. If a staker were to maliciously forge a message on Ethereum and transmit it to Gnosis, anyone can submit proof of this and slash the staker.
Since the staker's stake and commitment enforcement occur on Ethereum, the slashing logic is also implemented on Ethereum as smart contracts. If the staker breaches their commitment, anyone can present proof to the slashing contract and forfeit the malicious staker's stake.
This forms the foundation of EigenLayer - any staker can make credible commitments to any infrastructure protocol.
Initial Design for EigenLayer
Now, let's try to implement this. We'll start with the simplest design: a staker stakes tokens into a contract, which includes a function that allows for the slashing of the staker's tokens if a proof is submitted and meets the criteria. The user can then withdraw their balance.
Other stakers can also stake into this contract, adding to the security of the infrastructure. We'll refer to this contract as TokenPool
.
To provide some clarity, here are some definitions for the terms used in this article:
- Staker: anyone who provides tokens into EigenLayer.
- Token: any kind of token; for simplicity, think of an ERC20 token for now.
- TokenPool: a smart contract that holds the stakers' tokens.
- Slashing: removing the staker's access to their staked tokens.
The interface of this TokenPool
can be represented as the following:
contract TokenPool {
mapping(address => uint256) public balance;
function stake(uint256 amount) public;
function withdraw() public;
function slash(address staker, ??? proof) public;
}
Does this fulfill the requirement for any staker to provide stake for any infrastructure? Yes!
- Staker can pledge stake to a specific commitment (defined in the
slash
function) - Slashed if found malicious (breaks from the commitment).
- Withdraw from the system.
- Stakers can offer economic security using any token.
- Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards.
- Through restaking, EigenLayer pools security instead of fragmenting it.
Goal: Reduce opportunity cost for stakers
When new infrastructure protocols launch, they typically introduce a native token. However, convincing stakers to hold this token is still challenging.
First, the native token's value can fluctuate significantly, making it difficult to persuade stakers to acquire it.
Second, there is competition from other protocols offering higher rewards rates, which makes it necessary for the protocol to provide compelling incentives to attract stakers.
EigenLayer addresses these challenges by allowing stakers to stake any token and earn multiple rewards simultaneously. Since EigenLayer operates on the Ethereum network, we can leverage existing Ethereum primitives to achieve this.
One approach is to enable stakers to provide stake in a liquid staking token (LST). By using LST, stakers can retain their native ETH rewards while securing other infrastructures and potentially earning additional rewards.
β Stakers can offer economic security using any token.
β Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards.
- Through restaking, EigenLayer pools security instead of fragmenting it.
Goal: Pooling security through restaking
EigenLayer's final goal is to address the issue of fragmented security.
Currently, if an application's security relies on multiple infrastructure dependencies, a single economically compromised dependency can threaten the entire application's security.
This problem becomes worse when the economic security of these dependencies is isolated. These isolated points form economic "honey pots" that attract potential attacks.
Therefore, instead of maintaining several separate economic security pools for different infrastructures, it would be beneficial to consolidate these security measures. Doing so could progressively improve the economic security of all dependencies at once.
Implementing pooled security
Given the model we currently have, how can we incorporate pooled security into it?
As it is, each TokenPool
has its own unique slashing condition embedded within the contract. This implies that for each AVS, a separate TokenPool
contract needs to be coded. If a staker wishes to participate in two AVSs simultaneously, they would need a new TokenPool
. This new pool must incorporate the slash
functions from both AVSs.
If two slashing conditions can simultanesouly slash a staker, the staker is making two credible commitments at the same time.
A more efficient approach would allow the staker to participate in different AVSs without the need to create new TokenPool
contracts or modify the slash
function.
This can be accomplished by relocating the slash
function into a different contract, termed the "slasher". Here is how they would interact:
The new interface for the TokenPool
would look like this. (slash
is no longer there.)
contract TokenPool {
mapping(address => uint256) public balance;
mapping(address => address[]) public slasher;
function stake(uint256 amount) public;
function withdraw() public;
function enroll(address slasher) onlyOwner;
}
We still maintain the stake
, withdraw
, and balance
functions and variables. In addition to these, we've introduced a new function and a new variable. The variable, slasher
, monitors each staker's currently enrolled AVSs. The function, enroll
, allows a staker to participate in AVSs
Enrolling into an AVS therefore is giving a slasher
the ability to slash your stake.
With this modification, after staking into the TokenPool
, a staker can join a specific AVS by calling the enroll
function. This function will incorporate the AVS-specific slasher contract into the slasher
mapping.
During the withdrawal process, the TokenPool
contract can ask each slasher
to determine if the staker is eligible to withdraw. This verification is done using the isSlashed
function in the slasher
contract. If isSlashed
for this staker is TRUE
, the staker cannot withdraw their stakes as they have been slashed.
contract slasher {
mapping(address => bool) public isSlashed;
function slash(address staker, ??? proof) public;
}
The slash
function contains the slashing logic for each AVS. While the slashing logic might differ across various AVSs, upon successful execution of the slash
function, the function will set the isSlashed
variable to true for the questioned staker.
β Stakers can offer economic security using any token.
β Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards.
β Through restaking, EigenLayer pools security instead of fragmenting it.
With pooled security implemented, we have created the minimum viable EigenLayer! The minimum viable version acts as a platform connecting stakers and AVS developers. Stakers can now stake any token without compromising other rewards, while AVS developers can utilize the shared security pool to safeguard various types of new infrastructure applications.
In the upcoming section, we will improve the minimum viable version to accommodate additional use cases and make EigenLayer future proof.
Goal: Staker doesn't have to operate
In the minimum viable design, stakers can stake tokens for various AVSs. However, some stakers may not have the capability or desire to personally handle the operations that secure the AVS. This is similar to ETH holders who may prefer not to operate their own validators.
Our goal is to distinguish between these two roles. Stakers provide tokens for the economic security of each AVS, while operators are individuals who run software to ensure the operational security of each AVS.
We can make a slight adjustment to the TokenPool
contract to include a delegation process. This allows a staker's token balance to be represented by the operator they delegate to. However, if the operator violates the AVS commitment, the staker's tokens will be slashed.
contract TokenPool {
mapping(address => uint256) public stakerBalance;
mapping(address => uint256) public operatorBalance;
mapping(address => address) public delegation;
mapping(address => address[]) public slasher;
function stake(uint256 amount) public;
function withdraw() public;
function delegateTo(address operator) public;
function enroll(address slasher) operatorOnly;
function exit(address slasher) operatorOnly;
}
We've divided the balance variable into two distinct parts: one for the operator and one for the staker. Additionally, we've introduced a delegation
mapping to record the delegation relationships between stakers and operators.
We've also made a minor modification to the slasher mapping. Now, it gives the slasher contract the authority to slash the operators instead of the stakers.
contract slasher {
mapping(address => bool) public isSlashed;
function slash(address operator, ??? proof) public;
}
In terms of functions, we've incorporated delegateTo
, enabling stakers to delegate their tokens to operators. The enroll
function has been redesigned to be operator-specific, allowing the operator to enroll in different AVSs, rather than the stakers.
During the withdraw
process, the contract initially identifies the operator to whom the staker is delegated. It then checks each slasher
contract linked to that operator address. If any slasher
reports that the operator is slashed, the withdrawal process is immediately halted.
Maintaining staker autonomy
If the operator is the only one who can opt into AVSs, how can stakers decide which AVSs they want their stake to secure? When a staker is also an operator and self-delegates their stake, they maintain full control over the AVS they operate. But what if a staker can't run it on their own and requires another operator to manage these services? Doesn't this give the operator total control over AVS selection?
We can allow the staker to select the AVS through a simple workaround. The operator can maintain different addresses for various AVS combinations. Each address represents a potential combination of AVSs supported by the operator.
For instance, if an operator supports two AVSs, they can have three addresses: one for AVS 1, another for AVS 2, and a third for running both.
If a staker trusts the operator and only wants to secure AVS 1, they can delegate to the appropriate operator's address.
Through this method, we enable stakers to fully control their stake. At the same time, they can delegate off-chain responsibilities to operators.
Modularizing the operator
The TokenPool
contract is quite bulky right now. We will take the operator-specific functions from the TokenPool
and put it into a separate contract, DelegationManager
.
Now, the TokenPool contract would look like the following:
contract TokenPool {
mapping(address => uint256) public stakerBalance;
function stake(uint256 amount) public;
function withdraw() public;
}
The TokenPool
contract will only track the balance of each staker. The tracking of AVS and enrolling will be handled by the DelegationManager
.
contract DelegationManager {
mapping(address => uint256) public operatorBalance;
mapping(address => address) public delegation;
mapping(address => address[]) public slasher;
function delegateTo(address operator) public;
function enroll(address slasher) operatorOnly;
function exit(address slasher) operatorOnly;
}
Lastly, the slasher contract remains unchanged. It tracks which operator is slashed at the current time for each AVS.
contract slasher {
mapping (address => bool) isSlashed;
function slash(address operator, ??? proof);
}
After cleaning the contract structure and modularizing each component, the architecture would now look like this:
Goal: Support more tokens
The design we've developed so far only supports staking one token because we only maintain one mapping for stakers.
We can address this by adopting a LP-share based model of accounting and creating token-specific TokenPool
.
To do this, we will create a new contract called TokenManager
. TokenManager
will be the place where stakers stake and withdraw their tokens.
Underneath the TokenManager
, each token will have a TokenPool
. The TokenManager
will act as the accounting hub for all tokens; it will not store any tokens itself. Each TokenPool
will hold its own corresponding tokens.
When a user stakes a token, it is processed by the TokenManager
. The TokenManager
then invokes the stake
function of the relevant TokenPool
associated with that token. The user's tokens are transferred to the TokenPool
. By the end of the function, totalShares
and stakerPoolShares
are updated to reflect the new stake. totalShares
keeps track of the total number of shares issued by the TokenPool
, while stakerPoolShares
records the number of shares held by each individual staker in each TokenPool
.
The interface for each contract would look like this.
contract TokenManager {
mapping(address => address) tokenPoolRegistry;
mapping(address => mapping(address => uint256)) stakerPoolShares;
function stakeToPool(address pool, uint256 amount);
function withdrawFromPool(address pool);
}
contract TokenPool {
uint256 public totalShares;
function stake(uint256 amount) TokenManagerOnly;
function withdraw(uint256 shares) TokenManagerOnly;
}
A minor change will be made to the DelegationManager as well to reflect this change. Instead of tracking a simple mapping of each operator's balance, the DelegationManager
will track how much shares does the operator have for each TokenPool
, similar to TokenManager
.
contract DelegationManager {
// ...
mapping(address => mapping(address => uint256)) operatorPoolShares;
// ...
}
Now, under the same core architecture, stakers can stake any token into EigenLayer to secure other AVSs.
Goal: Expand AVS designs
Consider the following situation: a staker withdraws their stakes right after engaging in slashable behavior, but before others can slash their assets.
For example, let's say there's an operator who is also a staker, and they act maliciously in an AVS. Before anyone else can slash onchain, the operator withdraws their stake from the EigenLayer contracts. This is possible because, at the time of withdrawal, the slasher
has not yet been updated to slash their assets. Consequently, the AVS can no longer slash the malicious operator/staker since there are no more tokens to slash with.
Therefore, an AVS that is "safe" would need a slashing contract capable of freezing malicious operators in the same block where the incident occurred. This limitation greatly restricts AVS designs, rendering most of them unsafe.
One solution is to incorporate an unbonding period. Instead of enabling stakers to instantly withdraw their stake, we introduce a delay in the withdrawal process known as the unbonding period. After which, the staker can withdraw like before.
When a staker decides to withdraw from the system, their request is placed in a queue. This queued withdrawal is only processed after the operator's longest unbonding period has expired. This is because an operator may manage multiple AVSs, but a staker's withdrawal can only align with one unbonding period. For security reasons, the system sets the unbonding period to the longest one.
For example, if a staker delegates their stake to an operator participating in three AVSs with unbonding periods of six, five, and seven days, they must wait seven days after requesting a withdrawal from EigenLayer to access their stakes. This is because seven days is the longest of the three periods.
After the seven-day period concludes, the staker can withdraw their stakes. However, if the operator to whom they delegated becomes slashed during this time, the pending withdrawal will be stopped as well.
To incorporate this change, the DelegationManager
would need to track this unbonding period for each operator and update it whenever the operator enrolls into a new AVS.
contract DelegationManager {
// ...
mapping(address => uint256) public unbondingPeriod;
// ...
}
After tracking the unbonding period for each operator, we will also incorporate this value into the staker withdraw process. The withdraw process consists of two steps:
- Stakers queue a withdrawal.
- After the unbonding period has passed, the staker can complete their withdrawal.
The updated interface would look like the following
contract TokenManager {
mapping(address => address) public tokenPoolRegistry;
mapping(address => mapping(address => uint256)) public stakerPoolShares;
mapping(address => uint256) public withdrawalCompleteTime;
function stakeToPool(address pool, uint256 amount) public;
function queueWithdrawal(address pool) public;
function completeWithdrawal(address pool) public;
}
When a staker queues a withdrawal, the TokenManager
will verify with DelegationManager
whether the staker's delegated operator is slashed. If the operator is not slashed, the TokenManager
will update the staker's withdrawalCompleteTime
according to the unbondingPeriod
from the DelegationManager
and the current time.
After the unbonding period, the staker can finalize its withdrawal through completeWithdrawal
. The function will verify if the withdrawalCompleteTime
has passed. If it has, the staker's token will be transferred out following the same process as before.
Modularize the slashers
Since we are making the slashing mechanism safe, let's also try to make it more modular and efficient.
Currently, during the withdrawal process, TokenManager
would need to check with each individual slasher
to see if the operator is slashed. This adds gas overhead to stakers and could significantly lower the rewards for smaller stakers.
Moreover, as AVS developers typically design slashers, modularizing this particular component could simplify the development process for individual AVSs.
Like the TokenManager
, we will utilize a two-part design for the slashing mechanism. SlasherManager
maintains the status of each operator. Individual slasher
will handle the slashing logic for each AVS.
contract SlasherManager {
mapping(address => bool) public isSlashed;
mapping(address => mapping(address => bool)) public canSlash;
function enrollAVS(address slasher) operatorOnly;
function exitAVS(address slasher) operatorOnly;
}
The SlasherManager
allows an operator to enroll in an AVS by adding the AVS slasher contract as one of the slashers capable of slashing the operator. This permission of "who can slash who" is tracked by the canSlash
variable.
contract slasher {
function slash(address operator, ??? proof) public;
}
The slasher
will be AVS-specific and most-likely be developed by the AVS developers. It will interact with the SlasherManager
to update the status of different operators.
We've Invented EigenLayer!
To recap: EigenLayer aims to simplify infrastructure building. We started with four main goals:
- Connect stakers and infrastructure developers through a platform.
- Allow stakers to provide economic security using any token.
- Enable stakers to restake their stake and earn native ETH rewards while contributing to the security of other infrastructures.
- Pool security through restaking instead of fragmenting it.
After several iterations, we developed three core components: TokenManager
, DelegationManager
, and SlasherManager
. Each component has a specific function:
TokenManager
: Handles staking and withdrawals for stakers.DelegationManager
: Allows operators to register and track operator shares.SlasherManager
: Provides AVS developers with the interface to determine the slashing logic.
These core components also communicate with each other to ensure the safety of the entire system.
In addition to these core contracts, there are many other functions and contracts that enhance the entire stack. These additional functionalities support various AVS designs, simplify offchain technical complexity, and reduce gas fees for users and operators.
To learn more about these other functions, you can visit our public repository at: https://github.com/Layr-Labs/eigenlayer-contracts
Bonus 1: Who's trusting who?
When a system is modular, it can be challenging to track the trust assumptions within the protocol. Therefore, it is essential to explicitly outline the trust assumptions among the participants involved in the protocol.
In EigenLayer, there are three main agents: stakers, operators, and AVS developers.
The operator is relying on the AVS developer to accurately code the client software and the onchain slashing condition. If there are bugs in the AVS softwares, at best, the operator might miss potential fee payments. At worst, the operator could be slashed for all their stakes.
Given the significant amount of value at stake, it is important to ensure that the entire system has training wheels before it is put to the test.
The veto committee serves as these training wheels. It has the power to reverse slashing resulted from non-malicious behaviors. The veto committee is a mutually trusted party among the stakers, operators, and AVS developers.
This way, the trust assumptions placed on the AVS developers can be removed. Even if there's a software bug inside the AVS, the staker and operator won't be penalized.
The staker is trusting the operator they are delegating to. If the operator misbehaves, the staker could miss out on potential fee payments or even lose their entire stake. This trust assumption is the same for existing validating services such as Binance Staking and other staking services.
The AVS developers rely on the operators to act honestly. If the operators don't, the AVS service will degrade significantly, leading to customer loss and other consequences.
With the veto committee, among the participants, the trust assumptions are the following:
- Stakers trust operators to behave honestly, and misbehavior could lead to slashing.
- AVS developers trust operators to operate the AVS software honestly.
- Stakers, operators and AVS developers trust the veto committee for reversing slashing.
Bonus 2: Native Restaking
So far, we've discussed staking LST for restaking. But what if you don't want to stake into EigenLayer through a liquid staking protocol? You can start participate in EigenLayer through native restaking.
Let's define native restaking: it is the process of using ETH within a validator for additional commitments. If the validator deviates from the commitment, they will lose the ETH held within their validator.
The challenge here is that the ETH inside these validators is not represented as ERC20 tokens. Instead, the ETH exists on the beacon chain. If you're not familiar with the execution layer or the consensus layer (beacon chain), this explainer is a great resource to get you up to speed.
To solve this issue, we can use EigenPod
to track Ethereum validator balances and slash them if necessary.
EigenPod
functions as a virtual accounting system. With EigenPod
, we can monitor the ETH balance for each restaked validator.
At a high level, EigenPods
handle the withdrawal process for validators. When a validator withdraws their stakes from EigenLayer, the ETH first passes through EigenPod
to check if the validator has been slashed. If the validator has been slashed, the tokens will be frozen within the EigenPod
contract, effectively slashing them.
Implementing EigenPod
Implementing EigenPod
is tricky because Ethereum validator balances are stored on the beacon chain and we cannot access the beacon chain data on the execution layer.
To overcome this issue, we utilize an oracle to relay the beacon chain state root to the execution layer. By obtaining the beacon state root, we can access validator balances by providing the corresponding merkle proof.
With EIP-4788 live, we can remove this oracle and query the beacon root directly from the execution layer.
To encapsulate the accounting system, we will incorporate a model similar to the TokenPool
and TokenManager
model, to modularize the native restaking system. Each EigenPod
will handle the withdrawal process for one validator. The EigenPodManager
will coordinate with other core contracts to track the amount of ETH restaked for each operator and staker.
contract EigenPodManager{
mapping(address => uint256) public restakerShares;
function createEigenPod(address owner) public;
function stakeToPod(address pod, uint256 amount) public;
function withdrawFromPod(address pod) public;
}
contract EigenPod{
address BEACON_CHAIN_ORACLE;
address podOwner;
uint256 restakedAmount;
function stake(uint256 amount) public;
function verifyRestakedBalance(uint256 amount, MerkleProof proof) public;
function withdraw() public;
}
The EigenPodManager
tracks how many shares each staker has. It allows stakers to create EigenPod
, stake into it, and withdraw from it.
EigenPod
tracks individual validator balances on the beacon chain through the restakedBalance
variable. Whenever a balance changes for any restaked validator, anyone can update the balance for this specific validator by calling the verifyRestakedBalance()
function. The function will check if the updated balance is correct through the beacon state root, which we retrieved from the BEACON_CHAIN_ORACLE
.
And that is how EigenLayer allows native restaking.