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}