Skip to content

Commit 4c600ec

Browse files
committed
Add unpacking to string
1 parent 8b8146d commit 4c600ec

File tree

3 files changed

+207
-1
lines changed

3 files changed

+207
-1
lines changed

accounts/abi/argument.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,28 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte)
109109
return nil
110110
}
111111

112+
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value converted to string.
113+
func (arguments Arguments) UnpackIntoMapAsStrings(v map[string]interface{}, data []byte) error {
114+
// Make sure map is not nil
115+
if v == nil {
116+
return errors.New("abi: cannot unpack into a nil map")
117+
}
118+
if len(data) == 0 {
119+
if len(arguments.NonIndexed()) != 0 {
120+
return errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
121+
}
122+
return nil // Nothing to unmarshal, return
123+
}
124+
marshalledValues, err := arguments.UnpackValuesAsStrings(data)
125+
if err != nil {
126+
return err
127+
}
128+
for i, arg := range arguments.NonIndexed() {
129+
v[arg.Name] = marshalledValues[i]
130+
}
131+
return nil
132+
}
133+
112134
// Copy performs the operation go format -> provided struct.
113135
func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
114136
// make sure the passed value is arguments pointer
@@ -212,6 +234,40 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
212234
return retval, nil
213235
}
214236

237+
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
238+
// without supplying a struct to unpack into. Instead, this method returns a list containing the
239+
// values converted to strings. An atomic argument will be a list with one element.
240+
func (arguments Arguments) UnpackValuesAsStrings(data []byte) ([]interface{}, error) {
241+
nonIndexedArgs := arguments.NonIndexed()
242+
retval := make([]interface{}, 0, len(nonIndexedArgs))
243+
virtualArgs := 0
244+
for index, arg := range nonIndexedArgs {
245+
marshalledValue, err := toString((index+virtualArgs)*32, arg.Type, data)
246+
if err != nil {
247+
return nil, err
248+
}
249+
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
250+
// If we have a static array, like [3]uint256, these are coded as
251+
// just like uint256,uint256,uint256.
252+
// This means that we need to add two 'virtual' arguments when
253+
// we count the index from now on.
254+
//
255+
// Array values nested multiple levels deep are also encoded inline:
256+
// [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
257+
//
258+
// Calculate the full array size to get the correct offset for the next argument.
259+
// Decrement it by 1, as the normal index increment is still applied.
260+
virtualArgs += getTypeSize(arg.Type)/32 - 1
261+
} else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) {
262+
// If we have a static tuple, like (uint256, bool, uint256), these are
263+
// coded as just like uint256,bool,uint256
264+
virtualArgs += getTypeSize(arg.Type)/32 - 1
265+
}
266+
retval = append(retval, marshalledValue)
267+
}
268+
return retval, nil
269+
}
270+
215271
// PackValues performs the operation Go format -> Hexdata.
216272
// It is the semantic opposite of UnpackValues.
217273
func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) {

accounts/abi/unpack.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"math"
2424
"math/big"
2525
"reflect"
26+
"strconv"
2627

2728
"github.com/ethereum/go-ethereum/common"
2829
)
@@ -188,6 +189,46 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
188189
return refSlice.Interface(), nil
189190
}
190191

192+
// forEachUnpack iteratively unpack elements.
193+
func forEachUnpackAsString(t Type, output []byte, start, size int) (interface{}, error) {
194+
if size < 0 {
195+
return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size)
196+
}
197+
if start+32*size > len(output) {
198+
return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
199+
}
200+
201+
// this value will become our slice or our array, depending on the type
202+
var refSlice reflect.Value
203+
204+
if t.T == SliceTy {
205+
// declare our slice
206+
refSlice = reflect.MakeSlice(t.GetType(), size, size)
207+
} else if t.T == ArrayTy {
208+
// declare our array
209+
refSlice = reflect.New(t.GetType()).Elem()
210+
} else {
211+
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
212+
}
213+
214+
// Arrays have packed elements, resulting in longer unpack steps.
215+
// Slices have just 32 bytes per element (pointing to the contents).
216+
elemSize := getTypeSize(*t.Elem)
217+
218+
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
219+
inter, err := toString(i, *t.Elem, output)
220+
if err != nil {
221+
return nil, err
222+
}
223+
224+
// append the item to our reflect slice
225+
refSlice.Index(j).Set(reflect.ValueOf(inter))
226+
}
227+
228+
// return the interface
229+
return refSlice.Interface(), nil
230+
}
231+
191232
func forTupleUnpack(t Type, output []byte) (interface{}, error) {
192233
retval := reflect.New(t.GetType()).Elem()
193234
virtualArgs := 0
@@ -218,6 +259,36 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) {
218259
return retval.Interface(), nil
219260
}
220261

262+
func forTupleUnpackAsString(t Type, output []byte) (interface{}, error) {
263+
retval := reflect.New(t.GetType()).Elem()
264+
virtualArgs := 0
265+
for index, elem := range t.TupleElems {
266+
marshalledValue, err := toString((index+virtualArgs)*32, *elem, output)
267+
if err != nil {
268+
return nil, err
269+
}
270+
if elem.T == ArrayTy && !isDynamicType(*elem) {
271+
// If we have a static array, like [3]uint256, these are coded as
272+
// just like uint256,uint256,uint256.
273+
// This means that we need to add two 'virtual' arguments when
274+
// we count the index from now on.
275+
//
276+
// Array values nested multiple levels deep are also encoded inline:
277+
// [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
278+
//
279+
// Calculate the full array size to get the correct offset for the next argument.
280+
// Decrement it by 1, as the normal index increment is still applied.
281+
virtualArgs += getTypeSize(*elem)/32 - 1
282+
} else if elem.T == TupleTy && !isDynamicType(*elem) {
283+
// If we have a static tuple, like (uint256, bool, uint256), these are
284+
// coded as just like uint256,bool,uint256
285+
virtualArgs += getTypeSize(*elem)/32 - 1
286+
}
287+
retval.Field(index).Set(reflect.ValueOf(marshalledValue))
288+
}
289+
return retval.Interface(), nil
290+
}
291+
221292
// toGoType parses the output bytes and recursively assigns the value of these bytes
222293
// into a go type with accordance with the ABI spec.
223294
func toGoType(index int, t Type, output []byte) (interface{}, error) {
@@ -283,6 +354,85 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
283354
}
284355
}
285356

357+
// toString parses the output bytes and recursively assigns the value of these bytes into string.
358+
func toString(index int, t Type, output []byte) (interface{}, error) {
359+
if index+32 > len(output) {
360+
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32)
361+
}
362+
363+
var (
364+
returnOutput []byte
365+
begin, length int
366+
err error
367+
)
368+
369+
// if we require a length prefix, find the beginning word and size returned.
370+
if t.requiresLengthPrefix() {
371+
begin, length, err = lengthPrefixPointsTo(index, output)
372+
if err != nil {
373+
return nil, err
374+
}
375+
} else {
376+
returnOutput = output[index : index+32]
377+
}
378+
379+
switch t.T {
380+
case TupleTy:
381+
if isDynamicType(t) {
382+
begin, err := tuplePointsTo(index, output)
383+
if err != nil {
384+
return nil, err
385+
}
386+
return forTupleUnpackAsString(t, output[begin:])
387+
}
388+
return forTupleUnpackAsString(t, output[index:])
389+
case SliceTy:
390+
return forEachUnpackAsString(t, output[begin:], 0, length)
391+
case ArrayTy:
392+
if isDynamicType(*t.Elem) {
393+
offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:])
394+
if offset > uint64(len(output)) {
395+
return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output))
396+
}
397+
return forEachUnpackAsString(t, output[offset:], 0, t.Size)
398+
}
399+
return forEachUnpackAsString(t, output[index:], 0, t.Size)
400+
case StringTy: // variable arrays are written at the end of the return bytes
401+
return string(output[begin : begin+length]), nil
402+
case IntTy, UintTy:
403+
return ReadInteger(t, returnOutput)
404+
case BoolTy:
405+
var b bool
406+
b, err = readBool(returnOutput)
407+
if err != nil {
408+
return nil, fmt.Errorf("abi: cannot convert value as bool: %v", returnOutput)
409+
}
410+
return strconv.FormatBool(b), nil
411+
case AddressTy:
412+
return string(common.BytesToAddress(returnOutput).Bytes()), nil
413+
case HashTy:
414+
return string(common.BytesToHash(returnOutput).Bytes()), nil
415+
case BytesTy:
416+
return string(output[begin : begin+length]), nil
417+
case FixedBytesTy:
418+
var b interface{}
419+
b, err = ReadFixedBytes(t, returnOutput)
420+
if err != nil {
421+
return nil, fmt.Errorf("abi: cannot convert value as fixed bytes array: %v", returnOutput)
422+
}
423+
return string(b.([]byte)), nil
424+
case FunctionTy:
425+
var f interface{}
426+
f, err = ReadFixedBytes(t, returnOutput)
427+
if err != nil {
428+
return nil, fmt.Errorf("abi: cannot convert value as function: %v", returnOutput)
429+
}
430+
return string(f.([]byte)), nil
431+
default:
432+
return nil, fmt.Errorf("abi: unknown type %v", t.T)
433+
}
434+
}
435+
286436
// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type.
287437
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
288438
bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32])

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/zeriontech/go-ethereum
1+
module github.com/ethereum/go-ethereum
22

33
go 1.19
44

0 commit comments

Comments
 (0)