package v1 import ( "chain" "gno.land/r/gnoswap/access" "gno.land/r/gnoswap/halt" ) // SetUnDelegationLockupPeriodByAdmin sets the undelegation lockup period. // This administrative function configures the time period that undelegated tokens // must wait before they can be collected by users. // // The lockup period serves as a security mechanism to: // - Prevent rapid delegation/undelegation cycles // - Provide time for governance decisions to take effect // - Maintain system stability during volatile periods // // Parameters: // - period: lockup period in seconds (must be non-negative) // // Panics: // - if caller is not admin // - if period is negative // // Note: This change affects all future undelegation operations func (gs *govStakerV1) SetUnDelegationLockupPeriodByAdmin(_ int, rlm realm, period int64) { if !rlm.IsCurrent() { panic(errSpoofedRealm) } halt.AssertIsNotHaltedGovStaker() prev := rlm.Previous() caller := prev.Address() access.AssertIsAdmin(caller) if period < 0 { panic("period must be greater than 0") } gs.setUnDelegationLockupPeriod(0, rlm, period) chain.Emit( "SetUnDelegationLockupPeriod", "prevAddr", prev.Address().String(), "prevRealm", prev.PkgPath(), "period", formatInt(period), ) } // CleanStakerDelegationSnapshotByAdmin cleans old delegation history records. // This administrative function removes delegation history records older than the specified threshold // to prevent unlimited growth of historical data and optimize storage usage. // // The cleanup process: // 1. Validates the snapshot time is within allowed range // 2. Checks that no active proposals need data older than the cleanup threshold // 3. Filters delegation history to keep only records after cutoff time // 4. Updates the delegation history with filtered records // // Parameters: // - snapshotTime: cutoff timestamp (records older than this will be removed) // - target: the user address whose delegation history should be cleaned // // Panics: // - if caller is not admin // - if snapshotTime is invalid (negative or too recent) // - if active proposals have snapshotTime older than the cleanup threshold func (gs *govStakerV1) CleanStakerDelegationSnapshotByAdmin(_ int, rlm realm, snapshotTime int64, target address) { if !rlm.IsCurrent() { panic(errSpoofedRealm) } halt.AssertIsNotHaltedGovStaker() prev := rlm.Previous() caller := prev.Address() access.AssertIsAdmin(caller) assertIsValidSnapshotTime(snapshotTime) assertIsAvailableCleanupSnapshotTime(snapshotTime) // Clean total delegation history gs.cleanTotalDelegationHistory(0, rlm, snapshotTime) // Clean user delegation history gs.cleanUserDelegationHistoryForAddress(0, rlm, target, snapshotTime) chain.Emit( "CleanStakerDelegationSnapshot", "prevAddr", prev.Address().String(), "prevRealm", prev.PkgPath(), "snapshotTime", formatInt(snapshotTime), "target", target.String(), ) } // cleanTotalDelegationHistory removes total delegation history entries older than cutoff time. // Keeps the most recent entry before cutoff to preserve state continuity. func (gs *govStakerV1) cleanTotalDelegationHistory(_ int, rlm realm, cutoffTimestamp int64) { history := gs.store.GetTotalDelegationHistory() // First, find the most recent entry before cutoff to preserve state var lastValue any hasLastValue := false history.ReverseIterate(0, cutoffTimestamp, func(timestamp int64, value any) bool { lastValue = value hasLastValue = true return true // stop after first (most recent) }) // If there was a value before cutoff, set it at cutoff time to preserve continuity if hasLastValue && !history.Has(cutoffTimestamp) { history.Set(cutoffTimestamp, lastValue) } // Collect keys to remove (cannot modify tree during iteration) var keysToRemove []int64 history.Iterate(0, cutoffTimestamp, func(timestamp int64, _ any) bool { keysToRemove = append(keysToRemove, timestamp) return false // continue }) for _, key := range keysToRemove { history.Remove(key) } if err := gs.store.SetTotalDelegationHistory(0, rlm, history); err != nil { panic(err) } } // cleanUserDelegationHistoryForAddress removes user delegation history entries // older than cutoff time for a single target address. // Keeps the most recent entry strictly before cutoff // in place so range-based snapshot lookups continue to resolve correctly. func (gs *govStakerV1) cleanUserDelegationHistoryForAddress(_ int, rlm realm, target address, cutoffTimestamp int64) { if cutoffTimestamp <= 0 { return } history := gs.store.GetUserDelegationHistory() addrStr := target.String() iterationStartKey, _ := userHistoryKeyRange(addrStr) iterationEndKey := makeUserHistoryKey(addrStr, cutoffTimestamp) // First, find the most recent entry before cutoff to preserve state var lastValue any hasLastValue := false history.ReverseIterate(iterationStartKey, iterationEndKey, func(key string, value any) bool { lastValue = value hasLastValue = true return true // stop after first (most recent) }) if hasLastValue && !history.Has(iterationEndKey) { history.Set(iterationEndKey, lastValue) } // Collect all keys strictly before preserveKey within this address prefix. // Iterate's end is exclusive, so [lo, preserveKey) skips the preserved entry. var keysToRemove []string history.Iterate(iterationStartKey, iterationEndKey, func(key string, _ any) bool { keysToRemove = append(keysToRemove, key) return false }) for _, key := range keysToRemove { history.Remove(key) } if err := gs.store.SetUserDelegationHistory(0, rlm, history); err != nil { panic(err) } }