From 2e9995b9fc6885b34c5b6dcdebf986aa24927466 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Wed, 15 Nov 2023 19:25:18 -0500 Subject: [PATCH] Clean up chain reader, removed map decoder, as it will no longer be used (#242) --- pkg/codec/map_decoder.go | 246 ---------------- pkg/codec/map_decoder_test.go | 272 ------------------ pkg/codec/utils.go | 120 ++++++++ pkg/codec/utils_test.go | 110 +++++++ pkg/loop/internal/chain_reader.go | 52 +--- pkg/loop/internal/chain_reader_test.go | 68 ++--- pkg/loop/internal/pb/relayer.pb.go | 18 +- pkg/loop/internal/pb/relayer.proto | 1 - pkg/types/codec.go | 2 +- ...sts.go => chain_reader_interface_tests.go} | 65 ++++- 10 files changed, 325 insertions(+), 629 deletions(-) delete mode 100644 pkg/codec/map_decoder.go delete mode 100644 pkg/codec/map_decoder_test.go rename pkg/types/interfacetests/{codec_interface_tests.go => chain_reader_interface_tests.go} (84%) diff --git a/pkg/codec/map_decoder.go b/pkg/codec/map_decoder.go deleted file mode 100644 index 19331de76..000000000 --- a/pkg/codec/map_decoder.go +++ /dev/null @@ -1,246 +0,0 @@ -package codec - -import ( - "context" - "errors" - "math/big" - "reflect" - "strconv" - - "github.com/mitchellh/mapstructure" - - "github.com/smartcontractkit/chainlink-relay/pkg/types" -) - -func DecoderFromMapDecoder(decoder types.MapDecoder, extraHooks ...mapstructure.DecodeHookFunc) (types.Decoder, error) { - if decoder == nil { - return nil, errors.New("decoder must not be nil") - } - - numExtraHooks := len(extraHooks) - hooks := make([]mapstructure.DecodeHookFunc, numExtraHooks+2) - copy(hooks, extraHooks) - hooks[numExtraHooks] = SliceToArrayVerifySizeHook - hooks[numExtraHooks+1] = BigIntHook - return &mapDecoder{decoder: decoder, hooks: hooks}, nil -} - -type mapDecoder struct { - decoder types.MapDecoder - hooks []mapstructure.DecodeHookFunc -} - -func (m *mapDecoder) GetMaxDecodingSize(ctx context.Context, n int, itemType string) (int, error) { - return m.decoder.GetMaxDecodingSize(ctx, n, itemType) -} - -func (m *mapDecoder) Decode(ctx context.Context, raw []byte, into any, itemType string) error { - rInto := reflect.ValueOf(into) - if rInto.Kind() != reflect.Pointer { - return types.InvalidTypeError{} - } - - elm := reflect.Indirect(rInto) - switch elm.Kind() { - case reflect.Array: - return m.decodeMultiple(ctx, raw, arrayProvider(elm), itemType) - case reflect.Slice: - return m.decodeMultiple(ctx, raw, sliceProvider(elm), itemType) - default: - rawMap, err := m.decoder.DecodeSingle(ctx, raw, itemType) - if err != nil { - return err - } - return m.mapToItem(rawMap, into) - } -} - -// VerifyFieldMaps is a utility for verifying the keys exactly match the fields -// it is not done in Decode directly as it can often be more efficiently by MapDecoders -// in the case of DecodeMany -func VerifyFieldMaps(fields []string, result map[string]any) error { - for _, field := range fields { - if _, ok := result[field]; !ok { - return types.InvalidEncodingError{} - } - } - - if len(fields) != len(result) { - return types.InvalidEncodingError{} - } - - return nil -} - -func arrayProvider(rInto reflect.Value) func(size int) (reflect.Value, error) { - return func(size int) (reflect.Value, error) { - if rInto.Len() != size { - return reflect.Value{}, types.WrongNumberOfElements{} - } - return rInto, nil - } -} - -func sliceProvider(rInto reflect.Value) func(size int) (reflect.Value, error) { - return func(size int) (reflect.Value, error) { - element := reflect.MakeSlice(rInto.Type(), size, size) - rInto.Set(element) - return rInto, nil - } -} - -func (m *mapDecoder) decodeMultiple(ctx context.Context, raw []byte, init func(size int) (reflect.Value, error), itemType string) error { - decoded, err := m.decoder.DecodeMany(ctx, raw, itemType) - if err != nil { - return err - } - - rInto, err := init(len(decoded)) - if err != nil { - return err - } - - for i, singleDecode := range decoded { - if err = m.mapToItem(singleDecode, rInto.Index(i).Addr().Interface()); err != nil { - return err - } - } - - return nil -} - -func BigIntHook(_, to reflect.Type, data any) (any, error) { - if to == reflect.TypeOf(&big.Int{}) { - bigInt := big.NewInt(0) - - switch v := data.(type) { - case float64: - bigInt.SetInt64(int64(v)) - case float32: - bigInt.SetInt64(int64(v)) - case int: - bigInt.SetInt64(int64(v)) - case int8: - bigInt.SetInt64(int64(v)) - case int16: - bigInt.SetInt64(int64(v)) - case int32: - bigInt.SetInt64(int64(v)) - case int64: - bigInt.SetInt64(v) - case uint: - bigInt.SetUint64(uint64(v)) - case uint8: - bigInt.SetUint64(uint64(v)) - case uint16: - bigInt.SetUint64(uint64(v)) - case uint32: - bigInt.SetUint64(uint64(v)) - case uint64: - bigInt.SetUint64(v) - case string: - _, ok := bigInt.SetString(v, 10) - if !ok { - return nil, types.InvalidTypeError{} - } - default: - return data, nil - } - - return bigInt, nil - } - - if bi, ok := data.(*big.Int); ok { - switch to { - case reflect.TypeOf(0): - if FitsInNBitsSigned(strconv.IntSize, bi) { - return int(bi.Int64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(int8(0)): - if FitsInNBitsSigned(8, bi) { - return int8(bi.Int64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(int16(0)): - if FitsInNBitsSigned(16, bi) { - return int16(bi.Int64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(int32(0)): - if FitsInNBitsSigned(32, bi) { - return int32(bi.Int64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(int64(0)): - if FitsInNBitsSigned(64, bi) { - return bi.Int64(), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(uint(0)): - if bi.Sign() >= 0 && bi.BitLen() <= strconv.IntSize { - return uint(bi.Uint64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(uint8(0)): - if bi.Sign() >= 0 && bi.BitLen() <= 8 { - return uint8(bi.Uint64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(uint16(0)): - if bi.Sign() >= 0 && bi.BitLen() <= 16 { - return uint16(bi.Uint64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(uint32(0)): - if bi.Sign() >= 0 && bi.BitLen() <= 32 { - return uint32(bi.Uint64()), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(uint64(0)): - if bi.Sign() >= 0 && bi.BitLen() <= 64 { - return bi.Uint64(), nil - } - return nil, types.InvalidTypeError{} - case reflect.TypeOf(""): - return bi.String(), nil - default: - return data, nil - } - } - - return data, nil -} - -func SliceToArrayVerifySizeHook(from reflect.Type, to reflect.Type, data any) (any, error) { - // By default, if the array is bigger it'll still work. (ie []int{1, 2, 3} => [4]int{} works with 0 at the end - // [2]int{} would not work. This seems to lenient, but can be discussed. - if from.Kind() == reflect.Slice && to.Kind() == reflect.Array { - slice := reflect.ValueOf(data) - if slice.Len() != to.Len() { - return nil, types.WrongNumberOfElements{} - } - } - - return data, nil -} - -func (m *mapDecoder) mapToItem(rawMap map[string]any, into any) error { - md := &mapstructure.Metadata{} - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - // TODO add hook to verify number sizes. mapstructure seems to check -ve values for unsigned, but not other boundaries - DecodeHook: mapstructure.ComposeDecodeHookFunc(m.hooks...), - Metadata: md, - Result: into, - }) - - if err != nil { - return types.InvalidTypeError{} - } - - if err = decoder.Decode(rawMap); err != nil { - return types.InvalidTypeError{} - } - - return nil -} diff --git a/pkg/codec/map_decoder_test.go b/pkg/codec/map_decoder_test.go deleted file mode 100644 index 4a01e3144..000000000 --- a/pkg/codec/map_decoder_test.go +++ /dev/null @@ -1,272 +0,0 @@ -package codec_test - -import ( - "context" - "fmt" - "math" - "math/big" - "reflect" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-relay/pkg/codec" - "github.com/smartcontractkit/chainlink-relay/pkg/types" -) - -const ( - anyItemTypeForMapDecoder = "anything" - anyNElementsForMapDecoder = 10 - anyDecodingSizeForMapDecoder = 200 -) - -var anyRawBytes = []byte("raw") - -func TestMapDecoder(t *testing.T) { - t.Parallel() - t.Run("Decode works on a single item", func(t *testing.T) { - item := &mapTestType{} - field1 := "Field1" - anyValue1 := "value1" - field2 := "Field2" - anyValue2 := 122 - anySingleResult := map[string]any{field1: anyValue1, field2: anyValue2} - tmd := &testMapDecoder{resultSingle: anySingleResult} - decoder, err := codec.DecoderFromMapDecoder(tmd) - assert.NoError(t, err) - - err = decoder.Decode(context.Background(), anyRawBytes, item, anyItemTypeForMapDecoder) - - assert.NoError(t, err) - assert.Equal(t, anyValue1, item.Field1) - assert.Equal(t, anyValue2, item.Field2) - assertTestMapDecoder(t, tmd) - }) - - t.Run("Decode works on a slice", func(t *testing.T) { runSliceArrayDecodeTest(t, &[]mapTestType{}) }) - - t.Run("Decode works on an array", func(t *testing.T) { runSliceArrayDecodeTest(t, &[2]mapTestType{}) }) - - t.Run("Decode returns an error if the type is not a pointer", func(t *testing.T) { - item := mapTestType{} - field1 := "Field1" - anyValue1 := "value1" - field2 := "Field2" - anyValue2 := 13 - anySingleResult := map[string]any{field1: anyValue1, field2: anyValue2} - tmd := &testMapDecoder{resultSingle: anySingleResult} - decoder, err := codec.DecoderFromMapDecoder(tmd) - assert.NoError(t, err) - - err = decoder.Decode(context.Background(), anyRawBytes, item, anyItemTypeForMapDecoder) - - assert.IsType(t, types.InvalidTypeError{}, err) - }) - - t.Run("Decode returns an error if map is too big for an array", func(t *testing.T) { - testWrongArraySize(t, &[3]mapTestType{}) - }) - - t.Run("Decode returns an error if map is too small for an array", func(t *testing.T) { - testWrongArraySize(t, &[1]mapTestType{}) - }) - - t.Run("Decode returns an error for nil argument", func(t *testing.T) { - _, err := codec.DecoderFromMapDecoder(nil) - assert.Error(t, err) - }) - - t.Run("GetMaxDecodingSize delegates", func(t *testing.T) { - tmd := &testMapDecoder{} - decoder, err := codec.DecoderFromMapDecoder(tmd) - require.NoError(t, err) - - maxSize, err := decoder.GetMaxDecodingSize(context.Background(), anyNElementsForMapDecoder, anyItemTypeForMapDecoder) - assert.NoError(t, err) - assert.Equal(t, anyDecodingSizeForMapDecoder, maxSize) - }) -} - -func TestVerifyFieldMaps(t *testing.T) { - t.Parallel() - anyKey1 := "anything" - anyKey2 := "different" - input := map[string]any{ - anyKey1: 1, - anyKey2: 2, - } - t.Run("returns nil if fields match", func(t *testing.T) { - assert.NoError(t, codec.VerifyFieldMaps([]string{anyKey1, anyKey2}, input)) - }) - - t.Run("returns error if field is missing", func(t *testing.T) { - assert.IsType(t, types.InvalidEncodingError{}, codec.VerifyFieldMaps([]string{anyKey1, anyKey2, "missing"}, input)) - }) - - t.Run("returns error for extra key", func(t *testing.T) { - assert.IsType(t, types.InvalidEncodingError{}, codec.VerifyFieldMaps([]string{anyKey1}, input)) - }) - - t.Run("returns error if keys do not match", func(t *testing.T) { - assert.IsType(t, types.InvalidEncodingError{}, codec.VerifyFieldMaps([]string{anyKey1, "new key"}, input)) - }) -} - -func TestBigIntHook(t *testing.T) { - intTypes := []struct { - Type reflect.Type - Max *big.Int - Min *big.Int - }{ - {Type: reflect.TypeOf(0), Min: big.NewInt(math.MinInt), Max: big.NewInt(math.MaxInt)}, - {Type: reflect.TypeOf(uint(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint)}, - {Type: reflect.TypeOf(int8(0)), Min: big.NewInt(math.MinInt8), Max: big.NewInt(math.MaxInt8)}, - {Type: reflect.TypeOf(uint8(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint8)}, - {Type: reflect.TypeOf(int16(0)), Min: big.NewInt(math.MinInt16), Max: big.NewInt(math.MaxInt16)}, - {Type: reflect.TypeOf(uint16(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint16)}, - {Type: reflect.TypeOf(int32(0)), Min: big.NewInt(math.MinInt32), Max: big.NewInt(math.MaxInt32)}, - {Type: reflect.TypeOf(uint32(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint32)}, - {Type: reflect.TypeOf(int64(0)), Min: big.NewInt(math.MinInt64), Max: big.NewInt(math.MaxInt64)}, - {Type: reflect.TypeOf(uint64(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint64)}, - } - for _, intType := range intTypes { - t.Run(fmt.Sprintf("Fits conversion %v", intType.Type), func(t *testing.T) { - anyValidNumber := big.NewInt(5) - result, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), intType.Type, anyValidNumber) - require.NoError(t, err) - require.IsType(t, reflect.New(intType.Type).Elem().Interface(), result) - if intType.Min.Cmp(big.NewInt(0)) == 0 { - u64 := reflect.ValueOf(result).Convert(reflect.TypeOf(uint64(0))).Interface().(uint64) - actual := new(big.Int).SetUint64(u64) - require.Equal(t, anyValidNumber, actual) - } else { - i64 := reflect.ValueOf(result).Convert(reflect.TypeOf(int64(0))).Interface().(int64) - actual := big.NewInt(i64) - require.Equal(t, 0, anyValidNumber.Cmp(actual)) - } - }) - - t.Run("Overflow return an error "+intType.Type.String(), func(t *testing.T) { - bigger := new(big.Int).Add(intType.Max, big.NewInt(1)) - _, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), intType.Type, bigger) - assert.IsType(t, types.InvalidTypeError{}, err) - }) - - t.Run("Underflow return an error "+intType.Type.String(), func(t *testing.T) { - smaller := new(big.Int).Sub(intType.Min, big.NewInt(1)) - _, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), intType.Type, smaller) - assert.IsType(t, types.InvalidTypeError{}, err) - }) - - t.Run("Converts from "+intType.Type.String(), func(t *testing.T) { - anyValidNumber := int64(5) - asType := reflect.ValueOf(anyValidNumber).Convert(intType.Type).Interface() - result, err := codec.BigIntHook(intType.Type, reflect.TypeOf((*big.Int)(nil)), asType) - require.NoError(t, err) - bi, ok := result.(*big.Int) - require.True(t, ok) - assert.Equal(t, anyValidNumber, bi.Int64()) - }) - } - - t.Run("Converts from string", func(t *testing.T) { - anyNumber := int64(5) - result, err := codec.BigIntHook(reflect.TypeOf(""), reflect.TypeOf((*big.Int)(nil)), strconv.FormatInt(anyNumber, 10)) - require.NoError(t, err) - bi, ok := result.(*big.Int) - require.True(t, ok) - assert.Equal(t, anyNumber, bi.Int64()) - }) - - t.Run("Converts to string", func(t *testing.T) { - anyNumber := int64(5) - result, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), reflect.TypeOf(""), big.NewInt(anyNumber)) - require.NoError(t, err) - assert.Equal(t, strconv.FormatInt(anyNumber, 10), result) - }) - - t.Run("Errors for invalid string", func(t *testing.T) { - _, err := codec.BigIntHook(reflect.TypeOf(""), reflect.TypeOf((*big.Int)(nil)), "Not a number :(") - require.IsType(t, types.InvalidTypeError{}, err) - }) -} - -func runSliceArrayDecodeTest(t *testing.T, item any) { - field1 := "Field1" - anyValue11 := "value1" - anyValue12 := "value12" - field2 := "Field2" - anyValue21 := 23 - anyValue22 := 33 - anyManyResult := []map[string]any{ - {field1: anyValue11, field2: anyValue21}, - {field1: anyValue12, field2: anyValue22}, - } - // template is being used to provide the types - tmd := &testMapDecoder{resultMany: anyManyResult} - decoder, err := codec.DecoderFromMapDecoder(tmd) - assert.NoError(t, err) - - err = decoder.Decode(context.Background(), anyRawBytes, item, anyItemTypeForMapDecoder) - assert.NoError(t, err) - - rItem := reflect.ValueOf(item).Elem() - assert.Equal(t, 2, rItem.Len()) - assert.Equal(t, mapTestType{Field1: anyValue11, Field2: anyValue21}, rItem.Index(0).Interface()) - assert.Equal(t, mapTestType{Field1: anyValue12, Field2: anyValue22}, rItem.Index(1).Interface()) -} - -func testWrongArraySize(t *testing.T, item any) { - field1 := "Field1" - anyValue1 := "value1" - field2 := "Field2" - anyValue2 := "value2" - anyManyResult := []map[string]any{ - {field1: anyValue1, field2: anyValue2}, - {field1: anyValue2, field2: anyValue1}, - } - tmd := &testMapDecoder{resultMany: anyManyResult} - decoder, err := codec.DecoderFromMapDecoder(tmd) - assert.NoError(t, err) - - err = decoder.Decode(context.Background(), anyRawBytes, item, anyItemTypeForMapDecoder) - assert.IsType(t, types.WrongNumberOfElements{}, err) -} - -func assertTestMapDecoder(t *testing.T, md *testMapDecoder) { - assert.True(t, md.correctRaw) - assert.True(t, md.correctItem) -} - -type testMapDecoder struct { - resultSingle map[string]any - resultMany []map[string]any - correctRaw bool - correctItem bool - correctN bool -} - -func (t *testMapDecoder) DecodeSingle(ctx context.Context, raw []byte, itemType string) (map[string]any, error) { - t.correctRaw = reflect.DeepEqual(raw, anyRawBytes) - t.correctItem = itemType == anyItemTypeForMapDecoder - return t.resultSingle, nil -} - -func (t *testMapDecoder) DecodeMany(ctx context.Context, raw []byte, itemType string) ([]map[string]any, error) { - t.correctRaw = reflect.DeepEqual(raw, anyRawBytes) - t.correctItem = itemType == anyItemTypeForMapDecoder - return t.resultMany, nil -} - -func (t *testMapDecoder) GetMaxDecodingSize(ctx context.Context, n int, itemType string) (int, error) { - t.correctN = anyNElementsForMapDecoder == n - t.correctItem = itemType == anyItemTypeForMapDecoder - return anyDecodingSizeForMapDecoder, nil -} - -type mapTestType struct { - Field1 string - Field2 int -} diff --git a/pkg/codec/utils.go b/pkg/codec/utils.go index 43d686f67..a2126a83d 100644 --- a/pkg/codec/utils.go +++ b/pkg/codec/utils.go @@ -2,6 +2,10 @@ package codec import ( "math/big" + "reflect" + "strconv" + + "github.com/smartcontractkit/chainlink-relay/pkg/types" ) func FitsInNBitsSigned(n int, bi *big.Int) bool { @@ -11,3 +15,119 @@ func FitsInNBitsSigned(n int, bi *big.Int) bool { } return bi.BitLen() <= n-1 } + +func BigIntHook(_, to reflect.Type, data any) (any, error) { + if to == reflect.TypeOf(&big.Int{}) { + bigInt := big.NewInt(0) + + switch v := data.(type) { + case float64: + bigInt.SetInt64(int64(v)) + case float32: + bigInt.SetInt64(int64(v)) + case int: + bigInt.SetInt64(int64(v)) + case int8: + bigInt.SetInt64(int64(v)) + case int16: + bigInt.SetInt64(int64(v)) + case int32: + bigInt.SetInt64(int64(v)) + case int64: + bigInt.SetInt64(v) + case uint: + bigInt.SetUint64(uint64(v)) + case uint8: + bigInt.SetUint64(uint64(v)) + case uint16: + bigInt.SetUint64(uint64(v)) + case uint32: + bigInt.SetUint64(uint64(v)) + case uint64: + bigInt.SetUint64(v) + case string: + _, ok := bigInt.SetString(v, 10) + if !ok { + return nil, types.InvalidTypeError{} + } + default: + return data, nil + } + + return bigInt, nil + } + + if bi, ok := data.(*big.Int); ok { + switch to { + case reflect.TypeOf(0): + if FitsInNBitsSigned(strconv.IntSize, bi) { + return int(bi.Int64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(int8(0)): + if FitsInNBitsSigned(8, bi) { + return int8(bi.Int64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(int16(0)): + if FitsInNBitsSigned(16, bi) { + return int16(bi.Int64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(int32(0)): + if FitsInNBitsSigned(32, bi) { + return int32(bi.Int64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(int64(0)): + if FitsInNBitsSigned(64, bi) { + return bi.Int64(), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(uint(0)): + if bi.Sign() >= 0 && bi.BitLen() <= strconv.IntSize { + return uint(bi.Uint64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(uint8(0)): + if bi.Sign() >= 0 && bi.BitLen() <= 8 { + return uint8(bi.Uint64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(uint16(0)): + if bi.Sign() >= 0 && bi.BitLen() <= 16 { + return uint16(bi.Uint64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(uint32(0)): + if bi.Sign() >= 0 && bi.BitLen() <= 32 { + return uint32(bi.Uint64()), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(uint64(0)): + if bi.Sign() >= 0 && bi.BitLen() <= 64 { + return bi.Uint64(), nil + } + return nil, types.InvalidTypeError{} + case reflect.TypeOf(""): + return bi.String(), nil + default: + return data, nil + } + } + + return data, nil +} + +func SliceToArrayVerifySizeHook(from reflect.Type, to reflect.Type, data any) (any, error) { + // By default, if the array is bigger it'll still work. (ie []int{1, 2, 3} => [4]int{} works with 0 at the end + // [2]int{} would not work. This seems to lenient, but can be discussed. + if from.Kind() == reflect.Slice && to.Kind() == reflect.Array { + slice := reflect.ValueOf(data) + if slice.Len() != to.Len() { + return nil, types.WrongNumberOfElements{} + } + } + + return data, nil +} diff --git a/pkg/codec/utils_test.go b/pkg/codec/utils_test.go index 946e8990c..2ce2fc743 100644 --- a/pkg/codec/utils_test.go +++ b/pkg/codec/utils_test.go @@ -1,13 +1,18 @@ package codec_test import ( + "fmt" "math" "math/big" + "reflect" + "strconv" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-relay/pkg/codec" + "github.com/smartcontractkit/chainlink-relay/pkg/types" ) func TestFitsInNBitsSigned(t *testing.T) { @@ -27,3 +32,108 @@ func TestFitsInNBitsSigned(t *testing.T) { assert.False(t, codec.FitsInNBitsSigned(16, bi)) }) } + +func TestBigIntHook(t *testing.T) { + intTypes := []struct { + Type reflect.Type + Max *big.Int + Min *big.Int + }{ + {Type: reflect.TypeOf(0), Min: big.NewInt(math.MinInt), Max: big.NewInt(math.MaxInt)}, + {Type: reflect.TypeOf(uint(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint)}, + {Type: reflect.TypeOf(int8(0)), Min: big.NewInt(math.MinInt8), Max: big.NewInt(math.MaxInt8)}, + {Type: reflect.TypeOf(uint8(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint8)}, + {Type: reflect.TypeOf(int16(0)), Min: big.NewInt(math.MinInt16), Max: big.NewInt(math.MaxInt16)}, + {Type: reflect.TypeOf(uint16(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint16)}, + {Type: reflect.TypeOf(int32(0)), Min: big.NewInt(math.MinInt32), Max: big.NewInt(math.MaxInt32)}, + {Type: reflect.TypeOf(uint32(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint32)}, + {Type: reflect.TypeOf(int64(0)), Min: big.NewInt(math.MinInt64), Max: big.NewInt(math.MaxInt64)}, + {Type: reflect.TypeOf(uint64(0)), Min: big.NewInt(0), Max: new(big.Int).SetUint64(math.MaxUint64)}, + } + for _, intType := range intTypes { + t.Run(fmt.Sprintf("Fits conversion %v", intType.Type), func(t *testing.T) { + anyValidNumber := big.NewInt(5) + result, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), intType.Type, anyValidNumber) + require.NoError(t, err) + require.IsType(t, reflect.New(intType.Type).Elem().Interface(), result) + if intType.Min.Cmp(big.NewInt(0)) == 0 { + u64 := reflect.ValueOf(result).Convert(reflect.TypeOf(uint64(0))).Interface().(uint64) + actual := new(big.Int).SetUint64(u64) + require.Equal(t, anyValidNumber, actual) + } else { + i64 := reflect.ValueOf(result).Convert(reflect.TypeOf(int64(0))).Interface().(int64) + actual := big.NewInt(i64) + require.Equal(t, 0, anyValidNumber.Cmp(actual)) + } + }) + + t.Run("Overflow return an error "+intType.Type.String(), func(t *testing.T) { + bigger := new(big.Int).Add(intType.Max, big.NewInt(1)) + _, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), intType.Type, bigger) + assert.IsType(t, types.InvalidTypeError{}, err) + }) + + t.Run("Underflow return an error "+intType.Type.String(), func(t *testing.T) { + smaller := new(big.Int).Sub(intType.Min, big.NewInt(1)) + _, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), intType.Type, smaller) + assert.IsType(t, types.InvalidTypeError{}, err) + }) + + t.Run("Converts from "+intType.Type.String(), func(t *testing.T) { + anyValidNumber := int64(5) + asType := reflect.ValueOf(anyValidNumber).Convert(intType.Type).Interface() + result, err := codec.BigIntHook(intType.Type, reflect.TypeOf((*big.Int)(nil)), asType) + require.NoError(t, err) + bi, ok := result.(*big.Int) + require.True(t, ok) + assert.Equal(t, anyValidNumber, bi.Int64()) + }) + } + + t.Run("Converts from string", func(t *testing.T) { + anyNumber := int64(5) + result, err := codec.BigIntHook(reflect.TypeOf(""), reflect.TypeOf((*big.Int)(nil)), strconv.FormatInt(anyNumber, 10)) + require.NoError(t, err) + bi, ok := result.(*big.Int) + require.True(t, ok) + assert.Equal(t, anyNumber, bi.Int64()) + }) + + t.Run("Converts to string", func(t *testing.T) { + anyNumber := int64(5) + result, err := codec.BigIntHook(reflect.TypeOf((*big.Int)(nil)), reflect.TypeOf(""), big.NewInt(anyNumber)) + require.NoError(t, err) + assert.Equal(t, strconv.FormatInt(anyNumber, 10), result) + }) + + t.Run("Errors for invalid string", func(t *testing.T) { + _, err := codec.BigIntHook(reflect.TypeOf(""), reflect.TypeOf((*big.Int)(nil)), "Not a number :(") + require.IsType(t, types.InvalidTypeError{}, err) + }) +} + +func TestSliceToArrayVerifySizeHook(t *testing.T) { + t.Run("correct size slice converts", func(t *testing.T) { + to := reflect.TypeOf([2]int64{}) + data := []int64{1, 2} + res, err := codec.SliceToArrayVerifySizeHook(reflect.TypeOf(data), to, data) + assert.NoError(t, err) + + // Mapstructure will convert slices to arrays, all we need in this hook is to pass it along + assert.Equal(t, data, res) + }) + + t.Run("Too large slice returns error", func(t *testing.T) { + to := reflect.TypeOf([2]int64{}) + data := []int64{1, 2, 3} + _, err := codec.SliceToArrayVerifySizeHook(reflect.TypeOf(data), to, data) + assert.IsType(t, types.WrongNumberOfElements{}, err) + }) + + t.Run("Too small slice returns error", func(t *testing.T) { + to := reflect.TypeOf([2]int64{}) + data := []int64{1} + _, err := codec.SliceToArrayVerifySizeHook(reflect.TypeOf(data), to, data) + assert.IsType(t, types.WrongNumberOfElements{}, err) + }) +} diff --git a/pkg/loop/internal/chain_reader.go b/pkg/loop/internal/chain_reader.go index 83e0db569..70e4cdb89 100644 --- a/pkg/loop/internal/chain_reader.go +++ b/pkg/loop/internal/chain_reader.go @@ -4,8 +4,6 @@ import ( "context" jsonv1 "encoding/json" "fmt" - "reflect" - "unicode" jsonv2 "github.com/go-json-experiment/json" @@ -89,33 +87,6 @@ func decodeVersionedBytes(res any, vData *pb.VersionedBytes) error { return nil } -func isArray(vData *pb.VersionedBytes) (bool, error) { - data := vData.Data - if len(data) > 0 { - switch vData.Version { - case JSONEncodingVersion1: - fallthrough - case JSONEncodingVersion2: - i := int(0) - for ; i < len(data); i++ { - if !unicode.IsSpace(rune(data[i])) { - break - } - } - return i < len(data) && data[i] == '[', nil - case CBOREncodingVersion: - - // Major type for array in CBOR is 4 which is 100 in binary. - // So, we are checking if the first 3 bits are 100. - return data[0]>>5 == 4, nil - default: - return false, fmt.Errorf("Unsupported encoding version %d for versionedData %v", vData.Version, vData.Data) - } - } - - return false, nil -} - func (c *chainReaderClient) GetLatestValue(ctx context.Context, bc types.BoundContract, method string, params, retVal any) error { versionedParams, err := encodeVersionedBytes(params, ParamsCurrentEncodingVersion) if err != nil { @@ -151,11 +122,9 @@ func (c *chainReaderClient) Encode(ctx context.Context, item any, itemType strin } func (c *chainReaderClient) Decode(ctx context.Context, raw []byte, into any, itemType string) error { - k := reflect.ValueOf(into).Kind() request := &pb.GetDecodingRequest{ - Encoded: raw, - ItemType: itemType, - ForceSplit: k == reflect.Array || k == reflect.Slice, + Encoded: raw, + ItemType: itemType, } resp, err := c.grpc.GetDecoding(ctx, request) if err != nil { @@ -196,7 +165,7 @@ func (c *chainReaderServer) GetLatestValue(ctx context.Context, request *pb.GetL bc.Address = request.Bc.Address[:] bc.Pending = request.Bc.Pending - params, err := c.getEncodedType(request.Method, false, true) + params, err := c.getEncodedType(request.Method, true) if err != nil { return nil, err } @@ -205,7 +174,7 @@ func (c *chainReaderServer) GetLatestValue(ctx context.Context, request *pb.GetL return nil, err } - retVal, err := c.getEncodedType(request.Method, false, false) + retVal, err := c.getEncodedType(request.Method, false) if err != nil { return nil, err } @@ -223,12 +192,7 @@ func (c *chainReaderServer) GetLatestValue(ctx context.Context, request *pb.GetL } func (c *chainReaderServer) GetEncoding(ctx context.Context, req *pb.GetEncodingRequest) (*pb.GetEncodingResponse, error) { - forceArray, err := isArray(req.Params) - if err != nil { - return nil, err - } - - encodedType, err := c.getEncodedType(req.ItemType, forceArray, true) + encodedType, err := c.getEncodedType(req.ItemType, true) if err != nil { return nil, err } @@ -242,7 +206,7 @@ func (c *chainReaderServer) GetEncoding(ctx context.Context, req *pb.GetEncoding } func (c *chainReaderServer) GetDecoding(ctx context.Context, req *pb.GetDecodingRequest) (*pb.GetDecodingResponse, error) { - encodedType, err := c.getEncodedType(req.ItemType, req.ForceSplit, false) + encodedType, err := c.getEncodedType(req.ItemType, false) if err != nil { return nil, err } @@ -271,9 +235,9 @@ func (c *chainReaderServer) GetMaxSize(ctx context.Context, req *pb.GetMaxSizeRe return &pb.GetMaxSizeResponse{SizeInBytes: int32(maxSize)}, nil } -func (c *chainReaderServer) getEncodedType(itemType string, forceArray bool, forEncoding bool) (any, error) { +func (c *chainReaderServer) getEncodedType(itemType string, forEncoding bool) (any, error) { if rc, ok := c.impl.(types.RemoteCodec); ok { - return rc.CreateType(itemType, forceArray, forEncoding) + return rc.CreateType(itemType, forEncoding) } return &map[string]any{}, nil diff --git a/pkg/loop/internal/chain_reader_test.go b/pkg/loop/internal/chain_reader_test.go index d157fa20b..3bde8b550 100644 --- a/pkg/loop/internal/chain_reader_test.go +++ b/pkg/loop/internal/chain_reader_test.go @@ -62,36 +62,6 @@ func TestVersionedBytesFunctions(t *testing.T) { t.Errorf("expected error: %s, but got: %v", expected, err) } }) - - t.Run("detect json array with leading whitespace", func(t *testing.T) { - // Non-array with leading whitespace - versionedBytes := &pb.VersionedBytes{ - Version: 0, // json - Data: []byte("\n { 'key' : 'value' } "), - } - b, err := isArray(versionedBytes) - assert.NoError(t, err) - assert.False(t, b) - versionedBytesArray := &pb.VersionedBytes{ - Version: 0, // json - Data: []byte("\n [ 1, 2, 3 ] "), - } - - // Array with leading whitespace - b, err = isArray(versionedBytesArray) - assert.NoError(t, err) - assert.True(t, b) - - // All whitespace - versionedBytesAllWhitespace := &pb.VersionedBytes{ - Version: 0, // json - Data: []byte(" \t "), - } - b, err = isArray(versionedBytesAllWhitespace) - assert.NoError(t, err) - assert.False(t, b) - - }) } func TestChainReaderClient(t *testing.T) { @@ -175,8 +145,6 @@ type interfaceTester struct { fs *fakeCodecServer } -const methodName = "method" - var encoder = makeEncoder() func makeEncoder() cbor.EncMode { @@ -186,9 +154,17 @@ func makeEncoder() cbor.EncMode { return e } -func (it *interfaceTester) SetLatestValue(_ *testing.T, testStruct *TestStruct) (types.BoundContract, string) { +func (it *interfaceTester) SetLatestValue(ctx context.Context, t *testing.T, testStruct *TestStruct) types.BoundContract { it.fs.SetLatestValue(testStruct) - return types.BoundContract{}, methodName + return types.BoundContract{} +} + +func (it *interfaceTester) GetPrimitiveContract(ctx context.Context, t *testing.T) types.BoundContract { + return types.BoundContract{} +} + +func (it *interfaceTester) GetSliceContract(ctx context.Context, t *testing.T) types.BoundContract { + return types.BoundContract{} } func (it *interfaceTester) EncodeFields(t *testing.T, request *EncodeRequest) ocrtypes.Report { @@ -274,7 +250,19 @@ func (f *fakeCodecServer) SetLatestValue(ts *TestStruct) { f.latest = append(f.latest, *ts) } -func (f *fakeCodecServer) GetLatestValue(ctx context.Context, _ types.BoundContract, _ string, params, returnVal any) error { +func (f *fakeCodecServer) GetLatestValue(ctx context.Context, _ types.BoundContract, method string, params, returnVal any) error { + if method == MethodReturningUint64 { + r := returnVal.(*uint64) + *r = AnyValueToReadWithoutAnArgument + return nil + } else if method == MethodReturningUint64Slice { + r := returnVal.(*[]uint64) + *r = AnySliceToReadWithoutAnArgument + return nil + } else if method != MethodTakingLatestParamsReturningTestStruct { + return errors.New("unknown method " + method) + } + f.lock.Lock() defer f.lock.Unlock() lp := params.(*LatestParams) @@ -300,7 +288,7 @@ func (f *fakeCodecServer) Decode(_ context.Context, raw []byte, into any, itemTy return types.InvalidTypeError{} } -func (f *fakeCodecServer) CreateType(itemType string, _, isEncode bool) (any, error) { +func (f *fakeCodecServer) CreateType(itemType string, isEncode bool) (any, error) { switch itemType { case TestItemType: return &TestStruct{}, nil @@ -310,11 +298,17 @@ func (f *fakeCodecServer) CreateType(itemType string, _, isEncode bool) (any, er return &[2]TestStruct{}, nil case TestItemArray1Type: return &[1]TestStruct{}, nil - case methodName: + case MethodTakingLatestParamsReturningTestStruct: if isEncode { return &LatestParams{}, nil } return &TestStruct{}, nil + case MethodReturningUint64: + tmp := uint64(0) + return &tmp, nil + case MethodReturningUint64Slice: + var tmp []uint64 + return &tmp, nil } return nil, types.InvalidTypeError{} diff --git a/pkg/loop/internal/pb/relayer.pb.go b/pkg/loop/internal/pb/relayer.pb.go index 934da0d93..326bcbf4f 100644 --- a/pkg/loop/internal/pb/relayer.pb.go +++ b/pkg/loop/internal/pb/relayer.pb.go @@ -2420,9 +2420,8 @@ type GetDecodingRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Encoded []byte `protobuf:"bytes,1,opt,name=encoded,proto3" json:"encoded,omitempty"` - ItemType string `protobuf:"bytes,2,opt,name=itemType,proto3" json:"itemType,omitempty"` - ForceSplit bool `protobuf:"varint,3,opt,name=forceSplit,proto3" json:"forceSplit,omitempty"` + Encoded []byte `protobuf:"bytes,1,opt,name=encoded,proto3" json:"encoded,omitempty"` + ItemType string `protobuf:"bytes,2,opt,name=itemType,proto3" json:"itemType,omitempty"` } func (x *GetDecodingRequest) Reset() { @@ -2471,13 +2470,6 @@ func (x *GetDecodingRequest) GetItemType() string { return "" } -func (x *GetDecodingRequest) GetForceSplit() bool { - if x != nil { - return x.ForceSplit - } - return false -} - type GetDecodingResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3189,14 +3181,12 @@ var file_relayer_proto_rawDesc = []byte{ 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x22, 0x2d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x22, 0x6a, 0x0a, 0x12, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x22, 0x4a, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x6f, - 0x72, 0x63, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x22, 0x43, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, + 0x69, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x22, 0x43, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64, diff --git a/pkg/loop/internal/pb/relayer.proto b/pkg/loop/internal/pb/relayer.proto index adb01b365..5023f663f 100644 --- a/pkg/loop/internal/pb/relayer.proto +++ b/pkg/loop/internal/pb/relayer.proto @@ -292,7 +292,6 @@ message GetEncodingResponse { message GetDecodingRequest { bytes encoded = 1; string itemType = 2; - bool forceSplit = 3; } message GetDecodingResponse { diff --git a/pkg/types/codec.go b/pkg/types/codec.go index ed81700b6..4acbfcc68 100644 --- a/pkg/types/codec.go +++ b/pkg/types/codec.go @@ -30,7 +30,7 @@ type Codec interface { } type TypeProvider interface { - CreateType(itemType string, forceSlice, forEncoding bool) (any, error) + CreateType(itemType string, forEncoding bool) (any, error) } type RemoteCodec interface { diff --git a/pkg/types/interfacetests/codec_interface_tests.go b/pkg/types/interfacetests/chain_reader_interface_tests.go similarity index 84% rename from pkg/types/interfacetests/codec_interface_tests.go rename to pkg/types/interfacetests/chain_reader_interface_tests.go index 0d8d52400..7b3acdbd3 100644 --- a/pkg/types/interfacetests/codec_interface_tests.go +++ b/pkg/types/interfacetests/chain_reader_interface_tests.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "sort" "testing" "github.com/stretchr/testify/assert" @@ -36,16 +37,24 @@ type ChainReaderInterfaceTester interface { // SetLatestValue is expected to return the same bound contract and method in the same test // Any setup required for this should be done in Setup. // The contract should take a LatestParams as the params and return the nth TestStruct set - SetLatestValue(t *testing.T, testStruct *TestStruct) (types.BoundContract, string) + SetLatestValue(ctx context.Context, t *testing.T, testStruct *TestStruct) types.BoundContract + GetPrimitiveContract(ctx context.Context, t *testing.T) types.BoundContract + GetSliceContract(ctx context.Context, t *testing.T) types.BoundContract } const ( - TestItemType = "TestItem" - TestItemSliceType = "TestItemSliceType" - TestItemArray1Type = "TestItemArray1Type" - TestItemArray2Type = "TestItemArray2Type" + TestItemType = "TestItem" + TestItemSliceType = "TestItemSliceType" + TestItemArray1Type = "TestItemArray1Type" + TestItemArray2Type = "TestItemArray2Type" + AnyValueToReadWithoutAnArgument = uint64(3) + MethodTakingLatestParamsReturningTestStruct = "GetLatestValues" + MethodReturningUint64 = "GetPrimitiveValue" + MethodReturningUint64Slice = "GetSliceValue" ) +var AnySliceToReadWithoutAnArgument = []uint64{3, 4} + // RunChainReaderInterfaceTests uses TestStruct and TestStructWithSpecialFields func RunChainReaderInterfaceTests(t *testing.T, tester ChainReaderInterfaceTester) { ctx := context.Background() @@ -168,7 +177,7 @@ func RunChainReaderInterfaceTests(t *testing.T, tester ChainReaderInterfaceTeste require.NoError(t, codec.Decode(ctx, actualEncoding, &into, TestItemArray2Type)) assert.Equal(t, items, into) }, - "Encodes and decodes a arrays with one element": func(t *testing.T) { + "Encodes and decodes an arrays with one element": func(t *testing.T) { item1 := CreateTestStruct(0, tester.GetAccountBytes) items := [1]TestStruct{item1} req := &EncodeRequest{TestStructs: items[:], TestOn: TestItemArray1Type} @@ -204,7 +213,7 @@ func RunChainReaderInterfaceTests(t *testing.T, tester ChainReaderInterfaceTeste codec := tester.GetChainReader(t) _, err := codec.Encode(ctx, items, TestItemArray2Type) - assert.IsType(t, types.InvalidTypeError{}, err) + assert.IsType(t, types.WrongNumberOfElements{}, err) }, "Returns an error encoding if arrays are the too large to encode": func(t *testing.T) { if !tester.IncludeArrayEncodingSizeEnforcement() { @@ -217,26 +226,46 @@ func RunChainReaderInterfaceTests(t *testing.T, tester ChainReaderInterfaceTeste codec := tester.GetChainReader(t) _, err := codec.Encode(ctx, items, TestItemArray1Type) - assert.IsType(t, types.InvalidTypeError{}, err) + assert.IsType(t, types.WrongNumberOfElements{}, err) }, "Gets the latest value": func(t *testing.T) { firstItem := CreateTestStruct(0, tester.GetAccountBytes) - bc, method := tester.SetLatestValue(t, &firstItem) + bc := tester.SetLatestValue(ctx, t, &firstItem) secondItem := CreateTestStruct(1, tester.GetAccountBytes) - tester.SetLatestValue(t, &secondItem) + tester.SetLatestValue(ctx, t, &secondItem) cr := tester.GetChainReader(t) actual := &TestStruct{} params := &LatestParams{I: 1} - require.NoError(t, cr.GetLatestValue(ctx, bc, method, params, actual)) + require.NoError(t, cr.GetLatestValue(ctx, bc, MethodTakingLatestParamsReturningTestStruct, params, actual)) assert.Equal(t, &firstItem, actual) params.I = 2 actual = &TestStruct{} - require.NoError(t, cr.GetLatestValue(ctx, bc, method, params, actual)) + require.NoError(t, cr.GetLatestValue(ctx, bc, MethodTakingLatestParamsReturningTestStruct, params, actual)) assert.Equal(t, &secondItem, actual) }, + "Get latest value without arguments and with primitive return": func(t *testing.T) { + bc := tester.GetPrimitiveContract(ctx, t) + + cr := tester.GetChainReader(t) + + var prim uint64 + require.NoError(t, cr.GetLatestValue(ctx, bc, MethodReturningUint64, nil, &prim)) + + assert.Equal(t, AnyValueToReadWithoutAnArgument, prim) + }, + "Get latest value without arguments and with slice return": func(t *testing.T) { + bc := tester.GetSliceContract(ctx, t) + + cr := tester.GetChainReader(t) + + var slice []uint64 + require.NoError(t, cr.GetLatestValue(ctx, bc, MethodReturningUint64Slice, nil, &slice)) + + assert.Equal(t, AnySliceToReadWithoutAnArgument, slice) + }, "GetMaxEncodingSize returns errors for unknown types": func(t *testing.T) { cr := tester.GetChainReader(t) _, err := cr.GetMaxEncodingSize(ctx, 10, "not"+TestItemType) @@ -310,11 +339,19 @@ func RunChainReaderWithStrictArgsInterfaceTest(t *testing.T, tester ChainReaderI } func runTests(t *testing.T, tester ChainReaderInterfaceTester, tests map[string]func(t *testing.T)) { - for name, test := range tests { + // Order the tests for consistancy + testNames := make([]string, 0, len(tests)) + for name := range tests { + testNames = append(testNames, name) + } + sort.Strings(testNames) + + for i := 0; i < len(testNames); i++ { + name := testNames[i] t.Run(name, func(t *testing.T) { tester.Setup(t) defer func() { tester.Teardown(t) }() - test(t) + tests[name](t) }) } }