Stanford Blockchain Review
Volume 4, Article No. 9
📚Author: Kydo – EigenLayer Research
🌟Technical Prerequisite: Advanced
Simplified architecture of EigenLayer
Introduction
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 article, 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 some experience with smart contracts and have heard of EigenLayer and restaking {1}.
Now, let's dive in.
EigenLayer’s 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, for the following reasons:
It is difficult to identify where the stakers (people who provide stake) are located. There is no single place where developers can find stakers.
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.
Stakers must forgo other rewards opportunities, such as the 5% rewards offered by Ethereum.
The current security model is undesirable as the cost to corrupt any dApp is simply the cost needed to compromise its weakest infrastructural dependency {2}.
EigenLayer was created to address these issues. Specifically, as a solution to achieve these four key objectives:
A. It serves as a platform connecting stakers and infrastructure developers.
B. Stakers can offer economic security using any token.
C. Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards.
D. Through restaking, EigenLayer pools security instead of fragmenting it.
Thus, EigenLayer creates an infrastructure layer that generalizes the concept of providing economic security.
Initial Design for EigenLayer
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.
This simple design fulfills Goal (A) of connecting stakers and infrastructures. 1/4 so far:
A. It serves as a platform connecting stakers and infrastructure developers. ✅
B. Stakers can offer economic security using any token. ❌
C. Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards. ❌
D. Through restaking, EigenLayer pools security instead of fragmenting it. ❌
Liquid Staking: Reduce Opportunity Cost for Stakers
When new infrastructure protocols launch, they typically introduce a new, native token, and it can be challenging to convince stakers to hold onto this token, since (1) the native token's value can fluctuate significantly, making it difficult to persuade stakers to acquire it, and (2) 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. With this, we’ve managed to achieve goal (B) and (C).
A. It serves as a platform connecting stakers and infrastructure developers. ✅
B. Stakers can offer economic security using any token. ✅
C. Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards. ✅
D. Through restaking, EigenLayer pools security instead of fragmenting it. ❌
Implementing Pool Security
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.
In the EigenLayer context, each infrastructure service built using this restaking framework is referred to as an Actively Validated Service (AVS). 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 simultaneously 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:
Intermediate EigenLayer design after separating the slashing from the token pools.
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. The enroll function is access controlled here because it could potentially enable anyone to enroll into any slasher contract. To ensure that this TokenPool is managed securely, we will assume that it is overseen by a trusted third-party.
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.
A. It serves as a platform connecting stakers and infrastructure developers. ✅
B. Stakers can offer economic security using any token. ✅
C. Stakers have the option to restake their stake and contribute to the security of other infrastructures while earning native ETH rewards. ✅
D. 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.
Design Extension: 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:
Intermediate EigenLayer design after separating the operator role from the staker role.
Design Extension: 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.
New component of the design which can now track different tokens from the stakers.
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.
Design Extension: 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 on-chain, 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.
A potential timeline of event for a malicious operator/staker.
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 {3}.
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.
Modularize the slashing contract even more to reduce staker gas cost.
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:
A. Connect stakers and infrastructure developers through a platform.
B. Allow stakers to provide economic security using any token.
C. Enable stakers to restake their stake and earn native ETH rewards while contributing to the security of other infrastructures.
D. 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:
Simplified architecture of EigenLayer
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 [2].
If you have any questions on the EigenLayer core contracts, drop some comments at https://research.eigenlayer.xyz/
About the Author
Kydo is a protocol researcher at EigenLayer, and a crypto researcher, writer, and investor with a focus on infrastructure and governance systems. He is the founder of Native Labs, and has contributed to several prominent protocols such as Llama, Gitcoin, Uniswap, Aave, among others. Kydo holds a Master's degree from Stanford Medicine under Professor Dan Boneh, where he focused on decentralized scientific funding mechanisms.
This article is inspired by David Philipson’s series on account abstraction. Special thanks to Noam Hurwitz, Shiva, NashQ, Mike Neuder, and Sina from the community for comments and feedback on the article.
Footnotes
{1} Note that because this article aims to provide a high-level explanation of EigenLayer’s design evolution, there will be instances where the interface, variables, and logic may differ from the current EigenLayer core contracts.
{2} For now, we will assume that a staker participating in an infrastructure project is also responsible for operating the off-chain software to guarantee its security. However, we will change this assumption later in the article.
{3} The design of this process is complex and has undergone several iterations in our pipeline. We could even write a separate article on this topic! At present, we use a time-tracking system to achieve this unbonding tracking. This part is still a work in progress!
References
[1] See EigenLayer Whitepaper: https://docs.eigenlayer.xyz/overview/whitepaper
[2] To learn more about these other functions, you can visit our public repository at: https://github.com/Layr-Labs/eigenlayer-contracts