Skip to content

Commit

Permalink
feat: utilize split in existing rewards calculation (#117)
Browse files Browse the repository at this point in the history
Utilize the `OperatorAVSSplit` and `OperatorPISplit` in the existing
rewards v1 and Programmatic incentives calculation (instead of the
hardcoded 10% split).
  • Loading branch information
0xrajath authored Dec 2, 2024
2 parents c78d2cb + 5095472 commit d845aca
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 21 deletions.
7 changes: 6 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package config
import (
"errors"
"fmt"
"github.com/spf13/viper"
"strconv"
"strings"

"github.com/spf13/viper"
)

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

ENV_PREFIX = "SIDECAR"
)
Expand Down Expand Up @@ -194,18 +196,21 @@ func (c *Config) GetForkDates() (ForkMap, error) {
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on preprod as we backfilled
Fork_Nile: "2024-08-14", // Last calculation end timestamp was 8-13: https://holesky.etherscan.io/tx/0xb5a6855e88c79312b7c0e1c9f59ae9890b97f157ea27e69e4f0fadada4712b64#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-04",
}, nil
case Chain_Holesky:
return ForkMap{
Fork_Amazon: "1970-01-01", // Amazon hard fork was never on testnet as we backfilled
Fork_Nile: "2024-08-13", // Last calculation end timestamp was 8-12: https://holesky.etherscan.io/tx/0x5fc81b5ed2a78b017ef313c181d8627737a97fef87eee85acedbe39fc8708c56#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2024-12-10",
}, nil
case Chain_Mainnet:
return ForkMap{
Fork_Amazon: "2024-08-02", // Last calculation end timestamp was 8-01: https://etherscan.io/tx/0x2aff6f7b0132092c05c8f6f41a5e5eeeb208aa0d95ebcc9022d7823e343dd012#eventlog
Fork_Nile: "2024-08-12", // Last calculation end timestamp was 8-11: https://etherscan.io/tx/0x922d29d93c02d189fc2332041f01a80e0007cd7a625a5663ef9d30082f7ef66f#eventlog
Fork_Panama: "2024-10-01",
Fork_Arno: "2025-01-07",
}, nil
}
return nil, errors.New("unsupported chain")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package _202411221331_operatorAVSSplitSnapshots

import (
"database/sql"

"gorm.io/gorm"
)

type Migration struct {
}

func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error {
queries := []string{
`CREATE TABLE IF NOT EXISTS operator_avs_split_snapshots (
operator varchar not null,
avs varchar not null,
split integer not null,
snapshot date not null
)`,
}
for _, query := range queries {
if _, err := db.Exec(query); err != nil {
return err
}
}
return nil
}

func (m *Migration) GetName() string {
return "202411221331_operatorAVSSplitSnapshots"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package _202411221331_operatorPISplitSnapshots

import (
"database/sql"

"gorm.io/gorm"
)

type Migration struct {
}

func (m *Migration) Up(db *sql.DB, grm *gorm.DB) error {
queries := []string{
`CREATE TABLE IF NOT EXISTS operator_pi_split_snapshots (
operator varchar not null,
split integer not null,
snapshot date not null
)`,
}
for _, query := range queries {
if _, err := db.Exec(query); err != nil {
return err
}
}
return nil
}

func (m *Migration) GetName() string {
return "202411221331_operatorPISplitSnapshots"
}
4 changes: 4 additions & 0 deletions pkg/postgres/migrations/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import (
_202411191550_operatorAVSSplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191550_operatorAVSSplits"
_202411191708_operatorPISplits "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191708_operatorPISplits"
_202411191947_cleanupUnusedTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411191947_cleanupUnusedTables"
_202411221331_operatorAVSSplitSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411221331_operatorAVSSplitSnapshots"
_202411221331_operatorPISplitSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202411221331_operatorPISplitSnapshots"

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

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

import (
"database/sql"

"github.com/Layr-Labs/sidecar/internal/config"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -107,26 +108,33 @@ staker_operator_total_tokens AS (
END as total_staker_operator_payout
FROM staker_proportion
),
-- Calculate the token breakdown for each (staker, operator) pair
-- Calculate the token breakdown for each (staker, operator) pair with dynamic split logic
-- If no split is found, default to 1000 (10%)
token_breakdowns AS (
SELECT *,
SELECT sott.*,
CASE
WHEN snapshot < @amazonHardforkDate AND reward_submission_date < @amazonHardforkDate THEN
cast(total_staker_operator_payout * 0.10 AS DECIMAL(38,0))
WHEN snapshot < @nileHardforkDate AND reward_submission_date < @nileHardforkDate THEN
(total_staker_operator_payout * 0.10)::text::decimal(38,0)
WHEN sott.snapshot < @amazonHardforkDate AND sott.reward_submission_date < @amazonHardforkDate THEN
cast(sott.total_staker_operator_payout * 0.10 AS DECIMAL(38,0))
WHEN sott.snapshot < @nileHardforkDate AND sott.reward_submission_date < @nileHardforkDate THEN
(sott.total_staker_operator_payout * 0.10)::text::decimal(38,0)
WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN
floor(sott.total_staker_operator_payout * 0.10)
ELSE
floor(total_staker_operator_payout * 0.10)
floor(sott.total_staker_operator_payout * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL))
END as operator_tokens,
CASE
WHEN snapshot < @amazonHardforkDate AND reward_submission_date < @amazonHardforkDate THEN
total_staker_operator_payout - cast(total_staker_operator_payout * 0.10 as DECIMAL(38,0))
WHEN snapshot < @nileHardforkDate AND reward_submission_date < @nileHardforkDate THEN
total_staker_operator_payout - ((total_staker_operator_payout * 0.10)::text::decimal(38,0))
WHEN sott.snapshot < @amazonHardforkDate AND sott.reward_submission_date < @amazonHardforkDate THEN
sott.total_staker_operator_payout - cast(sott.total_staker_operator_payout * 0.10 as DECIMAL(38,0))
WHEN sott.snapshot < @nileHardforkDate AND sott.reward_submission_date < @nileHardforkDate THEN
sott.total_staker_operator_payout - ((sott.total_staker_operator_payout * 0.10)::text::decimal(38,0))
WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN
sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * 0.10)
ELSE
total_staker_operator_payout - floor(total_staker_operator_payout * 0.10)
sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(oas.split, 1000) / CAST(10000 AS DECIMAL))
END as staker_tokens
FROM staker_operator_total_tokens
FROM staker_operator_total_tokens sott
LEFT JOIN operator_avs_split_snapshots oas
ON sott.operator = oas.operator AND sott.avs = oas.avs AND sott.snapshot = oas.snapshot
)
SELECT * from token_breakdowns
ORDER BY reward_hash, snapshot, staker, operator
Expand All @@ -141,6 +149,7 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate
zap.String("destTableName", destTableName),
zap.String("amazonHardforkDate", forks[config.Fork_Amazon]),
zap.String("nileHardforkDate", forks[config.Fork_Nile]),
zap.String("arnoHardforkDate", forks[config.Fork_Arno]),
)

query, err := renderQueryTemplate(_2_goldStakerRewardAmountsQuery, map[string]string{
Expand All @@ -155,6 +164,7 @@ func (rc *RewardsCalculator) GenerateGold2StakerRewardAmountsTable(snapshotDate
res := rc.grm.Exec(query,
sql.Named("amazonHardforkDate", forks[config.Fork_Amazon]),
sql.Named("nileHardforkDate", forks[config.Fork_Nile]),
sql.Named("arnoHardforkDate", forks[config.Fork_Arno]),
)
if res.Error != nil {
rc.logger.Sugar().Errorw("Failed to create gold_staker_reward_amounts", "error", res.Error)
Expand Down
26 changes: 21 additions & 5 deletions pkg/rewards/5_goldRfaeStakers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rewards

import (
"database/sql"

"github.com/Layr-Labs/sidecar/internal/config"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -106,12 +107,25 @@ staker_operator_total_tokens AS (
FLOOR(staker_proportion * tokens_per_day_decimal) as total_staker_operator_payout
FROM staker_proportion
),
-- Calculate the token breakdown for each (staker, operator) pair
-- Calculate the token breakdown for each (staker, operator) pair with dynamic split logic
-- If no split is found, default to 1000 (10%)
token_breakdowns AS (
SELECT *,
floor(total_staker_operator_payout * 0.10) as operator_tokens,
total_staker_operator_payout - floor(total_staker_operator_payout * 0.10) as staker_tokens
FROM staker_operator_total_tokens
SELECT sott.*,
CASE
WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN
floor(sott.total_staker_operator_payout * 0.10)
ELSE
floor(sott.total_staker_operator_payout * COALESCE(ops.split, 1000) / CAST(10000 AS DECIMAL))
END as operator_tokens,
CASE
WHEN sott.snapshot < @arnoHardforkDate AND sott.reward_submission_date < @arnoHardforkDate THEN
sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * 0.10)
ELSE
sott.total_staker_operator_payout - floor(sott.total_staker_operator_payout * COALESCE(ops.split, 1000) / CAST(10000 AS DECIMAL))
END as staker_tokens
FROM staker_operator_total_tokens sott
LEFT JOIN operator_pi_split_snapshots ops
ON sott.operator = ops.operator AND sott.snapshot = ops.snapshot
)
SELECT * from token_breakdowns
ORDER BY reward_hash, snapshot, staker, operator
Expand All @@ -124,6 +138,7 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string,
rc.logger.Sugar().Infow("Generating rfae stakers table",
zap.String("cutoffDate", snapshotDate),
zap.String("destTableName", destTableName),
zap.String("arnoHardforkDate", forks[config.Fork_Arno]),
)

query, err := renderQueryTemplate(_5_goldRfaeStakersQuery, map[string]string{
Expand All @@ -138,6 +153,7 @@ func (rc *RewardsCalculator) GenerateGold5RfaeStakersTable(snapshotDate string,
res := rc.grm.Exec(query,
sql.Named("panamaForkDate", forks[config.Fork_Panama]),
sql.Named("network", rc.globalConfig.Chain.String()),
sql.Named("arnoHardforkDate", forks[config.Fork_Arno]),
)
if res.Error != nil {
rc.logger.Sugar().Errorw("Failed to generate gold_rfae_stakers", "error", res.Error)
Expand Down
92 changes: 92 additions & 0 deletions pkg/rewards/operatorAvsSplitSnapshots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package rewards

const operatorAvsSplitSnapshotQuery = `
WITH operator_avs_splits_with_block_info as (
select
oas.operator,
oas.avs,
oas.activated_at::timestamp(6) as activated_at,
oas.new_operator_avs_split_bips as split,
oas.block_number,
oas.log_index,
b.block_time::timestamp(6) as block_time
from operator_avs_splits as oas
join blocks as b on (b.number = oas.block_number)
where activated_at < TIMESTAMP '{{.cutoffDate}}'
),
-- Rank the records for each combination of (operator, avs, activation date) by activation time, block time and log index
ranked_operator_avs_split_records as (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY operator, avs, cast(activated_at AS DATE) ORDER BY activated_at DESC, block_time DESC, log_index DESC) AS rn
FROM operator_avs_splits_with_block_info
),
-- Get the latest record for each day & round up to the snapshot day
snapshotted_records as (
SELECT
operator,
avs,
split,
block_time,
date_trunc('day', activated_at) + INTERVAL '1' day AS snapshot_time
from ranked_operator_avs_split_records
where rn = 1
),
-- Get the range for each operator, avs pairing
operator_avs_split_windows as (
SELECT
operator, avs, split, snapshot_time as start_time,
CASE
-- If the range does not have the end, use the current timestamp truncated to 0 UTC
WHEN LEAD(snapshot_time) OVER (PARTITION BY operator, avs ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}')
ELSE LEAD(snapshot_time) OVER (PARTITION BY operator, avs ORDER BY snapshot_time)
END AS end_time
FROM snapshotted_records
),
-- Clean up any records where start_time >= end_time
cleaned_records as (
SELECT * FROM operator_avs_split_windows
WHERE start_time < end_time
),
-- Generate a snapshot for each day in the range
final_results as (
SELECT
operator,
avs,
split,
d AS snapshot
FROM
cleaned_records
CROSS JOIN
generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS d
)
select * from final_results
`

func (r *RewardsCalculator) GenerateAndInsertOperatorAvsSplitSnapshots(snapshotDate string) error {
tableName := "operator_avs_split_snapshots"

query, err := renderQueryTemplate(operatorAvsSplitSnapshotQuery, map[string]string{
"cutoffDate": snapshotDate,
})
if err != nil {
r.logger.Sugar().Errorw("Failed to render query template", "error", err)
return err
}

err = r.generateAndInsertFromQuery(tableName, query, nil)
if err != nil {
r.logger.Sugar().Errorw("Failed to generate operator_avs_split_snapshots", "error", err)
return err
}
return nil
}

func (r *RewardsCalculator) ListOperatorAvsSplitSnapshots() ([]*OperatorAVSSplitSnapshots, error) {
var snapshots []*OperatorAVSSplitSnapshots
res := r.grm.Model(&OperatorAVSSplitSnapshots{}).Find(&snapshots)
if res.Error != nil {
r.logger.Sugar().Errorw("Failed to list operator avs split snapshots", "error", res.Error)
return nil, res.Error
}
return snapshots, nil
}
Loading

0 comments on commit d845aca

Please sign in to comment.