Skip to content

Commit 8c6c1eb

Browse files
committed
fix(rewards): address v2.2 audit findings
- Table 17/20: evaluate refunds per operator-set/operator, not per-snapshot - operatorShareSnapshots: apply allocations to all backfill days, include cutoff snapshot
1 parent fcd56e3 commit 8c6c1eb

File tree

4 files changed

+71
-55
lines changed

4 files changed

+71
-55
lines changed

pkg/rewards/17_goldAvsOperatorSetUniqueStakeRewards.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@ WITH total_available_tokens AS (
2323
GROUP BY reward_hash, snapshot, token, avs, operator_set_id, operator
2424
),
2525
26-
-- Step 2: Calculate total tokens actually distributed from the operator rewards table
26+
-- Step 2: Calculate total tokens actually distributed per operator from the operator rewards table
2727
total_distributed_tokens AS (
2828
SELECT
2929
reward_hash,
3030
snapshot,
31+
avs,
32+
operator_set_id,
33+
operator,
3134
COALESCE(SUM(operator_tokens), 0) as distributed_tokens
3235
FROM {{.operatorRewardsTable}}
33-
GROUP BY reward_hash, snapshot
36+
GROUP BY reward_hash, snapshot, avs, operator_set_id, operator
3437
),
3538
36-
-- Step 3: Identify snapshots where distributed tokens = 0, refund all available tokens to AVS
39+
-- Step 3: Identify operator-sets where distributed tokens = 0, refund those tokens to AVS
3740
snapshots_requiring_refund AS (
3841
SELECT
3942
tat.reward_hash,
@@ -47,6 +50,9 @@ snapshots_requiring_refund AS (
4750
LEFT JOIN total_distributed_tokens tdt
4851
ON tat.reward_hash = tdt.reward_hash
4952
AND tat.snapshot = tdt.snapshot
53+
AND tat.avs = tdt.avs
54+
AND tat.operator_set_id = tdt.operator_set_id
55+
AND tat.operator = tdt.operator
5056
WHERE COALESCE(tdt.distributed_tokens, 0) = 0
5157
)
5258

pkg/rewards/20_goldAvsOperatorSetTotalStakeRewards.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@ WITH total_available_tokens AS (
2323
GROUP BY reward_hash, snapshot, token, avs, operator_set_id, operator
2424
),
2525
26-
-- Step 2: Calculate total tokens actually distributed from the operator rewards table
26+
-- Step 2: Calculate total tokens actually distributed per operator from the operator rewards table
2727
total_distributed_tokens AS (
2828
SELECT
2929
reward_hash,
3030
snapshot,
31+
avs,
32+
operator_set_id,
33+
operator,
3134
COALESCE(SUM(operator_tokens), 0) as distributed_tokens
3235
FROM {{.operatorRewardsTable}}
33-
GROUP BY reward_hash, snapshot
36+
GROUP BY reward_hash, snapshot, avs, operator_set_id, operator
3437
),
3538
36-
-- Step 3: Identify snapshots where distributed tokens = 0, refund all available tokens to AVS
39+
-- Step 3: Identify operator-sets where distributed tokens = 0, refund those tokens to AVS
3740
snapshots_requiring_refund AS (
3841
SELECT
3942
tat.reward_hash,
@@ -47,6 +50,9 @@ snapshots_requiring_refund AS (
4750
LEFT JOIN total_distributed_tokens tdt
4851
ON tat.reward_hash = tdt.reward_hash
4952
AND tat.snapshot = tdt.snapshot
53+
AND tat.avs = tdt.avs
54+
AND tat.operator_set_id = tdt.operator_set_id
55+
AND tat.operator = tdt.operator
5056
WHERE COALESCE(tdt.distributed_tokens, 0) = 0
5157
)
5258

pkg/rewards/operatorShareSnapshots.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ operator_share_windows as (
3030
SELECT
3131
operator, strategy, shares, snapshot_time as start_time,
3232
CASE
33-
-- If the range does not have the end, use the current timestamp truncated to 0 UTC
34-
WHEN LEAD(snapshot_time) OVER (PARTITION BY operator, strategy ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}')
33+
-- If the range does not have the end, use cutoff + 1 day to include cutoff date snapshot
34+
WHEN LEAD(snapshot_time) OVER (PARTITION BY operator, strategy ORDER BY snapshot_time) is null THEN date_trunc('day', TIMESTAMP '{{.cutoffDate}}') + INTERVAL '1' day
3535
ELSE LEAD(snapshot_time) OVER (PARTITION BY operator, strategy ORDER BY snapshot_time)
3636
END AS end_time
3737
FROM snapshotted_records
@@ -51,15 +51,15 @@ base_snapshots as (
5151
CROSS JOIN
5252
generate_series(DATE(start_time), DATE(end_time) - interval '1' day, interval '1' day) AS day
5353
),
54-
-- Add operator allocations (deallocation delay handled via effective_block)
54+
-- Add operator allocations for all days in the generated window
5555
allocation_adjustments as (
5656
SELECT
5757
oas.operator,
5858
oas.strategy,
5959
SUM(oas.magnitude) as total_magnitude,
6060
oas.snapshot
6161
FROM operator_allocation_snapshots oas
62-
WHERE oas.snapshot = DATE '{{.snapshotDate}}'
62+
WHERE oas.snapshot <= DATE '{{.snapshotDate}}'
6363
GROUP BY oas.operator, oas.strategy, oas.snapshot
6464
),
6565
combined_snapshots as (

pkg/rewards/operatorShareSnapshots_test.go

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -205,23 +205,23 @@ func Test_OperatorShareSnapshots_BasicShares(t *testing.T) {
205205

206206
// T0: Operator has 1000 shares
207207
err = grm.Exec(`
208-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
209-
VALUES (?, ?, ?, ?)
210-
`, operator, strategy, "1000000000000000000000", 100).Error
208+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
209+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
210+
`, operator, strategy, "1000000000000000000000", "0xtx100", 0, t0, t0.Format(time.DateOnly), 100).Error
211211
assert.Nil(t, err)
212212

213213
// T1: Shares increase to 1500
214214
err = grm.Exec(`
215-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
216-
VALUES (?, ?, ?, ?)
217-
`, operator, strategy, "1500000000000000000000", 200).Error
215+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
216+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
217+
`, operator, strategy, "1500000000000000000000", "0xtx200", 0, t1, t1.Format(time.DateOnly), 200).Error
218218
assert.Nil(t, err)
219219

220220
// T2: Shares decrease to 800
221221
err = grm.Exec(`
222-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
223-
VALUES (?, ?, ?, ?)
224-
`, operator, strategy, "800000000000000000000", 300).Error
222+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
223+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
224+
`, operator, strategy, "800000000000000000000", "0xtx300", 0, t2, t2.Format(time.DateOnly), 300).Error
225225
assert.Nil(t, err)
226226

227227
// Generate snapshots
@@ -308,15 +308,15 @@ func Test_OperatorShareSnapshots_WithAllocations(t *testing.T) {
308308

309309
// T0: Operator has 1000 shares from base operator_shares table
310310
err = grm.Exec(`
311-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
312-
VALUES (?, ?, ?, ?)
313-
`, operator, strategy, "1000000000000000000000", 100).Error
311+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
312+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
313+
`, operator, strategy, "1000000000000000000000", "0xtx100", 0, t0, t0.Format(time.DateOnly), 100).Error
314314
assert.Nil(t, err)
315315

316316
// T1: Operator has allocation of 2000 (should override base shares)
317317
err = grm.Exec(`
318-
INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
319-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
318+
INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index)
319+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
320320
`, operator, avs, strategy, 1, "2000000000000000000000", 200, 200, "0xtx1", 1).Error
321321
assert.Nil(t, err)
322322

@@ -372,6 +372,8 @@ func Test_OperatorShareSnapshots_MultipleStrategies(t *testing.T) {
372372
strategy2 := "0xstrategy_b"
373373

374374
t0 := time.Date(2024, 3, 1, 0, 0, 0, 0, time.UTC)
375+
// Use a cutoff date after t0 since snapshots round up to the next day
376+
cutoffDate := t0.AddDate(0, 0, 2)
375377

376378
t.Run("Operator has shares in multiple strategies", func(t *testing.T) {
377379
// Insert block
@@ -384,27 +386,27 @@ func Test_OperatorShareSnapshots_MultipleStrategies(t *testing.T) {
384386

385387
// Operator has shares in strategy1
386388
err = grm.Exec(`
387-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
388-
VALUES (?, ?, ?, ?)
389-
`, operator, strategy1, "500000000000000000000", 100).Error
389+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
390+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
391+
`, operator, strategy1, "500000000000000000000", "0xtx100a", 0, t0, t0.Format(time.DateOnly), 100).Error
390392
assert.Nil(t, err)
391393

392394
// Operator has shares in strategy2
393395
err = grm.Exec(`
394-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
395-
VALUES (?, ?, ?, ?)
396-
`, operator, strategy2, "700000000000000000000", 100).Error
396+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
397+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
398+
`, operator, strategy2, "700000000000000000000", "0xtx100b", 1, t0, t0.Format(time.DateOnly), 100).Error
397399
assert.Nil(t, err)
398400

399-
// Generate snapshots
401+
// Generate snapshots with cutoff date after data insertion
400402
sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg)
401403
rewards, err := NewRewardsCalculator(cfg, grm, nil, sog, sink, l)
402404
assert.Nil(t, err)
403405

404-
err = rewards.GenerateAndInsertOperatorAllocationSnapshots(t0.Format(time.DateOnly))
406+
err = rewards.GenerateAndInsertOperatorAllocationSnapshots(cutoffDate.Format(time.DateOnly))
405407
assert.Nil(t, err)
406408

407-
err = rewards.GenerateAndInsertOperatorShareSnapshots(t0.Format(time.DateOnly))
409+
err = rewards.GenerateAndInsertOperatorShareSnapshots(cutoffDate.Format(time.DateOnly))
408410
assert.Nil(t, err)
409411

410412
// Verify snapshots for both strategies
@@ -461,16 +463,16 @@ func Test_OperatorShareSnapshots_ZeroShares(t *testing.T) {
461463

462464
// T0: Operator has 300 shares
463465
err = grm.Exec(`
464-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
465-
VALUES (?, ?, ?, ?)
466-
`, operator, strategy, "300000000000000000000", 100).Error
466+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
467+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
468+
`, operator, strategy, "300000000000000000000", "0xtx100", 0, t0, t0.Format(time.DateOnly), 100).Error
467469
assert.Nil(t, err)
468470

469471
// T1: Operator shares go to 0
470472
err = grm.Exec(`
471-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
472-
VALUES (?, ?, ?, ?)
473-
`, operator, strategy, "0", 200).Error
473+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
474+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
475+
`, operator, strategy, "0", "0xtx200", 0, t1, t1.Format(time.DateOnly), 200).Error
474476
assert.Nil(t, err)
475477

476478
// Generate snapshots
@@ -524,6 +526,8 @@ func Test_OperatorShareSnapshots_MultipleOperators(t *testing.T) {
524526
strategy := "0xstrategy_shared"
525527

526528
t0 := time.Date(2024, 5, 1, 0, 0, 0, 0, time.UTC)
529+
// Use a cutoff date after t0 since snapshots round up to the next day
530+
cutoffDate := t0.AddDate(0, 0, 2)
527531

528532
t.Run("Multiple operators same strategy", func(t *testing.T) {
529533
// Insert block
@@ -536,27 +540,27 @@ func Test_OperatorShareSnapshots_MultipleOperators(t *testing.T) {
536540

537541
// Operator 1 has 600 shares
538542
err = grm.Exec(`
539-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
540-
VALUES (?, ?, ?, ?)
541-
`, operator1, strategy, "600000000000000000000", 100).Error
543+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
544+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
545+
`, operator1, strategy, "600000000000000000000", "0xtx100a", 0, t0, t0.Format(time.DateOnly), 100).Error
542546
assert.Nil(t, err)
543547

544548
// Operator 2 has 900 shares
545549
err = grm.Exec(`
546-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
547-
VALUES (?, ?, ?, ?)
548-
`, operator2, strategy, "900000000000000000000", 100).Error
550+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
551+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
552+
`, operator2, strategy, "900000000000000000000", "0xtx100b", 1, t0, t0.Format(time.DateOnly), 100).Error
549553
assert.Nil(t, err)
550554

551-
// Generate snapshots
555+
// Generate snapshots with cutoff date after data insertion
552556
sog := stakerOperators.NewStakerOperatorGenerator(grm, l, cfg)
553557
rewards, err := NewRewardsCalculator(cfg, grm, nil, sog, sink, l)
554558
assert.Nil(t, err)
555559

556-
err = rewards.GenerateAndInsertOperatorAllocationSnapshots(t0.Format(time.DateOnly))
560+
err = rewards.GenerateAndInsertOperatorAllocationSnapshots(cutoffDate.Format(time.DateOnly))
557561
assert.Nil(t, err)
558562

559-
err = rewards.GenerateAndInsertOperatorShareSnapshots(t0.Format(time.DateOnly))
563+
err = rewards.GenerateAndInsertOperatorShareSnapshots(cutoffDate.Format(time.DateOnly))
560564
assert.Nil(t, err)
561565

562566
// Verify both operators have snapshots
@@ -603,7 +607,7 @@ func Test_OperatorShareSnapshots_SameDayMultipleChanges(t *testing.T) {
603607
{102, 18, "150000000000000000000"}, // 18:00 - latest
604608
}
605609

606-
for _, s := range shares {
610+
for i, s := range shares {
607611
blockTime := baseDate.Add(time.Duration(s.hour) * time.Hour)
608612
err := grm.Exec(`
609613
INSERT INTO blocks (number, hash, block_time, created_at)
@@ -613,9 +617,9 @@ func Test_OperatorShareSnapshots_SameDayMultipleChanges(t *testing.T) {
613617
assert.Nil(t, err)
614618

615619
err = grm.Exec(`
616-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
617-
VALUES (?, ?, ?, ?)
618-
`, operator, strategy, s.amount, s.number).Error
620+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
621+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
622+
`, operator, strategy, s.amount, fmt.Sprintf("0xtx%d", s.number), i, blockTime, blockTime.Format(time.DateOnly), s.number).Error
619623
assert.Nil(t, err)
620624
}
621625

@@ -678,9 +682,9 @@ func Test_OperatorShareSnapshots_LargeNumbers(t *testing.T) {
678682
// Very large share amount (1 billion tokens with 18 decimals)
679683
largeAmount := "1000000000000000000000000000"
680684
err = grm.Exec(`
681-
INSERT INTO operator_shares (operator, strategy, shares, block_number)
682-
VALUES (?, ?, ?, ?)
683-
`, operator, strategy, largeAmount, 100).Error
685+
INSERT INTO operator_shares (operator, strategy, shares, transaction_hash, log_index, block_time, block_date, block_number)
686+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
687+
`, operator, strategy, largeAmount, "0xtx100", 0, t0, t0.Format(time.DateOnly), 100).Error
684688
assert.Nil(t, err)
685689

686690
// Generate snapshots

0 commit comments

Comments
 (0)