github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/types/coin_ibc.go (about)

     1  package types
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    10  )
    11  
    12  var (
    13  	// Denominations can be 3 ~ 128 characters long and support letters, followed by either
    14  	// a letter, a number or a separator ('/').
    15  	ibcReDnmString = `[a-zA-Z][a-zA-Z0-9/-]{2,127}`
    16  	ibcReDecAmt    = `[[:digit:]]+(?:\.[[:digit:]]+)?|\.[[:digit:]]+`
    17  	ibcReSpc       = `[[:space:]]*`
    18  	ibcReDnm       *regexp.Regexp
    19  	ibcReDecCoin   *regexp.Regexp
    20  )
    21  var ibcCoinDenomRegex = DefaultCoinDenomRegex
    22  
    23  func init() {
    24  	SetIBCCoinDenomRegex(DefaultIBCCoinDenomRegex)
    25  }
    26  
    27  func IBCParseDecCoin(coinStr string) (coin DecCoin, err error) {
    28  	coinStr = strings.TrimSpace(coinStr)
    29  
    30  	matches := ibcReDecCoin.FindStringSubmatch(coinStr)
    31  	if matches == nil {
    32  		return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr)
    33  	}
    34  
    35  	amountStr, denomStr := matches[1], matches[2]
    36  
    37  	amount, err := NewDecFromStr(amountStr)
    38  	if err != nil {
    39  		return DecCoin{}, errors.Wrap(err, fmt.Sprintf("failed to parse decimal coin amount: %s", amountStr))
    40  	}
    41  
    42  	if err := ValidateDenom(denomStr); err != nil {
    43  		return DecCoin{}, fmt.Errorf("invalid denom cannot contain upper case characters or spaces: %s", err)
    44  	}
    45  
    46  	return NewDecCoinFromDec(denomStr, amount), nil
    47  }
    48  
    49  // DefaultCoinDenomRegex returns the default regex string
    50  func DefaultIBCCoinDenomRegex() string {
    51  	return ibcReDnmString
    52  }
    53  
    54  func SetIBCCoinDenomRegex(reFn func() string) {
    55  	ibcCoinDenomRegex = reFn
    56  
    57  	ibcReDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, ibcCoinDenomRegex()))
    58  	ibcReDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, ibcReDecAmt, ibcReSpc, ibcCoinDenomRegex()))
    59  }
    60  
    61  type CoinAdapters []CoinAdapter
    62  
    63  // NewCoin returns a new coin with a denomination and amount. It will panic if
    64  // the amount is negative or if the denomination is invalid.
    65  func NewCoinAdapter(denom string, amount Int) CoinAdapter {
    66  	coin := CoinAdapter{
    67  		Denom:  denom,
    68  		Amount: amount,
    69  	}
    70  
    71  	if err := coin.Validate(); err != nil {
    72  		panic(err)
    73  	}
    74  
    75  	return coin
    76  }
    77  func (cs CoinAdapter) ToCoin() Coin {
    78  	if cs.Denom == DefaultIbcWei {
    79  		cs.Denom = DefaultBondDenom
    80  	}
    81  	return CoinAdapterToCoin(cs)
    82  }
    83  func (cs CoinAdapters) ToCoins() Coins {
    84  	ret := make([]Coin, 0)
    85  	for _, v := range cs {
    86  		ret = append(ret, v.ToCoin())
    87  	}
    88  	return ret
    89  }
    90  
    91  func (cas CoinAdapters) IsAnyNegative() bool {
    92  	for _, coin := range cas {
    93  		if coin.Amount.IsNegative() {
    94  			return true
    95  		}
    96  	}
    97  
    98  	return false
    99  }
   100  
   101  func (cas CoinAdapters) IsAnyNil() bool {
   102  	for _, coin := range cas {
   103  		if coin.Amount.IsNil() {
   104  			return true
   105  		}
   106  	}
   107  
   108  	return false
   109  }
   110  
   111  func (coins CoinAdapters) Add(coinsB ...CoinAdapter) CoinAdapters {
   112  	return coins.safeAdd(coinsB)
   113  }
   114  func (coins CoinAdapters) safeAdd(coinsB CoinAdapters) CoinAdapters {
   115  	// probably the best way will be to make Coins and interface and hide the structure
   116  	// definition (type alias)
   117  	if !coins.isSorted() {
   118  		panic("Coins (self) must be sorted")
   119  	}
   120  	if !coinsB.isSorted() {
   121  		panic("Wrong argument: coins must be sorted")
   122  	}
   123  
   124  	sum := ([]CoinAdapter)(nil)
   125  	indexA, indexB := 0, 0
   126  	lenA, lenB := len(coins), len(coinsB)
   127  
   128  	for {
   129  		if indexA == lenA {
   130  			if indexB == lenB {
   131  				// return nil coins if both sets are empty
   132  				return sum
   133  			}
   134  
   135  			// return set B (excluding zero coins) if set A is empty
   136  			return append(sum, removeZeroCoinAdapters(coinsB[indexB:])...)
   137  		} else if indexB == lenB {
   138  			// return set A (excluding zero coins) if set B is empty
   139  			return append(sum, removeZeroCoinAdapters(coins[indexA:])...)
   140  		}
   141  
   142  		coinA, coinB := coins[indexA], coinsB[indexB]
   143  
   144  		switch strings.Compare(coinA.Denom, coinB.Denom) {
   145  		case -1: // coin A denom < coin B denom
   146  			if !coinA.IsZero() {
   147  				sum = append(sum, coinA)
   148  			}
   149  
   150  			indexA++
   151  
   152  		case 0: // coin A denom == coin B denom
   153  			res := coinA.Add(coinB)
   154  			if !res.IsZero() {
   155  				sum = append(sum, res)
   156  			}
   157  
   158  			indexA++
   159  			indexB++
   160  
   161  		case 1: // coin A denom > coin B denom
   162  			if !coinB.IsZero() {
   163  				sum = append(sum, coinB)
   164  			}
   165  
   166  			indexB++
   167  		}
   168  	}
   169  }
   170  
   171  // ParseCoinsNormalized will parse out a list of coins separated by commas, and normalize them by converting to smallest
   172  // unit. If the parsing is successuful, the provided coins will be sanitized by removing zero coins and sorting the coin
   173  // set. Lastly a validation of the coin set is executed. If the check passes, ParseCoinsNormalized will return the
   174  // sanitized coins.
   175  // Otherwise it will return an error.
   176  // If an empty string is provided to ParseCoinsNormalized, it returns nil Coins.
   177  // ParseCoinsNormalized supports decimal coins as inputs, and truncate them to int after converted to smallest unit.
   178  // Expected format: "{amount0}{denomination},...,{amountN}{denominationN}"
   179  func ParseCoinsNormalized(coinStr string) (Coins, error) {
   180  	coins, err := ParseDecCoins(coinStr)
   181  	if err != nil {
   182  		return Coins{}, err
   183  	}
   184  	return NormalizeCoins(coins), nil
   185  }
   186  
   187  // ParseCoinNormalized parses and normalize a cli input for one coin type, returning errors if invalid or on an empty string
   188  // as well.
   189  // Expected format: "{amount}{denomination}"
   190  func ParseCoinNormalized(coinStr string) (coin Coin, err error) {
   191  	decCoin, err := ParseDecCoin(coinStr)
   192  	if err != nil {
   193  		return Coin{}, err
   194  	}
   195  
   196  	coin, _ = NormalizeDecCoin(decCoin).TruncateDecimal()
   197  	return coin, nil
   198  }
   199  
   200  // IsValid calls Validate and returns true when the Coins are sorted, have positive amount, with a
   201  // valid and unique denomination (i.e no duplicates).
   202  func (coins CoinAdapters) IsValid() bool {
   203  	return coins.Validate() == nil
   204  }
   205  
   206  func (coins CoinAdapters) Validate() error {
   207  	switch len(coins) {
   208  	case 0:
   209  		return nil
   210  
   211  	case 1:
   212  		if err := ValidateDenom(coins[0].Denom); err != nil {
   213  			return err
   214  		}
   215  		if coins[0].Amount.IsNil() {
   216  			return fmt.Errorf("coin %s amount is nil", coins[0])
   217  		}
   218  		if !coins[0].IsPositive() {
   219  			return fmt.Errorf("coin %s amount is not positive", coins[0])
   220  		}
   221  		return nil
   222  
   223  	default:
   224  		// check single coin case
   225  		if err := (CoinAdapters{coins[0]}).Validate(); err != nil {
   226  			return err
   227  		}
   228  
   229  		lowDenom := coins[0].Denom
   230  		seenDenoms := make(map[string]bool)
   231  		seenDenoms[lowDenom] = true
   232  
   233  		for _, coin := range coins[1:] {
   234  			if seenDenoms[coin.Denom] {
   235  				return fmt.Errorf("duplicate denomination %s", coin.Denom)
   236  			}
   237  			if err := ValidateDenom(coin.Denom); err != nil {
   238  				return err
   239  			}
   240  			if coin.Denom <= lowDenom {
   241  				return fmt.Errorf("denomination %s is not sorted", coin.Denom)
   242  			}
   243  			if !coin.IsPositive() {
   244  				return fmt.Errorf("coin %s amount is not positive", coin.Denom)
   245  			}
   246  
   247  			// we compare each coin against the last denom
   248  			lowDenom = coin.Denom
   249  			seenDenoms[coin.Denom] = true
   250  		}
   251  
   252  		return nil
   253  	}
   254  }
   255  
   256  func (coins CoinAdapters) isSorted() bool {
   257  	for i := 1; i < len(coins); i++ {
   258  		if coins[i-1].Denom > coins[i].Denom {
   259  			return false
   260  		}
   261  	}
   262  	return true
   263  }
   264  
   265  func (coins CoinAdapters) String() string {
   266  	if len(coins) == 0 {
   267  		return ""
   268  	}
   269  
   270  	out := ""
   271  	for _, coin := range coins {
   272  		out += fmt.Sprintf("%v,", coin.String())
   273  	}
   274  	return out[:len(out)-1]
   275  }
   276  
   277  // IsAllPositive returns true if there is at least one coin and all currencies
   278  // have a positive value.
   279  func (coins CoinAdapters) IsAllPositive() bool {
   280  	if len(coins) == 0 {
   281  		return false
   282  	}
   283  
   284  	for _, coin := range coins {
   285  		if !coin.IsPositive() {
   286  			return false
   287  		}
   288  	}
   289  
   290  	return true
   291  }
   292  
   293  type coinAdaptersJSON CoinAdapters
   294  
   295  // MarshalJSON implements a custom JSON marshaller for the Coins type to allow
   296  // nil Coins to be encoded as an empty array.
   297  func (coins CoinAdapters) MarshalJSON() ([]byte, error) {
   298  	if coins == nil {
   299  		return json.Marshal(coinAdaptersJSON(CoinAdapters{}))
   300  	}
   301  
   302  	return json.Marshal(coinAdaptersJSON(coins))
   303  }
   304  
   305  func (coins CoinAdapters) Copy() CoinAdapters {
   306  	copyCoins := make(CoinAdapters, len(coins))
   307  
   308  	for i, coin := range coins {
   309  		copyCoins[i] = coin
   310  	}
   311  
   312  	return copyCoins
   313  }
   314  
   315  func ConvWei2Tfibo(adapters CoinAdapters) (CoinAdapters, error) {
   316  	copyAdapters := adapters.Copy()
   317  	for index, _ := range copyAdapters {
   318  		if copyAdapters[index].Denom == DefaultIbcWei {
   319  			copyAdapters[index].Denom = DefaultBondDenom
   320  		} else if strings.ToLower(copyAdapters[index].Denom) == DefaultBondDenom {
   321  			return nil, errors.Wrap(errors.ErrInvalidCoins, "not support fibo denom")
   322  		}
   323  	}
   324  	return copyAdapters, nil
   325  }
   326  
   327  // AmountOf returns the amount of a denom from coins
   328  func (coins CoinAdapters) AmountOf(denom string) Int {
   329  	mustValidateDenom(denom)
   330  	return coins.AmountOfNoDenomValidation(denom)
   331  }
   332  
   333  // AmountOfNoDenomValidation returns the amount of a denom from coins
   334  // without validating the denomination.
   335  func (coins CoinAdapters) AmountOfNoDenomValidation(denom string) Int {
   336  	switch len(coins) {
   337  	case 0:
   338  		return ZeroInt()
   339  
   340  	case 1:
   341  		coin := coins[0]
   342  		if coin.Denom == denom {
   343  			return coin.Amount
   344  		}
   345  		return ZeroInt()
   346  
   347  	default:
   348  		// Binary search the amount of coins remaining
   349  		midIdx := len(coins) / 2 // 2:1, 3:1, 4:2
   350  		coin := coins[midIdx]
   351  		switch {
   352  		case denom < coin.Denom:
   353  			return coins[:midIdx].AmountOfNoDenomValidation(denom)
   354  		case denom == coin.Denom:
   355  			return coin.Amount
   356  		default:
   357  			return coins[midIdx+1:].AmountOfNoDenomValidation(denom)
   358  		}
   359  	}
   360  }
   361  
   362  func (coins CoinAdapters) Sub(coinsB CoinAdapters) CoinAdapters {
   363  	diff, hasNeg := coins.SafeSub(coinsB)
   364  	if hasNeg {
   365  		panic("negative coin amount")
   366  	}
   367  
   368  	return diff
   369  }
   370  
   371  func (coins CoinAdapters) SafeSub(coinsB CoinAdapters) (CoinAdapters, bool) {
   372  	diff := coins.safeAdd(coinsB.negative())
   373  	return diff, diff.IsAnyNegative()
   374  }
   375  func (coins CoinAdapters) negative() CoinAdapters {
   376  	res := make([]CoinAdapter, 0, len(coins))
   377  
   378  	for _, coin := range coins {
   379  		res = append(res, CoinAdapter{
   380  			Denom:  coin.Denom,
   381  			Amount: coin.Amount.Neg(),
   382  		})
   383  	}
   384  
   385  	return res
   386  }
   387  
   388  // AddAmount adds an amount to the Coin.
   389  func (coin CoinAdapter) AddAmount(amount Int) CoinAdapter {
   390  	return CoinAdapter{coin.Denom, coin.Amount.Add(amount)}
   391  }