398 lines
9.5 KiB
Go
398 lines
9.5 KiB
Go
package types
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Decimal Coin
|
|
|
|
// Coins which can have additional decimal points
|
|
type DecCoin struct {
|
|
Denom string `json:"denom"`
|
|
Amount Dec `json:"amount"`
|
|
}
|
|
|
|
func NewDecCoin(denom string, amount int64) DecCoin {
|
|
if amount < 0 {
|
|
panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount))
|
|
}
|
|
if strings.ToLower(denom) != denom {
|
|
panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom))
|
|
}
|
|
|
|
return DecCoin{
|
|
Denom: denom,
|
|
Amount: NewDec(amount),
|
|
}
|
|
}
|
|
|
|
func NewDecCoinFromDec(denom string, amount Dec) DecCoin {
|
|
if amount.LT(ZeroDec()) {
|
|
panic(fmt.Sprintf("negative decimal coin amount: %v\n", amount))
|
|
}
|
|
if strings.ToLower(denom) != denom {
|
|
panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", denom))
|
|
}
|
|
|
|
return DecCoin{
|
|
Denom: denom,
|
|
Amount: amount,
|
|
}
|
|
}
|
|
|
|
func NewDecCoinFromCoin(coin Coin) DecCoin {
|
|
if coin.Amount.LT(ZeroInt()) {
|
|
panic(fmt.Sprintf("negative decimal coin amount: %v\n", coin.Amount))
|
|
}
|
|
if strings.ToLower(coin.Denom) != coin.Denom {
|
|
panic(fmt.Sprintf("denom cannot contain upper case characters: %s\n", coin.Denom))
|
|
}
|
|
|
|
return DecCoin{
|
|
Denom: coin.Denom,
|
|
Amount: NewDecFromInt(coin.Amount),
|
|
}
|
|
}
|
|
|
|
// Adds amounts of two coins with same denom
|
|
func (coin DecCoin) Plus(coinB DecCoin) DecCoin {
|
|
if coin.Denom != coinB.Denom {
|
|
panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom))
|
|
}
|
|
return DecCoin{coin.Denom, coin.Amount.Add(coinB.Amount)}
|
|
}
|
|
|
|
// Subtracts amounts of two coins with same denom
|
|
func (coin DecCoin) Minus(coinB DecCoin) DecCoin {
|
|
if coin.Denom != coinB.Denom {
|
|
panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom))
|
|
}
|
|
return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)}
|
|
}
|
|
|
|
// return the decimal coins with trunctated decimals, and return the change
|
|
func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) {
|
|
truncated := coin.Amount.TruncateInt()
|
|
change := coin.Amount.Sub(NewDecFromInt(truncated))
|
|
return NewCoin(coin.Denom, truncated), DecCoin{coin.Denom, change}
|
|
}
|
|
|
|
// IsPositive returns true if coin amount is positive.
|
|
//
|
|
// TODO: Remove once unsigned integers are used.
|
|
func (coin DecCoin) IsPositive() bool {
|
|
return coin.Amount.IsPositive()
|
|
}
|
|
|
|
// String implements the Stringer interface for DecCoin. It returns a
|
|
// human-readable representation of a decimal coin.
|
|
func (coin DecCoin) String() string {
|
|
return fmt.Sprintf("%v%v", coin.Amount, coin.Denom)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Decimal Coins
|
|
|
|
// coins with decimal
|
|
type DecCoins []DecCoin
|
|
|
|
func NewDecCoins(coins Coins) DecCoins {
|
|
dcs := make(DecCoins, len(coins))
|
|
for i, coin := range coins {
|
|
dcs[i] = NewDecCoinFromCoin(coin)
|
|
}
|
|
return dcs
|
|
}
|
|
|
|
// String implements the Stringer interface for DecCoins. It returns a
|
|
// human-readable representation of decimal coins.
|
|
func (coins DecCoins) String() string {
|
|
if len(coins) == 0 {
|
|
return ""
|
|
}
|
|
|
|
out := ""
|
|
for _, coin := range coins {
|
|
out += fmt.Sprintf("%v,", coin.String())
|
|
}
|
|
|
|
return out[:len(out)-1]
|
|
}
|
|
|
|
// return the coins with trunctated decimals, and return the change
|
|
func (coins DecCoins) TruncateDecimal() (Coins, DecCoins) {
|
|
changeSum := DecCoins{}
|
|
out := make(Coins, len(coins))
|
|
for i, coin := range coins {
|
|
truncated, change := coin.TruncateDecimal()
|
|
out[i] = truncated
|
|
changeSum = changeSum.Plus(DecCoins{change})
|
|
}
|
|
return out, changeSum
|
|
}
|
|
|
|
// Plus combines two sets of coins
|
|
// CONTRACT: Plus will never return Coins where one Coin has a 0 amount.
|
|
func (coins DecCoins) Plus(coinsB DecCoins) DecCoins {
|
|
sum := ([]DecCoin)(nil)
|
|
indexA, indexB := 0, 0
|
|
lenA, lenB := len(coins), len(coinsB)
|
|
for {
|
|
if indexA == lenA {
|
|
if indexB == lenB {
|
|
return sum
|
|
}
|
|
return append(sum, coinsB[indexB:]...)
|
|
} else if indexB == lenB {
|
|
return append(sum, coins[indexA:]...)
|
|
}
|
|
coinA, coinB := coins[indexA], coinsB[indexB]
|
|
switch strings.Compare(coinA.Denom, coinB.Denom) {
|
|
case -1:
|
|
sum = append(sum, coinA)
|
|
indexA++
|
|
case 0:
|
|
if coinA.Amount.Add(coinB.Amount).IsZero() {
|
|
// ignore 0 sum coin type
|
|
} else {
|
|
sum = append(sum, coinA.Plus(coinB))
|
|
}
|
|
indexA++
|
|
indexB++
|
|
case 1:
|
|
sum = append(sum, coinB)
|
|
indexB++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Negative returns a set of coins with all amount negative
|
|
func (coins DecCoins) Negative() DecCoins {
|
|
res := make([]DecCoin, 0, len(coins))
|
|
for _, coin := range coins {
|
|
res = append(res, DecCoin{
|
|
Denom: coin.Denom,
|
|
Amount: coin.Amount.Neg(),
|
|
})
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Minus subtracts a set of coins from another (adds the inverse)
|
|
func (coins DecCoins) Minus(coinsB DecCoins) DecCoins {
|
|
return coins.Plus(coinsB.Negative())
|
|
}
|
|
|
|
// multiply all the coins by a decimal
|
|
func (coins DecCoins) MulDec(d Dec) DecCoins {
|
|
res := make([]DecCoin, len(coins))
|
|
for i, coin := range coins {
|
|
product := DecCoin{
|
|
Denom: coin.Denom,
|
|
Amount: coin.Amount.Mul(d),
|
|
}
|
|
res[i] = product
|
|
}
|
|
return res
|
|
}
|
|
|
|
// multiply all the coins by a decimal, truncating
|
|
func (coins DecCoins) MulDecTruncate(d Dec) DecCoins {
|
|
res := make([]DecCoin, len(coins))
|
|
for i, coin := range coins {
|
|
product := DecCoin{
|
|
Denom: coin.Denom,
|
|
Amount: coin.Amount.MulTruncate(d),
|
|
}
|
|
res[i] = product
|
|
}
|
|
return res
|
|
}
|
|
|
|
// divide all the coins by a decimal
|
|
func (coins DecCoins) QuoDec(d Dec) DecCoins {
|
|
res := make([]DecCoin, len(coins))
|
|
for i, coin := range coins {
|
|
quotient := DecCoin{
|
|
Denom: coin.Denom,
|
|
Amount: coin.Amount.Quo(d),
|
|
}
|
|
res[i] = quotient
|
|
}
|
|
return res
|
|
}
|
|
|
|
// divide all the coins by a decimal, truncating
|
|
func (coins DecCoins) QuoDecTruncate(d Dec) DecCoins {
|
|
res := make([]DecCoin, len(coins))
|
|
for i, coin := range coins {
|
|
quotient := DecCoin{
|
|
Denom: coin.Denom,
|
|
Amount: coin.Amount.QuoTruncate(d),
|
|
}
|
|
res[i] = quotient
|
|
}
|
|
return res
|
|
}
|
|
|
|
// returns the amount of a denom from deccoins
|
|
func (coins DecCoins) AmountOf(denom string) Dec {
|
|
switch len(coins) {
|
|
case 0:
|
|
return ZeroDec()
|
|
case 1:
|
|
coin := coins[0]
|
|
if coin.Denom == denom {
|
|
return coin.Amount
|
|
}
|
|
return ZeroDec()
|
|
default:
|
|
midIdx := len(coins) / 2 // binary search
|
|
coin := coins[midIdx]
|
|
if denom < coin.Denom {
|
|
return coins[:midIdx].AmountOf(denom)
|
|
} else if denom == coin.Denom {
|
|
return coin.Amount
|
|
} else {
|
|
return coins[midIdx+1:].AmountOf(denom)
|
|
}
|
|
}
|
|
}
|
|
|
|
// has a negative DecCoin amount
|
|
func (coins DecCoins) HasNegative() bool {
|
|
for _, coin := range coins {
|
|
if coin.Amount.IsNegative() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// return whether all coins are zero
|
|
func (coins DecCoins) IsZero() bool {
|
|
for _, coin := range coins {
|
|
if !coin.Amount.IsZero() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsValid asserts the DecCoins are sorted, have positive amount, and Denom
|
|
// does not contain upper case characters.
|
|
func (coins DecCoins) IsValid() bool {
|
|
switch len(coins) {
|
|
case 0:
|
|
return true
|
|
|
|
case 1:
|
|
if strings.ToLower(coins[0].Denom) != coins[0].Denom {
|
|
return false
|
|
}
|
|
return coins[0].IsPositive()
|
|
|
|
default:
|
|
// check single coin case
|
|
if !(DecCoins{coins[0]}).IsValid() {
|
|
return false
|
|
}
|
|
|
|
lowDenom := coins[0].Denom
|
|
for _, coin := range coins[1:] {
|
|
if strings.ToLower(coin.Denom) != coin.Denom {
|
|
return false
|
|
}
|
|
if coin.Denom <= lowDenom {
|
|
return false
|
|
}
|
|
if !coin.IsPositive() {
|
|
return false
|
|
}
|
|
|
|
// we compare each coin against the last denom
|
|
lowDenom = coin.Denom
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sorting
|
|
|
|
var _ sort.Interface = Coins{}
|
|
|
|
//nolint
|
|
func (coins DecCoins) Len() int { return len(coins) }
|
|
func (coins DecCoins) Less(i, j int) bool { return coins[i].Denom < coins[j].Denom }
|
|
func (coins DecCoins) Swap(i, j int) { coins[i], coins[j] = coins[j], coins[i] }
|
|
|
|
// Sort is a helper function to sort the set of decimal coins in-place.
|
|
func (coins DecCoins) Sort() DecCoins {
|
|
sort.Sort(coins)
|
|
return coins
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Parsing
|
|
|
|
// ParseDecCoin parses a decimal coin from a string, returning an error if
|
|
// invalid. An empty string is considered invalid.
|
|
func ParseDecCoin(coinStr string) (coin DecCoin, err error) {
|
|
coinStr = strings.TrimSpace(coinStr)
|
|
|
|
matches := reDecCoin.FindStringSubmatch(coinStr)
|
|
if matches == nil {
|
|
return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr)
|
|
}
|
|
|
|
amountStr, denomStr := matches[1], matches[2]
|
|
|
|
amount, err := NewDecFromStr(amountStr)
|
|
if err != nil {
|
|
return DecCoin{}, errors.Wrap(err, fmt.Sprintf("failed to parse decimal coin amount: %s", amountStr))
|
|
}
|
|
|
|
if denomStr != strings.ToLower(denomStr) {
|
|
return DecCoin{}, fmt.Errorf("denom cannot contain upper case characters: %s", denomStr)
|
|
}
|
|
|
|
return NewDecCoinFromDec(denomStr, amount), nil
|
|
}
|
|
|
|
// ParseDecCoins will parse out a list of decimal coins separated by commas.
|
|
// If nothing is provided, it returns nil DecCoins. Returned decimal coins are
|
|
// sorted.
|
|
func ParseDecCoins(coinsStr string) (coins DecCoins, err error) {
|
|
coinsStr = strings.TrimSpace(coinsStr)
|
|
if len(coinsStr) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
coinStrs := strings.Split(coinsStr, ",")
|
|
for _, coinStr := range coinStrs {
|
|
coin, err := ParseDecCoin(coinStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coins = append(coins, coin)
|
|
}
|
|
|
|
// sort coins for determinism
|
|
coins.Sort()
|
|
|
|
// validate coins before returning
|
|
if !coins.IsValid() {
|
|
return nil, fmt.Errorf("parsed decimal coins are invalid: %#v", coins)
|
|
}
|
|
|
|
return coins, nil
|
|
}
|