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