From 74941e1af2ba979de1b7cedb16dcd4b302e28dfc Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Tue, 3 Dec 2024 19:17:59 -0500 Subject: [PATCH 1/2] feat: operator-directed rewards calculation --- .gitignore | 5 +- .testdataVersion | 2 +- internal/config/config.go | 2 +- .../tests/testdata/avsOperators/README.md | 22 +- .../tests/testdata/combinedRewards/README.md | 20 ++ .../tests/testdata/fetchExpectedResults.sh | 14 +- .../README.md | 28 +++ ...prodRewardsV2_generateExpectedResults.sql} | 10 +- .../operatorAvsSplitSnapshots/README.md | 18 ++ .../README.md | 49 ++++ .../operatorPISplitSnapshots/README.md | 17 ++ .../operatorRestakedStrategies/README.md | 15 ++ ...prodRewardsV2_generateExpectedResults.sql} | 8 +- .../testdata/operatorShareSnapshots/README.md | 9 + ...eprodRewardsV2_generateExpectedResults.sql | 5 + .../tests/testdata/operatorShares/README.md | 24 ++ ...eprodRewardsV2_generateExpectedResults.sql | 22 ++ .../stakerDelegationSnapshots/README.md | 16 ++ ...eprodRewardsV2_generateExpectedResults.sql | 47 ++++ .../testdata/stakerShareSnapshots/README.md | 17 ++ ...eprodRewardsV2_generateExpectedResults.sql | 6 + .../tests/testdata/stakerShares/README.md | 40 ++++ ...eprodRewardsV2_generateExpectedResults.sql | 37 +++ internal/tests/utils.go | 23 ++ pkg/contractStore/coreContracts/preprod.json | 10 + .../operatorPISplits/operatorPISplits.go | 36 +-- .../operatorPISplits/operatorPISplits_test.go | 12 +- .../up.go | 29 +++ pkg/postgres/migrations/migrator.go | 5 +- pkg/rewards/10_goldAvsODRewardAmounts.go | 91 ++++++++ .../{7_goldStaging.go => 11_goldStaging.go} | 62 ++++- .../{8_goldFinal.go => 12_goldFinal.go} | 11 +- pkg/rewards/7_goldActiveODRewards.go | 159 +++++++++++++ pkg/rewards/8_goldOperatorODRewardAmounts.go | 92 ++++++++ pkg/rewards/9_goldStakerODRewardAmounts.go | 141 ++++++++++++ pkg/rewards/operatorAvsSplitSnapshots.go | 47 ++-- pkg/rewards/operatorAvsSplitSnapshots_test.go | 129 +++++++++++ pkg/rewards/operatorDirectedRewards.go | 73 ++++++ pkg/rewards/operatorDirectedRewards_test.go | 118 ++++++++++ pkg/rewards/operatorPISplitSnapshots.go | 53 +++-- pkg/rewards/operatorPISplitSnapshots_test.go | 129 +++++++++++ pkg/rewards/operatorShares_test.go | 2 +- pkg/rewards/rewards.go | 37 ++- pkg/rewards/rewardsV2_test.go | 212 ++++++++++++++++++ pkg/rewards/rewards_test.go | 99 +++++++- pkg/rewards/stakerShareSnapshots_test.go | 2 +- pkg/rewards/stakerShares_test.go | 2 +- pkg/rewards/tables.go | 18 ++ pkg/rewardsUtils/rewardsUtils.go | 43 ++-- scripts/updateTestData.sh | 3 + 50 files changed, 1948 insertions(+), 123 deletions(-) rename internal/tests/testdata/operatorAvsRegistrationSnapshots/{generateExpectedResults.sql => preprodRewardsV2_generateExpectedResults.sql} (89%) create mode 100644 internal/tests/testdata/operatorAvsSplitSnapshots/README.md create mode 100644 internal/tests/testdata/operatorDirectedRewardSubmissions/README.md create mode 100644 internal/tests/testdata/operatorPISplitSnapshots/README.md rename internal/tests/testdata/operatorRestakedStrategies/{generateExpectedResults.sql => preprodRewardsV2_generateExpectedResults.sql} (94%) create mode 100644 internal/tests/testdata/operatorShareSnapshots/preprodRewardsV2_generateExpectedResults.sql create mode 100644 internal/tests/testdata/operatorShares/preprodRewardsV2_generateExpectedResults.sql create mode 100644 internal/tests/testdata/stakerDelegationSnapshots/preprodRewardsV2_generateExpectedResults.sql create mode 100644 internal/tests/testdata/stakerShareSnapshots/preprodRewardsV2_generateExpectedResults.sql create mode 100644 internal/tests/testdata/stakerShares/preprodRewardsV2_generateExpectedResults.sql create mode 100644 pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields/up.go create mode 100644 pkg/rewards/10_goldAvsODRewardAmounts.go rename pkg/rewards/{7_goldStaging.go => 11_goldStaging.go} (60%) rename pkg/rewards/{8_goldFinal.go => 12_goldFinal.go} (80%) create mode 100644 pkg/rewards/7_goldActiveODRewards.go create mode 100644 pkg/rewards/8_goldOperatorODRewardAmounts.go create mode 100644 pkg/rewards/9_goldStakerODRewardAmounts.go create mode 100644 pkg/rewards/operatorAvsSplitSnapshots_test.go create mode 100644 pkg/rewards/operatorDirectedRewards.go create mode 100644 pkg/rewards/operatorDirectedRewards_test.go create mode 100644 pkg/rewards/operatorPISplitSnapshots_test.go create mode 100644 pkg/rewards/rewardsV2_test.go diff --git a/.gitignore b/.gitignore index 1d3760f9..c367e7a3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /*.sw* *.terraform *.terraform* +*.sql +*.dump /bin /scripts/runLocal sidecar.db* @@ -30,5 +32,4 @@ node_modules chart_releases /snapshots/**/*.sql /snapshots/**/*.csv -*.sql -*.dump + diff --git a/.testdataVersion b/.testdataVersion index 06514b7d..193e406f 100644 --- a/.testdataVersion +++ b/.testdataVersion @@ -1 +1 @@ -e94b8e364dfddd0743746f089f89a3d570751f03 \ No newline at end of file +d67ef5d895bdc0ccd6a006a1683ac3e58f820ad0 \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index f9efde0b..a6763ab1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -261,7 +261,7 @@ func (c *Config) GetForkDates() (ForkMap, error) { Fork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled Fork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog Fork_Panama: "2024-10-01", - Fork_Arno: "2024-12-10", + Fork_Arno: "2024-12-12", }, nil case Chain_Mainnet: return ForkMap{ diff --git a/internal/tests/testdata/avsOperators/README.md b/internal/tests/testdata/avsOperators/README.md index 1c17c83b..83865459 100644 --- a/internal/tests/testdata/avsOperators/README.md +++ b/internal/tests/testdata/avsOperators/README.md @@ -16,4 +16,24 @@ where address = '0x135dda560e946695d6f155dacafc6f1f25c1f5af' and event_name = 'OperatorAVSRegistrationStatusUpdated' and block_number < 20613003 -`` +``` + +## preprod rewardsv2 + + +```sql +select + transaction_hash, + transaction_index, + block_number, + address, + arguments, + event_name, + log_index, + output_data +from transaction_logs +where + address = '0x141d6995556135d4997b2ff72eb443be300353bc' + and event_name = 'OperatorAVSRegistrationStatusUpdated' + and block_number < 2909490 +``` diff --git a/internal/tests/testdata/combinedRewards/README.md b/internal/tests/testdata/combinedRewards/README.md index aeeebba8..a06edf0f 100644 --- a/internal/tests/testdata/combinedRewards/README.md +++ b/internal/tests/testdata/combinedRewards/README.md @@ -69,3 +69,23 @@ select from dbt_mainnet_ethereum_rewards.rewards_combined where block_time < '2024-08-20' ``` + +## preprod rewardsv2 + +```sql +select + avs, + reward_hash, + token, + amount as amount, + strategy, + strategy_index, + multiplier as multiplier, + start_timestamp::timestamp(6) as start_timestamp, + end_timestamp::timestamp(6) as end_timestamp, + reward_type, + duration, + block_number as block_number +from dbt_preprod_holesky_rewards.rewards_combined +where block_time < '2024-12-10' +``` diff --git a/internal/tests/testdata/fetchExpectedResults.sh b/internal/tests/testdata/fetchExpectedResults.sh index b27adea7..6a677347 100755 --- a/internal/tests/testdata/fetchExpectedResults.sh +++ b/internal/tests/testdata/fetchExpectedResults.sh @@ -5,6 +5,8 @@ NETWORK=$1 sqlFileName="generateExpectedResults.sql" outputFile="expectedResults.csv" +postgresPort=5432 + if [[ -z $NETWORK ]]; then echo "Usage: $0 " exit 1 @@ -12,13 +14,19 @@ fi if [[ $NETWORK == "mainnet-reduced" ]]; then sqlFileName="mainnetReduced_${sqlFileName}" + postgresPort=5434 fi if [[ $NETWORK == "testnet-reduced" ]]; then sqlFileName="testnetReduced_${sqlFileName}" fi -for d in operatorShares; do +if [[ $NETWORK == "preprod-rewardsV2" ]]; then + sqlFileName="preprodRewardsV2_${sqlFileName}" + postgresPort=5435 +fi + +for d in stakerShareSnapshots; do echo "Processing directory: $d" if [[ $d == "7_goldStaging" ]]; then files=$(ls "./${d}" | grep "_generateExpectedResults_") @@ -30,12 +38,12 @@ for d in operatorShares; do sqlFileWithPath="${d}/$f" outputFileWithPath="${d}/expectedResults_${snapshotDate}.csv" echo "Generating expected results for ${sqlFileWithPath} to ${outputFileWithPath}" - psql --host localhost --port 5434 --user blocklake --dbname blocklake --password < $sqlFileWithPath > $outputFileWithPath + psql --host localhost --port $postgresPort --user blocklake --dbname blocklake --password < $sqlFileWithPath > $outputFileWithPath done else echo "Generating expected results for $d" sqlFileWithPath="${d}/${sqlFileName}" outputFileWithPath="${d}/${outputFile}" - psql --host localhost --port 5434 --user blocklake --dbname blocklake --password < $sqlFileWithPath > $outputFileWithPath + psql --host localhost --port $postgresPort --user blocklake --dbname blocklake --password < $sqlFileWithPath > $outputFileWithPath fi done diff --git a/internal/tests/testdata/operatorAvsRegistrationSnapshots/README.md b/internal/tests/testdata/operatorAvsRegistrationSnapshots/README.md index 956797ca..30a4f29d 100644 --- a/internal/tests/testdata/operatorAvsRegistrationSnapshots/README.md +++ b/internal/tests/testdata/operatorAvsRegistrationSnapshots/README.md @@ -81,6 +81,34 @@ select from filtered ``` +preprod rewardsv2 + +```sql +with filtered as ( + SELECT + lower(t.arguments #>> '{0,Value}') as operator, + lower(t.arguments #>> '{1,Value}') as avs, + case when (t.output_data ->> 'status')::integer = 1 then true else false end as status, + 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 +WHERE t.address = '0x141d6995556135d4997b2ff72eb443be300353bc' + AND t.event_name = 'OperatorAVSRegistrationStatusUpdated' + AND date_trunc('day', b.block_time) < TIMESTAMP '2024-12-10' + ) +select + operator, + avs, + status as registered, + log_index, + block_number +from filtered +``` + ## Expected results _See `generateExpectedResults.sql`_ diff --git a/internal/tests/testdata/operatorAvsRegistrationSnapshots/generateExpectedResults.sql b/internal/tests/testdata/operatorAvsRegistrationSnapshots/preprodRewardsV2_generateExpectedResults.sql similarity index 89% rename from internal/tests/testdata/operatorAvsRegistrationSnapshots/generateExpectedResults.sql rename to internal/tests/testdata/operatorAvsRegistrationSnapshots/preprodRewardsV2_generateExpectedResults.sql index 3a338ea1..33ded9bd 100644 --- a/internal/tests/testdata/operatorAvsRegistrationSnapshots/generateExpectedResults.sql +++ b/internal/tests/testdata/operatorAvsRegistrationSnapshots/preprodRewardsV2_generateExpectedResults.sql @@ -1,7 +1,7 @@ COPY ( with filtered as ( - select * from dbt_testnet_holesky_rewards.operator_avs_status - where block_time < '2024-09-17' + select * from dbt_preprod_holesky_rewards.operator_avs_status + where block_time < '2024-12-11' ), marked_statuses AS ( SELECT @@ -34,7 +34,7 @@ marked_statuses AS ( operator, avs, block_time AS start_time, - COALESCE(next_block_time, TIMESTAMP '2024-09-01') AS end_time, + COALESCE(next_block_time, TIMESTAMP '2024-12-11') AS end_time, registered FROM removed_same_day_deregistrations WHERE registered = TRUE @@ -59,9 +59,9 @@ marked_statuses AS ( SELECT operator, avs, - day AS snapshot + to_char(d, 'YYYY-MM-DD') AS snapshot FROM cleaned_records - CROSS JOIN generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day + CROSS JOIN generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS d ) select * from final_results ) TO STDOUT WITH DELIMITER ',' CSV HEADER; diff --git a/internal/tests/testdata/operatorAvsSplitSnapshots/README.md b/internal/tests/testdata/operatorAvsSplitSnapshots/README.md new file mode 100644 index 00000000..013b0b4e --- /dev/null +++ b/internal/tests/testdata/operatorAvsSplitSnapshots/README.md @@ -0,0 +1,18 @@ +## preprod rewardsv2 + +```sql +select + lower(arguments #>> '{1, Value}') as operator, + lower(arguments #>> '{2, Value}') as avs, + to_timestamp((output_data ->> 'activatedAt')::integer)::timestamp(6) as activated_at, + output_data ->> 'oldOperatorAVSSplitBips' as old_operator_avs_split_bips, + output_data ->> 'newOperatorAVSSplitBips' as new_operator_avs_split_bips, + block_number, + transaction_hash, + log_index +from transaction_logs +where + address = '0xb22ef643e1e067c994019a4c19e403253c05c2b0' + and event_name = 'OperatorAVSSplitBipsSet' +order by block_number desc +``` diff --git a/internal/tests/testdata/operatorDirectedRewardSubmissions/README.md b/internal/tests/testdata/operatorDirectedRewardSubmissions/README.md new file mode 100644 index 00000000..7d549696 --- /dev/null +++ b/internal/tests/testdata/operatorDirectedRewardSubmissions/README.md @@ -0,0 +1,49 @@ +## preprod rewards-v2 + +```sql +WITH strategies AS ( + SELECT + tl.*, + output_data->'operatorDirectedRewardsSubmission'->>'token' as token, + output_data->'operatorDirectedRewardsSubmission'->>'duration' as duration, + output_data->'operatorDirectedRewardsSubmission'->>'startTimestamp' as start_timestamp, + strategy_data, + strategy_idx - 1 as strategy_idx -- Subtract 1 for 0-based indexing + FROM transaction_logs as tl, + jsonb_array_elements(output_data->'operatorDirectedRewardsSubmission'->'strategiesAndMultipliers') + WITH ORDINALITY AS t(strategy_data, strategy_idx) + where + address = '0xb22ef643e1e067c994019a4c19e403253c05c2b0' + and event_name = 'OperatorDirectedAVSRewardsSubmissionCreated' +), +operators AS ( + SELECT + operator_data, + output_data->'operatorDirectedRewardsSubmission' as rewards_submission, + operator_idx - 1 as operator_idx -- Subtract 1 to make it 0-based indexing + FROM transaction_logs, + jsonb_array_elements(output_data->'operatorDirectedRewardsSubmission'->'operatorRewards') + WITH ORDINALITY AS t(operator_data, operator_idx) + where + address = '0xb22ef643e1e067c994019a4c19e403253c05c2b0' + and event_name = 'OperatorDirectedAVSRewardsSubmissionCreated' +) +SELECT + lower(arguments #>> '{1, Value}') as avs, + lower(arguments #>> '{2, Value}') as reward_hash, + strategies.token, + operator_data->>'operator' as operator, + operator_idx as operator_index, + operator_data->>'amount' as amount, + strategy_data->>'strategy' as strategy, + strategy_idx as strategy_index, + strategy_data->>'multiplier' as multiplier, + (to_timestamp((rewards_submission->>'startTimestamp')::int))::timestamp(6) as start_timestamp, + (rewards_submission->>'duration')::int as duration, + to_timestamp((rewards_submission->>'startTimestamp')::int + (rewards_submission->>'duration')::int)::timestamp(6) as end_timestamp, + block_number, + transaction_hash, + log_index +FROM strategies +CROSS JOIN operators; +``` diff --git a/internal/tests/testdata/operatorPISplitSnapshots/README.md b/internal/tests/testdata/operatorPISplitSnapshots/README.md new file mode 100644 index 00000000..c50fa147 --- /dev/null +++ b/internal/tests/testdata/operatorPISplitSnapshots/README.md @@ -0,0 +1,17 @@ + +## preprod rewardsV2 +```sql +select + lower(arguments #>> '{1, Value}') as operator, + to_timestamp((output_data ->> 'activatedAt')::integer)::timestamp(6) as activated_at, + output_data ->> 'oldOperatorPISplitBips' as old_operator_pi_split_bips, + output_data ->> 'newOperatorPISplitBips' as new_operator_pi_split_bips, + block_number, + transaction_hash, + log_index +from transaction_logs +where + address = '0xb22ef643e1e067c994019a4c19e403253c05c2b0' + and event_name = 'OperatorPISplitBipsSet' +order by block_number desc +``` diff --git a/internal/tests/testdata/operatorRestakedStrategies/README.md b/internal/tests/testdata/operatorRestakedStrategies/README.md index 0722c631..796fc641 100644 --- a/internal/tests/testdata/operatorRestakedStrategies/README.md +++ b/internal/tests/testdata/operatorRestakedStrategies/README.md @@ -42,6 +42,21 @@ where avs_directory_address = '0x135dda560e946695d6f155dacafc6f1f25c1f5af' and block_time < '2024-08-20' ``` +preprod rewardsV2 + +```sql +select + block_number, + operator, + avs, + strategy, + block_time::timestamp(6), + avs_directory_address +from operator_restaked_strategies +where avs_directory_address = '0x141d6995556135d4997b2ff72eb443be300353bc' +and block_time < '2024-12-10' +``` + ## Expected results _See `generateExpectedResults.sql`_ diff --git a/internal/tests/testdata/operatorRestakedStrategies/generateExpectedResults.sql b/internal/tests/testdata/operatorRestakedStrategies/preprodRewardsV2_generateExpectedResults.sql similarity index 94% rename from internal/tests/testdata/operatorRestakedStrategies/generateExpectedResults.sql rename to internal/tests/testdata/operatorRestakedStrategies/preprodRewardsV2_generateExpectedResults.sql index 5236cc2d..52eceb25 100644 --- a/internal/tests/testdata/operatorRestakedStrategies/generateExpectedResults.sql +++ b/internal/tests/testdata/operatorRestakedStrategies/preprodRewardsV2_generateExpectedResults.sql @@ -10,8 +10,8 @@ copy (with ranked_records AS ( ORDER BY block_time DESC ) AS rn FROM public.operator_restaked_strategies - WHERE avs_directory_address = lower('0x055733000064333caddbc92763c58bf0192ffebf') - and block_time < '2024-09-17' + WHERE avs_directory_address = lower('0x141d6995556135d4997b2ff72eb443be300353bc') + and block_time < '2024-12-11' ), latest_records AS ( SELECT @@ -102,11 +102,11 @@ SELECT operator, avs, strategy, - cast(day AS DATE) AS snapshot + to_char(d, 'YYYY-MM-DD') AS snapshot FROM cleaned_records CROSS JOIN - generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day + generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS d ) select * from final_results ) to STDOUT DELIMITER ',' CSV HEADER; diff --git a/internal/tests/testdata/operatorShareSnapshots/README.md b/internal/tests/testdata/operatorShareSnapshots/README.md index 2b55bd90..71439e7f 100644 --- a/internal/tests/testdata/operatorShareSnapshots/README.md +++ b/internal/tests/testdata/operatorShareSnapshots/README.md @@ -32,6 +32,15 @@ from dbt_mainnet_ethereum_rewards.operator_shares where block_time < '2024-08-20' ``` +preprod-rewardsV2 + +```sql +select + * +from dbt_preprod_holesky_rewards.operator_shares +where block_time < '2024-12-10' +``` + ## Expected results _See `generateExpectedResults.sql`_ diff --git a/internal/tests/testdata/operatorShareSnapshots/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/operatorShareSnapshots/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..1f9fa0db --- /dev/null +++ b/internal/tests/testdata/operatorShareSnapshots/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,5 @@ +COPY ( +select * +FROM dbt_preprod_holesky_rewards.operator_share_snapshots +where snapshot < '2024-12-10' + ) TO STDOUT WITH DELIMITER ',' CSV HEADER diff --git a/internal/tests/testdata/operatorShares/README.md b/internal/tests/testdata/operatorShares/README.md index f37f4643..e3d05074 100644 --- a/internal/tests/testdata/operatorShares/README.md +++ b/internal/tests/testdata/operatorShares/README.md @@ -70,5 +70,29 @@ FROM ( FROM dbt_mainnet_ethereum_rewards.operator_share_decreases where block_date < '2024-08-20' ) combined_shares +``` + +### preprod-rewardsv2 +```sql +SELECT + operator, + strategy, + 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 dbt_testnet_holesky_rewards.operator_share_increases + where block_date < '2024-12-10' + + UNION ALL + + SELECT operator, strategy, shares * -1 AS shares, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_testnet_holesky_rewards.operator_share_decreases + where block_date < '2024-12-10' + ) combined_shares ``` diff --git a/internal/tests/testdata/operatorShares/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/operatorShares/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..42c38ff3 --- /dev/null +++ b/internal/tests/testdata/operatorShares/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,22 @@ +COPY ( +SELECT + 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 dbt_preprod_holesky_rewards.operator_share_increases + where block_date < '2024-12-11' + + UNION ALL + + SELECT operator, strategy, shares * -1 AS shares, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_preprod_holesky_rewards.operator_share_decreases + where block_date < '2024-12-11' + ) combined_shares + ) TO STDOUT WITH DELIMITER ',' CSV HEADER diff --git a/internal/tests/testdata/stakerDelegationSnapshots/README.md b/internal/tests/testdata/stakerDelegationSnapshots/README.md index 2ea4a454..92c095f6 100644 --- a/internal/tests/testdata/stakerDelegationSnapshots/README.md +++ b/internal/tests/testdata/stakerDelegationSnapshots/README.md @@ -48,6 +48,22 @@ FROM ( where block_time < '2024-08-20' ``` +preprod-rewardsV2 +```sql +SELECT + staker, + operator, + log_index, + block_number, + case when src = 'undelegations' THEN false ELSE true END AS delegated +FROM ( + SELECT *, 'undelegations' AS src FROM dbt_preprod_holesky_rewards.staker_undelegations + UNION ALL + SELECT *, 'delegations' AS src FROM dbt_preprod_holesky_rewards.staker_delegations + ) as delegations_combined +where block_time < '2024-12-10' +``` + ```bash psql --host localhost --port 5435 --user blocklake --dbname blocklake --password < internal/tests/testdata/stakerDelegationSnapshots/generateExpectedResults.sql > internal/tests/testdata/stakerDelegationSnapshots/expectedResults.csv diff --git a/internal/tests/testdata/stakerDelegationSnapshots/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/stakerDelegationSnapshots/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..1537e4b6 --- /dev/null +++ b/internal/tests/testdata/stakerDelegationSnapshots/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,47 @@ +COPY ( + with delegated_stakers as ( + select + * + from dbt_preprod_holesky_rewards.staker_delegation_status + where block_time < '2024-12-11' +), +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 delegated_stakers +), + 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_delegation_windows as ( + SELECT + staker, operator, snapshot_time as start_time, + CASE + -- If the range does not have the end, use the cutoff date truncated to 0 UTC + WHEN LEAD(snapshot_time) OVER (PARTITION BY staker ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '2024-12-11') + ELSE LEAD(snapshot_time) OVER (PARTITION BY staker ORDER BY snapshot_time) + END AS end_time + FROM snapshotted_records + ), +cleaned_records as ( + SELECT * FROM staker_delegation_windows + WHERE start_time < end_time +), +final_results as ( + SELECT + staker, + operator, + to_char(d, 'YYYY-MM-DD') AS snapshot +FROM + cleaned_records + CROSS JOIN + generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS d +) +select * from final_results +) TO STDOUT WITH DELIMITER ',' CSV HEADER; diff --git a/internal/tests/testdata/stakerShareSnapshots/README.md b/internal/tests/testdata/stakerShareSnapshots/README.md index 8ed7914b..5904126c 100644 --- a/internal/tests/testdata/stakerShareSnapshots/README.md +++ b/internal/tests/testdata/stakerShareSnapshots/README.md @@ -48,6 +48,23 @@ where block_time < '2024-08-20' ``` +preprod rewardsV2 + +```sql +select + staker, + strategy, + shares, + strategy_index, + transaction_hash, + log_index, + block_time, + block_date, + block_number +from dbt_preprod_holesky_rewards.staker_shares +where block_time < '2024-12-10' +``` + ## Expected results _See `generateExpectedResults.sql`_ diff --git a/internal/tests/testdata/stakerShareSnapshots/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/stakerShareSnapshots/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..3101b1d4 --- /dev/null +++ b/internal/tests/testdata/stakerShareSnapshots/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,6 @@ +COPY ( +select + * +from dbt_preprod_holesky_rewards.staker_share_snapshots +where snapshot < '2024-12-11' + ) TO STDOUT WITH DELIMITER ',' CSV HEADER diff --git a/internal/tests/testdata/stakerShares/README.md b/internal/tests/testdata/stakerShares/README.md index d6588736..cb87fa63 100644 --- a/internal/tests/testdata/stakerShares/README.md +++ b/internal/tests/testdata/stakerShares/README.md @@ -116,3 +116,43 @@ FROM ( ) combined_staker_shares ``` + +preprod-rewardsV2 + +```sql +SELECT + staker, + strategy, + 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 dbt_mainnet_ethereum_rewards.staker_deposits + where block_date < '2024-08-20' + + UNION ALL + + -- Subtract m1 & m2 withdrawals + SELECT staker, strategy, shares * -1, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_mainnet_ethereum_rewards.m1_staker_withdrawals + where block_date < '2024-08-20' + + UNION ALL + + SELECT staker, strategy, shares * -1, strategy_index, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_mainnet_ethereum_rewards.m2_staker_withdrawals + where block_date < '2024-08-20' + + UNION all + + -- Shares in eigenpod are positive or negative, so no need to multiply by -1 + SELECT staker, '0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0' as strategy, shares, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_mainnet_ethereum_rewards.eigenpod_shares + where block_date < '2024-08-20' +) combined_staker_shares +``` diff --git a/internal/tests/testdata/stakerShares/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/stakerShares/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..d24cdfc2 --- /dev/null +++ b/internal/tests/testdata/stakerShares/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,37 @@ +COPY ( +SELECT + staker, + strategy, + shares, + transaction_hash, + log_index, + SUM(shares) OVER (PARTITION BY staker, strategy ORDER BY block_time, log_index) AS shares, + 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 dbt_preprod_holesky_rewards.staker_deposits + where block_date < '2024-12-11' + + UNION ALL + + -- Subtract m1 & m2 withdrawals + SELECT staker, strategy, shares * -1, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_preprod_holesky_rewards.m1_staker_withdrawals + where block_date < '2024-12-11' + + UNION ALL + + SELECT staker, strategy, shares * -1, strategy_index, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_preprod_holesky_rewards.m2_staker_withdrawals + where block_date < '2024-12-11' + + UNION all + + -- Shares in eigenpod are positive or negative, so no need to multiply by -1 + SELECT staker, '0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0' as strategy, shares, 0 as strategy_index, transaction_hash, log_index, block_time, block_date, block_number + FROM dbt_preprod_holesky_rewards.eigenpod_shares + where block_date < '2024-12-11' + ) combined_staker_shares + ) TO STDOUT WITH DELIMITER ',' CSV HEADER diff --git a/internal/tests/utils.go b/internal/tests/utils.go index 057e97a5..b4d0ebb6 100644 --- a/internal/tests/utils.go +++ b/internal/tests/utils.go @@ -99,6 +99,11 @@ func GetAllBlocksSqlFile(projectBase string) (string, error) { return getSqlFile(path) } +func GetRewardsV2Blocks(projectBase string) (string, error) { + path := getTestdataPathFromProjectRoot(projectBase, "/rewardsV2Blocks.sql") + return getSqlFile(path) +} + func GetOperatorAvsRegistrationsSqlFile(projectBase string) (string, error) { path := getTestdataPathFromProjectRoot(projectBase, "/operatorAvsRegistrationSnapshots/operatorAvsRegistrations.sql") return getSqlFile(path) @@ -265,3 +270,21 @@ func GetStakerDelegationsTransactionLogsSqlFile(projectBase string) (string, err func LargeTestsEnabled() bool { return os.Getenv("TEST_REWARDS") == "true" || os.Getenv("TEST_LARGE") == "true" } + +// ---------------------------------------------------------------------------- +// Rewards V2 +// ---------------------------------------------------------------------------- +func GetOperatorAvsSplitsSqlFile(projectBase string) (string, error) { + path := getTestdataPathFromProjectRoot(projectBase, "/operatorAvsSplitSnapshots/operatorAvsSplits.sql") + return getSqlFile(path) +} + +func GetOperatorPISplitsSqlFile(projectBase string) (string, error) { + path := getTestdataPathFromProjectRoot(projectBase, "/operatorPISplitSnapshots/operatorPISplits.sql") + return getSqlFile(path) +} + +func GetOperatorDirectedRewardsSqlFile(projectBase string) (string, error) { + path := getTestdataPathFromProjectRoot(projectBase, "/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.sql") + return getSqlFile(path) +} diff --git a/pkg/contractStore/coreContracts/preprod.json b/pkg/contractStore/coreContracts/preprod.json index 26e30fd0..b804fd98 100644 --- a/pkg/contractStore/coreContracts/preprod.json +++ b/pkg/contractStore/coreContracts/preprod.json @@ -89,6 +89,11 @@ "contract_address": "0x1a26b23a004c512350d7dd89056655a80b850199", "contract_abi": "[{\"inputs\":[{\"internalType\":\"contract IDelegationManager\",\"name\":\"_delegation\",\"type\":\"address\"},{\"internalType\":\"contract IEigenPodManager\",\"name\":\"_eigenPodManager\",\"type\":\"address\"},{\"internalType\":\"contract ISlasher\",\"name\":\"_slasher\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IPauserRegistry\",\"name\":\"pauserRegistry\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IPauserRegistry\",\"name\":\"newPauserRegistry\",\"type\":\"address\"}],\"name\":\"PauserRegistrySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"}],\"name\":\"StrategyAddedToDepositWhitelist\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"}],\"name\":\"StrategyRemovedFromDepositWhitelist\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAddress\",\"type\":\"address\"}],\"name\":\"StrategyWhitelisterChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"value\",\"type\":\"bool\"}],\"name\":\"UpdatedThirdPartyTransfersForbidden\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEPOSIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"name\":\"addShares\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy[]\",\"name\":\"strategiesToWhitelist\",\"type\":\"address[]\"},{\"internalType\":\"bool[]\",\"name\":\"thirdPartyTransfersForbiddenValues\",\"type\":\"bool[]\"}],\"name\":\"addStrategiesToDepositWhitelist\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"contract IStrategy[]\",\"name\":\"strategies\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"shares\",\"type\":\"uint256[]\"},{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"withdrawer\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"nonce\",\"type\":\"uint96\"}],\"internalType\":\"struct IStrategyManager.DeprecatedStruct_WithdrawerAndNonce\",\"name\":\"withdrawerAndNonce\",\"type\":\"tuple\"},{\"internalType\":\"uint32\",\"name\":\"withdrawalStartBlock\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"delegatedAddress\",\"type\":\"address\"}],\"internalType\":\"struct IStrategyManager.DeprecatedStruct_QueuedWithdrawal\",\"name\":\"queuedWithdrawal\",\"type\":\"tuple\"}],\"name\":\"calculateWithdrawalRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"delegation\",\"outputs\":[{\"internalType\":\"contract IDelegationManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"depositIntoStrategy\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"expiry\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"depositIntoStrategyWithSignature\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eigenPodManager\",\"outputs\":[{\"internalType\":\"contract IEigenPodManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"}],\"name\":\"getDeposits\",\"outputs\":[{\"internalType\":\"contract IStrategy[]\",\"name\":\"\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialStrategyWhitelister\",\"type\":\"address\"},{\"internalType\":\"contract IPauserRegistry\",\"name\":\"_pauserRegistry\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialPausedStatus\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"contract IStrategy[]\",\"name\":\"strategies\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"shares\",\"type\":\"uint256[]\"},{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"withdrawer\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"nonce\",\"type\":\"uint96\"}],\"internalType\":\"struct IStrategyManager.DeprecatedStruct_WithdrawerAndNonce\",\"name\":\"withdrawerAndNonce\",\"type\":\"tuple\"},{\"internalType\":\"uint32\",\"name\":\"withdrawalStartBlock\",\"type\":\"uint32\"},{\"internalType\":\"address\",\"name\":\"delegatedAddress\",\"type\":\"address\"}],\"internalType\":\"struct IStrategyManager.DeprecatedStruct_QueuedWithdrawal\",\"name\":\"queuedWithdrawal\",\"type\":\"tuple\"}],\"name\":\"migrateQueuedWithdrawal\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauseAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"index\",\"type\":\"uint8\"}],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauserRegistry\",\"outputs\":[{\"internalType\":\"contract IPauserRegistry\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"name\":\"removeShares\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy[]\",\"name\":\"strategiesToRemoveFromWhitelist\",\"type\":\"address[]\"}],\"name\":\"removeStrategiesFromDepositWhitelist\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IPauserRegistry\",\"name\":\"newPauserRegistry\",\"type\":\"address\"}],\"name\":\"setPauserRegistry\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newStrategyWhitelister\",\"type\":\"address\"}],\"name\":\"setStrategyWhitelister\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"value\",\"type\":\"bool\"}],\"name\":\"setThirdPartyTransfersForbidden\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slasher\",\"outputs\":[{\"internalType\":\"contract ISlasher\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"stakerStrategyList\",\"outputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"}],\"name\":\"stakerStrategyListLength\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract IStrategy\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"stakerStrategyShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"strategyIsWhitelistedForDeposit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"strategyWhitelister\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"thirdPartyTransfersForbidden\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"withdrawSharesAsTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"withdrawalRootPending\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", "bytecode_hash": "0b5a653cdc78e427edf32a89325e3d45e47e4c1ce4ffd151a6788f743a747a08" + }, + { + "contract_address": "0x096694a4c8c2c13a005a200309d64995c08ed065", + "contract_abi": "[{\"inputs\":[{\"internalType\":\"contract IDelegationManager\",\"name\":\"_delegationManager\",\"type\":\"address\"},{\"internalType\":\"contract IStrategyManager\",\"name\":\"_strategyManager\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_CALCULATION_INTERVAL_SECONDS\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_MAX_REWARDS_DURATION\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_MAX_RETROACTIVE_LENGTH\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_MAX_FUTURE_LENGTH\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"__GENESIS_REWARDS_TIMESTAMP\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"avs\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"submissionNonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"rewardsSubmissionHash\",\"type\":\"bytes32\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"struct IRewardsCoordinator.RewardsSubmission\",\"name\":\"rewardsSubmission\",\"type\":\"tuple\"}],\"name\":\"AVSRewardsSubmissionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"oldActivationDelay\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newActivationDelay\",\"type\":\"uint32\"}],\"name\":\"ActivationDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"earner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldClaimer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"}],\"name\":\"ClaimerForSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"oldDefaultOperatorSplitBips\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"newDefaultOperatorSplitBips\",\"type\":\"uint16\"}],\"name\":\"DefaultOperatorSplitBipsSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rootIndex\",\"type\":\"uint32\"}],\"name\":\"DistributionRootDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rootIndex\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"rewardsCalculationEndTimestamp\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"activatedAt\",\"type\":\"uint32\"}],\"name\":\"DistributionRootSubmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"avs\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"activatedAt\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"oldOperatorAVSSplitBips\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"newOperatorAVSSplitBips\",\"type\":\"uint16\"}],\"name\":\"OperatorAVSSplitBipsSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"avs\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"operatorDirectedRewardsSubmissionHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"submissionNonce\",\"type\":\"uint256\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"struct IRewardsCoordinator.OperatorReward[]\",\"name\":\"operatorRewards\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"indexed\":false,\"internalType\":\"struct IRewardsCoordinator.OperatorDirectedRewardsSubmission\",\"name\":\"operatorDirectedRewardsSubmission\",\"type\":\"tuple\"}],\"name\":\"OperatorDirectedAVSRewardsSubmissionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"activatedAt\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"oldOperatorPISplitBips\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"newOperatorPISplitBips\",\"type\":\"uint16\"}],\"name\":\"OperatorPISplitBipsSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IPauserRegistry\",\"name\":\"pauserRegistry\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IPauserRegistry\",\"name\":\"newPauserRegistry\",\"type\":\"address\"}],\"name\":\"PauserRegistrySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"earner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"claimedAmount\",\"type\":\"uint256\"}],\"name\":\"RewardsClaimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"rewardsForAllSubmitter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"oldValue\",\"type\":\"bool\"},{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"newValue\",\"type\":\"bool\"}],\"name\":\"RewardsForAllSubmitterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"submitter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"submissionNonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"rewardsSubmissionHash\",\"type\":\"bytes32\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"struct IRewardsCoordinator.RewardsSubmission\",\"name\":\"rewardsSubmission\",\"type\":\"tuple\"}],\"name\":\"RewardsSubmissionForAllCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"tokenHopper\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"submissionNonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"rewardsSubmissionHash\",\"type\":\"bytes32\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"struct IRewardsCoordinator.RewardsSubmission\",\"name\":\"rewardsSubmission\",\"type\":\"tuple\"}],\"name\":\"RewardsSubmissionForAllEarnersCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldRewardsUpdater\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newRewardsUpdater\",\"type\":\"address\"}],\"name\":\"RewardsUpdaterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"CALCULATION_INTERVAL_SECONDS\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GENESIS_REWARDS_TIMESTAMP\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_FUTURE_LENGTH\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_RETROACTIVE_LENGTH\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_REWARDS_DURATION\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"activationDelay\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"beaconChainETHStrategy\",\"outputs\":[{\"internalType\":\"contract IStrategy\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"earner\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"earnerTokenRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct IRewardsCoordinator.EarnerTreeMerkleLeaf\",\"name\":\"leaf\",\"type\":\"tuple\"}],\"name\":\"calculateEarnerLeafHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"cumulativeEarnings\",\"type\":\"uint256\"}],\"internalType\":\"struct IRewardsCoordinator.TokenTreeMerkleLeaf\",\"name\":\"leaf\",\"type\":\"tuple\"}],\"name\":\"calculateTokenLeafHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"rootIndex\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"earnerIndex\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"earnerTreeProof\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"earner\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"earnerTokenRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct IRewardsCoordinator.EarnerTreeMerkleLeaf\",\"name\":\"earnerLeaf\",\"type\":\"tuple\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenIndices\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"tokenTreeProofs\",\"type\":\"bytes[]\"},{\"components\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"cumulativeEarnings\",\"type\":\"uint256\"}],\"internalType\":\"struct IRewardsCoordinator.TokenTreeMerkleLeaf[]\",\"name\":\"tokenLeaves\",\"type\":\"tuple[]\"}],\"internalType\":\"struct IRewardsCoordinator.RewardsMerkleClaim\",\"name\":\"claim\",\"type\":\"tuple\"}],\"name\":\"checkClaim\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"claimerFor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"}],\"internalType\":\"struct IRewardsCoordinator.RewardsSubmission[]\",\"name\":\"rewardsSubmissions\",\"type\":\"tuple[]\"}],\"name\":\"createAVSRewardsSubmission\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"avs\",\"type\":\"address\"},{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"struct IRewardsCoordinator.OperatorReward[]\",\"name\":\"operatorRewards\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"internalType\":\"struct IRewardsCoordinator.OperatorDirectedRewardsSubmission[]\",\"name\":\"operatorDirectedRewardsSubmissions\",\"type\":\"tuple[]\"}],\"name\":\"createOperatorDirectedAVSRewardsSubmission\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"}],\"internalType\":\"struct IRewardsCoordinator.RewardsSubmission[]\",\"name\":\"rewardsSubmissions\",\"type\":\"tuple[]\"}],\"name\":\"createRewardsForAllEarners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"contract IStrategy\",\"name\":\"strategy\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"multiplier\",\"type\":\"uint96\"}],\"internalType\":\"struct IRewardsCoordinator.StrategyAndMultiplier[]\",\"name\":\"strategiesAndMultipliers\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"startTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"duration\",\"type\":\"uint32\"}],\"internalType\":\"struct IRewardsCoordinator.RewardsSubmission[]\",\"name\":\"rewardsSubmissions\",\"type\":\"tuple[]\"}],\"name\":\"createRewardsForAllSubmission\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"cumulativeClaimed\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currRewardsCalculationEndTimestamp\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"defaultOperatorSplitBips\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"delegationManager\",\"outputs\":[{\"internalType\":\"contract IDelegationManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"rootIndex\",\"type\":\"uint32\"}],\"name\":\"disableRoot\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentClaimableDistributionRoot\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"rewardsCalculationEndTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"activatedAt\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"disabled\",\"type\":\"bool\"}],\"internalType\":\"struct IRewardsCoordinator.DistributionRoot\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentDistributionRoot\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"rewardsCalculationEndTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"activatedAt\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"disabled\",\"type\":\"bool\"}],\"internalType\":\"struct IRewardsCoordinator.DistributionRoot\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getDistributionRootAtIndex\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"rewardsCalculationEndTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"activatedAt\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"disabled\",\"type\":\"bool\"}],\"internalType\":\"struct IRewardsCoordinator.DistributionRoot\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDistributionRootsLength\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"avs\",\"type\":\"address\"}],\"name\":\"getOperatorAVSSplit\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"getOperatorPISplit\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"rootHash\",\"type\":\"bytes32\"}],\"name\":\"getRootIndexFromHash\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"},{\"internalType\":\"contract IPauserRegistry\",\"name\":\"_pauserRegistry\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialPausedStatus\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_rewardsUpdater\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_activationDelay\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"_defaultSplitBips\",\"type\":\"uint16\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"isAVSRewardsSubmissionHash\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"isOperatorDirectedAVSRewardsSubmissionHash\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isRewardsForAllSubmitter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"isRewardsSubmissionForAllEarnersHash\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"isRewardsSubmissionForAllHash\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauseAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"index\",\"type\":\"uint8\"}],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauserRegistry\",\"outputs\":[{\"internalType\":\"contract IPauserRegistry\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"rootIndex\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"earnerIndex\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"earnerTreeProof\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"earner\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"earnerTokenRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct IRewardsCoordinator.EarnerTreeMerkleLeaf\",\"name\":\"earnerLeaf\",\"type\":\"tuple\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenIndices\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"tokenTreeProofs\",\"type\":\"bytes[]\"},{\"components\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"cumulativeEarnings\",\"type\":\"uint256\"}],\"internalType\":\"struct IRewardsCoordinator.TokenTreeMerkleLeaf[]\",\"name\":\"tokenLeaves\",\"type\":\"tuple[]\"}],\"internalType\":\"struct IRewardsCoordinator.RewardsMerkleClaim\",\"name\":\"claim\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"processClaim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"rootIndex\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"earnerIndex\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"earnerTreeProof\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"earner\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"earnerTokenRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct IRewardsCoordinator.EarnerTreeMerkleLeaf\",\"name\":\"earnerLeaf\",\"type\":\"tuple\"},{\"internalType\":\"uint32[]\",\"name\":\"tokenIndices\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"tokenTreeProofs\",\"type\":\"bytes[]\"},{\"components\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"cumulativeEarnings\",\"type\":\"uint256\"}],\"internalType\":\"struct IRewardsCoordinator.TokenTreeMerkleLeaf[]\",\"name\":\"tokenLeaves\",\"type\":\"tuple[]\"}],\"internalType\":\"struct IRewardsCoordinator.RewardsMerkleClaim[]\",\"name\":\"claims\",\"type\":\"tuple[]\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"name\":\"processClaims\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rewardsUpdater\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_activationDelay\",\"type\":\"uint32\"}],\"name\":\"setActivationDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"}],\"name\":\"setClaimerFor\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"split\",\"type\":\"uint16\"}],\"name\":\"setDefaultOperatorSplit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"avs\",\"type\":\"address\"},{\"internalType\":\"uint16\",\"name\":\"split\",\"type\":\"uint16\"}],\"name\":\"setOperatorAVSSplit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"uint16\",\"name\":\"split\",\"type\":\"uint16\"}],\"name\":\"setOperatorPISplit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IPauserRegistry\",\"name\":\"newPauserRegistry\",\"type\":\"address\"}],\"name\":\"setPauserRegistry\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_submitter\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"_newValue\",\"type\":\"bool\"}],\"name\":\"setRewardsForAllSubmitter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_rewardsUpdater\",\"type\":\"address\"}],\"name\":\"setRewardsUpdater\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"strategyManager\",\"outputs\":[{\"internalType\":\"contract IStrategyManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"submissionNonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"rewardsCalculationEndTimestamp\",\"type\":\"uint32\"}],\"name\":\"submitRoot\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newPausedStatus\",\"type\":\"uint256\"}],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "bytecode_hash": "960dde4fe771e141992669bc5db4819a55cc5b4641ca509c7dac787e5e095237" } ], "proxy_contracts": [ @@ -171,6 +176,11 @@ "contract_address": "0xb22ef643e1e067c994019a4c19e403253c05c2b0", "proxy_contract_address": "0x7523b42b081c30fa245aa4039c645e36746fc400", "block_number": 2282920 + }, + { + "contract_address": "0xb22ef643e1e067c994019a4c19e403253c05c2b0", + "proxy_contract_address": "0x096694a4c8c2c13a005a200309d64995c08ed065", + "block_number": 2871534 } ] } diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits.go b/pkg/eigenState/operatorPISplits/operatorPISplits.go index da147662..624d0982 100644 --- a/pkg/eigenState/operatorPISplits/operatorPISplits.go +++ b/pkg/eigenState/operatorPISplits/operatorPISplits.go @@ -20,13 +20,13 @@ import ( ) type OperatorPISplit struct { - Operator string - ActivatedAt *time.Time - OldOperatorAVSSplitBips uint64 - NewOperatorAVSSplitBips uint64 - BlockNumber uint64 - TransactionHash string - LogIndex uint64 + Operator string + ActivatedAt *time.Time + OldOperatorPISplitBips uint64 + NewOperatorPISplitBips uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 } type OperatorPISplitModel struct { @@ -67,9 +67,9 @@ func (ops *OperatorPISplitModel) GetModelName() string { } type operatorPISplitOutputData struct { - ActivatedAt uint64 `json:"activatedAt"` - OldOperatorAVSSplitBips uint64 `json:"oldOperatorAVSSplitBips"` - NewOperatorAVSSplitBips uint64 `json:"newOperatorAVSSplitBips"` + ActivatedAt uint64 `json:"activatedAt"` + OldOperatorPISplitBips uint64 `json:"oldOperatorPISplitBips"` + NewOperatorPISplitBips uint64 `json:"newOperatorPISplitBips"` } func parseOperatorPISplitOutputData(outputDataStr string) (*operatorPISplitOutputData, error) { @@ -99,13 +99,13 @@ func (ops *OperatorPISplitModel) handleOperatorPISplitBipsSetEvent(log *storage. activatedAt := time.Unix(int64(outputData.ActivatedAt), 0) split := &OperatorPISplit{ - Operator: strings.ToLower(arguments[1].Value.(string)), - ActivatedAt: &activatedAt, - OldOperatorAVSSplitBips: outputData.OldOperatorAVSSplitBips, - NewOperatorAVSSplitBips: outputData.NewOperatorAVSSplitBips, - BlockNumber: log.BlockNumber, - TransactionHash: log.TransactionHash, - LogIndex: log.LogIndex, + Operator: strings.ToLower(arguments[1].Value.(string)), + ActivatedAt: &activatedAt, + OldOperatorPISplitBips: outputData.OldOperatorPISplitBips, + NewOperatorPISplitBips: outputData.NewOperatorPISplitBips, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, } return split, nil @@ -255,7 +255,7 @@ func (ops *OperatorPISplitModel) sortValuesForMerkleTree(splits []*OperatorPISpl inputs := make([]*base.MerkleTreeInput, 0) for _, split := range splits { slotID := base.NewSlotID(split.TransactionHash, split.LogIndex) - value := fmt.Sprintf("%s_%d_%d_%d", split.Operator, split.ActivatedAt.Unix(), split.OldOperatorAVSSplitBips, split.NewOperatorAVSSplitBips) + value := fmt.Sprintf("%s_%d_%d_%d", split.Operator, split.ActivatedAt.Unix(), split.OldOperatorPISplitBips, split.NewOperatorPISplitBips) inputs = append(inputs, &base.MerkleTreeInput{ SlotID: slotID, Value: []byte(value), diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits_test.go b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go index acd1de4a..9bc12b07 100644 --- a/pkg/eigenState/operatorPISplits/operatorPISplits_test.go +++ b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go @@ -89,10 +89,10 @@ func Test_OperatorPISplit(t *testing.T) { TransactionIndex: big.NewInt(100).Uint64(), BlockNumber: blockNumber, Address: cfg.GetContractsMapForChain().RewardsCoordinator, - Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "operator", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "activatedAt", "Type": "uint32", "Value": 1725494400, "Indexed": false}, {"Name": "oldOperatorAVSSplitBips", "Type": "uint16", "Value": 1000, "Indexed": false}, {"Name": "newOperatorAVSSplitBips", "Type": "uint16", "Value": 2000, "Indexed": false}]`, + Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xcf4f3453828f09f5b526101b81d0199d2de39ec5", "Indexed": true}, {"Name": "operator", "Type": "address", "Value": "0xcf4f3453828f09f5b526101b81d0199d2de39ec5", "Indexed": true}, {"Name": "activatedAt", "Type": "uint32", "Value": null, "Indexed": false}, {"Name": "oldOperatorPISplitBips", "Type": "uint16", "Value": null, "Indexed": false}, {"Name": "newOperatorPISplitBips", "Type": "uint16", "Value": null, "Indexed": false}]`, EventName: "OperatorPISplitBipsSet", LogIndex: big.NewInt(12).Uint64(), - OutputData: `{"activatedAt": 1725494400, "oldOperatorAVSSplitBips": 1000, "newOperatorAVSSplitBips": 2000}`, + OutputData: `{"activatedAt": 1733341104, "newOperatorPISplitBips": 6545, "oldOperatorPISplitBips": 1000}`, } err = model.SetupStateForBlock(blockNumber) @@ -107,10 +107,10 @@ func Test_OperatorPISplit(t *testing.T) { split := change.(*OperatorPISplit) - assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(split.Operator)) - assert.Equal(t, int64(1725494400), split.ActivatedAt.Unix()) - assert.Equal(t, uint64(1000), split.OldOperatorAVSSplitBips) - assert.Equal(t, uint64(2000), split.NewOperatorAVSSplitBips) + assert.Equal(t, strings.ToLower("0xcf4f3453828f09f5b526101b81d0199d2de39ec5"), strings.ToLower(split.Operator)) + assert.Equal(t, int64(1733341104), split.ActivatedAt.Unix()) + assert.Equal(t, uint64(6545), split.NewOperatorPISplitBips) + assert.Equal(t, uint64(1000), split.OldOperatorPISplitBips) err = model.CommitFinalState(blockNumber) assert.Nil(t, err) diff --git a/pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields/up.go b/pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields/up.go new file mode 100644 index 00000000..4275d606 --- /dev/null +++ b/pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields/up.go @@ -0,0 +1,29 @@ +package _202412091100_fixOperatorPiSplitsFields + +import ( + "database/sql" + "github.com/Layr-Labs/sidecar/internal/config" + "gorm.io/gorm" +) + +type Migration struct { +} + +func (m *Migration) Up(db *sql.DB, grm *gorm.DB, cfg *config.Config) error { + queries := []string{ + `alter table operator_pi_splits rename column old_operator_avs_split_bips to old_operator_pi_split_bips`, + `alter table operator_pi_splits rename column new_operator_avs_split_bips to new_operator_pi_split_bips`, + } + + for _, query := range queries { + res := grm.Exec(query) + if res.Error != nil { + return res.Error + } + } + return nil +} + +func (m *Migration) GetName() string { + return "202412091100_fixOperatorPiSplitsFields" +} diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index 0986819f..dca991b9 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -42,6 +42,7 @@ import ( _202412021311_stakerOperatorTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412021311_stakerOperatorTables" _202412061553_addBlockNumberIndexes "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412061553_addBlockNumberIndexes" _202412061626_operatorRestakedStrategiesConstraint "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412061626_operatorRestakedStrategiesConstraint" + _202412091100_fixOperatorPiSplitsFields "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields" "go.uber.org/zap" "gorm.io/gorm" "time" @@ -123,6 +124,7 @@ func (m *Migrator) MigrateAll() error { &_202412061626_operatorRestakedStrategiesConstraint.Migration{}, &_202411221331_operatorAVSSplitSnapshots.Migration{}, &_202411221331_operatorPISplitSnapshots.Migration{}, + &_202412091100_fixOperatorPiSplitsFields.Migration{}, } for _, migration := range migrations { @@ -142,7 +144,7 @@ func (m *Migrator) Migrate(migration Migration) error { result := m.GDb.Find(&migrationRecord, "name = ?", name).Limit(1) if result.Error == nil && result.RowsAffected == 0 { - m.Logger.Sugar().Infof("Running migration '%s'", name) + m.Logger.Sugar().Debugf("Running migration '%s'", name) // run migration err := migration.Up(m.Db, m.GDb, m.globalConfig) if err != nil { @@ -166,6 +168,7 @@ func (m *Migrator) Migrate(migration Migration) error { m.Logger.Sugar().Infof("Migration %s already run", name) return nil } + m.Logger.Sugar().Infof("Migration %s applied", name) return nil } diff --git a/pkg/rewards/10_goldAvsODRewardAmounts.go b/pkg/rewards/10_goldAvsODRewardAmounts.go new file mode 100644 index 00000000..bd5e7180 --- /dev/null +++ b/pkg/rewards/10_goldAvsODRewardAmounts.go @@ -0,0 +1,91 @@ +package rewards + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "go.uber.org/zap" +) + +const _10_goldAvsODRewardAmountsQuery = ` +CREATE TABLE {{.destTableName}} AS + +-- Step 1: Get the rows where operators have not registered for the AVS or if the AVS does not exist +WITH reward_snapshot_operators AS ( + SELECT + ap.reward_hash, + ap.snapshot AS snapshot, + ap.token, + ap.tokens_per_day, + ap.tokens_per_day_decimal, + ap.avs AS avs, + ap.operator AS operator, + ap.strategy, + ap.multiplier, + ap.reward_submission_date + FROM {{.activeODRewardsTable}} ap + LEFT JOIN operator_avs_registration_snapshots oar + ON ap.avs = oar.avs + AND ap.snapshot = oar.snapshot + AND ap.operator = oar.operator + WHERE oar.avs IS NULL OR oar.operator IS NULL +), + +-- Step 2: Dedupe the operator tokens across strategies for each (operator, reward hash, snapshot) +-- Since the above result is a flattened operator-directed reward submission across strategies +distinct_operators AS ( + SELECT * + FROM ( + SELECT + *, + -- We can use an arbitrary order here since the avs_tokens is the same for each (operator, strategy, hash, snapshot) + -- We use strategy ASC for better debuggability + ROW_NUMBER() OVER ( + PARTITION BY reward_hash, snapshot, operator + ORDER BY strategy ASC + ) AS rn + FROM reward_snapshot_operators + ) t + WHERE rn = 1 +), + +-- Step 3: Sum the operator tokens for each (reward hash, snapshot) +-- Since we want to refund the sum of those operator amounts to the AVS in that reward submission for that snapshot +operator_token_sums AS ( + SELECT + reward_hash, + snapshot, + token, + avs, + operator, + SUM(tokens_per_day_decimal) OVER (PARTITION BY reward_hash, snapshot) AS avs_tokens + FROM distinct_operators +) + +-- Step 4: Output the final table +SELECT * FROM operator_token_sums +` + +func (rc *RewardsCalculator) GenerateGold10AvsODRewardAmountsTable(snapshotDate string) error { + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) + destTableName := allTableNames[rewardsUtils.Table_10_AvsODRewardAmounts] + + rc.logger.Sugar().Infow("Generating Avs OD reward amounts", + zap.String("cutoffDate", snapshotDate), + zap.String("destTableName", destTableName), + ) + + query, err := rewardsUtils.RenderQueryTemplate(_10_goldAvsODRewardAmountsQuery, map[string]string{ + "destTableName": destTableName, + "activeODRewardsTable": allTableNames[rewardsUtils.Table_7_ActiveODRewards], + }) + if err != nil { + rc.logger.Sugar().Errorw("Failed to render query template", "error", err) + return err + } + + res := rc.grm.Exec(query) + if res.Error != nil { + rc.logger.Sugar().Errorw("Failed to create gold_avs_od_reward_amounts", "error", res.Error) + return res.Error + } + return nil +} diff --git a/pkg/rewards/7_goldStaging.go b/pkg/rewards/11_goldStaging.go similarity index 60% rename from pkg/rewards/7_goldStaging.go rename to pkg/rewards/11_goldStaging.go index ca6ca04b..677078ae 100644 --- a/pkg/rewards/7_goldStaging.go +++ b/pkg/rewards/11_goldStaging.go @@ -2,11 +2,12 @@ package rewards import ( "database/sql" + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "go.uber.org/zap" ) -const _7_goldStagingQuery = ` +const _11_goldStagingQuery = ` create table {{.destTableName}} as WITH staker_rewards AS ( -- We can select DISTINCT here because the staker's tokens are the same for each strategy in the reward hash @@ -55,6 +56,36 @@ rewards_for_all_earners_operators AS ( operator_tokens as amount FROM {{.rfaeOperatorTable}} ), +operator_od_rewards AS ( + SELECT DISTINCT + -- We can select DISTINCT here because the operator's tokens are the same for each strategy in the reward hash + operator as earner, + snapshot, + reward_hash, + token, + operator_tokens as amount + FROM {{.operatorODRewardAmountsTable}} +), +staker_od_rewards AS ( + SELECT DISTINCT + -- We can select DISTINCT here because the staker's tokens are the same for each strategy in the reward hash + staker as earner, + snapshot, + reward_hash, + token, + staker_tokens as amount + FROM {{.stakerODRewardAmountsTable}} +), +avs_od_rewards AS ( + SELECT DISTINCT + -- We can select DISTINCT here because the avs's tokens are the same for each strategy in the reward hash + avs as earner, + snapshot, + reward_hash, + token, + avs_tokens as amount + FROM {{.avsODRewardAmountsTable}} +), combined_rewards AS ( SELECT * FROM operator_rewards UNION ALL @@ -65,6 +96,12 @@ combined_rewards AS ( SELECT * FROM rewards_for_all_earners_stakers UNION ALL SELECT * FROM rewards_for_all_earners_operators + UNION ALL + SELECT * FROM operator_od_rewards + UNION ALL + SELECT * FROM staker_od_rewards + UNION ALL + SELECT * FROM avs_od_rewards ), -- Dedupe earners, primarily operators who are also their own staker. deduped_earners AS ( @@ -85,22 +122,25 @@ SELECT * FROM deduped_earners ` -func (rc *RewardsCalculator) GenerateGold7StagingTable(snapshotDate string) error { +func (rc *RewardsCalculator) GenerateGold11StagingTable(snapshotDate string) error { allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) - destTableName := allTableNames[rewardsUtils.Table_7_GoldStaging] + destTableName := allTableNames[rewardsUtils.Table_11_GoldStaging] rc.logger.Sugar().Infow("Generating gold staging", zap.String("cutoffDate", snapshotDate), zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_7_goldStagingQuery, map[string]string{ - "destTableName": destTableName, - "stakerRewardAmountsTable": allTableNames[rewardsUtils.Table_2_StakerRewardAmounts], - "operatorRewardAmountsTable": allTableNames[rewardsUtils.Table_3_OperatorRewardAmounts], - "rewardsForAllTable": allTableNames[rewardsUtils.Table_4_RewardsForAll], - "rfaeStakerTable": allTableNames[rewardsUtils.Table_5_RfaeStakers], - "rfaeOperatorTable": allTableNames[rewardsUtils.Table_6_RfaeOperators], + query, err := rewardsUtils.RenderQueryTemplate(_11_goldStagingQuery, map[string]string{ + "destTableName": destTableName, + "stakerRewardAmountsTable": allTableNames[rewardsUtils.Table_2_StakerRewardAmounts], + "operatorRewardAmountsTable": allTableNames[rewardsUtils.Table_3_OperatorRewardAmounts], + "rewardsForAllTable": allTableNames[rewardsUtils.Table_4_RewardsForAll], + "rfaeStakerTable": allTableNames[rewardsUtils.Table_5_RfaeStakers], + "rfaeOperatorTable": allTableNames[rewardsUtils.Table_6_RfaeOperators], + "operatorODRewardAmountsTable": allTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts], + "stakerODRewardAmountsTable": allTableNames[rewardsUtils.Table_9_StakerODRewardAmounts], + "avsODRewardAmountsTable": allTableNames[rewardsUtils.Table_10_AvsODRewardAmounts], }) if err != nil { rc.logger.Sugar().Errorw("Failed to render query template", "error", err) @@ -135,7 +175,7 @@ func (rc *RewardsCalculator) ListGoldStagingRowsForSnapshot(snapshotDate string) token, amount FROM {{.goldStagingTable}} WHERE DATE(snapshot) < @cutoffDate`, map[string]string{ - "goldStagingTable": allTableNames[rewardsUtils.Table_7_GoldStaging], + "goldStagingTable": allTableNames[rewardsUtils.Table_11_GoldStaging], }) if err != nil { rc.logger.Sugar().Errorw("Failed to render query template", "error", err) diff --git a/pkg/rewards/8_goldFinal.go b/pkg/rewards/12_goldFinal.go similarity index 80% rename from pkg/rewards/8_goldFinal.go rename to pkg/rewards/12_goldFinal.go index 48b243d2..c65e7096 100644 --- a/pkg/rewards/8_goldFinal.go +++ b/pkg/rewards/12_goldFinal.go @@ -1,12 +1,13 @@ package rewards import ( + "time" + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "go.uber.org/zap" - "time" ) -const _8_goldFinalQuery = ` +const _12_goldFinalQuery = ` insert into gold_table SELECT earner, @@ -25,15 +26,15 @@ type GoldRow struct { Amount string } -func (rc *RewardsCalculator) GenerateGold8FinalTable(snapshotDate string) error { +func (rc *RewardsCalculator) GenerateGold12FinalTable(snapshotDate string) error { allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) rc.logger.Sugar().Infow("Generating gold final table", zap.String("cutoffDate", snapshotDate), ) - query, err := rewardsUtils.RenderQueryTemplate(_8_goldFinalQuery, map[string]string{ - "goldStagingTable": allTableNames[rewardsUtils.Table_7_GoldStaging], + query, err := rewardsUtils.RenderQueryTemplate(_12_goldFinalQuery, map[string]string{ + "goldStagingTable": allTableNames[rewardsUtils.Table_11_GoldStaging], }) if err != nil { rc.logger.Sugar().Errorw("Failed to render query template", "error", err) diff --git a/pkg/rewards/7_goldActiveODRewards.go b/pkg/rewards/7_goldActiveODRewards.go new file mode 100644 index 00000000..ee84cc1d --- /dev/null +++ b/pkg/rewards/7_goldActiveODRewards.go @@ -0,0 +1,159 @@ +package rewards + +import ( + "database/sql" + + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "go.uber.org/zap" +) + +var _7_goldActiveODRewardsQuery = ` +CREATE TABLE {{.destTableName}} AS +WITH +-- Step 2: Modify active rewards and compute tokens per day +active_rewards_modified AS ( + SELECT + *, + amount / (duration / 86400) AS tokens_per_day, + CAST(@cutoffDate AS TIMESTAMP(6)) AS global_end_inclusive -- Inclusive means we DO USE this day as a snapshot + FROM operator_directed_rewards + WHERE end_timestamp >= TIMESTAMP '{{.rewardsStart}}' + AND start_timestamp <= TIMESTAMP '{{.cutoffDate}}' + AND block_time <= TIMESTAMP '{{.cutoffDate}}' -- Always ensure we're not using future data. Should never happen since we're never backfilling, but here for safety and consistency. +), + +-- Step 3: Cut each reward's start and end windows to handle the global range +active_rewards_updated_end_timestamps AS ( + SELECT + avs, + operator, + /** + * 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_hash, + global_end_inclusive, + block_date AS reward_submission_date + FROM active_rewards_modified +), + +-- Step 4: For each reward hash, find the latest snapshot +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, + -- We use floor to ensure we are always underesimating total tokens per day + FLOOR(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.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.tokens_per_day, + ap.multiplier, + ap.strategy, + ap.reward_hash, + ap.global_end_inclusive, + ap.reward_start_exclusive, + ap.reward_submission_date +), + +-- Step 5: Filter out invalid reward ranges +active_reward_ranges AS ( + /** 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 + */ + SELECT * + FROM active_rewards_updated_start_timestamps + WHERE reward_start_exclusive < reward_end_inclusive +), + +-- Step 6: Explode out the ranges for a day per inclusive date +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 +), + +-- Step 7: Prepare final active rewards +active_rewards_final AS ( + SELECT + avs, + operator, + CAST(day AS DATE) AS snapshot, + token, + tokens_per_day, + tokens_per_day_decimal, + multiplier, + strategy, + reward_hash, + reward_submission_date + FROM exploded_active_range_rewards + -- Remove snapshots on the start day + WHERE day != reward_start_exclusive +) + +SELECT * FROM active_rewards_final +` + +// Generate7ActiveODRewards generates active operator-directed rewards for the gold_7_active_od_rewards table +// +// @param snapshotDate: The upper bound of when to calculate rewards to +// @param startDate: The lower bound of when to calculate rewards from. If we're running rewards for the first time, +// this will be "1970-01-01". If this is a subsequent run, this will be the last snapshot date. +func (r *RewardsCalculator) Generate7ActiveODRewards(snapshotDate string) error { + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) + destTableName := allTableNames[rewardsUtils.Table_7_ActiveODRewards] + + rewardsStart := "1970-01-01 00:00:00" // This will always start as this date and get's updated later in the query + + r.logger.Sugar().Infow("Generating active rewards", + zap.String("rewardsStart", rewardsStart), + zap.String("cutoffDate", snapshotDate), + zap.String("destTableName", destTableName), + ) + + query, err := rewardsUtils.RenderQueryTemplate(_7_goldActiveODRewardsQuery, map[string]string{ + "destTableName": destTableName, + "rewardsStart": rewardsStart, + "cutoffDate": snapshotDate, + }) + if err != nil { + r.logger.Sugar().Errorw("Failed to render query template", "error", err) + return err + } + + res := r.grm.Exec(query, + sql.Named("cutoffDate", snapshotDate), + ) + if res.Error != nil { + r.logger.Sugar().Errorw("Failed to generate active od rewards", "error", res.Error) + return res.Error + } + return nil +} diff --git a/pkg/rewards/8_goldOperatorODRewardAmounts.go b/pkg/rewards/8_goldOperatorODRewardAmounts.go new file mode 100644 index 00000000..8ae90773 --- /dev/null +++ b/pkg/rewards/8_goldOperatorODRewardAmounts.go @@ -0,0 +1,92 @@ +package rewards + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "go.uber.org/zap" +) + +const _8_goldOperatorODRewardAmountsQuery = ` +CREATE TABLE {{.destTableName}} AS + +-- Step 1: Get the rows where operators have registered for the AVS +WITH reward_snapshot_operators AS ( + SELECT + ap.reward_hash, + ap.snapshot AS snapshot, + ap.token, + ap.tokens_per_day, + ap.tokens_per_day_decimal, + ap.avs AS avs, + ap.operator AS operator, + ap.strategy, + ap.multiplier, + ap.reward_submission_date + FROM {{.activeODRewardsTable}} ap + JOIN operator_avs_registration_snapshots oar + ON ap.avs = oar.avs + AND ap.snapshot = oar.snapshot + AND ap.operator = oar.operator +), + +-- Step 2: Dedupe the operator tokens across strategies for each (operator, reward hash, snapshot) +-- Since the above result is a flattened operator-directed reward submission across strategies. +distinct_operators AS ( + SELECT * + FROM ( + SELECT + *, + -- We can use an arbitrary order here since the avs_tokens is the same for each (operator, strategy, hash, snapshot) + -- We use strategy ASC for better debuggability + ROW_NUMBER() OVER ( + PARTITION BY reward_hash, snapshot, operator + ORDER BY strategy ASC + ) AS rn + FROM reward_snapshot_operators + ) t + -- Keep only the first row for each (operator, reward hash, snapshot) + WHERE rn = 1 +), + +-- Step 3: Calculate the tokens for each operator with dynamic split logic +-- If no split is found, default to 1000 (10%) +operator_splits AS ( + SELECT + dop.*, + COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL) as split_pct, + FLOOR(dop.tokens_per_day_decimal * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) AS operator_tokens + FROM distinct_operators dop + LEFT JOIN operator_avs_split_snapshots oas + ON dop.operator = oas.operator + AND dop.avs = oas.avs + AND dop.snapshot = oas.snapshot +) + +-- Step 4: Output the final table with operator splits +SELECT * FROM operator_splits +` + +func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotDate string) error { + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) + destTableName := allTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts] + + rc.logger.Sugar().Infow("Generating Operator OD reward amounts", + zap.String("cutoffDate", snapshotDate), + zap.String("destTableName", destTableName), + ) + + query, err := rewardsUtils.RenderQueryTemplate(_8_goldOperatorODRewardAmountsQuery, map[string]string{ + "destTableName": destTableName, + "activeODRewardsTable": allTableNames[rewardsUtils.Table_7_ActiveODRewards], + }) + if err != nil { + rc.logger.Sugar().Errorw("Failed to render query template", "error", err) + return err + } + + res := rc.grm.Exec(query) + if res.Error != nil { + rc.logger.Sugar().Errorw("Failed to create gold_operator_od_reward_amounts", "error", res.Error) + return res.Error + } + return nil +} diff --git a/pkg/rewards/9_goldStakerODRewardAmounts.go b/pkg/rewards/9_goldStakerODRewardAmounts.go new file mode 100644 index 00000000..5b66a75f --- /dev/null +++ b/pkg/rewards/9_goldStakerODRewardAmounts.go @@ -0,0 +1,141 @@ +package rewards + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "go.uber.org/zap" +) + +const _9_goldStakerODRewardAmountsQuery = ` +CREATE TABLE {{.destTableName}} AS + +-- Step 1: Get the rows where operators have registered for the AVS +WITH reward_snapshot_operators AS ( + SELECT + ap.reward_hash, + ap.snapshot AS snapshot, + ap.token, + ap.tokens_per_day, + ap.tokens_per_day_decimal, + ap.avs AS avs, + ap.operator AS operator, + ap.strategy, + ap.multiplier, + ap.reward_submission_date + FROM {{.activeODRewardsTable}} ap + JOIN operator_avs_registration_snapshots oar + ON ap.avs = oar.avs + AND ap.snapshot = oar.snapshot + AND ap.operator = oar.operator +), + +-- Calculate the total staker split for each operator reward with dynamic split logic +-- If no split is found, default to 1000 (10%) +staker_splits AS ( + SELECT + rso.*, + rso.tokens_per_day_decimal - FLOOR(rso.tokens_per_day_decimal * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) 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 +), +-- Get the stakers that were delegated to the operator for the snapshot +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 +), + +-- Get the shares for stakers delegated to the operator +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 + -- Filter out negative shares and zero multiplier to avoid division by zero + WHERE sss.shares > 0 AND sdo.multiplier != 0 +), + +-- Calculate the weight of each staker +staker_weights AS ( + SELECT + *, + SUM(multiplier * shares) OVER (PARTITION BY staker, reward_hash, snapshot) AS staker_weight + FROM staker_avs_strategy_shares +), +-- Get distinct stakers since their weights are already calculated +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 +), +-- Calculate the sum of all staker weights for each reward and snapshot +staker_weight_sum AS ( + SELECT + *, + SUM(staker_weight) OVER (PARTITION BY reward_hash, operator, snapshot) AS total_weight + FROM distinct_stakers +), +-- Calculate staker proportion of tokens for each reward and snapshot +staker_proportion AS ( + SELECT + *, + FLOOR((staker_weight / total_weight) * 1000000000000000) / 1000000000000000 AS staker_proportion + FROM staker_weight_sum +), +-- Calculate the staker reward amounts +staker_reward_amounts AS ( + SELECT + *, + FLOOR(staker_proportion * staker_split) AS staker_tokens + FROM staker_proportion +) +-- Output the final table +SELECT * FROM staker_reward_amounts +` + +func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDate string) error { + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) + destTableName := allTableNames[rewardsUtils.Table_9_StakerODRewardAmounts] + + rc.logger.Sugar().Infow("Generating Staker OD reward amounts", + zap.String("cutoffDate", snapshotDate), + zap.String("destTableName", destTableName), + ) + + query, err := rewardsUtils.RenderQueryTemplate(_9_goldStakerODRewardAmountsQuery, map[string]string{ + "destTableName": destTableName, + "activeODRewardsTable": allTableNames[rewardsUtils.Table_7_ActiveODRewards], + }) + if err != nil { + rc.logger.Sugar().Errorw("Failed to render query template", "error", err) + return err + } + + res := rc.grm.Exec(query) + if res.Error != nil { + rc.logger.Sugar().Errorw("Failed to create gold_staker_od_reward_amounts", "error", res.Error) + return res.Error + } + return nil +} diff --git a/pkg/rewards/operatorAvsSplitSnapshots.go b/pkg/rewards/operatorAvsSplitSnapshots.go index fea5c916..5456af2b 100644 --- a/pkg/rewards/operatorAvsSplitSnapshots.go +++ b/pkg/rewards/operatorAvsSplitSnapshots.go @@ -18,20 +18,37 @@ WITH operator_avs_splits_with_block_info as ( ), -- Rank the records for each combination of (operator, avs, activation date) by activation time, block time and log index ranked_operator_avs_split_records as ( - SELECT *, - ROW_NUMBER() OVER (PARTITION BY operator, avs, cast(activated_at AS DATE) ORDER BY activated_at DESC, block_time DESC, log_index DESC) AS rn + SELECT + *, + -- round activated up to the nearest day + date_trunc('day', activated_at) + INTERVAL '1' day AS rounded_activated_at, + ROW_NUMBER() OVER (PARTITION BY operator, avs ORDER BY block_time asc, log_index asc) AS rn FROM operator_avs_splits_with_block_info ), --- Get the latest record for each day & round up to the snapshot day -snapshotted_records as ( - SELECT - operator, - avs, - split, - block_time, - date_trunc('day', activated_at) + INTERVAL '1' day AS snapshot_time - from ranked_operator_avs_split_records - where rn = 1 +decorated_operator_avs_splits as ( + select + rops.*, + -- if there is a row, we have found another split that overlaps the current split + -- meaning the current split should be discarded + case when rops2.block_time is not null then false else true end as active + from ranked_operator_avs_split_records as rops + left join ranked_operator_avs_split_records as rops2 on ( + rops.operator = rops2.operator + and rops.avs = rops2.avs + -- rn is orderd by block and log_index, so this should encapsulate rops2 occurring afer rops + and rops.rn > rops2.rn + -- only find the next split that overlaps with the current one + and rops2.rounded_activated_at <= rops.rounded_activated_at + ) +), +-- filter in only splits flagged as active +active_operator_splits as ( + select + *, + rounded_activated_at as snapshot_time, + ROW_NUMBER() over (partition by operator, avs order by rounded_activated_at asc) as rn + from decorated_operator_avs_splits + where active = true ), -- Get the range for each operator, avs pairing operator_avs_split_windows as ( @@ -40,9 +57,11 @@ operator_avs_split_windows as ( CASE -- If the range does not have the end, use the current timestamp truncated to 0 UTC WHEN LEAD(snapshot_time) OVER (PARTITION BY operator, avs ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}') - ELSE LEAD(snapshot_time) OVER (PARTITION BY operator, avs ORDER BY snapshot_time) + + -- need to subtract 1 day from the end time since generate_series will be inclusive below. + ELSE LEAD(snapshot_time) OVER (PARTITION BY operator ORDER BY snapshot_time) - interval '1 day' END AS end_time - FROM snapshotted_records + FROM active_operator_splits ), -- Clean up any records where start_time >= end_time cleaned_records as ( diff --git a/pkg/rewards/operatorAvsSplitSnapshots_test.go b/pkg/rewards/operatorAvsSplitSnapshots_test.go new file mode 100644 index 00000000..8b65e954 --- /dev/null +++ b/pkg/rewards/operatorAvsSplitSnapshots_test.go @@ -0,0 +1,129 @@ +package rewards + +import ( + "fmt" + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/internal/logger" + "github.com/Layr-Labs/sidecar/internal/tests" + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" + "testing" +) + +func setupOperatorAvsSplitWindows() ( + string, + *config.Config, + *gorm.DB, + *zap.Logger, + error, +) { + testContext := getRewardsTestContext() + cfg := tests.GetConfig() + switch testContext { + case "testnet": + cfg.Chain = config.Chain_Holesky + case "testnet-reduced": + cfg.Chain = config.Chain_Holesky + case "mainnet-reduced": + cfg.Chain = config.Chain_Mainnet + default: + return "", nil, nil, nil, fmt.Errorf("Unknown test context") + } + + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, cfg, grm, l, nil +} + +func teardownOperatorAvsSplitWindows(dbname string, cfg *config.Config, db *gorm.DB, l *zap.Logger) { + rawDb, _ := db.DB() + _ = rawDb.Close() + + pgConfig := postgres.PostgresConfigFromDbConfig(&cfg.DatabaseConfig) + + if err := postgres.DeleteTestDatabase(pgConfig, dbname); err != nil { + l.Sugar().Errorw("Failed to delete test database", "error", err) + } +} + +func hydrateOperatorAvsSplits(grm *gorm.DB, l *zap.Logger) error { + projectRoot := getProjectRootPath() + contents, err := tests.GetOperatorAvsSplitsSqlFile(projectRoot) + + if err != nil { + return err + } + + res := grm.Exec(contents) + if res.Error != nil { + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) + return res.Error + } + return nil +} + +func Test_OperatorAvsSplitSnapshots(t *testing.T) { + if !rewardsTestsEnabled() { + t.Skipf("Skipping %s", t.Name()) + return + } + + // projectRoot := getProjectRootPath() + dbFileName, cfg, grm, l, err := setupOperatorAvsSplitWindows() + if err != nil { + t.Fatal(err) + } + // testContext := getRewardsTestContext() + + snapshotDate := "2024-12-09" + + t.Run("Should hydrate dependency tables", func(t *testing.T) { + t.Log("Hydrating blocks") + + _, err := hydrateRewardsV2Blocks(grm, l) + assert.Nil(t, err) + + t.Log("Hydrating restaked strategies") + err = hydrateOperatorAvsSplits(grm, l) + if err != nil { + t.Fatal(err) + } + + query := `select count(*) from operator_avs_splits` + var count int + res := grm.Raw(query).Scan(&count) + + assert.Nil(t, res.Error) + + assert.Equal(t, 48, count) + }) + + t.Run("Should calculate correct operatorAvsSplit windows", func(t *testing.T) { + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) + + t.Log("Generating snapshots") + err := rewards.GenerateAndInsertOperatorAvsSplitSnapshots(snapshotDate) + assert.Nil(t, err) + + windows, err := rewards.ListOperatorAvsSplitSnapshots() + assert.Nil(t, err) + + t.Logf("Found %d windows", len(windows)) + + assert.Equal(t, 192, len(windows)) + }) + t.Cleanup(func() { + teardownOperatorAvsSplitWindows(dbFileName, cfg, grm, l) + }) +} diff --git a/pkg/rewards/operatorDirectedRewards.go b/pkg/rewards/operatorDirectedRewards.go new file mode 100644 index 00000000..d0fa074c --- /dev/null +++ b/pkg/rewards/operatorDirectedRewards.go @@ -0,0 +1,73 @@ +package rewards + +import "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + +const operatorDirectedRewardsQuery = ` + 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 < 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 +` + +func (r *RewardsCalculator) GenerateAndInsertOperatorDirectedRewards(snapshotDate string) error { + tableName := "operator_directed_rewards" + + query, err := rewardsUtils.RenderQueryTemplate(operatorDirectedRewardsQuery, map[string]string{ + "cutoffDate": snapshotDate, + }) + if err != nil { + r.logger.Sugar().Errorw("Failed to render rewards combined query", "error", err) + return err + } + + err = r.generateAndInsertFromQuery(tableName, query, nil) + if err != nil { + r.logger.Sugar().Errorw("Failed to generate combined rewards", "error", err) + return err + } + return nil +} + +func (rc *RewardsCalculator) ListOperatorDirectedRewards() ([]*OperatorDirectedRewards, error) { + var operatorDirectedRewards []*OperatorDirectedRewards + res := rc.grm.Model(&OperatorDirectedRewards{}).Find(&operatorDirectedRewards) + if res.Error != nil { + rc.logger.Sugar().Errorw("Failed to list combined rewards", "error", res.Error) + return nil, res.Error + } + return operatorDirectedRewards, nil +} diff --git a/pkg/rewards/operatorDirectedRewards_test.go b/pkg/rewards/operatorDirectedRewards_test.go new file mode 100644 index 00000000..ffe62753 --- /dev/null +++ b/pkg/rewards/operatorDirectedRewards_test.go @@ -0,0 +1,118 @@ +package rewards + +import ( + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/internal/logger" + "github.com/Layr-Labs/sidecar/internal/tests" + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" + "testing" +) + +func setupOperatorDirectedRewards() ( + string, + *config.Config, + *gorm.DB, + *zap.Logger, + error, +) { + cfg := tests.GetConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, cfg, grm, l, nil +} + +func teardownOperatorDirectedRewards(dbname string, cfg *config.Config, db *gorm.DB, l *zap.Logger) { + rawDb, _ := db.DB() + _ = rawDb.Close() + + pgConfig := postgres.PostgresConfigFromDbConfig(&cfg.DatabaseConfig) + + if err := postgres.DeleteTestDatabase(pgConfig, dbname); err != nil { + l.Sugar().Errorw("Failed to delete test database", "error", err) + } +} + +func hydrateOperatorDirectedRewardSubmissionsTable(grm *gorm.DB, l *zap.Logger) error { + projectRoot := getProjectRootPath() + contents, err := tests.GetOperatorDirectedRewardsSqlFile(projectRoot) + + if err != nil { + return err + } + + res := grm.Exec(contents) + if res.Error != nil { + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) + return res.Error + } + return nil +} + +func Test_OperatorDirectedRewards(t *testing.T) { + if !rewardsTestsEnabled() { + t.Skipf("Skipping %s", t.Name()) + return + } + + dbFileName, cfg, grm, l, err := setupOperatorDirectedRewards() + + if err != nil { + t.Fatal(err) + } + + snapshotDate := "2024-12-09" + + t.Run("Should hydrate blocks and operator_directed_reward_submissions tables", func(t *testing.T) { + totalBlockCount, err := hydrateRewardsV2Blocks(grm, l) + if err != nil { + t.Fatal(err) + } + + query := "select count(*) from blocks" + var count int + res := grm.Raw(query).Scan(&count) + assert.Nil(t, res.Error) + assert.Equal(t, totalBlockCount, count) + + err = hydrateOperatorDirectedRewardSubmissionsTable(grm, l) + if err != nil { + t.Fatal(err) + } + + query = "select count(*) from operator_directed_reward_submissions" + res = grm.Raw(query).Scan(&count) + assert.Nil(t, res.Error) + assert.Equal(t, 1120, count) + }) + t.Run("Should generate the proper operatorDirectedRewards", func(t *testing.T) { + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) + + err = rewards.GenerateAndInsertOperatorDirectedRewards(snapshotDate) + assert.Nil(t, err) + + operatorDirectedRewards, err := rewards.ListOperatorDirectedRewards() + assert.Nil(t, err) + + assert.NotNil(t, operatorDirectedRewards) + + t.Logf("Generated %d operatorDirectedRewards", len(operatorDirectedRewards)) + + assert.Equal(t, 1120, len(operatorDirectedRewards)) + + }) + t.Cleanup(func() { + teardownOperatorDirectedRewards(dbFileName, cfg, grm, l) + }) +} diff --git a/pkg/rewards/operatorPISplitSnapshots.go b/pkg/rewards/operatorPISplitSnapshots.go index 710b5225..ef212e47 100644 --- a/pkg/rewards/operatorPISplitSnapshots.go +++ b/pkg/rewards/operatorPISplitSnapshots.go @@ -7,7 +7,7 @@ WITH operator_pi_splits_with_block_info as ( select ops.operator, ops.activated_at::timestamp(6) as activated_at, - ops.new_operator_avs_split_bips as split, + ops.new_operator_pi_split_bips as split, ops.block_number, ops.log_index, b.block_time::timestamp(6) as block_time @@ -15,32 +15,53 @@ WITH operator_pi_splits_with_block_info as ( join blocks as b on (b.number = ops.block_number) where activated_at < TIMESTAMP '{{.cutoffDate}}' ), --- Rank the records for each combination of (operator, activation date) by activation time, block time and log index +-- Rank the records for each operator by block time asc, log index asc (in the order they happened) ranked_operator_pi_split_records as ( - SELECT *, - ROW_NUMBER() OVER (PARTITION BY operator, cast(activated_at AS DATE) ORDER BY activated_at DESC, block_time DESC, log_index DESC) AS rn + SELECT + *, + -- round activated up to the nearest day + date_trunc('day', activated_at) + INTERVAL '1' day AS rounded_activated_at, + ROW_NUMBER() OVER (PARTITION BY operator ORDER BY block_time asc, log_index asc) AS rn FROM operator_pi_splits_with_block_info ), --- Get the latest record for each day & round up to the snapshot day -snapshotted_records as ( - SELECT - operator, - split, - block_time, - date_trunc('day', activated_at) + INTERVAL '1' day AS snapshot_time - from ranked_operator_pi_split_records - where rn = 1 +decorated_operator_splits as ( + select + rops.*, + -- if there is a row, we have found another split that overlaps the current split + -- meaning the current split should be discarded + case when rops2.block_time is not null then false else true end as active + from ranked_operator_pi_split_records as rops + left join ranked_operator_pi_split_records as rops2 on ( + rops.operator = rops2.operator + -- rn is orderd by block and log_index, so this should encapsulate rops2 occurring afer rops + and rops.rn > rops2.rn + -- only find the next split that overlaps with the current one + and rops2.rounded_activated_at <= rops.rounded_activated_at + ) +), +-- filter in only splits flagged as active +active_operator_splits as ( + select + *, + rounded_activated_at as snapshot_time, + ROW_NUMBER() over (partition by operator order by rounded_activated_at asc) as rn + from decorated_operator_splits + where active = true ), -- Get the range for each operator operator_pi_split_windows as ( SELECT - operator, split, snapshot_time as start_time, + operator, + split, + snapshot_time as start_time, CASE -- If the range does not have the end, use the current timestamp truncated to 0 UTC WHEN LEAD(snapshot_time) OVER (PARTITION BY operator ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}') - ELSE LEAD(snapshot_time) OVER (PARTITION BY operator ORDER BY snapshot_time) + + -- need to subtract 1 day from the end time since generate_series will be inclusive below. + ELSE LEAD(snapshot_time) OVER (PARTITION BY operator ORDER BY snapshot_time) - interval '1 day' END AS end_time - FROM snapshotted_records + FROM active_operator_splits ), -- Clean up any records where start_time >= end_time cleaned_records as ( diff --git a/pkg/rewards/operatorPISplitSnapshots_test.go b/pkg/rewards/operatorPISplitSnapshots_test.go new file mode 100644 index 00000000..c4f04d53 --- /dev/null +++ b/pkg/rewards/operatorPISplitSnapshots_test.go @@ -0,0 +1,129 @@ +package rewards + +import ( + "fmt" + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/internal/logger" + "github.com/Layr-Labs/sidecar/internal/tests" + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" + "testing" +) + +func setupOperatorPISplitWindows() ( + string, + *config.Config, + *gorm.DB, + *zap.Logger, + error, +) { + testContext := getRewardsTestContext() + cfg := tests.GetConfig() + switch testContext { + case "testnet": + cfg.Chain = config.Chain_Holesky + case "testnet-reduced": + cfg.Chain = config.Chain_Holesky + case "mainnet-reduced": + cfg.Chain = config.Chain_Mainnet + default: + return "", nil, nil, nil, fmt.Errorf("Unknown test context") + } + + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, cfg, grm, l, nil +} + +func teardownOperatorPISplitWindows(dbname string, cfg *config.Config, db *gorm.DB, l *zap.Logger) { + rawDb, _ := db.DB() + _ = rawDb.Close() + + pgConfig := postgres.PostgresConfigFromDbConfig(&cfg.DatabaseConfig) + + if err := postgres.DeleteTestDatabase(pgConfig, dbname); err != nil { + l.Sugar().Errorw("Failed to delete test database", "error", err) + } +} + +func hydrateOperatorPISplits(grm *gorm.DB, l *zap.Logger) error { + projectRoot := getProjectRootPath() + contents, err := tests.GetOperatorPISplitsSqlFile(projectRoot) + + if err != nil { + return err + } + + res := grm.Exec(contents) + if res.Error != nil { + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) + return res.Error + } + return nil +} + +func Test_OperatorPISplitSnapshots(t *testing.T) { + if !rewardsTestsEnabled() { + t.Skipf("Skipping %s", t.Name()) + return + } + + // projectRoot := getProjectRootPath() + dbFileName, cfg, grm, l, err := setupOperatorPISplitWindows() + if err != nil { + t.Fatal(err) + } + // testContext := getRewardsTestContext() + + snapshotDate := "2024-12-09" + + t.Run("Should hydrate dependency tables", func(t *testing.T) { + t.Log("Hydrating blocks") + + _, err := hydrateRewardsV2Blocks(grm, l) + assert.Nil(t, err) + + t.Log("Hydrating restaked strategies") + err = hydrateOperatorPISplits(grm, l) + if err != nil { + t.Fatal(err) + } + + query := `select count(*) from operator_pi_splits` + var count int + res := grm.Raw(query).Scan(&count) + + assert.Nil(t, res.Error) + + assert.Equal(t, 48, count) + }) + + t.Run("Should calculate correct operatorPISplit windows", func(t *testing.T) { + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) + + t.Log("Generating snapshots") + err := rewards.GenerateAndInsertOperatorPISplitSnapshots(snapshotDate) + assert.Nil(t, err) + + windows, err := rewards.ListOperatorPISplitSnapshots() + assert.Nil(t, err) + + t.Logf("Found %d windows", len(windows)) + + assert.Equal(t, 192, len(windows)) + }) + t.Cleanup(func() { + teardownOperatorPISplitWindows(dbFileName, cfg, grm, l) + }) +} diff --git a/pkg/rewards/operatorShares_test.go b/pkg/rewards/operatorShares_test.go index af42050c..20930efb 100644 --- a/pkg/rewards/operatorShares_test.go +++ b/pkg/rewards/operatorShares_test.go @@ -55,7 +55,7 @@ func hydrateOperatorShareDeltas(grm *gorm.DB, l *zap.Logger) error { res := grm.Exec(contents) if res.Error != nil { - l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error), zap.String("query", contents)) + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) return res.Error } return nil diff --git a/pkg/rewards/rewards.go b/pkg/rewards/rewards.go index a7b1e26c..7c072e4e 100644 --- a/pkg/rewards/rewards.go +++ b/pkg/rewards/rewards.go @@ -6,6 +6,8 @@ import ( "fmt" "time" + "sync/atomic" + "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/distribution" "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" @@ -18,7 +20,6 @@ import ( "gorm.io/gorm/clause" "slices" "strings" - "sync/atomic" ) type RewardsCalculator struct { @@ -215,7 +216,7 @@ func (rc *RewardsCalculator) MerkelizeRewardsForSnapshot(snapshotDate string) (* } func (rc *RewardsCalculator) GetMaxSnapshotDateForCutoffDate(cutoffDate string) (string, error) { - goldStagingTableName := rewardsUtils.GetGoldTableNames(cutoffDate)[rewardsUtils.Table_7_GoldStaging] + goldStagingTableName := rewardsUtils.GetGoldTableNames(cutoffDate)[rewardsUtils.Table_11_GoldStaging] var maxSnapshotStr string query := fmt.Sprintf(`select to_char(max(snapshot), 'YYYY-MM-DD') as snapshot from %s`, goldStagingTableName) @@ -604,6 +605,14 @@ func (rc *RewardsCalculator) generateSnapshotData(snapshotDate string) error { } rc.logger.Sugar().Debugw("Generated staker delegation snapshots") + // ------------------------------------------------------------------------ + // Rewards V2 snapshots + // ------------------------------------------------------------------------ + if err = rc.GenerateAndInsertOperatorDirectedRewards(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate operator directed rewards", "error", err) + return err + } + rc.logger.Sugar().Debugw("Generated operator directed rewards") if err = rc.GenerateAndInsertOperatorAvsSplitSnapshots(snapshotDate); err != nil { rc.logger.Sugar().Errorw("Failed to generate operator avs split snapshots", "error", err) return err @@ -654,12 +663,32 @@ func (rc *RewardsCalculator) generateGoldTables(snapshotDate string) error { return err } - if err := rc.GenerateGold7StagingTable(snapshotDate); err != nil { + if err := rc.Generate7ActiveODRewards(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate active od rewards", "error", err) + return err + } + + if err := rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate operator od reward amounts", "error", err) + return err + } + + if err := rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate staker od reward amounts", "error", err) + return err + } + + if err := rc.GenerateGold10AvsODRewardAmountsTable(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate avs od reward amounts", "error", err) + return err + } + + if err := rc.GenerateGold11StagingTable(snapshotDate); err != nil { rc.logger.Sugar().Errorw("Failed to generate gold staging", "error", err) return err } - if err := rc.GenerateGold8FinalTable(snapshotDate); err != nil { + if err := rc.GenerateGold12FinalTable(snapshotDate); err != nil { rc.logger.Sugar().Errorw("Failed to generate final table", "error", err) return err } diff --git a/pkg/rewards/rewardsV2_test.go b/pkg/rewards/rewardsV2_test.go new file mode 100644 index 00000000..49d9c6ee --- /dev/null +++ b/pkg/rewards/rewardsV2_test.go @@ -0,0 +1,212 @@ +package rewards + +import ( + "fmt" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "github.com/stretchr/testify/assert" +) + +func Test_RewardsV2(t *testing.T) { + if !rewardsTestsEnabled() { + t.Skipf("Skipping %s", t.Name()) + return + } + + dbFileName, cfg, grm, l, err := setupRewardsV2() + fmt.Printf("Using db file: %+v\n", dbFileName) + + if err != nil { + t.Fatal(err) + } + + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + + t.Run("Should initialize the rewards calculator", func(t *testing.T) { + rc, err := NewRewardsCalculator(cfg, grm, nil, sog, l) + assert.Nil(t, err) + if err != nil { + t.Fatal(err) + } + assert.NotNil(t, rc) + + fmt.Printf("DB Path: %+v\n", dbFileName) + + testStart := time.Now() + + // Setup all tables and source data + _, err = hydrateRewardsV2Blocks(grm, l) + assert.Nil(t, err) + + err = hydrateOperatorAvsStateChangesTable(grm, l) + assert.Nil(t, err) + + err = hydrateOperatorAvsRestakedStrategies(grm, l) + assert.Nil(t, err) + + err = hydrateOperatorShareDeltas(grm, l) + assert.Nil(t, err) + + err = hydrateStakerDelegations(grm, l) + assert.Nil(t, err) + + err = hydrateStakerShareDeltas(grm, l) + assert.Nil(t, err) + + err = hydrateRewardSubmissionsTable(grm, l) + assert.Nil(t, err) + + // RewardsV2 tables + err = hydrateOperatorAvsSplits(grm, l) + assert.Nil(t, err) + + err = hydrateOperatorPISplits(grm, l) + assert.Nil(t, err) + + err = hydrateOperatorDirectedRewardSubmissionsTable(grm, l) + assert.Nil(t, err) + + t.Log("Hydrated tables") + + snapshotDates := []string{ + "2024-12-11", + } + + fmt.Printf("Hydration duration: %v\n", time.Since(testStart)) + testStart = time.Now() + + for _, snapshotDate := range snapshotDates { + t.Log("-----------------------------\n") + + snapshotStartTime := time.Now() + + t.Logf("Generating rewards - snapshotDate: %s", snapshotDate) + // Generate snapshots + err = rc.generateSnapshotData(snapshotDate) + assert.Nil(t, err) + + goldTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) + + fmt.Printf("Snapshot duration: %v\n", time.Since(testStart)) + testStart = time.Now() + + t.Log("Generated and inserted snapshots") + forks, err := cfg.GetForkDates() + assert.Nil(t, err) + + fmt.Printf("Running gold_1_active_rewards\n") + err = rc.Generate1ActiveRewards(snapshotDate) + assert.Nil(t, err) + rows, err := getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_1_ActiveRewards]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_1_active_rewards: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_2_staker_reward_amounts %+v\n", time.Now()) + err = rc.GenerateGold2StakerRewardAmountsTable(snapshotDate, forks) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_2_StakerRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_2_staker_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_3_operator_reward_amounts\n") + err = rc.GenerateGold3OperatorRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_3_OperatorRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_3_operator_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_4_rewards_for_all\n") + err = rc.GenerateGold4RewardsForAllTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_4_RewardsForAll]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_4_rewards_for_all: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_5_rfae_stakers\n") + err = rc.GenerateGold5RfaeStakersTable(snapshotDate, forks) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_5_RfaeStakers]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_5_rfae_stakers: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_6_rfae_operators\n") + err = rc.GenerateGold6RfaeOperatorsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_6_RfaeOperators]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_6_rfae_operators: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + // ------------------------------------------------------------------------ + // Rewards V2 + // ------------------------------------------------------------------------ + fmt.Printf("Running gold_7_active_od_rewards\n") + err = rc.Generate7ActiveODRewards(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_7_ActiveODRewards]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_7_active_od_rewards: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_8_operator_od_reward_amounts\n") + err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_8_operator_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_9_staker_od_reward_amounts\n") + err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_9_StakerODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_9_staker_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_10_avs_od_reward_amounts\n") + err = rc.GenerateGold10AvsODRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_10_AvsODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_10_avs_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_11_staging\n") + err = rc.GenerateGold11StagingTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_11_GoldStaging]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_11_staging: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_12_final_table\n") + err = rc.GenerateGold12FinalTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, "gold_table") + assert.Nil(t, err) + fmt.Printf("\tRows in gold_table: %v - [time: %v]\n", rows, time.Since(testStart)) + + goldRows, err := rc.ListGoldRows() + assert.Nil(t, err) + + t.Logf("Gold staging rows for snapshot %s: %d", snapshotDate, len(goldRows)) + + fmt.Printf("Total duration for rewards compute %s: %v\n", snapshotDate, time.Since(snapshotStartTime)) + testStart = time.Now() + } + + fmt.Printf("Done!\n\n") + t.Cleanup(func() { + // teardownRewards(dbFileName, cfg, grm, l) + }) + }) +} diff --git a/pkg/rewards/rewards_test.go b/pkg/rewards/rewards_test.go index 723db0a3..ddee53e5 100644 --- a/pkg/rewards/rewards_test.go +++ b/pkg/rewards/rewards_test.go @@ -3,6 +3,12 @@ package rewards import ( "errors" "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/internal/logger" "github.com/Layr-Labs/sidecar/internal/tests" @@ -14,11 +20,6 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/zap" "gorm.io/gorm" - "os" - "path/filepath" - "strings" - "testing" - "time" ) // const TOTAL_BLOCK_COUNT = 1229187 @@ -57,6 +58,8 @@ func getSnapshotDate() (string, error) { return "2024-07-25", nil case "mainnet-reduced": return "2024-08-12", nil + case "preprod-rewardsV2": + return "2024-12-09", nil } return "", fmt.Errorf("Unknown context: %s", context) } @@ -79,6 +82,24 @@ func hydrateAllBlocksTable(grm *gorm.DB, l *zap.Logger) (int, error) { return count, nil } +func hydrateRewardsV2Blocks(grm *gorm.DB, l *zap.Logger) (int, error) { + projectRoot := getProjectRootPath() + contents, err := tests.GetRewardsV2Blocks(projectRoot) + + if err != nil { + return 0, err + } + + count := len(strings.Split(strings.Trim(contents, "\n"), "\n")) - 1 + + res := grm.Exec(contents) + if res.Error != nil { + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) + return count, res.Error + } + return count, nil +} + func getRowCountForTable(grm *gorm.DB, tableName string) (int, error) { query := fmt.Sprintf("select count(*) as cnt from %s", tableName) var count int @@ -114,6 +135,30 @@ func setupRewards() ( return dbname, cfg, grm, l, nil } +func setupRewardsV2() ( + string, + *config.Config, + *gorm.DB, + *zap.Logger, + error, +) { + cfg := tests.GetConfig() + cfg.Rewards.GenerateStakerOperatorsTable = true + cfg.Rewards.ValidateRewardsRoot = true + cfg.Chain = config.Chain_Preprod + + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, cfg, grm, l, nil +} + func Test_Rewards(t *testing.T) { if !rewardsTestsEnabled() { t.Skipf("Skipping %s", t.Name()) @@ -249,16 +294,48 @@ func Test_Rewards(t *testing.T) { fmt.Printf("\tRows in gold_6_rfae_operators: %v - [time: %v]\n", rows, time.Since(testStart)) testStart = time.Now() - fmt.Printf("Running gold_7_staging\n") - err = rc.GenerateGold7StagingTable(snapshotDate) + fmt.Printf("Running gold_7_active_od_rewards\n") + err = rc.Generate7ActiveODRewards(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_7_ActiveODRewards]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_7_active_od_rewards: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_8_operator_od_reward_amounts\n") + err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_8_operator_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_9_staker_od_reward_amounts\n") + err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_9_StakerODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_9_staker_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_10_avs_od_reward_amounts\n") + err = rc.GenerateGold10AvsODRewardAmountsTable(snapshotDate) + assert.Nil(t, err) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_10_AvsODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_10_avs_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + testStart = time.Now() + + fmt.Printf("Running gold_11_staging\n") + err = rc.GenerateGold11StagingTable(snapshotDate) assert.Nil(t, err) - rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_7_GoldStaging]) + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_11_GoldStaging]) assert.Nil(t, err) - fmt.Printf("\tRows in gold_7_staging: %v - [time: %v]\n", rows, time.Since(testStart)) + fmt.Printf("\tRows in gold_11_staging: %v - [time: %v]\n", rows, time.Since(testStart)) testStart = time.Now() - fmt.Printf("Running gold_8_final_table\n") - err = rc.GenerateGold8FinalTable(snapshotDate) + fmt.Printf("Running gold_12_final_table\n") + err = rc.GenerateGold12FinalTable(snapshotDate) assert.Nil(t, err) rows, err = getRowCountForTable(grm, "gold_table") assert.Nil(t, err) diff --git a/pkg/rewards/stakerShareSnapshots_test.go b/pkg/rewards/stakerShareSnapshots_test.go index abd5dbe8..31526dd4 100644 --- a/pkg/rewards/stakerShareSnapshots_test.go +++ b/pkg/rewards/stakerShareSnapshots_test.go @@ -67,7 +67,7 @@ func hydrateStakerShares(grm *gorm.DB, l *zap.Logger) error { res := grm.Exec(contents) if res.Error != nil { - l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error), zap.String("query", contents)) + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) return res.Error } return nil diff --git a/pkg/rewards/stakerShares_test.go b/pkg/rewards/stakerShares_test.go index d536ef9c..dfd090b6 100644 --- a/pkg/rewards/stakerShares_test.go +++ b/pkg/rewards/stakerShares_test.go @@ -55,7 +55,7 @@ func hydrateStakerShareDeltas(grm *gorm.DB, l *zap.Logger) error { res := grm.Exec(contents) if res.Error != nil { - l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error), zap.String("query", contents)) + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) return res.Error } return nil diff --git a/pkg/rewards/tables.go b/pkg/rewards/tables.go index 519f70b4..624ce4f9 100644 --- a/pkg/rewards/tables.go +++ b/pkg/rewards/tables.go @@ -89,3 +89,21 @@ type OperatorPISplitSnapshots struct { Split uint64 Snapshot time.Time } + +type OperatorDirectedRewards struct { + Avs string + RewardHash string + Token string + Operator string + OperatorIndex uint64 + Amount string + Strategy string + StrategyIndex uint64 + Multiplier string + StartTimestamp *time.Time + EndTimestamp *time.Time + Duration uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 +} diff --git a/pkg/rewardsUtils/rewardsUtils.go b/pkg/rewardsUtils/rewardsUtils.go index fcfa28c6..10ab3b6e 100644 --- a/pkg/rewardsUtils/rewardsUtils.go +++ b/pkg/rewardsUtils/rewardsUtils.go @@ -3,36 +3,45 @@ package rewardsUtils import ( "bytes" "fmt" + "text/template" + "github.com/Layr-Labs/sidecar/pkg/postgres/helpers" "github.com/Layr-Labs/sidecar/pkg/utils" "go.uber.org/zap" "gorm.io/gorm" - "text/template" ) var ( - Table_1_ActiveRewards = "gold_1_active_rewards" - Table_2_StakerRewardAmounts = "gold_2_staker_reward_amounts" - Table_3_OperatorRewardAmounts = "gold_3_operator_reward_amounts" - Table_4_RewardsForAll = "gold_4_rewards_for_all" - Table_5_RfaeStakers = "gold_5_rfae_stakers" - Table_6_RfaeOperators = "gold_6_rfae_operators" - Table_7_GoldStaging = "gold_7_staging" - Table_8_GoldTable = "gold_table" + Table_1_ActiveRewards = "gold_1_active_rewards" + Table_2_StakerRewardAmounts = "gold_2_staker_reward_amounts" + Table_3_OperatorRewardAmounts = "gold_3_operator_reward_amounts" + Table_4_RewardsForAll = "gold_4_rewards_for_all" + Table_5_RfaeStakers = "gold_5_rfae_stakers" + Table_6_RfaeOperators = "gold_6_rfae_operators" + Table_7_ActiveODRewards = "gold_7_active_od_rewards" + Table_8_OperatorODRewardAmounts = "gold_8_operator_od_reward_amounts" + Table_9_StakerODRewardAmounts = "gold_9_staker_od_reward_amounts" + Table_10_AvsODRewardAmounts = "gold_10_avs_od_reward_amounts" + Table_11_GoldStaging = "gold_11_staging" + Table_12_GoldTable = "gold_table" Sot_6_StakerOperatorStaging = "sot_6_staker_operator_staging" Sot_7_StakerOperatorTable = "staker_operator" ) var goldTableBaseNames = map[string]string{ - Table_1_ActiveRewards: Table_1_ActiveRewards, - Table_2_StakerRewardAmounts: Table_2_StakerRewardAmounts, - Table_3_OperatorRewardAmounts: Table_3_OperatorRewardAmounts, - Table_4_RewardsForAll: Table_4_RewardsForAll, - Table_5_RfaeStakers: Table_5_RfaeStakers, - Table_6_RfaeOperators: Table_6_RfaeOperators, - Table_7_GoldStaging: Table_7_GoldStaging, - Table_8_GoldTable: Table_8_GoldTable, + Table_1_ActiveRewards: Table_1_ActiveRewards, + Table_2_StakerRewardAmounts: Table_2_StakerRewardAmounts, + Table_3_OperatorRewardAmounts: Table_3_OperatorRewardAmounts, + Table_4_RewardsForAll: Table_4_RewardsForAll, + Table_5_RfaeStakers: Table_5_RfaeStakers, + Table_6_RfaeOperators: Table_6_RfaeOperators, + Table_7_ActiveODRewards: Table_7_ActiveODRewards, + Table_8_OperatorODRewardAmounts: Table_8_OperatorODRewardAmounts, + Table_9_StakerODRewardAmounts: Table_9_StakerODRewardAmounts, + Table_10_AvsODRewardAmounts: Table_10_AvsODRewardAmounts, + Table_11_GoldStaging: Table_11_GoldStaging, + Table_12_GoldTable: Table_12_GoldTable, Sot_6_StakerOperatorStaging: Sot_6_StakerOperatorStaging, } diff --git a/scripts/updateTestData.sh b/scripts/updateTestData.sh index f026f7f7..2e2b30ef 100755 --- a/scripts/updateTestData.sh +++ b/scripts/updateTestData.sh @@ -41,6 +41,9 @@ fi if [[ $NETWORK == "testnet-reduced" ]]; then bucketPath="${bucketPath}testnet-reduced/" fi +if [[ $NETWORK == "preprod-rewardsv2" ]]; then + bucketPath="${bucketPath}preprod-rewardsv2/" +fi aws s3 cp "${newVersion}.tar" $bucketPath From 04e19a4cad03d14e063a300f5cd35a535add1aee Mon Sep 17 00:00:00 2001 From: Sean McGary Date: Wed, 11 Dec 2024 15:51:05 -0600 Subject: [PATCH 2/2] Add check to see if rewards-v2 is enabled update snapshot date in test --- internal/config/config.go | 40 ++++++++++++++++++- pkg/pipeline/pipeline.go | 1 + pkg/rewards/10_goldAvsODRewardAmounts.go | 12 +++++- pkg/rewards/11_goldStaging.go | 21 ++++++++-- pkg/rewards/12_goldFinal.go | 2 +- pkg/rewards/1_goldActiveRewards.go | 2 +- pkg/rewards/2_goldStakerRewardAmounts.go | 2 +- pkg/rewards/3_goldOperatorRewardAmounts.go | 2 +- pkg/rewards/4_goldRewardsForAll.go | 2 +- pkg/rewards/5_goldRfaeStakers.go | 2 +- pkg/rewards/6_goldRfaeOperators.go | 2 +- pkg/rewards/7_goldActiveODRewards.go | 12 +++++- pkg/rewards/8_goldOperatorODRewardAmounts.go | 11 ++++- pkg/rewards/9_goldStakerODRewardAmounts.go | 12 +++++- pkg/rewards/combinedRewards.go | 2 +- .../operatorAvsRegistrationSnaphots.go | 2 +- pkg/rewards/operatorAvsSplitSnapshots.go | 2 +- pkg/rewards/operatorAvsStrategySnapshots.go | 2 +- pkg/rewards/operatorDirectedRewards.go | 2 +- pkg/rewards/operatorPISplitSnapshots.go | 2 +- pkg/rewards/operatorShareSnapshots.go | 2 +- pkg/rewards/operatorShares.go | 2 +- pkg/rewards/rewards.go | 2 +- pkg/rewards/rewardsV2_test.go | 37 +++++++++++------ pkg/rewards/stakerDelegationSnapshots.go | 2 +- .../1_stakerStrategyPayouts.go | 2 +- .../2_operatorStrategyRewards.go | 2 +- .../3_rewardsForAllStrategyPayouts.go | 2 +- .../4_rfaeStakerStrategyPayouts.go | 2 +- .../5_rfaeOperatorStrategyPayout.go | 2 +- .../6_stakerOperatorStaging.go | 2 +- .../stakerOperators/7_stakerOperator.go | 2 +- pkg/rewards/stakerShareSnapshots.go | 2 +- pkg/rewards/stakerShares.go | 2 +- pkg/rewardsUtils/rewardsUtils.go | 2 +- 35 files changed, 150 insertions(+), 50 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a6763ab1..a263bb5f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/spf13/viper" ) @@ -254,14 +255,14 @@ func (c *Config) GetForkDates() (ForkMap, error) { Fork_Amazon: "1970-01-01", // Amazon hard fork was never on preprod as we backfilled Fork_Nile: "2024-08-14", // Last calculation end timestamp was 8-13: https://holesky.etherscan.io/tx/0xb5a6855e88c79312b7c0e1c9f59ae9890b97f157ea27e69e4f0fadada4712b64#eventlog Fork_Panama: "2024-10-01", - Fork_Arno: "2024-12-04", + Fork_Arno: "2024-12-12", }, nil case Chain_Holesky: return ForkMap{ Fork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled Fork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog Fork_Panama: "2024-10-01", - Fork_Arno: "2024-12-12", + Fork_Arno: "2024-12-13", }, nil case Chain_Mainnet: return ForkMap{ @@ -296,6 +297,41 @@ func (c *Config) GetOperatorRestakedStrategiesStartBlock() uint64 { return 0 } +func (c *Config) ShouldSkipRewardsGeneration(blockNumber uint64) bool { + switch c.Chain { + case Chain_Preprod: + // During this period we deployed the rewards-v2 contracts before updating the sidecar. + // This results in missed events which have to be filled by some means. To fill them in, + // we needed to manually delete delete blocks >= 2871534 and re-index. The trouble here + // is that re-indexing introduces new state which was not present at the original process time. + if blockNumber >= 2871534 && blockNumber <= 2909856 { + return true + } + case Chain_Holesky: + // Skip rewards generation for holesky + case Chain_Mainnet: + // Skip rewards generation for mainnet + } + return false +} + +func (c *Config) IsRewardsV2EnabledForCutoffDate(cutoffDate string) (bool, error) { + forks, err := c.GetForkDates() + if err != nil { + return false, err + } + cutoffDateTime, err := time.Parse(time.DateOnly, cutoffDate) + if err != nil { + return false, errors.Join(fmt.Errorf("failed to parse cutoff date %s", cutoffDate), err) + } + arnoForkDateTime, err := time.Parse(time.DateOnly, forks[Fork_Arno]) + if err != nil { + return false, errors.Join(fmt.Errorf("failed to parse Arno fork date %s", forks[Fork_Arno]), err) + } + + return cutoffDateTime.Compare(arnoForkDateTime) >= 0, nil +} + // CanIgnoreIncorrectRewardsRoot returns true if the rewards root can be ignored for the given block number // // Due to inconsistencies in the rewards root calculation on testnet, we know that some roots diff --git a/pkg/pipeline/pipeline.go b/pkg/pipeline/pipeline.go index 957cdcaf..15d504d2 100644 --- a/pkg/pipeline/pipeline.go +++ b/pkg/pipeline/pipeline.go @@ -186,6 +186,7 @@ func (p *Pipeline) RunForFetchedBlock(ctx context.Context, block *fetcher.Fetche distributionRoots, err := p.stateManager.GetSubmittedDistributionRoots(blockNumber) if err == nil && distributionRoots != nil { for _, rs := range distributionRoots { + rewardStartTime := time.Now() // first check to see if the root was disabled. If it was, it's possible we introduced changes that diff --git a/pkg/rewards/10_goldAvsODRewardAmounts.go b/pkg/rewards/10_goldAvsODRewardAmounts.go index bd5e7180..19b5f77e 100644 --- a/pkg/rewards/10_goldAvsODRewardAmounts.go +++ b/pkg/rewards/10_goldAvsODRewardAmounts.go @@ -65,6 +65,16 @@ SELECT * FROM operator_token_sums ` func (rc *RewardsCalculator) GenerateGold10AvsODRewardAmountsTable(snapshotDate string) error { + rewardsV2Enabled, err := rc.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) + if err != nil { + rc.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) + return err + } + if !rewardsV2Enabled { + rc.logger.Sugar().Infow("Rewards v2 is not enabled for this cutoff date, skipping GenerateGold10AvsODRewardAmountsTable") + return nil + } + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) destTableName := allTableNames[rewardsUtils.Table_10_AvsODRewardAmounts] @@ -73,7 +83,7 @@ func (rc *RewardsCalculator) GenerateGold10AvsODRewardAmountsTable(snapshotDate zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_10_goldAvsODRewardAmountsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_10_goldAvsODRewardAmountsQuery, map[string]interface{}{ "destTableName": destTableName, "activeODRewardsTable": allTableNames[rewardsUtils.Table_7_ActiveODRewards], }) diff --git a/pkg/rewards/11_goldStaging.go b/pkg/rewards/11_goldStaging.go index 677078ae..2ad37727 100644 --- a/pkg/rewards/11_goldStaging.go +++ b/pkg/rewards/11_goldStaging.go @@ -56,6 +56,7 @@ rewards_for_all_earners_operators AS ( operator_tokens as amount FROM {{.rfaeOperatorTable}} ), +{{ if .enableRewardsV2 }} operator_od_rewards AS ( SELECT DISTINCT -- We can select DISTINCT here because the operator's tokens are the same for each strategy in the reward hash @@ -86,6 +87,7 @@ avs_od_rewards AS ( avs_tokens as amount FROM {{.avsODRewardAmountsTable}} ), +{{ end }} combined_rewards AS ( SELECT * FROM operator_rewards UNION ALL @@ -96,12 +98,14 @@ combined_rewards AS ( 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 }} ), -- Dedupe earners, primarily operators who are also their own staker. deduped_earners AS ( @@ -131,7 +135,14 @@ func (rc *RewardsCalculator) GenerateGold11StagingTable(snapshotDate string) err zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_11_goldStagingQuery, map[string]string{ + isRewardsV2Enabled, err := rc.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) + if err != nil { + rc.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) + return err + } + rc.logger.Sugar().Infow("Is RewardsV2 enabled?", "enabled", isRewardsV2Enabled) + + query, err := rewardsUtils.RenderQueryTemplate(_11_goldStagingQuery, map[string]interface{}{ "destTableName": destTableName, "stakerRewardAmountsTable": allTableNames[rewardsUtils.Table_2_StakerRewardAmounts], "operatorRewardAmountsTable": allTableNames[rewardsUtils.Table_3_OperatorRewardAmounts], @@ -141,6 +152,7 @@ func (rc *RewardsCalculator) GenerateGold11StagingTable(snapshotDate string) err "operatorODRewardAmountsTable": allTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts], "stakerODRewardAmountsTable": allTableNames[rewardsUtils.Table_9_StakerODRewardAmounts], "avsODRewardAmountsTable": allTableNames[rewardsUtils.Table_10_AvsODRewardAmounts], + "enableRewardsV2": isRewardsV2Enabled, }) if err != nil { rc.logger.Sugar().Errorw("Failed to render query template", "error", err) @@ -167,21 +179,22 @@ func (rc *RewardsCalculator) ListGoldStagingRowsForSnapshot(snapshotDate string) allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) results := make([]*GoldStagingRow, 0) - query, err := rewardsUtils.RenderQueryTemplate(` + query := ` SELECT earner, snapshot::text as snapshot, reward_hash, token, amount - FROM {{.goldStagingTable}} WHERE DATE(snapshot) < @cutoffDate`, map[string]string{ + FROM {{.goldStagingTable}} WHERE DATE(snapshot) < @cutoffDate` + query, err := rewardsUtils.RenderQueryTemplate(query, map[string]interface{}{ "goldStagingTable": allTableNames[rewardsUtils.Table_11_GoldStaging], }) if err != nil { rc.logger.Sugar().Errorw("Failed to render query template", "error", err) return nil, err } - res := rc.grm.Raw(query, + res := rc.grm.Debug().Raw(query, sql.Named("cutoffDate", snapshotDate), ).Scan(&results) if res.Error != nil { diff --git a/pkg/rewards/12_goldFinal.go b/pkg/rewards/12_goldFinal.go index c65e7096..f993b7b2 100644 --- a/pkg/rewards/12_goldFinal.go +++ b/pkg/rewards/12_goldFinal.go @@ -33,7 +33,7 @@ func (rc *RewardsCalculator) GenerateGold12FinalTable(snapshotDate string) error zap.String("cutoffDate", snapshotDate), ) - query, err := rewardsUtils.RenderQueryTemplate(_12_goldFinalQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_12_goldFinalQuery, map[string]interface{}{ "goldStagingTable": allTableNames[rewardsUtils.Table_11_GoldStaging], }) if err != nil { diff --git a/pkg/rewards/1_goldActiveRewards.go b/pkg/rewards/1_goldActiveRewards.go index 9c9ad3c2..d5a7377a 100644 --- a/pkg/rewards/1_goldActiveRewards.go +++ b/pkg/rewards/1_goldActiveRewards.go @@ -112,7 +112,7 @@ func (r *RewardsCalculator) Generate1ActiveRewards(snapshotDate string) error { zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_1_goldActiveRewardsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_1_goldActiveRewardsQuery, map[string]interface{}{ "destTableName": destTableName, "rewardsStart": rewardsStart, "cutoffDate": snapshotDate, diff --git a/pkg/rewards/2_goldStakerRewardAmounts.go b/pkg/rewards/2_goldStakerRewardAmounts.go index a4f510cc..52cbcb76 100644 --- a/pkg/rewards/2_goldStakerRewardAmounts.go +++ b/pkg/rewards/2_goldStakerRewardAmounts.go @@ -153,7 +153,7 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate zap.String("arnoHardforkDate", forks[config.Fork_Arno]), ) - query, err := rewardsUtils.RenderQueryTemplate(_2_goldStakerRewardAmountsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_2_goldStakerRewardAmountsQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], }) diff --git a/pkg/rewards/3_goldOperatorRewardAmounts.go b/pkg/rewards/3_goldOperatorRewardAmounts.go index 787ec446..13e76ace 100644 --- a/pkg/rewards/3_goldOperatorRewardAmounts.go +++ b/pkg/rewards/3_goldOperatorRewardAmounts.go @@ -45,7 +45,7 @@ func (rc *RewardsCalculator) GenerateGold3OperatorRewardAmountsTable(snapshotDat zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_3_goldOperatorRewardAmountsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_3_goldOperatorRewardAmountsQuery, map[string]interface{}{ "destTableName": destTableName, "stakerRewardAmountsTable": allTableNames[rewardsUtils.Table_2_StakerRewardAmounts], }) diff --git a/pkg/rewards/4_goldRewardsForAll.go b/pkg/rewards/4_goldRewardsForAll.go index a9464c76..46038352 100644 --- a/pkg/rewards/4_goldRewardsForAll.go +++ b/pkg/rewards/4_goldRewardsForAll.go @@ -76,7 +76,7 @@ func (rc *RewardsCalculator) GenerateGold4RewardsForAllTable(snapshotDate string zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_4_goldRewardsForAllQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_4_goldRewardsForAllQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], }) diff --git a/pkg/rewards/5_goldRfaeStakers.go b/pkg/rewards/5_goldRfaeStakers.go index 9d1fd7aa..af605f5f 100644 --- a/pkg/rewards/5_goldRfaeStakers.go +++ b/pkg/rewards/5_goldRfaeStakers.go @@ -142,7 +142,7 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string, zap.String("arnoHardforkDate", forks[config.Fork_Arno]), ) - query, err := rewardsUtils.RenderQueryTemplate(_5_goldRfaeStakersQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_5_goldRfaeStakersQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], }) diff --git a/pkg/rewards/6_goldRfaeOperators.go b/pkg/rewards/6_goldRfaeOperators.go index c17d80fe..2def909d 100644 --- a/pkg/rewards/6_goldRfaeOperators.go +++ b/pkg/rewards/6_goldRfaeOperators.go @@ -45,7 +45,7 @@ func (rc *RewardsCalculator) GenerateGold6RfaeOperatorsTable(snapshotDate string zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_6_goldRfaeOperatorsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_6_goldRfaeOperatorsQuery, map[string]interface{}{ "destTableName": destTableName, "rfaeStakersTable": allTableNames[rewardsUtils.Table_5_RfaeStakers], }) diff --git a/pkg/rewards/7_goldActiveODRewards.go b/pkg/rewards/7_goldActiveODRewards.go index ee84cc1d..902c9438 100644 --- a/pkg/rewards/7_goldActiveODRewards.go +++ b/pkg/rewards/7_goldActiveODRewards.go @@ -127,6 +127,16 @@ SELECT * FROM active_rewards_final // @param startDate: The lower bound of when to calculate rewards from. If we're running rewards for the first time, // this will be "1970-01-01". If this is a subsequent run, this will be the last snapshot date. func (r *RewardsCalculator) Generate7ActiveODRewards(snapshotDate string) error { + rewardsV2Enabled, err := r.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) + if err != nil { + r.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) + return err + } + if !rewardsV2Enabled { + r.logger.Sugar().Infow("Rewards v2 is not enabled for this cutoff date, skipping Generate7ActiveODRewards") + return nil + } + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) destTableName := allTableNames[rewardsUtils.Table_7_ActiveODRewards] @@ -138,7 +148,7 @@ func (r *RewardsCalculator) Generate7ActiveODRewards(snapshotDate string) error zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_7_goldActiveODRewardsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_7_goldActiveODRewardsQuery, map[string]interface{}{ "destTableName": destTableName, "rewardsStart": rewardsStart, "cutoffDate": snapshotDate, diff --git a/pkg/rewards/8_goldOperatorODRewardAmounts.go b/pkg/rewards/8_goldOperatorODRewardAmounts.go index 8ae90773..5bb9718f 100644 --- a/pkg/rewards/8_goldOperatorODRewardAmounts.go +++ b/pkg/rewards/8_goldOperatorODRewardAmounts.go @@ -66,6 +66,15 @@ SELECT * FROM operator_splits ` func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotDate string) error { + rewardsV2Enabled, err := rc.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) + if err != nil { + rc.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) + return err + } + if !rewardsV2Enabled { + rc.logger.Sugar().Infow("Rewards v2 is not enabled for this cutoff date, skipping GenerateGold8OperatorODRewardAmountsTable") + return nil + } allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) destTableName := allTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts] @@ -74,7 +83,7 @@ func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotD zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_8_goldOperatorODRewardAmountsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_8_goldOperatorODRewardAmountsQuery, map[string]interface{}{ "destTableName": destTableName, "activeODRewardsTable": allTableNames[rewardsUtils.Table_7_ActiveODRewards], }) diff --git a/pkg/rewards/9_goldStakerODRewardAmounts.go b/pkg/rewards/9_goldStakerODRewardAmounts.go index 5b66a75f..5956f39c 100644 --- a/pkg/rewards/9_goldStakerODRewardAmounts.go +++ b/pkg/rewards/9_goldStakerODRewardAmounts.go @@ -115,6 +115,16 @@ SELECT * FROM staker_reward_amounts ` func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDate string) error { + rewardsV2Enabled, err := rc.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) + if err != nil { + rc.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) + return err + } + if !rewardsV2Enabled { + rc.logger.Sugar().Infow("Rewards v2 is not enabled for this cutoff date, skipping GenerateGold9StakerODRewardAmountsTable") + return nil + } + allTableNames := rewardsUtils.GetGoldTableNames(snapshotDate) destTableName := allTableNames[rewardsUtils.Table_9_StakerODRewardAmounts] @@ -123,7 +133,7 @@ func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDat zap.String("destTableName", destTableName), ) - query, err := rewardsUtils.RenderQueryTemplate(_9_goldStakerODRewardAmountsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_9_goldStakerODRewardAmountsQuery, map[string]interface{}{ "destTableName": destTableName, "activeODRewardsTable": allTableNames[rewardsUtils.Table_7_ActiveODRewards], }) diff --git a/pkg/rewards/combinedRewards.go b/pkg/rewards/combinedRewards.go index 290eba6d..c4d90cf0 100644 --- a/pkg/rewards/combinedRewards.go +++ b/pkg/rewards/combinedRewards.go @@ -45,7 +45,7 @@ const rewardsCombinedQuery = ` func (r *RewardsCalculator) GenerateAndInsertCombinedRewards(snapshotDate string) error { tableName := "combined_rewards" - query, err := rewardsUtils.RenderQueryTemplate(rewardsCombinedQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(rewardsCombinedQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorAvsRegistrationSnaphots.go b/pkg/rewards/operatorAvsRegistrationSnaphots.go index 9ea856e3..fc16a4d7 100644 --- a/pkg/rewards/operatorAvsRegistrationSnaphots.go +++ b/pkg/rewards/operatorAvsRegistrationSnaphots.go @@ -105,7 +105,7 @@ CROSS JOIN generate_series(DATE(start_time), DATE(end_time) - interval '1' day, func (r *RewardsCalculator) GenerateAndInsertOperatorAvsRegistrationSnapshots(snapshotDate string) error { tableName := "operator_avs_registration_snapshots" - query, err := rewardsUtils.RenderQueryTemplate(operatorAvsRegistrationSnapshotsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorAvsRegistrationSnapshotsQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorAvsSplitSnapshots.go b/pkg/rewards/operatorAvsSplitSnapshots.go index 5456af2b..30a593d8 100644 --- a/pkg/rewards/operatorAvsSplitSnapshots.go +++ b/pkg/rewards/operatorAvsSplitSnapshots.go @@ -86,7 +86,7 @@ select * from final_results func (r *RewardsCalculator) GenerateAndInsertOperatorAvsSplitSnapshots(snapshotDate string) error { tableName := "operator_avs_split_snapshots" - query, err := rewardsUtils.RenderQueryTemplate(operatorAvsSplitSnapshotQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorAvsSplitSnapshotQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorAvsStrategySnapshots.go b/pkg/rewards/operatorAvsStrategySnapshots.go index 7e4f2174..3a15d93f 100644 --- a/pkg/rewards/operatorAvsStrategySnapshots.go +++ b/pkg/rewards/operatorAvsStrategySnapshots.go @@ -144,7 +144,7 @@ func (r *RewardsCalculator) GenerateAndInsertOperatorAvsStrategySnapshots(snapsh tableName := "operator_avs_strategy_snapshots" contractAddresses := r.globalConfig.GetContractsMapForChain() - query, err := rewardsUtils.RenderQueryTemplate(operatorAvsStrategyWindowsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorAvsStrategyWindowsQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorDirectedRewards.go b/pkg/rewards/operatorDirectedRewards.go index d0fa074c..9cc7d896 100644 --- a/pkg/rewards/operatorDirectedRewards.go +++ b/pkg/rewards/operatorDirectedRewards.go @@ -46,7 +46,7 @@ const operatorDirectedRewardsQuery = ` func (r *RewardsCalculator) GenerateAndInsertOperatorDirectedRewards(snapshotDate string) error { tableName := "operator_directed_rewards" - query, err := rewardsUtils.RenderQueryTemplate(operatorDirectedRewardsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorDirectedRewardsQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorPISplitSnapshots.go b/pkg/rewards/operatorPISplitSnapshots.go index ef212e47..2f9a5ed7 100644 --- a/pkg/rewards/operatorPISplitSnapshots.go +++ b/pkg/rewards/operatorPISplitSnapshots.go @@ -85,7 +85,7 @@ select * from final_results func (r *RewardsCalculator) GenerateAndInsertOperatorPISplitSnapshots(snapshotDate string) error { tableName := "operator_pi_split_snapshots" - query, err := rewardsUtils.RenderQueryTemplate(operatorPISplitSnapshotQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorPISplitSnapshotQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorShareSnapshots.go b/pkg/rewards/operatorShareSnapshots.go index f83e003e..35aa78d8 100644 --- a/pkg/rewards/operatorShareSnapshots.go +++ b/pkg/rewards/operatorShareSnapshots.go @@ -50,7 +50,7 @@ FROM func (r *RewardsCalculator) GenerateAndInsertOperatorShareSnapshots(snapshotDate string) error { tableName := "operator_share_snapshots" - query, err := rewardsUtils.RenderQueryTemplate(operatorShareSnapshotsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorShareSnapshotsQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/operatorShares.go b/pkg/rewards/operatorShares.go index 88c42517..3af9775a 100644 --- a/pkg/rewards/operatorShares.go +++ b/pkg/rewards/operatorShares.go @@ -19,7 +19,7 @@ const operatorSharesQuery = ` func (r *RewardsCalculator) GenerateAndInsertOperatorShares(snapshotDate string) error { tableName := "operator_shares" - query, err := rewardsUtils.RenderQueryTemplate(operatorSharesQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(operatorSharesQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/rewards.go b/pkg/rewards/rewards.go index 7c072e4e..c8d714c8 100644 --- a/pkg/rewards/rewards.go +++ b/pkg/rewards/rewards.go @@ -493,7 +493,7 @@ func (rc *RewardsCalculator) fetchRewardsForSnapshot(snapshotDate string) ([]*Re where snapshot <= date '{{.snapshotDate}}' group by 1, 2 order by snapshot desc - `, map[string]string{"snapshotDate": snapshotDate}) + `, map[string]interface{}{"snapshotDate": snapshotDate}) if err != nil { return nil, err diff --git a/pkg/rewards/rewardsV2_test.go b/pkg/rewards/rewardsV2_test.go index 49d9c6ee..cd890901 100644 --- a/pkg/rewards/rewardsV2_test.go +++ b/pkg/rewards/rewardsV2_test.go @@ -72,7 +72,7 @@ func Test_RewardsV2(t *testing.T) { t.Log("Hydrated tables") snapshotDates := []string{ - "2024-12-11", + "2024-12-12", } fmt.Printf("Hydration duration: %v\n", time.Since(testStart)) @@ -148,36 +148,47 @@ func Test_RewardsV2(t *testing.T) { // ------------------------------------------------------------------------ // Rewards V2 // ------------------------------------------------------------------------ + rewardsV2Enabled, err := cfg.IsRewardsV2EnabledForCutoffDate(snapshotDate) + assert.Nil(t, err) + fmt.Printf("Running gold_7_active_od_rewards\n") err = rc.Generate7ActiveODRewards(snapshotDate) assert.Nil(t, err) - rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_7_ActiveODRewards]) - assert.Nil(t, err) - fmt.Printf("\tRows in gold_7_active_od_rewards: %v - [time: %v]\n", rows, time.Since(testStart)) + if rewardsV2Enabled { + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_7_ActiveODRewards]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_7_active_od_rewards: %v - [time: %v]\n", rows, time.Since(testStart)) + } testStart = time.Now() fmt.Printf("Running gold_8_operator_od_reward_amounts\n") err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate) assert.Nil(t, err) - rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts]) - assert.Nil(t, err) - fmt.Printf("\tRows in gold_8_operator_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + if rewardsV2Enabled { + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_8_operator_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + } testStart = time.Now() fmt.Printf("Running gold_9_staker_od_reward_amounts\n") err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate) assert.Nil(t, err) - rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_9_StakerODRewardAmounts]) - assert.Nil(t, err) - fmt.Printf("\tRows in gold_9_staker_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + if rewardsV2Enabled { + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_9_StakerODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_9_staker_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + } testStart = time.Now() fmt.Printf("Running gold_10_avs_od_reward_amounts\n") err = rc.GenerateGold10AvsODRewardAmountsTable(snapshotDate) assert.Nil(t, err) - rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_10_AvsODRewardAmounts]) - assert.Nil(t, err) - fmt.Printf("\tRows in gold_10_avs_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + if rewardsV2Enabled { + rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_10_AvsODRewardAmounts]) + assert.Nil(t, err) + fmt.Printf("\tRows in gold_10_avs_od_reward_amounts: %v - [time: %v]\n", rows, time.Since(testStart)) + } testStart = time.Now() fmt.Printf("Running gold_11_staging\n") diff --git a/pkg/rewards/stakerDelegationSnapshots.go b/pkg/rewards/stakerDelegationSnapshots.go index 58edc9ad..117d2018 100644 --- a/pkg/rewards/stakerDelegationSnapshots.go +++ b/pkg/rewards/stakerDelegationSnapshots.go @@ -62,7 +62,7 @@ select * from final_results func (r *RewardsCalculator) GenerateAndInsertStakerDelegationSnapshots(snapshotDate string) error { tableName := "staker_delegation_snapshots" - query, err := rewardsUtils.RenderQueryTemplate(stakerDelegationSnapshotsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(stakerDelegationSnapshotsQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go b/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go index 88bda541..6ad5dbcc 100644 --- a/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go +++ b/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go @@ -128,7 +128,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert1StakerStrategyPayouts(cut tableName := "sot_1_staker_strategy_payouts" allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) - query, err := rewardsUtils.RenderQueryTemplate(_1_stakerStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_1_stakerStrategyPayoutsQuery, map[string]interface{}{ "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], "stakerRewardAmountsTable": allTableNames[rewardsUtils.Table_2_StakerRewardAmounts], }) diff --git a/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go b/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go index 0a0824b6..d9fa1d15 100644 --- a/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go +++ b/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go @@ -112,7 +112,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert2OperatorStrategyRewards(c tableName := "sot_2_operator_strategy_rewards" allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) - query, err := rewardsUtils.RenderQueryTemplate(_2_operatorStrategyRewardsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_2_operatorStrategyRewardsQuery, map[string]interface{}{ "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], "operatorRewardAmountsTable": allTableNames[rewardsUtils.Table_3_OperatorRewardAmounts], }) diff --git a/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go b/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go index 4c95ad7b..082dd1eb 100644 --- a/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go +++ b/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go @@ -102,7 +102,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert3RewardsForAllStrategyPayo tableName := "sot_3_rewards_for_all_strategy_payout" allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) - query, err := rewardsUtils.RenderQueryTemplate(_3_rewardsForAllStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_3_rewardsForAllStrategyPayoutsQuery, map[string]interface{}{ "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], "rewardsForAllTable": allTableNames[rewardsUtils.Table_4_RewardsForAll], }) diff --git a/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go b/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go index dfb1f71b..4968c0a1 100644 --- a/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go +++ b/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go @@ -134,7 +134,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert4RfaeStakerStrategyPayout( tableName := "sot_4_rfae_staker_strategy_payout" allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) - query, err := rewardsUtils.RenderQueryTemplate(_4_rfaeStakerStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_4_rfaeStakerStrategyPayoutsQuery, map[string]interface{}{ "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], "rfaeStakerTable": allTableNames[rewardsUtils.Table_5_RfaeStakers], }) diff --git a/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go b/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go index 7a532572..a0d35ce3 100644 --- a/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go +++ b/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go @@ -123,7 +123,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert5RfaeOperatorStrategyPayou tableName := "sot_5_rfae_operator_strategy_payout" allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) - query, err := rewardsUtils.RenderQueryTemplate(_5_rfaeOperatorStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_5_rfaeOperatorStrategyPayoutsQuery, map[string]interface{}{ "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], "rfaeOperatorTable": allTableNames[rewardsUtils.Table_6_RfaeOperators], }) diff --git a/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go b/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go index 151c6021..514f98eb 100644 --- a/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go +++ b/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go @@ -113,7 +113,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert6StakerOperatorStaging(cut zap.String("cutoffDate", cutoffDate), ) - query, err := rewardsUtils.RenderQueryTemplate(_6_stakerOperatorsStaging, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_6_stakerOperatorsStaging, map[string]interface{}{ "destTableName": destTableName, }) if err != nil { diff --git a/pkg/rewards/stakerOperators/7_stakerOperator.go b/pkg/rewards/stakerOperators/7_stakerOperator.go index 8d54d54d..2f5bb9f6 100644 --- a/pkg/rewards/stakerOperators/7_stakerOperator.go +++ b/pkg/rewards/stakerOperators/7_stakerOperator.go @@ -62,7 +62,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert7StakerOperator(cutoffDate zap.String("cutoffDate", cutoffDate), ) - query, err := rewardsUtils.RenderQueryTemplate(_7_stakerOperator, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_7_stakerOperator, map[string]interface{}{ "destTableName": destTableName, "stakerOperatorStaging": allTableNames[rewardsUtils.Sot_6_StakerOperatorStaging], }) diff --git a/pkg/rewards/stakerShareSnapshots.go b/pkg/rewards/stakerShareSnapshots.go index f2a2640c..1fdace1b 100644 --- a/pkg/rewards/stakerShareSnapshots.go +++ b/pkg/rewards/stakerShareSnapshots.go @@ -50,7 +50,7 @@ CROSS JOIN func (r *RewardsCalculator) GenerateAndInsertStakerShareSnapshots(snapshotDate string) error { tableName := "staker_share_snapshots" - query, err := rewardsUtils.RenderQueryTemplate(stakerShareSnapshotsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(stakerShareSnapshotsQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewards/stakerShares.go b/pkg/rewards/stakerShares.go index 66fda671..a8b16171 100644 --- a/pkg/rewards/stakerShares.go +++ b/pkg/rewards/stakerShares.go @@ -21,7 +21,7 @@ const stakerSharesQuery = ` func (r *RewardsCalculator) GenerateAndInsertStakerShares(snapshotDate string) error { tableName := "staker_shares" - query, err := rewardsUtils.RenderQueryTemplate(stakerSharesQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(stakerSharesQuery, map[string]interface{}{ "cutoffDate": snapshotDate, }) if err != nil { diff --git a/pkg/rewardsUtils/rewardsUtils.go b/pkg/rewardsUtils/rewardsUtils.go index 10ab3b6e..5d5ac980 100644 --- a/pkg/rewardsUtils/rewardsUtils.go +++ b/pkg/rewardsUtils/rewardsUtils.go @@ -54,7 +54,7 @@ func GetGoldTableNames(snapshotDate string) map[string]string { return tableNames } -func RenderQueryTemplate(query string, variables map[string]string) (string, error) { +func RenderQueryTemplate(query string, variables map[string]interface{}) (string, error) { queryTmpl := template.Must(template.New("").Parse(query)) var dest bytes.Buffer