Skip to content

Commit 2046a74

Browse files
committed
accounts/abi: allow abi tags when unpacking structs (ethereum#16648)
1 parent ec076dd commit 2046a74

File tree

3 files changed

+197
-36
lines changed

3 files changed

+197
-36
lines changed

accounts/abi/argument.go

+20-24
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,14 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
123123
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
124124
return err
125125
}
126-
// If the output interface is a struct, make sure names don't collide
126+
127+
// If the interface is a struct, get of abi->struct_field mapping
128+
129+
var abi2struct map[string]string
127130
if kind == reflect.Struct {
128-
if err := requireUniqueStructFieldNames(arguments); err != nil {
131+
var err error
132+
abi2struct, err = mapAbiToStructFields(arguments, value)
133+
if err != nil {
129134
return err
130135
}
131136
}
@@ -135,9 +140,10 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
135140

136141
switch kind {
137142
case reflect.Struct:
138-
err := unpackStruct(value, reflectValue, arg)
139-
if err != nil {
140-
return err
143+
if structField, ok := abi2struct[arg.Name]; ok {
144+
if err := set(value.FieldByName(structField), reflectValue, arg); err != nil {
145+
return err
146+
}
141147
}
142148
case reflect.Slice, reflect.Array:
143149
if value.Len() < i {
@@ -163,17 +169,22 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf
163169
if len(marshalledValues) != 1 {
164170
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
165171
}
172+
166173
elem := reflect.ValueOf(v).Elem()
167174
kind := elem.Kind()
168175
reflectValue := reflect.ValueOf(marshalledValues[0])
169176

177+
var abi2struct map[string]string
170178
if kind == reflect.Struct {
171-
//make sure names don't collide
172-
if err := requireUniqueStructFieldNames(arguments); err != nil {
179+
var err error
180+
if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil {
173181
return err
174182
}
175-
176-
return unpackStruct(elem, reflectValue, arguments[0])
183+
arg := arguments.NonIndexed()[0]
184+
if structField, ok := abi2struct[arg.Name]; ok {
185+
return set(elem.FieldByName(structField), reflectValue, arg)
186+
}
187+
return nil
177188
}
178189

179190
return set(elem, reflectValue, arguments.NonIndexed()[0])
@@ -283,18 +294,3 @@ func ToCamelCase(input string) string {
283294
}
284295
return strings.Join(parts, "")
285296
}
286-
287-
// unpackStruct extracts each argument into its corresponding struct field
288-
func unpackStruct(value, reflectValue reflect.Value, arg Argument) error {
289-
name := ToCamelCase(arg.Name)
290-
typ := value.Type()
291-
for j := 0; j < typ.NumField(); j++ {
292-
// TODO read tags: `abi:"fieldName"`
293-
if typ.Field(j).Name == name {
294-
if err := set(value.Field(j), reflectValue, arg); err != nil {
295-
return err
296-
}
297-
}
298-
}
299-
return nil
300-
}

accounts/abi/event_test.go

+80-1
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,28 @@ var jsonEventPledge = []byte(`{
5858
"type": "event"
5959
}`)
6060

61+
var jsonEventMixedCase = []byte(`{
62+
"anonymous": false,
63+
"inputs": [{
64+
"indexed": false, "name": "value", "type": "uint256"
65+
}, {
66+
"indexed": false, "name": "_value", "type": "uint256"
67+
}, {
68+
"indexed": false, "name": "Value", "type": "uint256"
69+
}],
70+
"name": "MixedCase",
71+
"type": "event"
72+
}`)
73+
6174
// 1000000
6275
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
6376

6477
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
6578
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
6679

80+
// 1000000,2218516807680,1000001
81+
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
82+
6783
func TestEventId(t *testing.T) {
6884
var table = []struct {
6985
definition string
@@ -154,6 +170,27 @@ func TestEventTupleUnpack(t *testing.T) {
154170
Value *big.Int
155171
}
156172

173+
type EventTransferWithTag struct {
174+
// this is valid because `value` is not exportable,
175+
// so value is only unmarshalled into `Value1`.
176+
value *big.Int
177+
Value1 *big.Int `abi:"value"`
178+
}
179+
180+
type BadEventTransferWithSameFieldAndTag struct {
181+
Value *big.Int
182+
Value1 *big.Int `abi:"value"`
183+
}
184+
185+
type BadEventTransferWithDuplicatedTag struct {
186+
Value1 *big.Int `abi:"value"`
187+
Value2 *big.Int `abi:"value"`
188+
}
189+
190+
type BadEventTransferWithEmptyTag struct {
191+
Value *big.Int `abi:""`
192+
}
193+
157194
type EventPledge struct {
158195
Who common.Address
159196
Wad *big.Int
@@ -166,9 +203,16 @@ func TestEventTupleUnpack(t *testing.T) {
166203
Currency [3]byte
167204
}
168205

206+
type EventMixedCase struct {
207+
Value1 *big.Int `abi:"value"`
208+
Value2 *big.Int `abi:"_value"`
209+
Value3 *big.Int `abi:"Value"`
210+
}
211+
169212
bigint := new(big.Int)
170213
bigintExpected := big.NewInt(1000000)
171214
bigintExpected2 := big.NewInt(2218516807680)
215+
bigintExpected3 := big.NewInt(1000001)
172216
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
173217
var testCases = []struct {
174218
data string
@@ -191,6 +235,34 @@ func TestEventTupleUnpack(t *testing.T) {
191235
jsonEventTransfer,
192236
"",
193237
"Can unpack ERC20 Transfer event into slice",
238+
}, {
239+
transferData1,
240+
&EventTransferWithTag{},
241+
&EventTransferWithTag{Value1: bigintExpected},
242+
jsonEventTransfer,
243+
"",
244+
"Can unpack ERC20 Transfer event into structure with abi: tag",
245+
}, {
246+
transferData1,
247+
&BadEventTransferWithDuplicatedTag{},
248+
&BadEventTransferWithDuplicatedTag{},
249+
jsonEventTransfer,
250+
"struct: abi tag in 'Value2' already mapped",
251+
"Can not unpack ERC20 Transfer event with duplicated abi tag",
252+
}, {
253+
transferData1,
254+
&BadEventTransferWithSameFieldAndTag{},
255+
&BadEventTransferWithSameFieldAndTag{},
256+
jsonEventTransfer,
257+
"abi: multiple variables maps to the same abi field 'value'",
258+
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
259+
}, {
260+
transferData1,
261+
&BadEventTransferWithEmptyTag{},
262+
&BadEventTransferWithEmptyTag{},
263+
jsonEventTransfer,
264+
"struct: abi tag in 'Value' is empty",
265+
"Can not unpack ERC20 Transfer event with an empty tag",
194266
}, {
195267
pledgeData1,
196268
&EventPledge{},
@@ -249,6 +321,13 @@ func TestEventTupleUnpack(t *testing.T) {
249321
jsonEventPledge,
250322
"abi: cannot unmarshal tuple into map[string]interface {}",
251323
"Can not unpack Pledge event into map",
324+
}, {
325+
mixedCaseData1,
326+
&EventMixedCase{},
327+
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
328+
jsonEventMixedCase,
329+
"",
330+
"Can unpack abi variables with mixed case",
252331
}}
253332

254333
for _, tc := range testCases {
@@ -260,7 +339,7 @@ func TestEventTupleUnpack(t *testing.T) {
260339
assert.Nil(err, "Should be able to unpack event data.")
261340
assert.Equal(tc.expected, tc.dest, tc.name)
262341
} else {
263-
assert.EqualError(err, tc.error)
342+
assert.EqualError(err, tc.error, tc.name)
264343
}
265344
})
266345
}

accounts/abi/reflect.go

+97-11
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
package abi
1818

1919
import (
20-
"errors"
2120
"fmt"
2221
"reflect"
22+
"strings"
2323
)
2424

2525
// indirect recursively dereferences the value until it either gets the value
@@ -127,18 +127,104 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
127127
return nil
128128
}
129129

130-
// requireUniqueStructFieldNames makes sure field names don't collide
131-
func requireUniqueStructFieldNames(args Arguments) error {
132-
exists := make(map[string]bool)
130+
// mapAbiToStringField maps abi to struct fields.
131+
// first round: for each Exportable field that contains a `abi:""` tag
132+
//
133+
// and this field name exists in the arguments, pair them together.
134+
//
135+
// second round: for each argument field that has not been already linked,
136+
//
137+
// find what variable is expected to be mapped into, if it exists and has not been
138+
// used, pair them.
139+
func mapAbiToStructFields(args Arguments, value reflect.Value) (map[string]string, error) {
140+
141+
typ := value.Type()
142+
143+
abi2struct := make(map[string]string)
144+
struct2abi := make(map[string]string)
145+
146+
// first round ~~~
147+
for i := 0; i < typ.NumField(); i++ {
148+
structFieldName := typ.Field(i).Name
149+
150+
// skip private struct fields.
151+
if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) {
152+
continue
153+
}
154+
155+
// skip fields that have no abi:"" tag.
156+
var ok bool
157+
var tagName string
158+
if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
159+
continue
160+
}
161+
162+
// check if tag is empty.
163+
if tagName == "" {
164+
return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName)
165+
}
166+
167+
// check which argument field matches with the abi tag.
168+
found := false
169+
for _, abiField := range args.NonIndexed() {
170+
if abiField.Name == tagName {
171+
if abi2struct[abiField.Name] != "" {
172+
return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
173+
}
174+
// pair them
175+
abi2struct[abiField.Name] = structFieldName
176+
struct2abi[structFieldName] = abiField.Name
177+
found = true
178+
}
179+
}
180+
181+
// check if this tag has been mapped.
182+
if !found {
183+
return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName)
184+
}
185+
186+
}
187+
188+
// second round ~~~
133189
for _, arg := range args {
134-
field := ToCamelCase(arg.Name)
135-
if field == "" {
136-
return errors.New("abi: purely underscored output cannot unpack to struct")
190+
191+
abiFieldName := arg.Name
192+
structFieldName := ToCamelCase(abiFieldName)
193+
194+
if structFieldName == "" {
195+
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
196+
}
197+
198+
// this abi has already been paired, skip it... unless there exists another, yet unassigned
199+
// struct field with the same field name. If so, raise an error:
200+
// abi: [ { "name": "value" } ]
201+
// struct { Value *big.Int , Value1 *big.Int `abi:"value"`}
202+
if abi2struct[abiFieldName] != "" {
203+
if abi2struct[abiFieldName] != structFieldName &&
204+
struct2abi[structFieldName] == "" &&
205+
value.FieldByName(structFieldName).IsValid() {
206+
return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", abiFieldName)
207+
}
208+
continue
209+
}
210+
211+
// return an error if this struct field has already been paired.
212+
if struct2abi[structFieldName] != "" {
213+
return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName)
137214
}
138-
if exists[field] {
139-
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
215+
216+
if value.FieldByName(structFieldName).IsValid() {
217+
// pair them
218+
abi2struct[abiFieldName] = structFieldName
219+
struct2abi[structFieldName] = abiFieldName
220+
} else {
221+
// not paired, but annotate as used, to detect cases like
222+
// abi : [ { "name": "value" }, { "name": "_value" } ]
223+
// struct { Value *big.Int }
224+
struct2abi[structFieldName] = abiFieldName
140225
}
141-
exists[field] = true
226+
142227
}
143-
return nil
228+
229+
return abi2struct, nil
144230
}

0 commit comments

Comments
 (0)