diff --git a/core/services/relay/evm/decoder.go b/core/services/relay/evm/decoder.go new file mode 100644 index 00000000000..d4b7f3508a6 --- /dev/null +++ b/core/services/relay/evm/decoder.go @@ -0,0 +1,84 @@ +package evm + +import ( + "context" + "reflect" + + "github.com/mitchellh/mapstructure" + relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" +) + +type decoder struct { + Definitions map[string]*CodecEntry +} + +var _ relaytypes.Decoder = &decoder{} + +func (m *decoder) Decode(ctx context.Context, raw []byte, into any, itemType string) error { + info, ok := m.Definitions[itemType] + if !ok { + return relaytypes.InvalidTypeError{} + } + + decode, err := extractDecoding(info, raw) + if err != nil { + return err + } + + rDecode := reflect.ValueOf(decode) + switch rDecode.Kind() { + case reflect.Array: + iInto := reflect.Indirect(reflect.ValueOf(into)) + length := rDecode.Len() + if length != iInto.Len() { + return relaytypes.WrongNumberOfElements{} + } + iInto.Set(reflect.New(iInto.Type()).Elem()) + return setElements(length, rDecode, iInto) + case reflect.Slice: + iInto := reflect.Indirect(reflect.ValueOf(into)) + length := rDecode.Len() + iInto.Set(reflect.MakeSlice(iInto.Type(), length, length)) + return setElements(length, rDecode, iInto) + default: + return mapstructureDecode(decode, into) + } +} + +func (m *decoder) GetMaxDecodingSize(ctx context.Context, n int, itemType string) (int, error) { + return GetMaxSizeFormEntry(n, m.Definitions[itemType]) +} + +func extractDecoding(info *CodecEntry, raw []byte) (any, error) { + unpacked := map[string]any{} + if err := info.Args.UnpackIntoMap(unpacked, raw); err != nil { + return nil, relaytypes.InvalidEncodingError{} + } + var decode any = unpacked + + if noName, ok := unpacked[""]; ok { + decode = noName + } + return decode, nil +} + +func setElements(length int, rDecode reflect.Value, iInto reflect.Value) error { + for i := 0; i < length; i++ { + if err := mapstructureDecode(rDecode.Index(i).Interface(), iInto.Index(i).Addr().Interface()); err != nil { + return err + } + } + + return nil +} + +func mapstructureDecode(src, dest any) error { + mDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: evmDecoderHook, + Result: dest, + }) + if err != nil || mDecoder.Decode(src) != nil { + return relaytypes.InvalidTypeError{} + } + return nil +} diff --git a/core/services/relay/evm/encoder.go b/core/services/relay/evm/encoder.go new file mode 100644 index 00000000000..8cbdc2cc2a0 --- /dev/null +++ b/core/services/relay/evm/encoder.go @@ -0,0 +1,140 @@ +package evm + +import ( + "context" + "math/big" + "reflect" + + "github.com/mitchellh/mapstructure" + "github.com/smartcontractkit/chainlink-relay/pkg/codec" + relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type encoder struct { + Definitions map[string]*CodecEntry +} + +var evmDecoderHook = mapstructure.ComposeDecodeHookFunc(codec.BigIntHook, codec.SliceToArrayVerifySizeHook, sizeVerifyBigIntHook) + +var _ relaytypes.Encoder = &encoder{} + +func (e *encoder) Encode(ctx context.Context, item any, itemType string) (ocrtypes.Report, error) { + info, ok := e.Definitions[itemType] + if !ok { + return nil, relaytypes.InvalidTypeError{} + } + + if item == nil { + cpy := make([]byte, len(info.encodingPrefix)) + copy(cpy, info.encodingPrefix) + return cpy, nil + } + + return encode(reflect.ValueOf(item), info) +} + +func (e *encoder) GetMaxEncodingSize(ctx context.Context, n int, itemType string) (int, error) { + return GetMaxSizeFormEntry(n, e.Definitions[itemType]) +} + +func encode(item reflect.Value, info *CodecEntry) (ocrtypes.Report, error) { + iType := item.Type() + for iType.Kind() == reflect.Pointer { + iType = iType.Elem() + } + switch iType.Kind() { + case reflect.Pointer: + return encode(item.Elem(), info) + case reflect.Array, reflect.Slice: + return encodeArray(item, info) + case reflect.Struct, reflect.Map: + return encodeItem(item, info) + default: + return nil, relaytypes.InvalidEncodingError{} + } +} + +func encodeArray(item reflect.Value, info *CodecEntry) (ocrtypes.Report, error) { + length := item.Len() + var native reflect.Value + switch info.checkedType.Kind() { + case reflect.Array: + if info.checkedType.Len() != length { + return nil, relaytypes.WrongNumberOfElements{} + } + native = reflect.New(info.nativeType).Elem() + case reflect.Slice: + native = reflect.MakeSlice(info.nativeType, length, length) + default: + return nil, relaytypes.InvalidTypeError{} + } + + checkedElm := info.checkedType.Elem() + nativeElm := info.nativeType.Elem() + for i := 0; i < length; i++ { + tmp := reflect.New(checkedElm) + if err := mapstructureDecode(item.Index(i).Interface(), tmp.Interface()); err != nil { + return nil, err + } + native.Index(i).Set(reflect.NewAt(nativeElm, tmp.UnsafePointer()).Elem()) + } + + return pack(info, native.Interface()) +} + +func encodeItem(item reflect.Value, info *CodecEntry) (ocrtypes.Report, error) { + if item.Type() == reflect.PointerTo(info.checkedType) { + item = reflect.NewAt(info.nativeType, item.UnsafePointer()) + } else if item.Type() != reflect.PointerTo(info.nativeType) { + checked := reflect.New(info.checkedType) + if err := mapstructureDecode(item.Interface(), checked.Interface()); err != nil { + return nil, err + } + item = reflect.NewAt(info.nativeType, checked.UnsafePointer()) + } + + item = reflect.Indirect(item) + length := item.NumField() + values := make([]any, length) + iType := item.Type() + for i := 0; i < length; i++ { + if iType.Field(i).IsExported() { + values[i] = item.Field(i).Interface() + } + } + + return pack(info, values...) +} + +func pack(info *CodecEntry, values ...any) (ocrtypes.Report, error) { + if bytes, err := info.Args.Pack(values...); err == nil { + withPrefix := make([]byte, 0, len(info.encodingPrefix)+len(bytes)) + withPrefix = append(withPrefix, info.encodingPrefix...) + return append(withPrefix, bytes...), nil + } + + return nil, relaytypes.InvalidTypeError{} +} + +func sizeVerifyBigIntHook(from, to reflect.Type, data any) (any, error) { + if !to.Implements(types.SizedBigIntType()) { + return data, nil + } + + var err error + data, err = codec.BigIntHook(from, reflect.TypeOf((*big.Int)(nil)), data) + if err != nil { + return nil, err + } + + bi, ok := data.(*big.Int) + if !ok { + return data, nil + } + + converted := reflect.ValueOf(bi).Convert(to).Interface().(types.SizedBigInt) + return converted, converted.Verify() +}