Refactor Gas/Fee Model (#3258)
This commit is contained in:
committed by
Jack Zampolin
parent
8f7a222308
commit
36d1736a08
+6
-39
@@ -299,41 +299,6 @@ func (coins Coins) IsAllLTE(coinsB Coins) bool {
|
||||
return coinsB.IsAllGTE(coins)
|
||||
}
|
||||
|
||||
// IsAnyGTE returns true iff coins contains at least one denom that is present
|
||||
// at a greater or equal amount in coinsB; it returns false otherwise.
|
||||
//
|
||||
// NOTE: IsAnyGTE operates under the invariant that coins are sorted by
|
||||
// denominations.
|
||||
func (coins Coins) IsAnyGTE(coinsB Coins) bool {
|
||||
if len(coinsB) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
j := 0
|
||||
for _, coin := range coins {
|
||||
searchOther := true // terminator in case coins breaks the sorted invariant
|
||||
|
||||
for j < len(coinsB) && searchOther {
|
||||
switch strings.Compare(coin.Denom, coinsB[j].Denom) {
|
||||
case -1:
|
||||
// coin denom in less than the current other coin, so move to next coin
|
||||
searchOther = false
|
||||
case 0:
|
||||
if coin.IsGTE(coinsB[j]) {
|
||||
return true
|
||||
}
|
||||
|
||||
fallthrough // skip to next other coin
|
||||
case 1:
|
||||
// coin denom is greater than the current other coin, so move to next other coin
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsZero returns true if there are no coins or all coins are zero.
|
||||
func (coins Coins) IsZero() bool {
|
||||
for _, coin := range coins {
|
||||
@@ -492,10 +457,12 @@ func (coins Coins) Sort() Coins {
|
||||
|
||||
var (
|
||||
// Denominations can be 3 ~ 16 characters long.
|
||||
reDnm = `[[:alpha:]][[:alnum:]]{2,15}`
|
||||
reAmt = `[[:digit:]]+`
|
||||
reSpc = `[[:space:]]*`
|
||||
reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnm))
|
||||
reDnm = `[[:alpha:]][[:alnum:]]{2,15}`
|
||||
reAmt = `[[:digit:]]+`
|
||||
reDecAmt = `[[:digit:]]*\.[[:digit:]]+`
|
||||
reSpc = `[[:space:]]*`
|
||||
reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnm))
|
||||
reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, reDnm))
|
||||
)
|
||||
|
||||
// ParseCoin parses a cli input for one coin type, returning errors if invalid.
|
||||
|
||||
@@ -368,22 +368,6 @@ func TestCoinsLTE(t *testing.T) {
|
||||
assert.True(t, Coins{}.IsAllLTE(Coins{{"a", one}}))
|
||||
}
|
||||
|
||||
func TestCoinsIsAnyGTE(t *testing.T) {
|
||||
one := NewInt(1)
|
||||
two := NewInt(2)
|
||||
|
||||
assert.False(t, Coins{}.IsAnyGTE(Coins{}))
|
||||
assert.False(t, Coins{{"a", one}}.IsAnyGTE(Coins{}))
|
||||
assert.False(t, Coins{}.IsAnyGTE(Coins{{"a", one}}))
|
||||
assert.False(t, Coins{{"a", one}}.IsAnyGTE(Coins{{"a", two}}))
|
||||
assert.True(t, Coins{{"a", one}, {"b", two}}.IsAnyGTE(Coins{{"a", two}, {"b", one}}))
|
||||
assert.True(t, Coins{{"a", one}}.IsAnyGTE(Coins{{"a", one}}))
|
||||
assert.True(t, Coins{{"a", two}}.IsAnyGTE(Coins{{"a", one}}))
|
||||
assert.True(t, Coins{{"a", one}}.IsAnyGTE(Coins{{"a", one}, {"b", two}}))
|
||||
assert.True(t, Coins{{"a", one}, {"b", two}}.IsAnyGTE(Coins{{"a", one}, {"b", one}}))
|
||||
assert.True(t, Coins{{"a", one}, {"b", one}}.IsAnyGTE(Coins{{"a", one}, {"b", two}}))
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
one := NewInt(1)
|
||||
|
||||
|
||||
+5
-5
@@ -47,7 +47,7 @@ func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Lo
|
||||
c = c.WithLogger(logger)
|
||||
c = c.WithVoteInfos(nil)
|
||||
c = c.WithGasMeter(NewInfiniteGasMeter())
|
||||
c = c.WithMinimumFees(Coins{})
|
||||
c = c.WithMinGasPrices(DecCoins{})
|
||||
c = c.WithConsensusParams(nil)
|
||||
return c
|
||||
}
|
||||
@@ -141,7 +141,7 @@ const (
|
||||
contextKeyVoteInfos
|
||||
contextKeyGasMeter
|
||||
contextKeyBlockGasMeter
|
||||
contextKeyMinimumFees
|
||||
contextKeyMinGasPrices
|
||||
contextKeyConsensusParams
|
||||
)
|
||||
|
||||
@@ -169,7 +169,7 @@ func (c Context) BlockGasMeter() GasMeter { return c.Value(contextKeyBlockGasMet
|
||||
|
||||
func (c Context) IsCheckTx() bool { return c.Value(contextKeyIsCheckTx).(bool) }
|
||||
|
||||
func (c Context) MinimumFees() Coins { return c.Value(contextKeyMinimumFees).(Coins) }
|
||||
func (c Context) MinGasPrices() DecCoins { return c.Value(contextKeyMinGasPrices).(DecCoins) }
|
||||
|
||||
func (c Context) ConsensusParams() *abci.ConsensusParams {
|
||||
return c.Value(contextKeyConsensusParams).(*abci.ConsensusParams)
|
||||
@@ -222,8 +222,8 @@ func (c Context) WithIsCheckTx(isCheckTx bool) Context {
|
||||
return c.withValue(contextKeyIsCheckTx, isCheckTx)
|
||||
}
|
||||
|
||||
func (c Context) WithMinimumFees(minFees Coins) Context {
|
||||
return c.withValue(contextKeyMinimumFees, minFees)
|
||||
func (c Context) WithMinGasPrices(gasPrices DecCoins) Context {
|
||||
return c.withValue(contextKeyMinGasPrices, gasPrices)
|
||||
}
|
||||
|
||||
func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context {
|
||||
|
||||
@@ -163,7 +163,7 @@ func TestContextWithCustom(t *testing.T) {
|
||||
logger := NewMockLogger()
|
||||
voteinfos := []abci.VoteInfo{{}}
|
||||
meter := types.NewGasMeter(10000)
|
||||
minFees := types.Coins{types.NewInt64Coin("feetoken", 1)}
|
||||
minGasPrices := types.DecCoins{types.NewDecCoin("feetoken", 1)}
|
||||
|
||||
ctx = types.NewContext(nil, header, ischeck, logger)
|
||||
require.Equal(t, header, ctx.BlockHeader())
|
||||
@@ -174,7 +174,7 @@ func TestContextWithCustom(t *testing.T) {
|
||||
WithTxBytes(txbytes).
|
||||
WithVoteInfos(voteinfos).
|
||||
WithGasMeter(meter).
|
||||
WithMinimumFees(minFees)
|
||||
WithMinGasPrices(minGasPrices)
|
||||
require.Equal(t, height, ctx.BlockHeight())
|
||||
require.Equal(t, chainid, ctx.ChainID())
|
||||
require.Equal(t, ischeck, ctx.IsCheckTx())
|
||||
@@ -182,5 +182,5 @@ func TestContextWithCustom(t *testing.T) {
|
||||
require.Equal(t, logger, ctx.Logger())
|
||||
require.Equal(t, voteinfos, ctx.VoteInfos())
|
||||
require.Equal(t, meter, ctx.GasMeter())
|
||||
require.Equal(t, minFees, types.Coins{types.NewInt64Coin("feetoken", 1)})
|
||||
require.Equal(t, minGasPrices, ctx.MinGasPrices())
|
||||
}
|
||||
|
||||
+169
-1
@@ -2,9 +2,15 @@ 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"`
|
||||
@@ -12,6 +18,13 @@ type DecCoin struct {
|
||||
}
|
||||
|
||||
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),
|
||||
@@ -19,6 +32,13 @@ func NewDecCoin(denom string, amount int64) DecCoin {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -26,6 +46,13 @@ func NewDecCoinFromDec(denom string, amount Dec) DecCoin {
|
||||
}
|
||||
|
||||
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),
|
||||
@@ -55,7 +82,21 @@ func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) {
|
||||
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
|
||||
@@ -68,6 +109,21 @@ func NewDecCoins(coins Coins) DecCoins {
|
||||
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{}
|
||||
@@ -201,3 +257,115 @@ func (coins DecCoins) IsZero() bool {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+189
-12
@@ -3,30 +3,80 @@ package types
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewDecCoin(t *testing.T) {
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoin("a", 5)
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoin("a", 0)
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoin("A", 5)
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoin("a", -5)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDecCoinFromDec(t *testing.T) {
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromDec("a", NewDec(5))
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromDec("a", ZeroDec())
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromDec("A", NewDec(5))
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromDec("a", NewDec(-5))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDecCoinFromCoin(t *testing.T) {
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"a", NewInt(5)})
|
||||
})
|
||||
require.NotPanics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"a", NewInt(0)})
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"A", NewInt(5)})
|
||||
})
|
||||
require.Panics(t, func() {
|
||||
NewDecCoinFromCoin(Coin{"a", NewInt(-5)})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecCoinIsPositive(t *testing.T) {
|
||||
dc := NewDecCoin("a", 5)
|
||||
require.True(t, dc.IsPositive())
|
||||
|
||||
dc = NewDecCoin("a", 0)
|
||||
require.False(t, dc.IsPositive())
|
||||
}
|
||||
|
||||
func TestPlusDecCoin(t *testing.T) {
|
||||
decCoinA1 := DecCoin{"A", NewDecWithPrec(11, 1)}
|
||||
decCoinA2 := DecCoin{"A", NewDecWithPrec(22, 1)}
|
||||
decCoinB1 := DecCoin{"B", NewDecWithPrec(11, 1)}
|
||||
decCoinA1 := NewDecCoinFromDec("a", NewDecWithPrec(11, 1))
|
||||
decCoinA2 := NewDecCoinFromDec("a", NewDecWithPrec(22, 1))
|
||||
decCoinB1 := NewDecCoinFromDec("b", NewDecWithPrec(11, 1))
|
||||
|
||||
// regular add
|
||||
res := decCoinA1.Plus(decCoinA1)
|
||||
require.Equal(t, decCoinA2, res, "sum of coins is incorrect")
|
||||
|
||||
// bad denom add
|
||||
assert.Panics(t, func() {
|
||||
require.Panics(t, func() {
|
||||
decCoinA1.Plus(decCoinB1)
|
||||
}, "expected panic on sum of different denoms")
|
||||
|
||||
}
|
||||
|
||||
func TestPlusDecCoins(t *testing.T) {
|
||||
one := NewDec(1)
|
||||
zero := NewDec(0)
|
||||
negone := NewDec(-1)
|
||||
two := NewDec(2)
|
||||
|
||||
cases := []struct {
|
||||
@@ -34,11 +84,9 @@ func TestPlusDecCoins(t *testing.T) {
|
||||
inputTwo DecCoins
|
||||
expected DecCoins
|
||||
}{
|
||||
{DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", two}, {"B", two}}},
|
||||
{DecCoins{{"A", zero}, {"B", one}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"B", one}}},
|
||||
{DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins(nil)},
|
||||
{DecCoins{{"A", one}, {"B", zero}}, DecCoins{{"A", negone}, {"B", zero}}, DecCoins(nil)},
|
||||
{DecCoins{{"A", negone}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", negone}}},
|
||||
{DecCoins{{"a", one}, {"b", one}}, DecCoins{{"a", one}, {"b", one}}, DecCoins{{"a", two}, {"b", two}}},
|
||||
{DecCoins{{"a", zero}, {"b", one}}, DecCoins{{"a", zero}, {"b", zero}}, DecCoins{{"b", one}}},
|
||||
{DecCoins{{"a", zero}, {"b", zero}}, DecCoins{{"a", zero}, {"b", zero}}, DecCoins(nil)},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range cases {
|
||||
@@ -46,3 +94,132 @@ func TestPlusDecCoins(t *testing.T) {
|
||||
require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortDecCoins(t *testing.T) {
|
||||
good := DecCoins{
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
NewDecCoin("tree", 1),
|
||||
}
|
||||
empty := DecCoins{
|
||||
NewDecCoin("gold", 0),
|
||||
}
|
||||
badSort1 := DecCoins{
|
||||
NewDecCoin("tree", 1),
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
badSort2 := DecCoins{ // both are after the first one, but the second and third are in the wrong order
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("tree", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
badAmt := DecCoins{
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("tree", 0),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
dup := DecCoins{
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("gas", 1),
|
||||
NewDecCoin("mineral", 1),
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
coins DecCoins
|
||||
before, after bool // valid before/after sort
|
||||
}{
|
||||
{good, true, true},
|
||||
{empty, false, false},
|
||||
{badSort1, false, true},
|
||||
{badSort2, false, true},
|
||||
{badAmt, false, false},
|
||||
{dup, false, false},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range cases {
|
||||
require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting, tc #%d", tcIndex)
|
||||
tc.coins.Sort()
|
||||
require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting, tc #%d", tcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecCoinsIsValid(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input DecCoins
|
||||
expected bool
|
||||
}{
|
||||
{DecCoins{}, true},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}}, true},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"b", NewDec(100000)}}, true},
|
||||
{DecCoins{DecCoin{"a", NewDec(-5)}}, false},
|
||||
{DecCoins{DecCoin{"A", NewDec(5)}}, false},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"B", NewDec(100000)}}, false},
|
||||
{DecCoins{DecCoin{"a", NewDec(5)}, DecCoin{"b", NewDec(-100000)}}, false},
|
||||
{DecCoins{DecCoin{"a", NewDec(-5)}, DecCoin{"b", NewDec(100000)}}, false},
|
||||
{DecCoins{DecCoin{"A", NewDec(5)}, DecCoin{"b", NewDec(100000)}}, false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res := tc.input.IsValid()
|
||||
require.Equal(t, tc.expected, res, "unexpected result for test case #%d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDecCoins(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expectedResult DecCoins
|
||||
expectedErr bool
|
||||
}{
|
||||
{"", nil, false},
|
||||
{"4stake", nil, true},
|
||||
{"5.5atom,4stake", nil, true},
|
||||
{"0.0stake", nil, true},
|
||||
{"0.004STAKE", nil, true},
|
||||
{
|
||||
"0.004stake",
|
||||
DecCoins{NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision))},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"5.04atom,0.004stake",
|
||||
DecCoins{
|
||||
NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)),
|
||||
NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res, err := ParseDecCoins(tc.input)
|
||||
if tc.expectedErr {
|
||||
require.Error(t, err, "expected error for test case #%d, input: %v", i, tc.input)
|
||||
} else {
|
||||
require.NoError(t, err, "unexpected error for test case #%d, input: %v", i, tc.input)
|
||||
require.Equal(t, tc.expectedResult, res, "unexpected result for test case #%d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecCoinsString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input DecCoins
|
||||
expected string
|
||||
}{
|
||||
{DecCoins{}, ""},
|
||||
{
|
||||
DecCoins{
|
||||
NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)),
|
||||
NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)),
|
||||
},
|
||||
"5.040000000000000000atom,0.004000000000000000stake",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
out := tc.input.String()
|
||||
require.Equal(t, tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -347,7 +347,7 @@ func chopPrecisionAndRound(d *big.Int) *big.Int {
|
||||
return d
|
||||
}
|
||||
|
||||
// get the trucated quotient and remainder
|
||||
// get the truncated quotient and remainder
|
||||
quo, rem := d, big.NewInt(0)
|
||||
quo, rem = quo.QuoRem(d, precisionReuse, rem)
|
||||
|
||||
@@ -419,6 +419,26 @@ func (d Dec) TruncateDec() Dec {
|
||||
return NewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int))
|
||||
}
|
||||
|
||||
// Ceil returns the smallest interger value (as a decimal) that is greater than
|
||||
// or equal to the given decimal.
|
||||
func (d Dec) Ceil() Dec {
|
||||
tmp := new(big.Int).Set(d.Int)
|
||||
|
||||
quo, rem := tmp, big.NewInt(0)
|
||||
quo, rem = quo.QuoRem(tmp, precisionReuse, rem)
|
||||
|
||||
// no need to round with a zero remainder regardless of sign
|
||||
if rem.Cmp(zeroInt) == 0 {
|
||||
return NewDecFromBigInt(quo)
|
||||
}
|
||||
|
||||
if rem.Sign() == -1 {
|
||||
return NewDecFromBigInt(quo)
|
||||
}
|
||||
|
||||
return NewDecFromBigInt(quo.Add(quo, oneInt))
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
|
||||
// reuse nil values
|
||||
|
||||
@@ -384,3 +384,24 @@ func TestDecMulInt(t *testing.T) {
|
||||
require.Equal(t, tc.want, got, "Incorrect result on test case %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecCeil(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input Dec
|
||||
expected Dec
|
||||
}{
|
||||
{NewDecWithPrec(1000000000000000, Precision), NewDec(1)}, // 0.001 => 1.0
|
||||
{NewDecWithPrec(-1000000000000000, Precision), ZeroDec()}, // -0.001 => 0.0
|
||||
{ZeroDec(), ZeroDec()}, // 0.0 => 0.0
|
||||
{NewDecWithPrec(900000000000000000, Precision), NewDec(1)}, // 0.9 => 1.0
|
||||
{NewDecWithPrec(4001000000000000000, Precision), NewDec(5)}, // 4.001 => 5.0
|
||||
{NewDecWithPrec(-4001000000000000000, Precision), NewDec(-4)}, // -4.001 => -4.0
|
||||
{NewDecWithPrec(4700000000000000000, Precision), NewDec(5)}, // 4.7 => 5.0
|
||||
{NewDecWithPrec(-4700000000000000000, Precision), NewDec(-4)}, // -4.7 => -4.0
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
res := tc.input.Ceil()
|
||||
require.Equal(t, tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user