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}