Skip to content

Commit 53e12db

Browse files
committed
add deallocation logic
1 parent ce7c8f7 commit 53e12db

File tree

4 files changed

+244
-4
lines changed

4 files changed

+244
-4
lines changed

pkg/postgres/migrations/202511171438_withdrawalQueueShareSnapshots/up.go renamed to pkg/postgres/migrations/202511171438_withdrawalAndDeallocationQueues/up.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package _202511171438_withdrawalQueueShareSnapshots
1+
package _202511171438_withdrawalAndDeallocationQueues
22

33
import (
44
"database/sql"
@@ -37,6 +37,34 @@ func (m *Migration) Up(db *sql.DB, grm *gorm.DB, cfg *config.Config) error {
3737

3838
// Index for staker lookups
3939
`create index if not exists idx_withdrawal_queue_share_snapshots_staker on withdrawal_queue_share_snapshots(staker, snapshot)`,
40+
41+
// =============================================================================
42+
// PART 5: Create deallocation_queue_snapshots table for operator allocation tracking
43+
// =============================================================================
44+
45+
// Table to track operator allocation decreases that haven't reached effective_date yet
46+
// Operators continue earning on old (higher) allocation until effective_date
47+
// Similar concept to withdrawal queue but for operator deallocations
48+
`create table if not exists deallocation_queue_snapshots (
49+
operator varchar not null,
50+
avs varchar not null,
51+
strategy varchar not null,
52+
magnitude_decrease numeric not null,
53+
block_date date not null,
54+
effective_date date not null,
55+
snapshot date not null,
56+
primary key (operator, avs, strategy, snapshot),
57+
constraint uniq_deallocation_queue_snapshots unique (operator, avs, strategy, snapshot)
58+
)`,
59+
60+
// Index for querying deallocation queue snapshots by snapshot date
61+
`create index if not exists idx_deallocation_queue_snapshots_snapshot on deallocation_queue_snapshots(snapshot)`,
62+
63+
// Index for operator lookups
64+
`create index if not exists idx_deallocation_queue_snapshots_operator on deallocation_queue_snapshots(operator, snapshot)`,
65+
66+
// Index for AVS lookups
67+
`create index if not exists idx_deallocation_queue_snapshots_avs on deallocation_queue_snapshots(avs, snapshot)`,
4068
}
4169

4270
for _, query := range queries {
@@ -50,5 +78,5 @@ func (m *Migration) Up(db *sql.DB, grm *gorm.DB, cfg *config.Config) error {
5078
}
5179

5280
func (m *Migration) GetName() string {
53-
return "202511171438_withdrawalQueueShareSnapshots"
81+
return "202511171438_withdrawalAndDeallocationQueues"
5482
}

pkg/postgres/migrations/migrator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import (
8383
_202507301421_crossChainRegistryTables "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202507301421_crossChainRegistryTables"
8484
_202511051502_keyRotationScheduled "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202511051502_keyRotationScheduled"
8585
_202511141700_withdrawalQueueAndAllocationRounding "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202511141700_withdrawalQueueAndAllocationRounding"
86-
_202511171438_withdrawalQueueShareSnapshots "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202511171438_withdrawalQueueShareSnapshots"
86+
_202511171438_withdrawalAndDeallocationQueues "github.com/Layr-Labs/sidecar/pkg/postgres/migrations/202511171438_withdrawalAndDeallocationQueues"
8787
)
8888

8989
// Migration interface defines the contract for database migrations.
@@ -229,7 +229,7 @@ func (m *Migrator) MigrateAll() error {
229229
&_202507301346_taskMailboxTables.Migration{},
230230
&_202511051502_keyRotationScheduled.Migration{},
231231
&_202511141700_withdrawalQueueAndAllocationRounding.Migration{},
232-
&_202511171438_withdrawalQueueShareSnapshots.Migration{},
232+
&_202511171438_withdrawalAndDeallocationQueues.Migration{},
233233
}
234234

235235
for _, migration := range migrations {
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package rewards
2+
3+
import (
4+
"github.com/Layr-Labs/sidecar/internal/config"
5+
"github.com/Layr-Labs/sidecar/pkg/rewardsUtils"
6+
"go.uber.org/zap"
7+
)
8+
9+
// deallocationQueueShareSnapshotsQuery calculates operator allocations that should
10+
// still be considered for rewards because the effective_date hasn't been reached yet.
11+
//
12+
// Logic (similar to withdrawals but for operator allocations):
13+
// - When an operator deallocates (reduces allocation), effective_date is set (rounded DOWN)
14+
// - The allocation magnitude is immediately updated in operator_allocations table
15+
// - But for rewards, the OLD (higher) allocation should be used until effective_date
16+
// - After effective_date, use the NEW (lower) allocation
17+
//
18+
// For a given snapshot_date, we need to identify deallocations where:
19+
// - block_date <= snapshot_date (deallocation already recorded)
20+
// - effective_date > snapshot_date (not yet effective)
21+
//
22+
// This is conceptually similar to withdrawal queue but uses effective_date instead of
23+
// a fixed 14-day period.
24+
//
25+
// NOTE: This handles DECREASES (deallocations). Increases (allocations) are simpler
26+
// because they round UP, so they naturally start earning on effective_date.
27+
const deallocationQueueShareSnapshotsQuery = `
28+
with deallocation_adjustments as (
29+
select
30+
oa.operator,
31+
oa.avs,
32+
oa.strategy,
33+
oa.magnitude as new_magnitude,
34+
oa.effective_date,
35+
date(b.block_time) as block_date,
36+
oa.block_number,
37+
-- Get previous allocation to calculate the difference
38+
lag(oa.magnitude) over (
39+
partition by oa.operator, oa.avs, oa.strategy
40+
order by oa.block_number, oa.log_index
41+
) as prev_magnitude
42+
from operator_allocations oa
43+
inner join blocks b on oa.block_number = b.number
44+
where
45+
oa.effective_date is not null -- Only Sabine fork records
46+
-- Allocation already recorded before or on snapshot
47+
and date(b.block_time) <= '{{.snapshotDate}}'
48+
-- Effective date is in the future relative to snapshot
49+
and oa.effective_date > '{{.snapshotDate}}'
50+
),
51+
deallocations_only as (
52+
select
53+
operator,
54+
avs,
55+
strategy,
56+
new_magnitude,
57+
prev_magnitude,
58+
(prev_magnitude::numeric - new_magnitude::numeric) as magnitude_decrease,
59+
effective_date,
60+
block_date
61+
from deallocation_adjustments
62+
where
63+
-- Only deallocations (decreases)
64+
prev_magnitude is not null
65+
and new_magnitude::numeric < prev_magnitude::numeric
66+
)
67+
insert into deallocation_queue_snapshots(
68+
operator,
69+
avs,
70+
strategy,
71+
magnitude_decrease,
72+
block_date,
73+
effective_date,
74+
snapshot
75+
)
76+
select
77+
operator,
78+
avs,
79+
strategy,
80+
magnitude_decrease,
81+
block_date,
82+
effective_date,
83+
'{{.snapshotDate}}'::date as snapshot
84+
from deallocations_only
85+
on conflict on constraint uniq_deallocation_queue_snapshots do nothing;
86+
`
87+
88+
// DeallocationQueueSnapshot represents operator allocation decreases that haven't
89+
// reached their effective_date yet, and should still be counted for rewards.
90+
type DeallocationQueueSnapshot struct {
91+
Operator string `gorm:"column:operator;primaryKey"`
92+
Avs string `gorm:"column:avs;primaryKey"`
93+
Strategy string `gorm:"column:strategy;primaryKey"`
94+
MagnitudeDecrease string `gorm:"column:magnitude_decrease"`
95+
BlockDate string `gorm:"column:block_date"`
96+
EffectiveDate string `gorm:"column:effective_date"`
97+
Snapshot string `gorm:"column:snapshot;primaryKey"`
98+
}
99+
100+
func (DeallocationQueueSnapshot) TableName() string {
101+
return "deallocation_queue_snapshots"
102+
}
103+
104+
// GenerateAndInsertDeallocationQueueSnapshots calculates and inserts operator allocation
105+
// decreases that should still be counted for rewards because their effective_date hasn't
106+
// been reached yet.
107+
//
108+
// This feature is only active after the Sabine fork (when effective_date is populated).
109+
func (r *RewardsCalculator) GenerateAndInsertDeallocationQueueSnapshots(snapshotDate string) error {
110+
// Check if we're past the Sabine fork
111+
forks, err := r.globalConfig.GetRewardsSqlForkDates()
112+
if err != nil {
113+
r.logger.Sugar().Errorw("Failed to get rewards fork dates", "error", err)
114+
return err
115+
}
116+
117+
sabineFork, exists := forks[config.RewardsFork_Sabine]
118+
if !exists {
119+
r.logger.Sugar().Warnw("Sabine fork not configured, skipping deallocation queue logic")
120+
return nil
121+
}
122+
123+
// Get the block number for the snapshot date to compare with fork block
124+
var maxBlock uint64
125+
res := r.grm.Raw(`
126+
SELECT COALESCE(MAX(number), 0) as max_block
127+
FROM blocks
128+
WHERE DATE(block_time) <= ?
129+
`, snapshotDate).Scan(&maxBlock)
130+
131+
if res.Error != nil {
132+
r.logger.Sugar().Errorw("Failed to get max block for snapshot date", "error", res.Error)
133+
return res.Error
134+
}
135+
136+
// Only apply deallocation queue logic if we're past the Sabine fork
137+
if maxBlock < sabineFork.BlockNumber {
138+
r.logger.Sugar().Debugw("Snapshot date is before Sabine fork, skipping deallocation queue logic",
139+
zap.String("snapshotDate", snapshotDate),
140+
zap.Uint64("maxBlock", maxBlock),
141+
zap.Uint64("sabineForkBlock", sabineFork.BlockNumber),
142+
)
143+
return nil
144+
}
145+
146+
query, err := rewardsUtils.RenderQueryTemplate(deallocationQueueShareSnapshotsQuery, map[string]interface{}{
147+
"snapshotDate": snapshotDate,
148+
})
149+
if err != nil {
150+
r.logger.Sugar().Errorw("Failed to render deallocation queue snapshots query template", "error", err)
151+
return err
152+
}
153+
154+
res = r.grm.Debug().Exec(query)
155+
if res.Error != nil {
156+
r.logger.Sugar().Errorw("Failed to insert deallocation_queue_snapshots",
157+
zap.String("snapshotDate", snapshotDate),
158+
zap.Error(res.Error),
159+
)
160+
return res.Error
161+
}
162+
163+
r.logger.Sugar().Infow("Generated deallocation queue snapshots",
164+
zap.String("snapshotDate", snapshotDate),
165+
zap.Int64("rowsAffected", res.RowsAffected),
166+
)
167+
168+
return nil
169+
}
170+
171+
// ListDeallocationQueueSnapshots returns all deallocation queue snapshots for debugging
172+
func (r *RewardsCalculator) ListDeallocationQueueSnapshots() ([]*DeallocationQueueSnapshot, error) {
173+
var snapshots []*DeallocationQueueSnapshot
174+
res := r.grm.Model(&DeallocationQueueSnapshot{}).Find(&snapshots)
175+
if res.Error != nil {
176+
r.logger.Sugar().Errorw("Failed to list deallocation queue snapshots", "error", res.Error)
177+
return nil, res.Error
178+
}
179+
return snapshots, nil
180+
}
181+
182+
// adjustOperatorAllocationSnapshotsForDeallocationQueueQuery adds deallocation queue
183+
// allocations back to operator allocation snapshots.
184+
//
185+
// This is similar to withdrawal queue adjustment but for operator allocations.
186+
// We add back the magnitude_decrease to the current allocation to get the pre-deallocation value.
187+
const adjustOperatorAllocationSnapshotsForDeallocationQueueQuery = `
188+
-- NOT YET IMPLEMENTED
189+
-- This would need to integrate with operator allocation snapshots used in rewards v2.2
190+
-- For now, this is a placeholder for future operator set rewards calculation
191+
-- TODO: Integrate with operator set rewards calculation
192+
`
193+
194+
// AdjustOperatorAllocationSnapshotsForDeallocationQueue adds deallocation queue
195+
// allocations back to operator allocation snapshots.
196+
//
197+
// NOTE: This is a placeholder for integration with operator set rewards (v2.2).
198+
// The actual adjustment logic will depend on how operator allocations are used in rewards.
199+
func (r *RewardsCalculator) AdjustOperatorAllocationSnapshotsForDeallocationQueue(snapshotDate string) error {
200+
// TODO: Implement adjustment logic once operator set rewards calculation structure is finalized
201+
r.logger.Sugar().Debugw("Deallocation queue adjustment not yet implemented",
202+
zap.String("snapshotDate", snapshotDate),
203+
)
204+
return nil
205+
}

pkg/rewards/rewards.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,13 @@ func (rc *RewardsCalculator) generateSnapshotData(snapshotDate string) error {
674674
}
675675
rc.logger.Sugar().Debugw("Generated operator share snapshots")
676676

677+
// Generate deallocation queue snapshots - operators continue earning on old allocation until effective_date
678+
if err = rc.GenerateAndInsertDeallocationQueueSnapshots(snapshotDate); err != nil {
679+
rc.logger.Sugar().Errorw("Failed to generate deallocation queue snapshots", "error", err)
680+
return err
681+
}
682+
rc.logger.Sugar().Debugw("Generated deallocation queue snapshots")
683+
677684
if err = rc.GenerateAndInsertStakerShareSnapshots(snapshotDate); err != nil {
678685
rc.logger.Sugar().Errorw("Failed to generate staker share snapshots", "error", err)
679686
return err

0 commit comments

Comments
 (0)