Search Apps Documentation Source Content File Folder Download Copy Actions Download

assert.gno

6.23 Kb · 217 lines
  1package v1
  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// assertIsGreaterThanMinimumRewardAmount ensures the reward amount meets minimum requirements.
 89func assertIsGreaterThanMinimumRewardAmount(s *stakerV1, rewardToken string, rewardAmount int64) {
 90	minReward := s.getMinimumRewardAmount()
 91
 92	if minRewardInt64, found := s.store.GetTokenSpecificMinimumRewards()[rewardToken]; found {
 93		minReward = minRewardInt64
 94	}
 95
 96	if rewardAmount < minReward {
 97		panic(makeErrorWithDetails(
 98			errInvalidInput,
 99			ufmt.Sprintf("rewardAmount(%d) is less than minimum required amount(%d)", rewardAmount, minReward),
100		))
101	}
102}
103
104// assertIsAllowedForExternalReward ensures the token is allowed for external rewards.
105func assertIsAllowedForExternalReward(s *stakerV1, poolPath, tokenPath string) {
106	token0, token1, _ := poolPathDivide(poolPath)
107
108	if tokenPath == token0 || tokenPath == token1 {
109		return
110	}
111
112	allowed := contains(s.store.GetAllowedTokens(), tokenPath)
113	if allowed {
114		return
115	}
116
117	panic(makeErrorWithDetails(
118		errNotAllowedForExternalReward,
119		ufmt.Sprintf("tokenPath(%s) is not allowed for external reward for poolPath(%s)", tokenPath, poolPath),
120	))
121}
122
123const maxUnstakingFee = uint64(1000) // 10%
124
125// assertIsValidFeeRate ensures the fee rate is within valid range (0-1000 basis points).
126func assertIsValidFeeRate(fee uint64) {
127	if fee > maxUnstakingFee {
128		panic(makeErrorWithDetails(
129			errInvalidUnstakingFee,
130			ufmt.Sprintf("fee(%d) must be in range 0 ~ %d", fee, maxUnstakingFee),
131		))
132	}
133}
134
135// assertIsValidIncentiveStartTime ensures the incentive starts at midnight of a future date.
136func assertIsValidIncentiveStartTime(startTimestamp int64) {
137	// must be in seconds format, not milliseconds
138	// REF: https://stackoverflow.com/a/23982005
139	numStr := strconv.Itoa(int(startTimestamp))
140
141	if len(numStr) >= 13 {
142		panic(makeErrorWithDetails(
143			errInvalidIncentiveStartTime,
144			ufmt.Sprintf("startTimestamp(%d) must be in seconds format, not milliseconds", startTimestamp),
145		))
146	}
147
148	// must be at least +1 day midnight
149	tomorrowMidnight := time.Now().AddDate(0, 0, 1).Truncate(24 * time.Hour).Unix()
150	if startTimestamp < tomorrowMidnight {
151		panic(makeErrorWithDetails(
152			errInvalidIncentiveStartTime,
153			ufmt.Sprintf("startTimestamp(%d) must be at least +1 day midnight(%d)", startTimestamp, tomorrowMidnight),
154		))
155	}
156
157	// must be midnight of the day
158	startTime := time.Unix(startTimestamp, 0)
159	if !isMidnight(startTime) {
160		panic(makeErrorWithDetails(
161			errInvalidIncentiveStartTime,
162			ufmt.Sprintf("startTime(%d = %s) must be midnight of the day", startTimestamp, startTime.String()),
163		))
164	}
165}
166
167// assertIsValidIncentiveEndTime ensures the end timestamp is within valid epoch range.
168func assertIsValidIncentiveEndTime(endTimestamp int64) {
169	if endTimestamp >= MAX_UNIX_EPOCH_TIME {
170		panic(makeErrorWithDetails(
171			errInvalidInput,
172			ufmt.Sprintf("endTimestamp(%d) cannot be later than 253402300799 (9999-12-31 23:59:59)", endTimestamp),
173		))
174	}
175}
176
177// assertIsValidIncentiveDuration ensures the duration is 90, 180, or 365 days.
178func assertIsValidIncentiveDuration(externalDuration int64) {
179	switch externalDuration {
180	case TIMESTAMP_90DAYS, TIMESTAMP_180DAYS, TIMESTAMP_365DAYS:
181		return
182	}
183
184	panic(makeErrorWithDetails(
185		errInvalidIncentiveDuration,
186		ufmt.Sprintf("externalDuration(%d) must be 90, 180, 365 days", externalDuration),
187	))
188}
189
190// AssertIsValidAddress panics if the provided address is invalid.
191func assertIsValidAddress(addr address) {
192	if addr == "" || !addr.IsValid() {
193		panic(makeErrorWithDetails(
194			errInvalidAddress,
195			ufmt.Sprintf("address(%s) is invalid", addr.String()),
196		))
197	}
198}
199
200// isMidnight checks if a time represents midnight (00:00:00).
201func isMidnight(startTime time.Time) bool {
202	hour := startTime.Hour()
203	minute := startTime.Minute()
204	second := startTime.Second()
205
206	return hour == 0 && minute == 0 && second == 0
207}
208
209// assertIsPositionOwner validates that the caller has permission to operate the token.
210func assertIsPositionOwner(owner, caller address) {
211	if owner != caller {
212		panic(makeErrorWithDetails(
213			errNoPermission,
214			ufmt.Sprintf("caller(%s) is not owner of positionId(%s)", caller, owner),
215		))
216	}
217}