Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: utilize split in existing rewards calculation #117

Merged
merged 15 commits into from
Dec 2, 2024
7 changes: 6 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package config
import (
"errors"
"fmt"
"github.com/spf13/viper"
"strconv"
"strings"

"github.com/spf13/viper"
)

type EnvScope string
Expand All @@ -29,6 +30,7 @@ const (
Fork_Nile ForkName = "nile"
Fork_Amazon ForkName = "amazon"
Fork_Panama ForkName = "panama"
Fork_Arno ForkName = "arno"

ENV_PREFIX = "SIDECAR"
)
Expand Down Expand Up @@ -194,18 +196,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-04",
}, 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-10",
}, 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package _202411221331_operatorAVSSplitSnapshots

import (
"database/sql"

"gorm.io/gorm"
)

type Migration struct {
}

func (m *Migration) Up(db *sql.DB, grm *gorm.DB) 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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package _202411221331_operatorPISplitSnapshots

import (
"database/sql"

"gorm.io/gorm"
)

type Migration struct {
}

func (m *Migration) Up(db *sql.DB, grm *gorm.DB) 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"
}
4 changes: 4 additions & 0 deletions pkg/postgres/migrations/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import (
_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"

"go.uber.org/zap"
"gorm.io/gorm"
Expand Down Expand Up @@ -112,6 +114,8 @@ func (m *Migrator) MigrateAll() error {
&_202411191550_operatorAVSSplits.Migration{},
&_202411191708_operatorPISplits.Migration{},
&_202411191947_cleanupUnusedTables.Migration{},
&_202411221331_operatorAVSSplitSnapshots.Migration{},
&_202411221331_operatorPISplitSnapshots.Migration{},
}

for _, migration := range migrations {
Expand Down
36 changes: 23 additions & 13 deletions pkg/rewards/2_goldStakerRewardAmounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rewards

import (
"database/sql"

"github.com/Layr-Labs/sidecar/internal/config"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -107,26 +108,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))
ypatil12 marked this conversation as resolved.
Show resolved Hide resolved
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))
seanmcgary marked this conversation as resolved.
Show resolved Hide resolved
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
Expand All @@ -141,6 +149,7 @@ 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 := renderQueryTemplate(_2_goldStakerRewardAmountsQuery, map[string]string{
Expand All @@ -155,6 +164,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)
Expand Down
26 changes: 21 additions & 5 deletions pkg/rewards/5_goldRfaeStakers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rewards

import (
"database/sql"

"github.com/Layr-Labs/sidecar/internal/config"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -106,12 +107,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
Expand All @@ -124,6 +138,7 @@ 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 := renderQueryTemplate(_5_goldRfaeStakersQuery, map[string]string{
Expand All @@ -138,6 +153,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)
Expand Down
92 changes: 92 additions & 0 deletions pkg/rewards/operatorAvsSplitSnapshots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package rewards

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 *,
ROW_NUMBER() OVER (PARTITION BY operator, avs, cast(activated_at AS DATE) ORDER BY activated_at DESC, block_time DESC, log_index DESC) AS rn
FROM operator_avs_splits_with_block_info
),
-- Get the latest record for each day & round up to the snapshot day
snapshotted_records as (
SELECT
operator,
avs,
split,
block_time,
date_trunc('day', activated_at) + INTERVAL '1' day AS snapshot_time
from ranked_operator_avs_split_records
where rn = 1
),
-- 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}}')
ELSE LEAD(snapshot_time) OVER (PARTITION BY operator, avs ORDER BY snapshot_time)
END AS end_time
FROM snapshotted_records
),
-- 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 := renderQueryTemplate(operatorAvsSplitSnapshotQuery, map[string]string{
"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
}
Loading