EigenLayer RewardsCoordinator 奖励协调器 介绍

  • Layr-Labs
  • 发布于 2025-03-13 17:28
  • 阅读 19

本文档介绍了EigenLayer协议中RewardsCoordinator合约的功能和机制。该合约负责接收来自AVS的ERC20奖励,分配给他们的Operators和delegated Stakers,并允许Stakers和Operators申领他们的累计收益。文档涵盖了提交奖励请求、分配和申领奖励、系统配置、奖励 Merkle 树结构和链下计算等关键概念和流程。

文件 类型 代理
RewardsCoordinator.sol 单例 透明代理

<!-- RewardsCoordinator 合约的主要功能是 (i) 接受 AVS(主动验证服务)向其运营商和委托的 Staker 在给定的时间范围内提供的 ERC20 奖励;(ii) 使协议能够在指定的时间范围内向所有 staker 提供 ERC20 代币;(iii) 允许 staker 和运营商领取他们积累的收益。 -->

RewardsCoordinator 接受来自 AVS 的 ERC20,以及向运营商发出的奖励提交请求,这些运营商在指定的时间范围内已在核心 AllocationManager 合约中向 AVS 注册。

有两种形式的奖励:

  • 奖励 v1,也称为奖励提交。
  • 奖励 v2,也称为运营商导向的奖励提交。有关此奖励类型的其他背景信息,请参见 ELIP

链下,受信任的 rewards updater 根据奖励提交的时间范围(取决于奖励类型)计算奖励分配。对于 v1 奖励提交,它基于:(i)每个运营商的 Staker 的相对 stake 权重和(ii)分配给运营商的默认拆分。对于 v2 奖励提交,它基于:(i)AVS 的自定义奖励逻辑,(ii)每个运营商的拆分。

链上,rewards updater 向 RewardsCoordinator 发送每个收益者累积收益的 Merkle 根。收益者向 RewardsCoordinator 提供 Merkle 证明,以根据这些根来领取奖励。

典型的用户流程如下:

  1. AVS 向 RewardsCoordinator 合约提交 rewards submission,可以是 RewardsSubmission (v1) 或 OperatorDirectedRewardsSubmission (v2),其中指定时间范围(startTimestampduration)和 token。rewards submission 还指定策略的相对奖励权重(即“将 80% 分配给 X 策略的持有者,将 20% 分配给 Y 策略的持有者”)。
    • 请注意,v1 奖励 指定总 amount,而 v2 奖励 指定每个运营商的奖励(由于可自定义的奖励逻辑)。v2 奖励 还允许添加对 rewards submission 目的的 description
  2. 链下,rewards submission 用于计算奖励分配,这些分配定期合并到 Merkle 树中。
  3. 这棵树的根(也称为 DistributionRoot)由 rewards updater 发布在链上。DistributionRoot 在某个全局配置的 activationDelay 之后变为可声明状态。
  4. Staker 和运营商(或其配置的“申领人”)可以通过提供针对先前发布的任何 DistributionRoot 的 Merkle 证明来申领其累积的收益。

整个流程将定期重复,因为 AVS 提交 rewards submission,提交 DistributionRoot,并且 Staker/运营商申领其累积的收益。请注意,DistributionRoot 包含 cumulative earnings,这意味着 Staker/运营商不需要针对每个根进行申领 - 只需针对最新的根进行申领即可申领任何尚未申领的内容。

注意:在使用不严格符合 ERC20 标准的奖励代币时,请务必谨慎。如果你的代币不符合 ERC20 规范,请进行 DYOR。 需要注意的具体事项包括(但不限于):异国情调的 rebase 代币、transfer 收费代币、支持重入行为的代币(如 ERC-777)以及其他非标准 ERC20 衍生品。

高级概念

本文档按照以下主题组织(单击每个主题以转到相关部分):

重要的状态变量

  • DistributionRoot[] public distributionRoots
    • distributionRoots 存储由 rewards updater 提交的历史奖励 Merkle 树根。对于每个收益者,奖励 Merkle 树存储每个 ERC20 奖励代币的累积收益。有关 Merkle 树结构的更多详细信息,请参见下面的 奖励 Merkle 树结构
  • mapping(address => address) public claimerFor: earner => claimer
    • Staker 和运营商可以指定一个“claimer”,他们可以通过 processClaim 代表他们申领奖励。如果在 claimerFor 中未设置 claimer,则 earner 必须自己调用 processClaim
    • 请注意,claimer 不一定是奖励接收者,但他们确实有权在代表 earner 调用 processClaim 时指定接收者。
  • mapping(address => mapping(IERC20 => uint256)) public cumulativeClaimed: earner => token => 累计申领的总金额
    • 收益人 (Staker/运营商) 的映射,用于跟踪每个奖励代币的累计申领收益总额。此映射用于计算 Merkle 树中存储的 cumulativeEarnings 与先前申领的总金额之间的差额。然后,此差额将转移到指定的目的地地址。
  • uint16 public defaultOperatorSplitBips链下使用,rewards updater 用于计算运营商特定奖励的拆分。
    • 对于初始奖励发布,预计这是一个 10% 的固定利率。以基点表示,为 1000
  • mapping(address => mapping(address => OperatorSplit)) internal _operatorAVSSplitBips: operator => AVS => OperatorSplit
    • 运营商为每个 OperatorDirectedRewardsSubmission 指定其给定 AVS 的自定义拆分,其中 Staker 接收剩余金额的相对比例(按 stake 权重)。
  • mapping(address => OperatorSplit) internal _operatorPISplitBips: operator => OperatorSplit
    • 运营商还可以为 programmatic incentives 指定其自定义拆分,其中 Staker 同样会获得剩余金额的相对比例(按 stake 权重)。
  • mapping(address operator => mapping(bytes32 operatorSetKey => OperatorSplit split)) internal _operatorSetSplitBips: operator => 运营商集合 Key => OperatorSplit
    • 运营商可以为其给定的运营商集合指定自定义拆分,这比全面的 AVS 拆分更精细

有用的定义

  • AVS(Autonomous Verifiable Service,自治可验证服务)是指将奖励提交给 RewardsCoordinator 的合约实体。
  • 运营商集合 是指已注册的运营商和策略的集合。有关更多详细信息,请参见 ELIP-002
  • 运营商集合 Key 描述 AVS 地址和唯一标识运营商集合的 ID 的元组。有关详细信息,请参见 AllocationManager
  • 除非另有说明,否则 rewards submission 包括 v1 RewardsSubmission 和 v2 OperatorDirectedRewardsSubmission 类型。
  • 内部函数 _checkClaim(RewardsMerkleClaim calldata claim, DistributionRoot memory root) 检查声明是否包含在 DistributionRoot 的 Merkle 树中
    • 如果以下任何一项为真,它将恢复:
      • 不匹配的输入参数长度:tokenIndices、tokenTreeProofs、tokenLeaves
      • 从调用 _verifyEarnerClaimProof 时恢复的 earner 证明
      • 从调用 _verifyTokenClaimProof 时恢复的任何 token 证明

提交奖励请求

奖励最初提交给合约,以便通过以下函数分配给运营商和 Staker:

createAVSRewardsSubmission

function createAVSRewardsSubmission(
    RewardsSubmission[] calldata RewardsSubmissions
)
    external
    onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION)
    nonReentrant

由 AVS 调用,以提交要分配给所有已注册运营商(以及委托给每个运营商的 Staker)的 RewardsSubmission 列表。RewardsSubmission 包含以下字段:

  • IERC20 token:用于 rewards submission 的 ERC20 代币的地址
  • uint256 amount:要转移到 RewardsCoordinatortoken 金额
  • uint32 startTimestamp:提交时间范围的开始时间
  • uint32 duration:提交时间范围的持续时间,以秒为单位
  • StrategyAndMultiplier[] strategiesAndMultipliersStrategyAndMultiplier 结构的数组,用于定义 AVS 认为有资格获得奖励的 EigenLayer 策略的线性组合。每个 StrategyAndMultiplier 包含:
    • IStrategy strategy:策略的地址,用于对 Staker/运营商的相对份额进行加权,以确定其奖励金额
    • uint96 multiplier:线性组合中策略的相对权重。(建议在此处使用 1e18 作为基本乘数,并相应地调整相对权重)

对于每个提交的 RewardsSubmission,此方法执行 transferFrom 以将指定的奖励 tokenamount 从调用者转移到 RewardsCoordinator

资格

为了有资格申领 createAVSRewardsSubmission 奖励,运营商应在奖励发放的时间段内在 AVSDirectory 中为 AVS 注册(请参阅 AVSDirectory.registerOperatorToAVS 的文档)。如果运营商没有资格,则委托给运营商的任何 Staker 也没有资格。

此外,AVS ServiceManager 合约还必须实现接口 ServiceManager.getRestakeableStrategiesServiceManager.getOperatorRestakedStrategies,以便成功分配其奖励,因为这些视图函数在链下作为奖励分配过程的一部分被调用。这默认在 ServiceManagerBase 合约中实现,但如果未从基本合约继承,则需要注意。 请参阅此处的 ServiceManagerBase 抽象合约:ServiceManagerBase.sol

奖励分配

AVS 的运营商和委托的 Staker 之间的奖励分配在链下确定,使用 RewardsSubmission 结构中提供的策略和乘数以及这些已定义策略在 RewardsSubmission 时间范围内的实际份额。这些份额从 EigenPodManager(在 Beacon Chain ETH 策略的情况下)或 StrategyManager 中读取,对于任何其他策略。请注意,Staker 的份额专门用于确定奖励分配;运营商根据他们自己的 deposited 份额和配置的 defaultOperatorSplitBips 的组合获得收益。

效果

  • 对于每个 RewardsSubmission 元素
    • amounttoken 从 msg.sender (AVS) 转移到 RewardsCoordinator
    • 对 msg.sender(AVS)、nonce 和 RewardsSubmission 结构进行哈希处理,以创建唯一的奖励哈希,并将此值设置为 isAVSRewardsSubmissionHash 映射中的 true
    • 递增 submissionNonce[msg.sender]
    • 发出 AVSRewardsSubmissionCreated 事件

要求

  • 暂停状态 不能 设置为:PAUSED_AVS_REWARDS_SUBMISSION
  • 函数调用未重入
  • 对于每个 RewardsSubmission 元素
    • 调用内部函数 _validateRewardsSubmission() 的要求
      • rewardsSubmission.strategiesAndMultipliers.length > 0
      • rewardsSubmission.amount > 0
      • rewardsSubmission.amount &lt;= MAX_REWARDS_AMOUNT
      • rewardsSubmission.duration &lt;= MAX_REWARDS_DURATION
      • rewardsSubmission.duration % calculationIntervalSeconds == 0
      • rewardsSubmission.duration > 0
      • rewardsSubmission.startTimestamp % calculationIntervalSeconds == 0
      • block.timestamp - MAX_RETROACTIVE_LENGTH &lt;= rewardsSubmission.startTimestamp
      • GENESIS_REWARDS_TIMESTAMP &lt;= rewardsSubmission.startTimestamp
      • rewardsSubmission.startTimestamp &lt;= block.timestamp + MAX_FUTURE_LENGTH
      • rewardsSubmission.strategiesAndMultipliers 的要求
        • 每个 strategy 都被列入 StrategyManager 的存款白名单,或者是 beaconChainETHStrategy
        • rewardsSubmission.strategiesAndMultipliers 按升序策略地址排序,以防止重复策略
    • transferFrom 必须 成功地将 msg.senderamounttoken 转移到 RewardsCoordinator

下面的文本图更好地可视化了 RewardsSubmission 的有效开始时间戳

RewardsSubmission 有效 startTimestamp 的滑动窗口

场景 A:GENESIS_REWARDS_TIMESTAMP 在范围内
        &lt;-----MAX_RETROACTIVE_LENGTH-----> t (block.timestamp) &lt;---MAX_FUTURE_LENGTH--->
            &lt;--------------------startTimestamp 的有效范围------------------------>
            ^
        GENESIS_REWARDS_TIMESTAMP

场景 B:GENESIS_REWARDS_TIMESTAMP 超出范围
        &lt;-----MAX_RETROACTIVE_LENGTH-----> t (block.timestamp) &lt;---MAX_FUTURE_LENGTH--->
        &lt;------------------------startTimestamp 的有效范围------------------------>
    ^
GENESIS_REWARDS_TIMESTAMP

createRewardsForAllSubmission

function createRewardsForAllSubmission(
    RewardsSubmission[] calldata RewardsSubmissions
)
    external
    onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION)
    onlyRewardsForAllSubmitter
    nonReentrant

此方法在功能上与上面的 createAVSRewardsSubmission 相同,除了:

  • 它只能由列入白名单的“所有奖励提交者”调用
  • 所有 Staker 都有资格获得奖励,而不是专门为给定的 AVS 注册的 Staker

效果

  • 请参阅上面的 createAVSRewardsSubmission。唯一的区别是:
    • 每个 rewards submission 哈希都存储在 isRewardsSubmissionForAllHash 映射中
    • 发出 RewardsSubmissionForAllCreated 事件

要求

  • 请参阅上面的 createAVSRewardsSubmission。唯一的区别是,每个计算出的 rewards submission 哈希 必须 尚未存在于 isRewardsSubmissionForAllHash 映射中。

createRewardsForAllEarners

function createRewardsForAllEarners(
    RewardsSubmission[] calldata RewardsSubmissions
)
    external
    onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION)
    onlyRewardsForAllSubmitter
    nonReentrant

此方法在功能上与上面的 createAVSRewardsSubmission 相同,除了:

  • 它只能由列入白名单的“所有奖励提交者”调用
  • 只有选择加入至少一个 AVS 的运营商和运营商的委托 staker 才有资格获得奖励

效果

  • 请参阅上面的 createAVSRewardsSubmission。唯一的区别是:
    • 每个 rewards submission 哈希都存储在 isRewardsSubmissionForAllEarnersHash 映射中
    • 发出 RewardsSubmissionForAllEarnersCreated 事件

要求

  • 请参阅上面的 createAVSRewardsSubmission。唯一的区别是,每个计算出的 rewards submission 哈希 必须 尚未存在于 isRewardsSubmissionForAllEarnersHash 映射中。

createOperatorDirectedAVSRewardsSubmission

function createOperatorDirectedAVSRewardsSubmission(
    address avs,
    OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
)
    external
    onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION)
    checkCanCall(avs)
    nonReentrant

AVS 可以通过调用 createOperatorDirectedAVSRewardsSubmission() 来提交 Rewards v2 提交,其中包含任何自定义链上或链下逻辑,以确定其奖励分配策略。这可以是特定于运营商在特定时间段内执行的工作的自定义,可以是一个 flat 奖励率,或者基于 AVS 经济模型的其他一些结构。这将使 AVS 能够灵活地奖励不同运营商的绩效和其他变量,同时保持对委托给同一运营商和策略的 Staker 的相同易于计算的奖励率。AVS 可以提交以不同代币计价的多个基于绩效的奖励,从而实现更大的灵活性。

效果

  • 对于每个 OperatorDirectedRewardsSubmission 元素
    • amounttokenmsg.sender 转移到 RewardsCoordinator
    • AVSnonceOperatorDirectedRewardsSubmission 结构进行哈希处理,以创建唯一的奖励哈希,并将此值设置为 isOperatorDirectedAVSRewardsSubmissionHash 映射中的 true
    • 递增 submissionNonce[avs]
    • 发出 OperatorDirectedAVSRewardsSubmissionCreated 事件

要求

  • 暂停状态 不能 设置为:PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION
  • 调用者 必须 获得授权,无论是 AVS 本身还是管理员/被任命者(请参阅 PermissionController.md
  • 函数调用未重入
  • 对于每个 OperatorDirectedRewardsSubmission 元素:
    • 调用内部函数 _validateOperatorDirectedRewardsSubmission() 的要求
    • operatorDirectedRewardsSubmission.strategiesAndMultipliers.length > 0
    • operatorDirectedRewardsSubmission.duration &lt;= MAX_REWARDS_DURATION
    • operatorDirectedRewardsSubmission.duration % calculationIntervalSeconds == 0
    • operatorDirectedRewardsSubmission.duration > 0
    • operatorDirectedRewardsSubmission.startTimestamp % calculationIntervalSeconds == 0
    • block.timestamp - MAX_RETROACTIVE_LENGTH &lt;= operatorDirectedRewardsSubmission.startTimestamp
    • GENESIS_REWARDS_TIMESTAMP &lt;= operatorDirectedRewardsSubmission.startTimestamp
    • 对于每个 operatorDirectedRewardsSubmission.strategiesAndMultipliers 元素:
      • 每个 strategy 都被列入 StrategyManager 的存款白名单,或者是 beaconChainETHStrategy
      • rewardsSubmission.strategiesAndMultipliers 按升序策略地址排序,以防止重复策略
    • operatorDirectedRewardsSubmission.operatorRewards.length > 0
    • 对于每个 operatorDirectedRewardsSubmission.operatorRewards 元素:
      • operatorReward.operator != address(0)
      • currOperatorAddress &lt; operatorReward.operator
      • operatorReward.amount > 0
    • totalAmount &lt;= MAX_REWARDS_AMOUNT,其中 totalAmount 是每个 operatorReward.amount 的总和
    • operatorDirectedRewardsSubmission.startTimestamp + operatorDirectedRewardsSubmission.duration &lt; block.timestamp,强制执行严格的追溯奖励提交
    • transferFrom 必须 成功地将 msg.senderamounttoken 转移到 RewardsCoordinator

createOperatorDirectedOperatorSetRewardsSubmission

function createOperatorDirectedOperatorSetRewardsSubmission(
    OperatorSet calldata operatorSet,
    OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
)
    external
    onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION)
    checkCanCall(operatorSet.avs)
    nonReentrant

此函数允许 AVS 向特定的运营商集合提交奖励,从而根据分配给特定运营商集合的任务或任何其他自定义 AVS 逻辑,实现更精细的目标奖励。其功能几乎与 createOperatorDirectedAVSRewardsSubmission 相同,除了某些特定于运营商集合的要求、状态变量和事件。

请注意,AVS 必须指定一个已向 AVS 注册的运营商集合;换句话说,属于不同 AVS 的运营商集合或未注册的运营商集合将导致此函数恢复。

另请注意,使用持续时间延长到 slashing 发布之前的 duration 发出此奖励提交将导致那些在 slashing 发布之前的奖励快照退还给 AVS(这在 Sidecar 奖励计算逻辑中处理)。

效果

  • 请参阅上面的 createOperatorDirectedAVSRewardsSubmission。唯一的区别是:
    • 每个奖励提交都存储在 isOperatorDirectedOperatorSetRewardsSubmissionHash 映射中
    • 发出 OperatorDirectedOperatorSetRewardsSubmissionCreated 事件

要求


分配和申领奖励

rewards updater 计算奖励分配,并通过以下函数提交可申领的根:submitRoot。他们还可以禁用尚未激活的根:

收益者可以使用以下函数配置和申领这些奖励:

submitRoot

function submitRoot(
    bytes32 root,
    uint32 rewardsCalculationEndTimestamp
)
    external
    onlyWhenNotPaused(PAUSED_SUBMIT_DISABLE_ROOTS)
    onlyRewardsUpdater

仅由 rewardsUpdater 地址调用,以在 RewardsCoordinator 中创建一个新的 DistributionRootDistributionRoot 结构包含以下字段:

  • bytes32 root:奖励 Merkle 树的 Merkle 根
  • uint32 rewardsCalculationEndTimestamp:提交 DistributionRoot 的奖励时间范围的结束时间
  • uint32 activatedAt:激活 DistributionRoot 并可以针对其进行申领的时间戳(以秒为单位)

submitRoot 将新的 DistributionRoot 推送到 distributionRoots 数组。DistributionRoot.activatedAt 时间戳设置为 block.timestamp + activationDelay(),以便在可以处理申领之前允许延迟。一旦此延迟过去,该根可用于验证向 Staker/运营商发放的奖励的 Merkle 证明。

效果

  • 将新的 DistributionRoot 推送到 distributionRoots 数组
  • currRewardsCalculationEndTimestamp 设置为参数 rewardsCalculationEndTimestamp
  • 发出 DistributionRootSubmitted 事件

要求

  • 暂停状态 不能 设置为:PAUSED_SUBMIT_DISABLE_ROOTS
  • msg.sender 必须rewardsUpdater
  • rewardsCalculationEndTimestamp > currRewardsCalculationEndTimestamp
  • rewardsCalculationEndTimestamp &lt; block.timestamp

disableRoot

function disableRoot(
    uint32 rootIndex
)
    external
    onlyWhenNotPaused(PAUSED_SUBMIT_DISABLE_ROOTS)
    onlyRewardsUpdater

仅由 rewardsUpdater 地址调用,以禁用 RewardsCoordinator 中尚未激活的挂起的 DistributionRoot(尚未达到 activatedAt 时间戳)。一旦达到 activatedAt 时间戳,根将无法再被禁用,并被视为最终确定且可针对其进行申领。 这是为了添加额外的措施来防止发布到合约的无效根,无论是由于错误还是可能发布的恶意根。

效果

  • 将相应 DistributionRootdisabled 字段设置为 True
  • 无法再在 processClaim 中针对 DistributionRoot 进行申领
  • 发出 DistributionRootDisabled 事件

要求

  • 暂停状态 不能 设置为:PAUSED_SUBMIT_DISABLE_ROOTS
  • msg.sender 必须rewardsUpdater
  • rootIndex &lt; distributionRoots.length
  • root.disabled == False
  • block.timestamp &lt; root.activatedAt
  • rewardsCalculationEndTimestamp &lt; block.timestamp

setClaimerFor

function setClaimerFor(address claimer) external

由 earner (Staker/运营商) 调用,以设置可以代表他们调用 processClaim 的申领人地址。如果未设置申领人(claimerFor[earner] == address(0)),则 earner 本身可以直接调用 processClaim

效果

  • claimerFor[msg.sender] 设置为输入参数 claimer
  • 发出 ClaimerForSet 事件

processClaim

function processClaim(
    RewardsMerkleClaim calldata claim,
    address recipient
)
    external
    onlyWhenNotPaused(PAUSED_PROCESS_CLAIM)
    nonReentrant

由 earner (Staker/运营商) 调用,以通过提供针对已发布的 DistributionRoot 的 Merkle 证明来申领其累积的收益。如果 earner 已配置申领人(通过 setClaimerFor),则申领人必须改为调用此方法。

RewardsMerkleClaim 结构包含以下字段(有关更多详细信息,请参见 奖励 Merkle 树结构):

  • uint32 rootIndexdistributionRootsDistributionRoot 的索引,用于证明
  • uint32 earnerIndex:Merkle 树中 earner 的帐户根的索引
  • bytes earnerTreeProof:针对 DistributionRoot 的 earner 的 EarnerTreeMerkleLeaf 的证明
  • EarnerTreeMerkleLeaf earnerLeaf:earner 的地址和代币子树根
    • address earner:earner 的地址
    • bytes32 earnerTokenRoot:earner 的代币 Merkle 树的 Merkle 根
  • uint32[] tokenIndices:earner 的子树中代币叶子的索引
  • bytes[] tokenTreeProofs:针对 earner 的 earnerTokenRoot 的代币叶子的证明
  • TokenTreeMerkleLeaf[] tokenLeaves:要申领的代币叶子:
    • IERC20 token:要申领的 ERC20 代币
    • uint256 amount:要申领的 ERC20 代币数量

processClaim 是一个简单的包装函数,它调用内部函数 _processClaim,该函数包含所有必要的逻辑。

_processClaim 将首先调用 _checkClaim 以验证针对指定 rootIndex 处的 DistributionRoot 的 Merkle 证明。这是通过首先对 DistributionRoot 执行 earner 的 EarnerTreeMerkleLeaf 的 Merkle 证明验证,然后对于每个 tokenIndex,验证每个代币叶子是否与 earner 的 earnerTokenRoot 对齐来完成的。

如果 earner 未设置申领人,则调用者必须是 claimerFor 映射中设置的申领人地址,或者 earner 本身。

在验证了申领之后,对于每个代币叶子,计算 Merkle 树中的累积收入与合约中上次存储的先前申领总额之间的差异,并从 RewardsCoordinator 合约转移到地址 recipient

效果

  • 对于每个 claim.tokenLeaves
    • 计算 uint claimAmount = tokenLeaf.cumulativeEarnings - cumulativeClaimed[earner][tokenLeaf.token]
      • tokenLeaf.tokenclaimAmount 转移到指定的 recipient
    • 更新 earner 和代币的 cumulativeClaimed 映射
    • 发出 RewardsClaimed 事件

要求

  • 暂停状态 不能 设置为:PAUSED_PROCESS_CLAIM
  • claim 必须具有针对有效 DistributionRoot 的有效证明:
    • 对于由 claim.rootIndex 给定的 DistributionRoot,根 必须 处于活动状态(block.timestamp >= root.activatedAt
    • claim.tokenIndices 必须 等于 claim.TokenTreeProofsclaim.tokenLeaves 的长度
    • claim.earnerTreeProof 必须 针对 DistributionRoot 验证 claim.earnerLeaf
    • 对于每个 claim.tokenIndices[i]
      • claim.tokenTreeProofs[i] 必须 针对 claim.earnerLeaf.earnerTokenRoot 验证 claim.tokenLeaves[i]
  • 如果在 claim.earnerLeaf.earner 中指定的 earnerclaimerFor[earner] 中有指定的 claimer,则 msg.sender 必须claimer
    • 否则,msg.sender 必须earner
  • 对于每个 TokenTreeMerkleLeaf,
    • tokenLeaf.cumulativeEarnings > cumulativeClaimed[earner][token]: cumulativeEarnings 必须大于 cumulativeClaimed. 尝试使用相同的证明重新申领将恢复,因为申领的和收入值将相等,从而打破此要求。
    • tokenLeaf.token.safeTransfer(recipient, claimAmount) 必须 成功

processClaims

function processClaims(
        RewardsMerkleClaim[] calldata claims,
        address recipient
)
    external
    onlyWhenNotPaused(PAUSED_PROCESS_CLAIM)
    nonReentrant

processClaims 是围绕 _processClaim 的简单包装函数,对于每个提供的声明调用一次。

效果

  • 对于每个 RewardsMerkleClaim 元素:请参阅上面的 processClaim

要求

---### 系统配置

setActivationDelay

function setActivationDelay(uint32 _activationDelay) external onlyOwner

允许 Owner 设置全局的 activationDelay。激活延迟是指在提交 DistributionRoot 后,可以对其进行声明的时间(以秒为单位)。此延迟允许相关方在开始声明之前对根执行验证。

效果

  • 设置全局的 activationDelay
  • 发出 ActivationDelaySet 事件

要求

  • 调用者必须是 Owner

setDefaultOperatorSplit

function setDefaultOperatorSplit(uint16 split) external onlyOwner

允许 Owner 以 basis points 为单位设置默认的 operator split。

此 split 在计算给定奖励分配的 Operator 收益时链下使用。Operator split 计算为分配给每个 Operator 的奖励金额的百分比。此 split 从奖励金额中扣除,之后剩余部分用于计算分配给委托给 Operator 的任何 Staker 的奖励。

效果

  • 设置 defaultOperatorSplitBips
  • 发出 DefaultOperatorSplitBipsSet 事件

要求

  • 调用者必须是 Owner

setRewardsUpdater

function setRewardsUpdater(address _rewardsUpdater) external onlyOwner

允许 Owner 设置 rewardsUpdater 地址。rewardsUpdater 是一个单例地址,可以将新的 DistributionRoots 提交到 RewardsCoordinatorrewardsUpdater 是一个受信任的实体,它执行本文档中描述的大部分计算和 Merkle 树结构。

效果

  • 设置全局的 rewardsUpdater 地址
  • 发出 RewardsUpdaterSet 事件

要求

  • 调用者必须是 Owner

setRewardsForAllSubmitter

function setRewardsForAllSubmitter(address _submitter, bool _newValue) external onlyOwner

允许 Owner 更新 isRewardsForAllSubmitter 映射中 _submitter 的权限。此映射用于确定给定地址是否是 createRewardsForAllSubmission 方法的有效提交者。

效果

  • 将地址 _submitterisRewardsForAllSubmitter 映射设置为布尔值 _newValue
  • 发出 RewardsForAllSubmitterSet 事件

要求

  • 调用者必须是 Owner

setOperatorAVSsplit

function setOperatorAVSSplit(
    address operator,
    address avs,
    uint16 split
)
    external
    onlyWhenNotPaused(PAUSED_OPERATOR_AVS_SPLIT)
    checkCanCall(operator)

对于给定的 AVS,Operator 可以设置一个 split,该 split 将确定其归属奖励中有多少百分比分配给自己。剩余的百分比将分配给 Staker。

该 split 将在合约所有者设置的 activationDelay 之后生效。请注意,一旦 operator 启动 split 更新,必须经过 activationDelay 才能启动新的 split 更新。

效果

  • operatorSplit.activatedAt 更新为 block.timestamp + activationDelay
  • 如果 operator 尚未初始化,则将 operatorSplit.oldSplitBips 设置为 defaultOperatorSplitBips。否则,将 operatorSplit.oldSplitBips 设置为当前的 newSplitBips
  • operatorSplit.newSplitBips 更新为 split
  • 发出 OperatorAVSSplitBipsSet 事件

要求

  • 调用者必须经过授权,可以是 operator 本身,也可以是管理员/被任命者(请参阅 PermissionController.md
  • Split 必须 <= 10,000 basis points (100%)
  • 当前的 block.timestamp 必须大于当前的 operatorSplit.activatedAt
    • 任何待处理的 split 必须在设置新的 split 之前完成。

setOperatorPIsplit

function setOperatorPISplit(
    address operator,
    uint16 split
)
    external
    onlyWhenNotPaused(PAUSED_OPERATOR_PI_SPLIT)
    checkCanCall(operator)

setOperatorAVSSplit 类似,Operator 可以为程序化激励设置他们的 split,从而允许他们指定他们将保留这些奖励的百分之多少,以及将分配给他们的 Staker 的百分之多少。allocationDelay 也适用于此处,以及在延迟通过之前无法重新启动 split 更新。

效果

  • 请参阅上面的 setOperatorAVSSplit。唯一的区别是:
    • 该 split 存储在 _operatorPISplitBips 中,而不是 _operatorAVSSplitBips 中。
    • 发出 OperatorPISplitBipsSet 事件

要求

  • 请参阅上面的 setOperatorAVSSplit。唯一的区别是:
    • 暂停状态改为:PAUSED_OPERATOR_PI_SPLIT

setOperatorSetSplit

function setOperatorSetSplit(
    address operator,
    OperatorSet calldata operatorSet,
    uint16 split
) 
    external 
    onlyWhenNotPaused(PAUSED_OPERATOR_SET_SPLIT) 
    checkCanCall(operator)

效果

  • 请参阅上面的 setOperatorAVSSplit。唯一的区别是:
    • 该 split 存储在 _operatorSetSplitBips 中,而不是 _operatorAVSSplitBips
    • 发出 OperatorSetSplitBipsSet 事件

要求

  • 请参阅上面的 setOperatorAVSSplit。唯一的区别在于:
    • 根据 allocationManager.isOperatorSet()operatorSet 必须是给定 AVS 的注册 operator set
    • 暂停状态改为:PAUSED_OPERATOR_SET_SPLIT

奖励 Merkle 树结构

此 Merkle 树用于验证针对 DistributionRoot 的声明。

提交新的 DistributionRoot 时,奖励更新者会将自上次提交的 DistributionRoot 以来,由 AVS 提交的所有 RewardsSubmissions 合并到 Merkle 树中,该 Merkle 树由收益者及其各自奖励代币的累计收益组成。

当收益者或其指定的声明者调用 processClaim 时,他们必须提供一个 RewardsMerkleClaim 结构,其中包含验证其针对最新 DistributionRoot 的声明的必要信息。Merkle 证明验证在内部 _checkClaim 辅助函数中完成。此函数针对 DistributionRoot 验证收益者的 EarnerTreeMerkleLeaf 的 Merkle 证明,然后对于每个 tokenIndex,针对收益者的 earnerTokenRoot 验证每个 token leaf。

声明者可以选择性地选择要针对其进行证明并声明累计收益的 token leaf。在 processClaim 调用中声明的每个 token 奖励都会将 token 发送到调用中指定的 recipient 地址。

奖励 Merkle 树的结构如下图所示:

![.] (https://img.learnblockchain.cn/2025/09/01/RewardsCoordinator_Merkle_Tree.png)


链下计算

奖励通过链下数据管道计算。该管道以 SNAPSHOT_CADENCE 拍摄核心合约状态的快照,目前设置为每天一次。然后,它将这些快照与任何有效的奖励结合起来,以计算收益者单日的奖励是多少。每隔 CALCULATION_INTERVAL_SECONDS,奖励会累积到 lastRewardsTimestamp + CALCULATION_INTERVAL_SECONDS,并由具有 rewardsUpdater 角色的实体在链上发布。

鉴于链下管道的精度界限,MAX_REWARDS_AMOUNT 设置为 1e38-1。有关链下计算的深入概述,请参见此处

  • 原文链接: github.com/Layr-Labs/eig...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Layr-Labs
Layr-Labs
江湖只有他的大名,没有他的介绍。