EigenLayer 奖励计算

  • Layr-Labs
  • 发布于 2025-03-18 13:14
  • 阅读 14

本文档详细描述了EigenLayer奖励计算的流程,该过程通过一系列SQL查询来计算AVS向stakers和operators分配奖励。计算分为数据提取、数据转换和奖励计算三个阶段,每日运行,以确保与链上状态的一致性。文档还深入探讨了关键的SQL查询,涉及staker和operator的状态、奖励提交以及快照和窗口处理,最终生成Merkle化的奖励分配结果。

概述

先前奖励计算的链接是 here.

EigenLayer 奖励计算是一组 SQL 查询,用于计算通过 RewardsCoordinator 由 AVS 向 staker 和 operator 分配的奖励。 数据被导入到 Postgres 表中并进行转换,以计算最终奖励。

这些查询在一个每日任务中运行,该任务使用核心合约状态的快照来计算来自任何活跃奖励提交的奖励。

计算过程

该计算分 3 个阶段进行,并且每天运行

  1. 数据提取:从事件日志中提取数据。 这些被称为青铜表。 作为此过程的一部分,我们会使用额外的数据提供商来协调所有事件数据(包括 RPC 数据),以确保一致性。
  2. 数据转换:将青铜表中的数据转换为链上状态的每日快照。
  3. 奖励计算:将快照与活动奖励提交进行交叉引用,以计算对 staker 和 operator 的奖励。

然后,该管道汇总所有奖励,直到 lastRewardTimestamp + calculationIntervalSeconds,并提交一个将每个 earner 的累积总和进行 Merkle 化处理的根,提交到 RewardsCoordinator

作业排序

奖励计算是一个 airflow 管道,每天 UTC 时间 16:00 运行。 对链上事件和事件计算的查询都向下舍入到 UTC 时间 0:00。 也就是说,如果管道在 4 月 27 日 UTC 时间 16:00 运行,则 cutoff_date 参数设置为 4 月 26 日 UTC 时间 0:00。

我们通过在 UTC 时间 0:00 之后几个小时运行每日管道来处理重组,从而为我们的重组处理程序提供足够的时间来恢复状态。

关键考虑因素

以下三个部分中的每一个都详细说明了阅读查询和理解计算时需要注意的关键注意事项。 这些注意事项的摘要是:

  • 每 24 小时拍摄核心合约状态快照:RewardsCoordinator 中的 SNAPSHOT_CADENCE
  • 来自链上状态的快照向上舍入到最接近的一天 UTC 时间 0:00。 唯一的例外是 operator{'<>'}avs 的注销,这些注销向下舍入到最接近的一天 UTC 时间 0:00
  • 由于快照已向上舍入,因此我们只关心来自一天的最新状态更新
  • 所有 earner 的奖励分配必须 {'<'}= 奖励提交的支付金额

词汇表

  • earner:接收奖励的实体,即 staker 或 operator
  • calculationIntervalSeconds:奖励提交持续时间必须是的倍数
  • SNAPSHOT_CADENCE:拍摄 EigenLayer 核心合约状态快照的频率
  • typo rewardSnaphot -> rewardSnapshot:快照中给 earner 的奖励
  • cutoff-date:运行转换的日期。 始终设置为一天的 UTC 时间 0:00
  • run:每日奖励管道作业的迭代
  • stakeWeight:AVS 如何评估其 earner 的 stake,由每个奖励策略的乘数给出
  • gold_table:包含 rewardSnapshots 的表。 其列为 earneramounttokensnapshotreward_hash

数据提取

关键考虑因素

份额转换为 Decimal(78,0),这是一种最多可容纳 uint256 的数据类型。 允许存款的 token(所有 LST 和 Eigen)和原生 ETH 不应出现截断问题。

截止日期

我们在每次运行时使用以下逻辑设置截止日期:

def get_cutoff_date():
    # 获取 UTC 中的当前时间
    ts = datetime.now(timezone.utc)
    # 向下舍入到当天 UTC 时间 00:00
    ts = ts.replace(hour=0, minute=0, second=0, microsecond=0)
    # 减去 1 天
    ts = ts - timedelta(days=1)

    return ts

Airflow 变量

在管道的每日运行时,如果运行是回填,我们将获取传入的变量。 在回填运行时,我们强制执行开始日期和结束日期有效,即结束日期不得晚于截止日期,并且开始日期不得晚于结束日期。

如果在管道运行中遗漏了事件,则在最坏的情况下运行回填。 我们与多个数据供应商运行协调以确保不必这样做。 此外,我们在管道生成的末尾运行健全性检查查询,以确保:

  1. earner 的累积奖励永远不会减少
  2. 对于给定的快照和奖励哈希,AVS 的每日 token 始终 >= sum(earner 支出)
  3. (earnerreward_hashsnapshot) 的行数永远不会减少
def get_gold_calculation_dates(**kwargs):
    # 截止日期
    cutoff_date = get_cutoff_date()
    cutoff_date_str = cutoff_date.strftime('%Y-%m-%d %H:%M:%S')

    # 回填日期
    dag_run = kwargs.get('dag_run')
    if dag_run is not None:
        start_date_str = dag_run.conf.get('start_date', '1970-01-01 00:00:00')
        end_date_str = dag_run.conf.get('end_date', cutoff_date_str)
        is_backfill = str.lower(dag_run.conf.get('is_backfill', 'false'))
    else:
        raise ValueError('Dag run is None')

    # 清理开始和结束日期
    start_datetime = datetime.strptime(start_date_str, '%Y-%m-%d %H:%M:%S')
    end_datetime = datetime.strptime(end_date_str, '%Y-%m-%d %H:%M:%S')
    cutoff_datetime = datetime.strptime(cutoff_date_str, '%Y-%m-%d %H:%M:%S')

    if start_datetime >= end_datetime:
        raise ValueError('开始日期必须在结束日期之前')

    if end_datetime > cutoff_datetime:
        raise ValueError('结束日期必须小于或等于截止日期')

    # 推送到 XCom
    kwargs['ti'].xcom_push(key='cutoff_date', value=end_date_str)
    kwargs['ti'].xcom_push(key='rewards_start', value=start_date_str)
    kwargs['ti'].xcom_push(key='is_backfill', value=is_backfill)

查询

这组查询从 EigenLayer Core 合约中提取事件数据。 事件日志会自动从合约 ABI 中解码 here。 运行 forge build 将构建合约,ABI 存储在 /out 文件夹中。

在下面的查询中,block_date 是区块的日期,而 block_time 是区块的完整日期 + 时间。

Staker 状态

存款

SELECT
  lower(coalesce(t.output_data ->> 'depositor', t.output_data ->> 'staker')) as staker,
  lower(t.output_data ->> 'strategy') as strategy,
  (t.output_data ->> 'shares')::numeric(78,0) as shares,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs as t
LEFT JOIN blocks as b ON (t.block_sequence_id = b.id)
WHERE t.address = '{{ var('strategy_manager_address') }}'
AND t.event_name = 'Deposit'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

EigenPod 份额

注意:份额可以是负数

SELECT
  lower(t.arguments #>> '{0,Value}') AS staker,
  (t.output_data ->> 'sharesDelta')::numeric(78,0) as shares,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('eigen_pod_manager_address') }}'
AND t.event_name = 'PodSharesUpdated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

M1 提款

M1 中的提款通过 StrategyManager 路由。 请注意,我们删除了作为 M1 中的份额完成的单笔提款,因为此代码路径没有存款事件。

SELECT
  lower(coalesce(t.output_data ->> 'depositor', t.output_data ->> 'staker')) as staker,
  lower(t.output_data ->> 'strategy') as strategy,
  (t.output_data ->> 'shares')::numeric(78,0) as shares,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('strategy_manager_address') }}'
AND t.event_name = 'ShareWithdrawalQueued'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'
-- 删除此交易哈希,因为它是 m1 上唯一以份额完成的提款。 没有相应的存款事件。 提款已完成到同一 staker 地址。
AND t.transaction_hash != '0x62eb0d0865b2636c74ed146e2d161e39e42b09bac7f86b8905fc7a830935dc1e'

M2 提款

与 M1 提款事件不同,M2 提款事件返回一个元组,其中包含策略和份额的列表。 因此,我们将元组拆分为单独的行以创建(staker,strategy$_0$,share$_0$),(staker,strategy$_1$,share$_1$)。 我们丢弃从 M1 迁移的所有 M2 提款,因此我们不会重复计算提款。

WITH migrations AS (
  SELECT
    (
      SELECT lower(string_agg(lpad(to_hex(elem::int), 2, '0'), ''))
      FROM jsonb_array_elements_text(t.output_data->'oldWithdrawalRoot') AS elem
    ) AS m1_withdrawal_root,
    (
      SELECT lower(string_agg(lpad(to_hex(elem::int), 2, '0'), ''))
      FROM jsonb_array_elements_text(t.output_data->'newWithdrawalRoot') AS elem
    ) AS m2_withdrawal_root
  FROM transaction_logs t
  WHERE t.address = '{{ var('delegation_manager_address') }}'
  AND t.event_name = 'WithdrawalMigrated'
),
full_m2_withdrawals AS (
  SELECT
    lower(t.output_data #>> '{withdrawal}') as withdrawals,
    (
      SELECT lower(string_agg(lpad(to_hex(elem::int), 2, '0'), ''))
      FROM jsonb_array_elements_text(t.output_data ->'withdrawalRoot') AS elem
    ) AS withdrawal_root,
    lower(t.output_data #>> '{withdrawal, staker}') AS staker,
    lower(t_strategy.strategy) AS strategy,
    (t_share.share)::numeric(78,0) AS shares,
    t_strategy.strategy_index,
    t_share.share_index,
    t.transaction_hash,
    t.log_index,
    b.block_time::timestamp(6),
    to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
    t.block_number
  FROM transaction_logs t
  LEFT JOIN blocks b ON t.block_sequence_id = b.id,
  jsonb_array_elements_text(t.output_data #> '{withdrawal, strategies}') WITH ORDINALITY AS t_strategy(strategy, strategy_index),
  jsonb_array_elements_text(t.output_data #> '{withdrawal, shares}') WITH ORDINALITY AS t_share(share, share_index)
  WHERE t.address = '{{ var('delegation_manager_address') }}'
  AND t.event_name = 'WithdrawalQueued'
  AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'
  AND t_strategy.strategy_index = t_share.share_index
)
-- 解析出从 m1 迁移的 m2 提款
SELECT
  full_m2_withdrawals.*
FROM
  full_m2_withdrawals
LEFT JOIN
  migrations
ON
  full_m2_withdrawals.withdrawal_root = migrations.m2_withdrawal_root
WHERE
  migrations.m2_withdrawal_root IS NULL

Operator 状态

Operator 状态由 staker 委托给他们的 stake 组成。

Operator 份额增加

SELECT
  lower(t.arguments #>> '{0,Value}') as operator,
  lower(t.output_data ->> 'strategy') as strategy,
  (t.output_data ->> 'shares')::numeric(78,0) as shares,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('delegation_manager_address') }}'
AND t.event_name = 'OperatorSharesIncreased'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

Operator 份额减少

SELECT
  lower(t.arguments #>> '{0,Value}') as operator,
  lower(t.output_data ->> 'strategy') as strategy,
  (t.output_data ->> 'shares')::numeric(78,0) as shares,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('delegation_manager_address') }}'
AND t.event_name = 'OperatorSharesDecreased'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

Staker 委托

Staker 已委托

SELECT
  lower(t.arguments #>> '{0,Value}') AS staker,
  lower(t.arguments #>> '{1,Value}') AS operator,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('delegation_manager_address') }}'
AND t.event_name = 'StakerDelegated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

Staker 已取消委托

SELECT
  lower(t.arguments #>> '{0,Value}') AS staker,
  lower(t.arguments #>> '{1,Value}') AS operator,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('delegation_manager_address') }}'
AND t.event_name = 'StakerUndelegated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

Operator 拆分

每天跟踪三种类型的 operator 拆分:

  1. 默认 Operator 拆分:确定所有 operator 的 operator 和其委托 staker 之间的奖励拆分(10% 默认值,1000 个基点)
  2. Operator-PI 拆分:确定 Programmatic Incentives 的 operator 和其委托 staker 之间的奖励拆分
  3. Operator-AVS 拆分:确定非惩罚性 stake 的 operator 和其委托 staker 之间的奖励拆分
  4. Operator-Set 拆分:确定惩罚性 stake 的 operator 和其委托 staker 之间的奖励拆分

拆分计算遵循以下规则:

  • Arno 分叉前:使用 10% 默认值(1000 个基点)
  • Arno 分叉后:使用 operator 特定拆分或 10% 默认值(1000 个基点)
  • Trinity 分叉后:使用 operator 特定拆分、全局默认值或 10% 回退

Operator-AVS 拆分快照示例:

WITH operator_avs_splits_with_block_info as (
SELECT
  oas.operator,
  oas.avs,
  oas.activated_at::timestamp(6) as activated_at,
  oas.new_operator_avs_split_bips as split,
  oas.block_number,
  oas.log_index,
  b.block_time::timestamp(6) as block_time
FROM operator_avs_splits as oas
JOIN blocks as b on (b.number = oas.block_number)
WHERE activated_at &lt; TIMESTAMP '{{.cutoffDate}}'
)

奖励提交

协议中有四种类型的奖励提交:

  1. AVS 奖励提交:由任何 AVS 调用的非惩罚性 stake 的 Permissionless 函数。
  2. 所有 Earner 的奖励:对协议的所有 earner 的 Permissioned 奖励,用于 Programmatic Incentives。
  3. Operator 指向的奖励提交:由任何 AVS 调用的 Permissionless 函数,用于将奖励定向到特定 operator 以获得非惩罚性 stake。
  4. Operator 指向的 Operator Set 奖励提交:由任何 AVS 调用的 Permissionless 函数,用于将奖励定向到特定 operator 以获得惩罚性 stake。

注意:“奖励协调器”(RewardsCoordinator)中的金额最大值为$1e38-1$,这允许我们将其截断为 DECIMAL(38,0)。

AVS 奖励提交

对于每个奖励提交,我们在单独的行中提取每个(strategy、multiplier),以便于核算

SELECT
    lower(tl.arguments #>> '{0,Value}') AS avs,
    lower(tl.arguments #>> '{2,Value}') AS reward_hash,
    coalesce(lower(tl.output_data #>> '{rewardsSubmission}'), lower(tl.output_data #>> '{rangePayment}')) as rewards_submission,
    coalesce(lower(tl.output_data #>> '{rewardsSubmission, token}'), lower(tl.output_data #>> '{rangePayment, token}')) as token,
    coalesce(tl.output_data #>> '{rewardsSubmission,amount}', tl.output_data #>> '{rangePayment,amount}')::numeric(78,0) as amount,
    to_timestamp(coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint)::timestamp(6) as start_timestamp,
    coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint as duration,
    to_timestamp(
            coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint
                + coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint
    )::timestamp(6) as end_timestamp,
    lower(t.entry ->> 'strategy') as strategy,
    (t.entry ->> 'multiplier')::numeric(78,0) as multiplier,
    t.strategy_index as strategy_index,
    tl.transaction_hash,
    tl.log_index,
    b.block_time::timestamp(6),
    to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
    tl.block_number
FROM transaction_logs tl
    LEFT JOIN blocks b ON (tl.block_sequence_id = b.id)
    CROSS JOIN LATERAL jsonb_array_elements(
        coalesce(tl.output_data #> '{rewardsSubmission,strategiesAndMultipliers}',tl.output_data #> '{rangePayment,strategiesAndMultipliers}')
    ) WITH ORDINALITY AS t(entry, strategy_index)
WHERE address = '{{ var('rewards_coordinator_address') }}'
AND event_name = 'AVSRewardsSubmissionCreated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

所有提交的奖励


SELECT
  lower(tl.arguments #>> '{0,Value}') AS avs, -- 保留为 AVS,以便与 range_payments 上的联合兼容。
  lower(tl.arguments #>> '{2,Value}') AS reward_hash,
  coalesce(lower(tl.output_data #>> '{rewardsSubmission}'), lower(tl.output_data #>> '{rangePayment}')) as rewards_submission,
  coalesce(lower(tl.output_data #>> '{rewardsSubmission, token}'), lower(tl.output_data #>> '{rangePayment, token}')) as token,
  coalesce(tl.output_data #>> '{rewardsSubmission,amount}', tl.output_data #>> '{rangePayment,amount}')::numeric(78,0) as amount,
  to_timestamp(coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint)::timestamp(6) as start_timestamp,
  coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint as duration,
  to_timestamp(
            coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint
                + coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint
    )::timestamp(6) as end_timestamp,
  lower(t.entry ->> 'strategy') as strategy,
  (t.entry ->> 'multiplier')::numeric(78,0) as multiplier,
  t.strategy_index as strategy_index,
  tl.transaction_hash,
  tl.log_index,
  b.block_time::timestamp(6),
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  tl.block_number
FROM transaction_logs tl
LEFT JOIN blocks b ON tl.block_sequence_id = b.id
CROSS JOIN LATERAL jsonb_array_elements(
    coalesce(tl.output_data #> '{rewardsSubmission,strategiesAndMultipliers}',tl.output_data #> '{rangePayment,strategiesAndMultipliers}')
) WITH ORDINALITY AS t(entry, strategy_index)
WHERE address = '{{ var('rewards_coordinator_address') }}'
AND event_name = 'RewardsSubmissionForAllCreated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

所有 Earner 提交的奖励

SELECT
  lower(tl.arguments #>> '{0,Value}') AS avs, -- 保留为 AVS,以便与 range_payments 上的联合兼容。
  lower(tl.arguments #>> '{2,Value}') AS reward_hash,
  coalesce(lower(tl.output_data #>> '{rewardsSubmission}'), lower(tl.output_data #>> '{rangePayment}')) as rewards_submission,
  coalesce(lower(tl.output_data #>> '{rewardsSubmission, token}'), lower(tl.output_data #>> '{rangePayment, token}')) as token,
  coalesce(tl.output_data #>> '{rewardsSubmission,amount}', tl.output_data #>> '{rangePayment,amount}')::numeric(78,0) as amount,
  to_timestamp(coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint)::timestamp(6) as start_timestamp,
  coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint as duration,
  to_timestamp(
            coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint
                + coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint
    )::timestamp(6) as end_timestamp,
  lower(t.entry ->> 'strategy') as strategy,
  (t.entry ->> 'multiplier')::numeric(78,0) as multiplier,
  t.strategy_index as strategy_index,
  tl.transaction_hash,
  tl.log_index,
  b.block_time::timestamp(6),
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  tl.block_number
FROM transaction_logs tl
LEFT JOIN blocks b ON tl.block_sequence_id = b.id
CROSS JOIN LATERAL jsonb_array_elements(
    coalesce(tl.output_data #> '{rewardsSubmission,strategiesAndMultipliers}',tl.output_data #> '{rangePayment,strategiesAndMultipliers}')
) WITH ORDINALITY AS t(entry, strategy_index)
WHERE address = '{{ var('rewards_coordinator_address') }}'
AND event_name = 'RewardsSubmissionForAllEarnersCreated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

Operator 指向的奖励提交

WITH _operator_directed_rewards as (
        SELECT
            odrs.avs,
            odrs.reward_hash,
            odrs.token,
            odrs.operator,
            odrs.operator_index,
            odrs.amount,
            odrs.strategy,
            odrs.strategy_index,
            odrs.multiplier,
            odrs.start_timestamp::TIMESTAMP(6),
            odrs.end_timestamp::TIMESTAMP(6),
            odrs.duration,
            odrs.block_number,
            b.block_time::TIMESTAMP(6),
            TO_CHAR(b.block_time, 'YYYY-MM-DD') AS block_date
        FROM operator_directed_reward_submissions AS odrs
        JOIN blocks AS b ON(b.number = odrs.block_number)
        WHERE b.block_time &lt; TIMESTAMP '{{.cutoffDate}}'
    )
    SELECT
        avs,
        reward_hash,
        token,
        operator,
        operator_index,
        amount,
        strategy,
        strategy_index,
        multiplier,
        start_timestamp::TIMESTAMP(6),
        end_timestamp::TIMESTAMP(6),
        duration,
        block_number,
        block_time,
        block_date
    FROM _operator_directed_rewards

Operator 指向的 Operator Set 奖励提交

SELECT
    odosrs.avs,
    odosrs.operator_set_id,
    odosrs.reward_hash,
    odosrs.token,
    odosrs.operator,
    odosrs.operator_index,
    odosrs.amount,
    odosrs.strategy,
    odosrs.strategy_index,
    odosrs.multiplier,
    odosrs.start_timestamp::TIMESTAMP(6),
    odosrs.end_timestamp::TIMESTAMP(6),
    odosrs.duration,
    odosrs.block_number,
    b.block_time::TIMESTAMP(6),
    TO_CHAR(b.block_time, 'YYYY-MM-DD') AS block_date
FROM operator_directed_operator_set_reward_submissions AS odosrs
JOIN blocks AS b ON (b.number = odosrs.block_number)
WHERE b.block_time &lt; TIMESTAMP '{{.cutoffDate}}'

Operator{'<>'}AVS 状态

从 operator 到 AVS 的每次注销和注册都记录在 AVSDirectory 中

Operator 注册

SELECT
  lower(t.arguments #>> '{0,Value}') as operator,
  lower(t.arguments #>> '{1,Value}') as avs,
  (t.output_data -> 'status')::int as status,
  t.transaction_hash,
  t.log_index,
  b.block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date,
  t.block_number
FROM transaction_logs t
LEFT JOIN blocks b ON t.block_sequence_id = b.id
WHERE t.address = '{{ var('avs_directory_address') }}'
AND t.event_name = 'OperatorAVSRegistrationStatusUpdated'
AND date_trunc('day', b.block_time) &lt; TIMESTAMP '{{ var("cutoff_date") }}'

Operator 重新质押的策略

AVS Directory 为 operator 在 AVS 上重新质押或取消重新质押的策略发出事件。 为了检索此信息,我们每 3600 个区块(即,blockNum % 3600 = 0)运行一个 cron 作业,从部署 AVSDirectory 时开始,该作业执行以下操作:

  1. 检索在 AVS 上重新质押的所有 operator
  2. 在每个 AVS 的 serviceManager 合约上调用 getOperatorRestakedStrategies(address operator) returns (address[])

要求 AVS 符合此接口,如我们的 docs 中所述

假设 operator 在时间戳 $t$ 注册到 AVS,则此 cron 作业的示例输出为:

Operator AVS 策略 区块时间
Operator1 AVS-A stETH t
Operator1 AVS-A rETH t
Operator2 AVS-A rETH t
Operator3 AVS-B cbETH t

Operator{'<>'}Operator Set 状态

每个 operator{'<>'}operator set 注册和注销都记录在 AllocationManager 合约中。

Operator Set Operator 注册

select
  osor.*,
  b.block_time::timestamp(6) as block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date
from operator_set_operator_registrations as osor
join blocks as b on (b.number = osor.block_number)
where b.block_time &lt; TIMESTAMP '{{.cutoffDate}}'

Operator Set 策略注册

select
  ossr.*,
  b.block_time::timestamp(6) as block_time,
  to_char(b.block_time, 'YYYY-MM-DD') AS block_date
from operator_set_strategy_registrations as ossr
join blocks as b on (b.number = ossr.block_number)
where b.block_time &lt; TIMESTAMP '{{.cutoffDate}}'

数据转换

一旦我们提取了 EigenLayer 核心合约和 AVS 的所有日志和相关存储,我们就对其进行转换以创建状态的每日快照,分为两部分

  1. 将提取数据聚合到链上合约状态
  2. 将状态合并到范围中并展开为每日快照

关键考虑因素

在第 2 部分中,一旦状态被聚合,我们就将状态范围展开为每日快照。

状态快照向上舍入到最接近的一天,但 operator{'<>'}avs 注销除外,这些注销向下舍入。 让我们假设我们有以下范围,其中事件 A 和 B 是 staker 份额的更新。

GENESIS_TIMESTAMP---------------------Day1---------------------Day2
                     ^       ^
                  A=100    B=200

快照转换的输出应表示在 Day1 上,Staker 拥有 200 个份额。 更一般地,我们采用 [Day$_{i-1}$, Day$_i$] 范围内的最新更新,并将其设置为 Day$_i$ 上的状态。 我们将给定一天的奖励称为奖励快照

Operator{'<>'}AVS 注册/注销

在 operator 注册和注销的情况下:

GENESIS_TIMESTAMP---------------------Day1---------------------Day2
                     ^                       ^
                  注册                注销

最终状态是,operator 已在第 1 天注册和注销,导致没有向 operator 发放奖励。 我们添加此机制是为了保护 operator,如果我们要向上舍入注销,他们将获得额外的奖励天数。 副作用如下:

--------------Day1--------------Day2--------------Day3--------------Day4
          ^                                                       ^
        注册                                             注销
```在这种情况下,operator 将在 Day3 注销,导致 operator 在 [Day3,Day4] 范围内没有收到任何奖励,因为它正在保护 AVS。向下取整注销日期是为什么 `cutoff_date` 必须是 **前一天** 的 0:00 UTC。

#### Operator{'&lt;>'}Operator 集注册/注销

在 operator{'&lt;>'}operator 集注册和注销的情况下:

GENESIS_TIMESTAMP---------------------Day1---------------------Day2 ^ ^ 注册 注销


最终状态是 operator 在第 1 天注册和注销,导致没有向 operator 发放任何奖励。我们添加这个机制是为了防止如果我们向上取整注销日期,operator 获得额外的奖励天数。副作用如下:

--------------Day1--------------Day2--------------Day3--------------Day4 ^ ^ 注册 注销


在这种情况下,operator 将在 Day3 注销,导致 operator 在 [Day3,Day4] 范围内没有收到任何奖励,因为它正在保护 operator 集。向下取整注销日期是为什么 `cutoff_date` 必须是 **前一天** 的 0:00 UTC。

## 第一部分:聚合

### Staker 份额

staker $s$ 和策略 $y$ 的 LST 份额由下式给出:

Shares$_{s,y}$ = Deposits$_{s,y}$ $-$ M1Withdrawals$_{s,y}$ $-$ M2Withdrawals$_{s,y}$

staker 的 Native ETH 份额是给定 staker 的所有 `PodSharesUpdated` 事件的总和。请注意,在此事件中,份额**_可能为负_**。

NativeETHShares$_s$ = $\sum_{i=0}^{n}$ PodSharesUpdated$_i$- M1Withdrawals$_i$ - M2Withdrawals$_i$

将这两个结合起来,我们就得到了每个更新的每个策略的 staker 份额。

此查询的关键部分是:

```sql
SUM(shares) OVER (PARTITION BY staker, strategy ORDER BY block_time, log_index) AS shares,

它获取每个更新处每个(staker,strategy)对的运行总和。

SELECT
    staker,
    strategy,
    -- 对窗口中的每个份额金额求和,以在每次时间戳更新时获得每个 (staker, strategy) 的总份额 */
    SUM(shares) OVER (PARTITION BY staker, strategy ORDER BY block_time, log_index) AS shares,
    transaction_hash,
    log_index,
    strategy_index,
    block_time,
    block_date,
    block_number
FROM (
    SELECT staker, strategy, shares, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number
    FROM {{ ref('staker_deposits') }}

    UNION ALL

    -- 减去 m1 & m2 提款
    SELECT staker, strategy, shares * -1, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number
    FROM {{ ref('m1_staker_withdrawals') }}

    UNION ALL

    SELECT staker, strategy, shares * -1, strategy_index, transaction_hash, log_index, block_time, block_date, block_number
    FROM {{ ref('m2_staker_withdrawals') }}

    UNION all

    -- eigenpod 中的份额是正数或负数,因此无需乘以 -1
    SELECT staker, '0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0' as strategy, shares, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number
    FROM {{ ref('eigenpod_shares') }}
) combined_staker_shares

注意:所有奖励都不会支付给那些尚未向执行层证明其信标链余额的 staker。

Operator 份额

operator $o$ 对于策略 $y$ 的份额由以下公式给出: $Shares{o,y} = ShareIncrease{o,y} - ShareDecrease_{o,y}$

SELECT
  operator,
  strategy,
  -- 对窗口中的每个份额金额求和,以在每次时间戳更新时获得每个 (operator, strategy) 的总份额 */
  SUM(shares) OVER (PARTITION BY operator, strategy ORDER BY block_time, log_index) AS shares,
  transaction_hash,
  log_index,
  block_time,
  block_date,
  block_number
FROM (
    SELECT operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number
    FROM {{ ref('operator_share_increases') }}

    UNION ALL

    SELECT operator, strategy, shares * -1 AS shares, transaction_hash, log_index, block_time, block_date, block_number
    FROM {{ ref('operator_share_decreases') }}
) combined_shares

Staker 委托状态

在这里,我们将每次委托和取消委托聚合到一个视图中。当 staker 被取消委托时,我们将其 operator 标记为 0x0000000000000000000000000000000000000000

SELECT
  staker,
  CASE when src = 'undelegations' THEN '0x0000000000000000000000000000000000000000' ELSE operator END AS operator,
  transaction_hash,
  log_index,
  block_time,
  block_date,
  block_number
FROM (
    SELECT *, 'undelegations' AS src FROM {{ ref('staker_undelegations') }}
    UNION ALL
    SELECT *, 'delegations' AS src FROM {{ ref('staker_delegations') }}
) as delegations_combined

组合的奖励提交

将 AVS 奖励提交和所有奖励提交组合到一个视图中,并添加了 reward_type 参数。

SELECT *, 'avs' as reward_type from {{ ref('avs_reward_submissions') }}

UNION ALL

SELECT *, 'all_stakers' as reward_type from {{ ref('reward_submission_for_all') }}

UNION ALL

SELECT *, 'all_earners' as reward_type from {{ ref('reward_submission_for_all_earners') }}

Operator AVS 状态

将 AVSDirectory 中的 状态元组 格式化为 truefalse

SELECT
    operator,
    avs,
    CASE WHEN status = 1 then true ELSE false END AS registered,
    transaction_hash,
    log_index,
    block_date,
    block_time,
    block_number
FROM {{ ref('operator_avs_registrations') }}

第 2 部分:窗口和快照

一旦我们转换了链上状态,我们就会将状态聚合到状态处于活动状态的一段时间内。最后,状态窗口被展开为每日快照。

我们已经在 上面的考虑因素 中解释过的关键设计决策是,状态总是向上舍入到最近的一天 0:00 UTC,除了 operator{'<>'}avs 取消注册

窗口

Staker 份额窗口

  1. Ranked_staker_records:对给定 staker、策略和日期的每个记录进行排名。排名越低,记录在窗口中的时间越晚
WITH ranked_staker_records as (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY staker, strategy, cast(block_time AS DATE) ORDER BY block_time DESC, log_index DESC) AS rn
    FROM {{ ref('staker_shares') }}
),
  1. Snapshotted_records:选择每天的最新记录。向上取整记录以创建 snapshot_time 每天的最新记录 & 向上取整到快照日
 snapshotted_records as (
     SELECT
         staker,
         strategy,
         shares,
         block_time,
         date_trunc('day', block_time) + INTERVAL '1' day AS snapshot_time
     from ranked_staker_records
     where rn = 1
 ),
  1. Staker_share_windows:获取每个 staker、策略、份额的范围
staker_share_windows as (
     SELECT
     staker, strategy, shares, snapshot_time as start_time,
     CASE
         -- 如果范围没有结束时间,则使用截断为 0 UTC 的当前时间戳
         WHEN LEAD(snapshot_time) OVER (PARTITION BY staker, strategy ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{ var("cutoff_date") }}')
         ELSE LEAD(snapshot_time) OVER (PARTITION BY staker, strategy ORDER BY snapshot_time)
         END AS end_time
     FROM snapshotted_records
 )
SELECT * from staker_share_windows

我们在 staker_share_windows CTE 中使用了 LEAD 运算符。此运算符查找给定(stakerstrategy)组合的下一个记录。逻辑是:

  • 如果没有下一个记录(即 null),则将窗口的 end_time 设置为 cutoff_date
  • 如果有记录,则将当前记录的窗口的 end_time 设置为下一个记录的 start_time

注意:上述逻辑可能具有 (staker, strategy) 组合,其中一个记录的 end_record 等于下一个记录的 start_time。当我们把窗口展开到快照中时,会对此进行处理。

Operator 份额窗口

WITH ranked_operator_records as (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY operator, strategy, cast(block_time AS DATE) ORDER BY block_time DESC, log_index DESC) AS rn
    FROM {{ ref('operator_shares') }}
),
-- 获取每天的最新记录 & 向上取整到快照日
snapshotted_records as (
 SELECT
     operator,
     strategy,
     shares,
     block_time,
     date_trunc('day', block_time) + INTERVAL '1' day as snapshot_time
 from ranked_operator_records
 where rn = 1
),
-- 获取每个 operator,strategy 配对的范围
operator_share_windows as (
 SELECT
     operator, strategy, shares, snapshot_time as start_time,
     CASE
         -- 如果范围没有结束时间,则使用截断为 0 UTC 的当前时间戳
         WHEN LEAD(snapshot_time) OVER (PARTITION BY operator, strategy ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{ var("cutoff_date") }}')
         ELSE LEAD(snapshot_time) OVER (PARTITION BY operator, strategy ORDER BY snapshot_time)
         END AS end_time
 FROM snapshotted_records
)
SELECT * from operator_share_windows

此逻辑与 Staker 份额窗口完全相同。

Staker 委托窗口

with ranked_delegations as (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY staker, cast(block_time AS DATE) ORDER BY block_time DESC, log_index DESC) AS rn
    FROM {{ ref('staker_delegation_status') }}
),
-- 获取每天的最新记录 & 向上取整到快照日
 snapshotted_records as (
     SELECT
         staker,
         operator,
         block_time,
         date_trunc('day', block_time) + INTERVAL '1' day AS snapshot_time
     from ranked_delegations
     where rn = 1
 ),
-- 获取每个 staker 的范围
 staker_delegation_windows as (
     SELECT
         staker, operator, snapshot_time as start_time,
         CASE
             -- 如果范围没有结束时间,则使用截断为 0 UTC 的截止日期
             WHEN LEAD(snapshot_time) OVER (PARTITION BY staker ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{ var("cutoff_date") }}')
             ELSE LEAD(snapshot_time) OVER (PARTITION BY staker ORDER BY snapshot_time)
             END AS end_time
     FROM snapshotted_records
 )
SELECT * from staker_delegation_windows

此逻辑与 Staker 份额窗口完全相同。

Operator AVS 注册窗口

此计算与上述 3 个查询不同,因为注册窗口不能互相连续。例如,如果 operator 具有以下状态:

Operator AVS 已注册 日期
Operator1 AVS1 2024-4-24
Operator1 AVS1 2024-4-26
Operator1 AVS1 2024-4-29
Operator1 AVS1 2024-5-1
Operator1 AVS1 2024-5-1
Operator1 AVS1 2024-5-1

我们不关心第 2 行和第 3 行的(取消注册,注册)窗口。我们还需要丢弃第 5 行和第 6 行的(注册,取消注册窗口)。

  1. 标记每次注册之间的链接
WITH marked_statuses AS (
    SELECT
        operator,
        avs,
        registered,
        block_time,
        block_date,
        -- 将下一个操作标记为 next_block_time
        LEAD(block_time) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS next_block_time,
        -- 下面的 lead/lag 组合仅在下一个 CTE 中使用
        -- 获取下一行的注册状态和 block_date
        LEAD(registered) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS next_registration_status,
        LEAD(block_date) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS next_block_date,
        -- 获取上一行的注册状态和 block_date
        LAG(registered) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS prev_registered,
        LAG(block_date) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS prev_block_date
    FROM {{ ref('operator_avs_status') }}
),
  1. 忽略同一天发生的(注册、取消注册)对。这是为了确保在我们将取消注册向下取整并将注册向上取整后,分组不被计算在内。
 removed_same_day_deregistrations AS (
     SELECT * from marked_statuses
     WHERE NOT (
         -- 删除注册部分
         (registered = TRUE AND
          COALESCE(next_registration_status = FALSE, false) AND -- 如果为空,则默认为 false
          COALESCE(block_date = next_block_date, false)) OR
             -- 删除取消注册部分
         (registered = FALSE AND
          COALESCE(prev_registered = TRUE, false) and
          COALESCE(block_date = prev_block_date, false)
             )
         )
 ),
  1. 将记录的 end_time 标记为下一个记录。如果不是这种情况,则将 end_time 标记为 cutoff_date
 registration_periods AS (
     SELECT
         operator,
         avs,
         block_time AS start_time,
         -- 将 next_block_time 标记为范围的 end_time
         -- 使用 coalesce,因为如果注册的 next_block_time 未关闭,那么我们使用 cutoff_date
         COALESCE(next_block_time, TIMESTAMP '{{ var("cutoff_date") }}') AS end_time,
         registered
     FROM removed_same_day_deregistrations
     WHERE registered = TRUE
 ),
  1. 向上取整每个 start_time 并向下取整每个 end_time
 registration_windows_extra as (
     SELECT
         operator,
         avs,
         date_trunc('day', start_time) + interval '1' day as start_time,
         -- 结束时间是排他的结束时间,因为 operator 未在结束时间在 AVS 上注册,或者它是向上取整的当前时间戳
         date_trunc('day', end_time) as end_time
     FROM registration_periods
 ),
  1. 删除具有相同 start_timeend_time 的记录:
 operator_avs_registration_windows as (
     SELECT * from registration_windows_extra
     WHERE start_time != end_time
 )
select * from operator_avs_registration_windows

Operator AVS 策略窗口

此查询将来自 operator 重新抵押策略 cron 作业的条目聚合到窗口中。

  1. 对所有记录进行排序。将 block_time 向上取整为 0 UTC。Log_index 无关紧要,因为此数据是从 RPC 调用生成的。
with ranked_records AS (
    SELECT
        lower(operator) as operator,
        lower(avs) as avs,
        lower(strategy) as strategy,
        block_time,
        date_trunc('day', CAST(block_time as timestamp(6))) + interval '1' day as start_time,
        ROW_NUMBER() OVER (
            PARTITION BY operator, avs, strategy, date_trunc('day', CAST(block_time as timestamp(6))) + interval '1' day
            ORDER BY block_time DESC -- 希望最新的记录排名最高
            ) AS rn
    -- 无法在此处使用 ref,因为此表不是通过 DBT 生成的
    FROM public.operator_restaked_strategies
    -- testnet 和 holesky 都一起存在于 blocklake 中,因此 avs_directory_address 允许我们过滤
    WHERE avs_directory_address = lower('{{ var('avs_directory_address') }}')
),
  1. 获取每个 (operator, avs, strategy, day) 组合的最新记录
 latest_records AS (
     SELECT
         operator,
         avs,
         strategy,
         start_time,
         block_time
     FROM ranked_records
     WHERE rn = 1
 ),
  1. 查找每个 (operator, avs, strategy) 分组的下一个条目。
 grouped_records AS (
     SELECT
         operator,
         avs,
         strategy,
         start_time,
         LEAD(start_time) OVER (
             PARTITION BY operator, avs, strategy
             ORDER BY start_time ASC
             ) AS next_start_time
     FROM latest_records
 ),
  1. 解析出任何漏洞(即,任何 next_start_times 不是正好比当前记录的 start_time 晚一天)。这是因为 operator 将从 AVS 取消注册。
 parsed_ranges AS (
     SELECT
         operator,
         avs,
         strategy,
         start_time,
         -- 如果 next_start_time 不在第二天,则关闭 end_time
         CASE
             WHEN next_start_time IS NULL OR next_start_time > start_time + INTERVAL '1' DAY THEN start_time
             ELSE next_start_time
             END AS end_time
     FROM grouped_records
 ),
  1. 删除任何 start_time == end_time 的记录
 active_windows as (
     SELECT *
     FROM parsed_ranges
     WHERE start_time != end_time
 ),
  1. 我们现在使用 gaps and islands 算法来查找 (operator, avs, strategy) 组合的连续组。首先,我们标记每行的 prev_end_time。如果有新窗口,则 gap 为空
 gaps_and_islands AS (
     SELECT
         operator,
         avs,
         strategy,
         start_time,
         end_time,
         LAG(end_time) OVER(PARTITION BY operator, avs, strategy ORDER BY start_time) as prev_end_time
     FROM active_windows
 ),
  1. 接下来,我们检测 islands。如果 prev_end_timestart_time 相同,则记录是同一连续分组的一部分。
 island_detection AS (
     SELECT operator, avs, strategy, start_time, end_time, prev_end_time,
            CASE
                -- 如果之前的结束时间等于开始时间,则标记为 island 的一部分,否则创建新的 island
                WHEN prev_end_time = start_time THEN 0
                ELSE 1
                END as new_island
     FROM gaps_and_islands
 ),
  1. 根据每个记录的 new_island 创建组。具有相同 island_id 总和的行是同一分组的一部分
 island_groups AS (
     SELECT
         operator,
         avs,
         strategy,
         start_time,
         end_time,
         SUM(new_island) OVER (
             PARTITION BY operator, avs, strategy ORDER BY start_time
             ) AS island_id
     FROM island_detection
 ),
  1. 将组合并在一起
 operator_avs_strategy_windows AS (
     SELECT
         operator,
         avs,
         strategy,
         MIN(start_time) AS start_time,
         MAX(end_time) AS end_time
     FROM island_groups
     GROUP BY operator, avs, strategy, island_id
     ORDER BY operator, avs, strategy, start_time
 )
select * from operator_avs_strategy_windows

Operator {'<>'} Operator 集注册窗口

在此计算中,注册窗口不能互相连续。例如,如果 operator 具有以下状态:

Operator Operator 集 已注册 日期
Operator1 Operator 集 1 True 2024-4-24
Operator1 Operator 集 1 2024-4-26
Operator1 Operator 集 1 True 2024-4-29
Operator1 Operator 集 1 2024-5-1
Operator1 Operator 集 1 True 2024-5-1
Operator1 Operator 集 1 2024-5-1

我们不关心第 2 行和第 3 行的(取消注册,注册)窗口。我们还需要丢弃第 5 行和第 6 行的(注册,取消注册窗口)。

此外,operator 取消注册向下舍入,注册向上舍入。

  1. 标记每次注册之间的链接
marked_statuses AS (
    SELECT
        operator,
        avs,
            operator_set_id,
        is_active,
        block_time,
        block_date,
        -- 将下一个操作标记为 next_block_time
        LEAD(block_time) OVER (PARTITION BY operator, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS next_block_time,
        -- 下面的 lead/lag 组合仅在下一个 CTE 中使用
        -- 获取下一行的注册状态和 block_date
        LEAD(is_active) OVER (PARTITION BY operator, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS next_is_active,
        LEAD(block_date) OVER (PARTITION BY operator, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS next_block_date,
        -- 获取上一行的注册状态和 block_date
        LAG(is_active) OVER (PARTITION BY operator, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS prev_is_active,
        LAG(block_date) OVER (PARTITION BY operator, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS prev_block_date
    FROM state_changes
),
  1. 忽略同一天发生的(注册、取消注册)对。这是为了确保在我们将取消注册向下取整并将注册向上取整后,分组不被计算在内。
 removed_same_day_deregistrations AS (
     SELECT * from marked_statuses
     WHERE NOT (
         -- 删除注册部分
         (is_active = TRUE AND
          COALESCE(next_is_active = FALSE, false) AND -- 如果为空,则默认为 false
          COALESCE(block_date = next_block_date, false)) OR
             -- 删除取消注册部分
         (is_active = FALSE AND
          COALESCE(prev_is_active = TRUE, false) and
          COALESCE(block_date = prev_block_date, false)
             )
         )
 ),
  1. 将记录的 end_time 标记为下一个记录。如果不是这种情况,则将 end_time 标记为 cutoff_date
registration_periods AS (
    SELECT
        operator,
        avs,
        operator_set_id,
        block_time AS start_time,
        -- 将 next_block_time 标记为范围的 end_time
        -- 使用 coalesce,因为如果注册的 next_block_time 未关闭,那么我们使用 cutoff_date
        COALESCE(next_block_time, '{{.cutoffDate}}')::timestamp AS end_time,
        is_active
    FROM removed_same_day_deregistrations
    WHERE is_active = TRUE
 ),
  1. 向上取整每个 start_time 并向下取整每个 end_time
registration_windows_extra as (
    SELECT
        operator,
        avs,
        operator_set_id,
        date_trunc('day', start_time) + interval '1' day as start_time,
        -- 结束时间是排他的结束时间,因为 strategy 未在 operator 集的结束时间注册,或者它是向上取整的当前时间戳
        date_trunc('day', end_time) as end_time
    FROM registration_periods
),
  1. 删除具有相同 start_timeend_time 的记录:
operator_set_operator_registration_windows as (
     SELECT * from registration_windows_extra
     WHERE start_time != end_time
),

策略{'<>'} Operator 集注册窗口

在此计算中,注册窗口不能互相连续。例如,如果 strategy 具有以下状态:

Strategy Operator 集 已注册 日期
Strategy1 Operator 集 1 True 2024-4-24
Strategy1 Operator 集 1 2024-4-26
Strategy1 Operator 集 1 True 2024-4-29
Strategy1 Operator 集 1 2024-5-1
Strategy1 Operator 集 1 True 2024-5-1
Strategy1 Operator 集 1 2024-5-1

我们不关心第 2 行和第 3 行的(取消注册,注册)窗口。我们还需要丢弃第 5 行和第 6 行的(注册,取消注册窗口)。

此外,strategy 注册和取消注册都向上取整。

  1. 标记每次注册之间的链接
marked_statuses AS (
    SELECT
        strategy,
        avs,
            operator_set_id,
        is_active,
        block_time,
        block_date,
        -- 将下一个操作标记为 next_block_time
        LEAD(block_time) OVER (PARTITION BY strategy, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS next_block_time,
        -- 下面的 lead/lag 组合仅在下一个 CTE 中使用
        -- 获取下一行的注册状态和 block_date
        LEAD(is_active) OVER (PARTITION BY strategy, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS next_is_active,
        LEAD(block_date) OVER (PARTITION BY strategy, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS next_block_date,
        -- 获取上一行的注册状态和 block_date
        LAG(is_active) OVER (PARTITION BY strategy, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS prev_is_active,
        LAG(block_date) OVER (PARTITION BY strategy, avs, operator_set_id ORDER BY block_time ASC, log_index ASC) AS prev_block_date
    FROM state_changes
),
  1. 忽略同一天发生的(注册、取消注册)对。这是为了确保在我们将取消注册向下取整并将注册向上取整后,分组不被计算在内。
 removed_same_day_deregistrations AS (
     SELECT * from marked_statuses
     WHERE NOT (
         -- 删除注册部分
         (is_active = TRUE AND
          COALESCE(next_is_active = FALSE, false) AND -- 如果为空,则默认为 false
          COALESCE(block_date = next_block_date, false)) OR
             -- 删除取消注册部分
         (is_active = FALSE AND
          COALESCE(prev_is_active = TRUE, false) and
          COALESCE(block_date = prev_block_date, false)
             )
         )
 ),
  1. 将记录的 end_time 标记为下一个记录。如果不是这种情况,则将 end_time 标记为 cutoff_date
registration_periods AS (
    SELECT
        strategy,
        avs,
        operator_set_id,
        block_time AS start_time,
        -- 将 next_block_time 标记为范围的 end_time
        -- 使用 coalesce,因为如果注册的 next_block_time 未关闭,那么我们使用 cutoff_date
        COALESCE(next_block_time, '{{.cutoffDate}}')::timestamp AS end_time,
        is_active
    FROM removed_same_day_deregistrations
    WHERE is_active = TRUE
 ),
  1. 向上取整每个 start_timeend_time
registration_windows_extra as (
    SELECT
        strategy,
        avs,
        operator_set_id,
        date_trunc('day', start_time) + interval '1' day as start_time,
        -- 结束时间向上舍入以包括注册的完整最后一天
        date_trunc('day', end_time) + interval '1' day as end_time
    FROM registration_periods
),
  1. 删除具有相同 start_timeend_time 的记录:
operator_set_strategy_registration_windows as (
     SELECT * from registration_windows_extra
     WHERE start_time != end_time
),

快照

一旦我们为核心合同状态的每个表创建了窗口,我们将这些窗口展开为每日快照。在下面的每个查询中,我们将 end_time 向下舍入一天,因为新记录可以在同一天开始,或者它将被包含在 cutoff_date 之后的单独流水线运行中。

Staker 份额快照

WITH cleaned_records as (
  SELECT * FROM {{ ref('staker_share_windows')}}
  WHERE start_time &lt; end_time
)
SELECT
    staker,
    strategy,
    shares,
    cast(day AS DATE) AS snapshot
FROM
    cleaned_records
CROSS JOIN
    generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day

我们删除任何具有错误 start_timeend_time 值的记录。然后,我们展开整个范围。

Operator 份额快照

WITH cleaned_records as (
    SELECT * FROM {{ ref('operator_share_windows')}}
        WHERE start_time &lt; end_time
)
SELECT
    operator,
    strategy,
    shares,
    cast(day AS DATE) AS snapshot
FROM
    cleaned_records
        CROSS JOIN
    generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day

Staker 委托快照

WITH cleaned_records as (
    SELECT * FROM {{ ref('staker_delegation_windows') }}
        WHERE start_time &lt; end_time
)
SELECT
    staker,
    operator,
    cast(day AS DATE) AS snapshot
FROM
    cleaned_records
        CROSS JOIN
    generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day

Operator AVS Strategy Snapshots

WITH cleaned_records AS (
    SELECT * FROM {{ ref('operator_avs_strategy_windows') }}
        WHERE start_time &lt; end_time
)
SELECT
    operator,
    avs,
    strategy,
    cast(day AS DATE) AS snapshot
FROM
    cleaned_records
        CROSS JOIN
    generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day

Operator AVS Registration Snapshots

WITH cleaned_records AS (
    SELECT * FROM {{ ref('operator_avs_registration_windows') }}
        WHERE start_time &lt; end_time
)
SELECT
    operator,
    avs,
    day AS snapshot
FROM cleaned_records
CROSS JOIN generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day

Operator Set Operator Registration Snapshots

WITH cleaned_records AS (
    SELECT * FROM operator_set_operator_registration_windows
    WHERE start_time &lt; end_time
)
SELECT
    operator,
    avs,
    operator_set_id,
    d AS snapshot
FROM cleaned_records
CROSS JOIN generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS d

Operator Set Strategy Registration Snapshots

WITH cleaned_records AS (
    SELECT * FROM operator_set_strategy_registration_windows
    WHERE start_time &lt; end_time
)
SELECT
    strategy,
    avs,
    operator_set_id,
    d AS snapshot
FROM cleaned_records
CROSS JOIN generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS d

Reward Calculation

Key Considerations

Calculation Ranges

奖励分配是从每日状态快照计算得出的。例如,如果我们有一个以下范围的奖励提交:

Day0------Day1------Day2------Day3------Day4------Day5------Day6------Day7

奖励管道将从 7 个状态快照计算奖励分配:Day1、Day2、...、Day7。我们包括最后一天(Day7)作为一个快照,而不是第一天(Day0)。你可以将每个快照视为代表过去 24 小时内最新的状态更新。换句话说,$Dayi$ 表示来自 [$Day{i-1}$ 00:00 UTC, $Day_{i-1}$ 23:59 UTC] 的最新状态。

State Entry/Exit

由于快照四舍五入向上到最近的一天,除了 operator{'<>'}avs 取消注册和 operator{'<>'}operator set 取消注册外,同一天内的状态更新将收到完全相同的奖励金额。

例如,假设 stakers A 和 B 拥有等量的 stake,并且在以下时间选择加入同一个 operator,他们在 Day 2 rewardSnaphot 的奖励将是相等的:

Day1---------------------Day2
      ^               ^
      A               B

这是从每日快照计算奖励的一个已知副作用。

此外,退出系统也具有相同的属性。如果 Operators J 和 K 在同一天内从同一个 AVS 退出,他们都不会收到来自该 AVS 的奖励:

Day1---------------------Day2
      ^               ^
      J               K

如上文转换部分所述,一个已知的副作用是,operator可以通过在 2 个快照的边界处从 AVS 取消注册来失去一天的奖励。例如:

--------Day1---------------------Day2---------------------Day3
      ^                                                 ^
     Entry                                             Exit

在上述情况下,即使 operator 仅验证了 AVS 近 2 天,该 operator 也会被计为在 Day1 注册,并在 Day2 取消注册。

Multiplier Calculation

每次奖励提交都有两个长度相等的数组:strategiesmultiplier。管道使用此值来计算快照奖励的参与者的 stakeWeight。对于给定的 staker $s$,在快照 $d$ 上,stakeWeight 由下式给出:

$stakeWeight_{s, d} = multiplieri \cdot shares{i,s,d}$

该计算也在 AVS 的 StakeRegistry 合约中完成。参考 solidity 实现

Token Reward Amounts

一个关键的不变量是,对于给定的奖励提交 $r$,在奖励快照 $d$ 上,$Tokens{r,d} >= \sum{i=0}^{n=paidEarners} Earner_{i,r,d}$

换句话说,奖励提交的 tokensPerDay 不能小于分配给 rewardSnaphot 的所有参与者的奖励总和。我们将此作为从转换 shares 和 multipliers 为 double 类型时的截断的一个关键考虑因素,double 类型最多可容纳 15 个有效数字。

Reward Aggregation

RewardsCoordinator 要求 CALCULATION_INTERVAL_SECONDS % SNAPSHOT_CADENCE == 0,这保证了每个奖励快照都将位于奖励范围的边界内。

按照奖励更新者定义的某个频率,管道会将所有奖励分配快照聚合到某个时间戳 $t$。对于要成为“全新”的根,它必须对大于 lastRewardTimestamprewardSnaphot 之后的状态进行 merkleize。

Lack of reward rollover

如果 AVS 为没有重新stake策略的快照进行了奖励,则该奖励将不会重新分配到奖励提交的未来快照。 请参阅 奖励快照 operators 一个具体的例子。

1. Get Active Rewards

以下每个查询都是 active_rewards 视图的一部分 CTE 集。

为了处理追溯奖励,我们寻找在 cutoff_date 之前开始的任何奖励。为了确保不重新计算奖励,我们限制计算活动奖励的快照范围。

下表将用于帮助可视化转换。 让我们假设管道的 cutoff_date 是 2024 年 4 月 29 日 0:00 UTC。 此外,假设上次奖励快照是 2024 年 4 月 24 日。注意:由于青铜表查询和截止日期,奖励事件需要 2 天才能显示在当前快照的黄金计算中。

AVS Reward Hash Start Timestamp Duration End Timestamp Amount Strategy Multiplier
AVS1 0xReward1 4-21-2024 21 days 5-12-2024 21e18 stETH 1e18
AVS1 0xReward1 4-21-2024 21 days 5-12-2024 21e18 rETH 2e18

为简洁起见,每个示例中仅显示表的相应行。

Active Rewards

WITH active_rewards_modified as (
    SELECT *,
           amount/(duration/86400) as tokens_per_day,
           cast('{{ var("cutoff_date") }}' AS TIMESTAMP(6)) as global_end_inclusive -- Inclusive means we DO USE this day as a snapshot
    FROM {{ ref('rewards_combined') }}
        WHERE end_timestamp >= TIMESTAMP '{{ var("rewards_start") }}' and start_timestamp &lt;= TIMESTAMP '{{ var("cutoff_date") }}'
),
  • 我们将金额除以奖励提交持续时间的天数,以获得 tokens_per_day。 这是在给定的 rewardSnapshot 中分配给所有参与者的 token 数量
  • 在给定的运行中可能存在多个 rewardsSnapshot,因为对于每次奖励提交,我们可以在管道运行中计算多个快照的奖励
  • global_end_inclusive 是此管道运行的最后一个快照,用于计算奖励。 它与 cutoff_date 相同
  • end_timestamp 大于 UNIX 时间的开始时间,但在后续步骤中会得到适当处理
Tokens Per Day Global End Inclusive
1e18 4-27-2024

Active Rewards Updated End Timestamps

 active_rewards_updated_end_timestamps as (
     SELECT
         avs,
         /**
          * Cut the start and end windows to handle
          * A. Retroactive rewards that came recently whose start date is less than start_timestamp
          * B. Don't make any rewards past end_timestamp for this run
          */
         start_timestamp as reward_start_exclusive,
         LEAST(global_end_inclusive, end_timestamp) as reward_end_inclusive,
         tokens_per_day,
         token,
         multiplier,
         strategy,
         reward_type,
         reward_for_all,
         global_end_inclusive,
         block_date as reward_submission_date
     FROM active_rewards_modified
),
  • 奖励提交的原始 start_timestamp 被重命名为 reward_start_exclusive。 它被标记为 exclusive,因为我们已经在之前的运行中在这个时间点拍摄了快照,或者它是奖励提交的第 0 天,并且不会拍摄任何快照(请参阅 计算范围
  • reward_end_inclusiveMIN(global_end_inclsuive, end_timestamp)。 这会将 end_timestamp 限制为不大于给定运行的 cutoff_time
Reward Start Exclusive Reward End Inclusive
4-21-2024 4-27-2024

Active Rewards Updated Start Timestamps

-- For each reward hash, find the latest snapshot
 active_rewards_updated_start_timestamps as (
     SELECT
         ap.avs,
         CASE
             WHEN '{{ var("is_backfill") }}' = 'true' THEN ap.reward_start_exclusive
             ELSE COALESCE(MAX(g.snapshot), ap.reward_start_exclusive)
             END as reward_start_exclusive,
         ap.reward_end_inclusive,
         ap.token,
         ap.tokens_per_day as tokens_per_day_decimal,
         -- Round down to 15 sigfigs for double precision, ensuring know errouneous round up or down
         ap.tokens_per_day * ((POW(10, 15) - 1)/(POW(10, 15))) as tokens_per_day,
         ap.multiplier,
         ap.strategy,
         ap.reward_hash,
         ap.reward_type,
         ap.global_end_inclusive,
         ap.reward_submission_date
     FROM active_rewards_updated_end_timestamps ap
              LEFT JOIN {{ var('schema_name') }}.gold_table g
                        ON g.reward_hash = ap.reward_hash
     GROUP BY ap.avs, ap.reward_end_inclusive, ap.token, ap.tokens_per_day, ap.multiplier, ap.strategy, ap.reward_hash, ap.global_end_inclusive, ap.reward_start_exclusive, ap.reward_for_all
 ),

根据来自最终黄金表的 reward_hash 的最新 snapshot 钳制 reward_start_exclusive,该表包含每个 snapshotReward。 我们这样做是为了不重新计算奖励。 如果黄金表中没有快照,那么我们只使用奖励提交的原始 reward_start_exclusive

此步骤还将 tokens_per_day 转换为 double,这将在其余的奖励计算中使用。 这使我们能够支持高达 1e38-1 的值,但代价是只有 15 个小数位的精度。

最后,如果运行是回填,我们将开始时间戳设置为最早的可能的 unix 时间戳 1970-01-01 00:00:00

让我们假设 reward_hash 的最新 snapshotReward 是 4-24-2024。reward_start_exclusive 的先前值是 4-21-2024。

Reward start exclusive Reward end inclusive
4-24-2024 4-28-2024

Active Reward Ranges

 active_reward_ranges AS (
     SELECT * from active_rewards_updated_start_timestamps
     /** Take out (reward_start_exclusive, reward_end_inclusive) windows where
      * 1. reward_start_exclusive >= reward_end_inclusive: The reward period is done or we will handle on a subsequent run
     */
     WHERE reward_start_exclusive &lt; reward_end_inclusive
 ),

解析出无效范围。 如果当抢跑是回填,并且先抢跑的快照大于我们正在回填的 cutoff_time,则可能会发生这种情况。

Unwinded Active Reward Ranges

 exploded_active_range_rewards AS (
     SELECT * FROM active_reward_ranges
     CROSS JOIN generate_series(DATE(reward_start_exclusive), DATE(reward_end_inclusive), INTERVAL '1' DAY) AS day
 ),

为奖励范围内的每个快照创建一行

AVS Reward Hash Day Strategy Multiplier ...
AVS1 0xReward1 4-24-2024 stETH 1e18
AVS1 0xReward1 4-25-2024 stETH 1e18
AVS1 0xReward1 4-26-2024 stETH 1e18
AVS1 0xReward1 4-27-2024 stETH 1e18
AVS1 0xReward1 4-24-2024 rETH 2e18
AVS1 0xReward1 4-25-2024 rETH 2e18
AVS1 0xReward1 4-26-2024 rETH 2e18
AVS1 0xReward1 4-27-2024 rETH 2e18

Final Active Reward

 active_rewards_final AS (
     SELECT
         avs,
         cast(day as DATE) as snapshot,
         token,
         tokens_per_day,
         tokens_per_day_decimal,
         multiplier,
         strategy,
         reward_hash,
         reward_type,
         reward_submission_date
     FROM exploded_active_range_rewards
     -- Remove snapshots on the start day
     WHERE day != reward_start_exclusive
 )
select * from active_rewards_final

删除快照等于奖励的 reward_start_exclusive 的行。

AVS Reward Hash Snapshot Strategy Multiplier Reward Start Exclusive
<s>AVS1</s> <s>0xReward1</s> <s>4-24-2024</s> <s>stETH</s> <s>1e18</s> <s>4-24-2024</s>
AVS1 0xReward1 4-25-2024 stETH 1e18 4-24-2024
AVS1 0xReward1 4-26-2024 stETH 1e18 4-24-2024
AVS1 0xReward1 4-27-2024 stETH 1e18 4-24-2024
<s>AVS1</s> <s>0xReward1</s> <s>4-24-2024</s> <s>rETH</s> <s>2e18</s> <s>4-24-2024</s>
AVS1 0xReward1 4-25-2024 rETH 2e18 4-24-2024
AVS1 0xReward1 4-26-2024 rETH 2e18 4-24-2024
AVS1 0xReward1 4-27-2024 rETH 2e18 4-24-2024

2. Staker Reward Amounts

生成有效奖励后,管道随后计算奖励分配给 staker operator set。

我们将奖励提交的 multiplier 和 staker 的 shares 转换为 double,以便对这些值进行计算。 double 类型有 15 个有效数字,这种不精确性反映在计算 staker 的 stakeWeightstaker_proportiontotal_staker_operator_rewardstaker_tokens 的 CTE 中。必须是这种情况,对于给定的奖励提交 $r$,$\sum{i=0}^{n=avsStakerOperatorSet} stakeroperatorSetReward{i, r} {'<='} tokensPerDay_r$。

我们首先计算奖励分配给整个 staker operator set,然后将其传递给 operators 和 stakers。

Reward Snapshot Operators

WITH reward_snapshot_operators as (
  SELECT
    ap.reward_hash,
    ap.snapshot,
    ap.token,
    ap.tokens_per_day,
    ap.tokens_per_day_decimal,
    ap.avs,
    ap.strategy,
    ap.multiplier,
    ap.reward_type,
    ap.reward_submission_date,
    oar.operator
  FROM {{ ref('1_active_rewards') }} ap
  JOIN {{ ref('operator_avs_registration_snapshots') }} oar
  ON ap.avs = oar.avs and ap.snapshot = oar.snapshot
  WHERE ap.reward_type = 'avs'
),

active_rewards 中,获取为 AVS 注册的 operators。我们过滤 reward_for_all = false,因为我们只关注 AVS 奖励提交。

让我们假设 AVS 的 operator 注册快照是。 4-27-2024 是活动快照,但青铜表将没有来自此日期的事件(因为查询是针对所有事件 < cutoffDate)。在此运行中,4-27-2024 不会支付给任何 staker 或 operator。这就是两个快照延迟的来源。

对于没有 operators 选择加入 AVS 的日子,1e18 的 tokens_per_day 不会重新分配到未来的 rewardSnapshots

Operator AVS Snapshot
Operator1 AVS1 4-25-2024
Operator1 AVS1 4-26-2024
Operator2 AVS1 4-25-2024
Operator2 AVS1 4-26-2024

Operator Restaked Strategies

_operator_restaked_strategies AS (
  SELECT
    rso.*
  FROM reward_snapshot_operators rso
  JOIN {{ ref('operator_avs_strategy_snapshots') }} oas
  ON
    rso.operator = oas.operator AND
    rso.avs = oas.avs AND
    rso.strategy = oas.strategy AND
    rso.snapshot = oas.snapshot
),

从在 AVS 上重新stake的 operators 中,获取他们已在上面重新stake的每个 AVS 的策略和 shares 和 shares。

让我们假设 AVS 上每个 operator 的策略是:

Operator AVS Strategy Snapshot
Operator1 AVS1 stETH 4-25-2024
Operator1 AVS1 stETH 4-26-2024
Operator1 AVS1 rETH 4-26-2024
Operator2 AVS1 stETH 4-25-2024
Operator2 AVS1 stETH 4-26-2024

Staker Delegated Operators

staker_delegated_operators AS (
  SELECT
    ors.*,
    sds.staker
  FROM _operator_restaked_strategies ors
  JOIN {{ ref('staker_delegation_snapshots') }} sds
  ON
    ors.operator = sds.operator AND
    ors.snapshot = sds.snapshot
),

获取为快照委托给 operator 的 stakers。

Operator AVS Strategy Snapshot Staker
Operator1 AVS1 stETH 4-25-2024 Staker1
Operator1 AVS1 stETH 4-26-2024 Staker1
Operator1 AVS1 stETH 4-26-2024 Staker2
Operator1 AVS1 rETH 4-26-2024 Staker1
Operator1 AVS1 rETH 4-26-2024 Staker2
Operator2 AVS1 stETH 4-25-2024 Staker3
Operator2 AVS1 stETH 4-26-2024 Staker3

Staker Strategy Shares

staker_avs_strategy_shares AS (
  SELECT
    sdo.*,
    sss.shares
  FROM staker_delegated_operators sdo
  JOIN {{ ref('staker_share_snapshots') }} sss
  ON
    sdo.staker = sss.staker AND
    sdo.snapshot = sss.snapshot AND
    sdo.strategy = sss.strategy
  -- Parse out negative shares and zero multiplier so there is no division by zero case
  WHERE sss.shares > 0 and sdo.multiplier != 0
),

让我们假设 stakers 具有以下状态:

Staker Strategy Shares Snapshot
Staker1 stETH 1e18 4-25-2024
Staker1 stETH 1e18 4-26-2024
Staker1 rETH 2e18 4-26-2024
Staker2 stETH 1e18 4-26-2024
Staker3 stETH 2e18 4-25-2024
Staker3 stETH 2e18 4-26-2024

连接将产生以下结果:

Operator AVS Strategy Snapshot Staker Shares
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18
Operator1 AVS1 rETH 4-26-2024 Staker1 2e18
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18

Staker2 没有 rETH shares,这就是为什么此视图的行数少 1 行。

Staker Weights

staker_weights AS (
  SELECT *,
    SUM(multiplier * shares) OVER (PARTITION BY staker, reward_hash, snapshot) AS staker_weight
  FROM staker_avs_strategy_shares
),

计算 staker 的 stakeWeight。有关此计算的讨论请参见上文

Operator AVS Strategy Snapshot Staker Shares Multiplier Staker Weight
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18 1e18 1e36
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18 1e18 3e36
Operator1 AVS1 rETH 4-26-2024 Staker1 1e18 2e18 3e36
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18 1e18 1e36
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18 1e18 2e36
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18 1e18 2e36

Distinct Stakers

distinct_stakers AS (
  SELECT *
  FROM (
      SELECT *,
        -- We can use an arbitrary order here since the staker_weight is the same for each (staker, strategy, hash, snapshot)
        -- We use strategy ASC for better debuggability
        ROW_NUMBER() OVER (PARTITION BY reward_hash, snapshot, staker ORDER BY strategy ASC) as rn
      FROM staker_weights
  ) t
  WHERE rn = 1
  ORDER BY reward_hash, snapshot, staker
),

在之前的计算中,stake weight 是按 (reward_hash, staker, snapshot) 计算的。 我们需要删除任何具有相同组合的行,因为下一步是计算每个快照的总 staker weight。 strategy 列是不相关的,因为最终奖励由 $tokensPerDay * stakerWeightProportion$ 给出。 我们添加 order by 子句,以便下一步中 staker weight 的总和是确定性的。

删除具有相同 (staker, reward_hash, snapshot) 的行

Operator AVS Strategy Snapshot Staker Shares Multiplier Staker Weight
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18 1e18 1e36
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18 1e18 3e36
<s> Operator1 </s> <s> AVS1 </s> <s> rETH </s> <s> 4-26-2024 </s> <s> Staker1 </s> <s> 1e18 </s> <s> 2e18 </s> <s> 3e36 </s>
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18 1e18 1e36
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18 1e18 2e36
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18 1e18 2e36

Staker Weight Sum

staker_weight_sum AS (
  SELECT *,
    SUM(staker_weight) OVER (PARTITION BY reward_hash, snapshot) as total_weight
  FROM distinct_stakers
),

获取给定 (reward_hash, snapshot) 的所有 staker weight 的总和

Operator AVS Strategy Snapshot Staker Shares Staker Weight Total Staker Weight
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18 1e36 3e36
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18 3e36 6e36
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18 1e36 6e36
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18 2e36 3e36
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18 2e36 6e36

Staker Proportion

staker_proportion AS (
  SELECT *,
    FLOOR((staker_weight / total_weight) * 1000000000000000) / 1000000000000000 AS staker_proportion
  FROM staker_weight_sum
),
计算 snapshotReward 的 token 的 staker 比例我们向下舍入 staker_proportion 以确保 staker_proportions 的总和不大于 1 运算符 (Operator) AVS 策略 (Strategy) 快照 (Snapshot) 质押者 (Staker) 份额 (Shares) 质押者权重 (Staker Weight) 总质押者权重 (Total Staker Weight) 质押者比例 (Staker Proportion)
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18 1e36 3e36 0.333
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18 3e36 6e36 0.5
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18 1e36 6e36 0.166
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18 2e36 3e36 0.666
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18 2e36 6e36 0.333

总代币 (Total Tokens)

staker_operator_total_tokens AS (
  SELECT *,
    CASE
      -- 对于在硬分叉之前的快照,并且在硬分叉之前提交,我们使用旧的计算方法
      WHEN snapshot &lt; '{{ var("amazon_hard_fork") }}' AND reward_submission_date &lt; '{{ var("amazon_hard_fork") }}' THEN
        cast(staker_proportion * tokens_per_day AS DECIMAL(38,0))
      WHEN snapshot &lt; '{{ var("nile_hard_fork") }}' AND reward_submission_date &lt; '{{ var("nile_hard_fork") }}' THEN
        (staker_proportion * tokens_per_day)::text::decimal(38,0)
      ELSE
        FLOOR(staker_proportion * tokens_per_day_decimal)
    END as total_staker_operator_payout
  FROM staker_proportion
),

我们已经进行了两次硬分叉:

  1. 向下取整到文本 (Round down to text)
  2. 在所有地方使用 decimal 而不是 double。(Use decimal instead of double everywhere.)

这段代码反映了处理这个边界情况,以使回填情况下的计算具有幂等性。(This code reflects handling this edge case to make calculations idempotent in the cases of a backfill.)

计算将支付给所有质押者和运营者的代币数量。我们将结果从文本转换为 DECIMAL(38,0) 以不损失任何精度。此外,对于给定的快照,代币值适合此类型。(Calculate the number of tokens that will be paid out to all stakers and operators. We cast the result from text to DECIMAL(38,0) to not lose any precision. Furthemore, the token value fits within this type for a given snapshot.)

运算符 (Operator) AVS 策略 (Strategy) 快照 (Snapshot) 质押者 (Staker) 份额 (Shares) 质押者权重 (Staker Weight) 总质押者权重 (Total Staker Weight) 质押者比例 (Staker Proportion) 每日代币 (Tokens Per Day) 总质押者运算符奖励 (Total Staker Operator Reward)
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18 1e36 3e36 0.333 1e18 333333333333333000
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18 3e36 6e36 0.5 1e18 5e17
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18 1e36 6e36 0.166 1e18 166666666666666000
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18 2e36 3e36 0.666 1e18 666666666666666000
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18 2e36 6e36 0.333 1e18 333333333333333000

代币分解 (Token Breakdowns)

token_breakdowns AS (
  SELECT *,
    CASE
      WHEN snapshot &lt; '{{ var("amazon_hard_fork") }}' AND reward_submission_date &lt; '{{ var("amazon_hard_fork") }}' THEN
        cast(total_staker_operator_payout * 0.10 AS DECIMAL(38,0))
      WHEN snapshot &lt; DATE '{{ var("nile_hard_fork") }}' AND reward_submission_date &lt; '{{ var("nile_hard_fork") }}' THEN
        (total_staker_operator_payout * 0.10)::text::decimal(38,0)
      ELSE
        floor(total_staker_operator_payout * 0.10)
    END as operator_tokens,
    CASE
      WHEN snapshot &lt; '{{ var("amazon_hard_fork") }}' AND reward_submission_date &lt; '{{ var("amazon_hard_fork") }}' THEN
        total_staker_operator_payout - cast(total_staker_operator_payout * 0.10 as DECIMAL(38,0))
      WHEN snapshot &lt; '{{ var("nile_hard_fork") }}' AND reward_submission_date &lt; '{{ var("nile_hard_fork") }}' THEN
        total_staker_operator_payout - ((total_staker_operator_payout * 0.10)::text::decimal(38,0))
      ELSE
        total_staker_operator_payout - floor(total_staker_operator_payout * 0.10)
    END as staker_tokens
  FROM staker_operator_total_tokens
)
SELECT * from token_breakdowns
ORDER BY reward_hash, snapshot, staker, operator

计算欠运营商和质押者的代币数量。运营商获得固定的 10% 佣金。从文本转换为 DECIMAL(38,0) 将仅截断数字的小数部分(即,不会有四舍五入)。我们添加 order by 子句是为了使下一个查询中质押者权重的总和是确定性的。(Calculate the number of tokens owed to the operator and to the staker. Operators get a fixed 10% commission. Casting from text to DECIMAL(38,0) will only truncate the decimal proportion of the number (ie. there will be no rounding). We add the order by clause in order to have the sum of staker weight in the next query be deterministic.)

运算符 (Operator) AVS 策略 (Strategy) 快照 (Snapshot) 质押者 (Staker) 份额 (Shares) 质押者权重 (Staker Weight) 总质押者权重 (Total Staker Weight) 质押者比例 (Staker Proportion) 每日代币 (Tokens Per Day) 总质押者运算符奖励 (Total Staker Operator Reward) 运算符代币 (Operator Tokens) 质押者代币 (Staker Tokens)
Operator1 AVS1 stETH 4-25-2024 Staker1 1e18 1e36 3e36 0.333 1e18 333333333333333000 33333333333333300 299999999999999700
Operator1 AVS1 stETH 4-26-2024 Staker1 1e18 3e36 6e36 0.5 1e18 5e17 5e16 4.5e17
Operator1 AVS1 stETH 4-26-2024 Staker2 1e18 1e36 6e36 0.166 1e18 166666666666666000 16666666666666600 149999999999999400
Operator2 AVS1 stETH 4-25-2024 Staker3 2e18 2e36 3e36 0.666 1e18 666666666666666000 66666666666666600 599999999999999400
Operator2 AVS1 stETH 4-26-2024 Staker3 2e18 2e36 6e36 0.333 1e18 333333333333333000 33333333333333300 299999999999999700

3. 运算符奖励金额 (Operator Reward Amounts)

我们可以从其质押者的奖励分配总和来计算运算符奖励分配,因为运算符 $o$ 的份额由下式给出:

$Shares{o} = \sum{i=0}^{n=operatorStakers} Shares_i$

运算符代币总和 (Operator Token Sums)

WITH operator_token_sums AS (
  SELECT
    reward_hash,
    snapshot,
    token,
    tokens_per_day,
    avs,
    strategy,
    multiplier,
    reward_type,
    operator,
    SUM(operator_tokens) OVER (PARTITION BY operator, reward_hash, snapshot) AS operator_tokens
  FROM {{ ref('2_staker_reward_amounts') }}
),

获取给定 reward_hashsnapshot 的每个运算符的总和。每天的代币是1e18。如果我们在4-25-26上进行运算符奖励分配,则大约是 1e18 的 10%。此外,第 2 行和第 3 行是等效的。(Gets the sum for each operator for a given reward_hash and snapshot. The tokens per day was 1e18. If we take the operators reward distribution on 4-25-26, that is roughly 10% of 1e18. In addition, the rows 2 and 3 are equivalent.)

运算符 (Operator) AVS 策略 (Strategy) 快照 (Snapshot) 质押者 (Staker) 运算符代币 (Operator Tokens) 质押者代币 (Staker Tokens) 运算符代币 (总和) (Operator Tokens (Sum))
Operator1 AVS1 stETH 4-25-2024 Staker1 33333333333333300 299999999999999700 33333333333333300
Operator1 AVS1 stETH 4-26-2024 Staker1 5e16 4.5e17 66666666666666600
Operator1 AVS1 stETH 4-26-2024 Staker2 16666666666666600 149999999999999400 66666666666666600
Operator2 AVS1 stETH 4-25-2024 Staker3 66666666666666600 599999999999999400 66666666666666600
Operator2 AVS1 stETH 4-26-2024 Staker3 33333333333333300 299999999999999700 33333333333333300

去重运算符 (Dedupe Operators)

distinct_operators AS (
  SELECT *
  FROM (
      SELECT *,
        -- 我们可以使用任意顺序,因为每个(运算符、策略、哈希、快照)的 staker_weight 都相同
        -- 我们使用 strategy ASC 以获得更好的可调试性
        ROW_NUMBER() OVER (PARTITION BY reward_hash, snapshot, operator ORDER BY strategy ASC) as rn
      FROM operator_token_sums
  ) t
  WHERE rn = 1
)
SELECT * FROM distinct_operators

在上一步中,我们汇总了具有相同snapshotreward_hash的运算符奖励。现在我们删除这些行,以便在最终奖励分配中,每个reward_hashsnapshot仅对运算符计数一次。(In the previous step, we aggregated operator rewards with the same snapshot and reward_hash. Now we remove these rows so the operator is only counted once per reward_hash and snapshot in the final reward distribution.)

运算符 (Operator) AVS 策略 (Strategy) 快照 (Snapshot) 质押者 (Staker) 运算符代币 (Operator Tokens) 质押者代币 (Staker Tokens) 运算符代币 (总和) (Operator Tokens (Sum))
Operator1 AVS1 stETH 4-25-2024 Staker1 33333333333333300 299999999999999700 33333333333333300
<s> Operator1 </s> <s> AVS1 </s> <s> stETH </s> <s> 4-26-2024 </s> <s> Staker1 </s> <s> 5e16 </s> <s> 4.5e17 </s> <s> 66666666666666600 </s>
Operator1 AVS1 stETH 4-26-2024 Staker2 16666666666666600 149999999999999400 66666666666666600
Operator2 AVS1 stETH 4-25-2024 Staker3 66666666666666600 599999999999999400 66666666666666600
Operator2 AVS1 stETH 4-26-2024 Staker3 33333333333333300 299999999999999700 33333333333333300

4. 所有质押者的奖励 (Reward for all stakers)

此查询计算通过RewardCoordinator上的createRewardsForAllSubmission函数获得的奖励。此奖励直接发给质押者。(This query calculates rewards made by via the createRewardsForAllSubmission function on the RewardCoordinator. This reward goes directly to stakers.)

注意:此功能目前已暂停且未使用,因此不存在硬分叉逻辑。(Note: This functionality is currently paused and is unused, hence no hardfork logic is present.)

质押者快照 (Staker Snapshots)

WITH reward_snapshot_stakers AS (
  SELECT
    ap.reward_hash,
    ap.snapshot,
    ap.token,
    ap.tokens_per_day,
    ap.avs,
    ap.strategy,
    ap.multiplier,
    ap.reward_type,
    sss.staker,
    sss.shares
  FROM {{ ref('1_active_rewards') }} ap
  JOIN {{ ref('staker_share_snapshots') }} as sss
  ON ap.strategy = sss.strategy and ap.snapshot = sss.snapshot
  WHERE ap.reward_type = 'all_stakers'
  -- 解析出负份额和零乘数,因此不存在除以零的情况
  AND sss.shares > 0 and ap.multiplier != 0
),

选择所有将reward_for_all设置为 true 的奖励 (Select all the rewards that set reward_for_all to true)

质押者权重 -> 质押者代币 (Staker Weights -> Staker Tokens)

计算与 步骤 2 相同,除了我们不需要检查运算符 a 质押者是否已委托 (The calculation is the same as step 2, except we do not need to check the operator a stakers is delegated to)

-- 计算质押者的权重
staker_weights AS (
  SELECT *,
    SUM(multiplier * shares) OVER (PARTITION BY staker, reward_hash, snapshot) AS staker_weight
  FROM reward_snapshot_stakers
),
-- 由于质押者的权重已经计算出来,因此获取不同的质押者
distinct_stakers AS (
  SELECT *
  FROM (
      SELECT *,
        -- 我们可以使用任意顺序,因为每个(质押者、策略、哈希、快照)的 staker_weight 都相同
        ROW_NUMBER() OVER (PARTITION BY reward_hash, snapshot, staker ORDER BY strategy ASC) as rn
      FROM staker_weights
  ) t
  WHERE rn = 1
  ORDER BY reward_hash, snapshot, staker
),
-- 计算所有质押者权重的总和
staker_weight_sum AS (
  SELECT *,
    SUM(staker_weight) OVER (PARTITION BY reward_hash, snapshot) as total_staker_weight
  FROM distinct_stakers
),
-- 计算质押者代币比例
staker_proportion AS (
  SELECT *,
    FLOOR((staker_weight / total_staker_weight) * 1000000000000000) / 1000000000000000 AS staker_proportion
  FROM staker_weight_sum
),
-- 计算质押者的总代币
staker_tokens AS (
  SELECT *,
  (tokens_per_day * staker_proportion)::text::decimal(38,0) as staker_tokens
  FROM staker_proportion
)
SELECT * from staker_tokens

5. 所有收益者的奖励 - 质押者 (Reward For All Earners - Stakers)

此奖励功能奖励所有选择加入至少一个 AVS 的运营商(及其委托的质押者)。运营商获得固定的 10% 佣金。(This reward functionality rewards all operators (and their delegated stakers) who have opted into at least one AVS. The operator gets a fixed 10% commission.)

我们通过以下方式做到这一点:

  1. 获取所有已选择加入至少 1 个 AVS 的运营商 (Getting all operators who have opted into at least 1 AVS)
  2. 获取运营商的质押者 (Get the operator's stakers)
  3. 计算支付给质押者的款项 (Calculate payout to stakers)
  4. 计算支付给运营商的款项(步骤 7)(Calculate payout to operators (step 7))

AVS 选择的运营商 (AVS Opted Operators)

WITH avs_opted_operators AS (
  SELECT DISTINCT
    snapshot,
    operator
  FROM {{ ref('operator_avs_registration_snapshots') }}
),

获取已注册给定快照的 AVS 的唯一运算符。这使用operator_avs_registration_snapshots预计算视图。请注意,此表中不存在取消注册。(Gets the unique operators who have registered for an AVS for a given snapshot. This uses the operator_avs_registration_snapshots preclalculation view. Note that deregistrations do not exist in this table.)

奖励快照运营商 (Reward Snapshot Operators)

-- 获取将在给定快照的奖励提交中获得奖励的运算符
reward_snapshot_operators as (
  SELECT
    ap.reward_hash,
    ap.snapshot,
    ap.token,
    ap.tokens_per_day_decimal,
    ap.avs,
    ap.strategy,
    ap.multiplier,
    ap.reward_type,
    ap.reward_submission_date,
    aoo.operator
  FROM {{ ref('1_active_rewards') }} ap
  JOIN avs_opted_operators aoo
  ON ap.snapshot = aoo.snapshot
  WHERE ap.reward_type = 'all_earners'
),

我们将活动奖励与快照中已注册至少一个 AVS 的运算符连接起来。(We add join the active rewards with operators who have registered to at least one AVS for the snapshot.)

质押者委托的运营商 (Staker Delegated Operators)

-- 获取已委托给快照的运算符的质押者
staker_delegated_operators AS (
  SELECT
    rso.*,
    sds.staker
  FROM reward_snapshot_operators rso
  JOIN {{ ref('staker_delegation_snapshots') }} sds
  ON
    rso.operator = sds.operator AND
    rso.snapshot = sds.snapshot
),

获取已委托给快照的注册运算符的质押者。(Get the stakers that were delegated to the registered operator for the snapshot.)

质押者策略份额 -> 代币分解 (Staker Strategy Shares -> Token Breakdowns)

其余计算与2_staker_reward_amounts相同,没有硬分叉。(The rest of the calculation proceeds as 2_staker_reward_amounts, without having hard forks.)

-- 获取质押者已委托给运算符的每种策略的份额
staker_strategy_shares AS (
  SELECT
    sdo.*,
    sss.shares
  FROM staker_delegated_operators sdo
  JOIN {{ ref('staker_share_snapshots') }} sss
  ON
    sdo.staker = sss.staker AND
    sdo.snapshot = sss.snapshot AND
    sdo.strategy = sss.strategy
  -- 解析出负份额和零乘数,因此不存在除以零的情况
  WHERE sss.shares > 0 and sdo.multiplier != 0
),
-- 计算质押者的权重
staker_weights AS (
  SELECT *,
    SUM(multiplier * shares) OVER (PARTITION BY staker, reward_hash, snapshot) AS staker_weight
  FROM staker_strategy_shares
),
-- 由于质押者的权重已经计算出来,因此获取不同的质押者
distinct_stakers AS (
  SELECT *
  FROM (
      SELECT *,
        -- 我们可以使用任意顺序,因为每个(质押者、策略、哈希、快照)的 staker_weight 都相同
        -- 我们使用 strategy ASC 以获得更好的可调试性
        ROW_NUMBER() OVER (PARTITION BY reward_hash, snapshot, staker ORDER BY strategy ASC) as rn
      FROM staker_weights
  ) t
  WHERE rn = 1
  ORDER BY reward_hash, snapshot, staker
),
-- 计算每个奖励和快照的所有质押者权重的总和
staker_weight_sum AS (
  SELECT *,
    SUM(staker_weight) OVER (PARTITION BY reward_hash, snapshot) as total_weight
  FROM distinct_stakers
),
-- 计算每个奖励和快照的质押者代币比例
staker_proportion AS (
  SELECT *,
    FLOOR((staker_weight / total_weight) * 1000000000000000) / 1000000000000000 AS staker_proportion
  FROM staker_weight_sum
),
-- 计算(质押者、运算符)对的总代币
staker_operator_total_tokens AS (
  SELECT *,
    FLOOR(staker_proportion * tokens_per_day_decimal) as total_staker_operator_payout
  FROM staker_proportion
),
-- 计算每个(质押者、运算符)对的代币分解
token_breakdowns AS (
  SELECT *,
    floor(total_staker_operator_payout * 0.10) as operator_tokens,
    total_staker_operator_payout - floor(total_staker_operator_payout * 0.10) as staker_tokens
  FROM staker_operator_total_tokens
)
SELECT * from token_breakdowns
ORDER BY reward_hash, snapshot, staker, operator

6. 所有收益者的奖励 - 运算符 (Reward for All Earners - Operators)

与步骤 3 类似,我们现在必须解析出运算符的奖励。(Similar to step 3, we now have to parse out the reward for operators.)

-- 对于每个运算符、奖励哈希和快照,汇总运算符在质押者中的代币
WITH operator_token_sums AS (
  SELECT
    reward_hash,
    snapshot,
    token,
    tokens_per_day_decimal,
    avs,
    strategy,
    multiplier,
    reward_type,
    operator,
    SUM(operator_tokens) OVER (PARTITION BY operator, reward_hash, snapshot) AS operator_tokens
  FROM {{ ref('5_rfae_stakers') }}
),
-- 对于每个运算符、奖励哈希和快照,对策略中的运算符代币进行去重
distinct_operators AS (
  SELECT *
  FROM (
      SELECT *,
        -- 我们可以使用任意顺序,因为每个(运算符、策略、哈希、快照)的 staker_weight 都相同
        -- 我们使用 strategy ASC 以获得更好的可调试性
        ROW_NUMBER() OVER (PARTITION BY reward_hash, snapshot, operator ORDER BY strategy ASC) as rn
      FROM operator_token_sums
  ) t
  WHERE rn = 1
)
SELECT * FROM distinct_operators

7. Gold Active OD 奖励 (Gold Active OD Rewards)

WITH
-- 步骤 2:修改活动奖励并计算每天的代币
active_rewards_modified AS (
    SELECT
        *,
        CAST(@cutoffDate AS TIMESTAMP(6)) AS global_end_inclusive -- Inclusive 表示我们确实使用这一天作为快照
    FROM operator_directed_rewards
    WHERE end_timestamp >= TIMESTAMP '{{.rewardsStart}}'
      AND start_timestamp &lt;= TIMESTAMP '{{.cutoffDate}}'
      AND block_time &lt;= TIMESTAMP '{{.cutoffDate}}' -- 始终确保我们不使用未来数据。由于我们从不回填,因此永远不会发生,但为了安全和一致性,我们在此处进行操作。
),

-- 步骤 3:削减每个奖励的开始和结束窗口以处理全局范围
active_rewards_updated_end_timestamps AS (
    SELECT
        avs,
        operator,
        /**
         * 削减开始和结束窗口以处理
         * A. 最近出现的追溯奖励,其开始日期小于 start_timestamp
         * B. 不要在此运行的 end_timestamp 之后进行任何奖励
         */
        start_timestamp AS reward_start_exclusive,
        LEAST(global_end_inclusive, end_timestamp) AS reward_end_inclusive,
        amount,
        token,
        multiplier,
        strategy,
        reward_hash,
        duration,
        global_end_inclusive,
        block_date AS reward_submission_date
    FROM active_rewards_modified
),

-- 步骤 4:对于每个奖励哈希,找到最新的快照
active_rewards_updated_start_timestamps AS (
    SELECT
        ap.avs,
        ap.operator,
        COALESCE(MAX(g.snapshot), ap.reward_start_exclusive) AS reward_start_exclusive,
        ap.reward_end_inclusive,
        ap.token,
        -- 我们使用 floor 来确保我们始终低估每天的总代币
        FLOOR(ap.amount) AS amount_decimal,
        ap.multiplier,
        ap.strategy,
        ap.reward_hash,
        ap.duration,
        ap.global_end_inclusive,
        ap.reward_submission_date
    FROM active_rewards_updated_end_timestamps ap
    LEFT JOIN gold_table g
        ON g.reward_hash = ap.reward_hash
    GROUP BY
        ap.avs,
        ap.operator,
        ap.reward_end_inclusive,
        ap.token,
        ap.amount,
        ap.multiplier,
        ap.strategy,
        ap.reward_hash,
        ap.duration,
        ap.global_end_inclusive,
        ap.reward_start_exclusive,
        ap.reward_submission_date
),

-- 步骤 5:过滤掉无效的奖励范围
active_reward_ranges AS (
    /** 删除 (reward_start_exclusive, reward_end_inclusive) 窗口,其中
    * 1. reward_start_exclusive >= reward_end_inclusive:奖励期已完成,或者我们将在后续运行中处理
    */
    SELECT *
    FROM active_rewards_updated_start_timestamps
    WHERE reward_start_exclusive &lt; reward_end_inclusive
),

-- 步骤 6:展开范围以获得每个包含日期的每日
exploded_active_range_rewards AS (
    SELECT
        *
    FROM active_reward_ranges
    CROSS JOIN generate_series(
        DATE(reward_start_exclusive),
        DATE(reward_end_inclusive),
        INTERVAL '1' DAY
    ) AS day
),

-- 步骤 7:准备清理后的活动奖励
active_rewards_cleaned AS (
    SELECT
        avs,
        operator,
        CAST(day AS DATE) AS snapshot,
        token,
        amount_decimal,
        multiplier,
        strategy,
        duration,
        reward_hash,
        reward_submission_date
    FROM exploded_active_range_rewards
    -- 删除开始日期的快照
    WHERE day != reward_start_exclusive
),

-- 步骤 8:通过 (avs, snapshot, operator, reward_hash) 删除活动奖励
active_rewards_reduced_deduped AS (
    SELECT DISTINCT avs, snapshot, operator, reward_hash
    FROM active_rewards_cleaned
),

-- 步骤 9:除以运算符注册的快照数
op_avs_num_registered_snapshots AS (
    SELECT
        ar.reward_hash,
        ar.operator,
        COUNT(*) AS num_registered_snapshots
    FROM active_rewards_reduced_deduped ar
    JOIN operator_avs_registration_snapshots oar
    ON
        ar.avs = oar.avs
        AND ar.snapshot = oar.snapshot
        AND ar.operator = oar.operator
    GROUP BY ar.reward_hash, ar.operator
),

-- 步骤 10:按运算符注册的快照数划分要支付的金额
active_rewards_with_registered_snapshots AS (
    SELECT
        arc.*,
        COALESCE(nrs.num_registered_snapshots, 0) as num_registered_snapshots
    FROM active_rewards_cleaned arc
    LEFT JOIN op_avs_num_registered_snapshots nrs
    ON
        arc.reward_hash = nrs.reward_-- 计算每个 operator reward 的总 staker 分配,具有动态分配逻辑
-- 如果未找到分配,则默认为 1000 (10%)
```sql
staker_splits AS (
    SELECT
        rso.*,
        CASE
            WHEN rso.snapshot &lt; @trinityHardforkDate AND rso.reward_submission_date &lt; @trinityHardforkDate THEN
                rso.tokens_per_registered_snapshot_decimal - FLOOR(rso.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL))
            ELSE
                rso.tokens_per_registered_snapshot_decimal - FLOOR(rso.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, dos.split, 1000) / CAST(10000 AS DECIMAL))
        END AS staker_split
    FROM reward_snapshot_operators rso
    LEFT JOIN operator_avs_split_snapshots oas
        ON rso.operator = oas.operator
       AND rso.avs = oas.avs
       AND rso.snapshot = oas.snapshot
    LEFT JOIN default_operator_split_snapshots dos ON (rso.snapshot = dos.snapshot)
),

-- 获取为 snapshot 委托给 operator 的 staker

staker_delegated_operators AS (
    SELECT
        ors.*,
        sds.staker
    FROM staker_splits ors
    JOIN staker_delegation_snapshots sds
        ON ors.operator = sds.operator
       AND ors.snapshot = sds.snapshot
),

-- 获取委托给 operator 的 staker 的 shares

staker_avs_strategy_shares AS (
    SELECT
        sdo.*,
        sss.shares
    FROM staker_delegated_operators sdo
    JOIN staker_share_snapshots sss
        ON sdo.staker = sss.staker
       AND sdo.snapshot = sss.snapshot
       AND sdo.strategy = sss.strategy
    -- 过滤掉负 shares 和零 multiplier,以避免除以零
    WHERE sss.shares > 0 AND sdo.multiplier != 0
),

-- 计算每个 staker 的权重

staker_weights AS (
    SELECT
        *,
        SUM(multiplier * shares) OVER (PARTITION BY staker, reward_hash, snapshot) AS staker_weight
    FROM staker_avs_strategy_shares
),

-- 获取不同的 staker,因为他们的权重已经计算出来了

distinct_stakers AS (
    SELECT *
    FROM (
        SELECT
            *,
            -- 这里我们可以使用任意顺序,因为每个(staker、strategy、hash、snapshot)的 staker_weight 都是相同的
            -- 我们使用 strategy ASC 以便更好地进行调试
            ROW_NUMBER() OVER (
                PARTITION BY reward_hash, snapshot, staker
                ORDER BY strategy ASC
            ) AS rn
        FROM staker_weights
    ) t
    WHERE rn = 1
    ORDER BY reward_hash, snapshot, staker
),

-- 计算每个 reward 和 snapshot 的所有 staker 权重的总和

staker_weight_sum AS (
    SELECT
        *,
        SUM(staker_weight) OVER (PARTITION BY reward_hash, operator, snapshot) AS total_weight
    FROM distinct_stakers
),

-- 计算每个 reward 和 snapshot 的 tokens 的 staker 比例

staker_proportion AS (
    SELECT
        *,
        FLOOR((staker_weight / total_weight) * 1000000000000000) / 1000000000000000 AS staker_proportion
    FROM staker_weight_sum
),

-- 计算 staker reward 金额

staker_reward_amounts AS (
    SELECT
        *,
        FLOOR(staker_proportion * staker_split) AS staker_tokens
    FROM staker_proportion
)

-- 输出最终表

SELECT * FROM staker_reward_amounts

10. Gold AVS Operator-Directed Reward Amounts

-- 步骤 1:获取 operator 尚未注册 AVS 或 AVS 不存在的行
WITH reward_snapshot_operators AS (
    SELECT
        ap.reward_hash,
        ap.snapshot AS snapshot,
        ap.token,
        ap.tokens_per_registered_snapshot_decimal,
        ap.avs AS avs,
        ap.operator AS operator,
        ap.strategy,
        ap.multiplier,
        ap.reward_submission_date
    FROM {{.activeODRewardsTable}} ap
    WHERE
        ap.num_registered_snapshots = 0
),

-- 步骤 2:为每个 (operator, reward hash, snapshot) 删除跨 strategies 的 operator tokens -- 由于以上结果是一个跨 strategies 的扁平化的 operator-directed reward 提交

distinct_operators AS (
    SELECT *
    FROM (
        SELECT
            *,
            -- 这里我们可以使用任意顺序,因为每个 (operator, strategy, hash, snapshot) 的 avs_tokens 都是相同的
            -- 我们使用 strategy ASC 以便更好地进行调试
            ROW_NUMBER() OVER (
                PARTITION BY reward_hash, snapshot, operator
                ORDER BY strategy ASC
            ) AS rn
        FROM reward_snapshot_operators
    ) t
    WHERE rn = 1
),

-- 步骤 3:求和每个(reward hash、snapshot)的 operator tokens -- 因为我们想要将这些 operator 金额的总和退还给该 snapshot 的 reward 提交中的 AVS

operator_token_sums AS (
    SELECT
        reward_hash,
        snapshot,
        token,
        avs,
        operator,
        SUM(tokens_per_registered_snapshot_decimal) OVER (PARTITION BY reward_hash, snapshot) AS avs_tokens
    FROM distinct_operators
)

-- 步骤 4:输出最终表

SELECT * FROM operator_token_sums

11. Gold Active Operator-Directed Operator Set Rewards

WITH

-- 步骤 1:修改 active rewards 并计算每天的 tokens

active_rewards_modified AS (
    SELECT
        *,
        CAST(@cutoffDate AS TIMESTAMP(6)) AS global_end_inclusive -- Inclusive(包含性)意味着我们**使用**这一天作为一个快照
    FROM operator_directed_operator_set_rewards
    WHERE end_timestamp >= TIMESTAMP '{{.rewardsStart}}'
      AND start_timestamp &lt;= TIMESTAMP '{{.cutoffDate}}'
      AND block_time &lt;= TIMESTAMP '{{.cutoffDate}}' -- 始终确保我们没有使用未来的数据。 由于我们从不回填,所以永远不会发生这种情况,但为了安全和一致性,这里加上。
),

-- 步骤 2:切割每个 reward 的开始和结束窗口以处理全局范围

active_rewards_updated_end_timestamps AS (
    SELECT
        avs,
        operator_set_id,
        operator,
        /**
         * 切割开始和结束窗口以处理
         * A. 最近出现的追溯奖励,其开始日期小于 start_timestamp
         * B. 此运行的任何奖励都不要超过 end_timestamp
         */
        start_timestamp AS reward_start_exclusive,
        LEAST(global_end_inclusive, end_timestamp) AS reward_end_inclusive,
        amount,
        token,
        multiplier,
        strategy,
        reward_hash,
        duration,
        global_end_inclusive,
        block_date AS reward_submission_date
    FROM active_rewards_modified
),

-- 步骤 3:对于每个 reward hash,找到最新的 snapshot

active_rewards_updated_start_timestamps AS (
    SELECT
        ap.avs,
        ap.operator_set_id,
        ap.operator,
        COALESCE(MAX(g.snapshot), ap.reward_start_exclusive) AS reward_start_exclusive,
        ap.reward_end_inclusive,
        ap.token,
        -- 我们使用 floor 以确保我们始终低估每天的总tokens
        FLOOR(ap.amount) AS amount_decimal,
        ap.multiplier,
        ap.strategy,
        ap.reward_hash,
        ap.duration,
        ap.global_end_inclusive,
        ap.reward_submission_date
    FROM active_rewards_updated_end_timestamps ap
    LEFT JOIN gold_table g
        ON g.reward_hash = ap.reward_hash
    GROUP BY
        ap.avs,
        ap.operator_set_id,
        ap.operator,
        ap.reward_end_inclusive,
        ap.token,
        ap.amount,
        ap.multiplier,
        ap.strategy,
        ap.reward_hash,
        ap.duration,
        ap.global_end_inclusive,
        ap.reward_start_exclusive,
        ap.reward_submission_date
),

-- 步骤 4:过滤掉无效的 reward 范围

active_reward_ranges AS (
    /** 取出 (reward_start_exclusive, reward_end_inclusive) 窗口,其中
    * 1. reward_start_exclusive >= reward_end_inclusive:奖励期已完成,或者我们将在后续运行中处理
    */
    SELECT *
    FROM active_rewards_updated_start_timestamps
    WHERE reward_start_exclusive &lt; reward_end_inclusive
),

-- 步骤 5:展开范围,包括每个日期的天数

exploded_active_range_rewards AS (
    SELECT
        *
    FROM active_reward_ranges
    CROSS JOIN generate_series(
        DATE(reward_start_exclusive),
        DATE(reward_end_inclusive),
        INTERVAL '1' DAY
    ) AS day
),

-- 步骤 6:准备好清理的 active rewards

active_rewards_cleaned AS (
    SELECT
        avs,
        operator_set_id,
        operator,
        CAST(day AS DATE) AS snapshot,
        token,
        amount_decimal,
        multiplier,
        strategy,
        duration,
        reward_hash,
        reward_submission_date
    FROM exploded_active_range_rewards
    -- 删除开始当天的快照
    WHERE day != reward_start_exclusive
),

-- 步骤 7:通过 (avs, operator_set_id, snapshot, operator, reward_hash) 删除 active rewards 的重复数据

active_rewards_reduced_deduped AS (
    SELECT DISTINCT avs, operator_set_id, snapshot, operator, reward_hash
    FROM active_rewards_cleaned
),

-- 步骤 8:除以 operator 注册到 operator set 的快照数量

op_set_op_num_registered_snapshots AS (
    SELECT
        ar.reward_hash,
        ar.operator,
        COUNT(*) AS num_registered_snapshots
    FROM active_rewards_reduced_deduped ar
    JOIN operator_set_operator_registration_snapshots osor
    ON
        ar.avs = osor.avs
        AND ar.operator_set_id = osor.operator_set_id
        AND ar.snapshot = osor.snapshot
        AND ar.operator = osor.operator
    GROUP BY ar.reward_hash, ar.operator
),

-- 步骤 9:将 amount 除以 operator 注册的快照数量

active_rewards_with_registered_snapshots AS (
    SELECT
        arc.*,
        COALESCE(nrs.num_registered_snapshots, 0) as num_registered_snapshots
    FROM active_rewards_cleaned arc
    LEFT JOIN op_set_op_num_registered_snapshots nrs
    ON
        arc.reward_hash = nrs.reward_hash
        AND arc.operator = nrs.operator
),

-- 步骤 10:将 amount 除以 operator 注册的快照数量

active_rewards_final AS (
    SELECT
        ar.*,
        CASE
            -- 如果 operator 没有注册任何快照,只需获得每天的常规tokens来退还 AVS
            WHEN ar.num_registered_snapshots = 0 THEN floor(ar.amount_decimal / (duration / 86400))
            ELSE floor(ar.amount_decimal / ar.num_registered_snapshots)
        END AS tokens_per_registered_snapshot_decimal
    FROM active_rewards_with_registered_snapshots ar
)
SELECT * FROM active_rewards_final

12. Gold Operator Operator-Directed Operator Set Reward Amounts

-- 步骤 1:获取 operator 已注册 operator set 的行
WITH reward_snapshot_operators AS (
    SELECT
        ap.reward_hash,
        ap.snapshot AS snapshot,
        ap.token,
        ap.tokens_per_registered_snapshot_decimal,
        ap.avs AS avs,
        ap.operator_set_id AS operator_set_id,
        ap.operator AS operator,
        ap.strategy,
        ap.multiplier,
        ap.reward_submission_date
    FROM {{.activeODRewardsTable}} ap
    JOIN operator_set_operator_registration_snapshots osor
        ON ap.avs = osor.avs
       AND ap.operator_set_id = osor.operator_set_id
       AND ap.snapshot = osor.snapshot
       AND ap.operator = osor.operator
),

-- 步骤 2:删除每个 (operator, reward hash, snapshot) 中跨 strategies 的 operator tokens 的重复数据 -- 由于以上结果是一个扁平化的 operator-directed reward 提交

distinct_operators AS (
    SELECT *
    FROM (
        SELECT
            *,
            -- 这里我们可以使用任意顺序,因为每个 (operator, strategy, hash, snapshot) 的 avs_tokens 都是相同的
            -- 我们使用 strategy ASC 以便更好地进行调试
            ROW_NUMBER() OVER (
                PARTITION BY reward_hash, snapshot, operator
                ORDER BY strategy ASC
            ) AS rn
        FROM reward_snapshot_operators
    ) t
    -- 仅保留每个 (operator, reward hash, snapshot) 的第一行
    WHERE rn = 1
),

-- 步骤 3:使用动态分割逻辑计算每个 operator 的 tokens -- 如果未找到分割,则默认为 1000 (10%)

operator_splits AS (
    SELECT
        dop.*,
        COALESCE(oss.split, dos.split, 1000) / CAST(10000 AS DECIMAL) AS split_pct,
        FLOOR(dop.tokens_per_registered_snapshot_decimal * COALESCE(oss.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) AS operator_tokens
    FROM distinct_operators dop
    LEFT JOIN operator_set_split_snapshots oss
        ON dop.operator = oss.operator
       AND dop.avs = oss.avs
       AND dop.operator_set_id = oss.operator_set_id
       AND dop.snapshot = oss.snapshot
    LEFT JOIN default_operator_split_snapshots dos ON (dop.snapshot = dos.snapshot)
)

-- 步骤 4:输出具有 operator 分割的最终表

SELECT * FROM operator_splits

13. Gold Staker Operator-Directed Operator Set Reward Amounts

-- 步骤 1:获取 operator 已注册 operator set 的行
WITH reward_snapshot_operators AS (
    SELECT
        ap.reward_hash,
        ap.snapshot AS snapshot,
        ap.token,
        ap.tokens_per_registered_snapshot_decimal,
        ap.avs AS avs,
        ap.operator_set_id AS operator_set_id,
        ap.operator AS operator,
        ap.strategy,
        ap.multiplier,
        ap.reward_submission_date
    FROM {{.activeODRewardsTable}} ap
    JOIN operator_set_operator_registration_snapshots osor
        ON ap.avs = osor.avs
       AND ap.operator_set_id = osor.operator_set_id
       AND ap.snapshot = osor.snapshot
       AND ap.operator = osor.operator
),

-- 获取 strategies 已注册 operator set 的行

operator_set_strategy_registrations AS (
    SELECT
        rso.*
    FROM reward_snapshot_operators rso
    JOIN operator_set_strategy_registration_snapshots ossr
        ON rso.avs = ossr.avs
       AND rso.operator_set_id = ossr.operator_set_id
       AND rso.snapshot = ossr.snapshot
       AND rso.strategy = ossr.strategy
),

-- 使用动态分割逻辑计算每个 operator reward 的总 staker 分割 -- 如果未找到分割,则默认为 1000 (10%)

staker_splits AS (
    SELECT
        ossr.*,
        ossr.tokens_per_registered_snapshot_decimal - FLOOR(ossr.tokens_per_registered_snapshot_decimal * COALESCE(oss.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) AS staker_split
    FROM operator_set_strategy_registrations ossr
    LEFT JOIN operator_set_split_snapshots oss
        ON ossr.operator = oss.operator
       AND ossr.avs = oss.avs
       AND ossr.operator_set_id = oss.operator_set_id
       AND ossr.snapshot = oss.snapshot
    LEFT JOIN default_operator_split_snapshots dos ON (ossr.snapshot = dos.snapshot)
),

-- 获取为 snapshot 委托给 operator 的 staker

staker_delegated_operators AS (
    SELECT
        ors.*,
        sds.staker
    FROM staker_splits ors
    JOIN staker_delegation_snapshots sds
        ON ors.operator = sds.operator
       AND ors.snapshot = sds.snapshot
),

-- 获取委托给 operator 的 staker 的 shares

staker_strategy_shares AS (
    SELECT
        sdo.*,
        sss.shares
    FROM staker_delegated_operators sdo
    JOIN staker_share_snapshots sss
        ON sdo.staker = sss.staker
       AND sdo.snapshot = sss.snapshot
       AND sdo.strategy = sss.strategy
    -- 过滤掉负 shares 和零 multiplier,以避免除以零
    WHERE sss.shares > 0 AND sdo.multiplier != 0
),

-- 计算每个 staker 的权重

staker_weights AS (
    SELECT
        *,
        SUM(multiplier * shares) OVER (PARTITION BY staker, reward_hash, snapshot) AS staker_weight
    FROM staker_strategy_shares
),

-- 获取不同的 staker,因为他们的权重已经计算出来了

distinct_stakers AS (
    SELECT *
    FROM (
        SELECT
            *,
            -- 这里我们可以使用任意顺序,因为每个(staker、strategy、hash、snapshot)的 staker_weight 都是相同的
            -- 我们使用 strategy ASC 以便更好地进行调试
            ROW_NUMBER() OVER (
                PARTITION BY reward_hash, snapshot, staker
                ORDER BY strategy ASC
            ) AS rn
        FROM staker_weights
    ) t
    WHERE rn = 1
    ORDER BY reward_hash, snapshot, staker
),

-- 计算每个 reward 和 snapshot 的所有 staker 权重的总和

staker_weight_sum AS (
    SELECT
        *,
        SUM(staker_weight) OVER (PARTITION BY reward_hash, operator, snapshot) AS total_weight
    FROM distinct_stakers
),

-- 计算每个 reward 和 snapshot 的 tokens 的 staker 比例

staker_proportion AS (
    SELECT
        *,
        FLOOR((staker_weight / total_weight) * 1000000000000000) / 1000000000000000 AS staker_proportion
    FROM staker_weight_sum
),

-- 计算 staker reward 金额

staker_reward_amounts AS (
    SELECT
        *,
        FLOOR(staker_proportion * staker_split) AS staker_tokens
    FROM staker_proportion
)

-- 输出最终表

SELECT * FROM staker_reward_amounts

14. Gold AVS Operator-Directed Operator Set Reward Amounts

-- 步骤 1:获取 operator 尚未注册 AVS 或 AVS 不存在的行
WITH not_registered_operators AS (
    SELECT
        ap.reward_hash,
        ap.snapshot AS snapshot,
        ap.token,
        ap.tokens_per_registered_snapshot_decimal,
        ap.avs AS avs,
        ap.operator_set_id AS operator_set_id,
        ap.operator AS operator,
        ap.strategy,
        ap.multiplier,
        ap.reward_submission_date
    FROM {{.activeODRewardsTable}} ap
    WHERE
        ap.num_registered_snapshots = 0
),

-- 步骤 2:删除每个 (operator, reward hash, snapshot) 中跨 strategies 的 operator tokens 的重复数据 -- 由于以上结果是一个扁平化的 operator-directed reward 提交

distinct_not_registered_operators AS (
    SELECT *
    FROM (
        SELECT
            *,
            -- 这里我们可以使用任意顺序,因为每个 (operator, strategy, hash, snapshot) 的 avs_tokens 都是相同的
            -- 我们使用 strategy ASC 以便更好地进行调试
            ROW_NUMBER() OVER (
                PARTITION BY reward_hash, snapshot, operator
                ORDER BY strategy ASC
            ) AS rn
        FROM not_registered_operators
    ) t
    WHERE rn = 1
),

-- 步骤 3:将每个(reward hash、snapshot)的 operator tokens 求和 -- 因为我们想要将这些 operator 金额的总和退还给该 snapshot 的 reward 提交中的 AVS

avs_operator_refund_sums AS (
    SELECT
        reward_hash,
        snapshot,
        token,
        avs,
        operator_set_id,
        operator,
        SUM(tokens_per_registered_snapshot_decimal) OVER (PARTITION BY reward_hash, snapshot) AS avs_tokens
    FROM distinct_not_registered_operators
),

-- 步骤 4:查找 operator 已注册但 strategies 未注册到 operator set 的行 -- 首先,获取 operator 已注册的所有行

registered_operators AS (
    SELECT
        ap.reward_hash,
        ap.snapshot,
        ap.token,
        ap.tokens_per_registered_snapshot_decimal,
        ap.avs,
        ap.operator_set_id,
        ap.operator,
        ap.strategy,
        ap.multiplier,
        ap.reward_submission_date
    FROM {{.activeODRewardsTable}} ap
    JOIN operator_set_operator_registration_snapshots osor
        ON ap.avs = osor.avs
        AND ap.operator_set_id = osor.operator_set_id
        AND ap.snapshot = osor.snapshot
        AND ap.operator = osor.operator
    WHERE ap.num_registered_snapshots != 0
      AND ap.reward_submission_date >= @coloradoHardforkDate
),

-- 步骤 5:对于每个 reward/snapshot/operator_set,检查是否已注册任何 strategies

strategies_registered AS (
    SELECT DISTINCT
        ro.reward_hash,
        ro.snapshot,
        ro.avs,
        ro.operator_set_id
    FROM registered_operators ro
    JOIN operator_set_strategy_registration_snapshots ossr
        ON ro.avs = ossr.avs
        AND ro.operator_set_id = ossr.operator_set_id
        AND ro.snapshot = ossr.snapshot
        AND ro.strategy = ossr.strategy
),

-- 步骤 6:查找 operator 已注册但未注册 strategies 的 reward/snapshot 组合

strategies_not_registered AS (
    SELECT
        ro.*
    FROM registered_operators ro
    LEFT JOIN strategies_registered sr
        ON ro.reward_hash = sr.reward_hash
        AND ro.snapshot = sr.snapshot
        AND ro.avs = sr.avs
        AND ro.operator_set_id = sr.operator_set_id
    WHERE sr.reward_hash IS NULL
),

-- 步骤 7:使用动态分割逻辑计算每个 reward 的 staker 分割 -- 如果未找到分割,则默认为 1000 (10%)

staker_splits AS (
    SELECT
        snr.*,
        snr.tokens_per_registered_snapshot_decimal - FLOOR(snr.tokens_per_registered_snapshot_decimal * COALESCE(oss.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) AS staker_split
    FROM strategies_not_registered snr
    LEFT JOIN operator_set_split_snapshots oss
        ON snr.operator = oss.operator
        AND snr.avs = oss.avs
        AND snr.operator_set_id = oss.operator_set_id
        AND snr.snapshot = oss.snapshot
    LEFT JOIN default_operator_split_snapshots dos ON (snr.snapshot = dos.snapshot)
),

-- 步骤 8:删除每个(operator、reward hash、snapshot)中跨 strategies 的 staker 分割的重复数据 -- 由于以上结果是一个扁平化的 operator-directed reward 提交。

distinct_staker_splits AS (
    SELECT *
    FROM (
        SELECT
            *,
            ROW_NUMBER() OVER (
                PARTITION BY reward_hash, snapshot, operator
                ORDER BY strategy ASC
            ) AS rn
        FROM staker_splits
    ) t
    WHERE rn = 1
),

-- 步骤 9:将应退还的每个(reward hash、snapshot)的 staker tokens 求和

avs_staker_refund_sums AS (
    SELECT
        reward_hash,
        snapshot,
        token,
        avs,
        operator_set_id,
        operator,
        SUM(staker_split) OVER (PARTITION BY reward_hash, snapshot) AS avs_tokens
    FROM distinct_staker_splits
),

-- 步骤 10:将两个退款案例合并为一个结果

combined_avs_refund_amounts AS (
    SELECT * FROM avs_operator_refund_sums
    UNION ALL
    SELECT * FROM avs_staker_refund_sums
)

-- 输出最终表

SELECT * FROM combined_avs_refund_amounts

15. Gold Table Staging

此查询结合了步骤 2,3,4,5,6,7,8,9,10,11,12,13,14 中的 rewards,以生成具有以下列的表:

Earner(收益人) Snapshot(快照) Reward Hash(Reward哈希) Token(Token) Amount(金额)

获取所有 rewards

汇总步骤 2、3 和 4 中的 rewards。 我们使用 DISTINCT 作为一个健全性检查,以丢弃给定 reward_hashsnapshot 的具有相同 strategyearner 的行。

WITH staker_rewards AS (
  -- 我们可以在这里选择 DISTINCT,因为 staker 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
  SELECT DISTINCT
    staker as earner,
    snapshot,
    reward_hash,
    token,
    staker_tokens as amount
  FROM {{.stakerRewardAmountsTable}}
),
operator_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 operator 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    operator as earner,
    snapshot,
    reward_hash,
    token,
    operator_tokens as amount
  FROM {{.operatorRewardAmountsTable}}
),
rewards_for_all AS (
  SELECT DISTINCT
    staker as earner,
    snapshot,
    reward_hash,
    token,
    staker_tokens as amount
  FROM {{.rewardsForAllTable}}
),
rewards_for_all_earners_stakers AS (
  SELECT DISTINCT
    staker as earner,
    snapshot,
    reward_hash,
    token,
    staker_tokens as amount
  FROM {{.rfaeStakerTable}}
),
rewards_for_all_earners_operators AS (
  SELECT DISTINCT
    operator as earner,
    snapshot,
    reward_hash,
    token,
    operator_tokens as amount
  FROM {{.rfaeOperatorTable}}
),
{{ if .enableRewardsV2 }}
operator_od_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 operator 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    operator as earner,
    snapshot,
    reward_hash,
    token,
    operator_tokens as amount
  FROM {{.operatorODRewardAmountsTable}}
),
staker_od_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 staker 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    staker as earner,
    snapshot,
    reward_hash,
    token,
    staker_tokens as amount
  FROM {{.stakerODRewardAmountsTable}}
),
avs_od_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 avs 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    avs as earner,
    snapshot,
    reward_hash,
    token,
    avs_tokens as amount
  FROM {{.avsODRewardAmountsTable}}
),
{{ end }}
{{ if .enableRewardsV2_1 }}
operator_od_operator_set_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 operator 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    operator as earner,
    snapshot,
    reward_hash,
    token,
    operator_tokens as amount
  FROM {{.operatorODOperatorSetRewardAmountsTable}}
),
staker_od_operator_set_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 staker 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    staker as earner,
    snapshot,
    reward_hash,
    token,
    staker_tokens as amount
  FROM {{.stakerODOperatorSetRewardAmountsTable}}
),
avs_od_operator_set_rewards AS (
  SELECT DISTINCT
    -- 我们可以在这里选择 DISTINCT,因为 avs 的 tokens 对于 reward hash 中的每种 strategy 都是相同的
    avs as earner,
    snapshot,
    reward_hash,
    token,
    avs_tokens as amount
  FROM {{.avsODOperatorSetRewardAmountsTable}}
),
{{ end }}
combined_rewards AS (
  SELECT * FROM operator_rewards
  UNION ALL
  SELECT * FROM staker_rewards
  UNION ALL
  SELECT * FROM rewards_for_all
  UNION ALL
  SELECT * FROM rewards_for_all_earners_stakers
  UNION ALL
  SELECT * FROM rewards_for_all_earners_operators
{{ if .enableRewardsV2 }}
  UNION ALL
  SELECT * FROM operator_od_rewards
  UNION ALL
  SELECT * FROM staker_od_rewards
  UNION ALL
  SELECT * FROM avs_od_rewards
{{ end }}
{{ if .enableRewardsV2_1 }}
  UNION ALL
  SELECT * FROM operator_od_operator_set_rewards
  UNION ALL
  SELECT * FROM staker_od_operator_set_rewards
  UNION ALL
  SELECT * FROM avs_od_operator_set_rewards
{{ end }}
)

删除 Earners 的重复数据

-- 删除 earners 的重复数据,主要是作为他们自己的 staker 的 operators。
deduped_earners AS (
  SELECT
    earner,
    snapshot,
    reward_hash,
    token,
    SUM(amount) as amount
  FROM combined_rewards
  GROUP BY
    earner,
    snapshot,
    reward_hash,
    token
)
SELECT *
FROM deduped_earners

对给定 reward_hashsnapshot 的具有多行的 earners 的余额进行求和。 此步骤处理了 operators 也委托给自己的情况。

16. Gold Table Merge

之前的查询都是视图。 此步骤从 步骤 7 中选择行,并将它们附加到表中。

SELECT
    earner,
    snapshot,
    reward_hash,
    token,
    amount
FROM {{ ref('gold_staging') }}

此表通过 merge incremental_strategy['reward_hash', 'earner', 'snapshot'] 的唯一键上合并。 有关更多信息,请参见 dbt 文档

最后,Rewards Root Updater 将查询此表以获取累积金额,merkelize 以下 earner, token, cumulative_amount 列。

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

0 条评论

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