Skip to content

Commit f5ed81c

Browse files
gballetfirmianavan
authored andcommitted
accounts/abi: allow abi: tags when unpacking structs
Go code users can now tag event struct members with `abi:` to specify in what fields the event will be de-serialized. See PR ethereum#16648 for details.
1 parent 7ce4b59 commit f5ed81c

File tree

3 files changed

+194
-35
lines changed

3 files changed

+194
-35
lines changed

accounts/abi/argument.go

+20-24
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,14 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
111111
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
112112
return err
113113
}
114-
// If the output interface is a struct, make sure names don't collide
114+
115+
// If the interface is a struct, get of abi->struct_field mapping
116+
117+
var abi2struct map[string]string
115118
if kind == reflect.Struct {
116-
if err := requireUniqueStructFieldNames(arguments); err != nil {
119+
var err error
120+
abi2struct, err = mapAbiToStructFields(arguments, value)
121+
if err != nil {
117122
return err
118123
}
119124
}
@@ -123,9 +128,10 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
123128

124129
switch kind {
125130
case reflect.Struct:
126-
err := unpackStruct(value, reflectValue, arg)
127-
if err != nil {
128-
return err
131+
if structField, ok := abi2struct[arg.Name]; ok {
132+
if err := set(value.FieldByName(structField), reflectValue, arg); err != nil {
133+
return err
134+
}
129135
}
130136
case reflect.Slice, reflect.Array:
131137
if value.Len() < i {
@@ -151,17 +157,22 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf
151157
if len(marshalledValues) != 1 {
152158
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
153159
}
160+
154161
elem := reflect.ValueOf(v).Elem()
155162
kind := elem.Kind()
156163
reflectValue := reflect.ValueOf(marshalledValues[0])
157164

165+
var abi2struct map[string]string
158166
if kind == reflect.Struct {
159-
//make sure names don't collide
160-
if err := requireUniqueStructFieldNames(arguments); err != nil {
167+
var err error
168+
if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil {
161169
return err
162170
}
163-
164-
return unpackStruct(elem, reflectValue, arguments[0])
171+
arg := arguments.NonIndexed()[0]
172+
if structField, ok := abi2struct[arg.Name]; ok {
173+
return set(elem.FieldByName(structField), reflectValue, arg)
174+
}
175+
return nil
165176
}
166177

167178
return set(elem, reflectValue, arguments.NonIndexed()[0])
@@ -277,18 +288,3 @@ func capitalise(input string) string {
277288
}
278289
return strings.ToUpper(input[:1]) + input[1:]
279290
}
280-
281-
//unpackStruct extracts each argument into its corresponding struct field
282-
func unpackStruct(value, reflectValue reflect.Value, arg Argument) error {
283-
name := capitalise(arg.Name)
284-
typ := value.Type()
285-
for j := 0; j < typ.NumField(); j++ {
286-
// TODO read tags: `abi:"fieldName"`
287-
if typ.Field(j).Name == name {
288-
if err := set(value.Field(j), reflectValue, arg); err != nil {
289-
return err
290-
}
291-
}
292-
}
293-
return nil
294-
}

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
@@ -121,6 +137,27 @@ func TestEventTupleUnpack(t *testing.T) {
121137
Value *big.Int
122138
}
123139

140+
type EventTransferWithTag struct {
141+
// this is valid because `value` is not exportable,
142+
// so value is only unmarshalled into `Value1`.
143+
value *big.Int
144+
Value1 *big.Int `abi:"value"`
145+
}
146+
147+
type BadEventTransferWithSameFieldAndTag struct {
148+
Value *big.Int
149+
Value1 *big.Int `abi:"value"`
150+
}
151+
152+
type BadEventTransferWithDuplicatedTag struct {
153+
Value1 *big.Int `abi:"value"`
154+
Value2 *big.Int `abi:"value"`
155+
}
156+
157+
type BadEventTransferWithEmptyTag struct {
158+
Value *big.Int `abi:""`
159+
}
160+
124161
type EventPledge struct {
125162
Who common.Address
126163
Wad *big.Int
@@ -133,9 +170,16 @@ func TestEventTupleUnpack(t *testing.T) {
133170
Currency [3]byte
134171
}
135172

173+
type EventMixedCase struct {
174+
Value1 *big.Int `abi:"value"`
175+
Value2 *big.Int `abi:"_value"`
176+
Value3 *big.Int `abi:"Value"`
177+
}
178+
136179
bigint := new(big.Int)
137180
bigintExpected := big.NewInt(1000000)
138181
bigintExpected2 := big.NewInt(2218516807680)
182+
bigintExpected3 := big.NewInt(1000001)
139183
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
140184
var testCases = []struct {
141185
data string
@@ -158,6 +202,34 @@ func TestEventTupleUnpack(t *testing.T) {
158202
jsonEventTransfer,
159203
"",
160204
"Can unpack ERC20 Transfer event into slice",
205+
}, {
206+
transferData1,
207+
&EventTransferWithTag{},
208+
&EventTransferWithTag{Value1: bigintExpected},
209+
jsonEventTransfer,
210+
"",
211+
"Can unpack ERC20 Transfer event into structure with abi: tag",
212+
}, {
213+
transferData1,
214+
&BadEventTransferWithDuplicatedTag{},
215+
&BadEventTransferWithDuplicatedTag{},
216+
jsonEventTransfer,
217+
"struct: abi tag in 'Value2' already mapped",
218+
"Can not unpack ERC20 Transfer event with duplicated abi tag",
219+
}, {
220+
transferData1,
221+
&BadEventTransferWithSameFieldAndTag{},
222+
&BadEventTransferWithSameFieldAndTag{},
223+
jsonEventTransfer,
224+
"abi: multiple variables maps to the same abi field 'value'",
225+
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
226+
}, {
227+
transferData1,
228+
&BadEventTransferWithEmptyTag{},
229+
&BadEventTransferWithEmptyTag{},
230+
jsonEventTransfer,
231+
"struct: abi tag in 'Value' is empty",
232+
"Can not unpack ERC20 Transfer event with an empty tag",
161233
}, {
162234
pledgeData1,
163235
&EventPledge{},
@@ -216,6 +288,13 @@ func TestEventTupleUnpack(t *testing.T) {
216288
jsonEventPledge,
217289
"abi: cannot unmarshal tuple into map[string]interface {}",
218290
"Can not unpack Pledge event into map",
291+
}, {
292+
mixedCaseData1,
293+
&EventMixedCase{},
294+
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
295+
jsonEventMixedCase,
296+
"",
297+
"Can unpack abi variables with mixed case",
219298
}}
220299

221300
for _, tc := range testCases {
@@ -227,7 +306,7 @@ func TestEventTupleUnpack(t *testing.T) {
227306
assert.Nil(err, "Should be able to unpack event data.")
228307
assert.Equal(tc.expected, tc.dest, tc.name)
229308
} else {
230-
assert.EqualError(err, tc.error)
309+
assert.EqualError(err, tc.error, tc.name)
231310
}
232311
})
233312
}

accounts/abi/reflect.go

+94-10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package abi
1919
import (
2020
"fmt"
2121
"reflect"
22+
"strings"
2223
)
2324

2425
// indirect recursively dereferences the value until it either gets the value
@@ -111,18 +112,101 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
111112
return nil
112113
}
113114

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

0 commit comments

Comments
 (0)