diff --git a/tx/textual/valuerenderer/bench_test.go b/tx/textual/valuerenderer/bench_test.go index fd485fbd76..5ce072ef2e 100644 --- a/tx/textual/valuerenderer/bench_test.go +++ b/tx/textual/valuerenderer/bench_test.go @@ -9,15 +9,15 @@ import ( ) var intValues = []protoreflect.Value{ - protoreflect.ValueOfString("10.00"), - protoreflect.ValueOfString("999.00"), - protoreflect.ValueOfString("999.9999"), - protoreflect.ValueOfString("99999999.9999"), + protoreflect.ValueOfString("1000"), + protoreflect.ValueOfString("99900"), + protoreflect.ValueOfString("9999999"), + protoreflect.ValueOfString("999999999999"), protoreflect.ValueOfString("9999999999999999999"), - protoreflect.ValueOfString("1000000000000000000000000000000000000000000000000000000.00"), - protoreflect.ValueOfString("77777777777.777777777777777777777700"), - protoreflect.ValueOfString("-77777777777.777777777777777777777700"), - protoreflect.ValueOfString("777777777777777777777777.77777777700"), + protoreflect.ValueOfString("100000000000000000000000000000000000000000000000000000000"), + protoreflect.ValueOfString("77777777777777777777777777777777700"), + protoreflect.ValueOfString("-77777777777777777777777777777777700"), + protoreflect.ValueOfString("77777777777777777777777777777777700"), } func BenchmarkIntValueRendererFormat(b *testing.B) { @@ -37,6 +37,35 @@ func BenchmarkIntValueRendererFormat(b *testing.B) { } } +var decimalValues = []protoreflect.Value{ + protoreflect.ValueOfString("10.00"), + protoreflect.ValueOfString("999.00"), + protoreflect.ValueOfString("999.9999"), + protoreflect.ValueOfString("99999999.9999"), + protoreflect.ValueOfString("9999999999999999999"), + protoreflect.ValueOfString("1000000000000000000000000000000000000000000000000000000.00"), + protoreflect.ValueOfString("77777777777.777777777777777777777700"), + protoreflect.ValueOfString("-77777777777.777777777777777777777700"), + protoreflect.ValueOfString("777777777777777777777777.77777777700"), +} + +func BenchmarkDecimalValueRendererFormat(b *testing.B) { + ctx := context.Background() + dvr := new(decValueRenderer) + buf := new(bytes.Buffer) + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for _, value := range intValues { + if err := dvr.Format(ctx, value, buf); err != nil { + b.Fatal(err) + } + } + buf.Reset() + } +} + var byteValues = []protoreflect.Value{ protoreflect.ValueOfBytes(bytes.Repeat([]byte("abc"), 1<<20)), protoreflect.ValueOfBytes([]byte("999.00")), diff --git a/tx/textual/valuerenderer/dec.go b/tx/textual/valuerenderer/dec.go index 5ea72e110d..6c26876613 100644 --- a/tx/textual/valuerenderer/dec.go +++ b/tx/textual/valuerenderer/dec.go @@ -34,15 +34,15 @@ func (vr decValueRenderer) Parse(_ context.Context, r io.Reader) (protoreflect.V // object). func formatDecimal(v string) (string, error) { parts := strings.Split(v, ".") + if len(parts) > 2 { + return "", fmt.Errorf("invalid decimal: too many points in %s", v) + } + intPart, err := formatInteger(parts[0]) if err != nil { return "", err } - if len(parts) > 2 { - return "", fmt.Errorf("invalid decimal %s", v) - } - if len(parts) == 1 { return intPart, nil } @@ -52,5 +52,11 @@ func formatDecimal(v string) (string, error) { return intPart, nil } + // Ensure that the decimal part has only digits. + // https://github.com/cosmos/cosmos-sdk/issues/12811 + if !hasOnlyDigits(decPart) { + return "", fmt.Errorf("non-digits detected after decimal point in: %q", decPart) + } + return intPart + "." + decPart, nil } diff --git a/tx/textual/valuerenderer/dec_test.go b/tx/textual/valuerenderer/dec_test.go new file mode 100644 index 0000000000..187c6fd551 --- /dev/null +++ b/tx/textual/valuerenderer/dec_test.go @@ -0,0 +1,35 @@ +package valuerenderer + +import ( + "strings" + "testing" +) + +func TestFormatDecimalNonDigits(t *testing.T) { + badCases := []string{ + "10.a", + "1a.10", + "p1a10.", + "0.10p", + "--10", + "12.😎😎", + "11111111111133333333333333333333333333333a", + "11111111111133333333333333333333333333333 192892", + } + + for _, value := range badCases { + value := value + t.Run(value, func(t *testing.T) { + s, err := formatDecimal(value) + if err == nil { + t.Fatal("Expected an error") + } + if g, w := err.Error(), "non-digits"; !strings.Contains(g, w) { + t.Errorf("Error mismatch\nGot: %q\nWant substring: %q", g, w) + } + if s != "" { + t.Fatalf("Got a non-empty string: %q", s) + } + }) + } +} diff --git a/tx/textual/valuerenderer/int.go b/tx/textual/valuerenderer/int.go index 476dbe2b4b..572f3b9b17 100644 --- a/tx/textual/valuerenderer/int.go +++ b/tx/textual/valuerenderer/int.go @@ -2,6 +2,7 @@ package valuerenderer import ( "context" + "fmt" "io" "strings" @@ -26,6 +27,18 @@ func (vr intValueRenderer) Parse(_ context.Context, r io.Reader) (protoreflect.V panic("implement me") } +func hasOnlyDigits(s string) bool { + if s == "" { + return false + } + for _, r := range s { + if r < '0' || r > '9' { + return false + } + } + return true +} + // formatInteger formats an integer into a value-rendered string. This function // operates with string manipulation (instead of manipulating the int or sdk.Int // object). @@ -37,7 +50,11 @@ func formatInteger(v string) (string, error) { } if len(v) > 1 { v = strings.TrimLeft(v, "0") + } + // Ensure that the string contains only digits at this point. + if !hasOnlyDigits(v) { + return "", fmt.Errorf("expecting only digits 0-9, but got non-digits in %q", v) } startOffset := 3 diff --git a/tx/textual/valuerenderer/int_test.go b/tx/textual/valuerenderer/int_test.go new file mode 100644 index 0000000000..4e30599faf --- /dev/null +++ b/tx/textual/valuerenderer/int_test.go @@ -0,0 +1,35 @@ +package valuerenderer + +import ( + "strings" + "testing" +) + +func TestFormatIntegerNonDigits(t *testing.T) { + badCases := []string{ + "a10", + "1a10", + "p1a10", + "10p", + "--10", + "😎😎", + "11111111111133333333333333333333333333333a", + "11111111111133333333333333333333333333333 192892", + } + + for _, value := range badCases { + value := value + t.Run(value, func(t *testing.T) { + s, err := formatInteger(value) + if err == nil { + t.Fatal("Expected an error") + } + if g, w := err.Error(), "but got non-digits in"; !strings.Contains(g, w) { + t.Errorf("Error mismatch\nGot: %q\nWant substring: %q", g, w) + } + if s != "" { + t.Fatalf("Got a non-empty string: %q", s) + } + }) + } +}