diff --git a/cmd/database.go b/cmd/database.go index a8a0592b..d90aba02 100644 --- a/cmd/database.go +++ b/cmd/database.go @@ -18,6 +18,7 @@ import ( "github.com/Layr-Labs/sidecar/pkg/postgres" "github.com/Layr-Labs/sidecar/pkg/postgres/migrations" "github.com/Layr-Labs/sidecar/pkg/rewards" + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" pgStorage "github.com/Layr-Labs/sidecar/pkg/storage/postgres" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -88,7 +89,9 @@ var runDatabaseCmd = &cobra.Command{ idxr := indexer.NewIndexer(mds, contractStore, cm, client, fetchr, cc, grm, l, cfg) - rc, err := rewards.NewRewardsCalculator(cfg, grm, mds, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + + rc, err := rewards.NewRewardsCalculator(cfg, grm, mds, sog, l) if err != nil { l.Sugar().Fatalw("Failed to create rewards calculator", zap.Error(err)) } diff --git a/cmd/run.go b/cmd/run.go index 7e1bc275..606b4254 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -14,6 +14,7 @@ import ( "github.com/Layr-Labs/sidecar/pkg/pipeline" "github.com/Layr-Labs/sidecar/pkg/postgres" "github.com/Layr-Labs/sidecar/pkg/rewards" + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" "github.com/Layr-Labs/sidecar/pkg/shutdown" "github.com/Layr-Labs/sidecar/pkg/sidecar" pgStorage "github.com/Layr-Labs/sidecar/pkg/storage/postgres" @@ -94,7 +95,9 @@ var runCmd = &cobra.Command{ idxr := indexer.NewIndexer(mds, contractStore, cm, client, fetchr, cc, grm, l, cfg) - rc, err := rewards.NewRewardsCalculator(cfg, grm, mds, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + + rc, err := rewards.NewRewardsCalculator(cfg, grm, mds, sog, l) if err != nil { l.Sugar().Fatalw("Failed to create rewards calculator", zap.Error(err)) } diff --git a/pkg/postgres/migrations/202412021311_stakerOperatorTables/up.go b/pkg/postgres/migrations/202412021311_stakerOperatorTables/up.go new file mode 100644 index 00000000..4a2a3cb4 --- /dev/null +++ b/pkg/postgres/migrations/202412021311_stakerOperatorTables/up.go @@ -0,0 +1,60 @@ +package _202412021311_stakerOperatorTables + +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 sot_staker_strategy_payouts ( + reward_hash varchar NOT NULL, + snapshot TIMESTAMP NOT NULL, + token varchar NOT NULL, + tokens_per_day double precision NOT NULL, + avs varchar NOT NULL, + strategy varchar NOT NULL, + multiplier numeric NOT NULL, + reward_type varchar NOT NULL, + operator varchar NOT NULL, + staker varchar NOT NULL, + shares numeric NOT NULL, + staker_tokens numeric NOT NULL, + staker_strategy_weight numeric NOT NULL, + staker_total_strategy_weight numeric NOT NULL, + staker_strategy_proportion numeric NOT NULL, + staker_strategy_tokens numeric NOT NULL + );`, + `CREATE TABLE IF NOT EXISTS sot_operator_strategy_rewards ( + reward_hash varchar NOT NULL, + snapshot TIMESTAMP NOT NULL, + token varchar NOT NULL, + tokens_per_day double precision NOT NULL, + avs varchar NOT NULL, + strategy varchar NOT NULL, + multiplier numeric NOT NULL, + reward_type varchar NOT NULL, + operator varchar NOT NULL, + shares numeric NOT NULL, + operator_tokens numeric NOT NULL, + operator_strategy_weight numeric NOT NULL, + operator_total_strategy_weight numeric NOT NULL, + operator_strategy_proportion numeric NOT NULL, + operator_strategy_tokens numeric NOT NULL + )`, + } + + for _, query := range queries { + if err := grm.Exec(query).Error; err != nil { + return err + } + } + return nil +} + +func (m *Migration) GetName() string { + return "202412021311_stakerOperatorTables" +} diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index da3bfbc1..7d8fc7ed 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -33,6 +33,7 @@ import ( _202411130953_addHashColumns "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411130953_addHashColumns" _202411131200_eigenStateModelConstraints "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411131200_eigenStateModelConstraints" _202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables" + _202412021311_stakerOperatorTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412021311_stakerOperatorTables" "go.uber.org/zap" "gorm.io/gorm" "time" @@ -104,6 +105,7 @@ func (m *Migrator) MigrateAll() error { &_202411130953_addHashColumns.Migration{}, &_202411131200_eigenStateModelConstraints.Migration{}, &_202411191947_cleanupUnusedTables.Migration{}, + &_202412021311_stakerOperatorTables.Migration{}, } for _, migration := range migrations { diff --git a/pkg/rewards/combinedRewards_test.go b/pkg/rewards/combinedRewards_test.go index 37051a92..bec0f0b4 100644 --- a/pkg/rewards/combinedRewards_test.go +++ b/pkg/rewards/combinedRewards_test.go @@ -5,6 +5,7 @@ import ( "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" @@ -109,7 +110,8 @@ func Test_CombinedRewards(t *testing.T) { } }) t.Run("Should generate the proper combinedRewards", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) err = rewards.GenerateAndInsertCombinedRewards(snapshotDate) assert.Nil(t, err) diff --git a/pkg/rewards/operatorAvsRegistrationSnaphots_test.go b/pkg/rewards/operatorAvsRegistrationSnaphots_test.go index 662fb8e0..b87d1d71 100644 --- a/pkg/rewards/operatorAvsRegistrationSnaphots_test.go +++ b/pkg/rewards/operatorAvsRegistrationSnaphots_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -124,7 +125,8 @@ func Test_OperatorAvsRegistrationSnapshots(t *testing.T) { } }) t.Run("Should generate the proper operatorAvsRegistrationWindows", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) err := rewards.GenerateAndInsertOperatorAvsRegistrationSnapshots(snapshotDate) assert.Nil(t, err) diff --git a/pkg/rewards/operatorAvsStrategySnapshots_test.go b/pkg/rewards/operatorAvsStrategySnapshots_test.go index ecba8d06..9f373eac 100644 --- a/pkg/rewards/operatorAvsStrategySnapshots_test.go +++ b/pkg/rewards/operatorAvsStrategySnapshots_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -118,7 +119,8 @@ func Test_OperatorAvsStrategySnapshots(t *testing.T) { }) t.Run("Should calculate correct operatorAvsStrategy windows", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) t.Log("Generating snapshots") err := rewards.GenerateAndInsertOperatorAvsStrategySnapshots(snapshotDate) diff --git a/pkg/rewards/operatorShareSnapshots_test.go b/pkg/rewards/operatorShareSnapshots_test.go index d6b261f1..98f862bc 100644 --- a/pkg/rewards/operatorShareSnapshots_test.go +++ b/pkg/rewards/operatorShareSnapshots_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -99,7 +100,8 @@ func Test_OperatorShareSnapshots(t *testing.T) { } }) t.Run("Should generate operator share snapshots", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) t.Log("Generating operator share snapshots") err := rewards.GenerateAndInsertOperatorShareSnapshots(snapshotDate) diff --git a/pkg/rewards/operatorShares_test.go b/pkg/rewards/operatorShares_test.go index 1999bcd8..627c7ac2 100644 --- a/pkg/rewards/operatorShares_test.go +++ b/pkg/rewards/operatorShares_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -84,7 +85,8 @@ func Test_OperatorShares(t *testing.T) { } }) t.Run("Should generate staker shares", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) t.Log("Generating staker shares") err := rewards.GenerateAndInsertOperatorShares(snapshotDate) diff --git a/pkg/rewards/rewards.go b/pkg/rewards/rewards.go index ca84f5aa..c553e168 100644 --- a/pkg/rewards/rewards.go +++ b/pkg/rewards/rewards.go @@ -5,7 +5,8 @@ import ( "errors" "fmt" "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/distribution" - "github.com/Layr-Labs/sidecar/pkg/postgres/helpers" + "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "github.com/Layr-Labs/sidecar/pkg/storage" "github.com/Layr-Labs/sidecar/pkg/utils" gethcommon "github.com/ethereum/go-ethereum/common" @@ -23,6 +24,7 @@ type RewardsCalculator struct { logger *zap.Logger grm *gorm.DB blockStore storage.BlockStore + sog *stakerOperators.StakerOperatorsGenerator globalConfig *config.Config } @@ -30,12 +32,14 @@ func NewRewardsCalculator( cfg *config.Config, grm *gorm.DB, bs storage.BlockStore, + sog *stakerOperators.StakerOperatorsGenerator, l *zap.Logger, ) (*RewardsCalculator, error) { rc := &RewardsCalculator{ logger: l, grm: grm, blockStore: bs, + sog: sog, globalConfig: cfg, } @@ -231,6 +235,12 @@ func (rc *RewardsCalculator) calculateRewards(snapshotDate string) error { return err } + if err = rc.sog.GenerateStakerOperatorsTable(snapshotDate); err != nil { + _ = rc.UpdateRewardSnapshotStatus(snapshotDate, storage.RewardSnapshotStatusFailed) + rc.logger.Sugar().Errorw("Failed to generate staker operators table", "error", err) + return err + } + if err = rc.UpdateRewardSnapshotStatus(snapshotDate, storage.RewardSnapshotStatusCompleted); err != nil { rc.logger.Sugar().Errorw("Failed to update reward snapshot status", "error", err) return err @@ -366,32 +376,13 @@ func (rc *RewardsCalculator) generateAndInsertFromQuery( query string, variables map[string]interface{}, ) error { - tmpTableName := fmt.Sprintf("%s_tmp", tableName) - - queryWithInsert := fmt.Sprintf("CREATE TABLE %s AS %s", tmpTableName, query) - - _, err := helpers.WrapTxAndCommit(func(tx *gorm.DB) (interface{}, error) { - queries := []string{ - queryWithInsert, - fmt.Sprintf(`drop table if exists %s`, tableName), - fmt.Sprintf(`alter table %s rename to %s`, tmpTableName, tableName), - } - for i, query := range queries { - var res *gorm.DB - if i == 0 && variables != nil { - res = tx.Exec(query, variables) - } else { - res = tx.Exec(query) - } - if res.Error != nil { - rc.logger.Sugar().Errorw("Failed to execute query", "query", query, "error", res.Error) - return nil, res.Error - } - } - return nil, nil - }, rc.grm, nil) - - return err + return rewardsUtils.GenerateAndInsertFromQuery( + rc.grm, + tableName, + query, + variables, + rc.logger, + ) } var ( @@ -405,23 +396,8 @@ var ( Table_8_GoldTable = "gold_table" ) -var goldTableBaseNames = map[string]string{ - 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", -} - func getGoldTableNames(snapshotDate string) map[string]string { - tableNames := make(map[string]string) - for key, baseName := range goldTableBaseNames { - tableNames[key] = formatTableName(baseName, snapshotDate) - } - return tableNames + return rewardsUtils.GetGoldTableNames(snapshotDate) } func renderQueryTemplate(query string, variables map[string]string) (string, error) { diff --git a/pkg/rewards/rewards_test.go b/pkg/rewards/rewards_test.go index 4a65f7f6..f6cc5e9c 100644 --- a/pkg/rewards/rewards_test.go +++ b/pkg/rewards/rewards_test.go @@ -6,6 +6,7 @@ import ( "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/Layr-Labs/sidecar/pkg/utils" "github.com/stretchr/testify/assert" "go.uber.org/zap" @@ -94,6 +95,8 @@ func setupRewards() ( error, ) { cfg := tests.GetConfig() + cfg.Rewards.GenerateStakerOperatorsTable = true + cfg.Rewards.ValidateRewardsRoot = true cfg.Chain = config.Chain_Mainnet cfg.DatabaseConfig = *tests.GetDbConfigFromEnv() @@ -128,8 +131,10 @@ func Test_Rewards(t *testing.T) { // 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, l) + rc, err := NewRewardsCalculator(cfg, grm, nil, sog, l) assert.Nil(t, err) if err != nil { t.Fatal(err) @@ -312,6 +317,10 @@ func Test_Rewards(t *testing.T) { t.Fatalf("Invalid amounts: %d", invalidAmounts) } + t.Logf("Generating staker operators table") + err = rc.sog.GenerateStakerOperatorsTable(snapshotDate) + assert.Nil(t, err) + accountTree, _, err := rc.MerkelizeRewardsForSnapshot(snapshotDate) assert.Nil(t, err) diff --git a/pkg/rewards/stakerDelegationSnapshots_test.go b/pkg/rewards/stakerDelegationSnapshots_test.go index 6bcc61ef..0a8a9313 100644 --- a/pkg/rewards/stakerDelegationSnapshots_test.go +++ b/pkg/rewards/stakerDelegationSnapshots_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -100,7 +101,8 @@ func Test_StakerDelegationSnapshots(t *testing.T) { } }) t.Run("Should generate staker share snapshots", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) t.Log("Generating staker delegation snapshots") err = rewards.GenerateAndInsertStakerDelegationSnapshots(snapshotDate) diff --git a/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go b/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go new file mode 100644 index 00000000..6cd796d5 --- /dev/null +++ b/pkg/rewards/stakerOperators/1_stakerStrategyPayouts.go @@ -0,0 +1,152 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "time" +) + +const _1_stakerStrategyPayoutsQuery = ` +WITH reward_snapshot_operators as ( + SELECT + ap.reward_hash, + ap.snapshot, + ap.token, + ap.tokens_per_day, + ap.avs, + ap.strategy, + ap.multiplier, + ap.reward_type, + oar.operator + FROM {{.activeRewardsTable}} ap + JOIN operator_avs_registration_snapshots oar + ON ap.avs = oar.avs and ap.snapshot = oar.snapshot + WHERE ap.reward_type = 'avs' +), +-- Get the strategies that the operator is restaking on the snapshot +operator_restaked_strategies AS ( + SELECT + rso.* + FROM reward_snapshot_operators rso + JOIN operator_avs_strategy_snapshots oas + ON + rso.operator = oas.operator AND + rso.avs = oas.avs AND + rso.strategy = oas.strategy AND + rso.snapshot = oas.snapshot +), +-- Get the stakers that were delegated to the operator for the snapshot +staker_delegated_operators AS ( + SELECT + ors.*, + sds.staker + FROM operator_restaked_strategies ors + JOIN staker_delegation_snapshots sds + ON + ors.operator = sds.operator AND + ors.snapshot = sds.snapshot +), +-- Get the shares for staker 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 +), +-- Join the strategies that were not included in staker_rewards originally +rejoined_staker_strategies AS ( + SELECT + sas.*, + spa.staker_tokens + FROM staker_avs_strategy_shares sas + JOIN {{.stakerRewardAmountsTable}} spa + ON + sas.snapshot = spa.snapshot AND + sas.reward_hash = spa.reward_hash AND + sas.staker = spa.staker + WHERE sas.shares > 0 AND sas.multiplier != 0 +), +-- Calculate the weight of a staker for each of their strategies +staker_strategy_weights AS ( + SELECT *, + multiplier * shares AS staker_strategy_weight + FROM rejoined_staker_strategies + ORDER BY reward_hash, snapshot, staker, strategy +), +-- Calculate sum of all staker_strategy_weight for each reward and snapshot across all relevant strategies and stakers +staker_strategy_weights_sum AS ( + SELECT *, + SUM(staker_strategy_weight) OVER (PARTITION BY staker, reward_hash, snapshot) as staker_total_strategy_weight + FROM staker_strategy_weights +), +-- Calculate staker strategy proportion of tokens for each reward and snapshot +staker_strategy_proportions AS ( + SELECT *, + FLOOR((staker_strategy_weight / staker_total_strategy_weight) * 1000000000000000) / 1000000000000000 as staker_strategy_proportion + FROM staker_strategy_weights_sum +), +staker_strategy_tokens AS ( + SELECT *, + floor(staker_strategy_proportion * staker_tokens) as staker_strategy_tokens + FROM staker_strategy_proportions +) +SELECT * from staker_strategy_tokens +` + +type StakerStrategyPayout struct { + RewardHash string + Snapshot time.Time + Token string + TokensPerDay float64 + Avs string + Strategy string + Multiplier string + RewardType string + Operator string + Staker string + Shares string + StakerTokens string + StakerStrategyWeight string + StakerTotalStrategyWeight string + StakerStrategyProportion string + StakerStrategyTokens string +} + +func (ssp *StakerStrategyPayout) TableName() string { + return "sot_staker_strategy_payouts" +} + +func (sog *StakerOperatorsGenerator) GenerateAndInsert1StakerStrategyPayouts(cutoffDate string) error { + tableName := "sot_staker_strategy_payouts" + allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) + + query, err := rewardsUtils.RenderQueryTemplate(_1_stakerStrategyPayoutsQuery, map[string]string{ + "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], + "stakerRewardAmountsTable": allTableNames[rewardsUtils.Table_2_StakerRewardAmounts], + }) + if err != nil { + sog.logger.Sugar().Errorw("Failed to render 1_stakerStrategyPayouts query", "error", err) + return err + } + + err = rewardsUtils.GenerateAndInsertFromQuery(sog.db, tableName, query, nil, sog.logger) + if err != nil { + sog.logger.Sugar().Errorw("Failed to generate 1_stakerStrategyPayouts", "error", err) + return err + } + return nil +} + +func (sog *StakerOperatorsGenerator) List1StakerStrategyPayouts() ([]*StakerStrategyPayout, error) { + var stakerStrategyRewards []*StakerStrategyPayout + res := sog.db.Model(&StakerStrategyPayout{}).Find(&stakerStrategyRewards) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to list 1_stakerStrategyPayouts", "error", res.Error) + return nil, res.Error + } + return stakerStrategyRewards, nil +} diff --git a/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go b/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go new file mode 100644 index 00000000..b5b210b8 --- /dev/null +++ b/pkg/rewards/stakerOperators/2_operatorStrategyRewards.go @@ -0,0 +1,137 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "time" +) + +const _2_operatorStrategyRewardsQuery = ` +WITH reward_snapshot_operators as ( + SELECT + ap.reward_hash, + ap.snapshot, + ap.token, + ap.tokens_per_day, + ap.avs, + ap.strategy, + ap.multiplier, + ap.reward_type, + oar.operator + FROM {{.activeRewardsTable}} ap + JOIN operator_avs_registration_snapshots oar + ON ap.avs = oar.avs and ap.snapshot = oar.snapshot + WHERE ap.reward_type = 'avs' +), +operator_restaked_strategies AS ( + SELECT + rso.* + FROM reward_snapshot_operators rso + JOIN operator_avs_strategy_snapshots oas + ON + rso.operator = oas.operator AND + rso.avs = oas.avs AND + rso.strategy = oas.strategy AND + rso.snapshot = oas.snapshot +), +operator_avs_strategy_shares AS ( + SELECT + oas.*, + oss.shares + FROM operator_restaked_strategies oas + JOIN operator_share_snapshots oss + ON + oas.operator = oss.operator AND + oas.strategy = oss.strategy AND + oas.snapshot = oss.snapshot +), +rejoined_operator_strategies AS ( + SELECT + oass.*, + opa.operator_tokens + FROM operator_avs_strategy_shares oass + JOIN {{.operatorRewardAmountsTable}} opa + ON + oass.snapshot = opa.snapshot AND + oass.reward_hash = opa.reward_hash AND + oass.operator = opa.operator + WHERE oass.shares > 0 AND oass.multiplier != 0 +), +-- Calculate the weight of a operator for each of their strategies +operator_strategy_weights AS ( + SELECT *, + multiplier * shares AS operator_strategy_weight + FROM rejoined_operator_strategies + ORDER BY reward_hash, snapshot, operator, strategy +), +-- Calculate sum of each operator operator_strategy_weight for each reward and snapshot for a given operator +operator_strategy_weights_sum AS ( + SELECT *, + SUM(operator_strategy_weight) OVER (PARTITION BY operator, reward_hash, snapshot) as operator_total_strategy_weight + FROM operator_strategy_weights +), +-- Calculate operator strategy proportion of tokens for each reward and snapshot +operator_strategy_proportions AS ( + SELECT *, + FLOOR((operator_strategy_weight / operator_total_strategy_weight) * 1000000000000000) / 1000000000000000 as operator_strategy_proportion + FROM operator_strategy_weights_sum +), +operator_strategy_tokens AS ( + SELECT *, + floor(operator_strategy_proportion * operator_tokens) as operator_strategy_tokens + FROM operator_strategy_proportions +) +SELECT * FROM operator_strategy_tokens +` + +type OperatorStrategyRewards struct { + RewardHash string + Snapshot time.Time + Token string + TokensPerDay float64 + Avs string + Strategy string + Multiplier string + RewardType string + Operator string + Shares string + OperatorTokens string + OperatorStrategyWeight string + OperatorTotalStrategyWeight string + OperatorStrategyProportion string + OperatorStrategyTokens string +} + +func (osr *OperatorStrategyRewards) TableName() string { + return "sot_operator_strategy_rewards" +} + +func (sog *StakerOperatorsGenerator) GenerateAndInsert2OperatorStrategyRewards(cutoffDate string) error { + tableName := "sot_operator_strategy_rewards" + allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) + + query, err := rewardsUtils.RenderQueryTemplate(_2_operatorStrategyRewardsQuery, map[string]string{ + "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], + "operatorRewardAmountsTable": allTableNames[rewardsUtils.Table_3_OperatorRewardAmounts], + }) + if err != nil { + sog.logger.Sugar().Errorw("Failed to render 2_operatorStrategyRewards query", "error", err) + return err + } + + err = rewardsUtils.GenerateAndInsertFromQuery(sog.db, tableName, query, nil, sog.logger) + if err != nil { + sog.logger.Sugar().Errorw("Failed to generate 2_operatorStrategyRewards", "error", err) + return err + } + return nil +} + +func (sog *StakerOperatorsGenerator) List2OperatorStrategyRewards() ([]*OperatorStrategyRewards, error) { + var rewards []*OperatorStrategyRewards + res := sog.db.Model(&OperatorStrategyRewards{}).Find(&rewards) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to list 2_operatorStrategyRewards", "error", res.Error) + return nil, res.Error + } + return rewards, nil +} diff --git a/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go b/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go new file mode 100644 index 00000000..c2f8c73d --- /dev/null +++ b/pkg/rewards/stakerOperators/3_rewardsForAllStrategyPayouts.go @@ -0,0 +1,127 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "time" +) + +/** + * This table calculates the tokens for each staker from the pay for all rewards on a per-strategy basis + * + * Reward_snapshot_stakers: Get the stakers that are being paid out for a given snapshot + * Rejoined_staker_strategies: Join the strategies that were not included in staker_rewards originally + * Staker_strategy_weights: Calculate the weight of a staker for each of their strategies + * Staker_strategy_weights_sum: Calculate sum of all staker_strategy_weight for each rewards and snapshot across all relevant strategies and stakers + * Staker_strategy_proportions: Calculate staker strategy proportion of tokens for each rewards and snapshot + * Staker_strategy_p4a_tokens: Calculate the tokens for each staker from the pay for all rewards on a per-strategy basis + */ +const _3_rewardsForAllStrategyPayoutsQuery = ` +WITH reward_snapshot_stakers AS ( + SELECT + ap.reward_hash, + ap.snapshot, + ap.token, + ap.tokens_per_day, + ap.avs, + ap.strategy, + ap.multiplier, + ap.reward_type, + sss.staker, + sss.shares + FROM {{.activeRewardsTable}} ap + JOIN staker_share_snapshots sss + ON ap.strategy = sss.strategy and ap.snapshot = sss.snapshot + WHERE ap.reward_type = 'all_stakers' +), +-- Join the strategies that were not included in pay for all rewards originally +rejoined_staker_strategies AS ( + SELECT + rss.*, + rfa.staker_tokens + FROM reward_snapshot_stakers rss + JOIN {{.rewardsForAllTable}} rfa + ON + rss.snapshot = rfa.snapshot AND + rss.reward_hash = rfa.reward_hash AND + rss.staker = rfa.staker + WHERE rss.shares > 0 and rss.multiplier != 0 +), +-- Calculate the weight of a staker for each of their strategies +staker_strategy_weights AS ( + SELECT *, + multiplier * shares AS staker_strategy_weight + FROM rejoined_staker_strategies + ORDER BY reward_hash, snapshot, staker, strategy +), +-- Calculate sum of all staker_strategy_weight for each reward and snapshot across all relevant strategies and stakers +staker_strategy_weights_sum AS ( + SELECT *, + SUM(staker_strategy_weight) OVER (PARTITION BY staker, reward_hash, snapshot) as staker_total_strategy_weight + FROM staker_strategy_weights +), +-- Calculate staker strategy proportion of tokens for each reward and snapshot +staker_strategy_proportions AS ( + SELECT *, + FLOOR((staker_strategy_weight / staker_total_strategy_weight) * 1000000000000000) / 1000000000000000 as staker_strategy_proportion + FROM staker_strategy_weights_sum +), +staker_strategy_p4a_tokens AS ( + SELECT *, + floor(staker_strategy_proportion * staker_tokens) as staker_strategy_tokens + FROM staker_strategy_proportions +) +SELECT * from staker_strategy_p4a_tokens +` + +type RewardsForAllStrategyPayout struct { + RewardHash string + Snapshot time.Time + Token string + TokensPerDay float64 + Avs string + Strategy string + Multiplier string + RewardType string + Staker string + Shares string + StakerTokens string + StakerStrategyWeight string + StakerTotalStrategyWeight string + StakerStrategyProportion string + StakerStrategyTokens string +} + +func (osr *RewardsForAllStrategyPayout) TableName() string { + return "sot_rewards_for_all_strategy_payout" +} + +func (sog *StakerOperatorsGenerator) GenerateAndInsert3RewardsForAllStrategyPayout(cutoffDate string) error { + tableName := "sot_rewards_for_all_strategy_payout" + allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) + + query, err := rewardsUtils.RenderQueryTemplate(_3_rewardsForAllStrategyPayoutsQuery, map[string]string{ + "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], + "rewardForAllTable": allTableNames[rewardsUtils.Table_4_RewardsForAll], + }) + if err != nil { + sog.logger.Sugar().Errorw("Failed to render 3_rewardsForAllStrategyPayoutsQuery query", "error", err) + return err + } + + err = rewardsUtils.GenerateAndInsertFromQuery(sog.db, tableName, query, nil, sog.logger) + if err != nil { + sog.logger.Sugar().Errorw("Failed to generate 3_rewardsForAllStrategyPayoutsQuery", "error", err) + return err + } + return nil +} + +func (sog *StakerOperatorsGenerator) List3RewardsForAllStrategyPayout() ([]*RewardsForAllStrategyPayout, error) { + var rewards []*RewardsForAllStrategyPayout + res := sog.db.Model(&RewardsForAllStrategyPayout{}).Find(&rewards) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to list 3_rewardsForAllStrategyPayoutsQuery", "error", res.Error) + return nil, res.Error + } + return rewards, nil +} diff --git a/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go b/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go new file mode 100644 index 00000000..70a3e470 --- /dev/null +++ b/pkg/rewards/stakerOperators/4_rfaeStakerStrategyPayouts.go @@ -0,0 +1,159 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "time" +) + +/** + * This table calculates the tokens for each staker from the pay for all rewards on a per-strategy basis + * + * Reward_snapshot_stakers: Get the stakers that are being paid out for a given snapshot + * Rejoined_staker_strategies: Join the strategies that were not included in staker_rewards originally + * Staker_strategy_weights: Calculate the weight of a staker for each of their strategies + * Staker_strategy_weights_sum: Calculate sum of all staker_strategy_weight for each rewards and snapshot across all relevant strategies and stakers + * Staker_strategy_proportions: Calculate staker strategy proportion of tokens for each rewards and snapshot + * Staker_strategy_p4a_tokens: Calculate the tokens for each staker from the pay for all rewards on a per-strategy basis + */ +const _4_rfaeStakerStrategyPayoutsQuery = ` +WITH avs_opted_operators AS ( + SELECT DISTINCT + snapshot, + operator + FROM operator_avs_registration_snapshots +), +-- Get the operators who will earn rewards for the reward submission at the given snapshot +reward_snapshot_operators as ( + SELECT + ap.reward_hash, + ap.snapshot, + ap.token, + ap.tokens_per_day, + ap.avs, + ap.strategy, + ap.multiplier, + ap.reward_type, + aoo.operator + FROM {{.activeRewardsTable}} ap + JOIN avs_opted_operators aoo + ON ap.snapshot = aoo.snapshot + WHERE ap.reward_type = 'all_earners' +), +-- Get the stakers that were delegated to the operator for the snapshot +staker_delegated_operators AS ( + SELECT + rso.*, + sds.staker + FROM reward_snapshot_operators rso + JOIN staker_delegation_snapshots sds + ON + rso.operator = sds.operator AND + rso.snapshot = sds.snapshot +), +-- Get the shares of each strategy the staker has delegated to the operator +staker_strategy_shares AS ( + SELECT + sdo.*, + sss.shares + FROM staker_delegated_operators sdo + JOIN staker_share_snapshots sss + ON + sdo.staker = sss.staker AND + sdo.snapshot = sss.snapshot AND + sdo.strategy = sss.strategy +), +-- Join the strategies that were not included in rfae_stakers originally +rejoined_staker_strategies AS ( + SELECT + sss.*, + rfas.staker_tokens + FROM staker_strategy_shares sss + JOIN {{.rfaeStakerTable}} rfas + ON + sss.snapshot = rfas.snapshot AND + sss.reward_hash = rfas.reward_hash AND + sss.staker = rfas.staker + -- Parse out negative shares and zero multiplier so there is no division by zero case + WHERE sss.shares > 0 and sss.multiplier > 0 +), +-- Calculate the weight of a staker for each of their strategies +staker_strategy_weights AS ( + SELECT *, + multiplier * shares AS staker_strategy_weight + FROM rejoined_staker_strategies + ORDER BY reward_hash, snapshot, staker, strategy +), +-- Calculate sum of all staker_strategy_weight for each reward and snapshot across all relevant strategies and stakers +staker_strategy_weights_sum AS ( + SELECT *, + SUM(staker_strategy_weight) OVER (PARTITION BY staker, reward_hash, snapshot) as staker_total_strategy_weight + FROM staker_strategy_weights +), +-- Calculate staker strategy proportion of tokens for each reward and snapshot +staker_strategy_proportions AS ( + SELECT *, + FLOOR((staker_strategy_weight / staker_total_strategy_weight) * 1000000000000000) / 1000000000000000 as staker_strategy_proportion + FROM staker_strategy_weights_sum +), +staker_strategy_tokens AS ( + SELECT *, + floor(staker_strategy_proportion * staker_tokens) as staker_strategy_tokens + FROM staker_strategy_proportions +) +SELECT * from staker_strategy_tokens + +` + +type RfaeStakerStrategyPayout struct { + RewardHash string + Snapshot time.Time + Token string + TokensPerDay float64 + Avs string + Strategy string + Multiplier string + RewardType string + Operator string + Staker string + Shares string + StakerTokens string + StakerStrategyWeight string + StakerTotalStrategyWeight string + StakerStrategyProportion string + StakerStrategyTokens string +} + +func (osr *RfaeStakerStrategyPayout) TableName() string { + return "sot_rfae_staker_strategy_payout" +} + +func (sog *StakerOperatorsGenerator) GenerateAndInsert4RfaeStakerStrategyPayout(cutoffDate string) error { + tableName := "sot_rfae_staker_strategy_payout" + allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) + + query, err := rewardsUtils.RenderQueryTemplate(_4_rfaeStakerStrategyPayoutsQuery, map[string]string{ + "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], + "rfaeStakerTable": allTableNames[rewardsUtils.Table_5_RfaeStakers], + }) + if err != nil { + sog.logger.Sugar().Errorw("Failed to render 4_rfaeStakerStrategyPayoutsQuery query", "error", err) + return err + } + + err = rewardsUtils.GenerateAndInsertFromQuery(sog.db, tableName, query, nil, sog.logger) + if err != nil { + sog.logger.Sugar().Errorw("Failed to generate 4_rfaeStakerStrategyPayoutsQuery", "error", err) + return err + } + return nil +} + +func (sog *StakerOperatorsGenerator) List4RfaeStakerStrategyPayout() ([]*RfaeStakerStrategyPayout, error) { + var rewards []*RfaeStakerStrategyPayout + res := sog.db.Model(&RfaeStakerStrategyPayout{}).Find(&rewards) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to list 4_rfaeStakerStrategyPayoutsQuery", "error", res.Error) + return nil, res.Error + } + return rewards, nil +} diff --git a/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go b/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go new file mode 100644 index 00000000..a551b937 --- /dev/null +++ b/pkg/rewards/stakerOperators/5_rfaeOperatorStrategyPayout.go @@ -0,0 +1,148 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "time" +) + +/** + * This view calculates the tokens returned to each operator for rewards_for_all_earners on a per-strategy basis + * + * 1. avs_opted_operators: Get the operators who have registered for an AVS for a given snapshot + * 2. reward_snapshot_operators: Get the operators for the avs's strategy reward + * 3. staker_delegated_operators: Get the stakers that were delegated to the operator for the snapshot + * 4. staker_avs_strategy_shares: Get the shares for staker delegated to the operator + * 5. rejoined_staker_strategies: Join the strategies that were not included in rewards_for_all_earners originally + * 6. staker_strategy_weights: Calculate the weight of a staker for each of their strategies + * 7. staker_strategy_weights_sum: Calculate sum of all staker_strategy_weight for each (staker, reward, snapshot) + * 8. staker_strategy_proportions: Calculate staker strategy proportion of tokens for each reward and snapshot + * 9. staker_strategy_tokens: Calculate the tokens returned to each staker on a per-strategy basis + */ + +const _5_rfaeOperatorStrategyPayoutsQuery = ` +WITH avs_opted_operators AS ( + SELECT DISTINCT + snapshot, + operator + FROM operator_avs_registration_snapshots +), +-- Get the operators who will earn rewards for the reward submission at the given snapshot +reward_snapshot_operators as ( + SELECT + ap.reward_hash, + ap.snapshot, + ap.token, + ap.tokens_per_day, + ap.avs, + ap.strategy, + ap.multiplier, + ap.reward_type, + aoo.operator + FROM {{.activeRewardsTable}} ap + JOIN avs_opted_operators aoo + ON ap.snapshot = aoo.snapshot + WHERE ap.reward_type = 'all_earners' +), +operator_strategy_shares AS ( + SELECT + rso.*, + oss.shares + FROM reward_snapshot_operators rso + JOIN operator_share_snapshots oss + ON + rso.operator = oss.operator AND + rso.strategy = oss.strategy AND + rso.snapshot = oss.snapshot +), +rejoined_operator_strategies AS ( + SELECT + oss.*, + rfao.operator_tokens + FROM operator_strategy_shares oss + JOIN {{.rfaeOperatorTable}} rfao + ON + oss.snapshot = rfao.snapshot AND + oss.reward_hash = rfao.reward_hash AND + oss.operator = rfao.operator + -- Parse out negative shares and zero multiplier so there is no division by zero case + WHERE oss.shares > 0 and oss.multiplier > 0 +), +-- Calculate the weight of a operator for each of their strategies +operator_strategy_weights AS ( + SELECT *, + multiplier * shares AS operator_strategy_weight + FROM rejoined_operator_strategies + ORDER BY reward_hash, snapshot, operator, strategy +), +-- Calculate sum of each operator operator_strategy_weight for each reward and snapshot for a given operator +operator_strategy_weights_sum AS ( + SELECT *, + SUM(operator_strategy_weight) OVER (PARTITION BY operator, reward_hash, snapshot) as operator_total_strategy_weight + FROM operator_strategy_weights +), +-- Calculate operator strategy proportion of tokens for each reward and snapshot +operator_strategy_proportions AS ( + SELECT *, + FLOOR((operator_strategy_weight / operator_total_strategy_weight) * 1000000000000000) / 1000000000000000 as operator_strategy_proportion + FROM operator_strategy_weights_sum +), +operator_strategy_tokens AS ( + SELECT *, + floor(operator_strategy_proportion * operator_tokens) as operator_strategy_tokens + FROM operator_strategy_proportions +) +SELECT * FROM operator_strategy_tokens +` + +type RfaeOperatorStrategyPayout struct { + RewardHash string + Snapshot time.Time + Token string + TokensPerDay float64 + Avs string + Strategy string + Multiplier string + RewardType string + Operator string + Shares string + OperatorTokens string + OperatorStrategyWeight string + OperatorTotalStrategyWeight string + OperatorStrategyProportion string + OperatorStrategyTokens string +} + +func (osr *RfaeOperatorStrategyPayout) TableName() string { + return "sot_rfae_operator_strategy_payout" +} + +func (sog *StakerOperatorsGenerator) GenerateAndInsert5RfaeOperatorStrategyPayout(cutoffDate string) error { + tableName := "sot_rfae_operator_strategy_payout" + allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) + + query, err := rewardsUtils.RenderQueryTemplate(_5_rfaeOperatorStrategyPayoutsQuery, map[string]string{ + "activeRewardsTable": allTableNames[rewardsUtils.Table_1_ActiveRewards], + "rfaeOperatorTable": allTableNames[rewardsUtils.Table_6_RfaeOperators], + }) + if err != nil { + sog.logger.Sugar().Errorw("Failed to render 5_rfaeOperatorStrategyPayoutsQuery query", "error", err) + return err + } + + err = rewardsUtils.GenerateAndInsertFromQuery(sog.db, tableName, query, nil, sog.logger) + if err != nil { + sog.logger.Sugar().Errorw("Failed to generate 5_rfaeOperatorStrategyPayoutsQuery", "error", err) + return err + } + return nil +} + +func (sog *StakerOperatorsGenerator) List5RfaeOperatorStrategyPayout() ([]*RfaeOperatorStrategyPayout, error) { + var rewards []*RfaeOperatorStrategyPayout + res := sog.db.Model(&RfaeOperatorStrategyPayout{}).Find(&rewards) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to list 5_rfaeOperatorStrategyPayoutsQuery", "error", res.Error) + return nil, res.Error + } + return rewards, nil +} diff --git a/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go b/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go new file mode 100644 index 00000000..a4e01cae --- /dev/null +++ b/pkg/rewards/stakerOperators/6_stakerOperatorStaging.go @@ -0,0 +1,140 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + "go.uber.org/zap" + "time" +) + +const _6_stakerOperatorsStaging = ` +create table {{.destTableName}} as +SELECT + staker as earner, + operator, + 'staker_reward' as reward_type, + avs, + token, + strategy, + multiplier, + shares, + staker_strategy_tokens as amount, + reward_hash, + snapshot +FROM sot_staker_strategy_payouts + +UNION ALL + +SELECT + operator as earner, + operator as operator, + 'operator_reward' as reward_type, + avs, + token, + strategy, + multiplier, + shares, + operator_strategy_tokens as amount, + reward_hash, + snapshot +FROM sot_operator_strategy_rewards + +UNION all + +SELECT + staker as earner, + '0x0000000000000000000000000000000000000000' as operator, + 'reward_for_all' as reward_type, + avs, + token, + strategy, + multiplier, + shares, + staker_strategy_tokens as amount, + reward_hash, + snapshot +FROM sot_rewards_for_all_strategy_payout + +UNION ALL + +SELECT + staker as earner, + operator, + 'rfae_staker' as reward_type, + avs, + token, + strategy, + multiplier, + shares, + staker_strategy_tokens as amount, + reward_hash, + snapshot +FROM sot_rfae_staker_strategy_payout + +UNION ALL + +SELECT + operator as earner, + operator as operator, + 'rfae_operator' as reward_type, + avs, + token, + strategy, + multiplier, + shares, + operator_strategy_tokens as amount, + reward_hash, + snapshot +FROM sot_rfae_operator_strategy_payout +` + +type StakerOperatorStaging struct { + Earner string + Operator string + RewardType string + Avs string + Token string + Strategy string + Multiplier string + Shares string + Amount string + RewardHash string + Snapshot time.Time +} + +func (sog *StakerOperatorsGenerator) GenerateAndInsert6StakerOperatorStaging(cutoffDate string) error { + allTableNames := rewardsUtils.GetGoldTableNames(cutoffDate) + destTableName := allTableNames[rewardsUtils.Sot_7_StakerOperatorTable] + + sog.logger.Sugar().Infow("Generating 6_stakerOperatorsStaging", + zap.String("destTableName", destTableName), + zap.String("cutoffDate", cutoffDate), + ) + + query, err := rewardsUtils.RenderQueryTemplate(_6_stakerOperatorsStaging, map[string]string{ + "destTableName": destTableName, + }) + if err != nil { + sog.logger.Sugar().Errorw("Failed to render 6_stakerOperatorsStaging query", "error", err) + return err + } + + res := sog.db.Exec(query) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to generate 6_stakerOperatorsStaging", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + } + + return nil +} + +func (sog *StakerOperatorsGenerator) List6StakerOperatorStaging() ([]*StakerOperatorStaging, error) { + var rewards []*StakerOperatorStaging + res := sog.db.Model(&StakerOperatorStaging{}).Find(&rewards) + if res.Error != nil { + sog.logger.Sugar().Errorw("Failed to list 6_stakerOperatorsStaging", "error", res.Error) + return nil, res.Error + } + return rewards, nil +} diff --git a/pkg/rewards/stakerOperators/stakerOperator.go b/pkg/rewards/stakerOperators/stakerOperator.go new file mode 100644 index 00000000..09c5fa7e --- /dev/null +++ b/pkg/rewards/stakerOperators/stakerOperator.go @@ -0,0 +1,84 @@ +package stakerOperators + +import ( + "github.com/Layr-Labs/sidecar/internal/config" + "go.uber.org/zap" + "gorm.io/gorm" +) + +type StakerOperatorsGenerator struct { + db *gorm.DB + logger *zap.Logger + globalConfig *config.Config +} + +func NewStakerOperatorGenerator( + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) *StakerOperatorsGenerator { + return &StakerOperatorsGenerator{ + db: grm, + logger: logger, + globalConfig: globalConfig, + } +} + +func (sog *StakerOperatorsGenerator) GenerateStakerOperatorsTable(cutoffDate string) error { + if !sog.globalConfig.Rewards.GenerateStakerOperatorsTable { + sog.logger.Sugar().Infow("Skipping generation of staker operators table, disabled via config", + zap.String("cutoffDate", cutoffDate), + ) + return nil + } + + sog.logger.Sugar().Infow("Generating staker operators table", zap.String("cutoffDate", cutoffDate)) + if err := sog.GenerateAndInsert1StakerStrategyPayouts(cutoffDate); err != nil { + sog.logger.Sugar().Errorf("Failed to generate and insert 1 staker strategy rewards", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + return err + } + + if err := sog.GenerateAndInsert2OperatorStrategyRewards(cutoffDate); err != nil { + sog.logger.Sugar().Errorf("Failed to generate and insert 2 staker strategy rewards", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + return err + } + + if err := sog.GenerateAndInsert3RewardsForAllStrategyPayout(cutoffDate); err != nil { + sog.logger.Sugar().Errorf("Failed to generate and insert 3 staker strategy rewards", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + return err + } + + if err := sog.GenerateAndInsert4RfaeStakerStrategyPayout(cutoffDate); err != nil { + sog.logger.Sugar().Errorf("Failed to generate and insert 4 staker strategy rewards", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + return err + } + + if err := sog.GenerateAndInsert5RfaeOperatorStrategyPayout(cutoffDate); err != nil { + sog.logger.Sugar().Errorf("Failed to generate and insert 5 staker strategy rewards", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + return err + } + + if err := sog.GenerateAndInsert6StakerOperatorStaging(cutoffDate); err != nil { + sog.logger.Sugar().Errorf("Failed to generate and insert 6 staker strategy rewards", + zap.String("cutoffDate", cutoffDate), + zap.Error(err), + ) + return err + } + return nil +} diff --git a/pkg/rewards/stakerShareSnapshots_test.go b/pkg/rewards/stakerShareSnapshots_test.go index 9b0b40f4..61e72508 100644 --- a/pkg/rewards/stakerShareSnapshots_test.go +++ b/pkg/rewards/stakerShareSnapshots_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -96,7 +97,8 @@ func Test_StakerShareSnapshots(t *testing.T) { } }) t.Run("Should generate staker share snapshots", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) t.Log("Generating staker share snapshots") err := rewards.GenerateAndInsertStakerShareSnapshots(snapshotDate) diff --git a/pkg/rewards/stakerShares_test.go b/pkg/rewards/stakerShares_test.go index 6f8f7b0d..3546d1e9 100644 --- a/pkg/rewards/stakerShares_test.go +++ b/pkg/rewards/stakerShares_test.go @@ -6,6 +6,7 @@ import ( "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" @@ -84,7 +85,8 @@ func Test_StakerShares(t *testing.T) { } }) t.Run("Should generate staker shares", func(t *testing.T) { - rewards, _ := NewRewardsCalculator(cfg, grm, nil, l) + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) t.Log("Generating staker shares") err := rewards.GenerateAndInsertStakerShares(snapshotDate) diff --git a/pkg/rewardsUtils/rewardsUtils.go b/pkg/rewardsUtils/rewardsUtils.go new file mode 100644 index 00000000..7b8a13fb --- /dev/null +++ b/pkg/rewardsUtils/rewardsUtils.go @@ -0,0 +1,96 @@ +package rewardsUtils + +import ( + "bytes" + "fmt" + "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" + + Sot_6_StakerOperatorStaging = "sot_6_staker_operator_staging" + Sot_7_StakerOperatorTable = "staker_operator" +) + +var goldTableBaseNames = map[string]string{ + Table_1_ActiveRewards: Table_1_ActiveRewards, + Table_2_StakerRewardAmounts: Table_2_StakerRewardAmounts, + Table_3_OperatorRewardAmounts: Table_3_OperatorRewardAmounts, + Table_4_RewardsForAll: Table_4_RewardsForAll, + Table_5_RfaeStakers: Table_5_RfaeStakers, + Table_6_RfaeOperators: Table_6_RfaeOperators, + Table_7_GoldStaging: Table_7_GoldStaging, + Table_8_GoldTable: Table_8_GoldTable, + + Sot_6_StakerOperatorStaging: Sot_6_StakerOperatorStaging, + Sot_7_StakerOperatorTable: Sot_7_StakerOperatorTable, +} + +func GetGoldTableNames(snapshotDate string) map[string]string { + tableNames := make(map[string]string) + for key, baseName := range goldTableBaseNames { + tableNames[key] = FormatTableName(baseName, snapshotDate) + } + return tableNames +} + +func RenderQueryTemplate(query string, variables map[string]string) (string, error) { + queryTmpl := template.Must(template.New("").Parse(query)) + + var dest bytes.Buffer + if err := queryTmpl.Execute(&dest, variables); err != nil { + return "", err + } + return dest.String(), nil +} + +func FormatTableName(tableName string, snapshotDate string) string { + return fmt.Sprintf("%s_%s", tableName, utils.SnakeCase(snapshotDate)) +} + +func GenerateAndInsertFromQuery( + grm *gorm.DB, + tableName string, + query string, + variables map[string]interface{}, + l *zap.Logger, +) error { + tmpTableName := fmt.Sprintf("%s_tmp", tableName) + + queryWithInsert := fmt.Sprintf("CREATE TABLE %s AS %s", tmpTableName, query) + + _, err := helpers.WrapTxAndCommit(func(tx *gorm.DB) (interface{}, error) { + queries := []string{ + queryWithInsert, + fmt.Sprintf(`drop table if exists %s`, tableName), + fmt.Sprintf(`alter table %s rename to %s`, tmpTableName, tableName), + } + for i, query := range queries { + var res *gorm.DB + if i == 0 && variables != nil { + res = tx.Exec(query, variables) + } else { + res = tx.Exec(query) + } + if res.Error != nil { + l.Sugar().Errorw("Failed to execute query", "query", query, "error", res.Error) + return nil, res.Error + } + } + return nil, nil + }, grm, nil) + + return err +}