diff --git a/x/globalfee/ante/fee.go b/x/globalfee/ante/fee.go index da869608f9e..909fac4a765 100644 --- a/x/globalfee/ante/fee.go +++ b/x/globalfee/ante/fee.go @@ -7,12 +7,10 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/cosmos/gaia/v9/x/globalfee/types" - tmstrings "github.com/tendermint/tendermint/libs/strings" "github.com/cosmos/gaia/v9/x/globalfee" + "github.com/cosmos/gaia/v9/x/globalfee/types" ) // FeeWithBypassDecorator checks if the transaction's fee is at least as large @@ -54,10 +52,9 @@ func NewFeeDecorator(bypassMsgTypes []string, globalfeeSubspace, stakingSubspace // AnteHandle implements the AnteDecorator interface func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // please note: after parsing feeflag, the zero fees are removed already feeTx, ok := tx.(sdk.FeeTx) if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the sdk.FeeTx interface") } // Only check for minimum fees and global fee if the execution mode is CheckTx @@ -65,17 +62,41 @@ func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne return next(ctx, tx, simulate) } - // Get required Global Fee and min gas price - requiredGlobalFees, err := mfd.getGlobalFee(ctx, feeTx) + // Sort fee tx's coins, zero coins in feeCoins are already removed + feeCoins := feeTx.GetFee().Sort() + gas := feeTx.GetGas() + msgs := feeTx.GetMsgs() + + // Get required Global Fee + requiredGlobalFees, err := mfd.GetGlobalFee(ctx, feeTx) if err != nil { return ctx, err } - requiredFees := GetMinGasPrice(ctx, int64(feeTx.GetGas())) - // sort fee tx's coins - feeCoins := feeTx.GetFee().Sort() - gas := feeTx.GetGas() - msgs := feeTx.GetMsgs() + // Get local minimum-gas-prices + localFees := GetMinGasPrice(ctx, int64(feeTx.GetGas())) + + // CombinedFeeRequirement should never be empty since + // global fee is set to its default value, i.e. 0uatom, if empty + combinedFeeRequirement := CombinedFeeRequirement(requiredGlobalFees, localFees) + if len(combinedFeeRequirement) == 0 { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "required fees are not setup.") + } + + nonZeroCoinFeesReq, zeroCoinFeesDenomReq := getNonZeroFees(combinedFeeRequirement) + + // feeCoinsNonZeroDenom contains non-zero denominations from the combinedFeeRequirement + // + // feeCoinsNoZeroDenom is used to check if the fees meets the requirement imposed by nonZeroCoinFeesReq + // when feeCoins does not contain zero coins' denoms in combinedFeeRequirement + feeCoinsNonZeroDenom, feeCoinsZeroDenom := splitCoinsByDenoms(feeCoins, zeroCoinFeesDenomReq) + + // Check that the fees are in expected denominations. + // if feeCoinsNoZeroDenom=[], DenomsSubsetOf returns true + // if feeCoinsNoZeroDenom is not empty, but nonZeroCoinFeesReq empty, return false + if !feeCoinsNonZeroDenom.DenomsSubsetOf(nonZeroCoinFeesReq) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, combinedFeeRequirement) + } // Accept zero fee transactions only if both of the following statements are true: // @@ -88,45 +109,41 @@ func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne doesNotExceedMaxGasUsage := gas <= mfd.MaxTotalBypassMinFeeMsgGasUsage allowedToBypassMinFee := mfd.ContainsOnlyBypassMinFeeMsgs(msgs) && doesNotExceedMaxGasUsage - if allowedToBypassMinFee { - // Transactions with zero fees are accepted - if len(feeCoins) == 0 { + // Either the transaction contains at least one message of a type + // that cannot bypass the minimum fee or the total gas limit exceeds + // the imposed threshold. As a result, besides check the fees are in + // expected denominations, check the amounts are greater or equal than + // the expected amounts. + + // only check feeCoinsNoZeroDenom has coins IsAnyGTE than nonZeroCoinFeesReq + // when feeCoins does not contain denoms of zero denoms in combinedFeeRequirement + if !allowedToBypassMinFee && len(feeCoinsZeroDenom) == 0 { + // special case: when feeCoins=[] and there is zero coin in fee requirement + if len(feeCoins) == 0 && len(zeroCoinFeesDenomReq) != 0 { return next(ctx, tx, simulate) } - // If the transaction fee is non-zero, then check that the fees are in - // expected denominations. - if !DenomsSubsetOfIncludingZero(feeCoins, requiredGlobalFees) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fees denom is wrong; got: %s required: %s", feeCoins, requiredGlobalFees) - } - } else { - // Either the transaction contains at least one message of a type - // that cannot bypass the minimum fee or the total gas limit exceeds - // the imposed threshold. As a result, check that the fees are in - // expected denominations and the amounts are greater or equal than - // the expected amounts. - - combinedFees := CombinedFeeRequirement(requiredGlobalFees, requiredFees) - - // Check that the fees are in expected denominations. Note that a zero fee - // is accepted if the global fee has an entry with a zero amount, e.g., 0uatoms. - if !DenomsSubsetOfIncludingZero(feeCoins, combinedFees) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, combinedFees) - } + // Check that the amounts of the fees are greater or equal than // the expected amounts, i.e., at least one feeCoin amount must // be greater or equal to one of the combined required fees. - if !IsAnyGTEIncludingZero(feeCoins, combinedFees) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, combinedFees) + + // if feeCoinsNoZeroDenom=[], return false + // if nonZeroCoinFeesReq=[], return false (this situation should not happen + // because when nonZeroCoinFeesReq empty, and DenomsSubsetOf check passed, + // the tx should already passed before) + if !feeCoinsNonZeroDenom.IsAnyGTE(nonZeroCoinFeesReq) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, combinedFeeRequirement) } } return next(ctx, tx, simulate) } -// getGlobalFee returns the global fees for a given fee tx's gas (might also returns 0denom if globalMinGasPrice is 0) +// GetGlobalFee returns the global fees for a given fee tx's gas +// (might also return 0denom if globalMinGasPrice is 0) // sorted in ascending order. // Note that ParamStoreKeyMinGasPrices type requires coins sorted. -func (mfd FeeDecorator) getGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, error) { +func (mfd FeeDecorator) GetGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, error) { var ( globalMinGasPrices sdk.DecCoins err error @@ -185,7 +202,7 @@ func (mfd FeeDecorator) ContainsOnlyBypassMinFeeMsgs(msgs []sdk.Msg) bool { return true } -// GetMinGasPrice returns a nodes's local minimum gas prices +// GetMinGasPrice returns the validator's minimum gas prices // fees given a gas limit func GetMinGasPrice(ctx sdk.Context, gasLimit int64) sdk.Coins { minGasPrices := ctx.MinGasPrices() diff --git a/x/globalfee/ante/fee_utils.go b/x/globalfee/ante/fee_utils.go index de63e98e40c..c30cc508440 100644 --- a/x/globalfee/ante/fee_utils.go +++ b/x/globalfee/ante/fee_utils.go @@ -4,74 +4,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// DenomsSubsetOfIncludingZero and IsAnyGTEIncludingZero are similar to DenomsSubsetOf and IsAnyGTE in sdk. -// -// DenomsSubsetOfIncludingZero overwrites DenomsSubsetOf from sdk, to allow zero amt coins in superset. e.g. -// e.g. [1stake] is the DenomsSubsetOfIncludingZero of [0stake] and -// [] is the DenomsSubsetOfIncludingZero of [0stake] but not [1stake]. -// DenomsSubsetOfIncludingZero returns true if coins's denoms is subset of coinsB's denoms. -// If coins is empty set, empty set is any sets' subset -func DenomsSubsetOfIncludingZero(coins, coinsB sdk.Coins) bool { - // more denoms in B than in receiver - if len(coins) > len(coinsB) { - return false - } - // coins=[], coinsB=[0stake] - // let all len(coins) == 0 pass and reject later at IsAnyGTEIncludingZero - if len(coins) == 0 && ContainZeroCoins(coinsB) { - return true - } - // coins=1stake, coinsB=[0stake,1uatom] - for _, coin := range coins { - err := sdk.ValidateDenom(coin.Denom) - if err != nil { - panic(err) - } - if ok, _ := Find(coinsB, coin.Denom); !ok { - return false - } - } - - return true -} - -// IsAnyGTEIncludingZero overwrites the IsAnyGTE from sdk to allow zero coins in coins and coinsB. -// IsAnyGTEIncludingZero returns true if coins contain at least one denom that is present at a greater or equal amount in coinsB; -// it returns false otherwise. If CoinsB is emptyset, no coins sets are IsAnyGTEIncludingZero coinsB unless coins is also empty set. -// NOTE: IsAnyGTEIncludingZero operates under the invariant that both coin sets are sorted by denoms. -// contract !!!! coins must be DenomsSubsetOfIncludingZero of coinsB -func IsAnyGTEIncludingZero(coins, coinsB sdk.Coins) bool { - // no set is empty set's subset except empty set - // this is different from sdk, sdk return false for coinsB empty - if len(coinsB) == 0 && len(coins) == 0 { - return true - } - // nothing is gte empty coins - if len(coinsB) == 0 && len(coins) != 0 { - return false - } - // if feecoins empty (len(coins)==0 && len(coinsB) != 0 ), and globalfee has one denom of amt zero, return true - if len(coins) == 0 { - return ContainZeroCoins(coinsB) - } - - // len(coinsB) != 0 && len(coins) != 0 - // special case: coins=1stake, coinsB=[2stake,0uatom], fail - for _, coin := range coins { - // not find coin in CoinsB - if ok, _ := Find(coinsB, coin.Denom); ok { - // find coin in coinsB, and if the amt == 0, mean either coin=0denom or coinsB=0denom...both true - amt := coinsB.AmountOf(coin.Denom) - if coin.Amount.GTE(amt) { - return true - } - } - } - - return false -} - // ContainZeroCoins returns true if the given coins are empty or contain zero coins, +// Note that the coins denoms must be validated, see sdk.ValidateDenom func ContainZeroCoins(coins sdk.Coins) bool { if len(coins) == 0 { return true @@ -88,6 +22,7 @@ func ContainZeroCoins(coins sdk.Coins) bool { // CombinedFeeRequirement returns the global fee and min_gas_price combined and sorted. // Both globalFees and minGasPrices must be valid, but CombinedFeeRequirement // does not validate them, so it may return 0denom. +// if globalfee is empty, CombinedFeeRequirement return sdk.Coins{} func CombinedFeeRequirement(globalFees, minGasPrices sdk.Coins) sdk.Coins { // empty min_gas_price if len(minGasPrices) == 0 { @@ -95,7 +30,7 @@ func CombinedFeeRequirement(globalFees, minGasPrices sdk.Coins) sdk.Coins { } // empty global fee is not possible if we set default global fee if len(globalFees) == 0 && len(minGasPrices) != 0 { - return globalFees + return sdk.Coins{} } // if min_gas_price denom is in globalfee, and the amount is higher than globalfee, add min_gas_price to allFees @@ -139,3 +74,35 @@ func Find(coins sdk.Coins, denom string) (bool, sdk.Coin) { } } } + +// splitCoinsByDenoms returns the given coins split in two whether +// their demon is or isn't found in the given denom map. +func splitCoinsByDenoms(feeCoins sdk.Coins, denomMap map[string]bool) (feeCoinsNonZeroDenom sdk.Coins, feeCoinsZeroDenom sdk.Coins) { + for _, fc := range feeCoins { + _, found := denomMap[fc.Denom] + if found { + feeCoinsZeroDenom = append(feeCoinsZeroDenom, fc) + } else { + feeCoinsNonZeroDenom = append(feeCoinsNonZeroDenom, fc) + } + } + + return feeCoinsNonZeroDenom.Sort(), feeCoinsZeroDenom.Sort() +} + +// getNonZeroFees returns the given fees nonzero coins +// and a map storing the zero coins's denoms +func getNonZeroFees(fees sdk.Coins) (sdk.Coins, map[string]bool) { + requiredFeesNonZero := sdk.Coins{} + requiredFeesZeroDenom := map[string]bool{} + + for _, gf := range fees { + if gf.IsZero() { + requiredFeesZeroDenom[gf.Denom] = true + } else { + requiredFeesNonZero = append(requiredFeesNonZero, gf) + } + } + + return requiredFeesNonZero.Sort(), requiredFeesZeroDenom +} diff --git a/x/globalfee/ante/fee_utils_test.go b/x/globalfee/ante/fee_utils_test.go index f7d7aa944be..a9114f971bd 100644 --- a/x/globalfee/ante/fee_utils_test.go +++ b/x/globalfee/ante/fee_utils_test.go @@ -161,248 +161,138 @@ func TestCombinedFeeRequirement(t *testing.T) { } } -func TestDenomsSubsetOfIncludingZero(t *testing.T) { - emptyCoins := sdk.Coins{} - - zeroCoin1 := sdk.NewCoin("photon", sdk.ZeroInt()) - zeroCoin2 := sdk.NewCoin("stake", sdk.ZeroInt()) - zeroCoin3 := sdk.NewCoin("quark", sdk.ZeroInt()) - - coin1 := sdk.NewCoin("photon", sdk.NewInt(1)) - coin2 := sdk.NewCoin("stake", sdk.NewInt(2)) - coin3 := sdk.NewCoin("quark", sdk.NewInt(3)) +func TestSplitCoinsByDenoms(t *testing.T) { + zeroGlobalFeesDenom0 := map[string]bool{} + zeroGlobalFeesDenom1 := map[string]bool{ + "uatom": true, + "photon": true, + } + zeroGlobalFeesDenom2 := map[string]bool{ + "uatom": true, + } + zeroGlobalFeesDenom3 := map[string]bool{ + "stake": true, + } - coinNewDenom1 := sdk.NewCoin("newphoton", sdk.NewInt(1)) - coinNewDenom2 := sdk.NewCoin("newstake", sdk.NewInt(2)) - coinNewDenom3 := sdk.NewCoin("newquark", sdk.NewInt(3)) - coinNewDenom1Zero := sdk.NewCoin("newphoton", sdk.ZeroInt()) - // coins must be valid !!! and sorted!!! - coinsAllZero := sdk.Coins{zeroCoin1, zeroCoin2, zeroCoin3}.Sort() - coinsAllZeroShort := sdk.Coins{zeroCoin1, zeroCoin2}.Sort() - coinsContainZero := sdk.Coins{zeroCoin1, zeroCoin2, coin3}.Sort() - coinsContainZeroNewDenoms := sdk.Coins{zeroCoin1, zeroCoin2, coinNewDenom1Zero}.Sort() - coins := sdk.Coins{coin1, coin2, coin3}.Sort() - coinsShort := sdk.Coins{coin1, coin2}.Sort() - coinsAllNewDenom := sdk.Coins{coinNewDenom1, coinNewDenom2, coinNewDenom3}.Sort() - coinsOldNewDenom := sdk.Coins{coin1, coin2, coinNewDenom1}.Sort() + photon := sdk.NewCoin("photon", sdk.OneInt()) + uatom := sdk.NewCoin("uatom", sdk.OneInt()) + feeCoins := sdk.NewCoins(photon, uatom) tests := map[string]struct { - superset sdk.Coins - set sdk.Coins - subset bool + feeCoins sdk.Coins + zeroGlobalFeesDenom map[string]bool + expectedNonZeroCoins sdk.Coins + expectedZeroCoins sdk.Coins }{ - "empty coins is a DenomsSubsetOf empty coins": { - superset: emptyCoins, - set: emptyCoins, - subset: true, - }, - "nonempty coins is not a DenomsSubsetOf empty coins": { - superset: emptyCoins, - set: coins, - subset: false, - }, - "empty coins is not a DenomsSubsetOf nonempty, nonzero coins": { - superset: emptyCoins, - set: coins, - subset: false, - }, - "empty coins is a DenomsSubsetOf coins of all zeros": { - superset: coinsAllZero, - set: emptyCoins, - subset: true, - }, - "empty coins is a DenomsSubsetOf coinsContainZero": { - superset: coinsContainZero, - set: emptyCoins, - subset: true, - }, - "two sets no denoms overlapping, DenomsSubsetOf = false": { - superset: coins, - set: coinsAllNewDenom, - subset: false, - }, - "two sets have partially overlapping denoms, DenomsSubsetOf = false": { - superset: coins, - set: coinsOldNewDenom, - subset: false, - }, - "two sets are nonzero, set's denoms are all in superset, DenomsSubsetOf = true": { - superset: coins, - set: coinsShort, - subset: true, - }, - "supersets are zero coins, set's denoms are all in superset, DenomsSubsetOf = true": { - superset: coinsAllZero, - set: coinsShort, - subset: true, - }, - "supersets contains zero coins, set's denoms are all in superset, DenomsSubsetOf = true": { - superset: coinsContainZero, - set: coinsShort, - subset: true, - }, - "supersets contains zero coins, set's denoms contains zero coins, denoms are overlapping DenomsSubsetOf = true": { - superset: coinsContainZero, - set: coinsContainZero, - subset: true, - }, - "supersets contains zero coins, set's denoms contains zero coins, denoms are not overlapping DenomsSubsetOf = false": { - superset: coinsContainZero, - set: coinsContainZeroNewDenoms, - subset: false, - }, - "two sets of all zero coins, have the same denoms, DenomsSubsetOf = true": { - superset: coinsAllZero, - set: coinsAllZeroShort, - subset: true, + "no zero coins in global fees": { + feeCoins: feeCoins, + zeroGlobalFeesDenom: zeroGlobalFeesDenom0, + expectedNonZeroCoins: feeCoins, + expectedZeroCoins: sdk.Coins{}, + }, + "no split of fee coins": { + feeCoins: feeCoins, + zeroGlobalFeesDenom: zeroGlobalFeesDenom3, + expectedNonZeroCoins: feeCoins, + expectedZeroCoins: sdk.Coins{}, + }, + "split the fee coins": { + feeCoins: feeCoins, + zeroGlobalFeesDenom: zeroGlobalFeesDenom2, + expectedNonZeroCoins: sdk.NewCoins(photon), + expectedZeroCoins: sdk.NewCoins(uatom), + }, + "remove all of the fee coins": { + feeCoins: feeCoins, + zeroGlobalFeesDenom: zeroGlobalFeesDenom1, + expectedNonZeroCoins: sdk.Coins{}, + expectedZeroCoins: feeCoins, + }, + "fee coins are empty": { + feeCoins: sdk.Coins{}, + zeroGlobalFeesDenom: zeroGlobalFeesDenom1, + expectedNonZeroCoins: sdk.Coins{}, + expectedZeroCoins: sdk.Coins{}, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - subset := DenomsSubsetOfIncludingZero(test.set, test.superset) - require.Equal(t, test.subset, subset) + feeCoinsNoZeroDenoms, feeCoinsZeroDenoms := splitCoinsByDenoms(test.feeCoins, test.zeroGlobalFeesDenom) + require.Equal(t, test.expectedNonZeroCoins, feeCoinsNoZeroDenoms) + require.Equal(t, test.expectedZeroCoins, feeCoinsZeroDenoms) }) } } -func TestIsAnyGTEIncludingZero(t *testing.T) { - emptyCoins := sdk.Coins{} - - zeroCoin1 := sdk.NewCoin("photon", sdk.ZeroInt()) - zeroCoin2 := sdk.NewCoin("stake", sdk.ZeroInt()) - zeroCoin3 := sdk.NewCoin("quark", sdk.ZeroInt()) +func TestSplitGlobalFees(t *testing.T) { + photon0 := sdk.NewCoin("photon", sdk.ZeroInt()) + uatom0 := sdk.NewCoin("uatom", sdk.ZeroInt()) + photon1 := sdk.NewCoin("photon", sdk.OneInt()) + uatom1 := sdk.NewCoin("uatom", sdk.OneInt()) - coin1 := sdk.NewCoin("photon", sdk.NewInt(10)) - coin1Low := sdk.NewCoin("photon", sdk.NewInt(1)) - coin1High := sdk.NewCoin("photon", sdk.NewInt(100)) - coin2 := sdk.NewCoin("stake", sdk.NewInt(20)) - coin2Low := sdk.NewCoin("stake", sdk.NewInt(2)) - coin2High := sdk.NewCoin("stake", sdk.NewInt(200)) - coin3 := sdk.NewCoin("quark", sdk.NewInt(30)) + globalFeesEmpty := sdk.Coins{} + globalFees := sdk.Coins{photon1, uatom1}.Sort() + globalFeesZeroCoins := sdk.Coins{photon0, uatom0}.Sort() + globalFeesMix := sdk.Coins{photon0, uatom1}.Sort() - coinNewDenom1 := sdk.NewCoin("newphoton", sdk.NewInt(10)) - coinNewDenom2 := sdk.NewCoin("newstake", sdk.NewInt(20)) - coinNewDenom3 := sdk.NewCoin("newquark", sdk.NewInt(30)) - zeroCoinNewDenom1 := sdk.NewCoin("newphoton", sdk.NewInt(10)) - zeroCoinNewDenom2 := sdk.NewCoin("newstake", sdk.NewInt(20)) - zeroCoinNewDenom3 := sdk.NewCoin("newquark", sdk.NewInt(30)) - // coins must be valid !!! and sorted!!! - coinsAllZero := sdk.Coins{zeroCoin1, zeroCoin2, zeroCoin3}.Sort() - coinsAllNewDenomAllZero := sdk.Coins{zeroCoinNewDenom1, zeroCoinNewDenom2, zeroCoinNewDenom3}.Sort() - coinsAllZeroShort := sdk.Coins{zeroCoin1, zeroCoin2}.Sort() - coinsContainZero := sdk.Coins{zeroCoin1, zeroCoin2, coin3}.Sort() - - coins := sdk.Coins{coin1, coin2, coin3}.Sort() - coinsHighHigh := sdk.Coins{coin1High, coin2High} - coinsHighLow := sdk.Coins{coin1High, coin2Low}.Sort() - coinsLowLow := sdk.Coins{coin1Low, coin2Low}.Sort() - // coinsShort := sdk.Coins{coin1, coin2}.Sort() - coinsAllNewDenom := sdk.Coins{coinNewDenom1, coinNewDenom2, coinNewDenom3}.Sort() - coinsOldNewDenom := sdk.Coins{coin1, coinNewDenom1, coinNewDenom2}.Sort() - coinsOldLowNewDenom := sdk.Coins{coin1Low, coinNewDenom1, coinNewDenom2}.Sort() tests := map[string]struct { - c1 sdk.Coins - c2 sdk.Coins - gte bool // greater or equal + globalfees sdk.Coins + zeroGlobalFeesDenom map[string]bool + globalfeesNonZero sdk.Coins }{ - "zero coins are GTE zero coins": { - c1: coinsAllZero, - c2: coinsAllZero, - gte: true, - }, - "zero coins(short) are GTE zero coins": { - c1: coinsAllZero, - c2: coinsAllZeroShort, - gte: true, - }, - "zero coins are GTE zero coins(short)": { - c1: coinsAllZeroShort, - c2: coinsAllZero, - gte: true, - }, - "c2 is all zero coins, with different denoms from c1 which are all zero coins too": { - c1: coinsAllZero, - c2: coinsAllNewDenomAllZero, - gte: false, - }, - "empty coins are GTE empty coins": { - c1: emptyCoins, - c2: emptyCoins, - gte: true, - }, - "empty coins are GTE zero coins": { - c1: coinsAllZero, - c2: emptyCoins, - gte: true, - }, - "empty coins are GTE coins that contain zero denom": { - c1: coinsContainZero, - c2: emptyCoins, - gte: true, - }, - "zero coins are not GTE empty coins": { - c1: emptyCoins, - c2: coinsAllZero, - gte: false, - }, - "empty coins are not GTE nonzero coins": { - c1: coins, - c2: emptyCoins, - gte: false, - }, - // special case, not the opposite result of the above case - "nonzero coins are not GTE empty coins": { - c1: emptyCoins, - c2: coins, - gte: false, - }, - "nonzero coins are GTE zero coins, has overlapping denom": { - c1: coinsAllZero, - c2: coins, - gte: true, - }, - "nonzero coins are GTE coins contain zero coins, zero coin is overlapping denom": { - c1: coinsContainZero, - c2: coins, - gte: true, - }, - "one denom amount higher, one denom amount lower": { - c1: coins, - c2: coinsHighLow, - gte: true, - }, - "all coins amounts are lower, denom overlapping": { - c1: coins, - c2: coinsLowLow, - gte: false, - }, - "all coins amounts are higher, denom overlapping": { - c1: coins, - c2: coinsHighHigh, - gte: true, - }, - "denoms are all not overlapping": { - c1: coins, - c2: coinsAllNewDenom, - gte: false, - }, - "denom not all overlapping, one overlapping denom is gte": { - c1: coins, - c2: coinsOldNewDenom, - gte: true, - }, - "denom not all overlapping, the only one overlapping denom is smaller": { - c1: coins, - c2: coinsOldLowNewDenom, - gte: false, + "empty global fees": { + globalfees: globalFeesEmpty, + zeroGlobalFeesDenom: map[string]bool{}, + globalfeesNonZero: sdk.Coins{}, + }, + "nonzero coins global fees": { + globalfees: globalFees, + zeroGlobalFeesDenom: map[string]bool{}, + globalfeesNonZero: globalFees, + }, + "zero coins global fees": { + globalfees: globalFeesZeroCoins, + zeroGlobalFeesDenom: map[string]bool{ + "photon": true, + "uatom": true, + }, + globalfeesNonZero: sdk.Coins{}, + }, + "mix zero, nonzero coins global fees": { + globalfees: globalFeesMix, + zeroGlobalFeesDenom: map[string]bool{ + "photon": true, + }, + globalfeesNonZero: sdk.NewCoins(uatom1), }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - gte := IsAnyGTEIncludingZero(test.c2, test.c1) - require.Equal(t, test.gte, gte) + nonZeroCoins, zeroCoinsMap := splitFees(test.globalfees) + require.True(t, nonZeroCoins.IsEqual(test.globalfeesNonZero)) + require.True(t, equalMap(zeroCoinsMap, test.zeroGlobalFeesDenom)) }) } } + +func equalMap(a, b map[string]bool) bool { + if len(a) != len(b) { + return false + } + if len(a) == 0 && len(b) == 0 { + return true + } + if len(a) == 0 { + return false + } + + for k := range a { + if _, ok := b[k]; !ok { + return false + } + } + + return true +}