From 39711ba64ee46045554a84bf9379e97c7c0dc95a Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Mon, 6 Jan 2025 14:21:35 -0500 Subject: [PATCH 1/4] feat: dynamic default split --- internal/config/config.go | 36 +-- .../defaultOperatorSplits.go | 260 ++++++++++++++++++ .../defaultOperatorSplits_test.go | 139 ++++++++++ pkg/eigenState/eigenState.go | 5 + .../202501061422_defaultOperatorSplits/up.go | 33 +++ .../up.go | 30 ++ pkg/postgres/migrations/migrator.go | 6 +- pkg/rewards/2_goldStakerRewardAmounts.go | 11 +- pkg/rewards/5_goldRfaeStakers.go | 11 +- pkg/rewards/8_goldOperatorODRewardAmounts.go | 17 +- pkg/rewards/9_goldStakerODRewardAmounts.go | 16 +- pkg/rewards/defaultOperatorSplitSnapshots.go | 89 ++++++ .../defaultOperatorSplitSnapshots_test.go | 179 ++++++++++++ pkg/rewards/rewards.go | 15 +- pkg/rewards/rewardsV2_test.go | 4 +- pkg/rewards/rewards_test.go | 4 +- pkg/rewards/tables.go | 5 + 17 files changed, 824 insertions(+), 36 deletions(-) create mode 100644 pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits.go create mode 100644 pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits_test.go create mode 100644 pkg/postgres/migrations/202501061422_defaultOperatorSplits/up.go create mode 100644 pkg/postgres/migrations/202501071401_defaultOperatorSplitSnapshots/up.go create mode 100644 pkg/rewards/defaultOperatorSplitSnapshots.go create mode 100644 pkg/rewards/defaultOperatorSplitSnapshots_test.go diff --git a/internal/config/config.go b/internal/config/config.go index 8e74c1b5..81af0228 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,10 +28,11 @@ const ( Chain_Holesky Chain = "holesky" Chain_Preprod Chain = "preprod" - Fork_Nile ForkName = "nile" - Fork_Amazon ForkName = "amazon" - Fork_Panama ForkName = "panama" - Fork_Arno ForkName = "arno" + Fork_Nile ForkName = "nile" + Fork_Amazon ForkName = "amazon" + Fork_Panama ForkName = "panama" + Fork_Arno ForkName = "arno" + Fork_Trinity ForkName = "trinity" ENV_PREFIX = "SIDECAR" ) @@ -253,24 +254,27 @@ func (c *Config) GetForkDates() (ForkMap, error) { switch c.Chain { case Chain_Preprod: return ForkMap{ - 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", + 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", + Fork_Trinity: "2025-01-13", }, 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", + 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", + Fork_Trinity: "2025-01-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", + 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-21", + Fork_Trinity: "2025-01-21", }, nil } return nil, errors.New("unsupported chain") diff --git a/pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits.go b/pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits.go new file mode 100644 index 00000000..e50a462a --- /dev/null +++ b/pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits.go @@ -0,0 +1,260 @@ +package defaultOperatorSplits + +import ( + "encoding/json" + "fmt" + "slices" + "sort" + "strings" + + "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" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type DefaultOperatorSplit struct { + OldDefaultOperatorSplitBips uint64 + NewDefaultOperatorSplitBips uint64 + BlockNumber uint64 + TransactionHash string + LogIndex uint64 +} + +type DefaultOperatorSplitModel struct { + base.BaseEigenState + StateTransitions types.StateTransitions[[]*DefaultOperatorSplit] + 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]*DefaultOperatorSplit +} + +func NewDefaultOperatorSplitModel( + esm *stateManager.EigenStateManager, + grm *gorm.DB, + logger *zap.Logger, + globalConfig *config.Config, +) (*DefaultOperatorSplitModel, error) { + model := &DefaultOperatorSplitModel{ + BaseEigenState: base.BaseEigenState{ + Logger: logger, + }, + DB: grm, + logger: logger, + globalConfig: globalConfig, + stateAccumulator: make(map[uint64]map[types.SlotID]*DefaultOperatorSplit), + } + + esm.RegisterState(model, 10) + return model, nil +} + +func (oas *DefaultOperatorSplitModel) GetModelName() string { + return "DefaultOperatorSplitModel" +} + +type defaultOperatorSplitOutputData struct { + OldDefaultOperatorSplitBips uint64 `json:"oldDefaultOperatorSplitBips"` + NewDefaultOperatorSplitBips uint64 `json:"newDefaultOperatorSplitBips"` +} + +func parseDefaultOperatorSplitOutputData(outputDataStr string) (*defaultOperatorSplitOutputData, error) { + outputData := &defaultOperatorSplitOutputData{} + decoder := json.NewDecoder(strings.NewReader(outputDataStr)) + decoder.UseNumber() + + err := decoder.Decode(&outputData) + if err != nil { + return nil, err + } + + return outputData, err +} + +func (oas *DefaultOperatorSplitModel) handleDefaultOperatorSplitBipsSetEvent(log *storage.TransactionLog) (*DefaultOperatorSplit, error) { + outputData, err := parseDefaultOperatorSplitOutputData(log.OutputData) + if err != nil { + return nil, err + } + + split := &DefaultOperatorSplit{ + OldDefaultOperatorSplitBips: outputData.OldDefaultOperatorSplitBips, + NewDefaultOperatorSplitBips: outputData.NewDefaultOperatorSplitBips, + BlockNumber: log.BlockNumber, + TransactionHash: log.TransactionHash, + LogIndex: log.LogIndex, + } + + return split, nil +} + +func (oas *DefaultOperatorSplitModel) GetStateTransitions() (types.StateTransitions[*DefaultOperatorSplit], []uint64) { + stateChanges := make(types.StateTransitions[*DefaultOperatorSplit]) + + stateChanges[0] = func(log *storage.TransactionLog) (*DefaultOperatorSplit, error) { + defaultOperatorSplit, err := oas.handleDefaultOperatorSplitBipsSetEvent(log) + if err != nil { + return nil, err + } + + slotId := base.NewSlotID(defaultOperatorSplit.TransactionHash, defaultOperatorSplit.LogIndex) + + _, ok := oas.stateAccumulator[log.BlockNumber][slotId] + if ok { + err := fmt.Errorf("Duplicate default operator split submitted for slot %s at block %d", slotId, log.BlockNumber) + oas.logger.Sugar().Errorw("Duplicate default operator split submitted", zap.Error(err)) + return nil, err + } + + oas.stateAccumulator[log.BlockNumber][slotId] = defaultOperatorSplit + + return defaultOperatorSplit, 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 *DefaultOperatorSplitModel) getContractAddressesForEnvironment() map[string][]string { + contracts := oas.globalConfig.GetContractsMapForChain() + return map[string][]string{ + contracts.RewardsCoordinator: { + "DefaultOperatorSplitBipsSet", + }, + } +} + +func (oas *DefaultOperatorSplitModel) IsInterestingLog(log *storage.TransactionLog) bool { + addresses := oas.getContractAddressesForEnvironment() + return oas.BaseEigenState.IsInterestingLog(addresses, log) +} + +func (oas *DefaultOperatorSplitModel) SetupStateForBlock(blockNumber uint64) error { + oas.stateAccumulator[blockNumber] = make(map[types.SlotID]*DefaultOperatorSplit) + return nil +} + +func (oas *DefaultOperatorSplitModel) CleanupProcessedStateForBlock(blockNumber uint64) error { + delete(oas.stateAccumulator, blockNumber) + return nil +} + +func (oas *DefaultOperatorSplitModel) 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 *DefaultOperatorSplitModel) prepareState(blockNumber uint64) ([]*DefaultOperatorSplit, error) { + accumulatedState, ok := oas.stateAccumulator[blockNumber] + if !ok { + err := fmt.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([]*DefaultOperatorSplit, 0) + for _, split := range accumulatedState { + recordsToInsert = append(recordsToInsert, split) + } + return recordsToInsert, nil +} + +// CommitFinalState commits the final state for the given block number. +func (oas *DefaultOperatorSplitModel) 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(&DefaultOperatorSplit{}).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 *DefaultOperatorSplitModel) 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 *DefaultOperatorSplitModel) sortValuesForMerkleTree(splits []*DefaultOperatorSplit) []*base.MerkleTreeInput { + inputs := make([]*base.MerkleTreeInput, 0) + for _, split := range splits { + slotID := base.NewSlotID(split.TransactionHash, split.LogIndex) + value := fmt.Sprintf("%016x_%016x", split.OldDefaultOperatorSplitBips, split.NewDefaultOperatorSplitBips) + 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 *DefaultOperatorSplitModel) DeleteState(startBlockNumber uint64, endBlockNumber uint64) error { + return oas.BaseEigenState.DeleteState("default_operator_splits", startBlockNumber, endBlockNumber, oas.DB) +} diff --git a/pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits_test.go b/pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits_test.go new file mode 100644 index 00000000..4594e349 --- /dev/null +++ b/pkg/eigenState/defaultOperatorSplits/defaultOperatorSplits_test.go @@ -0,0 +1,139 @@ +package defaultOperatorSplits + +import ( + "fmt" + "math/big" + "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 *DefaultOperatorSplitModel) { + queries := []string{ + `truncate table default_operator_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 *DefaultOperatorSplitModel, 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_DefaultOperatorSplit(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 := NewDefaultOperatorSplitModel(esm, grm, l, cfg) + + t.Run("Handle a default operator 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: `[]`, + EventName: "DefaultOperatorSplitBipsSet", + LogIndex: big.NewInt(12).Uint64(), + OutputData: `{"oldDefaultOperatorSplitBips": 1000, "newDefaultOperatorSplitBips": 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.(*DefaultOperatorSplit) + + assert.Equal(t, uint64(1000), split.OldDefaultOperatorSplitBips) + assert.Equal(t, uint64(2000), split.NewDefaultOperatorSplitBips) + + err = model.CommitFinalState(blockNumber) + assert.Nil(t, err) + + splits := make([]*DefaultOperatorSplit, 0) + query := `select * from default_operator_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/eigenState.go b/pkg/eigenState/eigenState.go index ecdac8c9..0d441810 100644 --- a/pkg/eigenState/eigenState.go +++ b/pkg/eigenState/eigenState.go @@ -3,6 +3,7 @@ package eigenState import ( "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/eigenState/avsOperators" + "github.com/Layr-Labs/sidecar/pkg/eigenState/defaultOperatorSplits" "github.com/Layr-Labs/sidecar/pkg/eigenState/disabledDistributionRoots" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorAVSSplits" "github.com/Layr-Labs/sidecar/pkg/eigenState/operatorDirectedRewardSubmissions" @@ -63,5 +64,9 @@ func LoadEigenStateModels( l.Sugar().Errorw("Failed to create OperatorPISplitModel", zap.Error(err)) return err } + if _, err := defaultOperatorSplits.NewDefaultOperatorSplitModel(sm, grm, l, cfg); err != nil { + l.Sugar().Errorw("Failed to create DefaultOperatorSplitModel", zap.Error(err)) + return err + } return nil } diff --git a/pkg/postgres/migrations/202501061422_defaultOperatorSplits/up.go b/pkg/postgres/migrations/202501061422_defaultOperatorSplits/up.go new file mode 100644 index 00000000..d24f903b --- /dev/null +++ b/pkg/postgres/migrations/202501061422_defaultOperatorSplits/up.go @@ -0,0 +1,33 @@ +package _202501061422_defaultOperatorSplits + +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 default_operator_splits ( + old_default_operator_split_bips integer not null, + new_default_operator_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 default_operator_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 "202501061422_defaultOperatorSplits" +} diff --git a/pkg/postgres/migrations/202501071401_defaultOperatorSplitSnapshots/up.go b/pkg/postgres/migrations/202501071401_defaultOperatorSplitSnapshots/up.go new file mode 100644 index 00000000..e9c9ab74 --- /dev/null +++ b/pkg/postgres/migrations/202501071401_defaultOperatorSplitSnapshots/up.go @@ -0,0 +1,30 @@ +package _202501071401_defaultOperatorSplitSnapshots + +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 default_operator_split_snapshots ( + 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 "202501071401_defaultOperatorSplitSnapshots" +} diff --git a/pkg/postgres/migrations/migrator.go b/pkg/postgres/migrations/migrator.go index 6de5177d..f3199203 100644 --- a/pkg/postgres/migrations/migrator.go +++ b/pkg/postgres/migrations/migrator.go @@ -3,7 +3,6 @@ package migrations import ( "database/sql" "fmt" - _202501061613_reindexTestnetForStaterootChange "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061613_reindexTestnetForStaterootChange" "time" "github.com/Layr-Labs/sidecar/internal/config" @@ -47,6 +46,9 @@ import ( _202412061626_operatorRestakedStrategiesConstraint "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412061626_operatorRestakedStrategiesConstraint" _202412091100_fixOperatorPiSplitsFields "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202412091100_fixOperatorPiSplitsFields" _202501061029_addDescription "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061029_addDescription" + _202501061422_defaultOperatorSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061422_defaultOperatorSplits" + _202501061613_reindexTestnetForStaterootChange "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501061613_reindexTestnetForStaterootChange" + _202501071401_defaultOperatorSplitSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202501071401_defaultOperatorSplitSnapshots" "go.uber.org/zap" "gorm.io/gorm" @@ -131,6 +133,8 @@ func (m *Migrator) MigrateAll() error { &_202412091100_fixOperatorPiSplitsFields.Migration{}, &_202501061029_addDescription.Migration{}, &_202501061613_reindexTestnetForStaterootChange.Migration{}, + &_202501061422_defaultOperatorSplits.Migration{}, + &_202501071401_defaultOperatorSplitSnapshots.Migration{}, } for _, migration := range migrations { diff --git a/pkg/rewards/2_goldStakerRewardAmounts.go b/pkg/rewards/2_goldStakerRewardAmounts.go index 52cbcb76..12173cd5 100644 --- a/pkg/rewards/2_goldStakerRewardAmounts.go +++ b/pkg/rewards/2_goldStakerRewardAmounts.go @@ -120,8 +120,10 @@ token_breakdowns AS ( (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 + WHEN sott.snapshot < @trinityHardforkDate AND sott.reward_submission_date < @trinityHardforkDate THEN floor(sott.total_staker_operator_payout * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) + ELSE + floor(sott.total_staker_operator_payout * COALESCE(oas.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) END as operator_tokens, CASE WHEN sott.snapshot < @amazonHardforkDate AND sott.reward_submission_date < @amazonHardforkDate THEN @@ -130,12 +132,15 @@ token_breakdowns AS ( 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 + WHEN sott.snapshot < @trinityHardforkDate AND sott.reward_submission_date < @trinityHardforkDate THEN sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) + ELSE + sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(oas.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) END as staker_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 + LEFT JOIN default_operator_split_snapshots dos ON (sott.snapshot = dos.snapshot) ) SELECT * from token_breakdowns ORDER BY reward_hash, snapshot, staker, operator @@ -151,6 +156,7 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate zap.String("amazonHardforkDate", forks[config.Fork_Amazon]), zap.String("nileHardforkDate", forks[config.Fork_Nile]), zap.String("arnoHardforkDate", forks[config.Fork_Arno]), + zap.String("trinityHardforkDate", forks[config.Fork_Trinity]), ) query, err := rewardsUtils.RenderQueryTemplate(_2_goldStakerRewardAmountsQuery, map[string]interface{}{ @@ -166,6 +172,7 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate sql.Named("amazonHardforkDate", forks[config.Fork_Amazon]), sql.Named("nileHardforkDate", forks[config.Fork_Nile]), sql.Named("arnoHardforkDate", forks[config.Fork_Arno]), + sql.Named("trinityHardforkDate", forks[config.Fork_Trinity]), ) if res.Error != nil { rc.logger.Sugar().Errorw("Failed to create gold_staker_reward_amounts", "error", res.Error) diff --git a/pkg/rewards/5_goldRfaeStakers.go b/pkg/rewards/5_goldRfaeStakers.go index af605f5f..64098db2 100644 --- a/pkg/rewards/5_goldRfaeStakers.go +++ b/pkg/rewards/5_goldRfaeStakers.go @@ -115,18 +115,23 @@ token_breakdowns AS ( CASE WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN floor(sott.total_staker_operator_payout * 0.10) - ELSE + WHEN sott.snapshot < @trinityHardforkDate AND sott.reward_submission_date < @trinityHardforkDate THEN floor(sott.total_staker_operator_payout * COALESCE(ops.split, 1000) / CAST(10000 AS DECIMAL)) + ELSE + floor(sott.total_staker_operator_payout * COALESCE(ops.split, dos.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 + WHEN sott.snapshot < @trinityHardforkDate AND sott.reward_submission_date < @trinityHardforkDate THEN sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(ops.split, 1000) / CAST(10000 AS DECIMAL)) + ELSE + sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(ops.split, dos.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 + LEFT JOIN default_operator_split_snapshots dos ON (sott.snapshot = dos.snapshot) ) SELECT * from token_breakdowns ORDER BY reward_hash, snapshot, staker, operator @@ -140,6 +145,7 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string, zap.String("cutoffDate", snapshotDate), zap.String("destTableName", destTableName), zap.String("arnoHardforkDate", forks[config.Fork_Arno]), + zap.String("trinityHardforkDate", forks[config.Fork_Trinity]), ) query, err := rewardsUtils.RenderQueryTemplate(_5_goldRfaeStakersQuery, map[string]interface{}{ @@ -155,6 +161,7 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string, sql.Named("panamaForkDate", forks[config.Fork_Panama]), sql.Named("network", rc.globalConfig.Chain.String()), sql.Named("arnoHardforkDate", forks[config.Fork_Arno]), + sql.Named("trinityHardforkDate", forks[config.Fork_Trinity]), ) if res.Error != nil { rc.logger.Sugar().Errorw("Failed to generate gold_rfae_stakers", "error", res.Error) diff --git a/pkg/rewards/8_goldOperatorODRewardAmounts.go b/pkg/rewards/8_goldOperatorODRewardAmounts.go index f3ec3c7c..b0824dcf 100644 --- a/pkg/rewards/8_goldOperatorODRewardAmounts.go +++ b/pkg/rewards/8_goldOperatorODRewardAmounts.go @@ -1,6 +1,9 @@ package rewards import ( + "database/sql" + + "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "go.uber.org/zap" ) @@ -51,20 +54,25 @@ distinct_operators AS ( 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 + CASE + WHEN dop.snapshot < @trinityHardforkDate AND dop.reward_submission_date < @trinityHardforkDate THEN + FLOOR(dop.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) + ELSE + FLOOR(dop.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) + END 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 + LEFT JOIN default_operator_split_snapshots dos ON (dop.snapshot = dos.snapshot) ) -- Step 4: Output the final table with operator splits SELECT * FROM operator_splits ` -func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotDate string) error { +func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotDate string, forks config.ForkMap) error { rewardsV2Enabled, err := rc.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) if err != nil { rc.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) @@ -80,6 +88,7 @@ func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotD rc.logger.Sugar().Infow("Generating Operator OD reward amounts", zap.String("cutoffDate", snapshotDate), zap.String("destTableName", destTableName), + zap.String("trinityHardforkDate", forks[config.Fork_Trinity]), ) query, err := rewardsUtils.RenderQueryTemplate(_8_goldOperatorODRewardAmountsQuery, map[string]interface{}{ @@ -91,7 +100,7 @@ func (rc *RewardsCalculator) GenerateGold8OperatorODRewardAmountsTable(snapshotD return err } - res := rc.grm.Exec(query) + res := rc.grm.Exec(query, sql.Named("trinityHardforkDate", forks[config.Fork_Trinity])) if res.Error != nil { rc.logger.Sugar().Errorw("Failed to create gold_operator_od_reward_amounts", "error", res.Error) return res.Error diff --git a/pkg/rewards/9_goldStakerODRewardAmounts.go b/pkg/rewards/9_goldStakerODRewardAmounts.go index f2f4f94e..1d736162 100644 --- a/pkg/rewards/9_goldStakerODRewardAmounts.go +++ b/pkg/rewards/9_goldStakerODRewardAmounts.go @@ -1,6 +1,9 @@ package rewards import ( + "database/sql" + + "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" "go.uber.org/zap" ) @@ -32,12 +35,18 @@ WITH reward_snapshot_operators AS ( 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 + CASE + WHEN rso.snapshot < @trinityHardforkDate AND rso.reward_submission_date < @trinityHardforkDate THEN + rso.tokens_per_registered_snapshot_decimal - FLOOR(rso.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) + ELSE + rso.tokens_per_registered_snapshot_decimal - FLOOR(rso.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, dos.split, 1000) / CAST(10000 AS DECIMAL)) + END AS staker_split FROM reward_snapshot_operators rso LEFT JOIN operator_avs_split_snapshots oas ON rso.operator = oas.operator AND rso.avs = oas.avs AND rso.snapshot = oas.snapshot + LEFT JOIN default_operator_split_snapshots dos ON (rso.snapshot = dos.snapshot) ), -- Get the stakers that were delegated to the operator for the snapshot staker_delegated_operators AS ( @@ -113,7 +122,7 @@ staker_reward_amounts AS ( SELECT * FROM staker_reward_amounts ` -func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDate string) error { +func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDate string, forks config.ForkMap) error { rewardsV2Enabled, err := rc.globalConfig.IsRewardsV2EnabledForCutoffDate(snapshotDate) if err != nil { rc.logger.Sugar().Errorw("Failed to check if rewards v2 is enabled", "error", err) @@ -130,6 +139,7 @@ func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDat rc.logger.Sugar().Infow("Generating Staker OD reward amounts", zap.String("cutoffDate", snapshotDate), zap.String("destTableName", destTableName), + zap.String("trinityHardforkDate", forks[config.Fork_Trinity]), ) query, err := rewardsUtils.RenderQueryTemplate(_9_goldStakerODRewardAmountsQuery, map[string]interface{}{ @@ -141,7 +151,7 @@ func (rc *RewardsCalculator) GenerateGold9StakerODRewardAmountsTable(snapshotDat return err } - res := rc.grm.Exec(query) + res := rc.grm.Exec(query, sql.Named("trinityHardforkDate", forks[config.Fork_Trinity])) if res.Error != nil { rc.logger.Sugar().Errorw("Failed to create gold_staker_od_reward_amounts", "error", res.Error) return res.Error diff --git a/pkg/rewards/defaultOperatorSplitSnapshots.go b/pkg/rewards/defaultOperatorSplitSnapshots.go new file mode 100644 index 00000000..05b533c7 --- /dev/null +++ b/pkg/rewards/defaultOperatorSplitSnapshots.go @@ -0,0 +1,89 @@ +package rewards + +import "github.com/Layr-Labs/sidecar/pkg/rewardsUtils" + +const defaultOperatorSplitSnapshotQuery = ` +WITH default_operator_splits_with_block_info as ( + select + dos.new_default_operator_split_bips as split, + dos.block_number, + dos.log_index, + b.block_time::timestamp(6) as block_time + from default_operator_splits as dos + join blocks as b on (b.number = dos.block_number) + where b.block_time < TIMESTAMP '{{.cutoffDate}}' +), +-- Rank the records for each combination of (block date) by block time and log index +ranked_default_operator_split_records as ( + SELECT + *, + ROW_NUMBER() OVER (PARTITION BY cast(block_time AS DATE) ORDER BY block_time DESC, log_index DESC) AS rn + FROM default_operator_splits_with_block_info +), +-- Get the latest record for each day & round up to the snapshot day +snapshotted_records as ( + SELECT + split, + block_time, + date_trunc('day', block_time) + INTERVAL '1' day AS snapshot_time + from ranked_default_operator_split_records + where rn = 1 +), +-- Get the range for each operator, avs pairing +default_operator_split_windows as ( + SELECT + 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 (ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}') + ELSE LEAD(snapshot_time) OVER (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 default_operator_split_windows + WHERE start_time < end_time +), +-- Generate a snapshot for each day in the range +final_results as ( + SELECT + 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) GenerateAndInsertDefaultOperatorSplitSnapshots(snapshotDate string) error { + tableName := "default_operator_split_snapshots" + + query, err := rewardsUtils.RenderQueryTemplate(defaultOperatorSplitSnapshotQuery, 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 default_operator_split_snapshots", "error", err) + return err + } + return nil +} + +func (r *RewardsCalculator) ListDefaultOperatorSplitSnapshots() ([]*DefaultOperatorSplitSnapshots, error) { + var snapshots []*DefaultOperatorSplitSnapshots + res := r.grm.Model(&DefaultOperatorSplitSnapshots{}).Find(&snapshots) + if res.Error != nil { + r.logger.Sugar().Errorw("Failed to list default operator split snapshots", "error", res.Error) + return nil, res.Error + } + return snapshots, nil +} diff --git a/pkg/rewards/defaultOperatorSplitSnapshots_test.go b/pkg/rewards/defaultOperatorSplitSnapshots_test.go new file mode 100644 index 00000000..c8dc7508 --- /dev/null +++ b/pkg/rewards/defaultOperatorSplitSnapshots_test.go @@ -0,0 +1,179 @@ +package rewards + +import ( + "fmt" + "testing" + + "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" +) + +func setupDefaultOperatorSplitWindows() ( + 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 teardownDefaultOperatorSplitWindows(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 hydrateDefaultOperatorSplits(grm *gorm.DB, l *zap.Logger) error { + query := ` + INSERT INTO default_operator_splits (old_default_operator_split_bips, new_default_operator_split_bips, block_number, transaction_hash, log_index) + VALUES (1000, 500, '1477020', '0xccc83cdfa365bacff5e4099b9931bccaec1c0b0cf37cd324c92c27b5cb5387d1', 545) + ` + + res := grm.Exec(query) + if res.Error != nil { + l.Sugar().Errorw("Failed to execute sql", "error", zap.Error(res.Error)) + return res.Error + } + return nil +} + +func Test_DefaultOperatorSplitSnapshots(t *testing.T) { + if !rewardsTestsEnabled() { + t.Skipf("Skipping %s", t.Name()) + return + } + + // projectRoot := getProjectRootPath() + dbFileName, cfg, grm, l, err := setupDefaultOperatorSplitWindows() + 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 default operator splits") + err = hydrateDefaultOperatorSplits(grm, l) + if err != nil { + t.Fatal(err) + } + + query := `select count(*) from default_operator_splits` + var count int + res := grm.Raw(query).Scan(&count) + + assert.Nil(t, res.Error) + + assert.Equal(t, 1, count) + }) + + t.Run("Should calculate correct default operator split windows", func(t *testing.T) { + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) + + t.Log("Generating snapshots") + err := rewards.GenerateAndInsertDefaultOperatorSplitSnapshots(snapshotDate) + assert.Nil(t, err) + + windows, err := rewards.ListDefaultOperatorSplitSnapshots() + assert.Nil(t, err) + + t.Logf("Found %d windows", len(windows)) + + assert.Equal(t, 218, len(windows)) + }) + t.Cleanup(func() { + teardownDefaultOperatorSplitWindows(dbFileName, cfg, grm, l) + }) +} + +func Test_NoDefaultOperatorSplitSnapshots(t *testing.T) { + if !rewardsTestsEnabled() { + t.Skipf("Skipping %s", t.Name()) + return + } + + // projectRoot := getProjectRootPath() + dbFileName, cfg, grm, l, err := setupDefaultOperatorSplitWindows() + 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) + + // No hydration of default operator splits + query := `select count(*) from default_operator_splits` + var count int + res := grm.Raw(query).Scan(&count) + + assert.Nil(t, res.Error) + + assert.Equal(t, 0, count) + }) + + t.Run("Should calculate correct default operator split windows", func(t *testing.T) { + sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg) + rewards, _ := NewRewardsCalculator(cfg, grm, nil, sog, l) + + t.Log("Generating snapshots") + err := rewards.GenerateAndInsertDefaultOperatorSplitSnapshots(snapshotDate) + assert.Nil(t, err) + + windows, err := rewards.ListDefaultOperatorSplitSnapshots() + assert.Nil(t, err) + + t.Logf("Found %d windows", len(windows)) + + assert.Equal(t, 0, len(windows)) + }) + t.Cleanup(func() { + teardownDefaultOperatorSplitWindows(dbFileName, cfg, grm, l) + }) +} diff --git a/pkg/rewards/rewards.go b/pkg/rewards/rewards.go index 70fda127..21b3586d 100644 --- a/pkg/rewards/rewards.go +++ b/pkg/rewards/rewards.go @@ -8,6 +8,9 @@ import ( "sync/atomic" + "slices" + "strings" + "github.com/Layr-Labs/eigenlayer-rewards-proofs/pkg/distribution" "github.com/Layr-Labs/sidecar/internal/config" "github.com/Layr-Labs/sidecar/pkg/rewards/stakerOperators" @@ -18,8 +21,6 @@ import ( "go.uber.org/zap" "gorm.io/gorm" "gorm.io/gorm/clause" - "slices" - "strings" ) type RewardsCalculator struct { @@ -625,6 +626,12 @@ func (rc *RewardsCalculator) generateSnapshotData(snapshotDate string) error { } rc.logger.Sugar().Debugw("Generated operator pi snapshots") + if err = rc.GenerateAndInsertDefaultOperatorSplitSnapshots(snapshotDate); err != nil { + rc.logger.Sugar().Errorw("Failed to generate default operator split snapshots", "error", err) + return err + } + rc.logger.Sugar().Debugw("Generated default operator split snapshots") + return nil } @@ -668,12 +675,12 @@ func (rc *RewardsCalculator) generateGoldTables(snapshotDate string) error { return err } - if err := rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate); err != nil { + if err := rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate, forks); err != nil { rc.logger.Sugar().Errorw("Failed to generate operator od reward amounts", "error", err) return err } - if err := rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate); err != nil { + if err := rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate, forks); err != nil { rc.logger.Sugar().Errorw("Failed to generate staker od reward amounts", "error", err) return err } diff --git a/pkg/rewards/rewardsV2_test.go b/pkg/rewards/rewardsV2_test.go index 2bd408ff..84a70e80 100644 --- a/pkg/rewards/rewardsV2_test.go +++ b/pkg/rewards/rewardsV2_test.go @@ -163,7 +163,7 @@ func Test_RewardsV2(t *testing.T) { testStart = time.Now() fmt.Printf("Running gold_8_operator_od_reward_amounts\n") - err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate) + err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate, forks) assert.Nil(t, err) if rewardsV2Enabled { rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts]) @@ -173,7 +173,7 @@ func Test_RewardsV2(t *testing.T) { testStart = time.Now() fmt.Printf("Running gold_9_staker_od_reward_amounts\n") - err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate) + err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate, forks) assert.Nil(t, err) if rewardsV2Enabled { rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_9_StakerODRewardAmounts]) diff --git a/pkg/rewards/rewards_test.go b/pkg/rewards/rewards_test.go index d376e2b4..b318e2da 100644 --- a/pkg/rewards/rewards_test.go +++ b/pkg/rewards/rewards_test.go @@ -308,7 +308,7 @@ func Test_Rewards(t *testing.T) { testStart = time.Now() fmt.Printf("Running gold_8_operator_od_reward_amounts\n") - err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate) + err = rc.GenerateGold8OperatorODRewardAmountsTable(snapshotDate, forks) assert.Nil(t, err) if rewardsV2Enabled { rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_8_OperatorODRewardAmounts]) @@ -318,7 +318,7 @@ func Test_Rewards(t *testing.T) { testStart = time.Now() fmt.Printf("Running gold_9_staker_od_reward_amounts\n") - err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate) + err = rc.GenerateGold9StakerODRewardAmountsTable(snapshotDate, forks) assert.Nil(t, err) if rewardsV2Enabled { rows, err = getRowCountForTable(grm, goldTableNames[rewardsUtils.Table_9_StakerODRewardAmounts]) diff --git a/pkg/rewards/tables.go b/pkg/rewards/tables.go index 624ce4f9..f2148633 100644 --- a/pkg/rewards/tables.go +++ b/pkg/rewards/tables.go @@ -77,6 +77,11 @@ type OperatorShares struct { BlockDate string } +type DefaultOperatorSplitSnapshots struct { + Split uint64 + Snapshot time.Time +} + type OperatorAVSSplitSnapshots struct { Operator string Avs string From 6241531953000c806e55e420a4a63e6d2bcd5782 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 8 Jan 2025 15:48:52 -0500 Subject: [PATCH 2/4] fix: split pct --- pkg/rewards/8_goldOperatorODRewardAmounts.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/rewards/8_goldOperatorODRewardAmounts.go b/pkg/rewards/8_goldOperatorODRewardAmounts.go index b0824dcf..50531efa 100644 --- a/pkg/rewards/8_goldOperatorODRewardAmounts.go +++ b/pkg/rewards/8_goldOperatorODRewardAmounts.go @@ -54,6 +54,12 @@ distinct_operators AS ( operator_splits AS ( SELECT dop.*, + CASE, + WHEN dop.snapshot < @trinityHardforkDate AND dop.reward_submission_date < @trinityHardforkDate THEN + COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL) + ELSE + COALESCE(oas.split, dos.split, 1000) / CAST(10000 AS DECIMAL) + END AS split_pct, CASE WHEN dop.snapshot < @trinityHardforkDate AND dop.reward_submission_date < @trinityHardforkDate THEN FLOOR(dop.tokens_per_registered_snapshot_decimal * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL)) From 475489bdde0b00da5921643247c08c551a3ee480 Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 8 Jan 2025 15:56:20 -0500 Subject: [PATCH 3/4] fix: missing comma --- pkg/rewards/8_goldOperatorODRewardAmounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rewards/8_goldOperatorODRewardAmounts.go b/pkg/rewards/8_goldOperatorODRewardAmounts.go index 50531efa..feab904b 100644 --- a/pkg/rewards/8_goldOperatorODRewardAmounts.go +++ b/pkg/rewards/8_goldOperatorODRewardAmounts.go @@ -54,7 +54,7 @@ distinct_operators AS ( operator_splits AS ( SELECT dop.*, - CASE, + CASE WHEN dop.snapshot < @trinityHardforkDate AND dop.reward_submission_date < @trinityHardforkDate THEN COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL) ELSE From b653852a2a4b1578d51e4e081cbfe06bb5347c1f Mon Sep 17 00:00:00 2001 From: 0xrajath Date: Wed, 8 Jan 2025 16:06:58 -0500 Subject: [PATCH 4/4] chore: updated trinity preprod and testnet dates --- internal/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 81af0228..b0b19944 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -258,7 +258,7 @@ func (c *Config) GetForkDates() (ForkMap, error) { 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", - Fork_Trinity: "2025-01-13", + Fork_Trinity: "2025-01-09", }, nil case Chain_Holesky: return ForkMap{ @@ -266,7 +266,7 @@ func (c *Config) GetForkDates() (ForkMap, error) { 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", - Fork_Trinity: "2025-01-13", + Fork_Trinity: "2025-01-09", }, nil case Chain_Mainnet: return ForkMap{