github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/std/coin.go (about) 1 package std 2 3 import ( 4 "fmt" 5 "regexp" 6 "sort" 7 "strconv" 8 "strings" 9 10 "github.com/gnolang/gno/tm2/pkg/errors" 11 "github.com/gnolang/overflow" 12 ) 13 14 // ----------------------------------------------------------------------------- 15 // Coin 16 17 // Coin hold some amount of one currency. 18 // A negative amount is invalid. 19 type Coin struct { 20 Denom string `json:"denom"` 21 Amount int64 `json:"amount"` 22 } 23 24 // NewCoin returns a new coin with a denomination and amount. 25 // It will panic if the amount is negative. 26 // To construct a negative (invalid) amount, use an operation. 27 func NewCoin(denom string, amount int64) Coin { 28 if err := validate(denom, amount); err != nil { 29 panic(err) 30 } 31 32 return Coin{ 33 Denom: denom, 34 Amount: amount, 35 } 36 } 37 38 func (coin Coin) MarshalAmino() (string, error) { 39 return coin.String(), nil 40 } 41 42 func (coin *Coin) UnmarshalAmino(coinstr string) (err error) { 43 if coinstr == "" { 44 return nil 45 } 46 coin2, err := ParseCoin(coinstr) 47 if err != nil { 48 return err 49 } 50 *coin = coin2 51 return nil 52 } 53 54 // String provides a human-readable representation of a coin 55 func (coin Coin) String() string { 56 if coin.IsZero() { 57 return "" 58 } else { 59 return fmt.Sprintf("%d%v", coin.Amount, coin.Denom) 60 } 61 } 62 63 // validate returns an error if the Coin has a negative amount or if 64 // the denom is invalid. 65 func validate(denom string, amount int64) error { 66 if err := validateDenom(denom); err != nil { 67 return err 68 } 69 70 if amount < 0 { 71 return fmt.Errorf("negative coin amount: %d", amount) 72 } 73 74 return nil 75 } 76 77 // IsValid returns true if the Coin has a non-negative amount and the denom is valid. 78 func (coin Coin) IsValid() bool { 79 if err := validate(coin.Denom, coin.Amount); err != nil { 80 return false 81 } 82 return true 83 } 84 85 // IsZero returns if this represents no money 86 func (coin Coin) IsZero() bool { 87 return coin.Amount == 0 88 } 89 90 // IsGTE returns true if they are the same type and the receiver is 91 // an equal or greater value 92 func (coin Coin) IsGTE(other Coin) bool { 93 if coin.Denom != other.Denom { 94 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 95 } 96 return coin.Amount >= other.Amount 97 } 98 99 // IsLT returns true if they are the same type and the receiver is 100 // a smaller value 101 func (coin Coin) IsLT(other Coin) bool { 102 if coin.Denom != other.Denom { 103 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 104 } 105 return coin.Amount < other.Amount 106 } 107 108 // IsEqual returns true if the two sets of Coins have the same value 109 func (coin Coin) IsEqual(other Coin) bool { 110 if coin.Denom != other.Denom { 111 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 112 } 113 return coin.Amount == other.Amount 114 } 115 116 // Adds amounts of two coins with same denom. 117 // If the coins differ in denom then it panics. 118 // An overflow or underflow panics. 119 // An invalid result panics. 120 func (coin Coin) Add(coinB Coin) Coin { 121 res := coin.AddUnsafe(coinB) 122 if !res.IsValid() { 123 panic(fmt.Sprintf("invalid result: %v + %v = %v", coin, coinB, res)) 124 } 125 return res 126 } 127 128 func (coin Coin) AddUnsafe(coinB Coin) Coin { 129 if coin.Denom != coinB.Denom { 130 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, coinB.Denom)) 131 } 132 sum, ok := overflow.Add64(coin.Amount, coinB.Amount) 133 if !ok { 134 panic(fmt.Sprintf("coin add overflow/underflow: %v, %v", coin, coinB)) 135 } 136 return Coin{coin.Denom, sum} 137 } 138 139 // Subtracts amounts of two coins with same denom. 140 // If the coins differ in denom then it panics. 141 // An overflow or underflow panics. 142 // An invalid result panics. 143 func (coin Coin) Sub(coinB Coin) Coin { 144 res := coin.SubUnsafe(coinB) 145 if !res.IsValid() { 146 panic(fmt.Sprintf("invalid result: %v - %v = %v", coin, coinB, res)) 147 } 148 return res 149 } 150 151 func (coin Coin) SubUnsafe(coinB Coin) Coin { 152 if coin.Denom != coinB.Denom { 153 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, coinB.Denom)) 154 } 155 dff, ok := overflow.Sub64(coin.Amount, coinB.Amount) 156 if !ok { 157 panic(fmt.Sprintf("coin subtract overflow/underflow: %v, %v", coin, coinB)) 158 } 159 return Coin{coin.Denom, dff} 160 } 161 162 // IsPositive returns true if coin amount is positive. 163 func (coin Coin) IsPositive() bool { 164 return coin.Amount > 0 165 } 166 167 // IsNegative returns true if the coin amount is negative and false otherwise. 168 func (coin Coin) IsNegative() bool { 169 return coin.Amount < 0 170 } 171 172 // ----------------------------------------------------------------------------- 173 // Coins 174 175 // Coins is a set of Coin, one per currency 176 type Coins []Coin 177 178 // NewCoins constructs a new coin set. 179 func NewCoins(coins ...Coin) Coins { 180 // remove zeroes 181 newCoins := removeZeroCoins(Coins(coins)) 182 if len(newCoins) == 0 { 183 return Coins{} 184 } 185 186 newCoins.Sort() 187 188 // detect duplicate Denoms 189 if dupIndex := findDup(newCoins); dupIndex != -1 { 190 panic(fmt.Errorf("find duplicate denom: %s", newCoins[dupIndex])) 191 } 192 193 if !newCoins.IsValid() { 194 panic(fmt.Errorf("invalid coin set: %s", newCoins)) 195 } 196 197 return newCoins 198 } 199 200 func (coins Coins) MarshalAmino() (string, error) { 201 return coins.String(), nil 202 } 203 204 func (coins *Coins) UnmarshalAmino(coinsstr string) (err error) { 205 coins2, err := ParseCoins(coinsstr) 206 if err != nil { 207 return err 208 } 209 *coins = coins2 210 return nil 211 } 212 213 func (coins Coins) String() string { 214 if len(coins) == 0 { 215 return "" 216 } 217 218 out := "" 219 for _, coin := range coins { 220 out += fmt.Sprintf("%v,", coin.String()) 221 } 222 return out[:len(out)-1] 223 } 224 225 // IsValid asserts the Coins are sorted, have positive amount, 226 // and Denom does not contain upper case characters. 227 func (coins Coins) IsValid() bool { 228 switch len(coins) { 229 case 0: 230 return true 231 case 1: 232 if err := validateDenom(coins[0].Denom); err != nil { 233 return false 234 } 235 return coins[0].IsPositive() 236 default: 237 // check single coin case 238 if !(Coins{coins[0]}).IsValid() { 239 return false 240 } 241 242 lowDenom := coins[0].Denom 243 for _, coin := range coins[1:] { 244 if strings.ToLower(coin.Denom) != coin.Denom { 245 return false 246 } 247 if coin.Denom <= lowDenom { 248 return false 249 } 250 if !coin.IsPositive() { 251 return false 252 } 253 254 // we compare each coin against the last denom 255 lowDenom = coin.Denom 256 } 257 258 return true 259 } 260 } 261 262 // Add adds two sets of coins. 263 // 264 // e.g. 265 // {2A} + {A, 2B} = {3A, 2B} 266 // {2A} + {0B} = {2A} 267 // 268 // NOTE: Add operates under the invariant that coins are sorted by 269 // denominations. Panics on invalid result. 270 func (coins Coins) Add(coinsB Coins) Coins { 271 res := coins.AddUnsafe(coinsB) 272 if !res.IsValid() { 273 panic(fmt.Sprintf("invalid result: %v + %v = %v", coins, coinsB, res)) 274 } 275 return res 276 } 277 278 // AddUnsafe will perform addition of two coins sets. If both coin sets are 279 // empty, then an empty set is returned. If only a single set is empty, the 280 // other set is returned. Otherwise, the coins are compared in order of their 281 // denomination and addition only occurs when the denominations match, otherwise 282 // the coin is simply added to the sum assuming it's not zero. 283 func (coins Coins) AddUnsafe(coinsB Coins) Coins { 284 sum := ([]Coin)(nil) 285 indexA, indexB := 0, 0 286 lenA, lenB := len(coins), len(coinsB) 287 288 for { 289 if indexA == lenA { 290 if indexB == lenB { 291 // return nil coins if both sets are empty 292 return sum 293 } 294 295 // return set B (excluding zero coins) if set A is empty 296 return append(sum, removeZeroCoins(coinsB[indexB:])...) 297 } else if indexB == lenB { 298 // return set A (excluding zero coins) if set B is empty 299 return append(sum, removeZeroCoins(coins[indexA:])...) 300 } 301 302 coinA, coinB := coins[indexA], coinsB[indexB] 303 304 switch strings.Compare(coinA.Denom, coinB.Denom) { 305 case -1: // coin A denom < coin B denom 306 if !coinA.IsZero() { 307 sum = append(sum, coinA) 308 } 309 310 indexA++ 311 312 case 0: // coin A denom == coin B denom 313 res := coinA.AddUnsafe(coinB) 314 if !res.IsZero() { 315 sum = append(sum, res) 316 } 317 318 indexA++ 319 indexB++ 320 321 case 1: // coin A denom > coin B denom 322 if !coinB.IsZero() { 323 sum = append(sum, coinB) 324 } 325 326 indexB++ 327 } 328 } 329 } 330 331 // DenomsSubsetOf returns true if receiver's denom set 332 // is subset of coinsB's denoms. 333 func (coins Coins) DenomsSubsetOf(coinsB Coins) bool { 334 // more denoms in B than in receiver 335 if len(coins) > len(coinsB) { 336 return false 337 } 338 339 for _, coin := range coins { 340 if coinsB.AmountOf(coin.Denom) == 0 { 341 return false 342 } 343 } 344 345 return true 346 } 347 348 // Sub subtracts a set of coins from another. 349 // 350 // e.g. 351 // {2A, 3B} - {A} = {A, 3B} 352 // {2A} - {0B} = {2A} 353 // {A, B} - {A} = {B} 354 // 355 // Panics on invalid result. 356 func (coins Coins) Sub(coinsB Coins) Coins { 357 res := coins.SubUnsafe(coinsB) 358 if !res.IsValid() { 359 panic(fmt.Sprintf("invalid result: %v - %v = %v", coins, coinsB, res)) 360 } 361 return res 362 } 363 364 // SubUnsafe performs the same arithmetic as Sub but returns a boolean if any 365 // negative coin amount was returned. 366 func (coins Coins) SubUnsafe(coinsB Coins) Coins { 367 res := coins.AddUnsafe(coinsB.negative()) 368 return res 369 } 370 371 // IsAllGT returns true if for every denom in coinsB, 372 // the denom is present at a greater amount in coins. 373 func (coins Coins) IsAllGT(coinsB Coins) bool { 374 if len(coins) == 0 { 375 return false 376 } 377 378 if len(coinsB) == 0 { 379 return true 380 } 381 382 if !coinsB.DenomsSubsetOf(coins) { 383 return false 384 } 385 386 for _, coinB := range coinsB { 387 amountA, amountB := coins.AmountOf(coinB.Denom), coinB.Amount 388 if amountA <= amountB { 389 return false 390 } 391 } 392 393 return true 394 } 395 396 // IsAllGTE returns false if for any denom in coinsB, 397 // the denom is present at a smaller amount in coins; 398 // else returns true. 399 func (coins Coins) IsAllGTE(coinsB Coins) bool { 400 if len(coinsB) == 0 { 401 return true 402 } 403 404 if len(coins) == 0 { 405 return false 406 } 407 408 for _, coinB := range coinsB { 409 if coinB.Amount > coins.AmountOf(coinB.Denom) { 410 return false 411 } 412 } 413 414 return true 415 } 416 417 // IsAllLT returns True iff for every denom in coins, the denom is present at 418 // a smaller amount in coinsB. 419 func (coins Coins) IsAllLT(coinsB Coins) bool { 420 return coinsB.IsAllGT(coins) 421 } 422 423 // IsAllLTE returns true iff for every denom in coins, the denom is present at 424 // a smaller or equal amount in coinsB. 425 func (coins Coins) IsAllLTE(coinsB Coins) bool { 426 return coinsB.IsAllGTE(coins) 427 } 428 429 // IsAnyGT returns true iff for any denom in coins, the denom is present at a 430 // greater amount in coinsB. 431 // 432 // e.g. 433 // {2A, 3B}.IsAnyGT{A} = true 434 // {2A, 3B}.IsAnyGT{5C} = false 435 // {}.IsAnyGT{5C} = false 436 // {2A, 3B}.IsAnyGT{} = false 437 func (coins Coins) IsAnyGT(coinsB Coins) bool { 438 if len(coinsB) == 0 { 439 return false 440 } 441 442 for _, coin := range coins { 443 amt := coinsB.AmountOf(coin.Denom) 444 if coin.Amount > amt && amt != 0 { 445 return true 446 } 447 } 448 449 return false 450 } 451 452 // IsAnyGTE returns true iff coins contains at least one denom that is present 453 // at a greater or equal amount in coinsB; it returns false otherwise. 454 // 455 // NOTE: IsAnyGTE operates under the invariant that both coin sets are sorted 456 // by denominations and there exists no zero coins. 457 func (coins Coins) IsAnyGTE(coinsB Coins) bool { 458 if len(coinsB) == 0 { 459 return false 460 } 461 462 for _, coin := range coins { 463 amt := coinsB.AmountOf(coin.Denom) 464 if coin.Amount >= amt && amt != 0 { 465 return true 466 } 467 } 468 469 return false 470 } 471 472 // IsZero returns true if there are no coins or all coins are zero. 473 func (coins Coins) IsZero() bool { 474 for _, coin := range coins { 475 if !coin.IsZero() { 476 return false 477 } 478 } 479 return true 480 } 481 482 // IsEqual returns true if the two sets of Coins have the same value 483 func (coins Coins) IsEqual(coinsB Coins) bool { 484 if len(coins) != len(coinsB) { 485 return false 486 } 487 488 // XXX instead, consider IsValid() on both and panic if not. 489 coins = coins.Sort() 490 coinsB = coinsB.Sort() 491 492 for i := 0; i < len(coins); i++ { 493 if !coins[i].IsEqual(coinsB[i]) { 494 return false 495 } 496 } 497 498 return true 499 } 500 501 // Empty returns true if there are no coins and false otherwise. 502 func (coins Coins) Empty() bool { 503 return len(coins) == 0 504 } 505 506 // Returns the amount of a denom from coins, which may be negative. 507 func (coins Coins) AmountOf(denom string) int64 { 508 mustValidateDenom(denom) 509 510 switch len(coins) { 511 case 0: 512 return 0 513 514 case 1: 515 coin := coins[0] 516 if coin.Denom == denom { 517 return coin.Amount 518 } 519 return 0 520 521 default: 522 midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 523 coin := coins[midIdx] 524 525 if denom < coin.Denom { 526 return coins[:midIdx].AmountOf(denom) 527 } else if denom == coin.Denom { 528 return coin.Amount 529 } else { 530 return coins[midIdx+1:].AmountOf(denom) 531 } 532 } 533 } 534 535 // IsAllPositive returns true if there is at least one coin and all currencies 536 // have a positive value. 537 // NOTE: besides this function, which is zero sensitive, all other functions 538 // don't need to be called "IsAll*" -- TODO: rename back coins.IsAll* to coins.Is*? 539 func (coins Coins) IsAllPositive() bool { 540 if len(coins) == 0 { 541 return false 542 } 543 544 for _, coin := range coins { 545 if !coin.IsPositive() { 546 return false 547 } 548 } 549 550 return true 551 } 552 553 // IsAnyNegative returns true if there is at least one coin whose amount 554 // is negative; returns false otherwise. It returns false if the coin set 555 // is empty too. 556 // 557 // TODO: Remove once unsigned integers are used. 558 func (coins Coins) IsAnyNegative() bool { 559 for _, coin := range coins { 560 if coin.IsNegative() { 561 return true 562 } 563 } 564 565 return false 566 } 567 568 // negative returns a set of coins with all amount negative. 569 // 570 // TODO: Remove once unsigned integers are used. 571 func (coins Coins) negative() Coins { 572 res := make([]Coin, 0, len(coins)) 573 574 for _, coin := range coins { 575 res = append(res, Coin{ 576 Denom: coin.Denom, 577 Amount: -1 * coin.Amount, 578 }) 579 } 580 581 return res 582 } 583 584 // removeZeroCoins removes all zero coins from the given coin set in-place. 585 func removeZeroCoins(coins Coins) Coins { 586 i, l := 0, len(coins) 587 for i < l { 588 if coins[i].IsZero() { 589 // remove coin 590 coins = append(coins[:i], coins[i+1:]...) 591 l-- 592 } else { 593 i++ 594 } 595 } 596 597 return coins[:i] 598 } 599 600 // ----------------------------------------------------------------------------- 601 // Sort interface 602 603 func (coins Coins) Len() int { return len(coins) } 604 func (coins Coins) Less(i, j int) bool { return coins[i].Denom < coins[j].Denom } 605 func (coins Coins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i] } 606 607 var _ sort.Interface = Coins{} 608 609 // Sort is a helper function to sort the set of coins inplace 610 func (coins Coins) Sort() Coins { 611 sort.Sort(coins) 612 return coins 613 } 614 615 // ----------------------------------------------------------------------------- 616 // Parsing 617 618 var ( 619 reDnmString = `[a-z\/][a-z0-9_.:\/]{2,}` 620 reAmt = `[[:digit:]]+` 621 reSpc = `[[:space:]]*` 622 reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, reDnmString)) 623 reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnmString)) 624 ) 625 626 func validateDenom(denom string) error { 627 if !reDnm.MatchString(denom) { 628 return fmt.Errorf("invalid denom: %s", denom) 629 } 630 return nil 631 } 632 633 func mustValidateDenom(denom string) { 634 if err := validateDenom(denom); err != nil { 635 panic(err) 636 } 637 } 638 639 func MustParseCoin(coinStr string) Coin { 640 coin, err := ParseCoin(coinStr) 641 if err != nil { 642 panic(err) 643 } 644 return coin 645 } 646 647 // ParseCoin parses a cli input for one coin type, returning errors if invalid. 648 // This returns an error on an empty string as well. 649 func ParseCoin(coinStr string) (coin Coin, err error) { 650 coinStr = strings.TrimSpace(coinStr) 651 652 matches := reCoin.FindStringSubmatch(coinStr) 653 if matches == nil { 654 return Coin{}, fmt.Errorf("invalid coin expression: %s", coinStr) 655 } 656 657 denomStr, amountStr := matches[2], matches[1] 658 659 amount, err := strconv.ParseInt(amountStr, 10, 64) 660 if err != nil { 661 return Coin{}, errors.Wrap(err, "failed to parse coin amount: %s", amountStr) 662 } 663 664 if err := validateDenom(denomStr); err != nil { 665 return Coin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %w", err) 666 } 667 668 return NewCoin(denomStr, amount), nil 669 } 670 671 func MustParseCoins(coinsStr string) Coins { 672 coins, err := ParseCoins(coinsStr) 673 if err != nil { 674 panic(err) 675 } 676 return coins 677 } 678 679 // ParseCoins will parse out a list of coins separated by commas. 680 // If nothing is provided, it returns nil Coins. 681 // Returned coins are sorted. 682 func ParseCoins(coinsStr string) (Coins, error) { 683 coinsStr = strings.TrimSpace(coinsStr) 684 if len(coinsStr) == 0 { 685 return nil, nil 686 } 687 688 coinStrs := strings.Split(coinsStr, ",") 689 coins := make(Coins, len(coinStrs)) 690 for i, coinStr := range coinStrs { 691 coin, err := ParseCoin(coinStr) 692 if err != nil { 693 return nil, err 694 } 695 696 coins[i] = coin 697 } 698 699 // sort coins for determinism 700 coins.Sort() 701 702 // validate coins before returning 703 if !coins.IsValid() { 704 return nil, fmt.Errorf("parseCoins invalid: %#v", coins) 705 } 706 707 return coins, nil 708 } 709 710 // findDup works on the assumption that coins is sorted 711 func findDup(coins Coins) int { 712 if len(coins) <= 1 { 713 return -1 714 } 715 716 prevDenom := coins[0].Denom 717 for i := 1; i < len(coins); i++ { 718 if coins[i].Denom == prevDenom { 719 return i 720 } 721 prevDenom = coins[i].Denom 722 } 723 724 return -1 725 }