| File | Type | Proxy |
|---|---|---|
RewardsCoordinator.sol |
Singleton | Transparent proxy |
The RewardsCoordinator accepts ERC20s from AVSs alongside rewards submission requests made out to Operators who, during a specified time range, were registered to the AVS in the core AllocationManager contract.
There are two forms of rewards:
- Rewards v1, also known as rewards submissions.
- Rewards v2, also known as operator-directed rewards submissions. See the ELIP for additional context on this rewards type.
Off-chain, the trusted rewards updater calculates a rewards distribution over some rewards submission's time range, depending on the rewards type. For a v1 rewards submission, it is based on: (i) the relative stake weight of each Operator's Stakers and (ii) a default split given to the Operator. For a v2 rewards submission, it is based on: (i) an AVS's custom rewards logic, (ii) the per-operator splits.
On-chain, the rewards updater sends the RewardsCoordinator a merkle root of each earner's cumulative earnings. Earners provide merkle proofs to the RewardsCoordinator to claim rewards against these roots.
The typical user flow is as follows:
- An AVS submits a rewards submission, either a
RewardsSubmission(v1) orOperatorDirectedRewardsSubmission(v2), to theRewardsCoordinatorcontract, which specifies a time range (startTimestampandduration) andtoken. The rewards submission also specifies the relative reward weights of strategies (i.e. "distribute 80% out to holders of X strategy, and 20% to holders of strategy Y").- Note that v1 rewards specify a total
amount, whereas v2 rewards specify a per-operator reward (due to customizable rewards logic). v2 rewards also allow for adding adescriptionof the rewards submission's purpose.
- Note that v1 rewards specify a total
- Off-chain, the rewards submissions are used to calculate reward distributions, which are periodically consolidated into a merkle tree.
- The root of this tree (aka the
DistributionRoot) is posted on-chain by the rewards updater. ADistributionRootbecomes active for claims after some globally-configuredactivationDelay. - Stakers and Operators (or their configured "claimers") can claim their accumulated earnings by providing a merkle proof against any previously-posted
DistributionRoot.
This entire flow will repeat periodically as AVSs submit rewards submissions, DistributionRoots are submitted, and Stakers/Operators claim their accumulated earnings. Note that DistributionRoots contain cumulative earnings, meaning Stakers/Operators aren't required to claim against every root - simply claiming against the most recent root will claim anything not yet claimed.
NOTE: Use caution when using reward tokens that do not strictly conform to ERC20 standards. Please DYOR if your token falls outside of ERC20 norms. Specific things to look out for include (but are not limited to): exotic rebasing tokens, fee-on-transfer tokens, tokens that support reentrant behavior (like ERC-777), and other nonstandard ERC20 derivatives.
This document is organized according to the following themes (click each to be taken to the relevant section):
- Submitting Rewards Requests
- Distributing and Claiming Rewards
- System Configuration
- Rewards Merkle Tree Structure
- Off Chain Calculation
DistributionRoot[] public distributionRoots:distributionRootsstores historic reward merkle tree roots submitted by the rewards updater. For each earner, the rewards merkle tree stores cumulative earnings per ERC20 reward token. For more details on merkle tree structure see Rewards Merkle Tree Structure below.
mapping(address => address) public claimerFor: earner => claimer- Stakers and Operators can designate a "claimer" who can claim rewards via on their behalf via
processClaim. If a claimer is not set inclaimerFor, the earner will have to callprocessClaimthemselves. - Note that the claimer isn't necessarily the reward recipient, but they do have the authority to specify the recipient when calling
processClaimon the earner's behalf.
- Stakers and Operators can designate a "claimer" who can claim rewards via on their behalf via
mapping(address => mapping(IERC20 => uint256)) public cumulativeClaimed: earner => token => total amount claimed to date- Mapping for earners(Stakers/Operators) to track their total claimed earnings per reward token. This mapping is used to calculate the difference between the cumulativeEarnings stored in the merkle tree and the previous total claimed amount. This difference is then transferred to the specified destination address.
uint16 public defaultOperatorSplitBips: Used off-chain by the rewards updater to calculate an Operator's split for a specific reward.- This is expected to be a flat 10% rate for the initial rewards release. Expressed in basis points, this is
1000.
- This is expected to be a flat 10% rate for the initial rewards release. Expressed in basis points, this is
mapping(address => mapping(address => OperatorSplit)) internal _operatorAVSSplitBips: operator => AVS =>OperatorSplit- Operators specify their custom split for a given AVS for each
OperatorDirectedRewardsSubmission, where Stakers receive a relative proportion (by stake weight) of the remaining amount.
- Operators specify their custom split for a given AVS for each
mapping(address => OperatorSplit) internal _operatorPISplitBips: operator =>OperatorSplit- Operators may also specify their custom split for programmatic incentives, where Stakers similarly receive a relative proportion (by stake weight) of the remaining amount.
mapping(address operator => mapping(bytes32 operatorSetKey => OperatorSplit split)) internal _operatorSetSplitBips: operator => Operator Set Key =>OperatorSplit- Operators may specify their custom split for a given Operator Set, which is more granular than an overarching AVS split
- AVS (Autonomous Verifiable Service) refers to the contract entity that is submitting rewards to the
RewardsCoordinator.- This is assumed to be a customized
ServiceManagercontract of some kind that is interfacing with the EigenLayer protocol. See theServiceManagerBasedocs here:eigenlayer-middleware/docs/ServiceManagerBase.md.
- This is assumed to be a customized
- An Operator Set refers to a collection of registered operators and strategies. See ELIP-002 for more details.
- An Operator Set Key describes the tuple of an AVS address and an ID that uniquely identifies an Operator Set. See the AllocationManager for details.
- A rewards submission includes, unless specified otherwise, both the v1
RewardsSubmissionand the v2OperatorDirectedRewardsSubmissiontypes. - The internal function
_checkClaim(RewardsMerkleClaim calldata claim, DistributionRoot memory root)checks the merkle inclusion of a claim against aDistributionRoot- It reverts if any of the following are true:
- mismatch input param lengths: tokenIndices, tokenTreeProofs, tokenLeaves
- earner proof reverting from calling
_verifyEarnerClaimProof - any of the token proofs reverting from calling
_verifyTokenClaimProof
- It reverts if any of the following are true:
Rewards are initially submitted to the contract to be distributed to Operators and Stakers by the following functions:
RewardsCoordinator.createAVSRewardsSubmissionRewardsCoordinator.createRewardsForAllSubmissionRewardsCoordinator.createRewardsForAllEarnersRewardsCoordinator.createOperatorDirectedAVSRewardsSubmissionRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission
function createAVSRewardsSubmission(
RewardsSubmission[] calldata RewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION)
nonReentrantCalled by an AVS to submit a list of RewardsSubmissions to be distributed across all registered Operators (and Stakers delegated to each Operator). A RewardsSubmission consists of the following fields:
IERC20 token: the address of the ERC20 token being used for reward submissionuint256 amount: amount oftokento transfer to theRewardsCoordinatoruint32 startTimestamp: the start of the submission time rangeuint32 duration: the duration of the submission time range, in secondsStrategyAndMultiplier[] strategiesAndMultipliers: an array ofStrategyAndMultiplierstructs that define a linear combination of EigenLayer strategies the AVS is considering eligible for rewards. EachStrategyAndMultipliercontains:IStrategy strategy: address of the strategy against which a Staker/Operator's relative shares are weighted in order to determine their reward amountuint96 multiplier: the relative weighting of the strategy in the linear combination. (Recommended use here is to use 1e18 as the base multiplier and adjust the relative weightings accordingly)
For each submitted RewardsSubmission, this method performs a transferFrom to transfer the specified reward token and amount from the caller to the RewardsCoordinator.
Eligibility:
In order to be eligible to claim a createAVSRewardsSubmission reward, the Operator should be registered for the AVS in the AVSDirectory during the time period over which the reward is being made (see docs for AVSDirectory.registerOperatorToAVS). If an Operator is ineligible, any Stakers delegated to the Operator are also ineligible.
In addition, the AVS ServiceManager contract must also implement the interfaces ServiceManager.getRestakeableStrategies and ServiceManager.getOperatorRestakedStrategies to have their rewards be successfully distributed as these view functions are called offchain as part of the rewards distribution process. This is by default implemented in the ServiceManagerBase contract but is important to note if the base contract is not being inherited from.
See the ServiceManagerBase abstract contract here: ServiceManagerBase.sol
Rewards Distribution:
The rewards distribution amongst the AVS's Operators and delegated Stakers is determined offchain using the strategies and multipliers provided in the RewardsSubmission struct as well as the actual shares for those defined strategies over the RewardsSubmission's time range. These shares are read from the EigenPodManager (in the case of the Beacon Chain ETH strategy), or the StrategyManager for any other strategy. Note that Stakers' shares specifically are what determines rewards distribution; Operators earn based on a combination of their own deposited shares and a configured defaultOperatorSplitBips.
Effects:
- For each
RewardsSubmissionelement- Transfers
amountoftokenfrom the msg.sender (AVS) to theRewardsCoordinator - Hashes msg.sender(AVS), nonce, and
RewardsSubmissionstruct to create a unique rewards hash and sets this value totruein theisAVSRewardsSubmissionHashmapping - Increments
submissionNonce[msg.sender] - Emits a
AVSRewardsSubmissionCreatedevent
- Transfers
Requirements:
- Pause status MUST NOT be set:
PAUSED_AVS_REWARDS_SUBMISSION - Function call is not reentered
- For each
RewardsSubmissionelement- Requirements from calling internal function
_validateRewardsSubmission()rewardsSubmission.strategiesAndMultipliers.length > 0rewardsSubmission.amount > 0rewardsSubmission.amount <= MAX_REWARDS_AMOUNTrewardsSubmission.duration <= MAX_REWARDS_DURATIONrewardsSubmission.duration % calculationIntervalSeconds == 0rewardsSubmission.duration > 0rewardsSubmission.startTimestamp % calculationIntervalSeconds == 0block.timestamp - MAX_RETROACTIVE_LENGTH <= rewardsSubmission.startTimestampGENESIS_REWARDS_TIMESTAMP <= rewardsSubmission.startTimestamprewardsSubmission.startTimestamp <= block.timestamp + MAX_FUTURE_LENGTH- Requirements for
rewardsSubmission.strategiesAndMultipliers- Each
strategyis whitelisted for deposit in the StrategyManager or is thebeaconChainETHStrategy rewardsSubmission.strategiesAndMultipliersis sorted by ascending strategy address to prevent duplicate strategies
- Each
transferFromMUST succeed in transferringamountoftokenfrommsg.senderto theRewardsCoordinator
- Requirements from calling internal function
The text diagram below better visualizes a valid start timestamp for a RewardsSubmission
Sliding Window for valid RewardsSubmission startTimestamp
Scenario A: GENESIS_REWARDS_TIMESTAMP IS WITHIN RANGE
<-----MAX_RETROACTIVE_LENGTH-----> t (block.timestamp) <---MAX_FUTURE_LENGTH--->
<--------------------valid range for startTimestamp------------------------>
^
GENESIS_REWARDS_TIMESTAMP
Scenario B: GENESIS_REWARDS_TIMESTAMP IS OUT OF RANGE
<-----MAX_RETROACTIVE_LENGTH-----> t (block.timestamp) <---MAX_FUTURE_LENGTH--->
<------------------------valid range for startTimestamp------------------------>
^
GENESIS_REWARDS_TIMESTAMP
function createRewardsForAllSubmission(
RewardsSubmission[] calldata RewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION)
onlyRewardsForAllSubmitter
nonReentrantThis method is identical in function to createAVSRewardsSubmission above, except:
- It can only be called by a whitelisted "rewards for all submitter"
- ALL Stakers are eligible for rewards, instead of those specifically registered for a given AVS
Effects:
- See
createAVSRewardsSubmissionabove. The only differences are that:- Each rewards submission hash is stored in the
isRewardsSubmissionForAllHashmapping - A
RewardsSubmissionForAllCreatedevent is emitted
- Each rewards submission hash is stored in the
Requirements:
- See
createAVSRewardsSubmissionabove. The only difference is that each calculated rewards submission hash MUST NOT already exist in theisRewardsSubmissionForAllHashmapping.
function createRewardsForAllEarners(
RewardsSubmission[] calldata RewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION)
onlyRewardsForAllSubmitter
nonReentrantThis method is identical in function to createAVSRewardsSubmission above, except:
- It can only be called by a whitelisted "rewards for all submitter"
- Only operators who have opted into at least one AVS and the operator's delegated stakers are eligible for rewards
Effects:
- See
createAVSRewardsSubmissionabove. The only differences are that:- Each rewards submission hash is stored in the
isRewardsSubmissionForAllEarnersHashmapping - Emits a
RewardsSubmissionForAllEarnersCreatedevent
- Each rewards submission hash is stored in the
Requirements:
- See
createAVSRewardsSubmissionabove. The only difference is that each calculated rewards submission hash MUST NOT already exist in theisRewardsSubmissionForAllEarnersHashmapping.
function createOperatorDirectedAVSRewardsSubmission(
address avs,
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION)
checkCanCall(avs)
nonReentrantAVS may make Rewards v2 submissions by calling createOperatorDirectedAVSRewardsSubmission() with any custom on-chain or off-chain logic to determine their rewards distribution strategy. This can be custom to the work performed by Operators during a certain period of time, can be a flat reward rate, or some other structure based on the AVS’s economic model. This would enable AVSs' flexibility in rewarding different operators for performance and other variables while maintaining the same easily calculable reward rate for stakers delegating to the same operator and strategy. The AVS can submit multiple performance-based rewards denominated in different tokens for even more flexibility.
Effects:
- For each
OperatorDirectedRewardsSubmissionelement- Transfers
amountoftokenfrommsg.senderto theRewardsCoordinator - Hashes
AVS,nonce, andOperatorDirectedRewardsSubmissionstruct to create a unique rewards hash and sets this value totruein theisOperatorDirectedAVSRewardsSubmissionHashmapping - Increments
submissionNonce[avs] - Emits an
OperatorDirectedAVSRewardsSubmissionCreatedevent
- Transfers
Requirements:
- Pause status MUST NOT be set:
PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION - Caller MUST be authorized, either as the AVS itself or an admin/appointee (see
PermissionController.md) - Function call is not reentered
- For each
OperatorDirectedRewardsSubmissionelement:- Requirements from calling internal function
_validateOperatorDirectedRewardsSubmission()operatorDirectedRewardsSubmission.strategiesAndMultipliers.length > 0operatorDirectedRewardsSubmission.duration <= MAX_REWARDS_DURATIONoperatorDirectedRewardsSubmission.duration % calculationIntervalSeconds == 0operatorDirectedRewardsSubmission.duration > 0operatorDirectedRewardsSubmission.startTimestamp % calculationIntervalSeconds == 0block.timestamp - MAX_RETROACTIVE_LENGTH <= operatorDirectedRewardsSubmission.startTimestampGENESIS_REWARDS_TIMESTAMP <= operatorDirectedRewardsSubmission.startTimestamp- For each
operatorDirectedRewardsSubmission.strategiesAndMultiplierselement:- Each
strategyis whitelisted for deposit in the StrategyManager or is thebeaconChainETHStrategy rewardsSubmission.strategiesAndMultipliersis sorted by ascending strategy address to prevent duplicate strategies
- Each
operatorDirectedRewardsSubmission.operatorRewards.length > 0- For each
operatorDirectedRewardsSubmission.operatorRewardselement:operatorReward.operator != address(0)currOperatorAddress < operatorReward.operatoroperatorReward.amount > 0
totalAmount <= MAX_REWARDS_AMOUNT, wheretotalAmountis the sum of everyoperatorReward.amountoperatorDirectedRewardsSubmission.startTimestamp + operatorDirectedRewardsSubmission.duration < block.timestamp, enforcing strictly retoractive rewards submissions
transferFromMUST succeed in transferringamountoftokenfrommsg.senderto theRewardsCoordinator
- Requirements from calling internal function
function createOperatorDirectedOperatorSetRewardsSubmission(
OperatorSet calldata operatorSet,
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION)
checkCanCall(operatorSet.avs)
nonReentrantThis function allows AVSs to make rewards submissions to specific operator sets, allowing for more granularly targeted rewards based on tasks assigned to a specific operator set, or any other custom AVS logic. Its functionality is almost identical to createOperatorDirectedAVSRewardsSubmission, save for some operator-set specific requirements, state variables, and events.
Note that an AVS must specify an operator set registered to the AVS; in other words, an operator set belonging to a different AVS, or an unregistered operator set, will cause this function to revert.
Also note that making this reward submission with a duration extending prior to the slashing release will result in those reward snapshots, prior to the slashing release, being refunded to the AVS (This is handled in the Sidecar rewards calculation logic).
Effects:
- See
createOperatorDirectedAVSRewardsSubmissionabove. The only differences are that:- Each rewards submission is stored in the
isOperatorDirectedOperatorSetRewardsSubmissionHashmapping - An
OperatorDirectedOperatorSetRewardsSubmissionCreatedevent is emitted
- Each rewards submission is stored in the
Requirements:
- See
createOperatorDirectedAVSRewardsSubmissionabove. The only differences are that:operatorSetMUST be a registered operator set for the given AVS as according toallocationManager.isOperatorSet()- Pause status is instead:
PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION
The rewards updater calculates rewards distributions and submit claimable roots through the following function submitRoot. They can also disable the root if it has not yet been activated:
Earners configure and claim these rewards using the following functions:
function submitRoot(
bytes32 root,
uint32 rewardsCalculationEndTimestamp
)
external
onlyWhenNotPaused(PAUSED_SUBMIT_DISABLE_ROOTS)
onlyRewardsUpdaterCalled only by the rewardsUpdater address to create a new DistributionRoot in the RewardsCoordinator. The DistributionRoot struct contains the following fields:
bytes32 root: the merkle root of the rewards merkle treeuint32 rewardsCalculationEndTimestamp: the end of the rewards time range for which theDistributionRootis being submitteduint32 activatedAt: the timestamp in seconds when theDistributionRootis activated and can be claimed against
submitRoot pushes a new DistributionRoot to the distributionRoots array. The DistributionRoot.activatedAt timestamp is set to block.timestamp + activationDelay() to allow for a delay before claims can be processed. Once this delay has passed, the root can be used to verify merkle proofs of rewards made out to Stakers/Operators.
Effects:
- Pushes a new
DistributionRootto thedistributionRootsarray - Sets
currRewardsCalculationEndTimestampto the paramrewardsCalculationEndTimestamp - Emits a
DistributionRootSubmittedevent
Requirements:
- Pause status MUST NOT be set:
PAUSED_SUBMIT_DISABLE_ROOTS msg.senderMUST be therewardsUpdaterrewardsCalculationEndTimestamp > currRewardsCalculationEndTimestamprewardsCalculationEndTimestamp < block.timestamp
function disableRoot(
uint32 rootIndex
)
external
onlyWhenNotPaused(PAUSED_SUBMIT_DISABLE_ROOTS)
onlyRewardsUpdaterCalled only by the rewardsUpdater address to disable a pending DistributionRoot that has not yet been activated (activatedAt timestamp hasn't been reached yet) in the RewardsCoordinator. Once the activatedAt timestamp has been reached, a root can no longer be disabled and is deemed finalized and claimable against.
This is to add additional measures to prevent invalid roots posted to the contract, either from error or potentially malicious roots posted.
Effects:
- Sets the
disabledfield to True for the correspondingDistributionRoot DistributionRootcan no longer be claimed against inprocessClaim- Emits a
DistributionRootDisabledevent
Requirements:
- Pause status MUST NOT be set:
PAUSED_SUBMIT_DISABLE_ROOTS msg.senderMUST be therewardsUpdaterrootIndex < distributionRoots.lengthroot.disabled == Falseblock.timestamp < root.activatedAtrewardsCalculationEndTimestamp < block.timestamp
function setClaimerFor(address claimer) externalCalled by an earner (Staker/Operator) to set a claimer address that can call processClaim on their behalf. If the claimer is not set (claimerFor[earner] == address(0)), the earner themselves can call processClaim directly.
Effects:
- Sets the
claimerFor[msg.sender]to the input paramclaimer - Emits a
ClaimerForSetevent
function processClaim(
RewardsMerkleClaim calldata claim,
address recipient
)
external
onlyWhenNotPaused(PAUSED_PROCESS_CLAIM)
nonReentrantCalled an earner (Staker/Operator) to claim their accumulated earnings by providing a merkle proof against a posted DistributionRoot. If the earner has configured a claimer (via setClaimerFor), the claimer must call this method instead.
The RewardsMerkleClaim struct contains the following fields (see Rewards Merkle Tree Structure for further details):
uint32 rootIndex: the index of theDistributionRootindistributionRootsto prove againstuint32 earnerIndex: the index of the earner's account root in the merkle treebytes earnerTreeProof: the proof of the earner'sEarnerTreeMerkleLeafagainst theDistributionRootEarnerTreeMerkleLeaf earnerLeaf: the earner's address and token subtree rootaddress earner: the address of the earnerbytes32 earnerTokenRoot: the merkle root of the earner's token merkle tree
uint32[] tokenIndices: the indices of the token leaves in the earner's subtreebytes[] tokenTreeProofs: the proofs of the token leaves against the earner'searnerTokenRootTokenTreeMerkleLeaf[] tokenLeaves: the token leaves to be claimed:IERC20 token: the ERC20 token to be claimeduint256 amount: the amount of the ERC20 token to be claimed
processClaim is a simple wrapper function which calls out to the internal function _processClaim, which holds all of the necessary logic.
_processClaim will first call _checkClaim to verify the merkle proofs against the DistributionRoot at the specified rootIndex. This is done by first performing a merkle proof verification of the earner's EarnerTreeMerkleLeaf against the DistributionRoot and then for each tokenIndex, verifying each token leaf against the earner's earnerTokenRoot.
The caller must be the set claimer address in the claimerFor mapping or the earner themselves if the claimer is not set.
After the claim is verified, for each token leaf, the difference between the cumulative earnings in the merkle tree and the previous total claimed amount last stored in the contract is calculated and transferred from the RewardsCoordinator contract to the address recipient.
Effects:
- For each
claim.tokenLeaves:- Calculates
uint claimAmount = tokenLeaf.cumulativeEarnings - cumulativeClaimed[earner][tokenLeaf.token]- Transfers
claimAmountoftokenLeaf.tokento the specifiedrecipient
- Transfers
- Updates the
cumulativeClaimedmapping for the earner and token - Emits a
RewardsClaimedevent
- Calculates
Requirements:
- Pause status MUST NOT be set:
PAUSED_PROCESS_CLAIM - The
claimmust have valid proofs against a validDistributionRoot:- For the
DistributionRootgiven byclaim.rootIndex, the root MUST be active (block.timestamp >= root.activatedAt) claim.tokenIndicesMUST equal the lengths ofclaim.TokenTreeProofsANDclaim.tokenLeavesclaim.earnerTreeProofMUST validateclaim.earnerLeafagainst theDistributionRoot- For each
claim.tokenIndices[i]:claim.tokenTreeProofs[i]MUST validateclaim.tokenLeaves[i]againstclaim.earnerLeaf.earnerTokenRoot
- For the
- If the
earnerspecified inclaim.earnerLeaf.earnerhas a designatedclaimerinclaimerFor[earner],msg.senderMUST be theclaimer- Otherwise,
msg.senderMUST be theearner
- Otherwise,
- For each
TokenTreeMerkleLeaf,tokenLeaf.cumulativeEarnings > cumulativeClaimed[earner][token]: cumulativeEarnings must be gt than cumulativeClaimed. Trying to reclaim with the same proofs will revert because the claimed and earnings values will equal, breaking this requirement.tokenLeaf.token.safeTransfer(recipient, claimAmount)MUST succeed
function processClaims(
RewardsMerkleClaim[] calldata claims,
address recipient
)
external
onlyWhenNotPaused(PAUSED_PROCESS_CLAIM)
nonReentrantprocessClaims is a simple wrapper function around _processClaim, calling it once for each claim provided.
Effects:
- For each
RewardsMerkleClaimelement: seeprocessClaimabove.
Requirements
- See
processClaimabove.
RewardsCoordinator.setActivationDelayRewardsCoordinator.setDefaultOperatorSplitRewardsCoordinator.setRewardsUpdaterRewardsCoordinator.setRewardsForAllSubmitterRewardsCoordinator.setOperatorAVSSplitRewardsCoordinator.setOperatorPISplitRewardsCoordinator.setOperatorSetSplit
function setActivationDelay(uint32 _activationDelay) external onlyOwnerAllows the Owner to set the global activationDelay. The activation delay is the time in seconds after a DistributionRoot is submitted before it can be claimed against. This delay is to allow for interested parties to perform verification of the root before claiming begins.
Effects:
- Sets the global
activationDelay - Emits a
ActivationDelaySetevent
Requirements:
- Caller MUST be the Owner
function setDefaultOperatorSplit(uint16 split) external onlyOwnerAllows the Owner to set the default operator split in basis points.
This split is used off-chain when calculating Operator earnings for a given rewards distribution. Operator split is calculated as a percentage of the reward amount made out to each Operator. This split is deducted from the reward amount, after which the remainder is used to calculate rewards made to any Stakers delegated to the Operator.
Effects:
- Sets the
defaultOperatorSplitBips - Emits a
DefaultOperatorSplitBipsSetevent
Requirements:
- Caller MUST be the Owner
function setRewardsUpdater(address _rewardsUpdater) external onlyOwnerAllows the Owner to set the rewardsUpdater address. The rewardsUpdater is the singleton address that can submit new DistributionRoots to the RewardsCoordinator. The rewardsUpdater is a trusted entity that performs the bulk of the calculations and merkle tree structuring described in this document.
Effects:
- Sets the global
rewardsUpdateraddress - Emits a
RewardsUpdaterSetevent
Requirements:
- Caller MUST be the Owner
function setRewardsForAllSubmitter(address _submitter, bool _newValue) external onlyOwnerAllows the Owner to update the _submitter's permissions in the isRewardsForAllSubmitter mapping. This mapping is used to determine if a given address is a valid submitter for the createRewardsForAllSubmission method.
Effects:
- Sets the
isRewardsForAllSubmittermapping for the address_submitterto the bool_newValue - Emits a
RewardsForAllSubmitterSetevent
Requirements:
- Caller MUST be the Owner
function setOperatorAVSSplit(
address operator,
address avs,
uint16 split
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_AVS_SPLIT)
checkCanCall(operator)An Operator may, for a given AVS, set a split which will determine what percent of their attributed rewards are allocated to themselves. The remaining percentage will go to Stakers.
The split will take effect after an activationDelay set by the contract owner. Note that once an operator initiates a split update, the activationDelay must pass before a new split update can be initiated.
Effects:
- Updates
operatorSplit.activatedAttoblock.timestamp + activationDelay - If the operator has not initialized yet, sets
operatorSplit.oldSplitBipstodefaultOperatorSplitBips. Else setsoperatorSplit.oldSplitBipsto the currentnewSplitBips - Updates
operatorSplit.newSplitBipstosplit - Emits an
OperatorAVSSplitBipsSetevent
Requirements:
- Caller MUST be authorized, either as the operator itself or an admin/appointee (see
PermissionController.md) - Split MUST BE <= 10,000 bips (100%)
- Current
block.timestampMUST BE greater than currentoperatorSplit.activatedAt.- Any pending split must have already completed prior to setting a new split.
function setOperatorPISplit(
address operator,
uint16 split
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_PI_SPLIT)
checkCanCall(operator)Similar to setOperatorAVSSplit, Operators may set their split for programmatic incentives, allowing them to specify what percent of these rewards they will maintain and what percent will go to their Stakers. The allocationDelay also applies here, as well as the inability to reinitiate a split update before the delay passes.
Effects:
- See
setOperatorAVSSplitabove. The only differences are that:- The split is stored within
_operatorPISplitBipsinstead of_operatorAVSSplitBips. - An
OperatorPISplitBipsSetevent is emitted
- The split is stored within
Requirements:
- See
setOperatorAVSSplitabove. The only difference is that:- Pause status is instead:
PAUSED_OPERATOR_PI_SPLIT
- Pause status is instead:
function setOperatorSetSplit(
address operator,
OperatorSet calldata operatorSet,
uint16 split
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_SET_SPLIT)
checkCanCall(operator)Effects:
- See
setOperatorAVSSplitabove. The only difference is that:- The split is stored within
_operatorSetSplitBipsinstead of_operatorAVSSplitBips - An
OperatorSetSplitBipsSetevent is emitted
- The split is stored within
Requirements:
- See
setOperatorAVSSplitabove. The only differences are that:operatorSetMUST be a registered operator set for the given AVS as according toallocationManager.isOperatorSet()- Pause status is instead:
PAUSED_OPERATOR_SET_SPLIT
This merkle tree is used to verify the claims against a DistributionRoot.
When submitting a new DistributionRoot, the rewards updater consolidates all RewardsSubmissions submitted by AVSs since the previously submitted DistributionRoot into a merkle tree comprised of earners and their cumulative earnings for their respective reward tokens distributed.
When an earner or their designated claimer calls processClaim, they must provide a RewardsMerkleClaim struct that contains the necessary information to verify their claim against the latest DistributionRoot. The merkle proof verification is done in the internal _checkClaim helper function. This function verifies the merkle proof of the earner's EarnerTreeMerkleLeaf against the DistributionRoot and then for each tokenIndex, verifies each token leaf against the earner's earnerTokenRoot.
Claimers can selectively choose which token leaves to prove against and claim accumulated earnings. Each token reward claimed in a processClaim call will send tokens to the recipient address specified in the call.
The rewards merkle tree is structured in the diagram below:
Rewards are calculated via an off-chain data pipeline. The pipeline takes snapshots of core contract state at the SNAPSHOT_CADENCE, currently set to once per day. It then combines these snapshots with any active rewards to calculate what the single daily reward of an earner is. Every CALCULATION_INTERVAL_SECONDS rewards are accumulated up to lastRewardsTimestamp + CALCULATION_INTERVAL_SECONDS and posted on-chain by the entity with the rewardsUpdater role.
MAX_REWARDS_AMOUNT is set to 1e38-1 given the precision bounds of the off-chain pipeline. An in-depth overview of the off-chain calculation can be found here
