fix: tx/textual/valuerenderer: reject non-digits in dec + int (#12817)

This commit is contained in:
Emmanuel T Odeke 2022-08-05 01:03:08 -07:00 committed by GitHub
parent 33517402b2
commit e39d84e06f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 12 deletions

View File

@ -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")),

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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)
}
})
}
}