Skip to content

Commit b45ee5e

Browse files
rjl493456442enriquefynn
authored andcommitted
accounts/abi: add internalType information and fix issues (ethereum#20179)
* accounts/abi: fix various issues The fixed issues include: (1) If there is no return in a call function, unpack should return nil error (2) For some functions which have struct array as parameter, it will also be detected and generate the struct definition (3) For event, if it has non-indexed parameter, the parameter name will also be assigned if empty. Also the internal struct will be detected and generate struct defition if not exist. (4) Fix annotation generation in event function * accounts/abi: add new abi field internalType * accounts: address comments and add tests * accounts/abi: replace strings.ReplaceAll with strings.Replace
1 parent 1033dfd commit b45ee5e

File tree

12 files changed

+175
-63
lines changed

12 files changed

+175
-63
lines changed

accounts/abi/abi.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
7575

7676
// Unpack output in v according to the abi specification
7777
func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
78-
if len(data) == 0 {
79-
return fmt.Errorf("abi: unmarshalling empty output")
80-
}
8178
// since there can't be naming collisions with contracts and events,
8279
// we need to decide whether we're calling a method or an event
8380
if method, ok := abi.Methods[name]; ok {
@@ -94,9 +91,6 @@ func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
9491

9592
// UnpackIntoMap unpacks a log into the provided map[string]interface{}
9693
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
97-
if len(data) == 0 {
98-
return fmt.Errorf("abi: unmarshalling empty output")
99-
}
10094
// since there can't be naming collisions with contracts and events,
10195
// we need to decide whether we're calling a method or an event
10296
if method, ok := abi.Methods[name]; ok {

accounts/abi/abi_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const jsondata2 = `
5757
]`
5858

5959
func TestReader(t *testing.T) {
60-
Uint256, _ := NewType("uint256", nil)
60+
Uint256, _ := NewType("uint256", "", nil)
6161
exp := ABI{
6262
Methods: map[string]Method{
6363
"balance": {
@@ -172,7 +172,7 @@ func TestTestSlice(t *testing.T) {
172172
}
173173

174174
func TestMethodSignature(t *testing.T) {
175-
String, _ := NewType("string", nil)
175+
String, _ := NewType("string", "", nil)
176176
m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
177177
exp := "foo(string,string)"
178178
if m.Sig() != exp {
@@ -184,15 +184,15 @@ func TestMethodSignature(t *testing.T) {
184184
t.Errorf("expected ids to match %x != %x", m.ID(), idexp)
185185
}
186186

187-
uintt, _ := NewType("uint256", nil)
187+
uintt, _ := NewType("uint256", "", nil)
188188
m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil}
189189
exp = "foo(uint256)"
190190
if m.Sig() != exp {
191191
t.Error("signature mismatch", exp, "!=", m.Sig())
192192
}
193193

194194
// Method with tuple arguments
195-
s, _ := NewType("tuple", []ArgumentMarshaling{
195+
s, _ := NewType("tuple", "", []ArgumentMarshaling{
196196
{Name: "a", Type: "int256"},
197197
{Name: "b", Type: "int256[]"},
198198
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
@@ -602,9 +602,9 @@ func TestBareEvents(t *testing.T) {
602602
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
603603
]`
604604

605-
arg0, _ := NewType("uint256", nil)
606-
arg1, _ := NewType("address", nil)
607-
tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
605+
arg0, _ := NewType("uint256", "", nil)
606+
arg1, _ := NewType("address", "", nil)
607+
tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
608608

609609
expectedEvents := map[string]struct {
610610
Anonymous bool

accounts/abi/argument.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ type Argument struct {
3434
type Arguments []Argument
3535

3636
type ArgumentMarshaling struct {
37-
Name string
38-
Type string
39-
Components []ArgumentMarshaling
40-
Indexed bool
37+
Name string
38+
Type string
39+
InternalType string
40+
Components []ArgumentMarshaling
41+
Indexed bool
4142
}
4243

4344
// UnmarshalJSON implements json.Unmarshaler interface
@@ -48,7 +49,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
4849
return fmt.Errorf("argument json err: %v", err)
4950
}
5051

51-
argument.Type, err = NewType(arg.Type, arg.Components)
52+
argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components)
5253
if err != nil {
5354
return err
5455
}
@@ -88,6 +89,13 @@ func (arguments Arguments) isTuple() bool {
8889

8990
// Unpack performs the operation hexdata -> Go format
9091
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
92+
if len(data) == 0 {
93+
if len(arguments) != 0 {
94+
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
95+
} else {
96+
return nil // Nothing to unmarshal, return
97+
}
98+
}
9199
// make sure the passed value is arguments pointer
92100
if reflect.Ptr != reflect.ValueOf(v).Kind() {
93101
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
@@ -104,11 +112,17 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
104112

105113
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
106114
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
115+
if len(data) == 0 {
116+
if len(arguments) != 0 {
117+
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
118+
} else {
119+
return nil // Nothing to unmarshal, return
120+
}
121+
}
107122
marshalledValues, err := arguments.UnpackValues(data)
108123
if err != nil {
109124
return err
110125
}
111-
112126
return arguments.unpackIntoMap(v, marshalledValues)
113127
}
114128

accounts/abi/bind/bind.go

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
8686
if input.Name == "" {
8787
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
8888
}
89-
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist {
89+
if hasStruct(input.Type) {
9090
bindStructType[lang](input.Type, structs)
9191
}
9292
}
@@ -96,7 +96,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
9696
if output.Name != "" {
9797
normalized.Outputs[j].Name = capitalise(output.Name)
9898
}
99-
if _, exist := structs[output.Type.String()]; output.Type.T == abi.TupleTy && !exist {
99+
if hasStruct(output.Type) {
100100
bindStructType[lang](output.Type, structs)
101101
}
102102
}
@@ -119,14 +119,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
119119
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
120120
copy(normalized.Inputs, original.Inputs)
121121
for j, input := range normalized.Inputs {
122-
// Indexed fields are input, non-indexed ones are outputs
123-
if input.Indexed {
124-
if input.Name == "" {
125-
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
126-
}
127-
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist {
128-
bindStructType[lang](input.Type, structs)
129-
}
122+
if input.Name == "" {
123+
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
124+
}
125+
if hasStruct(input.Type) {
126+
bindStructType[lang](input.Type, structs)
130127
}
131128
}
132129
// Append the event to the accumulator list
@@ -244,7 +241,7 @@ func bindBasicTypeGo(kind abi.Type) string {
244241
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
245242
switch kind.T {
246243
case abi.TupleTy:
247-
return structs[kind.String()].Name
244+
return structs[kind.TupleRawName+kind.String()].Name
248245
case abi.ArrayTy:
249246
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
250247
case abi.SliceTy:
@@ -321,7 +318,7 @@ func pluralizeJavaType(typ string) string {
321318
func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
322319
switch kind.T {
323320
case abi.TupleTy:
324-
return structs[kind.String()].Name
321+
return structs[kind.TupleRawName+kind.String()].Name
325322
case abi.ArrayTy, abi.SliceTy:
326323
return pluralizeJavaType(bindTypeJava(*kind.Elem, structs))
327324
default:
@@ -340,6 +337,13 @@ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct)
340337
// funcionality as for simple types, but dynamic types get converted to hashes.
341338
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
342339
bound := bindTypeGo(kind, structs)
340+
341+
// todo(rjl493456442) according solidity documentation, indexed event
342+
// parameters that are not value types i.e. arrays and structs are not
343+
// stored directly but instead a keccak256-hash of an encoding is stored.
344+
//
345+
// We only convert stringS and bytes to hash, still need to deal with
346+
// array(both fixed-size and dynamic-size) and struct.
343347
if bound == "string" || bound == "[]byte" {
344348
bound = "common.Hash"
345349
}
@@ -350,6 +354,13 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
350354
// funcionality as for simple types, but dynamic types get converted to hashes.
351355
func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
352356
bound := bindTypeJava(kind, structs)
357+
358+
// todo(rjl493456442) according solidity documentation, indexed event
359+
// parameters that are not value types i.e. arrays and structs are not
360+
// stored directly but instead a keccak256-hash of an encoding is stored.
361+
//
362+
// We only convert stringS and bytes to hash, still need to deal with
363+
// array(both fixed-size and dynamic-size) and struct.
353364
if bound == "String" || bound == "byte[]" {
354365
bound = "Hash"
355366
}
@@ -369,16 +380,26 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct
369380
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
370381
switch kind.T {
371382
case abi.TupleTy:
372-
if s, exist := structs[kind.String()]; exist {
383+
// We compose raw struct name and canonical parameter expression
384+
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
385+
// is empty, so we use canonical parameter expression to distinguish
386+
// different struct definition. From the consideration of backward
387+
// compatibility, we concat these two together so that if kind.TupleRawName
388+
// is not empty, it can have unique id.
389+
id := kind.TupleRawName + kind.String()
390+
if s, exist := structs[id]; exist {
373391
return s.Name
374392
}
375393
var fields []*tmplField
376394
for i, elem := range kind.TupleElems {
377395
field := bindStructTypeGo(*elem, structs)
378396
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
379397
}
380-
name := fmt.Sprintf("Struct%d", len(structs))
381-
structs[kind.String()] = &tmplStruct{
398+
name := kind.TupleRawName
399+
if name == "" {
400+
name = fmt.Sprintf("Struct%d", len(structs))
401+
}
402+
structs[id] = &tmplStruct{
382403
Name: name,
383404
Fields: fields,
384405
}
@@ -398,16 +419,26 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
398419
func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
399420
switch kind.T {
400421
case abi.TupleTy:
401-
if s, exist := structs[kind.String()]; exist {
422+
// We compose raw struct name and canonical parameter expression
423+
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
424+
// is empty, so we use canonical parameter expression to distinguish
425+
// different struct definition. From the consideration of backward
426+
// compatibility, we concat these two together so that if kind.TupleRawName
427+
// is not empty, it can have unique id.
428+
id := kind.TupleRawName + kind.String()
429+
if s, exist := structs[id]; exist {
402430
return s.Name
403431
}
404432
var fields []*tmplField
405433
for i, elem := range kind.TupleElems {
406434
field := bindStructTypeJava(*elem, structs)
407435
fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem})
408436
}
409-
name := fmt.Sprintf("Class%d", len(structs))
410-
structs[kind.String()] = &tmplStruct{
437+
name := kind.TupleRawName
438+
if name == "" {
439+
name = fmt.Sprintf("Class%d", len(structs))
440+
}
441+
structs[id] = &tmplStruct{
411442
Name: name,
412443
Fields: fields,
413444
}
@@ -497,6 +528,21 @@ func structured(args abi.Arguments) bool {
497528
return true
498529
}
499530

531+
// hasStruct returns an indicator whether the given type is struct, struct slice
532+
// or struct array.
533+
func hasStruct(t abi.Type) bool {
534+
switch t.T {
535+
case abi.SliceTy:
536+
return hasStruct(*t.Elem)
537+
case abi.ArrayTy:
538+
return hasStruct(*t.Elem)
539+
case abi.TupleTy:
540+
return true
541+
default:
542+
return false
543+
}
544+
}
545+
500546
// resolveArgName converts a raw argument representation into a user friendly format.
501547
func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string {
502548
var (
@@ -512,7 +558,7 @@ loop:
512558
case abi.ArrayTy:
513559
prefix += fmt.Sprintf("[%d]", typ.Size)
514560
default:
515-
embedded = typ.String()
561+
embedded = typ.TupleRawName + typ.String()
516562
break loop
517563
}
518564
typ = typ.Elem

accounts/abi/bind/bind_test.go

Lines changed: 16 additions & 8 deletions
Large diffs are not rendered by default.

accounts/abi/bind/template.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ type tmplField struct {
6565
// tmplStruct is a wrapper around an abi.tuple contains a auto-generated
6666
// struct name.
6767
type tmplStruct struct {
68-
Name string // Auto-generated struct name(We can't obtain the raw struct name through abi)
68+
Name string // Auto-generated struct name(before solidity v0.5.11) or raw name.
6969
Fields []*tmplField // Struct fields definition depends on the binding language.
7070
}
7171

@@ -483,7 +483,7 @@ var (
483483
484484
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
485485
//
486-
// Solidity: {{.Original.String}}
486+
// Solidity: {{formatevent .Original $structs}}
487487
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
488488
event := new({{$contract.Type}}{{.Normalized.Name}})
489489
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {

accounts/abi/bind/topics.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,19 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
8080
copy(topic[:], hash[:])
8181

8282
default:
83+
// todo(rjl493456442) according solidity documentation, indexed event
84+
// parameters that are not value types i.e. arrays and structs are not
85+
// stored directly but instead a keccak256-hash of an encoding is stored.
86+
//
87+
// We only convert stringS and bytes to hash, still need to deal with
88+
// array(both fixed-size and dynamic-size) and struct.
89+
8390
// Attempt to generate the topic from funky types
8491
val := reflect.ValueOf(rule)
85-
8692
switch {
87-
8893
// static byte array
8994
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
9095
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
91-
9296
default:
9397
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
9498
}
@@ -162,6 +166,7 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
162166

163167
default:
164168
// Ran out of plain primitive types, try custom types
169+
165170
switch field.Type() {
166171
case reflectHash: // Also covers all dynamic types
167172
field.Set(reflect.ValueOf(topics[0]))
@@ -178,11 +183,9 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
178183
default:
179184
// Ran out of custom types, try the crazies
180185
switch {
181-
182186
// static byte array
183187
case arg.Type.T == abi.FixedBytesTy:
184188
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
185-
186189
default:
187190
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
188191
}

accounts/abi/bind/topics_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestParseTopics(t *testing.T) {
5959
type bytesStruct struct {
6060
StaticBytes [5]byte
6161
}
62-
bytesType, _ := abi.NewType("bytes5", nil)
62+
bytesType, _ := abi.NewType("bytes5", "", nil)
6363
type args struct {
6464
createObj func() interface{}
6565
resultObj func() interface{}

accounts/abi/pack_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ func TestPack(t *testing.T) {
613613
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
614614
},
615615
} {
616-
typ, err := NewType(test.typ, test.components)
616+
typ, err := NewType(test.typ, "", test.components)
617617
if err != nil {
618618
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
619619
}

0 commit comments

Comments
 (0)