accounts/abi: fixes for static array conversion

Signed-off-by: RJ Catalano <rj@monax.io>
This commit is contained in:
RJ Catalano 2017-06-30 16:10:28 -05:00 committed by Bas van Kervel
parent 7c26a13cb7
commit de3d8771cd
No known key found for this signature in database
GPG key ID: BFB23B252EF5812B
3 changed files with 83 additions and 20 deletions

View file

@ -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

View file

@ -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")

View file

@ -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)