-
Notifications
You must be signed in to change notification settings - Fork 202
/
utils.go
309 lines (260 loc) · 8.72 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package utils
import (
"encoding/hex"
"errors"
"fmt"
"sort"
"strconv"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/cosmos/cosmos-sdk/types/module"
errorsmod "cosmossdk.io/errors"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
config "github.com/Stride-Labs/stride/v20/cmd/strided/config"
icacallbacktypes "github.com/Stride-Labs/stride/v20/x/icacallbacks/types"
recordstypes "github.com/Stride-Labs/stride/v20/x/records/types"
)
func FilterDepositRecords(arr []recordstypes.DepositRecord, condition func(recordstypes.DepositRecord) bool) (ret []recordstypes.DepositRecord) {
for _, elem := range arr {
if condition(elem) {
ret = append(ret, elem)
}
}
return ret
}
func Int64ToCoinString(amount int64, denom string) string {
return strconv.FormatInt(amount, 10) + denom
}
func ValidateAdminAddress(address string) error {
if !Admins[address] {
return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, fmt.Sprintf("address (%s) is not an admin", address))
}
return nil
}
func Min(a int, b int) int {
if a < b {
return a
}
return b
}
func StringMapKeys[V any](m map[string]V) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
func Int32MapKeys[V any](m map[int32]V) []int32 {
keys := make([]int32, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
return keys
}
//============================== ADDRESS VERIFICATION UTILS ================================
// ref: https://github.com/cosmos/cosmos-sdk/blob/b75c2ebcfab1a6b535723f1ac2889a2fc2509520/types/address.go#L177
var errBech32EmptyAddress = errors.New("decoding Bech32 address failed: must provide a non empty address")
// GetFromBech32 decodes a bytestring from a Bech32 encoded string.
func GetFromBech32(bech32str, prefix string) ([]byte, error) {
if len(bech32str) == 0 {
return nil, errBech32EmptyAddress
}
hrp, bz, err := bech32.DecodeAndConvert(bech32str)
if err != nil {
return nil, err
}
if hrp != prefix {
return nil, fmt.Errorf("invalid Bech32 prefix; expected %s, got %s", prefix, hrp)
}
return bz, nil
}
// VerifyAddressFormat verifies that the provided bytes form a valid address
// according to the default address rules or a custom address verifier set by
// GetConfig().SetAddressVerifier().
// TODO make an issue to get rid of global Config
// ref: https://github.com/cosmos/cosmos-sdk/issues/9690
func VerifyAddressFormat(bz []byte) error {
verifier := func(bz []byte) error {
n := len(bz)
// Base accounts are length 20, module/ICA accounts are length 32
if n == 20 || n == 32 {
return nil
}
return fmt.Errorf("incorrect address length %d", n)
}
if verifier(bz) != nil {
return verifier(bz)
}
if len(bz) == 0 {
return errorsmod.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty")
}
if len(bz) > address.MaxAddrLen {
return errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bz))
}
return nil
}
// AccAddress a wrapper around bytes meant to represent an account address.
// When marshaled to a string or JSON, it uses Bech32.
type AccAddress []byte
// AccAddressFromBech32 creates an AccAddress from a Bech32 string.
func AccAddressFromBech32(address string, bech32prefix string) (addr AccAddress, err error) {
if len(strings.TrimSpace(address)) == 0 {
return AccAddress{}, errors.New("empty address string is not allowed")
}
bz, err := GetFromBech32(address, bech32prefix)
if err != nil {
return nil, err
}
err = VerifyAddressFormat(bz)
if err != nil {
return nil, err
}
return AccAddress(bz), nil
}
// ============================== AIRDROP UTILS ================================
// max64 returns the maximum of its inputs.
func Max64(i, j int64) int64 {
if i > j {
return i
}
return j
}
// Min64 returns the minimum of its inputs.
func Min64(i, j int64) int64 {
if i < j {
return i
}
return j
}
// Compute coin amount for specific period using linear vesting calculation algorithm.
func GetVestedCoinsAt(vAt int64, vStart int64, vLength int64, vCoins sdk.Coins) sdk.Coins {
var vestedCoins sdk.Coins
if vAt < 0 || vStart < 0 || vLength < 0 {
return sdk.Coins{}
}
vEnd := vStart + vLength
if vAt <= vStart {
return sdk.Coins{}
} else if vAt >= vEnd {
return vCoins
}
// calculate the vesting scalar
portion := sdk.NewDec(vAt - vStart).Quo(sdk.NewDec(vLength))
for _, ovc := range vCoins {
vestedAmt := sdk.NewDec(ovc.Amount.Int64()).Mul(portion).RoundInt()
vestedCoins = append(vestedCoins, sdk.NewCoin(ovc.Denom, vestedAmt))
}
return vestedCoins
}
// check string array inclusion
func ContainsString(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// Convert any bech32 to stride address
func ConvertAddressToStrideAddress(address string) string {
_, bz, err := bech32.DecodeAndConvert(address)
if err != nil {
return ""
}
bech32Addr, err := bech32.ConvertAndEncode(config.Bech32PrefixAccAddr, bz)
if err != nil {
return ""
}
return bech32Addr
}
// Returns a log string with a chainId and tab as the prefix
// Ex:
//
// | COSMOSHUB-4 | string
func LogWithHostZone(chainId string, s string, a ...any) string {
msg := fmt.Sprintf(s, a...)
return fmt.Sprintf("| %-13s | %s", strings.ToUpper(chainId), msg)
}
// Returns a log string with a chain Id and callback as a prefix
// callbackType is either ICACALLBACK or ICQCALLBACK
// Format:
//
// | CHAIN-ID | {CALLBACK_ID} {CALLBACK_TYPE} | string
func logCallbackWithHostZone(chainId string, callbackId string, callbackType string, s string, a ...any) string {
msg := fmt.Sprintf(s, a...)
return fmt.Sprintf("| %-13s | %s %s | %s", strings.ToUpper(chainId), strings.ToUpper(callbackId), callbackType, msg)
}
// Returns a log string with a chain Id and icacallback as a prefix
// Ex:
//
// | COSMOSHUB-4 | DELEGATE ICACALLBACK | string
func LogICACallbackWithHostZone(chainId string, callbackId string, s string, a ...any) string {
return logCallbackWithHostZone(chainId, callbackId, "ICACALLBACK", s, a...)
}
// Returns a log string with a chain Id and icacallback as a prefix, and status of the callback
// Ex:
//
// | COSMOSHUB-4 | DELEGATE ICACALLBACK | ICA SUCCESS, Packet: ...
func LogICACallbackStatusWithHostZone(chainId string, callbackId string, status icacallbacktypes.AckResponseStatus, packet channeltypes.Packet) string {
var statusMsg string
switch status {
case icacallbacktypes.AckResponseStatus_SUCCESS:
statusMsg = "ICA SUCCESSFUL"
case icacallbacktypes.AckResponseStatus_TIMEOUT:
statusMsg = "ICA TIMEOUT"
default:
statusMsg = "ICA FAILED (ack error)"
}
return logCallbackWithHostZone(chainId, callbackId, "ICACALLBACK", "%s, Packet: %+v", statusMsg, packet)
}
// Returns a log string with a chain Id and icqcallback as a prefix
// Ex:
//
// | COSMOSHUB-4 | WITHDRAWALHOSTBALANCE ICQCALLBACK | string
func LogICQCallbackWithHostZone(chainId string, callbackId string, s string, a ...any) string {
return logCallbackWithHostZone(chainId, callbackId, "ICQCALLBACK", s, a...)
}
// Returns a log header string with a dash padding on either side
// Ex:
//
// ------------------------------ string ------------------------------
func LogHeader(s string, a ...any) string {
lineLength := 120
header := fmt.Sprintf(s, a...)
pad := strings.Repeat("-", (lineLength-len(header))/2)
return fmt.Sprintf("%s %s %s", pad, header, pad)
}
// Logs a module's migration info
func LogModuleMigration(ctx sdk.Context, versionMap module.VersionMap, moduleName string) {
currentVersion := versionMap[moduleName]
ctx.Logger().Info(fmt.Sprintf("migrating module %s from version %d to version %d", moduleName, currentVersion-1, currentVersion))
}
// isIBCToken checks if the token came from the IBC module
// Each IBC token starts with an ibc/ denom, the check is rather simple
func IsIBCToken(denom string) bool {
return strings.HasPrefix(denom, "ibc/")
}
// Returns the stDenom from a native denom by appending a st prefix
func StAssetDenomFromHostZoneDenom(hostZoneDenom string) string {
return "st" + hostZoneDenom
}
// Returns the native denom from an stDenom by removing the st prefix
func HostZoneDenomFromStAssetDenom(stAssetDenom string) string {
return stAssetDenom[2:]
}
// Verifies a tx hash is valid
func VerifyTxHash(txHash string) (err error) {
if txHash == "" {
return errorsmod.Wrapf(sdkerrors.ErrTxDecode, "tx hash is empty")
}
_, err = hex.DecodeString(txHash)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrTxDecode, "tx hash is invalid %s", txHash)
}
return nil
}