staker_delegation_snapshot.gno
5.91 Kb · 185 lines
1package staker
2
3import (
4 "chain"
5
6 "gno.land/p/gnoswap/utils/v1"
7 "gno.land/r/gnoswap/access/v1"
8 "gno.land/r/gnoswap/halt/v1"
9)
10
11// SetUnDelegationLockupPeriodByAdmin sets the undelegation lockup period.
12// This administrative function configures the time period that undelegated tokens
13// must wait before they can be collected by users.
14//
15// The lockup period serves as a security mechanism to:
16// - Prevent rapid delegation/undelegation cycles
17// - Provide time for governance decisions to take effect
18// - Maintain system stability during volatile periods
19//
20// Parameters:
21// - _: leading realm-call discriminator; callers pass 0 for the forwarded implementation call
22// - rlm: propagated current realm context validated before administrative state updates
23// - period: lockup period in seconds (must be non-negative)
24//
25// Panics:
26// - if caller is not admin
27// - if period is negative
28//
29// Note: This change affects all future undelegation operations
30func (gs *govStakerV1) SetUnDelegationLockupPeriodByAdmin(_ int, rlm realm, period int64) {
31 access.AssertIsRlmCurrent(0, rlm)
32
33 halt.AssertIsNotHaltedGovStaker()
34
35 prev := rlm.Previous()
36 caller := prev.Address()
37 access.AssertIsAdmin(caller)
38
39 if period < 0 {
40 panic("period must be greater than 0")
41 }
42
43 gs.setUnDelegationLockupPeriod(0, rlm, period)
44
45 chain.Emit(
46 "SetUnDelegationLockupPeriod",
47 "prevAddr", prev.Address().String(),
48 "prevRealm", prev.PkgPath(),
49 "period", utils.FormatInt(period),
50 )
51}
52
53// CleanStakerDelegationSnapshotByAdmin cleans old delegation history records.
54// This administrative function removes delegation history records older than the specified threshold
55// to prevent unlimited growth of historical data and optimize storage usage.
56//
57// The cleanup process:
58// 1. Validates the snapshot time is within allowed range
59// 2. Checks that no active proposals need data older than the cleanup threshold
60// 3. Filters delegation history to keep only records after cutoff time
61// 4. Updates the delegation history with filtered records
62//
63// Parameters:
64// - _: leading realm-call discriminator; callers pass 0 for the forwarded implementation call
65// - rlm: propagated current realm context validated before administrative state updates
66// - snapshotTime: cutoff timestamp (records older than this will be removed)
67// - target: the user address whose delegation history should be cleaned
68//
69// Panics:
70// - if caller is not admin
71// - if snapshotTime is invalid (negative or too recent)
72// - if active proposals have snapshotTime older than the cleanup threshold
73func (gs *govStakerV1) CleanStakerDelegationSnapshotByAdmin(_ int, rlm realm, snapshotTime int64, target address) {
74 access.AssertIsRlmCurrent(0, rlm)
75
76 halt.AssertIsNotHaltedGovStaker()
77
78 prev := rlm.Previous()
79 caller := prev.Address()
80 access.AssertIsAdmin(caller)
81
82 assertIsValidSnapshotTime(snapshotTime)
83 assertIsAvailableCleanupSnapshotTime(snapshotTime)
84
85 // Clean total delegation history
86 gs.cleanTotalDelegationHistory(0, rlm, snapshotTime)
87
88 // Clean user delegation history
89 gs.cleanUserDelegationHistoryForAddress(0, rlm, target, snapshotTime)
90
91 chain.Emit(
92 "CleanStakerDelegationSnapshot",
93 "prevAddr", prev.Address().String(),
94 "prevRealm", prev.PkgPath(),
95 "snapshotTime", utils.FormatInt(snapshotTime),
96 "target", target.String(),
97 )
98}
99
100// cleanTotalDelegationHistory removes total delegation history entries older than cutoff time.
101// Keeps the most recent entry before cutoff to preserve state continuity.
102func (gs *govStakerV1) cleanTotalDelegationHistory(_ int, rlm realm, cutoffTimestamp int64) {
103 history := gs.store.GetTotalDelegationHistory()
104
105 // First, find the most recent entry before cutoff to preserve state
106 var lastValue any
107
108 hasLastValue := false
109
110 history.ReverseIterate(0, cutoffTimestamp, func(timestamp int64, value any) bool {
111 lastValue = value
112 hasLastValue = true
113
114 return true // stop after first (most recent)
115 })
116
117 // If there was a value before cutoff, set it at cutoff time to preserve continuity
118 if hasLastValue && !history.Has(cutoffTimestamp) {
119 history.Set(cutoffTimestamp, lastValue)
120 }
121
122 // Collect keys to remove (cannot modify tree during iteration)
123 var keysToRemove []int64
124 history.Iterate(0, cutoffTimestamp, func(timestamp int64, _ any) bool {
125 keysToRemove = append(keysToRemove, timestamp)
126 return false // continue
127 })
128 for _, key := range keysToRemove {
129 history.Remove(key)
130 }
131
132 if err := gs.store.SetTotalDelegationHistory(0, rlm, history); err != nil {
133 panic(err)
134 }
135}
136
137// cleanUserDelegationHistoryForAddress removes user delegation history entries
138// older than cutoff time for a single target address.
139// Keeps the most recent entry strictly before cutoff
140// in place so range-based snapshot lookups continue to resolve correctly.
141func (gs *govStakerV1) cleanUserDelegationHistoryForAddress(_ int, rlm realm, target address, cutoffTimestamp int64) {
142 if cutoffTimestamp <= 0 {
143 return
144 }
145
146 history := gs.store.GetUserDelegationHistory()
147
148 addrStr := target.String()
149 iterationStartKey, _ := userHistoryKeyRange(addrStr)
150 iterationEndKey := makeUserHistoryKey(addrStr, cutoffTimestamp)
151
152 // First, find the most recent entry before cutoff to preserve state
153 var lastValue any
154
155 hasLastValue := false
156
157 history.ReverseIterate(iterationStartKey, iterationEndKey, func(key string, value any) bool {
158 lastValue = value
159 hasLastValue = true
160
161 return true // stop after first (most recent)
162 })
163
164 if hasLastValue && !history.Has(iterationEndKey) {
165 history.Set(iterationEndKey, lastValue)
166 }
167
168 // Collect all keys strictly before preserveKey within this address prefix.
169 // Iterate's end is exclusive, so [lo, preserveKey) skips the preserved entry.
170 var keysToRemove []string
171
172 history.Iterate(iterationStartKey, iterationEndKey, func(key string, _ any) bool {
173 keysToRemove = append(keysToRemove, key)
174
175 return false
176 })
177
178 for _, key := range keysToRemove {
179 history.Remove(key)
180 }
181
182 if err := gs.store.SetUserDelegationHistory(0, rlm, history); err != nil {
183 panic(err)
184 }
185}