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