github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/types/coin_ibc.go (about) 1 package types 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "regexp" 7 "strings" 8 9 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 10 ) 11 12 var ( 13 // Denominations can be 3 ~ 128 characters long and support letters, followed by either 14 // a letter, a number or a separator ('/'). 15 ibcReDnmString = `[a-zA-Z][a-zA-Z0-9/-]{2,127}` 16 ibcReDecAmt = `[[:digit:]]+(?:\.[[:digit:]]+)?|\.[[:digit:]]+` 17 ibcReSpc = `[[:space:]]*` 18 ibcReDnm *regexp.Regexp 19 ibcReDecCoin *regexp.Regexp 20 ) 21 var ibcCoinDenomRegex = DefaultCoinDenomRegex 22 23 func init() { 24 SetIBCCoinDenomRegex(DefaultIBCCoinDenomRegex) 25 } 26 27 func IBCParseDecCoin(coinStr string) (coin DecCoin, err error) { 28 coinStr = strings.TrimSpace(coinStr) 29 30 matches := ibcReDecCoin.FindStringSubmatch(coinStr) 31 if matches == nil { 32 return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr) 33 } 34 35 amountStr, denomStr := matches[1], matches[2] 36 37 amount, err := NewDecFromStr(amountStr) 38 if err != nil { 39 return DecCoin{}, errors.Wrap(err, fmt.Sprintf("failed to parse decimal coin amount: %s", amountStr)) 40 } 41 42 if err := ValidateDenom(denomStr); err != nil { 43 return DecCoin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %s", err) 44 } 45 46 return NewDecCoinFromDec(denomStr, amount), nil 47 } 48 49 // DefaultCoinDenomRegex returns the default regex string 50 func DefaultIBCCoinDenomRegex() string { 51 return ibcReDnmString 52 } 53 54 func SetIBCCoinDenomRegex(reFn func() string) { 55 ibcCoinDenomRegex = reFn 56 57 ibcReDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, ibcCoinDenomRegex())) 58 ibcReDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, ibcReDecAmt, ibcReSpc, ibcCoinDenomRegex())) 59 } 60 61 type CoinAdapters []CoinAdapter 62 63 // NewCoin returns a new coin with a denomination and amount. It will panic if 64 // the amount is negative or if the denomination is invalid. 65 func NewCoinAdapter(denom string, amount Int) CoinAdapter { 66 coin := CoinAdapter{ 67 Denom: denom, 68 Amount: amount, 69 } 70 71 if err := coin.Validate(); err != nil { 72 panic(err) 73 } 74 75 return coin 76 } 77 func (cs CoinAdapter) ToCoin() Coin { 78 if cs.Denom == DefaultIbcWei { 79 cs.Denom = DefaultBondDenom 80 } 81 return CoinAdapterToCoin(cs) 82 } 83 func (cs CoinAdapters) ToCoins() Coins { 84 ret := make([]Coin, 0) 85 for _, v := range cs { 86 ret = append(ret, v.ToCoin()) 87 } 88 return ret 89 } 90 91 func (cas CoinAdapters) IsAnyNegative() bool { 92 for _, coin := range cas { 93 if coin.Amount.IsNegative() { 94 return true 95 } 96 } 97 98 return false 99 } 100 101 func (cas CoinAdapters) IsAnyNil() bool { 102 for _, coin := range cas { 103 if coin.Amount.IsNil() { 104 return true 105 } 106 } 107 108 return false 109 } 110 111 func (coins CoinAdapters) Add(coinsB ...CoinAdapter) CoinAdapters { 112 return coins.safeAdd(coinsB) 113 } 114 func (coins CoinAdapters) safeAdd(coinsB CoinAdapters) CoinAdapters { 115 // probably the best way will be to make Coins and interface and hide the structure 116 // definition (type alias) 117 if !coins.isSorted() { 118 panic("Coins (self) must be sorted") 119 } 120 if !coinsB.isSorted() { 121 panic("Wrong argument: coins must be sorted") 122 } 123 124 sum := ([]CoinAdapter)(nil) 125 indexA, indexB := 0, 0 126 lenA, lenB := len(coins), len(coinsB) 127 128 for { 129 if indexA == lenA { 130 if indexB == lenB { 131 // return nil coins if both sets are empty 132 return sum 133 } 134 135 // return set B (excluding zero coins) if set A is empty 136 return append(sum, removeZeroCoinAdapters(coinsB[indexB:])...) 137 } else if indexB == lenB { 138 // return set A (excluding zero coins) if set B is empty 139 return append(sum, removeZeroCoinAdapters(coins[indexA:])...) 140 } 141 142 coinA, coinB := coins[indexA], coinsB[indexB] 143 144 switch strings.Compare(coinA.Denom, coinB.Denom) { 145 case -1: // coin A denom < coin B denom 146 if !coinA.IsZero() { 147 sum = append(sum, coinA) 148 } 149 150 indexA++ 151 152 case 0: // coin A denom == coin B denom 153 res := coinA.Add(coinB) 154 if !res.IsZero() { 155 sum = append(sum, res) 156 } 157 158 indexA++ 159 indexB++ 160 161 case 1: // coin A denom > coin B denom 162 if !coinB.IsZero() { 163 sum = append(sum, coinB) 164 } 165 166 indexB++ 167 } 168 } 169 } 170 171 // ParseCoinsNormalized will parse out a list of coins separated by commas, and normalize them by converting to smallest 172 // unit. If the parsing is successuful, the provided coins will be sanitized by removing zero coins and sorting the coin 173 // set. Lastly a validation of the coin set is executed. If the check passes, ParseCoinsNormalized will return the 174 // sanitized coins. 175 // Otherwise it will return an error. 176 // If an empty string is provided to ParseCoinsNormalized, it returns nil Coins. 177 // ParseCoinsNormalized supports decimal coins as inputs, and truncate them to int after converted to smallest unit. 178 // Expected format: "{amount0}{denomination},...,{amountN}{denominationN}" 179 func ParseCoinsNormalized(coinStr string) (Coins, error) { 180 coins, err := ParseDecCoins(coinStr) 181 if err != nil { 182 return Coins{}, err 183 } 184 return NormalizeCoins(coins), nil 185 } 186 187 // ParseCoinNormalized parses and normalize a cli input for one coin type, returning errors if invalid or on an empty string 188 // as well. 189 // Expected format: "{amount}{denomination}" 190 func ParseCoinNormalized(coinStr string) (coin Coin, err error) { 191 decCoin, err := ParseDecCoin(coinStr) 192 if err != nil { 193 return Coin{}, err 194 } 195 196 coin, _ = NormalizeDecCoin(decCoin).TruncateDecimal() 197 return coin, nil 198 } 199 200 // IsValid calls Validate and returns true when the Coins are sorted, have positive amount, with a 201 // valid and unique denomination (i.e no duplicates). 202 func (coins CoinAdapters) IsValid() bool { 203 return coins.Validate() == nil 204 } 205 206 func (coins CoinAdapters) Validate() error { 207 switch len(coins) { 208 case 0: 209 return nil 210 211 case 1: 212 if err := ValidateDenom(coins[0].Denom); err != nil { 213 return err 214 } 215 if coins[0].Amount.IsNil() { 216 return fmt.Errorf("coin %s amount is nil", coins[0]) 217 } 218 if !coins[0].IsPositive() { 219 return fmt.Errorf("coin %s amount is not positive", coins[0]) 220 } 221 return nil 222 223 default: 224 // check single coin case 225 if err := (CoinAdapters{coins[0]}).Validate(); err != nil { 226 return err 227 } 228 229 lowDenom := coins[0].Denom 230 seenDenoms := make(map[string]bool) 231 seenDenoms[lowDenom] = true 232 233 for _, coin := range coins[1:] { 234 if seenDenoms[coin.Denom] { 235 return fmt.Errorf("duplicate denomination %s", coin.Denom) 236 } 237 if err := ValidateDenom(coin.Denom); err != nil { 238 return err 239 } 240 if coin.Denom <= lowDenom { 241 return fmt.Errorf("denomination %s is not sorted", coin.Denom) 242 } 243 if !coin.IsPositive() { 244 return fmt.Errorf("coin %s amount is not positive", coin.Denom) 245 } 246 247 // we compare each coin against the last denom 248 lowDenom = coin.Denom 249 seenDenoms[coin.Denom] = true 250 } 251 252 return nil 253 } 254 } 255 256 func (coins CoinAdapters) isSorted() bool { 257 for i := 1; i < len(coins); i++ { 258 if coins[i-1].Denom > coins[i].Denom { 259 return false 260 } 261 } 262 return true 263 } 264 265 func (coins CoinAdapters) String() string { 266 if len(coins) == 0 { 267 return "" 268 } 269 270 out := "" 271 for _, coin := range coins { 272 out += fmt.Sprintf("%v,", coin.String()) 273 } 274 return out[:len(out)-1] 275 } 276 277 // IsAllPositive returns true if there is at least one coin and all currencies 278 // have a positive value. 279 func (coins CoinAdapters) IsAllPositive() bool { 280 if len(coins) == 0 { 281 return false 282 } 283 284 for _, coin := range coins { 285 if !coin.IsPositive() { 286 return false 287 } 288 } 289 290 return true 291 } 292 293 type coinAdaptersJSON CoinAdapters 294 295 // MarshalJSON implements a custom JSON marshaller for the Coins type to allow 296 // nil Coins to be encoded as an empty array. 297 func (coins CoinAdapters) MarshalJSON() ([]byte, error) { 298 if coins == nil { 299 return json.Marshal(coinAdaptersJSON(CoinAdapters{})) 300 } 301 302 return json.Marshal(coinAdaptersJSON(coins)) 303 } 304 305 func (coins CoinAdapters) Copy() CoinAdapters { 306 copyCoins := make(CoinAdapters, len(coins)) 307 308 for i, coin := range coins { 309 copyCoins[i] = coin 310 } 311 312 return copyCoins 313 } 314 315 func ConvWei2Tfibo(adapters CoinAdapters) (CoinAdapters, error) { 316 copyAdapters := adapters.Copy() 317 for index, _ := range copyAdapters { 318 if copyAdapters[index].Denom == DefaultIbcWei { 319 copyAdapters[index].Denom = DefaultBondDenom 320 } else if strings.ToLower(copyAdapters[index].Denom) == DefaultBondDenom { 321 return nil, errors.Wrap(errors.ErrInvalidCoins, "not support fibo denom") 322 } 323 } 324 return copyAdapters, nil 325 } 326 327 // AmountOf returns the amount of a denom from coins 328 func (coins CoinAdapters) AmountOf(denom string) Int { 329 mustValidateDenom(denom) 330 return coins.AmountOfNoDenomValidation(denom) 331 } 332 333 // AmountOfNoDenomValidation returns the amount of a denom from coins 334 // without validating the denomination. 335 func (coins CoinAdapters) AmountOfNoDenomValidation(denom string) Int { 336 switch len(coins) { 337 case 0: 338 return ZeroInt() 339 340 case 1: 341 coin := coins[0] 342 if coin.Denom == denom { 343 return coin.Amount 344 } 345 return ZeroInt() 346 347 default: 348 // Binary search the amount of coins remaining 349 midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 350 coin := coins[midIdx] 351 switch { 352 case denom < coin.Denom: 353 return coins[:midIdx].AmountOfNoDenomValidation(denom) 354 case denom == coin.Denom: 355 return coin.Amount 356 default: 357 return coins[midIdx+1:].AmountOfNoDenomValidation(denom) 358 } 359 } 360 } 361 362 func (coins CoinAdapters) Sub(coinsB CoinAdapters) CoinAdapters { 363 diff, hasNeg := coins.SafeSub(coinsB) 364 if hasNeg { 365 panic("negative coin amount") 366 } 367 368 return diff 369 } 370 371 func (coins CoinAdapters) SafeSub(coinsB CoinAdapters) (CoinAdapters, bool) { 372 diff := coins.safeAdd(coinsB.negative()) 373 return diff, diff.IsAnyNegative() 374 } 375 func (coins CoinAdapters) negative() CoinAdapters { 376 res := make([]CoinAdapter, 0, len(coins)) 377 378 for _, coin := range coins { 379 res = append(res, CoinAdapter{ 380 Denom: coin.Denom, 381 Amount: coin.Amount.Neg(), 382 }) 383 } 384 385 return res 386 } 387 388 // AddAmount adds an amount to the Coin. 389 func (coin CoinAdapter) AddAmount(amount Int) CoinAdapter { 390 return CoinAdapter{coin.Denom, coin.Amount.Add(amount)} 391 }