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

assert.gno

6.51 Kb · 224 lines
  1package staker
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"time"
  7
  8	ufmt "gno.land/p/nt/ufmt/v0"
  9)
 10
 11const (
 12	TIMESTAMP_90DAYS  = int64(7776000)
 13	TIMESTAMP_180DAYS = int64(15552000)
 14	TIMESTAMP_365DAYS = int64(31536000)
 15
 16	MAX_UNIX_EPOCH_TIME = 253402300799 // 9999-12-31 23:59:59
 17)
 18
 19// assertIsValidAmount ensures the amount is non-negative.
 20func assertIsValidAmount(amount int64) {
 21	if amount < 0 {
 22		panic(makeErrorWithDetails(
 23			errInvalidInput,
 24			ufmt.Sprintf("amount(%d) must be positive", amount),
 25		))
 26	}
 27}
 28
 29// assertIsValidRewardAmountFormat ensures the reward amount string is formatted as "tokenPath:amount".
 30func assertIsValidRewardAmountFormat(rewardAmountStr string) {
 31	parts := strings.SplitN(rewardAmountStr, ":", 2)
 32	if len(parts) != 2 {
 33		panic(makeErrorWithDetails(
 34			errInvalidInput,
 35			ufmt.Sprintf("invalid format for SetTokenMinimumRewardAmount params: expected 'tokenPath:amount', got '%s'", rewardAmountStr),
 36		))
 37	}
 38}
 39
 40// assertIsDepositor ensures the caller is the owner of the deposit.
 41func assertIsDepositor(s *stakerV1, caller address, positionId uint64) {
 42	deposit := s.getDeposits().get(positionId)
 43	if deposit == nil {
 44		panic(makeErrorWithDetails(
 45			errDataNotFound,
 46			ufmt.Sprintf("positionId(%d) not found", positionId),
 47		))
 48	}
 49
 50	if caller != deposit.Owner() {
 51		panic(makeErrorWithDetails(
 52			errNoPermission,
 53			ufmt.Sprintf("caller(%s) is not depositor(%s)", caller.String(), deposit.Owner().String()),
 54		))
 55	}
 56}
 57
 58// assertIsNotStaked ensures the position is not already staked.
 59func assertIsNotStaked(s *stakerV1, positionId uint64) {
 60	if s.getDeposits().Has(positionId) {
 61		panic(makeErrorWithDetails(
 62			errAlreadyStaked,
 63			ufmt.Sprintf("positionId(%d) already staked", positionId),
 64		))
 65	}
 66}
 67
 68// assertIsPoolExists ensures the pool exists.
 69func assertIsPoolExists(s *stakerV1, poolPath string) {
 70	if !s.poolAccessor.ExistsPoolPath(poolPath) {
 71		panic(makeErrorWithDetails(
 72			errInvalidPoolPath,
 73			ufmt.Sprintf("pool(%s) does not exist", poolPath),
 74		))
 75	}
 76}
 77
 78// assertIsValidPoolTier ensures the tier is within valid range.
 79func assertIsValidPoolTier(tier uint64) {
 80	if tier >= AllTierCount {
 81		panic(makeErrorWithDetails(
 82			errInvalidPoolTier,
 83			ufmt.Sprintf("tier(%d) must be less than %d", tier, AllTierCount),
 84		))
 85	}
 86}
 87
 88// assertTier1HasSparePool ensures tier 1 keeps at least one pool after a tier change.
 89func assertTier1HasSparePool(currentTier, tier1Count uint64) {
 90	if currentTier == Tier1 && tier1Count == 1 {
 91		panic(makeErrorWithDetails(errInvalidPoolTier, "tier 1 must have at least one pool"))
 92	}
 93}
 94
 95// assertIsGreaterThanMinimumRewardAmount ensures the reward amount meets minimum requirements.
 96func assertIsGreaterThanMinimumRewardAmount(s *stakerV1, rewardToken string, rewardAmount int64) {
 97	minReward := s.getMinimumRewardAmount()
 98
 99	if minRewardInt64, found := s.store.GetTokenSpecificMinimumRewards()[rewardToken]; found {
100		minReward = minRewardInt64
101	}
102
103	if rewardAmount < minReward {
104		panic(makeErrorWithDetails(
105			errInvalidInput,
106			ufmt.Sprintf("rewardAmount(%d) is less than minimum required amount(%d)", rewardAmount, minReward),
107		))
108	}
109}
110
111// assertIsAllowedForExternalReward ensures the token is allowed for external rewards.
112func assertIsAllowedForExternalReward(s *stakerV1, poolPath, tokenPath string) {
113	token0, token1, _ := poolPathDivide(poolPath)
114
115	if tokenPath == token0 || tokenPath == token1 {
116		return
117	}
118
119	allowed := contains(s.store.GetAllowedTokens(), tokenPath)
120	if allowed {
121		return
122	}
123
124	panic(makeErrorWithDetails(
125		errNotAllowedForExternalReward,
126		ufmt.Sprintf("tokenPath(%s) is not allowed for external reward for poolPath(%s)", tokenPath, poolPath),
127	))
128}
129
130const maxUnstakingFee = uint64(1000) // 10%
131
132// assertIsValidFeeRate ensures the fee rate is within valid range (0-1000 basis points).
133func assertIsValidFeeRate(fee uint64) {
134	if fee > maxUnstakingFee {
135		panic(makeErrorWithDetails(
136			errInvalidUnstakingFee,
137			ufmt.Sprintf("fee(%d) must be in range 0 ~ %d", fee, maxUnstakingFee),
138		))
139	}
140}
141
142// assertIsValidIncentiveStartTime ensures the incentive starts at midnight of a future date.
143func assertIsValidIncentiveStartTime(startTimestamp int64) {
144	// must be in seconds format, not milliseconds
145	// REF: https://stackoverflow.com/a/23982005
146	numStr := strconv.Itoa(int(startTimestamp))
147
148	if len(numStr) >= 13 {
149		panic(makeErrorWithDetails(
150			errInvalidIncentiveStartTime,
151			ufmt.Sprintf("startTimestamp(%d) must be in seconds format, not milliseconds", startTimestamp),
152		))
153	}
154
155	// must be at least +1 day midnight
156	tomorrowMidnight := time.Now().AddDate(0, 0, 1).Truncate(24 * time.Hour).Unix()
157	if startTimestamp < tomorrowMidnight {
158		panic(makeErrorWithDetails(
159			errInvalidIncentiveStartTime,
160			ufmt.Sprintf("startTimestamp(%d) must be at least +1 day midnight(%d)", startTimestamp, tomorrowMidnight),
161		))
162	}
163
164	// must be midnight of the day
165	startTime := time.Unix(startTimestamp, 0)
166	if !isMidnight(startTime) {
167		panic(makeErrorWithDetails(
168			errInvalidIncentiveStartTime,
169			ufmt.Sprintf("startTime(%d = %s) must be midnight of the day", startTimestamp, startTime.String()),
170		))
171	}
172}
173
174// assertIsValidIncentiveEndTime ensures the end timestamp is within valid epoch range.
175func assertIsValidIncentiveEndTime(endTimestamp int64) {
176	if endTimestamp >= MAX_UNIX_EPOCH_TIME {
177		panic(makeErrorWithDetails(
178			errInvalidInput,
179			ufmt.Sprintf("endTimestamp(%d) cannot be later than 253402300799 (9999-12-31 23:59:59)", endTimestamp),
180		))
181	}
182}
183
184// assertIsValidIncentiveDuration ensures the duration is 90, 180, or 365 days.
185func assertIsValidIncentiveDuration(externalDuration int64) {
186	switch externalDuration {
187	case TIMESTAMP_90DAYS, TIMESTAMP_180DAYS, TIMESTAMP_365DAYS:
188		return
189	}
190
191	panic(makeErrorWithDetails(
192		errInvalidIncentiveDuration,
193		ufmt.Sprintf("externalDuration(%d) must be 90, 180, 365 days", externalDuration),
194	))
195}
196
197// AssertIsValidAddress panics if the provided address is invalid.
198func assertIsValidAddress(addr address) {
199	if addr == "" || !addr.IsValid() {
200		panic(makeErrorWithDetails(
201			errInvalidAddress,
202			ufmt.Sprintf("address(%s) is invalid", addr.String()),
203		))
204	}
205}
206
207// isMidnight checks if a time represents midnight (00:00:00).
208func isMidnight(startTime time.Time) bool {
209	hour := startTime.Hour()
210	minute := startTime.Minute()
211	second := startTime.Second()
212
213	return hour == 0 && minute == 0 && second == 0
214}
215
216// assertIsPositionOwner validates that the caller has permission to operate the token.
217func assertIsPositionOwner(owner, caller address) {
218	if owner != caller {
219		panic(makeErrorWithDetails(
220			errNoPermission,
221			ufmt.Sprintf("caller(%s) is not owner of positionId(%s)", caller, owner),
222		))
223	}
224}