EigenLayer Rewards

Restaking rewards distribution from EigenLayer to Byzantine stakers, operators and curators

This section explores the detailed technical structure of the EigenRewards contract, focusing on how restaking rewards from EigenLayer are distributed to the Byzantine stakers, operators and curators.


Keywords

  • RewardsCoordinator: A smart contract within the EigenLayer ecosystem that handles restaking rewards distributed by AVSs to their stakers and operators.

  • EigenRewards: A Byzantine contract that interacts with the RewardsCoordinator contract. It recalculates the rewards for each Byzantine staker based on the rewards distributed by the RewardsCoordinator and allows these stakers to claim their EigenLayer restaking rewards.

  • EigenLayer Sidecar: An open source, permissionless, verified indexer enabling anyone (AVS, operator, etc) to access EigenLayer’s protocol rewards in real-time.

  • Byzantine Sidecar: Operates similarly to the EigenLayer Sidecar. It serves as an indexer enabling users to access Byzantine's Eigen-related rewards in real-time. The Byzantine Sidecar runs in parallel with the EigenLayer Sidecar to extract the relevant rewards data. These rewards are recalculated to ensure equitable distribution among stakers in the same Eigen Byz vault, based on their staking amount and duration.

  • Merkle trees: Cryptographic data structures that allow efficient and secure verification of large data sets.

  • Distribution root: A Merkle root of the earnings distribution for a given period. It is used later to verify that a staker has a specific amount of rewards in specific tokens within a particular vault.

  • Merkle proof: When using merkle trees to store data, a proof is used to verify that a specific transaction is part of a leaf against a Merkle root hash. By comparing hashes along a path from the data to the root, the authenticity of specific data can be verified.


Overview

  • The EigenLayer rewards calculation is performed off-chain based on the rewards given by AVSs to operators and stakers.

  • The Byzantine rewards are also calculated off-chain based on the EigenLayer rewards calculation by monitoring the EigenLayer Sidecar.

  • Both EigenLayer and Byzantine protocols use the Merkle tree solution to enable stakers to claim rewards.

  • The EigenRewards contract requires that every time EigenLayer submits a distribution root to RewardsCoordinator, the root submitter from Byzantine must submit a distribution root to EigenRewards.


High Level Design

A simplified and high level flow is as follows:

  1. AVSs distributes rewards to their stakers and operators via RewardsCoordinator.

  2. The EigenLayer Sidecar runs daily to listen to the events emitted by the RewardsCoordinator contract and calculate offchain the rewards of each EigenLayer staker. A distribution root that merkleize the cumulative sum of each EigenLayer staker and operator is submitted to RewardsCoordinator by the RootUpdater.

  3. At the same time, the Byzantine Sidecar is running constantly to access the rewards data provided by the Sidecar, recalculates the rewards for each Byzantine staker based on the rewards accumulated by the Eigen Vaults that are extracted from the Sidecar. A distribution root that merkleize the cumulative sum of each Byzantine staker is also submitted to EigenRewards.

  4. Anyone including the staker can pull the rewards that belongs to a specific Eigen Vault from RewardsCoordinator to EigenRewards.

  5. Once the vault rewards are transferred to EigenRewards, the Byzantine stakers can claim their rewards.


EigenRewards Merkle Tree

As mentioned earlier, Merkle proofs are used in the Byzantine rewards distribution. In other words, rewards are saved using a Merkle tree and a Merkle proof verification is required in order to claim rewards.

Byzantine has developed a custom Merkle tree structure tailored to its protocol architecture.

The EigenRewards Merkle tree is designed to contain Byzantine stakers' cumulative rewards. Utilizing the EigenLayer RewardsCoordinator Merkle tree, the Byzantine Sidecar extracts rewards data related to any Eigen Vault and constructs the Byzantine EigenReward tree. This tree is organized hierarchically with the Merkle root at the top and nested subtrees below, efficiently organizing rewards data across Eigen Vaults, stakers, and tokens.

The structure of the tree is explained as follows:

  • The Merkle tree's highest root contains the vault tree Merkle leaves. Each vault leaf holds the cumulative rewards of all stakers within a specific Eigen Vault.

  • Each vault leaf has its own subtree with StakerTreeMerkleLeaf as leaves in the subtree. Each staker leaf contains the cumulative rewards of one staker within a specific Eigen Vault.

  • Each staker leaf has its own subtree with TokenTreeMerkleLeaf as leaves in the subtree. Each token leaf contains the cumulative rewards in specific tokens that a staker has earned for the relevant period.

Overview of the EigenLayer and Byzantine Merkle trees

EigenRewards Merkle Tree Overview

Distribution roots of both merkle trees

In both EigenLayer and Byzantine protocols, a root submitter is responsible to submit, every 7 days, a new distribution root the the respective contract (RewardsCoordinator and EigenRewards) to ensure that the onchain rewards are up to date.

Both contracts store all historical distribution roots so that stakers can claim their rewards against older roots if they wish. However, rewards contained in both merkle trees are cumulative. The Eigen merkle tree contains the the cumulative earnings of all earners and tokens for a given period, whereas the Byzantine merkle tree includes the cumulative earnings of all all Eigen Byz vaults, stakers, and tokens for the same given period. (cf. graphic below) Therefore, stakers only need to claim against the latest root to claim all available earnings.

Demonstration of cumulative earnings in distribution roots

Byzantine Rewards Calculation Process

Byzantine has developed an offchain Sidecar that monitors the EigenLayer Sidecar in real-time, enabling the protocol to calculate staker rewards within the Byzantine ecosystem.

(For more details related to the EigenLayer rewards calculation, cf. below:)

Just like EigenLayer rewards calculation, the Byzantine rewards calculation proceeds in 3 stages and runs daily:

  • Data extraction: Accesses the EigenLayer rewards data from the Sidecar and extracts the rewards pertinent to all Eigen Vaults.

  • Rewards calculation: Reorganizes and calculates the rewards in specific tokens for each staker in an Eigen Vault.

  • Rewards aggregation: Aggregates all rewards for a specific period and submits a root that merkleizes the cumulative sum of each staker within a given vault to the EigenReward.

Step-by-Step Process For Staker Rewards

To claim rewards, follow these three steps: submit the root, pull vault rewards from EigenLayer to Byzantine, and claim staker rewards.

For more details about the code mentioned below, refer to the interface of the EigenRewards contract.

1

Submitting Distribution Roots (done by Byzantine)

Function to invoke:

function submitRoot(bytes32 _root, uint32 _rewardsCalculationEndTimestamp) external;

Explanation:

Once the Byzantine Sidecar aggregates all rewards for a specific period and updates the Merkle tree, the root submitter designated by Byzantine must invoke the submitRoot() function. This is necessary to submit the new distribution root to the contract, ensuring the onchain rewards state is updated.

A root becomes claimable by Byzantine stakers if the following conditions are met:

  • The root is not disable by the root submitter.

  • The root's activation delay has elapsed.

  • For each vault that is included the root, the rewards of the vault are pulled from RewardsCoordinator to EigenRewards.

2

Pulling Vault Rewards (done by anyone or the stakers)

Function to invoke:

function pullVaultRewards(
    IRewardsCoordinator.RewardsMerkleClaim calldata _claim
) external;

Parameters to prepare:

The claim for this specific function is provided by the RewardsCoordinator contract.

struct RewardsMerkleClaim {
    uint32 rootIndex;
    uint32 earnerIndex;
    bytes earnerTreeProof;
    EarnerTreeMerkleLeaf earnerLeaf;
    uint32[] tokenIndices;
    bytes[] tokenTreeProofs;
    TokenTreeMerkleLeaf[] tokenLeaves;
}

Explanation:

The pullVaultRewards() function can be called by anyone or a staker to transfer a specific Eigen Vault's rewards from RewardsCoordinator to EigenRewards. These vault rewards, received by EigenRewards, consist of the total earnings in all tokens not yet pulled for all stakers of that specific vault.

Every pullVaultRewards() call is stored at the lastPulledAt mapping which maps the vault address to the distribution root index for later use in claimsStakerRewards().

When claiming vault rewards based on an old root in a Merkle tree, all accumulated rewards up to that root will be claimed (cf. cumulative earnings ). The rewards stored in later roots can still be claimed at any time.

🍀 It is recommended and also beneficial to claim vault rewards against the latest root to ensure that the EigenRewards contract always holds most up-to-date token rewards, providing a better UX for stakers. Claiming the latest root doen't cost more gas fee than claiming against an older root.

For practical reasons and simplicity, the transfer of vault rewards from RewardsCoordinator to EigenRewards is structured so that every call to pullVaultRewards() automatically claims all rewards for all stakers associated with a specific vault. This design reduces the number of pullVaultRewards()calls: one single call enables all stakers to proceed with step 3.

Before calling pullVaultRewards() , it is recommended to call rewardsCoordinator.getCurrentClaimableDistributionRoot() . This is to check the latest claimable root on RewardsCoordinator to avoid any revert due to non-activated or disabled root.

Batch claims:

Use this function to execute a single transaction to pull rewards of multiple vaults to EigenRewards. It is required to provide a list of IRewardsCoordinator.RewardsMerkleClaim as input to the function.

function batchPullVaultRewards(
    IRewardsCoordinator.RewardsMerkleClaim[] calldata _claims
) external;

3

Claiming Staker Rewards (done by the stakers)

Function to invoke:

function claimStakerRewards(RewardsMerkleClaim calldata _claim, address _recipient) external;

Parameters to prepare:

struct RewardsMerkleClaim {
    uint32 rootIndex;
    uint32 vaultIndex;
    bytes vaultTreeProof;
    VaultTreeMerkleLeaf vaultLeaf;
    uint32 stakerIndex;
    bytes stakerTreeProof;
    StakerTreeMerkleLeaf stakerLeaf;
    uint32[] tokenIndices;
    bytes[] tokenTreeProofs;
    TokenTreeMerkleLeaf[] tokenLeaves;
}

Explanation:

The staker or their claimer of EigenByzVaults can call claimStakerRewards() to claim rewards of a specific staker in any vault and in any token. To accomplish this, the staker must prepare the RewardMerkleClaim for this purpose by specifying the vault, the staker, and the list of tokens to claim.

The final claimed amount is the amount of rewards that is left after subtracting the curator fee from the actual earnings.

In a RewardsMerkleClaim, three types of proofs are required: vaultTreeProof, stakerTreeProof, and a list of tokenTreeProofs. A staker can claim their eligible rewards in any vault by providing the vaultTreeProof for that specific vault. Similarly, they can claim rewards for any token by submitting the tokenTreeProof within the bytes[] tokenTreeProof array. The remaining unclaimed tokens can be claimed at any later time.

Before calling claimStakerRewards() , it is recommended to call getCurrentActivatedDistributionRoot() . This is to check the latest claimable root on EigenRewards to avoid any revert due to non-activated root, disabled root or vault rewards not pulled yet.

Batch claims:

Use this function to execute a single transaction to claim rewards of multiple stakers in the same or different vaults. It is required to provide a list of RewardsMerkleClaim as input to the function.

function batchClaimStakerRewards(RewardsMerkleClaim[] calldata _claims, address _recipient) external;

Fees For Curator

Function to invoke:

function claimCuratorFee(IERC20 _token, address _vault, address _recipient) external;

The curator of an Eigen Byz Vault can set and adjust the curator fee percentage. They call this function claim all cumulative fees distributed in a specific token for a particular vault at once.

Each time a staker calls the claimStakerRewards, the curator fee amount is increased and stored in the contract.

Merkle Proof Generation For Claims

Coming soon...

Last updated