aboutsummaryrefslogtreecommitdiffstats
path: root/accounts/abi/unpack.go
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/abi/unpack.go')
-rw-r--r--accounts/abi/unpack.go92
1 files changed, 64 insertions, 28 deletions
diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go
index 04716f7a2..8406b09c8 100644
--- a/accounts/abi/unpack.go
+++ b/accounts/abi/unpack.go
@@ -115,17 +115,6 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) {
}
-func getFullElemSize(elem *Type) int {
- //all other should be counted as 32 (slices have pointers to respective elements)
- size := 32
- //arrays wrap it, each element being the same size
- for elem.T == ArrayTy {
- size *= elem.Size
- elem = elem.Elem
- }
- return size
-}
-
// iteratively unpack elements
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
if size < 0 {
@@ -150,13 +139,9 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
// Arrays have packed elements, resulting in longer unpack steps.
// Slices have just 32 bytes per element (pointing to the contents).
- elemSize := 32
- if t.T == ArrayTy || t.T == SliceTy {
- elemSize = getFullElemSize(t.Elem)
- }
+ elemSize := getTypeSize(*t.Elem)
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
-
inter, err := toGoType(i, *t.Elem, output)
if err != nil {
return nil, err
@@ -170,6 +155,36 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
return refSlice.Interface(), nil
}
+func forTupleUnpack(t Type, output []byte) (interface{}, error) {
+ retval := reflect.New(t.Type).Elem()
+ virtualArgs := 0
+ for index, elem := range t.TupleElems {
+ marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output)
+ if elem.T == ArrayTy && !isDynamicType(*elem) {
+ // If we have a static array, like [3]uint256, these are coded as
+ // just like uint256,uint256,uint256.
+ // This means that we need to add two 'virtual' arguments when
+ // we count the index from now on.
+ //
+ // Array values nested multiple levels deep are also encoded inline:
+ // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
+ //
+ // Calculate the full array size to get the correct offset for the next argument.
+ // Decrement it by 1, as the normal index increment is still applied.
+ virtualArgs += getTypeSize(*elem)/32 - 1
+ } else if elem.T == TupleTy && !isDynamicType(*elem) {
+ // If we have a static tuple, like (uint256, bool, uint256), these are
+ // coded as just like uint256,bool,uint256
+ virtualArgs += getTypeSize(*elem)/32 - 1
+ }
+ if err != nil {
+ return nil, err
+ }
+ retval.Field(index).Set(reflect.ValueOf(marshalledValue))
+ }
+ return retval.Interface(), nil
+}
+
// 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) {
@@ -178,14 +193,14 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
}
var (
- returnOutput []byte
- begin, end int
- err error
+ returnOutput []byte
+ begin, length int
+ err error
)
// if we require a length prefix, find the beginning word and size returned.
if t.requiresLengthPrefix() {
- begin, end, err = lengthPrefixPointsTo(index, output)
+ begin, length, err = lengthPrefixPointsTo(index, output)
if err != nil {
return nil, err
}
@@ -194,19 +209,26 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
}
switch t.T {
- case SliceTy:
- if (*t.Elem).T == StringTy {
- return forEachUnpack(t, output[begin:], 0, end)
+ case TupleTy:
+ if isDynamicType(t) {
+ begin, err := tuplePointsTo(index, output)
+ if err != nil {
+ return nil, err
+ }
+ return forTupleUnpack(t, output[begin:])
+ } else {
+ return forTupleUnpack(t, output[index:])
}
- return forEachUnpack(t, output, begin, end)
+ case SliceTy:
+ return forEachUnpack(t, output[begin:], 0, length)
case ArrayTy:
- if (*t.Elem).T == StringTy {
+ if isDynamicType(*t.Elem) {
offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]))
return forEachUnpack(t, output[offset:], 0, t.Size)
}
- return forEachUnpack(t, output, index, t.Size)
+ return forEachUnpack(t, output[index:], 0, t.Size)
case StringTy: // variable arrays are written at the end of the return bytes
- return string(output[begin : begin+end]), nil
+ return string(output[begin : begin+length]), nil
case IntTy, UintTy:
return readInteger(t.T, t.Kind, returnOutput), nil
case BoolTy:
@@ -216,7 +238,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
case HashTy:
return common.BytesToHash(returnOutput), nil
case BytesTy:
- return output[begin : begin+end], nil
+ return output[begin : begin+length], nil
case FixedBytesTy:
return readFixedBytes(t, returnOutput)
case FunctionTy:
@@ -257,3 +279,17 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
length = int(lengthBig.Uint64())
return
}
+
+// tuplePointsTo resolves the location reference for dynamic tuple.
+func tuplePointsTo(index int, output []byte) (start int, err error) {
+ offset := big.NewInt(0).SetBytes(output[index : index+32])
+ outputLen := big.NewInt(int64(len(output)))
+
+ if offset.Cmp(big.NewInt(int64(len(output)))) > 0 {
+ return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen)
+ }
+ if offset.BitLen() > 63 {
+ return 0, fmt.Errorf("abi offset larger than int64: %v", offset)
+ }
+ return int(offset.Uint64()), nil
+}