diff --git a/.gitignore b/.gitignore index 1d3760f9..58930609 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /*.sw* *.terraform *.terraform* +*.sql +*.dump /bin /scripts/runLocal sidecar.db* @@ -30,5 +32,5 @@ node_modules chart_releases /snapshots/**/*.sql /snapshots/**/*.csv -*.sql -*.dump + +.env \ No newline at end of file diff --git a/.testdataVersion b/.testdataVersion index 72b6e54f..423fd962 100644 --- a/.testdataVersion +++ b/.testdataVersion @@ -1 +1 @@ -d67ef5d895bdc0ccd6a006a1683ac3e58f820ad0 +da8f00be49d1447f22934629d9d6819e3d763af9 diff --git a/go.mod b/go.mod index 932ecec8..161a6aea 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/wealdtech/go-merkletree/v2 v2.6.0 github.com/wk8/go-ordered-map/v2 v2.1.8 go.uber.org/zap v1.27.0 + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/grpc v1.65.0 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.10 diff --git a/go.sum b/go.sum index c09d6acd..0c8f4b20 100644 --- a/go.sum +++ b/go.sum @@ -670,6 +670,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/internal/config/config.go b/internal/config/config.go index a251c949..8c3d0ed1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,9 +3,11 @@ package config import ( "errors" "fmt" - "github.com/spf13/viper" "strconv" "strings" + "time" + + "github.com/spf13/viper" ) type EnvScope string @@ -29,6 +31,7 @@ const ( Fork_Nile ForkName = "nile" Fork_Amazon ForkName = "amazon" Fork_Panama ForkName = "panama" + Fork_Arno ForkName = "arno" ENV_PREFIX = "SIDECAR" ) @@ -252,18 +255,21 @@ 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-11", }, 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-13", }, nil case Chain_Mainnet: return ForkMap{ Fork_Amazon: "2024-08-02", // Last calculation end timestamp was 8-01: https://etherscan.io/tx/0x2aff6f7b0132092c05c8f6f41a5e5eeeb208aa0d95ebcc9022d7823e343dd012#eventlog Fork_Nile: "2024-08-12", // Last calculation end timestamp was 8-11: https://etherscan.io/tx/0x922d29d93c02d189fc2332041f01a80e0007cd7a625a5663ef9d30082f7ef66f#eventlog Fork_Panama: "2024-10-01", + Fork_Arno: "2025-01-07", }, nil } return nil, errors.New("unsupported chain") @@ -291,6 +297,23 @@ func (c *Config) GetOperatorRestakedStrategiesStartBlock() uint64 { return 0 } +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 @@ -306,6 +329,11 @@ func (c *Config) CanIgnoreIncorrectRewardsRoot(blockNumber uint64) bool { if blockNumber == 2812052 { return true } + + // ignore rewards-v2 deployment/testing range + if blockNumber >= 2877938 && blockNumber <= 2909856 { + return true + } case Chain_Holesky: // roughly 2024-08-01 if blockNumber < 2046020 { 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..e02385b1 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-13' +``` 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..ed331621 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-13' + ) +select + operator, + avs, + status as registered, + log_index, + block_number +from filtered +``` + ## Expected results _See `generateExpectedResults.sql`_ diff --git a/internal/tests/testdata/operatorAvsRegistrationSnapshots/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/operatorAvsRegistrationSnapshots/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..33ded9bd --- /dev/null +++ b/internal/tests/testdata/operatorAvsRegistrationSnapshots/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,67 @@ +COPY ( + with filtered as ( + select * from dbt_preprod_holesky_rewards.operator_avs_status + where block_time < '2024-12-11' +), +marked_statuses AS ( + SELECT + operator, + avs, + registered, + block_time, + block_date, + LEAD(block_time) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS next_block_time, + LEAD(registered) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS next_registration_status, + LEAD(block_date) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS next_block_date, + LAG(registered) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS prev_registered, + LAG(block_date) OVER (PARTITION BY operator, avs ORDER BY block_time ASC, log_index ASC) AS prev_block_date + FROM filtered +), + removed_same_day_deregistrations AS ( + SELECT * from marked_statuses + WHERE NOT ( + (registered = TRUE AND + COALESCE(next_registration_status = FALSE, false) AND + COALESCE(block_date = next_block_date, false)) OR + (registered = FALSE AND + COALESCE(prev_registered = TRUE, false) and + COALESCE(block_date = prev_block_date, false) + ) + ) + ), + registration_periods AS ( + SELECT + operator, + avs, + block_time AS start_time, + COALESCE(next_block_time, TIMESTAMP '2024-12-11') AS end_time, + registered + FROM removed_same_day_deregistrations + WHERE registered = TRUE + ), + registration_windows_extra as ( + SELECT + operator, + avs, + date_trunc('day', start_time) + interval '1' day as start_time, + date_trunc('day', end_time) as end_time + FROM registration_periods + ), + operator_avs_registration_windows as ( + SELECT * from registration_windows_extra + WHERE start_time != end_time + ), + cleaned_records AS ( + SELECT * FROM operator_avs_registration_windows + WHERE start_time < end_time + ), + final_results as ( + SELECT + operator, + avs, + 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/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..1bd489dd --- /dev/null +++ b/internal/tests/testdata/operatorDirectedRewardSubmissions/README.md @@ -0,0 +1,57 @@ +## preprod rewards-v2 + +```sql +WITH strategies AS ( + SELECT + tl.*, + lower(arguments #>> '{2, Value}') as reward_hash, + 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 + lower(arguments #>> '{2, Value}') as reward_hash, + operator_data, + operator_data->>'operator' as operator, + 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' + ), + joined_data as ( +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 + inner join operators on( + strategies.reward_hash = operators.reward_hash + ) + ) +select * from joined_data +``` 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..24d9d73c 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-13' +``` + ## Expected results _See `generateExpectedResults.sql`_ diff --git a/internal/tests/testdata/operatorRestakedStrategies/preprodRewardsV2_generateExpectedResults.sql b/internal/tests/testdata/operatorRestakedStrategies/preprodRewardsV2_generateExpectedResults.sql new file mode 100644 index 00000000..52eceb25 --- /dev/null +++ b/internal/tests/testdata/operatorRestakedStrategies/preprodRewardsV2_generateExpectedResults.sql @@ -0,0 +1,112 @@ +copy (with ranked_records AS ( + SELECT + lower(operator) as operator, + lower(avs) as avs, + lower(strategy) as strategy, + block_time, + date_trunc('day', CAST(block_time as timestamp(6))) + interval '1' day as start_time, + ROW_NUMBER() OVER ( + PARTITION BY operator, avs, strategy, date_trunc('day', CAST(block_time as timestamp(6))) + interval '1' day + ORDER BY block_time DESC + ) AS rn + FROM public.operator_restaked_strategies + WHERE avs_directory_address = lower('0x141d6995556135d4997b2ff72eb443be300353bc') + and block_time < '2024-12-11' +), + latest_records AS ( + SELECT + operator, + avs, + strategy, + start_time, + block_time + FROM ranked_records + WHERE rn = 1 + ), + grouped_records AS ( + SELECT + operator, + avs, + strategy, + start_time, + LEAD(start_time) OVER ( + PARTITION BY operator, avs, strategy + ORDER BY start_time ASC + ) AS next_start_time + FROM latest_records + ), + parsed_ranges AS ( + SELECT + operator, + avs, + strategy, + start_time, + CASE + WHEN next_start_time IS NULL OR next_start_time > start_time + INTERVAL '1' DAY THEN start_time + ELSE next_start_time + END AS end_time + FROM grouped_records + ), + active_windows as ( + SELECT * + FROM parsed_ranges + WHERE start_time != end_time + ), + gaps_and_islands AS ( + SELECT + operator, + avs, + strategy, + start_time, + end_time, + LAG(end_time) OVER(PARTITION BY operator, avs, strategy ORDER BY start_time) as prev_end_time + FROM active_windows + ), + island_detection AS ( + SELECT operator, avs, strategy, start_time, end_time, prev_end_time, + CASE + WHEN prev_end_time = start_time THEN 0 + ELSE 1 + END as new_island + FROM gaps_and_islands + ), + island_groups AS ( + SELECT + operator, + avs, + strategy, + start_time, + end_time, + SUM(new_island) OVER ( + PARTITION BY operator, avs, strategy ORDER BY start_time + ) AS island_id + FROM island_detection + ), + operator_avs_strategy_windows AS ( + SELECT + operator, + avs, + strategy, + MIN(start_time) AS start_time, + MAX(end_time) AS end_time + FROM island_groups + GROUP BY operator, avs, strategy, island_id + ORDER BY operator, avs, strategy, start_time + ), + cleaned_records AS ( + SELECT * FROM operator_avs_strategy_windows + WHERE start_time < end_time + ), + final_results as ( +SELECT + operator, + avs, + strategy, + 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 DELIMITER ',' CSV HEADER; diff --git a/internal/tests/testdata/operatorShareSnapshots/README.md b/internal/tests/testdata/operatorShareSnapshots/README.md index 2b55bd90..3b6165b8 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-13' +``` + ## 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..b8c5cfc8 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-13' + + 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-13' + ) 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..96a59a5b 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-13' +``` + ```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..a805cee2 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-13' +``` + ## 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..468f48d2 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_preprod_holesky_rewards.staker_deposits + where block_date < '2024-12-13' + + 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-13' + + 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-13' + + 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-13' + ) 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..57fca286 100644 --- a/pkg/contractStore/coreContracts/preprod.json +++ b/pkg/contractStore/coreContracts/preprod.json @@ -89,6 +89,19 @@ "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" + }, { + "contract_address": "0xe1200acdec6aef63005bba5f0f48cd719cc37040", + "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": "776118dac9d55b009dd3ec36e2595d1bd24ec5dba8bca55d8fbb444efb2ed469" + }, { + "contract_address": "0xdd6cf6cf3b60219c0e3627d595a44e09098c436e", + "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": "c3537671a1111d3bba88e2de3f25707ccedebca0cfac7ba82f805bfeb582ad8d" } ], "proxy_contracts": [ @@ -171,6 +184,21 @@ "contract_address": "0xb22ef643e1e067c994019a4c19e403253c05c2b0", "proxy_contract_address": "0x7523b42b081c30fa245aa4039c645e36746fc400", "block_number": 2282920 + }, + { + "contract_address": "0xb22ef643e1e067c994019a4c19e403253c05c2b0", + "proxy_contract_address": "0x096694a4c8c2c13a005a200309d64995c08ed065", + "block_number": 2871534 + }, + { + "contract_address": "0xb22ef643e1e067c994019a4c19e403253c05c2b0", + "proxy_contract_address": "0xe1200acdec6aef63005bba5f0f48cd719cc37040", + "block_number": 2910657 + }, + { + "contract_address": "0xb22ef643e1e067c994019a4c19e403253c05c2b0", + "proxy_contract_address": "0xdd6cf6cf3b60219c0e3627d595a44e09098c436e", + "block_number": 2919262 } ] } diff --git a/pkg/eigenState/eigenState.go b/pkg/eigenState/eigenState.go index b95f4ed0..ecdac8c9 100644 --- a/pkg/eigenState/eigenState.go +++ b/pkg/eigenState/eigenState.go @@ -4,6 +4,9 @@ import ( "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/eigenState/avsOperators" "github.com/Layr-Labs/sidecar/pkg/eigenState/disabledDistributionRoots" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorAVSSplits" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" + "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorPISplits" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorShares" "github.com/Layr-Labs/sidecar/pkg/eigenState/rewardSubmissions" "github.com/Layr-Labs/sidecar/pkg/eigenState/stakerDelegations" @@ -48,5 +51,17 @@ func LoadEigenStateModels( l.Sugar().Errorw("Failed to create DisabledDistributionRootsModel", zap.Error(err)) return err } + if _, err := operatorDirectedRewardSubmissions.NewOperatorDirectedRewardSubmissionsModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create OperatorDirectedRewardSubmissionsModel", zap.Error(err)) + return err + } + if _, err := operatorAVSSplits.NewOperatorAVSSplitModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create OperatorAVSSplitModel", zap.Error(err)) + return err + } + if _, err := operatorPISplits.NewOperatorPISplitModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create OperatorPISplitModel", zap.Error(err)) + return err + } return nil } diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go new file mode 100644 index 00000000..cb1c2f30 --- /dev/null +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits.go @@ -0,0 +1,276 @@ +package operatorAVSSplits + +import ( + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + "time" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/types" + "github.com/Layr-Labs/sidecar/pkg/storage" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type OperatorAVSSplit struct { + Operator string + Avs string + ActivatedAt *time.Time + OldOperatorAVSSplitBips uint64 + NewOperatorAVSSplitBips uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 +} + +type OperatorAVSSplitModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[[]*OperatorAVSSplit] + DB *gorm.DB + Network config.Network + Environment config.Environment + logger *zap.Logger + globalConfig *config.Config + + // Accumulates state changes for SlotIds, grouped by block number + stateAccumulator map[uint64]map[types.SlotID]*OperatorAVSSplit +} + +func NewOperatorAVSSplitModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*OperatorAVSSplitModel, error) { + model := &OperatorAVSSplitModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorAVSSplit), + } + + esm.RegisterState(model, 8) + return model, nil +} + +func (oas *OperatorAVSSplitModel) GetModelName() string { + return "OperatorAVSSplitModel" +} + +type operatorAVSSplitOutputData struct { + ActivatedAt uint64 `json:"activatedAt"` + OldOperatorAVSSplitBips uint64 `json:"oldOperatorAVSSplitBips"` + NewOperatorAVSSplitBips uint64 `json:"newOperatorAVSSplitBips"` +} + +func parseOperatorAVSSplitOutputData(outputDataStr string) (*operatorAVSSplitOutputData, error) { + outputData := &operatorAVSSplitOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (oas *OperatorAVSSplitModel) handleOperatorAVSSplitBipsSetEvent(log *storage.TransactionLog) (*OperatorAVSSplit, error) { + arguments, err := oas.ParseLogArguments(log) + if err != nil { + return nil, err + } + + outputData, err := parseOperatorAVSSplitOutputData(log.OutputData) + if err != nil { + return nil, err + } + + activatedAt := time.Unix(int64(outputData.ActivatedAt), 0) + + split := &OperatorAVSSplit{ + Operator: strings.ToLower(arguments[1].Value.(string)), + Avs: strings.ToLower(arguments[2].Value.(string)), + ActivatedAt: &activatedAt, + OldOperatorAVSSplitBips: outputData.OldOperatorAVSSplitBips, + NewOperatorAVSSplitBips: outputData.NewOperatorAVSSplitBips, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + } + + return split, nil +} + +func (oas *OperatorAVSSplitModel) GetStateTransitions() (types.StateTransitions[*OperatorAVSSplit], []uint64) { + stateChanges := make(types.StateTransitions[*OperatorAVSSplit]) + + stateChanges[0] = func(log *storage.TransactionLog) (*OperatorAVSSplit, error) { + operatorAVSSplit, err := oas.handleOperatorAVSSplitBipsSetEvent(log) + if err != nil { + return nil, err + } + + slotId := base.NewSlotID(operatorAVSSplit.TransactionHash, operatorAVSSplit.LogIndex) + + _, ok := oas.stateAccumulator[log.BlockNumber][slotId] + if ok { + err := xerrors.Errorf("Duplicate operator AVS split submitted for slot %s at block %d", slotId, log.BlockNumber) + oas.logger.Sugar().Errorw("Duplicate operator AVS split submitted", zap.Error(err)) + return nil, err + } + + oas.stateAccumulator[log.BlockNumber][slotId] = operatorAVSSplit + + return operatorAVSSplit, nil + } + + // Create an ordered list of block numbers + blockNumbers := make([]uint64, 0) + for blockNumber := range stateChanges { + blockNumbers = append(blockNumbers, blockNumber) + } + sort.Slice(blockNumbers, func(i, j int) bool { + return blockNumbers[i] < blockNumbers[j] + }) + slices.Reverse(blockNumbers) + + return stateChanges, blockNumbers +} + +func (oas *OperatorAVSSplitModel) getContractAddressesForEnvironment() map[string][]string { + contracts := oas.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "OperatorAVSSplitBipsSet", + }, + } +} + +func (oas *OperatorAVSSplitModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := oas.getContractAddressesForEnvironment() + return oas.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (oas *OperatorAVSSplitModel) SetupStateForBlock(blockNumber uint64) error { + oas.stateAccumulator[blockNumber] = make(map[types.SlotID]*OperatorAVSSplit) + return nil +} + +func (oas *OperatorAVSSplitModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(oas.stateAccumulator, blockNumber) + return nil +} + +func (oas *OperatorAVSSplitModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := oas.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + oas.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", log.BlockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state. +func (oas *OperatorAVSSplitModel) prepareState(blockNumber uint64) ([]*OperatorAVSSplit, error) { + accumulatedState, ok := oas.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + oas.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, err + } + + recordsToInsert := make([]*OperatorAVSSplit, 0) + for _, split := range accumulatedState { + recordsToInsert = append(recordsToInsert, split) + } + return recordsToInsert, nil +} + +// CommitFinalState commits the final state for the given block number. +func (oas *OperatorAVSSplitModel) CommitFinalState(blockNumber uint64) error { + recordsToInsert, err := oas.prepareState(blockNumber) + if err != nil { + return err + } + + if len(recordsToInsert) > 0 { + for _, record := range recordsToInsert { + res := oas.DB.Model(&OperatorAVSSplit{}).Clauses(clause.Returning{}).Create(&record) + if res.Error != nil { + oas.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + } + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes. +func (oas *OperatorAVSSplitModel) GenerateStateRoot(blockNumber uint64) ([]byte, error) { + inserts, err := oas.prepareState(blockNumber) + if err != nil { + return nil, err + } + + inputs := oas.sortValuesForMerkleTree(inserts) + + if len(inputs) == 0 { + return nil, nil + } + + fullTree, err := oas.MerkleizeEigenState(blockNumber, inputs) + if err != nil { + oas.logger.Sugar().Errorw("Failed to create merkle tree", + zap.Error(err), + zap.Uint64("blockNumber", blockNumber), + zap.Any("inputs", inputs), + ) + return nil, err + } + return fullTree.Root(), nil +} + +func (oas *OperatorAVSSplitModel) sortValuesForMerkleTree(splits []*OperatorAVSSplit) []*base.MerkleTreeInput { + inputs := make([]*base.MerkleTreeInput, 0) + for _, split := range splits { + slotID := base.NewSlotID(split.TransactionHash, split.LogIndex) + value := fmt.Sprintf("%s_%s_%d_%d_%d", split.Operator, split.Avs, split.ActivatedAt.Unix(), split.OldOperatorAVSSplitBips, split.NewOperatorAVSSplitBips) + inputs = append(inputs, &base.MerkleTreeInput{ + SlotID: slotID, + Value: []byte(value), + }) + } + + slices.SortFunc(inputs, func(i, j *base.MerkleTreeInput) int { + return strings.Compare(string(i.SlotID), string(j.SlotID)) + }) + + return inputs +} + +func (oas *OperatorAVSSplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return oas.BaseEigenState.DeleteState("operator_avs_splits", startBlockNumber, endBlockNumber, oas.DB) +} diff --git a/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go new file mode 100644 index 00000000..9e60db9d --- /dev/null +++ b/pkg/eigenState/operatorAVSSplits/operatorAVSSplits_test.go @@ -0,0 +1,143 @@ +package operatorAVSSplits + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/storage" + + "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/eigenState/stateManager" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func setup() ( + string, + *gorm.DB, + *zap.Logger, + *config.Config, + error, +) { + cfg := config.NewConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: true}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, grm, l, cfg, nil +} + +func teardown(model *OperatorAVSSplitModel) { + queries := []string{ + `truncate table operator_avs_splits`, + `truncate table blocks cascade`, + } + for _, query := range queries { + res := model.DB.Exec(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } + } +} + +func createBlock(model *OperatorAVSSplitModel, blockNumber uint64) error { + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * time.Duration(blockNumber)), + } + res := model.DB.Model(&storage.Block{}).Create(block) + if res.Error != nil { + return res.Error + } + return nil +} + +func Test_OperatorAVSSplit(t *testing.T) { + dbName, grm, l, cfg, err := setup() + + if err != nil { + t.Fatal(err) + } + + t.Run("Test each event type", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewOperatorAVSSplitModel(esm, grm, l, cfg) + + t.Run("Handle an operator avs split", func(t *testing.T) { + blockNumber := uint64(102) + + if err := createBlock(model, blockNumber); err != nil { + t.Fatal(err) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + 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": "avs", "Type": "address", "Value": "0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "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}]`, + EventName: "OperatorAVSSplitBipsSet", + LogIndex: big.NewInt(12).Uint64(), + OutputData: `{"activatedAt": 1725494400, "oldOperatorAVSSplitBips": 1000, "newOperatorAVSSplitBips": 2000}`, + } + + err = model.SetupStateForBlock(blockNumber) + assert.Nil(t, err) + + isInteresting := model.IsInterestingLog(log) + assert.True(t, isInteresting) + + change, err := model.HandleStateChange(log) + assert.Nil(t, err) + assert.NotNil(t, change) + + split := change.(*OperatorAVSSplit) + + assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(split.Operator)) + assert.Equal(t, strings.ToLower("0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1"), strings.ToLower(split.Avs)) + assert.Equal(t, int64(1725494400), split.ActivatedAt.Unix()) + assert.Equal(t, uint64(1000), split.OldOperatorAVSSplitBips) + assert.Equal(t, uint64(2000), split.NewOperatorAVSSplitBips) + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + splits := make([]*OperatorAVSSplit, 0) + query := `select * from operator_avs_splits where block_number = ?` + res := model.DB.Raw(query, blockNumber).Scan(&splits) + assert.Nil(t, res.Error) + assert.Equal(t, 1, len(splits)) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + postgres.TeardownTestDatabase(dbName, cfg, grm, l) + }) +} diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go new file mode 100644 index 00000000..2d2df124 --- /dev/null +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions.go @@ -0,0 +1,331 @@ +package operatorDirectedRewardSubmissions + +import ( + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + "time" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/types" + "github.com/Layr-Labs/sidecar/pkg/storage" + "github.com/Layr-Labs/sidecar/pkg/types/numbers" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type OperatorDirectedRewardSubmission 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 +} + +func NewSlotID(transactionHash string, logIndex uint64, rewardHash string, strategyIndex uint64, operatorIndex uint64) types.SlotID { + return base.NewSlotIDWithSuffix(transactionHash, logIndex, fmt.Sprintf("%s_%d_%d", rewardHash, strategyIndex, operatorIndex)) +} + +type OperatorDirectedRewardSubmissionsModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[[]*OperatorDirectedRewardSubmission] + DB *gorm.DB + Network config.Network + Environment config.Environment + logger *zap.Logger + globalConfig *config.Config + + // Accumulates state changes for SlotIds, grouped by block number + stateAccumulator map[uint64]map[types.SlotID]*OperatorDirectedRewardSubmission +} + +func NewOperatorDirectedRewardSubmissionsModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*OperatorDirectedRewardSubmissionsModel, error) { + model := &OperatorDirectedRewardSubmissionsModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorDirectedRewardSubmission), + } + + esm.RegisterState(model, 7) + return model, nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) GetModelName() string { + return "OperatorDirectedRewardSubmissionsModel" +} + +type operatorDirectedRewardData struct { + StrategiesAndMultipliers []struct { + Strategy string `json:"strategy"` + Multiplier json.Number `json:"multiplier"` + } `json:"strategiesAndMultipliers"` + Token string `json:"token"` + OperatorRewards []struct { + Operator string `json:"operator"` + Amount json.Number `json:"amount"` + } `json:"operatorRewards"` + StartTimestamp uint64 `json:"startTimestamp"` + Duration uint64 `json:"duration"` + Description string `json:"description"` +} + +type operatorDirectedRewardSubmissionOutputData struct { + SubmissionNonce json.Number `json:"submissionNonce"` + OperatorDirectedRewardsSubmission *operatorDirectedRewardData `json:"operatorDirectedRewardsSubmission"` +} + +func parseRewardSubmissionOutputData(outputDataStr string) (*operatorDirectedRewardSubmissionOutputData, error) { + outputData := &operatorDirectedRewardSubmissionOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) handleOperatorDirectedRewardSubmissionCreatedEvent(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { + arguments, err := odrs.ParseLogArguments(log) + if err != nil { + return nil, err + } + + outputData, err := parseRewardSubmissionOutputData(log.OutputData) + if err != nil { + return nil, err + } + outputRewardData := outputData.OperatorDirectedRewardsSubmission + + rewardSubmissions := make([]*OperatorDirectedRewardSubmission, 0) + + for i, strategyAndMultiplier := range outputRewardData.StrategiesAndMultipliers { + startTimestamp := time.Unix(int64(outputRewardData.StartTimestamp), 0) + endTimestamp := startTimestamp.Add(time.Duration(outputRewardData.Duration) * time.Second) + + multiplierBig, success := numbers.NewBig257().SetString(strategyAndMultiplier.Multiplier.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to parse multiplier to Big257: %s", strategyAndMultiplier.Multiplier.String()) + } + + for j, operatorReward := range outputRewardData.OperatorRewards { + amountBig, success := numbers.NewBig257().SetString(operatorReward.Amount.String(), 10) + if !success { + return nil, xerrors.Errorf("Failed to parse amount to Big257: %s", operatorReward.Amount.String()) + } + + rewardSubmission := &OperatorDirectedRewardSubmission{ + Avs: strings.ToLower(arguments[1].Value.(string)), + RewardHash: strings.ToLower(arguments[2].Value.(string)), + Token: strings.ToLower(outputRewardData.Token), + Operator: strings.ToLower(operatorReward.Operator), + OperatorIndex: uint64(j), + Amount: amountBig.String(), + Strategy: strings.ToLower(strategyAndMultiplier.Strategy), + StrategyIndex: uint64(i), + Multiplier: multiplierBig.String(), + StartTimestamp: &startTimestamp, + EndTimestamp: &endTimestamp, + Duration: outputRewardData.Duration, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + } + + rewardSubmissions = append(rewardSubmissions, rewardSubmission) + } + } + + return rewardSubmissions, nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) GetStateTransitions() (types.StateTransitions[[]*OperatorDirectedRewardSubmission], []uint64) { + stateChanges := make(types.StateTransitions[[]*OperatorDirectedRewardSubmission]) + + stateChanges[0] = func(log *storage.TransactionLog) ([]*OperatorDirectedRewardSubmission, error) { + rewardSubmissions, err := odrs.handleOperatorDirectedRewardSubmissionCreatedEvent(log) + if err != nil { + return nil, err + } + + for _, rewardSubmission := range rewardSubmissions { + slotId := NewSlotID(rewardSubmission.TransactionHash, rewardSubmission.LogIndex, rewardSubmission.RewardHash, rewardSubmission.StrategyIndex, rewardSubmission.OperatorIndex) + + _, ok := odrs.stateAccumulator[log.BlockNumber][slotId] + if ok { + err := xerrors.Errorf("Duplicate operator directed reward submission submitted for slot %s at block %d", slotId, log.BlockNumber) + odrs.logger.Sugar().Errorw("Duplicate operator directed reward submission submitted", zap.Error(err)) + return nil, err + } + + odrs.stateAccumulator[log.BlockNumber][slotId] = rewardSubmission + } + + return rewardSubmissions, nil + } + + // Create an ordered list of block numbers + blockNumbers := make([]uint64, 0) + for blockNumber := range stateChanges { + blockNumbers = append(blockNumbers, blockNumber) + } + sort.Slice(blockNumbers, func(i, j int) bool { + return blockNumbers[i] < blockNumbers[j] + }) + slices.Reverse(blockNumbers) + + return stateChanges, blockNumbers +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) getContractAddressesForEnvironment() map[string][]string { + contracts := odrs.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "OperatorDirectedAVSRewardsSubmissionCreated", + }, + } +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := odrs.getContractAddressesForEnvironment() + return odrs.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) SetupStateForBlock(blockNumber uint64) error { + odrs.stateAccumulator[blockNumber] = make(map[types.SlotID]*OperatorDirectedRewardSubmission) + return nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(odrs.stateAccumulator, blockNumber) + return nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := odrs.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + odrs.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", log.BlockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state. +func (odrs *OperatorDirectedRewardSubmissionsModel) prepareState(blockNumber uint64) ([]*OperatorDirectedRewardSubmission, error) { + accumulatedState, ok := odrs.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + odrs.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, err + } + + recordsToInsert := make([]*OperatorDirectedRewardSubmission, 0) + for _, submission := range accumulatedState { + recordsToInsert = append(recordsToInsert, submission) + } + return recordsToInsert, nil +} + +// CommitFinalState commits the final state for the given block number. +func (odrs *OperatorDirectedRewardSubmissionsModel) CommitFinalState(blockNumber uint64) error { + recordsToInsert, err := odrs.prepareState(blockNumber) + if err != nil { + return err + } + + if len(recordsToInsert) > 0 { + for _, record := range recordsToInsert { + res := odrs.DB.Model(&OperatorDirectedRewardSubmission{}).Clauses(clause.Returning{}).Create(&record) + if res.Error != nil { + odrs.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + } + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes. +func (odrs *OperatorDirectedRewardSubmissionsModel) GenerateStateRoot(blockNumber uint64) ([]byte, error) { + inserts, err := odrs.prepareState(blockNumber) + if err != nil { + return nil, err + } + + inputs := odrs.sortValuesForMerkleTree(inserts) + + if len(inputs) == 0 { + return nil, nil + } + + fullTree, err := odrs.MerkleizeEigenState(blockNumber, inputs) + if err != nil { + odrs.logger.Sugar().Errorw("Failed to create merkle tree", + zap.Error(err), + zap.Uint64("blockNumber", blockNumber), + zap.Any("inputs", inputs), + ) + return nil, err + } + return fullTree.Root(), nil +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) sortValuesForMerkleTree(submissions []*OperatorDirectedRewardSubmission) []*base.MerkleTreeInput { + inputs := make([]*base.MerkleTreeInput, 0) + for _, submission := range submissions { + slotID := NewSlotID(submission.TransactionHash, submission.LogIndex, submission.RewardHash, submission.StrategyIndex, submission.OperatorIndex) + value := fmt.Sprintf("%s_%s_%s_%s_%s", submission.RewardHash, submission.Strategy, submission.Multiplier, submission.Operator, submission.Amount) + inputs = append(inputs, &base.MerkleTreeInput{ + SlotID: slotID, + Value: []byte(value), + }) + } + + slices.SortFunc(inputs, func(i, j *base.MerkleTreeInput) int { + return strings.Compare(string(i.SlotID), string(j.SlotID)) + }) + + return inputs +} + +func (odrs *OperatorDirectedRewardSubmissionsModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return odrs.BaseEigenState.DeleteState("operator_directed_reward_submissions", startBlockNumber, endBlockNumber, odrs.DB) +} diff --git a/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go new file mode 100644 index 00000000..03521787 --- /dev/null +++ b/pkg/eigenState/operatorDirectedRewardSubmissions/operatorDirectedRewardSubmissions_test.go @@ -0,0 +1,173 @@ +package operatorDirectedRewardSubmissions + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/storage" + + "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/eigenState/stateManager" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func setup() ( + string, + *gorm.DB, + *zap.Logger, + *config.Config, + error, +) { + cfg := config.NewConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: true}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, grm, l, cfg, nil +} + +func teardown(model *OperatorDirectedRewardSubmissionsModel) { + queries := []string{ + `truncate table operator_directed_reward_submissions`, + `truncate table blocks cascade`, + } + for _, query := range queries { + res := model.DB.Exec(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } + } +} + +func createBlock(model *OperatorDirectedRewardSubmissionsModel, blockNumber uint64) error { + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * time.Duration(blockNumber)), + } + res := model.DB.Model(&storage.Block{}).Create(block) + if res.Error != nil { + return res.Error + } + return nil +} + +func Test_OperatorDirectedRewardSubmissions(t *testing.T) { + dbName, grm, l, cfg, err := setup() + + if err != nil { + t.Fatal(err) + } + + t.Run("Test each event type", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewOperatorDirectedRewardSubmissionsModel(esm, grm, l, cfg) + + submissionCounter := 0 + + t.Run("Handle an operator directed reward submission", func(t *testing.T) { + blockNumber := uint64(102) + + if err := createBlock(model, blockNumber); err != nil { + t.Fatal(err) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForChain().RewardsCoordinator, + Arguments: `[{"Name": "caller", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "avs", "Type": "address", "Value": "0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101", "Indexed": true}, {"Name": "operatorDirectedRewardsSubmissionHash", "Type": "bytes32", "Value": "0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9", "Indexed": true}, {"Name": "submissionNonce", "Type": "uint256", "Value": 0, "Indexed": false}, {"Name": "rewardsSubmission", "Type": "((address,uint96)[],address,(address,uint256)[],uint32,uint32,string)", "Value": null, "Indexed": false}]`, + EventName: "OperatorDirectedAVSRewardsSubmissionCreated", + LogIndex: big.NewInt(12).Uint64(), + OutputData: `{"submissionNonce": 0, "operatorDirectedRewardsSubmission": {"token": "0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24", "operatorRewards": [{"operator": "0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "amount": 30000000000000000000000}, {"operator": "0xF50Cba7a66b5E615587157e43286DaA7aF94009e", "amount": 40000000000000000000000}], "duration": 2419200, "startTimestamp": 1725494400, "strategiesAndMultipliers": [{"strategy": "0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "multiplier": 1000000000000000000}, {"strategy": "0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc", "multiplier": 2000000000000000000}]}}`, + } + + err = model.SetupStateForBlock(blockNumber) + assert.Nil(t, err) + + isInteresting := model.IsInterestingLog(log) + assert.True(t, isInteresting) + + change, err := model.HandleStateChange(log) + assert.Nil(t, err) + assert.NotNil(t, change) + + strategiesAndMultipliers := []struct { + Strategy string + Multiplier string + }{ + {"0x5074dfd18e9498d9e006fb8d4f3fecdc9af90a2c", "1000000000000000000"}, + {"0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc", "2000000000000000000"}, + } + + operatorRewards := []struct { + Operator string + Amount string + }{ + {"0x9401E5E6564DB35C0f86573a9828DF69Fc778aF1", "30000000000000000000000"}, + {"0xF50Cba7a66b5E615587157e43286DaA7aF94009e", "40000000000000000000000"}, + } + + typedChange := change.([]*OperatorDirectedRewardSubmission) + assert.Equal(t, len(strategiesAndMultipliers)*len(operatorRewards), len(typedChange)) + + for _, submission := range typedChange { + assert.Equal(t, strings.ToLower("0xd36b6e5eee8311d7bffb2f3bb33301a1ab7de101"), strings.ToLower(submission.Avs)) + assert.Equal(t, strings.ToLower("0x0ddd9dc88e638aef6a8e42d0c98aaa6a48a98d24"), strings.ToLower(submission.Token)) + assert.Equal(t, strings.ToLower("0x7402669fb2c8a0cfe8108acb8a0070257c77ec6906ecb07d97c38e8a5ddc66a9"), strings.ToLower(submission.RewardHash)) + assert.Equal(t, uint64(2419200), submission.Duration) + assert.Equal(t, int64(1725494400), submission.StartTimestamp.Unix()) + assert.Equal(t, int64(2419200+1725494400), submission.EndTimestamp.Unix()) + + assert.Equal(t, strings.ToLower(strategiesAndMultipliers[submission.StrategyIndex].Strategy), strings.ToLower(submission.Strategy)) + assert.Equal(t, strategiesAndMultipliers[submission.StrategyIndex].Multiplier, submission.Multiplier) + + assert.Equal(t, strings.ToLower(operatorRewards[submission.OperatorIndex].Operator), strings.ToLower(submission.Operator)) + assert.Equal(t, operatorRewards[submission.OperatorIndex].Amount, submission.Amount) + } + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + rewards := make([]*OperatorDirectedRewardSubmission, 0) + query := `select * from operator_directed_reward_submissions where block_number = ?` + res := model.DB.Raw(query, blockNumber).Scan(&rewards) + assert.Nil(t, res.Error) + assert.Equal(t, len(strategiesAndMultipliers)*len(operatorRewards), len(rewards)) + + submissionCounter += len(strategiesAndMultipliers) * len(operatorRewards) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + postgres.TeardownTestDatabase(dbName, cfg, grm, l) + }) +} diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits.go b/pkg/eigenState/operatorPISplits/operatorPISplits.go new file mode 100644 index 00000000..624d0982 --- /dev/null +++ b/pkg/eigenState/operatorPISplits/operatorPISplits.go @@ -0,0 +1,274 @@ +package operatorPISplits + +import ( + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + "time" + + "github.com/Layr-Labs/sidecar/internal/config" + "github.com/Layr-Labs/sidecar/pkg/eigenState/base" + "github.com/Layr-Labs/sidecar/pkg/eigenState/stateManager" + "github.com/Layr-Labs/sidecar/pkg/eigenState/types" + "github.com/Layr-Labs/sidecar/pkg/storage" + "go.uber.org/zap" + "golang.org/x/xerrors" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type OperatorPISplit struct { + Operator string + ActivatedAt *time.Time + OldOperatorPISplitBips uint64 + NewOperatorPISplitBips uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 +} + +type OperatorPISplitModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[[]*OperatorPISplit] + DB *gorm.DB + Network config.Network + Environment config.Environment + logger *zap.Logger + globalConfig *config.Config + + // Accumulates state changes for SlotIds, grouped by block number + stateAccumulator map[uint64]map[types.SlotID]*OperatorPISplit +} + +func NewOperatorPISplitModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*OperatorPISplitModel, error) { + model := &OperatorPISplitModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*OperatorPISplit), + } + + esm.RegisterState(model, 9) + return model, nil +} + +func (ops *OperatorPISplitModel) GetModelName() string { + return "OperatorPISplitModel" +} + +type operatorPISplitOutputData struct { + ActivatedAt uint64 `json:"activatedAt"` + OldOperatorPISplitBips uint64 `json:"oldOperatorPISplitBips"` + NewOperatorPISplitBips uint64 `json:"newOperatorPISplitBips"` +} + +func parseOperatorPISplitOutputData(outputDataStr string) (*operatorPISplitOutputData, error) { + outputData := &operatorPISplitOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (ops *OperatorPISplitModel) handleOperatorPISplitBipsSetEvent(log *storage.TransactionLog) (*OperatorPISplit, error) { + arguments, err := ops.ParseLogArguments(log) + if err != nil { + return nil, err + } + + outputData, err := parseOperatorPISplitOutputData(log.OutputData) + if err != nil { + return nil, err + } + + activatedAt := time.Unix(int64(outputData.ActivatedAt), 0) + + split := &OperatorPISplit{ + 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 +} + +func (ops *OperatorPISplitModel) GetStateTransitions() (types.StateTransitions[*OperatorPISplit], []uint64) { + stateChanges := make(types.StateTransitions[*OperatorPISplit]) + + stateChanges[0] = func(log *storage.TransactionLog) (*OperatorPISplit, error) { + operatorPISplit, err := ops.handleOperatorPISplitBipsSetEvent(log) + if err != nil { + return nil, err + } + + slotId := base.NewSlotID(operatorPISplit.TransactionHash, operatorPISplit.LogIndex) + + _, ok := ops.stateAccumulator[log.BlockNumber][slotId] + if ok { + err := xerrors.Errorf("Duplicate operator PI split submitted for slot %s at block %d", slotId, log.BlockNumber) + ops.logger.Sugar().Errorw("Duplicate operator PI split submitted", zap.Error(err)) + return nil, err + } + + ops.stateAccumulator[log.BlockNumber][slotId] = operatorPISplit + + return operatorPISplit, nil + } + + // Create an ordered list of block numbers + blockNumbers := make([]uint64, 0) + for blockNumber := range stateChanges { + blockNumbers = append(blockNumbers, blockNumber) + } + sort.Slice(blockNumbers, func(i, j int) bool { + return blockNumbers[i] < blockNumbers[j] + }) + slices.Reverse(blockNumbers) + + return stateChanges, blockNumbers +} + +func (ops *OperatorPISplitModel) getContractAddressesForEnvironment() map[string][]string { + contracts := ops.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "OperatorPISplitBipsSet", + }, + } +} + +func (ops *OperatorPISplitModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := ops.getContractAddressesForEnvironment() + return ops.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (ops *OperatorPISplitModel) SetupStateForBlock(blockNumber uint64) error { + ops.stateAccumulator[blockNumber] = make(map[types.SlotID]*OperatorPISplit) + return nil +} + +func (ops *OperatorPISplitModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(ops.stateAccumulator, blockNumber) + return nil +} + +func (ops *OperatorPISplitModel) HandleStateChange(log *storage.TransactionLog) (interface{}, error) { + stateChanges, sortedBlockNumbers := ops.GetStateTransitions() + + for _, blockNumber := range sortedBlockNumbers { + if log.BlockNumber >= blockNumber { + ops.logger.Sugar().Debugw("Handling state change", zap.Uint64("blockNumber", log.BlockNumber)) + + change, err := stateChanges[blockNumber](log) + if err != nil { + return nil, err + } + if change == nil { + return nil, nil + } + return change, nil + } + } + return nil, nil +} + +// prepareState prepares the state for commit by adding the new state to the existing state. +func (ops *OperatorPISplitModel) prepareState(blockNumber uint64) ([]*OperatorPISplit, error) { + accumulatedState, ok := ops.stateAccumulator[blockNumber] + if !ok { + err := xerrors.Errorf("No accumulated state found for block %d", blockNumber) + ops.logger.Sugar().Errorw(err.Error(), zap.Error(err), zap.Uint64("blockNumber", blockNumber)) + return nil, err + } + + recordsToInsert := make([]*OperatorPISplit, 0) + for _, split := range accumulatedState { + recordsToInsert = append(recordsToInsert, split) + } + return recordsToInsert, nil +} + +// CommitFinalState commits the final state for the given block number. +func (ops *OperatorPISplitModel) CommitFinalState(blockNumber uint64) error { + recordsToInsert, err := ops.prepareState(blockNumber) + if err != nil { + return err + } + + if len(recordsToInsert) > 0 { + for _, record := range recordsToInsert { + res := ops.DB.Model(&OperatorPISplit{}).Clauses(clause.Returning{}).Create(&record) + if res.Error != nil { + ops.logger.Sugar().Errorw("Failed to insert records", zap.Error(res.Error)) + return res.Error + } + } + } + return nil +} + +// GenerateStateRoot generates the state root for the given block number using the results of the state changes. +func (ops *OperatorPISplitModel) GenerateStateRoot(blockNumber uint64) ([]byte, error) { + inserts, err := ops.prepareState(blockNumber) + if err != nil { + return nil, err + } + + inputs := ops.sortValuesForMerkleTree(inserts) + + if len(inputs) == 0 { + return nil, nil + } + + fullTree, err := ops.MerkleizeEigenState(blockNumber, inputs) + if err != nil { + ops.logger.Sugar().Errorw("Failed to create merkle tree", + zap.Error(err), + zap.Uint64("blockNumber", blockNumber), + zap.Any("inputs", inputs), + ) + return nil, err + } + return fullTree.Root(), nil +} + +func (ops *OperatorPISplitModel) sortValuesForMerkleTree(splits []*OperatorPISplit) []*base.MerkleTreeInput { + 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.OldOperatorPISplitBips, split.NewOperatorPISplitBips) + inputs = append(inputs, &base.MerkleTreeInput{ + SlotID: slotID, + Value: []byte(value), + }) + } + + slices.SortFunc(inputs, func(i, j *base.MerkleTreeInput) int { + return strings.Compare(string(i.SlotID), string(j.SlotID)) + }) + + return inputs +} + +func (ops *OperatorPISplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return ops.BaseEigenState.DeleteState("operator_pi_splits", startBlockNumber, endBlockNumber, ops.DB) +} diff --git a/pkg/eigenState/operatorPISplits/operatorPISplits_test.go b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go new file mode 100644 index 00000000..9bc12b07 --- /dev/null +++ b/pkg/eigenState/operatorPISplits/operatorPISplits_test.go @@ -0,0 +1,142 @@ +package operatorPISplits + +import ( + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/Layr-Labs/sidecar/pkg/postgres" + "github.com/Layr-Labs/sidecar/pkg/storage" + + "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/eigenState/stateManager" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func setup() ( + string, + *gorm.DB, + *zap.Logger, + *config.Config, + error, +) { + cfg := config.NewConfig() + cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() + + l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: true}) + + dbname, _, grm, err := postgres.GetTestPostgresDatabase(cfg.DatabaseConfig, cfg, l) + if err != nil { + return dbname, nil, nil, nil, err + } + + return dbname, grm, l, cfg, nil +} + +func teardown(model *OperatorPISplitModel) { + queries := []string{ + `truncate table operator_pi_splits`, + `truncate table blocks cascade`, + } + for _, query := range queries { + res := model.DB.Exec(query) + if res.Error != nil { + fmt.Printf("Failed to run query: %v\n", res.Error) + } + } +} + +func createBlock(model *OperatorPISplitModel, blockNumber uint64) error { + block := &storage.Block{ + Number: blockNumber, + Hash: "some hash", + BlockTime: time.Now().Add(time.Hour * time.Duration(blockNumber)), + } + res := model.DB.Model(&storage.Block{}).Create(block) + if res.Error != nil { + return res.Error + } + return nil +} + +func Test_OperatorPISplit(t *testing.T) { + dbName, grm, l, cfg, err := setup() + + if err != nil { + t.Fatal(err) + } + + t.Run("Test each event type", func(t *testing.T) { + esm := stateManager.NewEigenStateManager(l, grm) + + model, err := NewOperatorPISplitModel(esm, grm, l, cfg) + + t.Run("Handle an operator pi split", func(t *testing.T) { + blockNumber := uint64(102) + + if err := createBlock(model, blockNumber); err != nil { + t.Fatal(err) + } + + log := &storage.TransactionLog{ + TransactionHash: "some hash", + TransactionIndex: big.NewInt(100).Uint64(), + BlockNumber: blockNumber, + Address: cfg.GetContractsMapForChain().RewardsCoordinator, + 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": 1733341104, "newOperatorPISplitBips": 6545, "oldOperatorPISplitBips": 1000}`, + } + + err = model.SetupStateForBlock(blockNumber) + assert.Nil(t, err) + + isInteresting := model.IsInterestingLog(log) + assert.True(t, isInteresting) + + change, err := model.HandleStateChange(log) + assert.Nil(t, err) + assert.NotNil(t, change) + + split := change.(*OperatorPISplit) + + 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) + + splits := make([]*OperatorPISplit, 0) + query := `select * from operator_pi_splits where block_number = ?` + res := model.DB.Raw(query, blockNumber).Scan(&splits) + assert.Nil(t, res.Error) + assert.Equal(t, 1, len(splits)) + + stateRoot, err := model.GenerateStateRoot(blockNumber) + assert.Nil(t, err) + assert.NotNil(t, stateRoot) + assert.True(t, len(stateRoot) > 0) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + teardown(model) + }) + }) + + t.Cleanup(func() { + postgres.TeardownTestDatabase(dbName, cfg, grm, l) + }) +} 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/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go b/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go new file mode 100644 index 00000000..72eeb23f --- /dev/null +++ b/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions/up.go @@ -0,0 +1,43 @@ +package _202411151931_operatorDirectedRewardSubmissions + +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 { + query := ` + create table if not exists operator_directed_reward_submissions ( + avs varchar not null, + reward_hash varchar not null, + token varchar not null, + operator varchar not null, + operator_index integer not null, + amount numeric not null, + strategy varchar not null, + strategy_index integer not null, + multiplier numeric(78) not null, + start_timestamp timestamp(6) not null, + end_timestamp timestamp(6) not null, + duration bigint not null, + block_number bigint not null, + transaction_hash varchar not null, + log_index bigint not null, + unique(transaction_hash, log_index, block_number, reward_hash, strategy_index, operator_index), + CONSTRAINT operator_directed_reward_submissions_block_number_fkey FOREIGN KEY (block_number) REFERENCES blocks(number) ON DELETE CASCADE + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *Migration) GetName() string { + return "202411151931_operatorDirectedRewardSubmissions" +} diff --git a/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go b/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go new file mode 100644 index 00000000..34f2b084 --- /dev/null +++ b/pkg/postgres/migrations/202411191550_operatorAVSSplits/up.go @@ -0,0 +1,36 @@ +package _202411191550_operatorAVSSplits + +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 { + query := ` + create table if not exists operator_avs_splits ( + operator varchar not null, + avs varchar not null, + activated_at timestamp(6) not null, + old_operator_avs_split_bips integer not null, + new_operator_avs_split_bips integer not null, + block_number bigint not null, + transaction_hash varchar not null, + log_index bigint not null, + unique(transaction_hash, log_index, block_number), + CONSTRAINT operator_avs_splits_block_number_fkey FOREIGN KEY (block_number) REFERENCES blocks(number) ON DELETE CASCADE + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *Migration) GetName() string { + return "202411191550_operatorAVSSplits" +} diff --git a/pkg/postgres/migrations/202411191708_operatorPISplits/up.go b/pkg/postgres/migrations/202411191708_operatorPISplits/up.go new file mode 100644 index 00000000..4bae75ce --- /dev/null +++ b/pkg/postgres/migrations/202411191708_operatorPISplits/up.go @@ -0,0 +1,35 @@ +package _202411191708_operatorPISplits + +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 { + query := ` + create table if not exists operator_pi_splits ( + operator varchar not null, + activated_at timestamp(6) not null, + old_operator_avs_split_bips integer not null, + new_operator_avs_split_bips integer not null, + block_number bigint not null, + transaction_hash varchar not null, + log_index bigint not null, + unique(transaction_hash, log_index, block_number), + CONSTRAINT operator_pi_splits_block_number_fkey FOREIGN KEY (block_number) REFERENCES blocks(number) ON DELETE CASCADE + ); + ` + if err := grm.Exec(query).Error; err != nil { + return err + } + return nil +} + +func (m *Migration) GetName() string { + return "202411191708_operatorPISplits" +} diff --git a/pkg/postgres/migrations/202411221331_operatorAVSSplitSnapshots/up.go b/pkg/postgres/migrations/202411221331_operatorAVSSplitSnapshots/up.go new file mode 100644 index 00000000..18c9172f --- /dev/null +++ b/pkg/postgres/migrations/202411221331_operatorAVSSplitSnapshots/up.go @@ -0,0 +1,32 @@ +package _202411221331_operatorAVSSplitSnapshots + +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{ + `CREATE TABLE IF NOT EXISTS operator_avs_split_snapshots ( + operator varchar not null, + avs varchar not null, + split integer not null, + snapshot date not null + )`, + } + for _, query := range queries { + if _, err := db.Exec(query); err != nil { + return err + } + } + return nil +} + +func (m *Migration) GetName() string { + return "202411221331_operatorAVSSplitSnapshots" +} diff --git a/pkg/postgres/migrations/202411221331_operatorPISplitSnapshots/up.go b/pkg/postgres/migrations/202411221331_operatorPISplitSnapshots/up.go new file mode 100644 index 00000000..ea86997c --- /dev/null +++ b/pkg/postgres/migrations/202411221331_operatorPISplitSnapshots/up.go @@ -0,0 +1,31 @@ +package _202411221331_operatorPISplitSnapshots + +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{ + `CREATE TABLE IF NOT EXISTS operator_pi_split_snapshots ( + operator varchar not null, + split integer not null, + snapshot date not null + )`, + } + for _, query := range queries { + if _, err := db.Exec(query); err != nil { + return err + } + } + return nil +} + +func (m *Migration) GetName() string { + return "202411221331_operatorPISplitSnapshots" +} 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 1bc7cca3..dca991b9 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -33,10 +33,16 @@ import ( _202411120947_disabledDistributionRoots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411120947_disabledDistributionRoots" _202411130953_addHashColumns "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411130953_addHashColumns" _202411131200_eigenStateModelConstraints "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411131200_eigenStateModelConstraints" + _202411151931_operatorDirectedRewardSubmissions "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411151931_operatorDirectedRewardSubmissions" + _202411191550_operatorAVSSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191550_operatorAVSSplits" + _202411191708_operatorPISplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191708_operatorPISplits" _202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables" + _202411221331_operatorAVSSplitSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411221331_operatorAVSSplitSnapshots" + _202411221331_operatorPISplitSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411221331_operatorPISplitSnapshots" _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" @@ -109,10 +115,16 @@ func (m *Migrator) MigrateAll() error { &_202411120947_disabledDistributionRoots.Migration{}, &_202411130953_addHashColumns.Migration{}, &_202411131200_eigenStateModelConstraints.Migration{}, + &_202411151931_operatorDirectedRewardSubmissions.Migration{}, + &_202411191550_operatorAVSSplits.Migration{}, + &_202411191708_operatorPISplits.Migration{}, &_202411191947_cleanupUnusedTables.Migration{}, &_202412021311_stakerOperatorTables.Migration{}, &_202412061553_addBlockNumberIndexes.Migration{}, &_202412061626_operatorRestakedStrategiesConstraint.Migration{}, + &_202411221331_operatorAVSSplitSnapshots.Migration{}, + &_202411221331_operatorPISplitSnapshots.Migration{}, + &_202412091100_fixOperatorPiSplitsFields.Migration{}, } for _, migration := range migrations { @@ -132,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 { @@ -156,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/postgres/postgres.go b/pkg/postgres/postgres.go index 44df42d4..7cf0832c 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -3,6 +3,8 @@ package postgres import ( "database/sql" "fmt" + "regexp" + "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/internal/tests" "github.com/Layr-Labs/sidecar/pkg/postgres/migrations" @@ -11,7 +13,6 @@ import ( "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" - "regexp" ) type PostgresConfig struct { diff --git a/pkg/rewards/10_goldAvsODRewardAmounts.go b/pkg/rewards/10_goldAvsODRewardAmounts.go new file mode 100644 index 00000000..2fe6d76f --- /dev/null +++ b/pkg/rewards/10_goldAvsODRewardAmounts.go @@ -0,0 +1,97 @@ +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_registered_snapshot_decimal, + ap.avs AS avs, + ap.operator AS operator, + ap.strategy, + ap.multiplier, + ap.reward_submission_date + FROM {{.activeODRewardsTable}} ap + WHERE + ap.num_registered_snapshots = 0 +), + +-- 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_registered_snapshot_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 { + 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] + + 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]interface{}{ + "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 53% rename from pkg/rewards/7_goldStaging.go rename to pkg/rewards/11_goldStaging.go index ca6ca04b..2ad37727 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,38 @@ 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 + 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}} +), +{{ end }} combined_rewards AS ( SELECT * FROM operator_rewards UNION ALL @@ -65,6 +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 ( @@ -85,22 +126,33 @@ 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], + 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], + "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], + "enableRewardsV2": isRewardsV2Enabled, }) if err != nil { rc.logger.Sugar().Errorw("Failed to render query template", "error", err) @@ -127,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{ - "goldStagingTable": allTableNames[rewardsUtils.Table_7_GoldStaging], + 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/8_goldFinal.go b/pkg/rewards/12_goldFinal.go similarity index 79% rename from pkg/rewards/8_goldFinal.go rename to pkg/rewards/12_goldFinal.go index 48b243d2..f993b7b2 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]interface{}{ + "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/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 e9f1bf53..52cbcb76 100644 --- a/pkg/rewards/2_goldStakerRewardAmounts.go +++ b/pkg/rewards/2_goldStakerRewardAmounts.go @@ -2,6 +2,7 @@ package rewards import ( "database/sql" + "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "go.uber.org/zap" @@ -108,26 +109,33 @@ staker_operator_total_tokens AS ( END as total_staker_operator_payout FROM staker_proportion ), --- Calculate the token breakdown for each (staker, operator) pair +-- Calculate the token breakdown for each (staker, operator) pair with dynamic split logic +-- If no split is found, default to 1000 (10%) token_breakdowns AS ( - SELECT *, + SELECT sott.*, CASE - WHEN snapshot < @amazonHardforkDate AND reward_submission_date < @amazonHardforkDate THEN - cast(total_staker_operator_payout * 0.10 AS DECIMAL(38,0)) - WHEN snapshot < @nileHardforkDate AND reward_submission_date < @nileHardforkDate THEN - (total_staker_operator_payout * 0.10)::text::decimal(38,0) + WHEN sott.snapshot < @amazonHardforkDate AND sott.reward_submission_date < @amazonHardforkDate THEN + cast(sott.total_staker_operator_payout * 0.10 AS DECIMAL(38,0)) + WHEN sott.snapshot < @nileHardforkDate AND sott.reward_submission_date < @nileHardforkDate THEN + (sott.total_staker_operator_payout * 0.10)::text::decimal(38,0) + WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN + floor(sott.total_staker_operator_payout * 0.10) ELSE - floor(total_staker_operator_payout * 0.10) + floor(sott.total_staker_operator_payout * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) END as operator_tokens, CASE - WHEN snapshot < @amazonHardforkDate AND reward_submission_date < @amazonHardforkDate THEN - total_staker_operator_payout - cast(total_staker_operator_payout * 0.10 as DECIMAL(38,0)) - WHEN snapshot < @nileHardforkDate AND reward_submission_date < @nileHardforkDate THEN - total_staker_operator_payout - ((total_staker_operator_payout * 0.10)::text::decimal(38,0)) + WHEN sott.snapshot < @amazonHardforkDate AND sott.reward_submission_date < @amazonHardforkDate THEN + sott.total_staker_operator_payout - cast(sott.total_staker_operator_payout * 0.10 as DECIMAL(38,0)) + WHEN sott.snapshot < @nileHardforkDate AND sott.reward_submission_date < @nileHardforkDate THEN + sott.total_staker_operator_payout - ((sott.total_staker_operator_payout * 0.10)::text::decimal(38,0)) + WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN + sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * 0.10) ELSE - total_staker_operator_payout - floor(total_staker_operator_payout * 0.10) + sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) END as staker_tokens - FROM staker_operator_total_tokens + FROM staker_operator_total_tokens sott + LEFT JOIN operator_avs_split_snapshots oas + ON sott.operator = oas.operator AND sott.avs = oas.avs AND sott.snapshot = oas.snapshot ) SELECT * from token_breakdowns ORDER BY reward_hash, snapshot, staker, operator @@ -142,9 +150,10 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate zap.String("destTableName", destTableName), zap.String("amazonHardforkDate", forks[config.Fork_Amazon]), zap.String("nileHardforkDate", forks[config.Fork_Nile]), + 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], }) @@ -156,6 +165,7 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate res := rc.grm.Exec(query, sql.Named("amazonHardforkDate", forks[config.Fork_Amazon]), sql.Named("nileHardforkDate", forks[config.Fork_Nile]), + sql.Named("arnoHardforkDate", forks[config.Fork_Arno]), ) if res.Error != nil { rc.logger.Sugar().Errorw("Failed to create gold_staker_reward_amounts", "error", res.Error) 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 9e7d87bc..af605f5f 100644 --- a/pkg/rewards/5_goldRfaeStakers.go +++ b/pkg/rewards/5_goldRfaeStakers.go @@ -2,6 +2,7 @@ package rewards import ( "database/sql" + "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "go.uber.org/zap" @@ -107,12 +108,25 @@ staker_operator_total_tokens AS ( FLOOR(staker_proportion * tokens_per_day_decimal) as total_staker_operator_payout FROM staker_proportion ), --- Calculate the token breakdown for each (staker, operator) pair +-- Calculate the token breakdown for each (staker, operator) pair with dynamic split logic +-- If no split is found, default to 1000 (10%) token_breakdowns AS ( - SELECT *, - floor(total_staker_operator_payout * 0.10) as operator_tokens, - total_staker_operator_payout - floor(total_staker_operator_payout * 0.10) as staker_tokens - FROM staker_operator_total_tokens + SELECT sott.*, + CASE + WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN + floor(sott.total_staker_operator_payout * 0.10) + ELSE + floor(sott.total_staker_operator_payout * COALESCE(ops.split, 1000) / CAST(10000 AS DECIMAL)) + END as operator_tokens, + CASE + WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN + sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * 0.10) + ELSE + sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(ops.split, 1000) / CAST(10000 AS DECIMAL)) + END as staker_tokens + FROM staker_operator_total_tokens sott + LEFT JOIN operator_pi_split_snapshots ops + ON sott.operator = ops.operator AND sott.snapshot = ops.snapshot ) SELECT * from token_breakdowns ORDER BY reward_hash, snapshot, staker, operator @@ -125,9 +139,10 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string, rc.logger.Sugar().Infow("Generating rfae stakers table", zap.String("cutoffDate", snapshotDate), zap.String("destTableName", destTableName), + 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], }) @@ -139,6 +154,7 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string, res := rc.grm.Exec(query, sql.Named("panamaForkDate", forks[config.Fork_Panama]), sql.Named("network", rc.globalConfig.Chain.String()), + sql.Named("arnoHardforkDate", forks[config.Fork_Arno]), ) if res.Error != nil { rc.logger.Sugar().Errorw("Failed to generate gold_rfae_stakers", "error", res.Error) 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 new file mode 100644 index 00000000..a52dc019 --- /dev/null +++ b/pkg/rewards/7_goldActiveODRewards.go @@ -0,0 +1,214 @@ +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 + *, + 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, + amount, + token, + multiplier, + strategy, + reward_hash, + duration, + 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 underestimating total tokens per day + FLOOR(ap.amount) AS amount_decimal, + ap.multiplier, + ap.strategy, + ap.reward_hash, + ap.duration, + ap.global_end_inclusive, + ap.reward_submission_date + FROM active_rewards_updated_end_timestamps ap + LEFT JOIN gold_table g + ON g.reward_hash = ap.reward_hash + GROUP BY + ap.avs, + ap.operator, + ap.reward_end_inclusive, + ap.token, + ap.amount, + ap.multiplier, + ap.strategy, + ap.reward_hash, + ap.duration, + ap.global_end_inclusive, + ap.reward_start_exclusive, + ap.reward_submission_date +), + +-- 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 cleaned active rewards +active_rewards_cleaned AS ( + SELECT + avs, + operator, + CAST(day AS DATE) AS snapshot, + token, + amount_decimal, + multiplier, + strategy, + duration, + reward_hash, + reward_submission_date + FROM exploded_active_range_rewards + -- Remove snapshots on the start day + WHERE day != reward_start_exclusive +), + +-- Step 8: Dedupe the active rewards by (avs, snapshot, operator, reward_hash) +active_rewards_reduced_deduped AS ( + SELECT DISTINCT avs, snapshot, operator, reward_hash + FROM active_rewards_cleaned +), + +-- Step 9: Divide by the number of snapshots that the operator was registered +op_avs_num_registered_snapshots AS ( + SELECT + ar.reward_hash, + ar.operator, + COUNT(*) AS num_registered_snapshots + FROM active_rewards_reduced_deduped ar + JOIN operator_avs_registration_snapshots oar + ON + ar.avs = oar.avs + AND ar.snapshot = oar.snapshot + AND ar.operator = oar.operator + GROUP BY ar.reward_hash, ar.operator +), + +-- Step 10: Divide amount to pay by the number of snapshots that the operator was registered +active_rewards_with_registered_snapshots AS ( + SELECT + arc.*, + COALESCE(nrs.num_registered_snapshots, 0) as num_registered_snapshots + FROM active_rewards_cleaned arc + LEFT JOIN op_avs_num_registered_snapshots nrs + ON + arc.reward_hash = nrs.reward_hash + AND arc.operator = nrs.operator +), + +-- Step 11: Divide amount to pay by the number of snapshots that the operator was registered +active_rewards_final AS ( + SELECT + ar.*, + CASE + -- If the operator was not registered for any snapshots, just get regular tokens per day to refund the AVS + WHEN ar.num_registered_snapshots = 0 THEN floor(ar.amount_decimal / (duration / 86400)) + ELSE floor(ar.amount_decimal / ar.num_registered_snapshots) + END AS tokens_per_registered_snapshot_decimal + FROM active_rewards_with_registered_snapshots ar +) + +SELECT * FROM active_rewards_final +` + +// 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 { + 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] + + 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]interface{}{ + "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..f3ec3c7c --- /dev/null +++ b/pkg/rewards/8_goldOperatorODRewardAmounts.go @@ -0,0 +1,100 @@ +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_registered_snapshot_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_registered_snapshot_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 { + 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] + + 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]interface{}{ + "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..f2f4f94e --- /dev/null +++ b/pkg/rewards/9_goldStakerODRewardAmounts.go @@ -0,0 +1,150 @@ +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_registered_snapshot_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_registered_snapshot_decimal - FLOOR(rso.tokens_per_registered_snapshot_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 { + 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] + + 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]interface{}{ + "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/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 new file mode 100644 index 00000000..30a593d8 --- /dev/null +++ b/pkg/rewards/operatorAvsSplitSnapshots.go @@ -0,0 +1,113 @@ +package rewards + +import "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + +const operatorAvsSplitSnapshotQuery = ` +WITH operator_avs_splits_with_block_info as ( + select + oas.operator, + oas.avs, + oas.activated_at::timestamp(6) as activated_at, + oas.new_operator_avs_split_bips as split, + oas.block_number, + oas.log_index, + b.block_time::timestamp(6) as block_time + from operator_avs_splits as oas + join blocks as b on (b.number = oas.block_number) + where activated_at < TIMESTAMP '{{.cutoffDate}}' +), +-- 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 + *, + -- 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 +), +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 ( + SELECT + operator, avs, 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, avs ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}') + + -- 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 active_operator_splits +), +-- Clean up any records where start_time >= end_time +cleaned_records as ( + SELECT * FROM operator_avs_split_windows + WHERE start_time < end_time +), +-- Generate a snapshot for each day in the range +final_results as ( + SELECT + operator, + avs, + split, + d 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 +` + +func (r *RewardsCalculator) GenerateAndInsertOperatorAvsSplitSnapshots(snapshotDate string) error { + tableName := "operator_avs_split_snapshots" + + query, err := rewardsUtils.RenderQueryTemplate(operatorAvsSplitSnapshotQuery, map[string]interface{}{ + "cutoffDate": snapshotDate, + }) + if err != nil { + r.logger.Sugar().Errorw("Failed to render query template", "error", err) + return err + } + + err = r.generateAndInsertFromQuery(tableName, query, nil) + if err != nil { + r.logger.Sugar().Errorw("Failed to generate operator_avs_split_snapshots", "error", err) + return err + } + return nil +} + +func (r *RewardsCalculator) ListOperatorAvsSplitSnapshots() ([]*OperatorAVSSplitSnapshots, error) { + var snapshots []*OperatorAVSSplitSnapshots + res := r.grm.Model(&OperatorAVSSplitSnapshots{}).Find(&snapshots) + if res.Error != nil { + r.logger.Sugar().Errorw("Failed to list operator avs split snapshots", "error", res.Error) + return nil, res.Error + } + return snapshots, nil +} 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/operatorAvsStrategySnapshots.go b/pkg/rewards/operatorAvsStrategySnapshots.go index 92eb2725..428cfecb 100644 --- a/pkg/rewards/operatorAvsStrategySnapshots.go +++ b/pkg/rewards/operatorAvsStrategySnapshots.go @@ -147,7 +147,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 new file mode 100644 index 00000000..9cc7d896 --- /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]interface{}{ + "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 new file mode 100644 index 00000000..2f9a5ed7 --- /dev/null +++ b/pkg/rewards/operatorPISplitSnapshots.go @@ -0,0 +1,112 @@ +package rewards + +import "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + +const operatorPISplitSnapshotQuery = ` +WITH operator_pi_splits_with_block_info as ( + select + ops.operator, + ops.activated_at::timestamp(6) as activated_at, + ops.new_operator_pi_split_bips as split, + ops.block_number, + ops.log_index, + b.block_time::timestamp(6) as block_time + from operator_pi_splits as ops + join blocks as b on (b.number = ops.block_number) + where activated_at < TIMESTAMP '{{.cutoffDate}}' +), +-- 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 + *, + -- 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 +), +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, + 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}}') + + -- 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 active_operator_splits +), +-- Clean up any records where start_time >= end_time +cleaned_records as ( + SELECT * FROM operator_pi_split_windows + WHERE start_time < end_time +), +-- Generate a snapshot for each day in the range +final_results as ( + SELECT + operator, + split, + d 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 +` + +func (r *RewardsCalculator) GenerateAndInsertOperatorPISplitSnapshots(snapshotDate string) error { + tableName := "operator_pi_split_snapshots" + + query, err := rewardsUtils.RenderQueryTemplate(operatorPISplitSnapshotQuery, map[string]interface{}{ + "cutoffDate": snapshotDate, + }) + if err != nil { + r.logger.Sugar().Errorw("Failed to render query template", "error", err) + return err + } + + err = r.generateAndInsertFromQuery(tableName, query, nil) + if err != nil { + r.logger.Sugar().Errorw("Failed to generate operator_pi_split_snapshots", "error", err) + return err + } + return nil +} + +func (r *RewardsCalculator) ListOperatorPISplitSnapshots() ([]*OperatorPISplitSnapshots, error) { + var snapshots []*OperatorPISplitSnapshots + res := r.grm.Model(&OperatorPISplitSnapshots{}).Find(&snapshots) + if res.Error != nil { + r.logger.Sugar().Errorw("Failed to list operator pi split snapshots", "error", res.Error) + return nil, res.Error + } + return snapshots, nil +} 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/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/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 029a3dcc..979e7f91 100644 --- a/pkg/rewards/rewards.go +++ b/pkg/rewards/rewards.go @@ -4,21 +4,22 @@ import ( "database/sql" "errors" "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" "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "github.com/Layr-Labs/sidecar/pkg/storage" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/wealdtech/go-merkletree/v2" + "go.uber.org/zap" + "gorm.io/gorm" "gorm.io/gorm/clause" "slices" "strings" - "sync/atomic" - "time" - - "github.com/Layr-Labs/sidecar/internal/config" - "go.uber.org/zap" - "gorm.io/gorm" ) 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) @@ -492,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 @@ -604,6 +605,26 @@ 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 + } + rc.logger.Sugar().Debugw("Generated operator avs split snapshots") + + if err = rc.GenerateAndInsertOperatorPISplitSnapshots(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate operator pi snapshots", "error", err) + return err + } + rc.logger.Sugar().Debugw("Generated operator pi snapshots") + return nil } @@ -642,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..599c990a --- /dev/null +++ b/pkg/rewards/rewardsV2_test.go @@ -0,0 +1,231 @@ +package rewards + +import ( + "fmt" + "strings" + "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-14", + } + + 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 + // ------------------------------------------------------------------------ + 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) + 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) + 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) + 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) + 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") + 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)) + for i, row := range goldRows { + if strings.EqualFold(row.RewardHash, strings.ToLower("0xB38AB57E8E858F197C07D0CDF61F34EB07C3D0FC58390417DDAD0BF528681909")) && + strings.EqualFold(row.Earner, strings.ToLower("0xaFF71569D30ED876987088a62E0EA881EBc761E6")) { + t.Logf("%d: %s %s %s %s %s", i, row.Earner, row.Snapshot.String(), row.RewardHash, row.Token, row.Amount) + } + // t.Logf("%d: %s %s %s %s %s", i, row.Earner, row.Snapshot.String(), row.RewardHash, row.Token, row.Amount) + } + + 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 ecfbda6d..d376e2b4 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,59 @@ 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) + 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) + 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) + 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) + 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) + 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") + 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/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 055a68f3..b0a06c53 100644 --- a/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go +++ b/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go @@ -146,7 +146,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert1StakerStrategyPayouts(cut return err } - query, err := rewardsUtils.RenderQueryTemplate(_1_stakerStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_1_stakerStrategyPayoutsQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": rewardsTables[rewardsUtils.Table_1_ActiveRewards], "stakerRewardAmountsTable": rewardsTables[rewardsUtils.Table_2_StakerRewardAmounts], diff --git a/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go b/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go index c4b0a4be..61a7e034 100644 --- a/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go +++ b/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go @@ -120,7 +120,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert2OperatorStrategyRewards(c return err } - query, err := rewardsUtils.RenderQueryTemplate(_2_operatorStrategyRewardsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_2_operatorStrategyRewardsQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": rewardsTables[rewardsUtils.Table_1_ActiveRewards], "operatorRewardAmountsTable": rewardsTables[rewardsUtils.Table_3_OperatorRewardAmounts], diff --git a/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go b/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go index c4a33b2a..56759787 100644 --- a/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go +++ b/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go @@ -89,7 +89,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert3RewardsForAllStrategyPayo return err } - query, err := rewardsUtils.RenderQueryTemplate(_3_rewardsForAllStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_3_rewardsForAllStrategyPayoutsQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": rewardsTables[rewardsUtils.Table_1_ActiveRewards], }) diff --git a/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go b/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go index 01cd7ac2..fbf74ec9 100644 --- a/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go +++ b/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go @@ -132,7 +132,7 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert4RfaeStakerStrategyPayout( return err } - query, err := rewardsUtils.RenderQueryTemplate(_4_rfaeStakerStrategyPayoutsQuery, map[string]string{ + query, err := rewardsUtils.RenderQueryTemplate(_4_rfaeStakerStrategyPayoutsQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": rewardsTables[rewardsUtils.Table_1_ActiveRewards], "rfaeStakersTable": rewardsTables[rewardsUtils.Table_5_RfaeStakers], diff --git a/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go b/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go index 6b61cd49..a290bbcc 100644 --- a/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go +++ b/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go @@ -116,8 +116,8 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert5RfaeOperatorStrategyPayou sog.logger.Sugar().Errorw("Failed to find staker operator table names", "error", err) return err } - - query, err := rewardsUtils.RenderQueryTemplate(_5_rfaeOperatorStrategyPayoutsQuery, map[string]string{ + + query, err := rewardsUtils.RenderQueryTemplate(_5_rfaeOperatorStrategyPayoutsQuery, map[string]interface{}{ "destTableName": destTableName, "activeRewardsTable": rewardsTables[rewardsUtils.Table_1_ActiveRewards], "rfaeOperatorsTable": rewardsTables[rewardsUtils.Table_6_RfaeOperators], diff --git a/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go b/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go index 4c7e0bab..252784f4 100644 --- a/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go +++ b/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go @@ -118,7 +118,8 @@ func (sog *StakerOperatorsGenerator) GenerateAndInsert6StakerOperatorStaging(cut zap.String("destTableName", destTableName), zap.String("cutoffDate", cutoffDate), ) - query, err := rewardsUtils.RenderQueryTemplate(_6_stakerOperatorsStaging, map[string]string{ + + query, err := rewardsUtils.RenderQueryTemplate(_6_stakerOperatorsStaging, map[string]interface{}{ "destTableName": destTableName, "sot1StakerStrategyPayouts": allTableNames[rewardsUtils.Sot_1_StakerStrategyPayouts], "sot2OperatorStrategyPayouts": allTableNames[rewardsUtils.Sot_2_OperatorStrategyPayouts], diff --git a/pkg/rewards/stakerOperators/7_stakerOperator.go b/pkg/rewards/stakerOperators/7_stakerOperator.go index 27c98c67..0db5a866 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/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.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/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 f52c5f94..624ce4f9 100644 --- a/pkg/rewards/tables.go +++ b/pkg/rewards/tables.go @@ -76,3 +76,34 @@ type OperatorShares struct { BlockTime time.Time BlockDate string } + +type OperatorAVSSplitSnapshots struct { + Operator string + Avs string + Split uint64 + Snapshot time.Time +} + +type OperatorPISplitSnapshots struct { + Operator string + 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 337ba719..2d93e40e 100644 --- a/pkg/rewardsUtils/rewardsUtils.go +++ b/pkg/rewardsUtils/rewardsUtils.go @@ -4,22 +4,27 @@ import ( "bytes" "database/sql" "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_1_StakerStrategyPayouts = "sot_1_staker_strategy_payouts" Sot_2_OperatorStrategyPayouts = "sot_2_operator_strategy_payouts" @@ -31,14 +36,18 @@ var ( ) 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_1_StakerStrategyPayouts: Sot_1_StakerStrategyPayouts, Sot_2_OperatorStrategyPayouts: Sot_2_OperatorStrategyPayouts, @@ -73,7 +82,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 diff --git a/scripts/downloadTestData.sh b/scripts/downloadTestData.sh index 2b82e230..0951bfbb 100755 --- a/scripts/downloadTestData.sh +++ b/scripts/downloadTestData.sh @@ -16,4 +16,5 @@ if [[ -z $version ]]; then exit 1 fi echo "Downloading testdata version $dataUrl" + curl -L $dataUrl | tar xvz -C ./ 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