From 1afeca75a19af590cb985800ac90dce9be193f6c Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 29 Jun 2023 23:21:28 -0700 Subject: [PATCH] test: x/tx/signing/textual: fuzz CoinsValueRenderer (#16521) Co-authored-by: Marko Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com> --- x/tx/go.mod | 1 + x/tx/go.sum | 3 +- x/tx/signing/textual/coins_test.go | 4 +- x/tx/signing/textual/fuzz_test.go | 86 +++++++++++++++++++ .../FuzzCoinsJSONTestcases/4d81af7cc74558bf | 2 + 5 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 x/tx/signing/textual/testdata/fuzz/FuzzCoinsJSONTestcases/4d81af7cc74558bf diff --git a/x/tx/go.mod b/x/tx/go.mod index 54758a3815..514deab28e 100644 --- a/x/tx/go.mod +++ b/x/tx/go.mod @@ -22,6 +22,7 @@ require ( github.com/cosmos/gogoproto v1.4.10 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/net v0.11.0 // indirect diff --git a/x/tx/go.sum b/x/tx/go.sum index 79a1faa465..35e82ad4dd 100644 --- a/x/tx/go.sum +++ b/x/tx/go.sum @@ -20,8 +20,9 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/x/tx/signing/textual/coins_test.go b/x/tx/signing/textual/coins_test.go index 35c7c4c27a..cc30c4cd73 100644 --- a/x/tx/signing/textual/coins_test.go +++ b/x/tx/signing/textual/coins_test.go @@ -71,7 +71,7 @@ func checkCoinsEqual(t *testing.T, l1, l2 protoreflect.List) { for i := 0; i < l1.Len(); i++ { coin, ok := l1.Get(i).Message().Interface().(*basev1beta1.Coin) - require.True(t, ok) + require.True(t, ok, "not a *basev1beta1.Coin: %#v", l1.Get(i).Message().Interface()) coinsMap[coin.Denom] = coin } @@ -90,7 +90,7 @@ func checkCoinEqual(t *testing.T, coin, coin1 *basev1beta1.Coin) { require.True(t, ok) v1, ok := math.NewIntFromString(coin1.Amount) require.True(t, ok) - require.True(t, v.Equal(v1)) + require.True(t, v.Equal(v1), "Mismatch\n\tv: %+v\n\tv1: %+v", v, v1) } // coinsJSONTest is the type of test cases in the testdata file. diff --git a/x/tx/signing/textual/fuzz_test.go b/x/tx/signing/textual/fuzz_test.go index e709d8513a..7df64de615 100644 --- a/x/tx/signing/textual/fuzz_test.go +++ b/x/tx/signing/textual/fuzz_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "os" + "regexp" "testing" "github.com/google/go-cmp/cmp" @@ -12,6 +13,7 @@ import ( "google.golang.org/protobuf/testing/protocmp" tspb "google.golang.org/protobuf/types/known/timestamppb" + basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" "cosmossdk.io/x/tx/internal/testpb" "cosmossdk.io/x/tx/signing/textual" ) @@ -211,3 +213,87 @@ func FuzzMessageValueRendererParse(f *testing.F) { } }) } + +// Copied from types/coin.go but pasted in here so as to avoid any imports +// of that package as has been mandated by team decisions. +var ( + reCoinDenom = regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9/:._-]{2,127}`) + reCoinAmount = regexp.MustCompile(`[[:digit:]]+(?:\.[[:digit:]]+)?|\.[[:digit:]]+`) +) + +func FuzzCoinsJSONTestcases(f *testing.F) { + // Generate some seeds. + seed, err := os.ReadFile("./internal/testdata/coins.json") + if err != nil { + f.Fatal(err) + } + f.Add(seed) + + txt, err := textual.NewSignModeHandler(textual.SignModeOptions{CoinMetadataQuerier: mockCoinMetadataQuerier}) + if err != nil { + f.Fatal(err) + } + rend, err := txt.GetFieldValueRenderer(fieldDescriptorFromName("COINS")) + if err != nil { + f.Fatal(err) + } + vrr := rend.(textual.RepeatedValueRenderer) + + f.Fuzz(func(t *testing.T, input []byte) { + var testCases []coinsJSONTest + if err := json.Unmarshal(input, &testCases); err != nil { + return + } + + for _, tc := range testCases { + if tc.Proto == nil { + continue + } + + // Create a context.Context containing all coins metadata, to simulate + // that they are in state. + ctx := context.Background() + for _, v := range tc.Metadata { + ctx = addMetadataToContext(ctx, v) + } + + listValue := NewGenericList(tc.Proto) + screens, err := vrr.FormatRepeated(ctx, protoreflect.ValueOf(listValue)) + if err != nil { + cpt := tc.Proto[0] + likeEmpty := err.Error() == "cannot format empty string" || err.Error() == "decimal string cannot be empty" + if likeEmpty && (!reCoinDenom.MatchString(cpt.Denom) || cpt.Amount == "") { + return + } + if !reCoinDenom.MatchString(cpt.Denom) { + return + } + if !reCoinAmount.MatchString(cpt.Amount) { + return + } + t.Fatalf("%v\n%q\n%#v => %t", err, tc.Text, cpt, cpt.Amount == "") + } + + if g, w := len(screens), 1; g != w { + t.Fatalf("Screens mismatch: got=%d want=%d", g, w) + } + + wantContent := tc.Text + if wantContent == "" { + wantContent = "zero" + } + if false { + if g, w := screens[0].Content, wantContent; g != w { + t.Fatalf("Content mismatch:\n\tGot: %s\n\tWant: %s", g, w) + } + } + + // Round trip. + parsedValue := NewGenericList([]*basev1beta1.Coin{}) + if err := vrr.ParseRepeated(ctx, screens, parsedValue); err != nil { + return + } + checkCoinsEqual(t, listValue, parsedValue) + } + }) +} diff --git a/x/tx/signing/textual/testdata/fuzz/FuzzCoinsJSONTestcases/4d81af7cc74558bf b/x/tx/signing/textual/testdata/fuzz/FuzzCoinsJSONTestcases/4d81af7cc74558bf new file mode 100644 index 0000000000..f16030abe3 --- /dev/null +++ b/x/tx/signing/textual/testdata/fuzz/FuzzCoinsJSONTestcases/4d81af7cc74558bf @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[{\"proto\":[{\"Amount\":\"0\"},{\"Amount\":\"1\"}]}]")