diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f15d65c37ab..e797f20787c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Unreleased +### Improvements + +* [\#10327](https://github.com/cosmos/cosmos-sdk/pull/10327) Add null guard for possible nil `Amount` in tx fee `Coins` + ### Bug Fixes * (client) [#10226](https://github.com/cosmos/cosmos-sdk/pull/10226) Fix --home flag parsing. diff --git a/types/coin.go b/types/coin.go index b86cff51865e..2749edadc7fb 100644 --- a/types/coin.go +++ b/types/coin.go @@ -129,6 +129,11 @@ func (coin Coin) IsNegative() bool { return coin.Amount.Sign() == -1 } +// IsNil returns true if the coin amount is nil and false otherwise. +func (coin Coin) IsNil() bool { + return coin.Amount.i == nil +} + //----------------------------------------------------------------------------- // Coins @@ -543,6 +548,19 @@ func (coins Coins) IsAnyNegative() bool { return false } +// IsAnyNil returns true if there is at least one coin whose amount +// is nil; returns false otherwise. It returns false if the coin set +// is empty too. +func (coins Coins) IsAnyNil() bool { + for _, coin := range coins { + if coin.IsNil() { + return true + } + } + + return false +} + // negative returns a set of coins with all amount negative. // // TODO: Remove once unsigned integers are used. diff --git a/types/coin_test.go b/types/coin_test.go index ab69f16a17db..bb1d00f1380a 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -237,6 +237,20 @@ func (s *coinTestSuite) TestCoinIsZero() { s.Require().False(res) } +func (s *coinTestSuite) TestCoinIsNil() { + coin := sdk.Coin{} + res := coin.IsNil() + s.Require().True(res) + + coin = sdk.Coin{Denom: "uatom"} + res = coin.IsNil() + s.Require().True(res) + + coin = sdk.NewInt64Coin(testDenom1, 1) + res = coin.IsNil() + s.Require().False(res) +} + func (s *coinTestSuite) TestFilteredZeroCoins() { cases := []struct { name string @@ -902,6 +916,19 @@ func (s *coinTestSuite) TestCoinsIsAnyGT() { } } +func (s *coinTestSuite) TestCoinsIsAnyNil() { + twoAtom := sdk.NewInt64Coin("atom", 2) + fiveAtom := sdk.NewInt64Coin("atom", 5) + threeEth := sdk.NewInt64Coin("eth", 3) + nilAtom := sdk.Coin{Denom: "atom"} + + s.Require().True(sdk.Coins{twoAtom, fiveAtom, threeEth, nilAtom}.IsAnyNil()) + s.Require().True(sdk.Coins{twoAtom, nilAtom, fiveAtom, threeEth}.IsAnyNil()) + s.Require().True(sdk.Coins{nilAtom, twoAtom, fiveAtom, threeEth}.IsAnyNil()) + s.Require().False(sdk.Coins{twoAtom, fiveAtom, threeEth}.IsAnyNil()) + +} + func (s *coinTestSuite) TestMarshalJSONCoins() { cdc := codec.NewLegacyAmino() sdk.RegisterLegacyAminoCodec(cdc) diff --git a/types/tx/types.go b/types/tx/types.go index 0392b2fcfd08..65313f735b7a 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -72,6 +72,13 @@ func (t *Tx) ValidateBasic() error { ) } + if fee.Amount.IsAnyNil() { + return sdkerrors.Wrapf( + sdkerrors.ErrInsufficientFee, + "invalid fee provided: null", + ) + } + if fee.Amount.IsAnyNegative() { return sdkerrors.Wrapf( sdkerrors.ErrInsufficientFee,