PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoswap/staker/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
gnoswap / staker
Files
28 (README)(gnomod.toml)
Exported functions
14
Module
gno.land/r/gnoswap/staker/v1
gno
0.9

Files (28)

  • README.mdmarkdown
  • gnomod.tomltoml
  • assert.gnogno
  • calculate_pool_position_reward.gnogno
  • consts.gnogno
  • doc.gnogno
  • emits.gnogno
  • errors.gnogno
  • event_info.gnogno
  • external_deposit_fee.gnogno
  • external_incentive.gnogno
  • external_token_list.gnogno
  • getter_utils.gnogno
  • getter.gnogno
  • init.gnogno
  • instance.gnogno
  • manage_pool_tier_and_warmup.gnogno
  • protocol_fee_unstaking.gnogno
  • render.gnogno
  • reward_calculation_incentives.gnogno
  • reward_calculation_pool_tier.gnogno
  • reward_calculation_pool.gnogno
  • reward_calculation_tick.gnogno
  • reward_calculation_warmup.gnogno
  • staker.gnogno
  • type.gnogno
  • unstaked_position.gnogno
  • utils.gnogno
  • assert.gnogno
    1package staker23import (4	"math"5	"strconv"6	"strings"7	"time"8

    Functions

    • 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.

    Rendered

    RenderedRawgnoweb ↗
    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
    15const (
    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.
    28func 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".
    38func 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.
    49func 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.
    69func 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.
    87func 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.
    98func 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.
    109func 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.
    119func 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.
    129func 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.
    139func 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.
    146func 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.
    162func 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.
    193func 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).
    227func 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.
    238func 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.
    280func 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.
    303func 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.
    338func 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.
    344func 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.
    355func 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.
    365func 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.
    378func 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).
    388func 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.
    397func 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