github.com/Finschia/finschia-sdk@v0.48.1/types/coin.go (about) 1 package types 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "regexp" 7 "sort" 8 "strings" 9 ) 10 11 //----------------------------------------------------------------------------- 12 // Coin 13 14 // NewCoin returns a new coin with a denomination and amount. It will panic if 15 // the amount is negative or if the denomination is invalid. 16 func NewCoin(denom string, amount Int) Coin { 17 coin := Coin{ 18 Denom: denom, 19 Amount: amount, 20 } 21 22 if err := coin.Validate(); err != nil { 23 panic(err) 24 } 25 26 return coin 27 } 28 29 // NewInt64Coin returns a new coin with a denomination and amount. It will panic 30 // if the amount is negative. 31 func NewInt64Coin(denom string, amount int64) Coin { 32 return NewCoin(denom, NewInt(amount)) 33 } 34 35 // String provides a human-readable representation of a coin 36 func (coin Coin) String() string { 37 return fmt.Sprintf("%v%s", coin.Amount, coin.Denom) 38 } 39 40 // Validate returns an error if the Coin has a negative amount or if 41 // the denom is invalid. 42 func (coin Coin) Validate() error { 43 if err := ValidateDenom(coin.Denom); err != nil { 44 return err 45 } 46 47 if coin.Amount.IsNegative() { 48 return fmt.Errorf("negative coin amount: %v", coin.Amount) 49 } 50 51 return nil 52 } 53 54 // IsValid returns true if the Coin has a non-negative amount and the denom is valid. 55 func (coin Coin) IsValid() bool { 56 return coin.Validate() == nil 57 } 58 59 // IsZero returns if this represents no money 60 func (coin Coin) IsZero() bool { 61 return coin.Amount.IsZero() 62 } 63 64 // IsGTE returns true if they are the same type and the receiver is 65 // an equal or greater value 66 func (coin Coin) IsGTE(other Coin) bool { 67 if coin.Denom != other.Denom { 68 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 69 } 70 71 return !coin.Amount.LT(other.Amount) 72 } 73 74 // IsLT returns true if they are the same type and the receiver is 75 // a smaller value 76 func (coin Coin) IsLT(other Coin) bool { 77 if coin.Denom != other.Denom { 78 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 79 } 80 81 return coin.Amount.LT(other.Amount) 82 } 83 84 // IsEqual returns true if the two sets of Coins have the same value 85 func (coin Coin) IsEqual(other Coin) bool { 86 if coin.Denom != other.Denom { 87 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 88 } 89 90 return coin.Amount.Equal(other.Amount) 91 } 92 93 // Add adds amounts of two coins with same denom. If the coins differ in denom then 94 // it panics. 95 func (coin Coin) Add(coinB Coin) Coin { 96 if coin.Denom != coinB.Denom { 97 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, coinB.Denom)) 98 } 99 100 return Coin{coin.Denom, coin.Amount.Add(coinB.Amount)} 101 } 102 103 // AddAmount adds an amount to the Coin. 104 func (coin Coin) AddAmount(amount Int) Coin { 105 return Coin{coin.Denom, coin.Amount.Add(amount)} 106 } 107 108 // Sub subtracts amounts of two coins with same denom. If the coins differ in denom 109 // then it panics. 110 func (coin Coin) Sub(coinB Coin) Coin { 111 if coin.Denom != coinB.Denom { 112 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, coinB.Denom)) 113 } 114 115 res := Coin{coin.Denom, coin.Amount.Sub(coinB.Amount)} 116 if res.IsNegative() { 117 panic("negative coin amount") 118 } 119 120 return res 121 } 122 123 // SubAmount subtracts an amount from the Coin. 124 func (coin Coin) SubAmount(amount Int) Coin { 125 res := Coin{coin.Denom, coin.Amount.Sub(amount)} 126 if res.IsNegative() { 127 panic("negative coin amount") 128 } 129 130 return res 131 } 132 133 // IsPositive returns true if coin amount is positive. 134 // 135 // TODO: Remove once unsigned integers are used. 136 func (coin Coin) IsPositive() bool { 137 return coin.Amount.Sign() == 1 138 } 139 140 // IsNegative returns true if the coin amount is negative and false otherwise. 141 // 142 // TODO: Remove once unsigned integers are used. 143 func (coin Coin) IsNegative() bool { 144 return coin.Amount.Sign() == -1 145 } 146 147 // IsNil returns true if the coin amount is nil and false otherwise. 148 func (coin Coin) IsNil() bool { 149 return coin.Amount.i == nil 150 } 151 152 //----------------------------------------------------------------------------- 153 // Coins 154 155 // Coins is a set of Coin, one per currency 156 type Coins []Coin 157 158 // NewCoins constructs a new coin set. The provided coins will be sanitized by removing 159 // zero coins and sorting the coin set. A panic will occur if the coin set is not valid. 160 func NewCoins(coins ...Coin) Coins { 161 newCoins := sanitizeCoins(coins) 162 if err := newCoins.Validate(); err != nil { 163 panic(fmt.Errorf("invalid coin set %s: %w", newCoins, err)) 164 } 165 166 return newCoins 167 } 168 169 func sanitizeCoins(coins []Coin) Coins { 170 newCoins := removeZeroCoins(coins) 171 if len(newCoins) == 0 { 172 return Coins{} 173 } 174 175 return newCoins.Sort() 176 } 177 178 type coinsJSON Coins 179 180 // MarshalJSON implements a custom JSON marshaller for the Coins type to allow 181 // nil Coins to be encoded as an empty array. 182 func (coins Coins) MarshalJSON() ([]byte, error) { 183 if coins == nil { 184 return json.Marshal(coinsJSON(Coins{})) 185 } 186 187 return json.Marshal(coinsJSON(coins)) 188 } 189 190 func (coins Coins) String() string { 191 if len(coins) == 0 { 192 return "" 193 } else if len(coins) == 1 { 194 return coins[0].String() 195 } 196 197 // Build the string with a string builder 198 var out strings.Builder 199 for _, coin := range coins[:len(coins)-1] { 200 out.WriteString(coin.String()) 201 out.WriteByte(',') 202 } 203 out.WriteString(coins[len(coins)-1].String()) 204 return out.String() 205 } 206 207 // Validate checks that the Coins are sorted, have positive amount, with a valid and unique 208 // denomination (i.e no duplicates). Otherwise, it returns an error. 209 func (coins Coins) Validate() error { 210 switch len(coins) { 211 case 0: 212 return nil 213 214 case 1: 215 if err := ValidateDenom(coins[0].Denom); err != nil { 216 return err 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 := (Coins{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 Coins) 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 // IsValid calls Validate and returns true when the Coins are sorted, have positive amount, with a 266 // valid and unique denomination (i.e no duplicates). 267 func (coins Coins) IsValid() bool { 268 return coins.Validate() == nil 269 } 270 271 // Add adds two sets of coins. 272 // 273 // e.g. 274 // {2A} + {A, 2B} = {3A, 2B} 275 // {2A} + {0B} = {2A} 276 // 277 // NOTE: Add operates under the invariant that coins are sorted by 278 // denominations. 279 // 280 // CONTRACT: Add will never return Coins where one Coin has a non-positive 281 // amount. In otherwords, IsValid will always return true. 282 // The function panics if `coins` or `coinsB` are not sorted (ascending). 283 func (coins Coins) Add(coinsB ...Coin) Coins { 284 return coins.safeAdd(coinsB) 285 } 286 287 // safeAdd will perform addition of two coins sets. If both coin sets are 288 // empty, then an empty set is returned. If only a single set is empty, the 289 // other set is returned. Otherwise, the coins are compared in order of their 290 // denomination and addition only occurs when the denominations match, otherwise 291 // the coin is simply added to the sum assuming it's not zero. 292 // The function panics if `coins` or `coinsB` are not sorted (ascending). 293 func (coins Coins) safeAdd(coinsB Coins) Coins { 294 // probably the best way will be to make Coins and interface and hide the structure 295 // definition (type alias) 296 if !coins.isSorted() { 297 panic("Coins (self) must be sorted") 298 } 299 if !coinsB.isSorted() { 300 panic("Wrong argument: coins must be sorted") 301 } 302 303 sum := ([]Coin)(nil) 304 indexA, indexB := 0, 0 305 lenA, lenB := len(coins), len(coinsB) 306 307 for { 308 if indexA == lenA { 309 if indexB == lenB { 310 // return nil coins if both sets are empty 311 return sum 312 } 313 314 // return set B (excluding zero coins) if set A is empty 315 return append(sum, removeZeroCoins(coinsB[indexB:])...) 316 } else if indexB == lenB { 317 // return set A (excluding zero coins) if set B is empty 318 return append(sum, removeZeroCoins(coins[indexA:])...) 319 } 320 321 coinA, coinB := coins[indexA], coinsB[indexB] 322 323 switch strings.Compare(coinA.Denom, coinB.Denom) { 324 case -1: // coin A denom < coin B denom 325 if !coinA.IsZero() { 326 sum = append(sum, coinA) 327 } 328 329 indexA++ 330 331 case 0: // coin A denom == coin B denom 332 res := coinA.Add(coinB) 333 if !res.IsZero() { 334 sum = append(sum, res) 335 } 336 337 indexA++ 338 indexB++ 339 340 case 1: // coin A denom > coin B denom 341 if !coinB.IsZero() { 342 sum = append(sum, coinB) 343 } 344 345 indexB++ 346 } 347 } 348 } 349 350 // DenomsSubsetOf returns true if receiver's denom set 351 // is subset of coinsB's denoms. 352 func (coins Coins) DenomsSubsetOf(coinsB Coins) bool { 353 // more denoms in B than in receiver 354 if len(coins) > len(coinsB) { 355 return false 356 } 357 358 for _, coin := range coins { 359 if coinsB.AmountOf(coin.Denom).IsZero() { 360 return false 361 } 362 } 363 364 return true 365 } 366 367 // Sub subtracts a set of coins from another. 368 // 369 // e.g. 370 // {2A, 3B} - {A} = {A, 3B} 371 // {2A} - {0B} = {2A} 372 // {A, B} - {A} = {B} 373 // 374 // CONTRACT: Sub will never return Coins where one Coin has a non-positive 375 // amount. In otherwords, IsValid will always return true. 376 func (coins Coins) Sub(coinsB Coins) Coins { 377 diff, hasNeg := coins.SafeSub(coinsB) 378 if hasNeg { 379 panic("negative coin amount") 380 } 381 382 return diff 383 } 384 385 // SafeSub performs the same arithmetic as Sub but returns a boolean if any 386 // negative coin amount was returned. 387 // The function panics if `coins` or `coinsB` are not sorted (ascending). 388 func (coins Coins) SafeSub(coinsB Coins) (Coins, bool) { 389 diff := coins.safeAdd(coinsB.negative()) 390 return diff, diff.IsAnyNegative() 391 } 392 393 // Max takes two valid Coins inputs and returns a valid Coins result 394 // where for every denom D, AmountOf(D) of the result is the maximum 395 // of AmountOf(D) of the inputs. Note that the result might be not 396 // be equal to either input. For any valid Coins a, b, and c, the 397 // following are always true: 398 // 399 // a.IsAllLTE(a.Max(b)) 400 // b.IsAllLTE(a.Max(b)) 401 // a.IsAllLTE(c) && b.IsAllLTE(c) == a.Max(b).IsAllLTE(c) 402 // a.Add(b...).IsEqual(a.Min(b).Add(a.Max(b)...)) 403 // 404 // E.g. 405 // {1A, 3B, 2C}.Max({4A, 2B, 2C} == {4A, 3B, 2C}) 406 // {2A, 3B}.Max({1B, 4C}) == {2A, 3B, 4C} 407 // {1A, 2B}.Max({}) == {1A, 2B} 408 func (coins Coins) Max(coinsB Coins) Coins { 409 max := make([]Coin, 0) 410 indexA, indexB := 0, 0 411 for indexA < len(coins) && indexB < len(coinsB) { 412 coinA, coinB := coins[indexA], coinsB[indexB] 413 switch strings.Compare(coinA.Denom, coinB.Denom) { 414 case -1: // denom missing from coinsB 415 max = append(max, coinA) 416 indexA++ 417 case 0: // same denom in both 418 maxCoin := coinA 419 if coinB.Amount.GT(maxCoin.Amount) { 420 maxCoin = coinB 421 } 422 max = append(max, maxCoin) 423 indexA++ 424 indexB++ 425 case 1: // denom missing from coinsA 426 max = append(max, coinB) 427 indexB++ 428 } 429 } 430 for ; indexA < len(coins); indexA++ { 431 max = append(max, coins[indexA]) 432 } 433 for ; indexB < len(coinsB); indexB++ { 434 max = append(max, coinsB[indexB]) 435 } 436 return NewCoins(max...) 437 } 438 439 // Min takes two valid Coins inputs and returns a valid Coins result 440 // where for every denom D, AmountOf(D) of the result is the minimum 441 // of AmountOf(D) of the inputs. Note that the result might be not 442 // be equal to either input. For any valid Coins a, b, and c, the 443 // following are always true: 444 // 445 // a.Min(b).IsAllLTE(a) 446 // a.Min(b).IsAllLTE(b) 447 // c.IsAllLTE(a) && c.IsAllLTE(b) == c.IsAllLTE(a.Min(b)) 448 // a.Add(b...).IsEqual(a.Min(b).Add(a.Max(b)...)) 449 // 450 // E.g. 451 // {1A, 3B, 2C}.Min({4A, 2B, 2C} == {1A, 2B, 2C}) 452 // {2A, 3B}.Min({1B, 4C}) == {1B} 453 // {1A, 2B}.Min({3C}) == empty 454 // 455 // See also DecCoins.Intersect(). 456 func (coins Coins) Min(coinsB Coins) Coins { 457 min := make([]Coin, 0) 458 for indexA, indexB := 0, 0; indexA < len(coins) && indexB < len(coinsB); { 459 coinA, coinB := coins[indexA], coinsB[indexB] 460 switch strings.Compare(coinA.Denom, coinB.Denom) { 461 case -1: // denom missing from coinsB 462 indexA++ 463 case 0: // same denom in both 464 minCoin := coinA 465 if coinB.Amount.LT(minCoin.Amount) { 466 minCoin = coinB 467 } 468 if !minCoin.IsZero() { 469 min = append(min, minCoin) 470 } 471 indexA++ 472 indexB++ 473 case 1: // denom missing from coins 474 indexB++ 475 } 476 } 477 return NewCoins(min...) 478 } 479 480 // IsAllGT returns true if for every denom in coinsB, 481 // the denom is present at a greater amount in coins. 482 func (coins Coins) IsAllGT(coinsB Coins) bool { 483 if len(coins) == 0 { 484 return false 485 } 486 487 if len(coinsB) == 0 { 488 return true 489 } 490 491 if !coinsB.DenomsSubsetOf(coins) { 492 return false 493 } 494 495 for _, coinB := range coinsB { 496 amountA, amountB := coins.AmountOf(coinB.Denom), coinB.Amount 497 if !amountA.GT(amountB) { 498 return false 499 } 500 } 501 502 return true 503 } 504 505 // IsAllGTE returns false if for any denom in coinsB, 506 // the denom is present at a smaller amount in coins; 507 // else returns true. 508 func (coins Coins) IsAllGTE(coinsB Coins) bool { 509 if len(coinsB) == 0 { 510 return true 511 } 512 513 if len(coins) == 0 { 514 return false 515 } 516 517 for _, coinB := range coinsB { 518 if coinB.Amount.GT(coins.AmountOf(coinB.Denom)) { 519 return false 520 } 521 } 522 523 return true 524 } 525 526 // IsAllLT returns True iff for every denom in coins, the denom is present at 527 // a smaller amount in coinsB. 528 func (coins Coins) IsAllLT(coinsB Coins) bool { 529 return coinsB.IsAllGT(coins) 530 } 531 532 // IsAllLTE returns true iff for every denom in coins, the denom is present at 533 // a smaller or equal amount in coinsB. 534 func (coins Coins) IsAllLTE(coinsB Coins) bool { 535 return coinsB.IsAllGTE(coins) 536 } 537 538 // IsAnyGT returns true iff for any denom in coins, the denom is present at a 539 // greater amount in coinsB. 540 // 541 // e.g. 542 // {2A, 3B}.IsAnyGT{A} = true 543 // {2A, 3B}.IsAnyGT{5C} = false 544 // {}.IsAnyGT{5C} = false 545 // {2A, 3B}.IsAnyGT{} = false 546 func (coins Coins) IsAnyGT(coinsB Coins) bool { 547 if len(coinsB) == 0 { 548 return false 549 } 550 551 for _, coin := range coins { 552 amt := coinsB.AmountOf(coin.Denom) 553 if coin.Amount.GT(amt) && !amt.IsZero() { 554 return true 555 } 556 } 557 558 return false 559 } 560 561 // IsAnyGTE returns true iff coins contains at least one denom that is present 562 // at a greater or equal amount in coinsB; it returns false otherwise. 563 // 564 // NOTE: IsAnyGTE operates under the invariant that both coin sets are sorted 565 // by denominations and there exists no zero coins. 566 func (coins Coins) IsAnyGTE(coinsB Coins) bool { 567 if len(coinsB) == 0 { 568 return false 569 } 570 571 for _, coin := range coins { 572 amt := coinsB.AmountOf(coin.Denom) 573 if coin.Amount.GTE(amt) && !amt.IsZero() { 574 return true 575 } 576 } 577 578 return false 579 } 580 581 // IsZero returns true if there are no coins or all coins are zero. 582 func (coins Coins) IsZero() bool { 583 for _, coin := range coins { 584 if !coin.IsZero() { 585 return false 586 } 587 } 588 return true 589 } 590 591 // IsEqual returns true if the two sets of Coins have the same value 592 func (coins Coins) IsEqual(coinsB Coins) bool { 593 if len(coins) != len(coinsB) { 594 return false 595 } 596 597 coins = coins.Sort() 598 coinsB = coinsB.Sort() 599 600 for i := 0; i < len(coins); i++ { 601 if !coins[i].IsEqual(coinsB[i]) { 602 return false 603 } 604 } 605 606 return true 607 } 608 609 // Empty returns true if there are no coins and false otherwise. 610 func (coins Coins) Empty() bool { 611 return len(coins) == 0 612 } 613 614 // AmountOf returns the amount of a denom from coins 615 func (coins Coins) AmountOf(denom string) Int { 616 mustValidateDenom(denom) 617 return coins.AmountOfNoDenomValidation(denom) 618 } 619 620 // AmountOfNoDenomValidation returns the amount of a denom from coins 621 // without validating the denomination. 622 func (coins Coins) AmountOfNoDenomValidation(denom string) Int { 623 switch len(coins) { 624 case 0: 625 return ZeroInt() 626 627 case 1: 628 coin := coins[0] 629 if coin.Denom == denom { 630 return coin.Amount 631 } 632 return ZeroInt() 633 634 default: 635 // Binary search the amount of coins remaining 636 midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 637 coin := coins[midIdx] 638 switch { 639 case denom < coin.Denom: 640 return coins[:midIdx].AmountOfNoDenomValidation(denom) 641 case denom == coin.Denom: 642 return coin.Amount 643 default: 644 return coins[midIdx+1:].AmountOfNoDenomValidation(denom) 645 } 646 } 647 } 648 649 // GetDenomByIndex returns the Denom of the certain coin to make the findDup generic 650 func (coins Coins) GetDenomByIndex(i int) string { 651 return coins[i].Denom 652 } 653 654 // IsAllPositive returns true if there is at least one coin and all currencies 655 // have a positive value. 656 func (coins Coins) IsAllPositive() bool { 657 if len(coins) == 0 { 658 return false 659 } 660 661 for _, coin := range coins { 662 if !coin.IsPositive() { 663 return false 664 } 665 } 666 667 return true 668 } 669 670 // IsAnyNegative returns true if there is at least one coin whose amount 671 // is negative; returns false otherwise. It returns false if the coin set 672 // is empty too. 673 // 674 // TODO: Remove once unsigned integers are used. 675 func (coins Coins) IsAnyNegative() bool { 676 for _, coin := range coins { 677 if coin.IsNegative() { 678 return true 679 } 680 } 681 682 return false 683 } 684 685 // IsAnyNil returns true if there is at least one coin whose amount 686 // is nil; returns false otherwise. It returns false if the coin set 687 // is empty too. 688 func (coins Coins) IsAnyNil() bool { 689 for _, coin := range coins { 690 if coin.IsNil() { 691 return true 692 } 693 } 694 695 return false 696 } 697 698 // negative returns a set of coins with all amount negative. 699 // 700 // TODO: Remove once unsigned integers are used. 701 func (coins Coins) negative() Coins { 702 res := make([]Coin, 0, len(coins)) 703 704 for _, coin := range coins { 705 res = append(res, Coin{ 706 Denom: coin.Denom, 707 Amount: coin.Amount.Neg(), 708 }) 709 } 710 711 return res 712 } 713 714 // removeZeroCoins removes all zero coins from the given coin set in-place. 715 func removeZeroCoins(coins Coins) Coins { 716 for i := 0; i < len(coins); i++ { 717 if coins[i].IsZero() { 718 break 719 } else if i == len(coins)-1 { 720 return coins 721 } 722 } 723 724 var result []Coin 725 if len(coins) > 0 { 726 result = make([]Coin, 0, len(coins)-1) 727 } 728 729 for _, coin := range coins { 730 if !coin.IsZero() { 731 result = append(result, coin) 732 } 733 } 734 735 return result 736 } 737 738 //----------------------------------------------------------------------------- 739 // Sort interface 740 741 // Len implements sort.Interface for Coins 742 func (coins Coins) Len() int { return len(coins) } 743 744 // Less implements sort.Interface for Coins 745 func (coins Coins) Less(i, j int) bool { return coins[i].Denom < coins[j].Denom } 746 747 // Swap implements sort.Interface for Coins 748 func (coins Coins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i] } 749 750 var _ sort.Interface = Coins{} 751 752 // Sort is a helper function to sort the set of coins in-place 753 func (coins Coins) Sort() Coins { 754 sort.Sort(coins) 755 return coins 756 } 757 758 //----------------------------------------------------------------------------- 759 // Parsing 760 761 var ( 762 // Denominations can be 3 ~ 128 characters long and support letters, followed by either 763 // a letter, a number or a separator ('/'). 764 reDnmString = `[a-zA-Z][a-zA-Z0-9/-]{2,127}` 765 reDecAmt = `[[:digit:]]+(?:\.[[:digit:]]+)?|\.[[:digit:]]+` 766 reSpc = `[[:space:]]*` 767 reDnm *regexp.Regexp 768 reDecCoin *regexp.Regexp 769 ) 770 771 func init() { 772 SetCoinDenomRegex(DefaultCoinDenomRegex) 773 } 774 775 // DefaultCoinDenomRegex returns the default regex string 776 func DefaultCoinDenomRegex() string { 777 return reDnmString 778 } 779 780 // coinDenomRegex returns the current regex string and can be overwritten for custom validation 781 var coinDenomRegex = DefaultCoinDenomRegex 782 783 // SetCoinDenomRegex allows for coin's custom validation by overriding the regular 784 // expression string used for denom validation. 785 func SetCoinDenomRegex(reFn func() string) { 786 coinDenomRegex = reFn 787 788 reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, coinDenomRegex())) 789 reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, coinDenomRegex())) 790 } 791 792 // ValidateDenom is the default validation function for Coin.Denom. 793 func ValidateDenom(denom string) error { 794 if !reDnm.MatchString(denom) { 795 return fmt.Errorf("invalid denom: %s", denom) 796 } 797 return nil 798 } 799 800 func mustValidateDenom(denom string) { 801 if err := ValidateDenom(denom); err != nil { 802 panic(err) 803 } 804 } 805 806 // ParseCoinNormalized parses and normalize a cli input for one coin type, returning errors if invalid or on an empty string 807 // as well. 808 // Expected format: "{amount}{denomination}" 809 func ParseCoinNormalized(coinStr string) (coin Coin, err error) { 810 decCoin, err := ParseDecCoin(coinStr) 811 if err != nil { 812 return Coin{}, err 813 } 814 815 coin, _ = NormalizeDecCoin(decCoin).TruncateDecimal() 816 return coin, nil 817 } 818 819 // ParseCoinsNormalized will parse out a list of coins separated by commas, and normalize them by converting to the smallest 820 // unit. If the parsing is successful, the provided coins will be sanitized by removing zero coins and sorting the coin 821 // set. Lastly a validation of the coin set is executed. If the check passes, ParseCoinsNormalized will return the 822 // sanitized coins. 823 // Otherwise, it will return an error. 824 // If an empty string is provided to ParseCoinsNormalized, it returns nil Coins. 825 // ParseCoinsNormalized supports decimal coins as inputs, and truncate them to int after converted to the smallest unit. 826 // Expected format: "{amount0}{denomination},...,{amountN}{denominationN}" 827 func ParseCoinsNormalized(coinStr string) (Coins, error) { 828 coins, err := ParseDecCoins(coinStr) 829 if err != nil { 830 return Coins{}, err 831 } 832 return NormalizeCoins(coins), nil 833 }