From de3d8771cd369cdd8b2ac51770cc99f02308b5e2 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Fri, 30 Jun 2017 16:10:28 -0500 Subject: [PATCH] accounts/abi: fixes for static array conversion Signed-off-by: RJ Catalano --- accounts/abi/method.go | 3 +- accounts/abi/unpack.go | 37 +++++++++++++++------- accounts/abi/unpack_test.go | 63 ++++++++++++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 6323451b1a..a15a989170 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -77,6 +77,7 @@ func (method Method) pack(args ...interface{}) ([]byte, error) { return ret, nil } +// unpacks a method return tuple into a struct of corresponding go types func (method Method) tupleUnpack(v interface{}, output []byte) error { // make sure the passed value is a pointer valueOf := reflect.ValueOf(v) @@ -118,7 +119,7 @@ func (method Method) tupleUnpack(v interface{}, output []byte) error { return nil } -func (method Method) tupleReturn() bool { return len(method.Outputs) > 1 } +func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 } func (method Method) singleUnpack(v interface{}, output []byte) error { // make sure the passed value is a pointer diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 93322e93bc..5887907f2d 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -25,12 +25,16 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// unpacker is a utility interface that enables us to have +// abstraction between events and methods and also to properly +// "unpack" them; e.g. events use Inputs, methods use Outputs. type unpacker interface { tupleUnpack(v interface{}, output []byte) error singleUnpack(v interface{}, output []byte) error - tupleReturn() bool + isTupleReturn() bool } +// reads the integer based on its kind func readInteger(kind reflect.Kind, b []byte) interface{} { switch kind { case reflect.Uint8: @@ -54,6 +58,7 @@ func readInteger(kind reflect.Kind, b []byte) interface{} { } } +// reads a bool func readBool(word []byte) (bool, error) { if len(word) != 32 { return false, fmt.Errorf("abi: fatal error: incorrect word length") @@ -74,6 +79,8 @@ func readBool(word []byte) (bool, error) { } } +// A function type is simply the address with the function selection signature at the end. +// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { if t.T != FunctionTy { return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array.") @@ -86,6 +93,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { return } +// through reflection, creates a fixed array to be read from func readFixedBytes(t Type, word []byte) (interface{}, error) { if t.T != FixedBytesTy { return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array.") @@ -98,6 +106,7 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) { } +// iteratively unpack elements func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { if start+32*size > len(output) { return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) @@ -106,6 +115,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) // this value will become our slice or our array, depending on the type var refSlice reflect.Value slice := output[start : start+size*32] + if t.T == SliceTy { // declare our slice refSlice = reflect.MakeSlice(t.Type, size, size) @@ -117,6 +127,10 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) } for i, j := start, 0; j*32 < len(slice); i, j = i+32, j+1 { + // this corrects the arrangement so that we get all the underlying array values + if t.Elem.T == ArrayTy && j != 0 { + i = start + t.Elem.Size*32*j + } inter, err := toGoType(i, *t.Elem, output) if err != nil { return nil, err @@ -129,36 +143,36 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) return refSlice.Interface(), nil } -// toGoType parses the input and casts it to the proper type defined by the ABI -// argument in T. +// toGoType parses the output bytes and recursively assigns the value of these bytes +// into a go type with accordance with the ABI spec. func toGoType(index int, t Type, output []byte) (interface{}, error) { if index+32 > len(output) { return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) } - // Parse the given index output and check whether we need to read - // a different offset and length based on the type (i.e. string, bytes) var ( returnOutput []byte - i, j int + begin, end int err error ) + // if we require a length prefix, find the beginning word and size returned. if t.requiresLengthPrefix() { - i, j, err = lengthPrefixPointsTo(index, output) + begin, end, err = lengthPrefixPointsTo(index, output) if err != nil { return nil, err } } else { returnOutput = output[index : index+32] } + switch t.T { case SliceTy: - return forEachUnpack(t, output, i, j) + return forEachUnpack(t, output, begin, end) case ArrayTy: - return forEachUnpack(t, output, i, t.Size) + return forEachUnpack(t, output, index, t.Size) case StringTy: // variable arrays are written at the end of the return bytes - return string(output[i : i+j]), nil + return string(output[begin : begin+end]), nil case IntTy, UintTy: return readInteger(t.Kind, returnOutput), nil case BoolTy: @@ -168,7 +182,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { case HashTy: return common.BytesToHash(returnOutput), nil case BytesTy: - return output[i : i+j], nil + return output[begin : begin+end], nil case FixedBytesTy: return readFixedBytes(t, returnOutput) case FunctionTy: @@ -194,6 +208,7 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err return } +// checks for proper formatting of byte output func bytesAreProper(output []byte) error { if len(output) == 0 { return fmt.Errorf("abi: unmarshalling empty output") diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 692bb40727..02d4a4797f 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -func TestSimpleMethodUnpack(t *testing.T) { +func TestSingleValueUnpack(t *testing.T) { for i, test := range []struct { def string // definition of the **output** ABI params marshalledOutput []byte // evm return data @@ -128,6 +128,13 @@ func TestSimpleMethodUnpack(t *testing.T) { "fixedBytes32", "abi: cannot unmarshal []uint8 in to [32]uint8", }, + { + `[ { "type": "bytes32" } ]`, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000"), + nil, + "bytes", + "abi: cannot unmarshal [32]uint8 in to []uint8", + }, { `[ { "type": "bytes32" } ]`, common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), @@ -238,7 +245,7 @@ func TestSimpleMethodUnpack(t *testing.T) { } } } -func TestArraysAndSlicesUnpack(t *testing.T) { +func TestSingleArraysAndSlicesUnpack(t *testing.T) { for i, test := range []struct { def string // definition of the **output** ABI params marshalledOutput []byte // evm return data @@ -253,6 +260,14 @@ func TestArraysAndSlicesUnpack(t *testing.T) { "[]uint8", "", }, + { + `[ { "type": "uint8[2]" } ]`, + common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + [2]uint8{1, 2}, + "[2]uint8", + "", + }, + // multi dimensional, if these pass, all types that don't require length prefix should pass { `[ { "type": "uint8[][]" } ]`, common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000E0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), @@ -261,10 +276,24 @@ func TestArraysAndSlicesUnpack(t *testing.T) { "", }, { - `[ { "type": "uint8[2]" } ]`, - common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - [2]uint8{1, 2}, - "[2]uint8", + `[ { "type": "uint8[2][2]" } ]`, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + [2][2]uint8{{1, 2}, {1, 2}}, + "[2][2]uint8", + "", + }, + { + `[ { "type": "uint8[][2]" } ]`, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"), + [2][]uint8{{1}, {1}}, + "[2][]uint8", + "", + }, + { + `[ { "type": "uint8[2][]" } ]`, + common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), + [][2]uint8{{1, 2}}, + "[][2]uint8", "", }, { @@ -394,8 +423,6 @@ func TestArraysAndSlicesUnpack(t *testing.T) { "", }, } { - //t.Log(test.marshalledOutput) - abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(abiDefinition)) if err != nil { @@ -409,6 +436,26 @@ func TestArraysAndSlicesUnpack(t *testing.T) { var v [][]uint8 err = abi.Unpack(&v, "method", test.marshalledOutput) outvar = v + case "[2][2]uint8": + var v [2][2]uint8 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "[][2]uint8": + var v [][2]uint8 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "[2][]uint8": + var v [2][]uint8 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "[2][2][2]uint8": + var v [2][2][2]uint8 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v + case "[][][]uint8": + var v [][][]uint8 + err = abi.Unpack(&v, "method", test.marshalledOutput) + outvar = v case "[]uint8": var v []uint8 err = abi.Unpack(&v, "method", test.marshalledOutput)