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