Skip to content

Commit 92639b6

Browse files
vedhavyasgballet
authored andcommitted
Add packing for dynamic array and slice types (#18051)
* added tests for new abi encoding features (#4) * added tests from bytes32[][] and string[] * added offset to other types * formatting * Abi/dynamic types (#5) * Revert "Abi/dynamic types (#5)" (#6) This reverts commit dabca31. * Abi/dynamic types (#7) * some cleanup * Apply suggestions from code review apply suggestions Co-Authored-By: vedhavyas <[email protected]> * added better formatting (#8) * review chnages * better comments
1 parent f74077b commit 92639b6

File tree

3 files changed

+117
-22
lines changed

3 files changed

+117
-22
lines changed

accounts/abi/argument.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,7 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
243243
// input offset is the bytes offset for packed output
244244
inputOffset := 0
245245
for _, abiArg := range abiArgs {
246-
if abiArg.Type.T == ArrayTy {
247-
inputOffset += 32 * abiArg.Type.Size
248-
} else {
249-
inputOffset += 32
250-
}
246+
inputOffset += getDynamicTypeOffset(abiArg.Type)
251247
}
252248
var ret []byte
253249
for i, a := range args {
@@ -257,14 +253,13 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
257253
if err != nil {
258254
return nil, err
259255
}
260-
// check for a slice type (string, bytes, slice)
261-
if input.Type.requiresLengthPrefix() {
262-
// calculate the offset
263-
offset := inputOffset + len(variableInput)
256+
// check for dynamic types
257+
if isDynamicType(input.Type) {
264258
// set the offset
265-
ret = append(ret, packNum(reflect.ValueOf(offset))...)
266-
// Append the packed output to the variable input. The variable input
267-
// will be appended at the end of the input.
259+
ret = append(ret, packNum(reflect.ValueOf(inputOffset))...)
260+
// calculate next offset
261+
inputOffset += len(packed)
262+
// append to variable input
268263
variableInput = append(variableInput, packed...)
269264
} else {
270265
// append the packed value to the input

accounts/abi/pack_test.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,66 @@ func TestPack(t *testing.T) {
324324
"foobar",
325325
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
326326
},
327+
{
328+
"string[]",
329+
[]string{"hello", "foobar"},
330+
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
331+
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
332+
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
333+
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
334+
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
335+
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
336+
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
337+
},
338+
{
339+
"string[2]",
340+
[]string{"hello", "foobar"},
341+
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
342+
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
343+
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
344+
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
345+
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
346+
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
347+
},
348+
{
349+
"bytes32[][]",
350+
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
351+
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
352+
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
353+
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
354+
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
355+
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
356+
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
357+
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
358+
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
359+
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
360+
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
361+
},
362+
363+
{
364+
"bytes32[][2]",
365+
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
366+
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
367+
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
368+
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
369+
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
370+
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
371+
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
372+
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
373+
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
374+
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
375+
},
376+
377+
{
378+
"bytes32[3][2]",
379+
[][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
380+
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
381+
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
382+
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
383+
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
384+
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
385+
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
386+
},
327387
} {
328388
typ, err := NewType(test.typ)
329389
if err != nil {
@@ -336,7 +396,7 @@ func TestPack(t *testing.T) {
336396
}
337397

338398
if !bytes.Equal(output, test.output) {
339-
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
399+
t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output)
340400
}
341401
}
342402
}

accounts/abi/type.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,27 +183,67 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
183183
return nil, err
184184
}
185185

186-
if t.T == SliceTy || t.T == ArrayTy {
187-
var packed []byte
186+
switch t.T {
187+
case SliceTy, ArrayTy:
188+
var ret []byte
188189

190+
if t.requiresLengthPrefix() {
191+
// append length
192+
ret = append(ret, packNum(reflect.ValueOf(v.Len()))...)
193+
}
194+
195+
// calculate offset if any
196+
offset := 0
197+
offsetReq := isDynamicType(*t.Elem)
198+
if offsetReq {
199+
offset = getDynamicTypeOffset(*t.Elem) * v.Len()
200+
}
201+
var tail []byte
189202
for i := 0; i < v.Len(); i++ {
190203
val, err := t.Elem.pack(v.Index(i))
191204
if err != nil {
192205
return nil, err
193206
}
194-
packed = append(packed, val...)
195-
}
196-
if t.T == SliceTy {
197-
return packBytesSlice(packed, v.Len()), nil
198-
} else if t.T == ArrayTy {
199-
return packed, nil
207+
if !offsetReq {
208+
ret = append(ret, val...)
209+
continue
210+
}
211+
ret = append(ret, packNum(reflect.ValueOf(offset))...)
212+
offset += len(val)
213+
tail = append(tail, val...)
200214
}
215+
return append(ret, tail...), nil
216+
default:
217+
return packElement(t, v), nil
201218
}
202-
return packElement(t, v), nil
203219
}
204220

205221
// requireLengthPrefix returns whether the type requires any sort of length
206222
// prefixing.
207223
func (t Type) requiresLengthPrefix() bool {
208224
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
209225
}
226+
227+
// isDynamicType returns true if the type is dynamic.
228+
// StringTy, BytesTy, and SliceTy(irrespective of slice element type) are dynamic types
229+
// ArrayTy is considered dynamic if and only if the Array element is a dynamic type.
230+
// This function recursively checks the type for slice and array elements.
231+
func isDynamicType(t Type) bool {
232+
// dynamic types
233+
// array is also a dynamic type if the array type is dynamic
234+
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem))
235+
}
236+
237+
// getDynamicTypeOffset returns the offset for the type.
238+
// See `isDynamicType` to know which types are considered dynamic.
239+
// If the type t is an array and element type is not a dynamic type, then we consider it a static type and
240+
// return 32 * size of array since length prefix is not required.
241+
// If t is a dynamic type or element type(for slices and arrays) is dynamic, then we simply return 32 as offset.
242+
func getDynamicTypeOffset(t Type) int {
243+
// if it is an array and there are no dynamic types
244+
// then the array is static type
245+
if t.T == ArrayTy && !isDynamicType(*t.Elem) {
246+
return 32 * t.Size
247+
}
248+
return 32
249+
}

0 commit comments

Comments
 (0)