Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflect Marshaler #1592

Merged
merged 15 commits into from
Oct 10, 2024
39 changes: 18 additions & 21 deletions abi/dynamic/reflect_marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package dynamic

import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
Expand All @@ -20,13 +21,11 @@ import (
"github.com/ava-labs/hypersdk/consts"
)

// Matches fixed-size arrays like [32]uint8
var fixedSizeArrayRegex = regexp.MustCompile(`^\[(\d+)\](.+)$`)
var ErrTypeNotFound = errors.New("type not found in ABI")

func Marshal(inputABI abi.ABI, typeName string, jsonData string) ([]byte, error) {
_, ok := findABIType(inputABI, typeName)
if !ok {
return nil, fmt.Errorf("type %s not found in ABI", typeName)
if _, ok := findABIType(inputABI, typeName); !ok {
return nil, fmt.Errorf("marshalling %s: %w", typeName, ErrTypeNotFound)
}

typeCache := make(map[string]reflect.Type)
Expand All @@ -38,52 +37,51 @@ func Marshal(inputABI abi.ABI, typeName string, jsonData string) ([]byte, error)

value := reflect.New(typ).Interface()

err = json.Unmarshal([]byte(jsonData), value)
if err != nil {
if err := json.Unmarshal([]byte(jsonData), value); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON data: %w", err)
}

writer := codec.NewWriter(0, consts.NetworkSizeLimit)
err = codec.LinearCodec.MarshalInto(value, writer.Packer)
if err != nil {
if err := codec.LinearCodec.MarshalInto(value, writer.Packer); err != nil {
return nil, fmt.Errorf("failed to marshal struct: %w", err)
}

return writer.Bytes(), nil
}

func Unmarshal(inputABI abi.ABI, typeName string, data []byte) (string, error) {
_, ok := findABIType(inputABI, typeName)
if !ok {
return "", fmt.Errorf("type %s not found in ABI", typeName)
if _, ok := findABIType(inputABI, typeName); !ok {
return "", fmt.Errorf("unmarshalling %s: %w", typeName, ErrTypeNotFound)
}

typeCache := make(map[string]reflect.Type)

dynamicType, err := getReflectType(typeName, inputABI, typeCache)
typ, err := getReflectType(typeName, inputABI, typeCache)
if err != nil {
return "", fmt.Errorf("failed to get reflect type: %w", err)
}

dynamicValue := reflect.New(dynamicType).Interface()
value := reflect.New(typ).Interface()

packer := wrappers.Packer{
Bytes: data,
MaxSize: consts.NetworkSizeLimit,
}
err = codec.LinearCodec.UnmarshalFrom(&packer, dynamicValue)
if err != nil {
if err := codec.LinearCodec.UnmarshalFrom(&packer, value); err != nil {
return "", fmt.Errorf("failed to unmarshal data: %w", err)
}

jsonData, err := json.Marshal(dynamicValue)
jsonData, err := json.Marshal(value)
if err != nil {
return "", fmt.Errorf("failed to marshal struct to JSON: %w", err)
}

return string(jsonData), nil
}

// Matches fixed-size arrays like [32]uint8
var fixedSizeArrayRegex = regexp.MustCompile(`^\[(\d+)\](.+)$`)

func getReflectType(abiTypeName string, inputABI abi.ABI, typeCache map[string]reflect.Type) (reflect.Type, error) {
switch abiTypeName {
case "string":
Expand Down Expand Up @@ -117,8 +115,8 @@ func getReflectType(abiTypeName string, inputABI abi.ABI, typeCache map[string]r
}

// golang arrays
match := fixedSizeArrayRegex.FindStringSubmatch(abiTypeName) // ^\[(\d+)\](.+)$
if match != nil {

if match := fixedSizeArrayRegex.FindStringSubmatch(abiTypeName); match != nil {
sizeStr := match[1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is unnecessary as it's only used on the next line and it doesn't add any additional context beyond what size itself does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's exclusively for readability

size, err := strconv.Atoi(sizeStr)
if err != nil {
Expand All @@ -132,8 +130,7 @@ func getReflectType(abiTypeName string, inputABI abi.ABI, typeCache map[string]r
}

// For custom types, recursively construct the struct type
cachedType, ok := typeCache[abiTypeName]
if ok {
if cachedType, ok := typeCache[abiTypeName]; ok {
containerman17 marked this conversation as resolved.
Show resolved Hide resolved
return cachedType, nil
}

Expand Down
22 changes: 9 additions & 13 deletions abi/dynamic/reflect_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,15 @@ func TestDynamicMarshalErrors(t *testing.T) {
err := json.Unmarshal(abiJSON, &abi)
require.NoError(err)

t.Run("malformed JSON", func(t *testing.T) {
malformedJSON := `{"uint8": 42, "uint16": 1000, "uint32": 100000, "uint64": 10000000000, "int8": -42, "int16": -1000, "int32": -100000, "int64": -10000000000,`
_, err := Marshal(abi, "MockObjectAllNumbers", malformedJSON)
require.Error(err)
require.Contains(err.Error(), "unexpected end of JSON input")
})

t.Run("wrong struct name", func(t *testing.T) {
jsonData := mustReadFile(t, "../testdata/numbers.json")
_, err := Marshal(abi, "NonExistentObject", string(jsonData))
require.Error(err)
require.Contains(err.Error(), "type NonExistentObject not found in ABI")
})
// Test malformed JSON
malformedJSON := `{"uint8": 42, "uint16": 1000, "uint32": 100000, "uint64": 10000000000, "int8": -42, "int16": -1000, "int32": -100000, "int64": -10000000000,`
_, err = Marshal(abi, "MockObjectAllNumbers", malformedJSON)
require.Contains(err.Error(), "failed to unmarshal JSON data")
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved

// Test wrong struct name
jsonData := mustReadFile(t, "../testdata/numbers.json")
_, err = Marshal(abi, "NonExistentObject", string(jsonData))
require.ErrorIs(err, ErrTypeNotFound)
}

func mustReadFile(t *testing.T, path string) []byte {
Expand Down
Loading