Kind Realm (renderable)
Name v1
Namespace gnoswap / staker
Exported functions 14
Module gno.land/r/gnoswap/staker/v1
gno 0.9 assert.gno gno
⧉
1 package staker 2 3 import ( 4 "math" 5 "strconv" 6 "strings" 7 "time" 8 NewDepositResolver (deposit *gno.land/r/gnoswap/staker.Deposit) *gno.land/r/gnoswap/staker/v1.DepositResolver
NewDeposits () *gno.land/r/gnoswap/staker/v1.Deposits
NewExternalIncentiveResolver (externalIncentive *gno.land/r/gnoswap/staker.ExternalIncentive) *gno.land/r/gnoswap/staker/v1.ExternalIncentiveResolver
NewExternalIncentives () *gno.land/r/gnoswap/staker/v1.ExternalIncentives
NewIncentivesResolver (incentives *gno.land/r/gnoswap/staker.Incentives) *gno.land/r/gnoswap/staker/v1.IncentivesResolver
NewPoolResolver (pool *gno.land/r/gnoswap/staker.Pool) *gno.land/r/gnoswap/staker/v1.PoolResolver
NewPools () *gno.land/r/gnoswap/staker/v1.Pools
NewPoolTier (pools *gno.land/r/gnoswap/staker/v1.Pools, currentTime int64, initialPoolPath string, getEmission func() (int64, .uverse.error), getHalvingBlocksInRange func(int64, int64) ([]int64, []int64, .uverse.error)) *gno.land/r/gnoswap/staker/v1.PoolTier
NewPoolTierBy (membership *gno.land/p/nt/bptree/v0.BPTree, tierRatio struct{Tier1 uint64; Tier2 uint64; Tier3 uint64}, counts [4]uint64, lastRewardCacheTimestamp int64, currentEmission int64, getEmission func() (int64, .uverse.error), getHalvingBlocksInRange func(int64, int64) ([]int64, []int64, .uverse.error)) *gno.land/r/gnoswap/staker/v1.PoolTier
NewStakerV1 (stakerStore interface {AddAllowedToken func(int, .uverse.realm, string) .uverse.error; AddDeniedRewardToken func(int, .uverse.realm, string) .uverse.error; GetAllowedTokens func() []string; GetCurrentSwapBatch func() *gno.land/r/gnoswap/staker.SwapBatchProcessor; GetDeniedRewardTokens func() []string; GetDepositGnsAmount func() int64; GetDeposits func() *gno.land/p/nt/bptree/v0.BPTree; GetExternalIncentives func() *gno.land/p/nt/bptree/v0.BPTree; GetIncentiveCounter func() *gno.land/r/gnoswap/staker.Counter; GetMinimumRewardAmount func() int64; GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetPoolTierCounts func() [4]uint64; GetPoolTierCurrentEmission func() int64; GetPoolTierGetEmission func() func() (int64, .uverse.error); GetPoolTierGetHalvingBlocksInRange func() func(int64, int64) ([]int64, []int64, .uverse.error); GetPoolTierLastRewardCacheTimestamp func() int64; GetPoolTierMemberships func() *gno.land/p/nt/bptree/v0.BPTree; GetPoolTierRatio func() gno.land/r/gnoswap/staker.TierRatio; GetPools func() *gno.land/p/nt/bptree/v0.BPTree; GetTokenSpecificMinimumRewards func() map[string]int64; GetTotalEmissionSent func() int64; GetUncollectedIncentiveCounts func() *gno.land/p/nt/bptree/v0.BPTree; GetUnstakedPositions func() *gno.land/p/nt/bptree/v0.BPTree; GetUnstakingFee func() uint64; GetWarmupTemplate func() []gno.land/r/gnoswap/staker.Warmup; HasAllowedTokensStoreKey func() bool; HasCurrentSwapBatchStoreKey func() bool; HasDeniedRewardTokensStoreKey func() bool; HasDepositGnsAmountStoreKey func() bool; HasDepositsStoreKey func() bool; HasExternalIncentivesStoreKey func() bool; HasIncentiveCounterStoreKey func() bool; HasMinimumRewardAmountStoreKey func() bool; HasPendingProtocolFeesStoreKey func() bool; HasPoolTierCountsStoreKey func() bool; HasPoolTierCurrentEmissionStoreKey func() bool; HasPoolTierGetEmissionStoreKey func() bool; HasPoolTierGetHalvingBlocksInRangeStoreKey func() bool; HasPoolTierLastRewardCacheTimestampStoreKey func() bool; HasPoolTierMembershipsStoreKey func() bool; HasPoolTierRatioStoreKey func() bool; HasPoolsStoreKey func() bool; HasTokenSpecificMinimumRewardsStoreKey func() bool; HasTotalEmissionSentStoreKey func() bool; HasUncollectedIncentiveCountsStoreKey func() bool; HasUnstakedPositionsStoreKey func() bool; HasUnstakingFeeStoreKey func() bool; HasWarmupTemplateStoreKey func() bool; NextIncentiveID func(.uverse.address, int64) string; RemoveAllowedToken func(int, .uverse.realm, string) .uverse.error; RemoveDeniedRewardToken func(int, .uverse.realm, string) .uverse.error; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; RemoveTokenSpecificMinimumRewardItem func(int, .uverse.realm, string) .uverse.error; SetAllowedTokens func(int, .uverse.realm, []string) .uverse.error; SetCurrentSwapBatch func(int, .uverse.realm, *gno.land/r/gnoswap/staker.SwapBatchProcessor) .uverse.error; SetDepositGnsAmount func(int, .uverse.realm, int64) .uverse.error; SetDeposits func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetExternalIncentives func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetIncentiveCounter func(int, .uverse.realm, *gno.land/r/gnoswap/staker.Counter) .uverse.error; SetMinimumRewardAmount func(int, .uverse.realm, int64) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetPoolTierCounts func(int, .uverse.realm, [4]uint64) .uverse.error; SetPoolTierCurrentEmission func(int, .uverse.realm, int64) .uverse.error; SetPoolTierGetEmission func(int, .uverse.realm, func() (int64, .uverse.error)) .uverse.error; SetPoolTierGetHalvingBlocksInRange func(int, .uverse.realm, func(int64, int64) ([]int64, []int64, .uverse.error)) .uverse.error; SetPoolTierLastRewardCacheTimestamp func(int, .uverse.realm, int64) .uverse.error; SetPoolTierMemberships func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetPoolTierRatio func(int, .uverse.realm, gno.land/r/gnoswap/staker.TierRatio) .uverse.error; SetPools func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetTokenSpecificMinimumRewardItem func(int, .uverse.realm, string, int64) .uverse.error; SetTokenSpecificMinimumRewards func(int, .uverse.realm, map[string]int64) .uverse.error; SetTotalEmissionSent func(int, .uverse.realm, int64) .uverse.error; SetUncollectedIncentiveCounts func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetUnstakedPositions func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetUnstakingFee func(int, .uverse.realm, uint64) .uverse.error; SetWarmupTemplate func(int, .uverse.realm, []gno.land/r/gnoswap/staker.Warmup) .uverse.error}, poolAccessor interface {ExistsPoolPath func(string) bool; GetSlot0SqrtPriceX96 func(string) string; GetSlot0Tick func(string) int32; SetSwapEndHook func(int, .uverse.realm, func(int, .uverse.realm, string) .uverse.error); SetSwapStartHook func(int, .uverse.realm, func(int, .uverse.realm, string, int64)); SetTickCrossHook func(int, .uverse.realm, func(int, .uverse.realm, string, int32, bool, int64))}, emissionAccessor interface {GetStakerEmissionAmountPerSecond func() (int64, .uverse.error); GetStakerEmissionAmountPerSecondInRange func(int64, int64) ([]int64, []int64, .uverse.error); MintAndDistributeGns func(int, .uverse.realm) (int64, bool); SetOnDistributionPctChangeCallback func(int, .uverse.realm, func(int, .uverse.realm, int64))}, nftAccessor interface {Approve func(int, .uverse.realm, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) .uverse.error; Burn func(int, .uverse.realm, gno.land/p/nt/grc721/v0.TokenID); Exists func(gno.land/p/nt/grc721/v0.TokenID) bool; Mint func(int, .uverse.realm, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) gno.land/p/nt/grc721/v0.TokenID; MustOwnerOf func(gno.land/p/nt/grc721/v0.TokenID) .uverse.address; OwnerOf func(gno.land/p/nt/grc721/v0.TokenID) (.uverse.address, .uverse.error); TotalSupply func() int64; TransferFrom func(int, .uverse.realm, .uverse.address, .uverse.address, gno.land/p/nt/grc721/v0.TokenID) .uverse.error})
NewTickCrossEventInfo (tickID int32, stakedLiquidityGross *gno.land/p/gnoswap/uint256/v1.Uint, stakedLiquidityDelta *gno.land/p/gnoswap/int256/v1.Int, outsideAccumulation *gno.land/p/gnoswap/uint256/v1.Uint) *gno.land/r/gnoswap/staker/v1.tickCrossEventInfo
NewTickResolver (tick *gno.land/r/gnoswap/staker.Tick) *gno.land/r/gnoswap/staker/v1.TickResolver
NewUnstakedPositions () *gno.land/r/gnoswap/staker/v1.UnstakedPositions
TierRatioFromCounts (tier1Count uint64, tier2Count uint64, tier3Count uint64) struct{Tier1 uint64; Tier2 uint64; Tier3 uint64}
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
This realm does not provide renderable output (vm/qrender → NoRenderDeclError).
View on gnoweb ↗ vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
9 prbac "gno.land/p/gnoswap/rbac/v1"
10 ufmt "gno.land/p/nt/ufmt/v0"
11
12 "gno.land/r/gnoswap/access/v1"
13 )
14
15 const (
16 TIMESTAMP_90DAYS = int64 ( 7776000 )
17 TIMESTAMP_180DAYS = int64 ( 15552000 )
18 TIMESTAMP_365DAYS = int64 ( 31536000 )
19
20 maxIncentiveStartDelay = 7 * 24 * time.Hour
21
22 MAX_UNIX_EPOCH_TIME = 253402300799 // 9999-12-31 23:59:59
23
24 maxUnstakingFee = uint64 ( 1000 ) // 10%
25 )
26
27 // assertIsValidAmount ensures the amount is non-negative.
28 func assertIsValidAmount(amount int64 ) {
29 if amount < 0 {
30 panic(makeErrorWithDetails(
31 errInvalidInput,
32 ufmt.Sprintf( "amount(%d) must be positive" , amount),
33 ))
34 }
35 }
36
37 // assertIsValidRewardAmountFormat ensures the reward amount string is formatted as "tokenPath:amount".
38 func assertIsValidRewardAmountFormat(rewardAmountStr string ) {
39 parts := strings.SplitN(rewardAmountStr, ":" , 2 )
40 if len(parts) != 2 {
41 panic(makeErrorWithDetails(
42 errInvalidInput,
43 ufmt.Sprintf( "invalid format for SetTokenMinimumRewardAmount params: expected 'tokenPath:amount', got '%s'" , rewardAmountStr),
44 ))
45 }
46 }
47
48 // assertIsDepositor ensures the caller is the owner of the deposit.
49 func assertIsDepositor(s *stakerV1, caller address, positionId uint64 ) {
50 deposit := s.getDeposits().get(positionId)
51 if deposit == nil {
52 panic(makeErrorWithDetails(
53 errDataNotFound,
54 ufmt.Sprintf( "positionId(%d) not found" , positionId),
55 ))
56 }
57
58 if caller != deposit.Owner() {
59 panic(makeErrorWithDetails(
60 errNoPermission,
61 ufmt.Sprintf( "caller(%s) is not depositor(%s)" , caller.String(), deposit.Owner().String()),
62 ))
63 }
64 }
65
66 // assertIsIncentiveOfDeposit ensures the incentive exists and targets the pool the position is staked
67 // in, so a caller cannot ask a deposit to collect an incentive that can never pay it.
68 // assertIsIncentiveOfPool ensures the incentive targets the pool the position is staked in.
69 func assertIsIncentiveOfPool(s *stakerV1, poolPath string , positionId uint64 , incentiveId string ) {
70 // get panics with errDataNotFound when the incentive does not exist.
71 incentive := s.getExternalIncentives().get(incentiveId)
72
73 if incentive.TargetPoolPath() != poolPath {
74 panic(makeErrorWithDetails(
75 errInvalidInput,
76 ufmt.Sprintf(
77 "incentiveId(%s) targets pool(%s), but positionId(%d) is staked in pool(%s)" ,
78 incentiveId, incentive.TargetPoolPath(), positionId, poolPath,
79 ),
80 ))
81 }
82 }
83
84 // assertHasNoExitCheckpoint ensures the position carries no reward from a previous stake.
85 // Collecting it here would smuggle an unbounded calculation into staking, and leaving it would
86 // let the next unstake overwrite the checkpoint.
87 func assertHasNoExitCheckpoint(s *stakerV1, positionId uint64 ) {
88 if s.HasUnstakedPosition(positionId) {
89 panic(makeErrorWithDetails(
90 errUncollectedPosition,
91 ufmt.Sprintf( "positionId(%d) cannot be staked while its exit checkpoint owes rewards; collect after emission resumes if emission is halted" , positionId),
92 ))
93 }
94 }
95
96 // assertHasNoUncollectedIncentive ensures no unstaked position still owes a reward from this
97 // incentive, which has not been drawn down from it yet.
98 func assertHasNoUncollectedIncentive(s *stakerV1, incentiveId string ) {
99 count := s.uncollectedIncentiveCountOf(incentiveId)
100 if count > 0 {
101 panic(makeErrorWithDetails(
102 errUncollectedPosition,
103 ufmt.Sprintf( "incentive(%s) is owed to %d unstaked position(s)" , incentiveId, count),
104 ))
105 }
106 }
107
108 // assertIsNotStaked ensures the position is not already staked.
109 func assertIsNotStaked(s *stakerV1, positionId uint64 ) {
110 if s.getDeposits().Has(positionId) {
111 panic(makeErrorWithDetails(
112 errAlreadyStaked,
113 ufmt.Sprintf( "positionId(%d) already staked" , positionId),
114 ))
115 }
116 }
117
118 // assertIsPoolExists ensures the pool exists.
119 func assertIsPoolExists(s *stakerV1, poolPath string ) {
120 if !s.poolAccessor.ExistsPoolPath(poolPath) {
121 panic(makeErrorWithDetails(
122 errInvalidPoolPath,
123 ufmt.Sprintf( "pool(%s) does not exist" , poolPath),
124 ))
125 }
126 }
127
128 // assertIsValidPoolTier ensures the tier is within valid range.
129 func assertIsValidPoolTier(tier uint64 ) {
130 if tier >= AllTierCount {
131 panic(makeErrorWithDetails(
132 errInvalidPoolTier,
133 ufmt.Sprintf( "tier(%d) must be less than %d" , tier, AllTierCount),
134 ))
135 }
136 }
137
138 // assertTier1HasSparePool ensures tier 1 keeps at least one pool after a tier change.
139 func assertTier1HasSparePool(currentTier, tier1Count uint64 ) {
140 if currentTier == Tier1 && tier1Count == 1 {
141 panic(makeErrorWithDetails(errInvalidPoolTier, "tier 1 must have at least one pool" ))
142 }
143 }
144
145 // assertIsGreaterThanMinimumRewardAmount ensures the reward amount meets minimum requirements.
146 func assertIsGreaterThanMinimumRewardAmount(s *stakerV1, rewardToken string , rewardAmount int64 ) {
147 minReward := s.getMinimumRewardAmount()
148
149 if minRewardInt64, found := s.store.GetTokenSpecificMinimumRewards()[rewardToken]; found {
150 minReward = minRewardInt64
151 }
152
153 if rewardAmount < minReward {
154 panic(makeErrorWithDetails(
155 errInvalidInput,
156 ufmt.Sprintf( "rewardAmount(%d) is less than minimum required amount(%d)" , rewardAmount, minReward),
157 ))
158 }
159 }
160
161 // assertIsAllowedForExternalReward ensures the token is allowed for external rewards.
162 func assertIsAllowedForExternalReward(s *stakerV1, poolPath, tokenPath string ) {
163 // Operational stop switch: a denied token can never start a NEW incentive,
164 // whether it qualifies as a pool-pair token or through the governance
165 // allowlist. Collection of already-created incentives is deliberately
166 // unaffected; the delivery guard in deliverExternalIncentiveReward bounds
167 // that blast radius instead.
168 if contains(s.store.GetDeniedRewardTokens(), tokenPath) {
169 panic(makeErrorWithDetails(
170 errDeniedRewardToken,
171 ufmt.Sprintf( "tokenPath(%s) is denied as an external reward token" , tokenPath),
172 ))
173 }
174
175 token0, token1, _ := poolPathDivide(poolPath)
176
177 if tokenPath == token0 || tokenPath == token1 {
178 return
179 }
180
181 allowed := contains(s.store.GetAllowedTokens(), tokenPath)
182 if allowed {
183 return
184 }
185
186 panic(makeErrorWithDetails(
187 errNotAllowedForExternalReward,
188 ufmt.Sprintf( "tokenPath(%s) is not allowed for external reward for poolPath(%s)" , tokenPath, poolPath),
189 ))
190 }
191
192 // assertIsExternalRewardTokenAvailable ensures a pool has no unfinished incentive for the token.
193 func assertIsExternalRewardTokenAvailable(s *stakerV1, poolPath, tokenPath string , currentTime int64 ) {
194 pool := s.getPools().GetPoolOrNil(poolPath)
195 if pool == nil || pool.Incentives() == nil {
196 return
197 }
198
199 minimumActiveIncentiveStartTime := currentTime - TIMESTAMP_365DAYS
200 if minimumActiveIncentiveStartTime < 0 {
201 minimumActiveIncentiveStartTime = 0
202 }
203
204 pool.Incentives().IterateIncentiveIdsByTime(minimumActiveIncentiveStartTime, math.MaxInt64, func (incentiveID string ) bool {
205 incentive := s.getExternalIncentives().get(incentiveID)
206 if incentive == nil {
207 return false
208 }
209
210 if incentive.RewardToken() != tokenPath || NewExternalIncentiveResolver(incentive).IsEnded(currentTime) {
211 return false
212 }
213
214 panic(makeErrorWithDetails(
215 errIncentiveAlreadyExists,
216 ufmt.Sprintf(
217 "rewardToken(%s) already has unfinished incentive(%s) for poolPath(%s)" ,
218 tokenPath,
219 incentiveID,
220 poolPath,
221 ),
222 ))
223 })
224 }
225
226 // assertIsValidFeeRate ensures the fee rate is within valid range (0-1000 basis points).
227 func assertIsValidFeeRate(fee uint64 ) {
228 if fee > maxUnstakingFee {
229 panic(makeErrorWithDetails(
230 errInvalidUnstakingFee,
231 ufmt.Sprintf( "fee(%d) must be in range 0 ~ %d" , fee, maxUnstakingFee),
232 ))
233 }
234 }
235
236 // assertIsValidIncentiveStartTime ensures the incentive starts at midnight no earlier than 24 hours after creation
237 // and no later than 7 days after the first eligible midnight.
238 func assertIsValidIncentiveStartTime(startTimestamp int64 ) {
239 // must be in seconds format, not milliseconds
240 // REF: https://stackoverflow.com/a/23982005
241 numStr := strconv.Itoa( int (startTimestamp))
242
243 if len(numStr) >= 13 {
244 panic(makeErrorWithDetails(
245 errInvalidIncentiveStartTime,
246 ufmt.Sprintf( "startTimestamp(%d) must be in seconds format, not milliseconds" , startTimestamp),
247 ))
248 }
249
250 // must be at least 24 hours from now
251 minimumStartTimestamp := getMinimumIncentiveStartTimestamp()
252 if startTimestamp < minimumStartTimestamp {
253 panic(makeErrorWithDetails(
254 errInvalidIncentiveStartTime,
255 ufmt.Sprintf( "startTimestamp(%d) must be at least 24 hours later" , startTimestamp),
256 ))
257 }
258
259 maximumStartTimestamp := getMaximumIncentiveStartTimestamp()
260 if startTimestamp > maximumStartTimestamp {
261 panic(makeErrorWithDetails(
262 errInvalidIncentiveStartTime,
263 ufmt.Sprintf( "startTimestamp(%d) exceeds the maximum start time policy" , startTimestamp),
264 ))
265 }
266
267 // must be midnight of the day
268 startTime := time.Unix(startTimestamp, 0 )
269 if !isMidnight(startTime) {
270 panic(makeErrorWithDetails(
271 errInvalidIncentiveStartTime,
272 ufmt.Sprintf( "startTime(%d = %s) must be midnight of the day" , startTimestamp, startTime.String()),
273 ))
274 }
275 }
276
277 // assertIsAdminGovernanceOrCreator ensures the caller may act on an incentive:
278 // admin and governance are the protocol-level operators, and the creator is the
279 // address that funded the incentive in CreateExternalIncentive.
280 func assertIsAdminGovernanceOrCreator(caller, creator address) {
281 if caller == creator {
282 return
283 }
284 if access.IsAuthorized(prbac.ROLE_ADMIN.String(), caller) ||
285 access.IsAuthorized(prbac.ROLE_GOVERNANCE.String(), caller) {
286 return
287 }
288
289 panic(makeErrorWithDetails(
290 errNoPermission,
291 ufmt.Sprintf(
292 "caller(%s) must be admin, governance, or the incentive creator(%s)" ,
293 caller.String(), creator.String(),
294 ),
295 ))
296 }
297
298 // assertIsNotStartedIncentive ensures the incentive can still be cancelled, i.e.
299 // it has not started and nothing has been distributed from it. Only such an
300 // incentive may be removed outright; anything that already emitted rewards has
301 // to be finalized through EndExternalIncentive so the refund accounting and the
302 // penalty balance stay reachable.
303 func assertIsNotStartedIncentive(incentiveResolver *ExternalIncentiveResolver, currentTime int64 ) {
304 if incentiveResolver.IsStarted(currentTime) {
305 panic(makeErrorWithDetails(
306 errCannotCancelIncentive,
307 ufmt.Sprintf(
308 "incentive(%s) already started at %d, current(%d)" ,
309 incentiveResolver.IncentiveId(), incentiveResolver.StartTimestamp(), currentTime,
310 ),
311 ))
312 }
313
314 if incentiveResolver.Refunded() {
315 panic(makeErrorWithDetails(
316 errCannotCancelIncentive,
317 ufmt.Sprintf( "incentive(%s) has already been refunded" , incentiveResolver.IncentiveId()),
318 ))
319 }
320
321 // Defensive: a not-yet-started incentive can never have distributed a reward
322 // or accrued a warmup penalty. Removing a record that carries either would
323 // strand those tokens in the staker, so refuse instead of dropping it.
324 if incentiveResolver.DistributedRewardAmount() != 0 || incentiveResolver.AccumulatedPenaltyAmount() != 0 {
325 panic(makeErrorWithDetails(
326 errCannotCancelIncentive,
327 ufmt.Sprintf(
328 "incentive(%s) has already accrued rewards: distributed(%d), penalty(%d)" ,
329 incentiveResolver.IncentiveId(),
330 incentiveResolver.DistributedRewardAmount(),
331 incentiveResolver.AccumulatedPenaltyAmount(),
332 ),
333 ))
334 }
335 }
336
337 // getMinimumIncentiveStartTimestamp returns after 24 hours from now.
338 func getMinimumIncentiveStartTimestamp() int64 {
339 return time.Now().Add( 24 * time.Hour).Unix()
340 }
341
342 // getMaximumIncentiveStartTimestamp returns 7 days after the first midnight that satisfies
343 // the minimum 24-hour delay.
344 func getMaximumIncentiveStartTimestamp() int64 {
345 minimumStartTimestamp := getMinimumIncentiveStartTimestamp()
346 firstEligibleMidnight := time.Unix(minimumStartTimestamp, 0 ).Truncate( 24 * time.Hour)
347 if firstEligibleMidnight.Unix() < minimumStartTimestamp {
348 firstEligibleMidnight = firstEligibleMidnight.Add( 24 * time.Hour)
349 }
350
351 return firstEligibleMidnight.Add(maxIncentiveStartDelay).Unix()
352 }
353
354 // assertIsValidIncentiveEndTime ensures the end timestamp is within valid epoch range.
355 func assertIsValidIncentiveEndTime(endTimestamp int64 ) {
356 if endTimestamp >= MAX_UNIX_EPOCH_TIME {
357 panic(makeErrorWithDetails(
358 errInvalidInput,
359 ufmt.Sprintf( "endTimestamp(%d) cannot be later than 253402300799 (9999-12-31 23:59:59)" , endTimestamp),
360 ))
361 }
362 }
363
364 // assertIsValidIncentiveDuration ensures the duration is 90, 180, or 365 days.
365 func assertIsValidIncentiveDuration(externalDuration int64 ) {
366 switch externalDuration {
367 case TIMESTAMP_90DAYS, TIMESTAMP_180DAYS, TIMESTAMP_365DAYS:
368 return
369 }
370
371 panic(makeErrorWithDetails(
372 errInvalidIncentiveDuration,
373 ufmt.Sprintf( "externalDuration(%d) must be 90, 180, 365 days" , externalDuration),
374 ))
375 }
376
377 // AssertIsValidAddress panics if the provided address is invalid.
378 func assertIsValidAddress(addr address) {
379 if addr == "" || !addr.IsValid() {
380 panic(makeErrorWithDetails(
381 errInvalidAddress,
382 ufmt.Sprintf( "address(%s) is invalid" , addr.String()),
383 ))
384 }
385 }
386
387 // isMidnight checks if a time represents midnight (00:00:00).
388 func isMidnight(startTime time.Time) bool {
389 hour := startTime.Hour()
390 minute := startTime.Minute()
391 second := startTime.Second()
392
393 return hour == 0 && minute == 0 && second == 0
394 }
395
396 // assertIsPositionOwner validates that the caller has permission to operate the token.
397 func assertIsPositionOwner(owner, caller address) {
398 if owner != caller {
399 panic(makeErrorWithDetails(
400 errNoPermission,
401 ufmt.Sprintf( "caller(%s) is not owner of positionId(%s)" , caller, owner),
402 ))
403 }
404 }
405
*gno.land/r/gnoswap/staker/v1.stakerV1