@@ -124,19 +124,272 @@ func Test_OperatorAllocationSnapshots(t *testing.T) {
124124 assert .True (t , len (snapshots ) > 0 , "Expected snapshots to be generated" )
125125
126126 // Verify rounding behavior:
127- // - First allocation (1000) at block 100 (2024-11-14 10:00) should have snapshot starting 2024-11-15
128- // - Increase (1500) at block 101 (2024-11-14 15:00) should have snapshot starting 2024-11-15 (next day after 2024-11-14)
129- // - Decrease (500) at block 102 (2024-11-15 12:00) should have snapshot starting 2024-11-15 (same day)
127+ // - Both block 100 (magnitude 1000) and block 101 (magnitude 1500) occur on 2024-11-14
128+ // - SQL takes the latest record per day (block 101 with magnitude 1500)
129+ // - This allocation is an increase, so it rounds up to 2024-11-15
130+ // - Decrease (500) at block 102 (2024-11-15 12:00) rounds down to 2024-11-15 (same day)
130131
131- // Find the snapshot with magnitude 1000
132- var mag1000Count int64
132+ // Find the snapshot with magnitude 1500 (latest allocation on 2024-11-14)
133+ var mag1500Count int64
134+ res = grm .Raw (`
135+ SELECT COUNT(*) FROM operator_allocation_snapshots
136+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
137+ AND magnitude = '1500' AND snapshot = ?
138+ ` , "0xoperator1" , "0xavs1" , "0xstrategy1" , 1 , "2024-11-15" ).Scan (& mag1500Count )
139+ assert .Nil (t , res .Error )
140+ assert .True (t , mag1500Count > 0 , "Expected magnitude 1500 to have snapshot on 2024-11-15 (latest record on 2024-11-14, rounded up)" )
141+ })
142+
143+ t .Run ("Basic allocation round up test" , func (t * testing.T ) {
144+ // Create test block
145+ blockTime := time .Date (2024 , 12 , 1 , 14 , 30 , 0 , 0 , time .UTC )
146+ blockNum := uint64 (200 )
147+ res := grm .Exec (`
148+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
149+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
150+ ` , blockNum , fmt .Sprintf ("hash_%d" , blockNum ), blockTime , blockTime .Format ("2006-01-02" ))
151+ assert .Nil (t , res .Error )
152+
153+ // First allocation should round up to next day (2024-12-02)
154+ res = grm .Exec (`
155+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
156+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
157+ ` , "0xoperator2" , "0xavs2" , "0xstrategy2" , 2 , "2000" , blockNum , blockNum , "tx_200" , 1 )
158+ assert .Nil (t , res .Error )
159+
160+ sog := stakerOperators .NewStakerOperatorGenerator (grm , l , cfg )
161+ calculator , err := NewRewardsCalculator (cfg , grm , nil , sog , sink , l )
162+ assert .Nil (t , err )
163+
164+ err = calculator .GenerateAndInsertOperatorAllocationSnapshots ("2024-12-05" )
165+ assert .Nil (t , err )
166+
167+ // Verify first allocation rounds up to 2024-12-02
168+ var count int64
169+ res = grm .Raw (`
170+ SELECT COUNT(*) FROM operator_allocation_snapshots
171+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
172+ AND magnitude = '2000' AND snapshot = ?
173+ ` , "0xoperator2" , "0xavs2" , "0xstrategy2" , 2 , "2024-12-02" ).Scan (& count )
174+ assert .Nil (t , res .Error )
175+ assert .True (t , count > 0 , "Expected first allocation to round up to 2024-12-02" )
176+ })
177+
178+ t .Run ("Multiple allocations same day uses latest" , func (t * testing.T ) {
179+ // Create blocks - all on same day
180+ baseDate := time .Date (2024 , 12 , 10 , 0 , 0 , 0 , 0 , time .UTC )
181+ blocks := []struct {
182+ number uint64
183+ hour int
184+ magnitude string
185+ }{
186+ {300 , 8 , "3000" }, // 08:00
187+ {301 , 12 , "3500" }, // 12:00
188+ {302 , 18 , "4000" }, // 18:00 - latest, should be used
189+ }
190+
191+ for _ , b := range blocks {
192+ blockTime := baseDate .Add (time .Duration (b .hour ) * time .Hour )
193+ res := grm .Exec (`
194+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
195+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
196+ ` , b .number , fmt .Sprintf ("hash_%d" , b .number ), blockTime , blockTime .Format ("2006-01-02" ))
197+ assert .Nil (t , res .Error )
198+
199+ res = grm .Exec (`
200+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
201+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
202+ ` , "0xoperator3" , "0xavs3" , "0xstrategy3" , 3 , b .magnitude , b .number , b .number , fmt .Sprintf ("tx_%d" , b .number ), 1 )
203+ assert .Nil (t , res .Error )
204+ }
205+
206+ sog := stakerOperators .NewStakerOperatorGenerator (grm , l , cfg )
207+ calculator , err := NewRewardsCalculator (cfg , grm , nil , sog , sink , l )
208+ assert .Nil (t , err )
209+
210+ err = calculator .GenerateAndInsertOperatorAllocationSnapshots ("2024-12-15" )
211+ assert .Nil (t , err )
212+
213+ // Verify only the latest allocation (4000) is used, rounded up to 2024-12-11
214+ var count int64
215+ res := grm .Raw (`
216+ SELECT COUNT(*) FROM operator_allocation_snapshots
217+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
218+ AND magnitude = '4000' AND snapshot = ?
219+ ` , "0xoperator3" , "0xavs3" , "0xstrategy3" , 3 , "2024-12-11" ).Scan (& count )
220+ assert .Nil (t , res .Error )
221+ assert .True (t , count > 0 , "Expected latest allocation (4000) to be used, rounded up to 2024-12-11" )
222+
223+ // Verify earlier allocations are not present
224+ res = grm .Raw (`
225+ SELECT COUNT(*) FROM operator_allocation_snapshots
226+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
227+ AND magnitude IN ('3000', '3500')
228+ ` , "0xoperator3" , "0xavs3" , "0xstrategy3" , 3 ).Scan (& count )
229+ assert .Nil (t , res .Error )
230+ assert .Equal (t , int64 (0 ), count , "Expected earlier allocations to not be present" )
231+ })
232+
233+ t .Run ("Allocate and deallocate same day uses latest" , func (t * testing.T ) {
234+ // Create blocks on same day
235+ baseDate := time .Date (2024 , 12 , 20 , 0 , 0 , 0 , 0 , time .UTC )
236+
237+ // Allocate at 10:00
238+ block1Time := baseDate .Add (10 * time .Hour )
239+ block1Num := uint64 (400 )
240+ res := grm .Exec (`
241+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
242+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
243+ ` , block1Num , fmt .Sprintf ("hash_%d" , block1Num ), block1Time , block1Time .Format ("2006-01-02" ))
244+ assert .Nil (t , res .Error )
245+
246+ res = grm .Exec (`
247+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
248+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
249+ ` , "0xoperator4" , "0xavs4" , "0xstrategy4" , 4 , "5000" , block1Num , block1Num , "tx_400" , 1 )
250+ assert .Nil (t , res .Error )
251+
252+ // Deallocate at 16:00 (same day)
253+ block2Time := baseDate .Add (16 * time .Hour )
254+ block2Num := uint64 (401 )
255+ res = grm .Exec (`
256+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
257+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
258+ ` , block2Num , fmt .Sprintf ("hash_%d" , block2Num ), block2Time , block2Time .Format ("2006-01-02" ))
259+ assert .Nil (t , res .Error )
260+
261+ res = grm .Exec (`
262+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
263+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
264+ ` , "0xoperator4" , "0xavs4" , "0xstrategy4" , 4 , "1000" , block2Num , block2Num , "tx_401" , 1 )
265+ assert .Nil (t , res .Error )
266+
267+ sog := stakerOperators .NewStakerOperatorGenerator (grm , l , cfg )
268+ calculator , err := NewRewardsCalculator (cfg , grm , nil , sog , sink , l )
269+ assert .Nil (t , err )
270+
271+ err = calculator .GenerateAndInsertOperatorAllocationSnapshots ("2024-12-25" )
272+ assert .Nil (t , err )
273+
274+ // Verify the latest record (deallocation to 1000) is used
275+ // Since it's a decrease, it should round down to 2024-12-20
276+ var count int64
133277 res = grm .Raw (`
134278 SELECT COUNT(*) FROM operator_allocation_snapshots
135279 WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
136280 AND magnitude = '1000' AND snapshot = ?
137- ` , "0xoperator1" , "0xavs1" , "0xstrategy1" , 1 , "2024-11-15" ).Scan (& mag1000Count )
281+ ` , "0xoperator4" , "0xavs4" , "0xstrategy4" , 4 , "2024-12-20" ).Scan (& count )
282+ assert .Nil (t , res .Error )
283+ assert .True (t , count > 0 , "Expected latest deallocation (1000) to be used, rounded down to 2024-12-20" )
284+ })
285+
286+ t .Run ("Allocate day 1, deallocate to 0 day 2 at 12pm - 0 days counted" , func (t * testing.T ) {
287+ // Allocate on day 1
288+ day1 := time .Date (2025 , 1 , 10 , 10 , 0 , 0 , 0 , time .UTC )
289+ block1Num := uint64 (500 )
290+ res := grm .Exec (`
291+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
292+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
293+ ` , block1Num , fmt .Sprintf ("hash_%d" , block1Num ), day1 , day1 .Format ("2006-01-02" ))
294+ assert .Nil (t , res .Error )
295+
296+ res = grm .Exec (`
297+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
298+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
299+ ` , "0xoperator5" , "0xavs5" , "0xstrategy5" , 5 , "6000" , block1Num , block1Num , "tx_500" , 1 )
300+ assert .Nil (t , res .Error )
301+
302+ // Deallocate to 0 on day 2 at 12pm (noon)
303+ day2Noon := time .Date (2025 , 1 , 11 , 12 , 0 , 0 , 0 , time .UTC )
304+ block2Num := uint64 (501 )
305+ res = grm .Exec (`
306+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
307+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
308+ ` , block2Num , fmt .Sprintf ("hash_%d" , block2Num ), day2Noon , day2Noon .Format ("2006-01-02" ))
309+ assert .Nil (t , res .Error )
310+
311+ res = grm .Exec (`
312+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
313+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
314+ ` , "0xoperator5" , "0xavs5" , "0xstrategy5" , 5 , "0" , block2Num , block2Num , "tx_501" , 1 )
315+ assert .Nil (t , res .Error )
316+
317+ sog := stakerOperators .NewStakerOperatorGenerator (grm , l , cfg )
318+ calculator , err := NewRewardsCalculator (cfg , grm , nil , sog , sink , l )
319+ assert .Nil (t , err )
320+
321+ err = calculator .GenerateAndInsertOperatorAllocationSnapshots ("2025-01-15" )
322+ assert .Nil (t , err )
323+
324+ // Allocation on day 1 (2025-01-10) rounds up to 2025-01-11
325+ // Deallocation on day 2 (2025-01-11) rounds down to 2025-01-11
326+ // So the range is [2025-01-11, 2025-01-11) which generates no days
327+ var count int64
328+ res = grm .Raw (`
329+ SELECT COUNT(*) FROM operator_allocation_snapshots
330+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
331+ ` , "0xoperator5" , "0xavs5" , "0xstrategy5" , 5 ).Scan (& count )
332+ assert .Nil (t , res .Error )
333+ assert .Equal (t , int64 (0 ), count , "Expected 0 days to be counted (allocation rounds up to day 2, deallocation rounds down to day 2)" )
334+ })
335+
336+ t .Run ("Allocate day 1, deallocate to 0 day 3 at 12pm - 1 day counted" , func (t * testing.T ) {
337+ // Allocate on day 1
338+ day1 := time .Date (2025 , 1 , 20 , 10 , 0 , 0 , 0 , time .UTC )
339+ block1Num := uint64 (600 )
340+ res := grm .Exec (`
341+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
342+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
343+ ` , block1Num , fmt .Sprintf ("hash_%d" , block1Num ), day1 , day1 .Format ("2006-01-02" ))
344+ assert .Nil (t , res .Error )
345+
346+ res = grm .Exec (`
347+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
348+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
349+ ` , "0xoperator6" , "0xavs6" , "0xstrategy6" , 6 , "7000" , block1Num , block1Num , "tx_600" , 1 )
350+ assert .Nil (t , res .Error )
351+
352+ // Deallocate to 0 on day 3 at 12pm (noon)
353+ day3Noon := time .Date (2025 , 1 , 22 , 12 , 0 , 0 , 0 , time .UTC )
354+ block2Num := uint64 (601 )
355+ res = grm .Exec (`
356+ INSERT INTO blocks (number, hash, block_time, block_date, state_root, created_at, updated_at)
357+ VALUES (?, ?, ?, ?, '', NOW(), NOW())
358+ ` , block2Num , fmt .Sprintf ("hash_%d" , block2Num ), day3Noon , day3Noon .Format ("2006-01-02" ))
359+ assert .Nil (t , res .Error )
360+
361+ res = grm .Exec (`
362+ INSERT INTO operator_allocations (operator, avs, strategy, operator_set_id, magnitude, effective_block, block_number, transaction_hash, log_index, created_at, updated_at)
363+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
364+ ` , "0xoperator6" , "0xavs6" , "0xstrategy6" , 6 , "0" , block2Num , block2Num , "tx_601" , 1 )
365+ assert .Nil (t , res .Error )
366+
367+ sog := stakerOperators .NewStakerOperatorGenerator (grm , l , cfg )
368+ calculator , err := NewRewardsCalculator (cfg , grm , nil , sog , sink , l )
369+ assert .Nil (t , err )
370+
371+ err = calculator .GenerateAndInsertOperatorAllocationSnapshots ("2025-01-25" )
372+ assert .Nil (t , err )
373+
374+ // Allocation on day 1 (2025-01-20) rounds up to 2025-01-21
375+ // Deallocation on day 3 (2025-01-22) rounds down to 2025-01-22
376+ // So the range is [2025-01-21, 2025-01-22) which generates 1 day (2025-01-21)
377+ var count int64
378+ res = grm .Raw (`
379+ SELECT COUNT(*) FROM operator_allocation_snapshots
380+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
381+ ` , "0xoperator6" , "0xavs6" , "0xstrategy6" , 6 ).Scan (& count )
382+ assert .Nil (t , res .Error )
383+ assert .Equal (t , int64 (1 ), count , "Expected 1 day to be counted (2025-01-21)" )
384+
385+ // Verify the snapshot is on 2025-01-21 with magnitude 7000
386+ res = grm .Raw (`
387+ SELECT COUNT(*) FROM operator_allocation_snapshots
388+ WHERE operator = ? AND avs = ? AND strategy = ? AND operator_set_id = ?
389+ AND magnitude = '7000' AND snapshot = ?
390+ ` , "0xoperator6" , "0xavs6" , "0xstrategy6" , 6 , "2025-01-21" ).Scan (& count )
138391 assert .Nil (t , res .Error )
139- assert .True (t , mag1000Count > 0 , "Expected magnitude 1000 to have snapshot on 2024-11-15 (rounded up) " )
392+ assert .Equal (t , int64 ( 1 ), count , "Expected snapshot on 2025-01-21 with magnitude 7000 " )
140393 })
141394
142395 t .Cleanup (func () {
0 commit comments