staker_delegation_snapshot.gno
5.35 Kb ยท 182 lines
1package v1
2
3import (
4 "chain"
5 "chain/runtime"
6 "math"
7
8 "gno.land/r/gnoswap/access"
9 "gno.land/r/gnoswap/gov/staker"
10 "gno.land/r/gnoswap/halt"
11)
12
13// SetUnDelegationLockupPeriodByAdmin sets the undelegation lockup period.
14// This administrative function configures the time period that undelegated tokens
15// must wait before they can be collected by users.
16//
17// The lockup period serves as a security mechanism to:
18// - Prevent rapid delegation/undelegation cycles
19// - Provide time for governance decisions to take effect
20// - Maintain system stability during volatile periods
21//
22// Parameters:
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(period int64) {
31 halt.AssertIsNotHaltedGovStaker()
32
33 previousRealm := runtime.PreviousRealm()
34 caller := previousRealm.Address()
35 access.AssertIsAdmin(caller)
36
37 if period < 0 {
38 panic("period must be greater than 0")
39 }
40
41 gs.setUnDelegationLockupPeriod(period)
42
43 chain.Emit(
44 "SetUnDelegationLockupPeriod",
45 "prevAddr", previousRealm.Address().String(),
46 "prevRealm", previousRealm.PkgPath(),
47 "period", formatInt(period),
48 )
49}
50
51// CleanStakerDelegationSnapshotByAdmin cleans old delegation history records.
52// This administrative function removes delegation history records older than the specified threshold
53// to prevent unlimited growth of historical data and optimize storage usage.
54//
55// The cleanup process:
56// 1. Validates the snapshot time is within allowed range
57// 2. Checks that no active proposals need data older than the cleanup threshold
58// 3. Filters delegation history to keep only records after cutoff time
59// 4. Updates the delegation history with filtered records
60//
61// Parameters:
62// - snapshotTime: cutoff timestamp (records older than this will be removed)
63//
64// Panics:
65// - if caller is not admin
66// - if snapshotTime is invalid (negative or too recent)
67// - if active proposals have snapshotTime older than the cleanup threshold
68//
69// Note: This operation is irreversible and will permanently remove historical data
70func (gs *govStakerV1) CleanStakerDelegationSnapshotByAdmin(snapshotTime int64) {
71 halt.AssertIsNotHaltedGovStaker()
72
73 previousRealm := runtime.PreviousRealm()
74 caller := previousRealm.Address()
75 access.AssertIsAdmin(caller)
76
77 assertIsValidSnapshotTime(snapshotTime)
78 assertIsAvailableCleanupSnapshotTime(snapshotTime)
79
80 // Clean total delegation history
81 gs.cleanTotalDelegationHistory(snapshotTime)
82
83 // Clean user delegation history
84 gs.cleanUserDelegationHistory(snapshotTime)
85
86 chain.Emit(
87 "CleanStakerDelegationSnapshot",
88 "prevAddr", previousRealm.Address().String(),
89 "prevRealm", previousRealm.PkgPath(),
90 "snapshotTime", formatInt(snapshotTime),
91 )
92}
93
94// cleanTotalDelegationHistory removes total delegation history entries older than cutoff time.
95// Keeps the most recent entry before cutoff to preserve state continuity.
96func (gs *govStakerV1) cleanTotalDelegationHistory(cutoffTimestamp int64) {
97 history := gs.store.GetTotalDelegationHistory()
98
99 toTimestamp := cutoffTimestamp
100 if cutoffTimestamp < math.MaxInt64 {
101 toTimestamp = safeAddInt64(toTimestamp, 1)
102 }
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, toTimestamp, 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 {
118 history.Set(cutoffTimestamp, lastValue)
119 }
120
121 history.Iterate(0, cutoffTimestamp, func(timestamp int64, _ any) bool {
122 history.Remove(timestamp)
123
124 return false // continue
125 })
126
127 if err := gs.store.SetTotalDelegationHistory(history); err != nil {
128 panic(err)
129 }
130}
131
132// cleanUserDelegationHistory removes user delegation history entries older than cutoff time.
133// Structure: address -> *UintTree[timestamp -> int64]
134// Keeps the most recent entry before cutoff for each user to preserve state continuity.
135func (gs *govStakerV1) cleanUserDelegationHistory(cutoffTimestamp int64) {
136 history := gs.store.GetUserDelegationHistory()
137
138 // Iterate over all users and clean each user's history
139 history.Iterate("", "", func(addrStr string, value any) bool {
140 userHistory := value.(*staker.UintTree)
141
142 toTimestamp := cutoffTimestamp
143 if cutoffTimestamp < math.MaxInt64 {
144 toTimestamp = safeAddInt64(toTimestamp, 1)
145 }
146
147 // Find the most recent entry before cutoff to preserve state
148 var lastValue any
149
150 hasLastValue := false
151
152 userHistory.ReverseIterate(0, toTimestamp, func(_ int64, val any) bool {
153 lastValue = val
154 hasLastValue = true
155
156 return true // stop after first (most recent)
157 })
158
159 // If there was a value before cutoff, set it at cutoff time to preserve continuity
160 if hasLastValue {
161 userHistory.Set(cutoffTimestamp, lastValue)
162 }
163
164 // Copy all entries at or after cutoff time
165 userHistory.Iterate(0, cutoffTimestamp, func(key int64, val any) bool {
166 userHistory.Remove(key)
167
168 return false // continue
169 })
170
171 // Only keep users with history entries
172 if userHistory.Size() > 0 {
173 history.Set(addrStr, userHistory)
174 }
175
176 return false // continue to next user
177 })
178
179 if err := gs.store.SetUserDelegationHistory(history); err != nil {
180 panic(err)
181 }
182}