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