Skip to content
This repository has been archived by the owner on May 13, 2022. It is now read-only.

enable abi encoding / decoding of structs as function params #1285

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 35 additions & 1 deletion execution/evm/abi/abi_test.go
Expand Up @@ -132,6 +132,18 @@ func TestPacker(t *testing.T) {
"arrayOfBoolsPack",
append(pad([]byte{1}, 32, true), pad([]byte{0}, 32, true)...),
},
{
`[{"constant":false,"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"value","type":"tuple"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"}]`,
[]interface{}{"[hello,[100]]"},
"set",
hex.MustDecodeString("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000"),
},
{
`[{"constant":false,"inputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"components":[{"internalType":"string","name":"name","type":"string"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"value","type":"tuple"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"components":[{"internalType":"string","name":"name","type":"string"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"}]`,
[]interface{}{"[100,[hello]]"},
"set",
hex.MustDecodeString("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000"),
},
} {
t.Log(test.args)
if output, _, err := EncodeFunctionCall(test.ABI, test.name, logging.NewNoopLogger(), test.args...); err != nil {
Expand Down Expand Up @@ -311,8 +323,30 @@ func TestUnpackerString(t *testing.T) {
},
},
},
{
`[{"constant":false,"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"value","type":"tuple"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"components":[{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"}]`,
hex.MustDecodeString("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000"),
"get",
[]Variable{
{
Name: "0",
Value: "hello,100",
},
},
},
{
`[{"constant":false,"inputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"components":[{"internalType":"string","name":"name","type":"string"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"value","type":"tuple"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"components":[{"internalType":"uint256","name":"value","type":"uint256"},{"components":[{"internalType":"string","name":"name","type":"string"}],"internalType":"struct Test.Inner","name":"data","type":"tuple"}],"internalType":"struct Test.Outer","name":"","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"}]`,
hex.MustDecodeString("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000"),
"get",
[]Variable{
{
Name: "0",
Value: "100,hello",
},
},
},
} {
//t.Log(test.name)
// t.Log(test.name)
t.Log(test.packed)
output, err := DecodeFunctionReturn(test.abi, test.name, test.packed)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions execution/evm/abi/event_spec.go
Expand Up @@ -14,6 +14,7 @@ type Argument struct {
Name string
EVM EVMType
IsArray bool
IsTuple bool
Indexed bool
Hashed bool
ArrayLength uint64
Expand Down
72 changes: 70 additions & 2 deletions execution/evm/abi/packing.go
Expand Up @@ -2,6 +2,7 @@ package abi

import (
"fmt"
"math/big"
"reflect"
"strings"

Expand Down Expand Up @@ -86,6 +87,8 @@ func argGetter(argSpec []Argument, args []interface{}, ptr bool) (func(int) inte
return func(i int) interface{} { return args[i] }, nil
}
return nil, fmt.Errorf("expected single argument to be struct but got %v", rv.Kind())
} else if _, ok := args[0].(*big.Int); ok {
return func(i int) interface{} { return args[i] }, nil
}
fields := rv.NumField()
if fields != len(argSpec) {
Expand Down Expand Up @@ -153,7 +156,7 @@ func pack(argSpec []Argument, getArg func(int) interface{}) ([]byte, error) {
fixedSize := 0
// Anything dynamic is stored after the "fixed" block. For the dynamic types, the fixed
// block contains byte offsets to the data. We need to know the length of the fixed
// block, so we can calcute the offsets
// block, so we can calculate the offsets
for _, as := range argSpec {
if as.Indexed {
continue
Expand Down Expand Up @@ -209,7 +212,7 @@ func pack(argSpec []Argument, getArg func(int) interface{}) ([]byte, error) {

if as.ArrayLength > 0 {
if as.ArrayLength != uint64(val.Len()) {
return nil, fmt.Errorf("argumment %d should be array of %d, not %d", i, as.ArrayLength, val.Len())
return nil, fmt.Errorf("argument %d should be array of %d, not %d", i, as.ArrayLength, val.Len())
}

for n := 0; n < val.Len(); n++ {
Expand All @@ -236,6 +239,15 @@ func pack(argSpec []Argument, getArg func(int) interface{}) ([]byte, error) {
packedDynamic = append(packedDynamic, d...)
}
}
} else if as.IsTuple {

offset := EVMUint{M: 256}
b, _ := offset.pack(fixedSize)
packed = append(packed, b...)

d, _ := as.EVM.pack(a)
packed = append(packed, d...)

} else {
err := addArg(a, as)
if err != nil {
Expand Down Expand Up @@ -362,6 +374,47 @@ func unpack(argSpec []Argument, data []byte, getArg func(int) interface{}) error
s += "]"
*ret = s
}
} else if as.IsTuple {
var length int64

_, err := offType.unpack(data, offset, &length)
if err != nil {
return err
}

offset += int(length)

tuple, ok := as.EVM.(EVMTuple)
if !ok {
return fmt.Errorf("wanted EVMTuple, got %s", as.EVM.GetSignature())
}

intermediate := make([]interface{}, len(tuple.Arguments))
for i, arg := range tuple.Arguments {
intermediate[i] = arg.EVM.getGoType()

if arg.EVM.Dynamic() {
l, err := offType.unpack(data, offset, &length)
if err != nil {
return err
}
_, err = arg.EVM.unpack(data, offset+int(length), intermediate[i])
if err != nil {
return err
}
offset += l
} else {
l, err := arg.EVM.unpack(data, offset, intermediate[i])
if err != nil {
return err
}
offset += l
}
}

if ret, ok := arg.(*string); ok {
*ret = strings.Join(tupleToStrings(intermediate), ",")
}
} else {
err := getPrimitive(arg, as)
if err != nil {
Expand All @@ -372,3 +425,18 @@ func unpack(argSpec []Argument, data []byte, getArg func(int) interface{}) error

return nil
}

func tupleToStrings(elements []interface{}) []string {
result := make([]string, 0)
for _, elem := range elements {
switch elem := elem.(type) {
case *[]interface{}:
result = append(result, tupleToStrings(*elem)...)
case *string:
result = append(result, *elem)
default:
result = append(result, fmt.Sprintf("%v", elem))
}
}
return result
}
93 changes: 93 additions & 0 deletions execution/evm/abi/primitives.go
Expand Up @@ -763,3 +763,96 @@ func pad(input []byte, size int, left bool) []byte {
}
return padded
}

var _ EVMType = (*EVMTuple)(nil)

type EVMTuple struct {
Arguments []Argument
}

func (e EVMTuple) String() string {
return "EVMTuple"
}

func (e EVMTuple) GetSignature() string {
params := make([]string, 0)
for _, arg := range e.Arguments {
params = append(params, arg.EVM.GetSignature())
}
return fmt.Sprintf("(%s)", strings.Join(params, ","))
}

func (e EVMTuple) getGoType() interface{} {
return &[]interface{}{}
}

func (e EVMTuple) pack(v interface{}) ([]byte, error) {
packedStatic := make([]byte, 0)
var packedDynamic []byte

var args []string
s, ok := v.(string)
if ok {
s = strings.Trim(s, " ")
if s[0:1] == "[" && s[len(s)-1:] == "]" {
args = strings.Split(s[1:len(s)-1], ",")
}
}
// TODO: throw error if input not string?

for i, a := range args {
data, err := e.Arguments[i].EVM.pack(a)
if err != nil {
return nil, err
}
if e.Arguments[i].EVM.Dynamic() {
packedDynamic = append(packedDynamic, data...)
} else {
packedStatic = append(packedStatic, data...)
}
}

if len(packedDynamic) > 0 {
fixedSize := len(packedStatic)
if len(packedStatic) == 0 {
offset := EVMUint{M: 256}
fixedSize += ElementSize
b, _ := offset.pack(fixedSize)
packedDynamic = append(b, packedDynamic...)
fixedSize += len(b)
} else {
fixedSize += ElementSize
}
offset := EVMUint{M: 256}
b, _ := offset.pack(fixedSize)
packedStatic = append(b, packedStatic...)
}

return append(packedStatic, packedDynamic...), nil
}

func (e EVMTuple) unpack(data []byte, offset int, v interface{}) (int, error) {
vals := make([]interface{}, len(e.Arguments))
for i, arg := range e.Arguments {
vals[i] = arg.EVM.getGoType()
}
err := Unpack(e.Arguments, data[offset:], vals...)
if err != nil {
return 0, err
}

switch v := v.(type) {
case *[]interface{}:
*v = vals
}

return ElementSize, nil
}

func (e EVMTuple) Dynamic() bool {
return false
}

func (e EVMTuple) ImplicitCast(o EVMType) bool {
return false
}
16 changes: 16 additions & 0 deletions execution/evm/abi/spec.go
Expand Up @@ -230,6 +230,20 @@ func readArgSpec(argsJ []argumentJSON) ([]Argument, error) {
baseType = strings.TrimSuffix(a.Type, "[]")
}

isTuple := regexp.MustCompile(`tuple`)
m = isTuple.FindStringSubmatch(a.Type)
if m != nil {
components, err := readArgSpec(a.Components)
if err != nil {
return nil, err
}
args[i].IsTuple = true
args[i].EVM = EVMTuple{
Arguments: components,
}
continue
}

isM := regexp.MustCompile("(bytes|uint|int)([0-9]+)")
m = isM.FindStringSubmatch(baseType)
if m != nil {
Expand Down Expand Up @@ -300,6 +314,8 @@ func readArgSpec(argsJ []argumentJSON) ([]Argument, error) {
args[i].EVM = EVMBytes{M: 0}
case "string":
args[i].EVM = EVMString{}
case "tuple":
args[i].EVM = EVMTuple{}
default:
// Assume it is a type of Contract
args[i].EVM = EVMAddress{}
Expand Down