diff --git a/CHANGELOG.md b/CHANGELOG.md index 3710cb8e778f..5337dbf6572e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -168,6 +168,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Bug Fixes +* (types) [#15691](https://github.com/cosmos/cosmos-sdk/pull/15691) Make Coin.Validate() check that .Amount is not nil * (types) [#15433](https://github.com/cosmos/cosmos-sdk/pull/15433) Allow disabling of account address caches (for printing bech32 account addresses). * (x/auth) [#15059](https://github.com/cosmos/cosmos-sdk/pull/15059) `ante.CountSubKeys` returns 0 when passing a nil `Pubkey`. * (x/capability) [#15030](https://github.com/cosmos/cosmos-sdk/pull/15030) Prevent `x/capability` from consuming `GasMeter` gas during `InitMemStore` diff --git a/types/coin.go b/types/coin.go index e1dccdf18e82..c624f4b2bd61 100644 --- a/types/coin.go +++ b/types/coin.go @@ -2,6 +2,7 @@ package types import ( "encoding/json" + "errors" "fmt" "regexp" "sort" @@ -44,6 +45,10 @@ func (coin Coin) Validate() error { return err } + if coin.Amount.IsNil() { + return errors.New("amount is nil") + } + if coin.Amount.IsNegative() { return fmt.Errorf("negative coin amount: %v", coin.Amount) } diff --git a/types/coin_test.go b/types/coin_test.go index 13a20eee6ed0..9f8dad0cd849 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -1276,6 +1276,40 @@ func (s *coinTestSuite) TestMarshalJSONCoins() { } } +func (s *coinTestSuite) TestCoinValidate() { + testCases := []struct { + name string + coin sdk.Coin + wantErr string + }{ + {"nil coin: nil Amount", sdk.Coin{}, "invalid denom"}, + {"non-blank coin, nil Amount", sdk.Coin{Denom: "atom"}, "amount is nil"}, + {"valid coin", sdk.Coin{Denom: "atom", Amount: math.NewInt(100)}, ""}, + {"negative coin", sdk.Coin{Denom: "atom", Amount: math.NewInt(-999)}, "negative coin amount"}, + } + + for _, tc := range testCases { + tc := tc + t := s.T() + t.Run(tc.name, func(t *testing.T) { + err := tc.coin.Validate() + if tc.wantErr == "" { + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return + } else { + if err == nil { + t.Error("Expected an error") + } else if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("Error mismatch\n\tGot: %q\nWant: %q", err, tc.wantErr) + } + } + }) + } + +} + func (s *coinTestSuite) TestCoinAminoEncoding() { cdc := codec.NewLegacyAmino() c := sdk.NewInt64Coin(testDenom1, 5) diff --git a/types/fuzz_test.go b/types/fuzz_test.go new file mode 100644 index 000000000000..167b648ac0ce --- /dev/null +++ b/types/fuzz_test.go @@ -0,0 +1,24 @@ +package types + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/codec" +) + +func FuzzCoinUnmarshalJSON(f *testing.F) { + if testing.Short() { + f.Skip() + } + + cdc := codec.NewLegacyAmino() + f.Add(`{"denom":"atom","amount":"1000"}`) + f.Add(`{"denom":"atom","amount":"-1000"}`) + f.Add(`{"denom":"uatom","amount":"1000111111111111111111111"}`) + f.Add(`{"denom":"mu","amount":"0"}`) + + f.Fuzz(func(t *testing.T, jsonBlob string) { + var c Coin + _ = cdc.UnmarshalJSON([]byte(jsonBlob), &c) + }) +}