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  }