accounts/abi: tuple support (#18406)
This commit is contained in:
committed by
Guillaume Ballet
parent
6df3e4eeb0
commit
7ca40306af
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user