Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}