github.com/cosmos/cosmos-sdk@v0.50.10/types/dec_coin.go (about) 1 package types 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "cosmossdk.io/errors" 9 "cosmossdk.io/math" 10 ) 11 12 // ---------------------------------------------------------------------------- 13 // Decimal Coin 14 15 // NewDecCoin creates a new DecCoin instance from an Int. 16 func NewDecCoin(denom string, amount math.Int) DecCoin { 17 coin := NewCoin(denom, amount) 18 19 return DecCoin{ 20 Denom: coin.Denom, 21 Amount: math.LegacyNewDecFromInt(coin.Amount), 22 } 23 } 24 25 // NewDecCoinFromDec creates a new DecCoin instance from a Dec. 26 func NewDecCoinFromDec(denom string, amount math.LegacyDec) DecCoin { 27 mustValidateDenom(denom) 28 29 if amount.IsNegative() { 30 panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount)) 31 } 32 33 return DecCoin{ 34 Denom: denom, 35 Amount: amount, 36 } 37 } 38 39 // NewDecCoinFromCoin creates a new DecCoin from a Coin. 40 func NewDecCoinFromCoin(coin Coin) DecCoin { 41 if err := coin.Validate(); err != nil { 42 panic(err) 43 } 44 45 return DecCoin{ 46 Denom: coin.Denom, 47 Amount: math.LegacyNewDecFromInt(coin.Amount), 48 } 49 } 50 51 // NewInt64DecCoin returns a new DecCoin with a denomination and amount. It will 52 // panic if the amount is negative or denom is invalid. 53 func NewInt64DecCoin(denom string, amount int64) DecCoin { 54 return NewDecCoin(denom, math.NewInt(amount)) 55 } 56 57 // IsZero returns if the DecCoin amount is zero. 58 func (coin DecCoin) IsZero() bool { 59 return coin.Amount.IsZero() 60 } 61 62 // IsGTE returns true if they are the same type and the receiver is 63 // an equal or greater value. 64 func (coin DecCoin) IsGTE(other DecCoin) bool { 65 if coin.Denom != other.Denom { 66 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 67 } 68 69 return !coin.Amount.LT(other.Amount) 70 } 71 72 // IsLT returns true if they are the same type and the receiver is 73 // a smaller value. 74 func (coin DecCoin) IsLT(other DecCoin) bool { 75 if coin.Denom != other.Denom { 76 panic(fmt.Sprintf("invalid coin denominations; %s, %s", coin.Denom, other.Denom)) 77 } 78 79 return coin.Amount.LT(other.Amount) 80 } 81 82 // IsEqual returns true if the two sets of Coins have the same value. 83 // Deprecated: Use DecCoin.Equal instead. 84 func (coin DecCoin) IsEqual(other DecCoin) bool { 85 return coin.Equal(other) 86 } 87 88 // Add adds amounts of two decimal coins with same denom. 89 func (coin DecCoin) Add(coinB DecCoin) DecCoin { 90 if coin.Denom != coinB.Denom { 91 panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) 92 } 93 return DecCoin{coin.Denom, coin.Amount.Add(coinB.Amount)} 94 } 95 96 // Sub subtracts amounts of two decimal coins with same denom. 97 func (coin DecCoin) Sub(coinB DecCoin) DecCoin { 98 if coin.Denom != coinB.Denom { 99 panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom)) 100 } 101 res := DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)} 102 if res.IsNegative() { 103 panic("negative decimal coin amount") 104 } 105 return res 106 } 107 108 // TruncateDecimal returns a Coin with a truncated decimal and a DecCoin for the 109 // change. Note, the change may be zero. 110 func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) { 111 truncated := coin.Amount.TruncateInt() 112 change := coin.Amount.Sub(math.LegacyNewDecFromInt(truncated)) 113 return NewCoin(coin.Denom, truncated), NewDecCoinFromDec(coin.Denom, change) 114 } 115 116 // IsPositive returns true if coin amount is positive. 117 // 118 // TODO: Remove once unsigned integers are used. 119 func (coin DecCoin) IsPositive() bool { 120 return coin.Amount.IsPositive() 121 } 122 123 // IsNegative returns true if the coin amount is negative and false otherwise. 124 // 125 // TODO: Remove once unsigned integers are used. 126 func (coin DecCoin) IsNegative() bool { 127 return coin.Amount.IsNegative() 128 } 129 130 // String implements the Stringer interface for DecCoin. It returns a 131 // human-readable representation of a decimal coin. 132 func (coin DecCoin) String() string { 133 return fmt.Sprintf("%v%v", coin.Amount, coin.Denom) 134 } 135 136 // Validate returns an error if the DecCoin has a negative amount or if the denom is invalid. 137 func (coin DecCoin) Validate() error { 138 if err := ValidateDenom(coin.Denom); err != nil { 139 return err 140 } 141 if coin.IsNegative() { 142 return fmt.Errorf("decimal coin %s amount cannot be negative", coin) 143 } 144 return nil 145 } 146 147 // IsValid returns true if the DecCoin has a non-negative amount and the denom is valid. 148 func (coin DecCoin) IsValid() bool { 149 return coin.Validate() == nil 150 } 151 152 // ---------------------------------------------------------------------------- 153 // Decimal Coins 154 155 // DecCoins defines a slice of coins with decimal values 156 type DecCoins []DecCoin 157 158 // NewDecCoins constructs a new coin set with with decimal values 159 // from DecCoins. The provided coins will be sanitized by removing 160 // zero coins and sorting the coin set. A panic will occur if the coin set is not valid. 161 func NewDecCoins(decCoins ...DecCoin) DecCoins { 162 newDecCoins := sanitizeDecCoins(decCoins) 163 if err := newDecCoins.Validate(); err != nil { 164 panic(fmt.Errorf("invalid coin set %s: %w", newDecCoins, err)) 165 } 166 167 return newDecCoins 168 } 169 170 func sanitizeDecCoins(decCoins []DecCoin) DecCoins { 171 // remove zeroes 172 newDecCoins := removeZeroDecCoins(decCoins) 173 if len(newDecCoins) == 0 { 174 return DecCoins{} 175 } 176 177 return newDecCoins.Sort() 178 } 179 180 // NewDecCoinsFromCoins constructs a new coin set with decimal values 181 // from regular Coins. 182 func NewDecCoinsFromCoins(coins ...Coin) DecCoins { 183 if len(coins) == 0 { 184 return DecCoins{} 185 } 186 187 decCoins := make([]DecCoin, 0, len(coins)) 188 newCoins := NewCoins(coins...) 189 for _, coin := range newCoins { 190 decCoins = append(decCoins, NewDecCoinFromCoin(coin)) 191 } 192 193 return decCoins 194 } 195 196 // String implements the Stringer interface for DecCoins. It returns a 197 // human-readable representation of decimal coins. 198 func (coins DecCoins) String() string { 199 if len(coins) == 0 { 200 return "" 201 } 202 203 out := "" 204 for _, coin := range coins { 205 out += fmt.Sprintf("%v,", coin.String()) 206 } 207 208 return out[:len(out)-1] 209 } 210 211 // TruncateDecimal returns the coins with truncated decimals and returns the 212 // change. Note, it will not return any zero-amount coins in either the truncated or 213 // change coins. 214 func (coins DecCoins) TruncateDecimal() (truncatedCoins Coins, changeCoins DecCoins) { 215 for _, coin := range coins { 216 truncated, change := coin.TruncateDecimal() 217 if !truncated.IsZero() { 218 truncatedCoins = truncatedCoins.Add(truncated) 219 } 220 if !change.IsZero() { 221 changeCoins = changeCoins.Add(change) 222 } 223 } 224 225 return truncatedCoins, changeCoins 226 } 227 228 // Add adds two sets of DecCoins. 229 // 230 // NOTE: Add operates under the invariant that coins are sorted by 231 // denominations. 232 // 233 // CONTRACT: Add will never return Coins where one Coin has a non-positive 234 // amount. In otherwords, IsValid will always return true. 235 func (coins DecCoins) Add(coinsB ...DecCoin) DecCoins { 236 return coins.safeAdd(coinsB) 237 } 238 239 // safeAdd will perform addition of two DecCoins sets. If both coin sets are 240 // empty, then an empty set is returned. If only a single set is empty, the 241 // other set is returned. Otherwise, the coins are compared in order of their 242 // denomination and addition only occurs when the denominations match, otherwise 243 // the coin is simply added to the sum assuming it's not zero. 244 func (coins DecCoins) safeAdd(coinsB DecCoins) DecCoins { 245 sum := ([]DecCoin)(nil) 246 indexA, indexB := 0, 0 247 lenA, lenB := len(coins), len(coinsB) 248 249 for { 250 if indexA == lenA { 251 if indexB == lenB { 252 // return nil coins if both sets are empty 253 return sum 254 } 255 256 // return set B (excluding zero coins) if set A is empty 257 return append(sum, removeZeroDecCoins(coinsB[indexB:])...) 258 } else if indexB == lenB { 259 // return set A (excluding zero coins) if set B is empty 260 return append(sum, removeZeroDecCoins(coins[indexA:])...) 261 } 262 263 coinA, coinB := coins[indexA], coinsB[indexB] 264 265 switch strings.Compare(coinA.Denom, coinB.Denom) { 266 case -1: // coin A denom < coin B denom 267 if !coinA.IsZero() { 268 sum = append(sum, coinA) 269 } 270 271 indexA++ 272 273 case 0: // coin A denom == coin B denom 274 res := coinA.Add(coinB) 275 if !res.IsZero() { 276 sum = append(sum, res) 277 } 278 279 indexA++ 280 indexB++ 281 282 case 1: // coin A denom > coin B denom 283 if !coinB.IsZero() { 284 sum = append(sum, coinB) 285 } 286 287 indexB++ 288 } 289 } 290 } 291 292 // negative returns a set of coins with all amount negative. 293 func (coins DecCoins) negative() DecCoins { 294 res := make([]DecCoin, 0, len(coins)) 295 for _, coin := range coins { 296 res = append(res, DecCoin{ 297 Denom: coin.Denom, 298 Amount: coin.Amount.Neg(), 299 }) 300 } 301 return res 302 } 303 304 // Sub subtracts a set of DecCoins from another (adds the inverse). 305 func (coins DecCoins) Sub(coinsB DecCoins) DecCoins { 306 diff, hasNeg := coins.SafeSub(coinsB) 307 if hasNeg { 308 panic("negative coin amount") 309 } 310 311 return diff 312 } 313 314 // SafeSub performs the same arithmetic as Sub but returns a boolean if any 315 // negative coin amount was returned. 316 func (coins DecCoins) SafeSub(coinsB DecCoins) (DecCoins, bool) { 317 diff := coins.safeAdd(coinsB.negative()) 318 return diff, diff.IsAnyNegative() 319 } 320 321 // Intersect will return a new set of coins which contains the minimum DecCoin 322 // for common denoms found in both `coins` and `coinsB`. For denoms not common 323 // to both `coins` and `coinsB` the minimum is considered to be 0, thus they 324 // are not added to the final set. In other words, trim any denom amount from 325 // coin which exceeds that of coinB, such that (coin.Intersect(coinB)).IsLTE(coinB). 326 // See also Coins.Min(). 327 func (coins DecCoins) Intersect(coinsB DecCoins) DecCoins { 328 res := make([]DecCoin, len(coins)) 329 for i, coin := range coins { 330 minCoin := DecCoin{ 331 Denom: coin.Denom, 332 Amount: math.LegacyMinDec(coin.Amount, coinsB.AmountOf(coin.Denom)), 333 } 334 res[i] = minCoin 335 } 336 return removeZeroDecCoins(res) 337 } 338 339 // GetDenomByIndex returns the Denom to make the findDup generic 340 func (coins DecCoins) GetDenomByIndex(i int) string { 341 return coins[i].Denom 342 } 343 344 // IsAnyNegative returns true if there is at least one coin whose amount 345 // is negative; returns false otherwise. It returns false if the DecCoins set 346 // is empty too. 347 // 348 // TODO: Remove once unsigned integers are used. 349 func (coins DecCoins) IsAnyNegative() bool { 350 for _, coin := range coins { 351 if coin.IsNegative() { 352 return true 353 } 354 } 355 356 return false 357 } 358 359 // MulDec multiplies all the coins by a decimal. 360 // 361 // CONTRACT: No zero coins will be returned. 362 func (coins DecCoins) MulDec(d math.LegacyDec) DecCoins { 363 var res DecCoins 364 for _, coin := range coins { 365 product := DecCoin{ 366 Denom: coin.Denom, 367 Amount: coin.Amount.Mul(d), 368 } 369 370 if !product.IsZero() { 371 res = res.Add(product) 372 } 373 } 374 375 return res 376 } 377 378 // MulDecTruncate multiplies all the decimal coins by a decimal, truncating. It 379 // returns nil DecCoins if d is zero. 380 // 381 // CONTRACT: No zero coins will be returned. 382 func (coins DecCoins) MulDecTruncate(d math.LegacyDec) DecCoins { 383 if d.IsZero() { 384 return DecCoins{} 385 } 386 387 var res DecCoins 388 for _, coin := range coins { 389 product := DecCoin{ 390 Denom: coin.Denom, 391 Amount: coin.Amount.MulTruncate(d), 392 } 393 394 if !product.IsZero() { 395 res = res.Add(product) 396 } 397 } 398 399 return res 400 } 401 402 // QuoDec divides all the decimal coins by a decimal. It panics if d is zero. 403 // 404 // CONTRACT: No zero coins will be returned. 405 func (coins DecCoins) QuoDec(d math.LegacyDec) DecCoins { 406 if d.IsZero() { 407 panic("invalid zero decimal") 408 } 409 410 var res DecCoins 411 for _, coin := range coins { 412 quotient := DecCoin{ 413 Denom: coin.Denom, 414 Amount: coin.Amount.Quo(d), 415 } 416 417 if !quotient.IsZero() { 418 res = res.Add(quotient) 419 } 420 } 421 422 return res 423 } 424 425 // QuoDecTruncate divides all the decimal coins by a decimal, truncating. It 426 // panics if d is zero. 427 // 428 // CONTRACT: No zero coins will be returned. 429 func (coins DecCoins) QuoDecTruncate(d math.LegacyDec) DecCoins { 430 if d.IsZero() { 431 panic("invalid zero decimal") 432 } 433 434 var res DecCoins 435 for _, coin := range coins { 436 quotient := DecCoin{ 437 Denom: coin.Denom, 438 Amount: coin.Amount.QuoTruncate(d), 439 } 440 441 if !quotient.IsZero() { 442 res = res.Add(quotient) 443 } 444 } 445 446 return res 447 } 448 449 // Empty returns true if there are no coins and false otherwise. 450 func (coins DecCoins) Empty() bool { 451 return len(coins) == 0 452 } 453 454 // AmountOf returns the amount of a denom from deccoins 455 func (coins DecCoins) AmountOf(denom string) math.LegacyDec { 456 mustValidateDenom(denom) 457 458 switch len(coins) { 459 case 0: 460 return math.LegacyZeroDec() 461 462 case 1: 463 coin := coins[0] 464 if coin.Denom == denom { 465 return coin.Amount 466 } 467 return math.LegacyZeroDec() 468 469 default: 470 midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 471 coin := coins[midIdx] 472 473 switch { 474 case denom < coin.Denom: 475 return coins[:midIdx].AmountOf(denom) 476 case denom == coin.Denom: 477 return coin.Amount 478 default: 479 return coins[midIdx+1:].AmountOf(denom) 480 } 481 } 482 } 483 484 // Equal returns true if the two sets of DecCoins have the same value. 485 func (coins DecCoins) Equal(coinsB DecCoins) bool { 486 if len(coins) != len(coinsB) { 487 return false 488 } 489 490 coins = coins.Sort() 491 coinsB = coinsB.Sort() 492 493 for i := 0; i < len(coins); i++ { 494 if !coins[i].Equal(coinsB[i]) { 495 return false 496 } 497 } 498 499 return true 500 } 501 502 // IsZero returns whether all coins are zero 503 func (coins DecCoins) IsZero() bool { 504 for _, coin := range coins { 505 if !coin.Amount.IsZero() { 506 return false 507 } 508 } 509 return true 510 } 511 512 // Validate checks that the DecCoins are sorted, have positive amount, with a valid and unique 513 // denomination (i.e no duplicates). Otherwise, it returns an error. 514 func (coins DecCoins) Validate() error { 515 switch len(coins) { 516 case 0: 517 return nil 518 519 case 1: 520 if err := ValidateDenom(coins[0].Denom); err != nil { 521 return err 522 } 523 if !coins[0].IsPositive() { 524 return fmt.Errorf("coin %s amount is not positive", coins[0]) 525 } 526 return nil 527 default: 528 // check single coin case 529 if err := (DecCoins{coins[0]}).Validate(); err != nil { 530 return err 531 } 532 533 lowDenom := coins[0].Denom 534 seenDenoms := make(map[string]bool) 535 seenDenoms[lowDenom] = true 536 537 for _, coin := range coins[1:] { 538 if seenDenoms[coin.Denom] { 539 return fmt.Errorf("duplicate denomination %s", coin.Denom) 540 } 541 if err := ValidateDenom(coin.Denom); err != nil { 542 return err 543 } 544 if coin.Denom <= lowDenom { 545 return fmt.Errorf("denomination %s is not sorted", coin.Denom) 546 } 547 if !coin.IsPositive() { 548 return fmt.Errorf("coin %s amount is not positive", coin.Denom) 549 } 550 551 // we compare each coin against the last denom 552 lowDenom = coin.Denom 553 seenDenoms[coin.Denom] = true 554 } 555 556 return nil 557 } 558 } 559 560 // IsValid calls Validate and returns true when the DecCoins are sorted, have positive amount, with a 561 // valid and unique denomination (i.e no duplicates). 562 func (coins DecCoins) IsValid() bool { 563 return coins.Validate() == nil 564 } 565 566 // IsAllPositive returns true if there is at least one coin and all currencies 567 // have a positive value. 568 // 569 // TODO: Remove once unsigned integers are used. 570 func (coins DecCoins) IsAllPositive() bool { 571 if len(coins) == 0 { 572 return false 573 } 574 575 for _, coin := range coins { 576 if !coin.IsPositive() { 577 return false 578 } 579 } 580 581 return true 582 } 583 584 func removeZeroDecCoins(coins DecCoins) DecCoins { 585 result := make([]DecCoin, 0, len(coins)) 586 587 for _, coin := range coins { 588 if !coin.IsZero() { 589 result = append(result, coin) 590 } 591 } 592 593 return result 594 } 595 596 //----------------------------------------------------------------------------- 597 // Sorting 598 599 var _ sort.Interface = DecCoins{} 600 601 // Len implements sort.Interface for DecCoins 602 func (coins DecCoins) Len() int { return len(coins) } 603 604 // Less implements sort.Interface for DecCoins 605 func (coins DecCoins) Less(i, j int) bool { return coins[i].Denom < coins[j].Denom } 606 607 // Swap implements sort.Interface for DecCoins 608 func (coins DecCoins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i] } 609 610 // Sort is a helper function to sort the set of decimal coins in-place. 611 func (coins DecCoins) Sort() DecCoins { 612 // sort.Sort(coins) does a costly runtime copy as part of `runtime.convTSlice` 613 // So we avoid this heap allocation if len(coins) <= 1. In the future, we should hopefully find 614 // a strategy to always avoid this. 615 if len(coins) > 1 { 616 sort.Sort(coins) 617 } 618 return coins 619 } 620 621 // ---------------------------------------------------------------------------- 622 // Parsing 623 624 // ParseDecCoin parses a decimal coin from a string, returning an error if 625 // invalid. An empty string is considered invalid. 626 func ParseDecCoin(coinStr string) (coin DecCoin, err error) { 627 coinStr = strings.TrimSpace(coinStr) 628 629 matches := reDecCoin.FindStringSubmatch(coinStr) 630 if matches == nil { 631 return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr) 632 } 633 634 amountStr, denomStr := matches[1], matches[2] 635 636 amount, err := math.LegacyNewDecFromStr(amountStr) 637 if err != nil { 638 return DecCoin{}, errors.Wrap(err, fmt.Sprintf("failed to parse decimal coin amount: %s", amountStr)) 639 } 640 641 if err := ValidateDenom(denomStr); err != nil { 642 return DecCoin{}, fmt.Errorf("invalid denom cannot contain spaces: %s", err) 643 } 644 645 return NewDecCoinFromDec(denomStr, amount), nil 646 } 647 648 // ParseDecCoins will parse out a list of decimal coins separated by commas. If the parsing is successuful, 649 // the provided coins will be sanitized by removing zero coins and sorting the coin set. Lastly 650 // a validation of the coin set is executed. If the check passes, ParseDecCoins will return the sanitized coins. 651 // Otherwise it will return an error. 652 // If an empty string is provided to ParseDecCoins, it returns nil Coins. 653 // Expected format: "{amount0}{denomination},...,{amountN}{denominationN}" 654 func ParseDecCoins(coinsStr string) (DecCoins, error) { 655 coinsStr = strings.TrimSpace(coinsStr) 656 if len(coinsStr) == 0 { 657 return nil, nil 658 } 659 660 coinStrs := strings.Split(coinsStr, ",") 661 decCoins := make(DecCoins, len(coinStrs)) 662 for i, coinStr := range coinStrs { 663 coin, err := ParseDecCoin(coinStr) 664 if err != nil { 665 return nil, err 666 } 667 668 decCoins[i] = coin 669 } 670 671 newDecCoins := sanitizeDecCoins(decCoins) 672 if err := newDecCoins.Validate(); err != nil { 673 return nil, err 674 } 675 676 return newDecCoins, nil 677 }