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 AVSDirectory
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 theRewardsCoordinator
contract, which specifies a time range (startTimestamp
andduration
) 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 adescription
of 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. ADistributionRoot
becomes 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.
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
:distributionRoots
stores 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 callprocessClaim
themselves. - Note that the claimer isn't necessarily the reward recipient, but they do have the authority to specify the recipient when calling
processClaim
on 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 transfered 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.
_checkClaim(RewardsMerkleClaim calldata claim, DistributionRoot memory root)
- Checks the merkle inclusion of a claim against a
DistributionRoot
- 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
- Checks the merkle inclusion of a claim against a
- Wherever AVS (Actively Validated Service) is mentioned, it refers to the contract entity that is submitting rewards to the
RewardsCoordinator
. This is assumed to be a customizedServiceManager
contract of some kind that is interfacing with the EigenLayer protocol. See theServiceManagerBase
docs here:eigenlayer-middleware/docs/ServiceManagerBase.md
. - A rewards submission includes, unless specified otherwise, both the v1
RewardsSubmission
and the v2OperatorDirectedRewardsSubmission
types.
Rewards are initially submitted to the contract to be distributed to Operators and Stakers by the following functions:
RewardsCoordinator.createAVSRewardsSubmission
RewardsCoordinator.createRewardsForAllSubmission
RewardsCoordinator.createRewardsForAllEarners
RewardsCoordinator.createOperatorDirectedAVSRewardsSubmission
function createAVSRewardsSubmission(
RewardsSubmission[] calldata RewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION)
nonReentrant
Called by an AVS to submit a list of RewardsSubmission
s 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 oftoken
to transfer to theRewardsCoordinator
uint32 startTimestamp
: the start of the submission time rangeuint32 duration
: the duration of the submission time range, in secondsStrategyAndMultiplier[] strategiesAndMultipliers
: an array ofStrategyAndMultiplier
structs that define a linear combination of EigenLayer strategies the AVS is considering eligible for rewards. EachStrategyAndMultiplier
contains: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
RewardsSubmission
element- Transfers
amount
oftoken
from the msg.sender (AVS) to theRewardsCoordinator
- Hashes msg.sender(AVS), nonce, and
RewardsSubmission
struct to create a unique rewards hash and sets this value totrue
in theisAVSRewardsSubmissionHash
mapping - Increments
submissionNonce[msg.sender]
- Emits a
AVSRewardsSubmissionCreated
event
- Transfers
Requirements:
- Pause status MUST NOT be set:
PAUSED_AVS_REWARDS_SUBMISSION
- Function call is not reentered
- For each
RewardsSubmission
element- Requirements from calling internal function
_validateRewardsSubmission()
rewardsSubmission.strategiesAndMultipliers.length > 0
rewardsSubmission.amount > 0
rewardsSubmission.amount <= MAX_REWARDS_AMOUNT
rewardsSubmission.duration <= MAX_REWARDS_DURATION
rewardsSubmission.duration % calculationIntervalSeconds == 0
rewardsSubmission.startTimestamp % calculationIntervalSeconds == 0
block.timestamp - MAX_RETROACTIVE_LENGTH <= rewardsSubmission.startTimestamp
GENESIS_REWARDS_TIMESTAMP <= rewardsSubmission.startTimestamp
rewardsSubmission.startTimestamp <= block.timestamp + MAX_FUTURE_LENGTH
- Requirements for
rewardsSubmission.strategiesAndMultipliers
- Each
strategy
is whitelisted for deposit in the StrategyManager or is thebeaconChainETHStrategy
rewardsSubmission.strategiesAndMultipliers
is sorted by ascending strategy address to prevent duplicate strategies
- Each
transferFrom
MUST succeed in transferringamount
oftoken
frommsg.sender
to 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
nonReentrant
This 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
createAVSRewardsSubmission
above. The only differences are that:- Each rewards submission hash is stored in the
isRewardsSubmissionForAllHash
mapping - Emits a
RewardsSubmissionForAllCreated
event
- Each rewards submission hash is stored in the
Requirements:
- See
createAVSRewardsSubmission
above. The only difference is that each calculated rewards submission hash MUST NOT already exist in theisRewardsSubmissionForAllHash
mapping.
function createRewardsForAllEarners(
RewardsSubmission[] calldata RewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION)
onlyRewardsForAllSubmitter
nonReentrant
This 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
createAVSRewardsSubmission
above. The only differences are that:- Each rewards submission hash is stored in the
isRewardsSubmissionForAllEarnersHash
mapping - Emits a
RewardsSubmissionForAllEarnersCreated
event
- Each rewards submission hash is stored in the
Requirements:
- See
createAVSRewardsSubmission
above. The only difference is that each calculated rewards submission hash MUST NOT already exist in theisRewardsSubmissionForAllEarnersHash
mapping.
function createOperatorDirectedAVSRewardsSubmission(
address avs,
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION)
nonReentrant
AVS 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
OperatorDirectedRewardsSubmission
element- Transfers
amount
oftoken
from themsg.sender
(AVS
) to theRewardsCoordinator
- Hashes
msg.sender
(AVS
),nonce
, andOperatorDirectedRewardsSubmission
struct to create a unique rewards hash and sets this value totrue
in theisOperatorDirectedAVSRewardsSubmissionHash
mapping - Increments
submissionNonce[msg.sender]
- Emits an
OperatorDirectedAVSRewardsSubmissionCreated
event
- Transfers
Requirements:
- Pause status MUST NOT be set:
PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION
- Caller MUST BE the AVS
- Function call is not reentered
- For each
OperatorDirectedRewardsSubmission
element:- Requirements from calling internal function
_validateOperatorDirectedRewardsSubmission()
operatorDirectedRewardsSubmission.strategiesAndMultipliers.length > 0
operatorDirectedRewardsSubmission.duration <= MAX_REWARDS_DURATION
operatorDirectedRewardsSubmission.duration % calculationIntervalSeconds == 0
operatorDirectedRewardsSubmission.startTimestamp % calculationIntervalSeconds == 0
block.timestamp - MAX_RETROACTIVE_LENGTH <= operatorDirectedRewardsSubmission.startTimestamp
GENESIS_REWARDS_TIMESTAMP <= operatorDirectedRewardsSubmission.startTimestamp
- For each
operatorDirectedRewardsSubmission.strategiesAndMultipliers
element:- Each
strategy
is whitelisted for deposit in the StrategyManager or is thebeaconChainETHStrategy
rewardsSubmission.strategiesAndMultipliers
is sorted by ascending strategy address to prevent duplicate strategies
- Each
operatorDirectedRewardsSubmission.operatorRewards.length > 0
- For each
operatorDirectedRewardsSubmission.operatorRewards
element:operatorReward.operator != address(0)
currOperatorAddress < operatorReward.operator
operatorReward.amount > 0
totalAmount <= MAX_REWARDS_AMOUNT
, wheretotalAmount
is the sum of everyoperatorReward.amount
operatorDirectedRewardsSubmission.startTimestamp + operatorDirectedRewardsSubmission.duration < block.timestamp
, enforcing strictly retoractive rewards submissions
transferFrom
MUST succeed in transferringamount
oftoken
frommsg.sender
to theRewardsCoordinator
- Requirements from calling internal function
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)
onlyRewardsUpdater
Called 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 theDistributionRoot
is being submitteduint32 activatedAt
: the timestamp in seconds when theDistributionRoot
is 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
DistributionRoot
to thedistributionRoots
array - Sets
currRewardsCalculationEndTimestamp
to the paramrewardsCalculationEndTimestamp
- Emits a
DistributionRootSubmitted
event
Requirements:
- Pause status MUST NOT be set:
PAUSED_SUBMIT_DISABLE_ROOTS
msg.sender
MUST be therewardsUpdater
rewardsCalculationEndTimestamp > currRewardsCalculationEndTimestamp
rewardsCalculationEndTimestamp < block.timestamp
function disableRoot(
uint32 rootIndex
)
external
onlyWhenNotPaused(PAUSED_SUBMIT_DISABLE_ROOTS)
onlyRewardsUpdater
Called 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
disabled
field to True for the correspondingDistributionRoot
DistributionRoot
can no longer be claimed against inprocessClaim
- Emits a
DistributionRootDisabled
event
Requirements:
- Pause status MUST NOT be set:
PAUSED_SUBMIT_DISABLE_ROOTS
msg.sender
MUST be therewardsUpdater
rootIndex < distributionRoots.length
root.disabled == False
block.timestamp < root.activatedAt
rewardsCalculationEndTimestamp < block.timestamp
function setClaimerFor(address claimer) external
Called 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
ClaimerForSet
event
function processClaim(
RewardsMerkleClaim calldata claim,
address recipient
)
external
onlyWhenNotPaused(PAUSED_PROCESS_CLAIM)
nonReentrant
Called 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 theDistributionRoot
indistributionRoots
to prove againstuint32 earnerIndex
: the index of the earner's account root in the merkle treebytes earnerTreeProof
: the proof of the earner'sEarnerTreeMerkleLeaf
against theDistributionRoot
EarnerTreeMerkleLeaf 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'searnerTokenRoot
TokenTreeMerkleLeaf[] 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
claimAmount
oftokenLeaf.token
to the specifiedrecipient
- Transfers
- Updates the
cumulativeClaimed
mapping for the earner and token - Emits a
RewardsClaimed
event
- Calculates
Requirements:
- Pause status MUST NOT be set:
PAUSED_PROCESS_CLAIM
- The
claim
must have valid proofs against a validDistributionRoot
:- For the
DistributionRoot
given byclaim.rootIndex
, the root MUST be active (block.timestamp >= root.activatedAt
) claim.tokenIndices
MUST equal the lengths ofclaim.TokenTreeProofs
ANDclaim.tokenLeaves
claim.earnerTreeProof
MUST validateclaim.earnerLeaf
against theDistributionRoot
- For each
claim.tokenIndices[i]
:claim.tokenTreeProofs[i]
MUST validateclaim.tokenLeaves[i]
againstclaim.earnerLeaf.earnerTokenRoot
- For the
- If the
earner
specified inclaim.earnerLeaf.earner
has a designatedclaimer
inclaimerFor[earner]
,msg.sender
MUST be theclaimer
- Otherwise,
msg.sender
MUST 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)
nonReentrant
processClaims
is a simple wrapper function around _processClaim
, calling it once for each claim provided.
Effects:
- For each
RewardsMerkleClaim
element: seeprocessClaim
above.
Requirements
- See
processClaim
above.
RewardsCoordinator.setActivationDelay
RewardsCoordinator.setDefaultOperatorSplit
RewardsCoordinator.setRewardsUpdater
RewardsCoordinator.setRewardsForAllSubmitter
RewardsCoordinator.setOperatorAVSsplit
RewardsCoordinator.setOperatorPIsplit
function setActivationDelay(uint32 _activationDelay) external onlyOwner
Allows 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
ActivationDelaySet
event
Requirements:
- Caller MUST be the Owner
function setDefaultOperatorSplit(uint16 split) external onlyOwner
Allows 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
DefaultOperatorSplitBipsSet
event
Requirements:
- Caller MUST be the Owner
function setRewardsUpdater(address _rewardsUpdater) external onlyOwner
Allows 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
rewardsUpdater
address - Emits a
RewardsUpdaterSet
event
Requirements:
- Caller MUST be the Owner
function setRewardsForAllSubmitter(address _submitter, bool _newValue) external onlyOwner
Allows 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
isRewardsForAllSubmitter
mapping for the address_submitter
to the bool_newValue
- Emits a
RewardsForAllSubmitterSet
event
Requirements:
- Caller MUST be the Owner
function setOperatorAVSSplit(
address operator,
address avs,
uint16 split
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_AVS_SPLIT)
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.activatedAt
toblock.timestamp + activationDelay
- If the operator has not initialized yet, sets
operatorSplit.oldSplitBips
todefaultOperatorSplitBips
. Else setsoperatorSplit.oldSplitBips
to the currentnewSplitBips
- Updates
operatorSplit.newSplitBips
tosplit
- Emits an
OperatorAVSSplitBipsSet
event
Requirements:
- Caller MUST BE the operator
- Split MUST BE <= 10,000 bips (100%)
- Current
block.timestamp
MUST 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)
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:
- Same as
setOperatorAVSSplit
, but withoperatorPISplitBips
instead ofoperatorAVSSplitBips
.
Requirements:
- See
setOperatorAVSSplit
.
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