Skip to content

Commit 4f8e247

Browse files
committed
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 c1e9efe commit 4f8e247

File tree

11 files changed

+139
-52
lines changed

11 files changed

+139
-52
lines changed

accounts/abi/abi.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
7777

7878
// Unpack output in v according to the abi specification
7979
func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
80-
if len(data) == 0 {
81-
return errors.New("abi: unmarshalling empty output")
82-
}
8380
// since there can't be naming collisions with contracts and events,
8481
// we need to decide whether we're calling a method or an event
8582
if method, ok := abi.Methods[name]; ok {
@@ -96,9 +93,6 @@ func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
9693

9794
// UnpackIntoMap unpacks a log into the provided map[string]interface{}
9895
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
99-
if len(data) == 0 {
100-
return fmt.Errorf("abi: unmarshalling empty output")
101-
}
10296
// since there can't be naming collisions with contracts and events,
10397
// we need to decide whether we're calling a method or an event
10498
if method, ok := abi.Methods[name]; ok {
@@ -207,7 +201,7 @@ func UnpackRevert(data []byte) (string, error) {
207201
if !bytes.Equal(data[:4], revertSelector) {
208202
return "", errors.New("invalid data for unpacking")
209203
}
210-
typ, _ := NewType("string", nil)
204+
typ, _ := NewType("string", "", nil)
211205
unpacked, err := (Arguments{{Type: typ}}).Unpack2(data[4:])
212206
if err != nil {
213207
return "", err

accounts/abi/abi_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const jsondata2 = `
6060
]`
6161

6262
func TestReader(t *testing.T) {
63-
Uint256, _ := NewType("uint256", nil)
63+
Uint256, _ := NewType("uint256", "", nil)
6464
exp := ABI{
6565
Methods: map[string]Method{
6666
"balance": {
@@ -175,7 +175,7 @@ func TestTestSlice(t *testing.T) {
175175
}
176176

177177
func TestMethodSignature(t *testing.T) {
178-
String, _ := NewType("string", nil)
178+
String, _ := NewType("string", "", nil)
179179
m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
180180
exp := "foo(string,string)"
181181
if m.Sig() != exp {
@@ -187,15 +187,15 @@ func TestMethodSignature(t *testing.T) {
187187
t.Errorf("expected ids to match %x != %x", m.ID(), idexp)
188188
}
189189

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

197197
// Method with tuple arguments
198-
s, _ := NewType("tuple", []ArgumentMarshaling{
198+
s, _ := NewType("tuple", "", []ArgumentMarshaling{
199199
{Name: "a", Type: "int256"},
200200
{Name: "b", Type: "int256[]"},
201201
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
@@ -606,9 +606,9 @@ func TestBareEvents(t *testing.T) {
606606
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
607607
]`
608608

609-
arg0, _ := NewType("uint256", nil)
610-
arg1, _ := NewType("address", nil)
611-
tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
609+
arg0, _ := NewType("uint256", "", nil)
610+
arg1, _ := NewType("address", "", nil)
611+
tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
612612

613613
expectedEvents := map[string]struct {
614614
Anonymous bool

accounts/abi/argument.go

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

3737
type ArgumentMarshaling struct {
38-
Name string
39-
Type string
40-
Components []ArgumentMarshaling
41-
Indexed bool
38+
Name string
39+
Type string
40+
InternalType string
41+
Components []ArgumentMarshaling
42+
Indexed bool
4243
}
4344

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

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

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

117125
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
118126
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
127+
if len(data) == 0 {
128+
if len(arguments) != 0 {
129+
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
130+
} else {
131+
return nil // Nothing to unmarshal, return
132+
}
133+
}
119134
marshalledValues, err := arguments.UnpackValues(data)
120135
if err != nil {
121136
return err
122137
}
123-
124138
return arguments.unpackIntoMap(v, marshalledValues)
125139
}
126140

accounts/abi/bind/bind.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
8383
if input.Name == "" {
8484
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
8585
}
86-
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist {
86+
if hasStruct(input.Type) {
8787
bindStructType[lang](input.Type, structs)
8888
}
8989
}
@@ -93,7 +93,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
9393
if output.Name != "" {
9494
normalized.Outputs[j].Name = capitalise(output.Name)
9595
}
96-
if _, exist := structs[output.Type.String()]; output.Type.T == abi.TupleTy && !exist {
96+
if hasStruct(output.Type) {
9797
bindStructType[lang](output.Type, structs)
9898
}
9999
}
@@ -116,14 +116,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
116116
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
117117
copy(normalized.Inputs, original.Inputs)
118118
for j, input := range normalized.Inputs {
119-
// Indexed fields are input, non-indexed ones are outputs
120-
if input.Indexed {
121-
if input.Name == "" {
122-
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
123-
}
124-
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist {
125-
bindStructType[lang](input.Type, structs)
126-
}
119+
if input.Name == "" {
120+
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
121+
}
122+
if hasStruct(input.Type) {
123+
bindStructType[lang](input.Type, structs)
127124
}
128125
}
129126
// Append the event to the accumulator list
@@ -235,7 +232,7 @@ func bindBasicTypeGo(kind abi.Type) string {
235232
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
236233
switch kind.T {
237234
case abi.TupleTy:
238-
return structs[kind.String()].Name
235+
return structs[kind.TupleRawName+kind.String()].Name
239236
case abi.ArrayTy:
240237
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
241238
case abi.SliceTy:
@@ -255,6 +252,13 @@ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct)
255252
// funcionality as for simple types, but dynamic types get converted to hashes.
256253
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
257254
bound := bindTypeGo(kind, structs)
255+
256+
// todo(rjl493456442) according solidity documentation, indexed event
257+
// parameters that are not value types i.e. arrays and structs are not
258+
// stored directly but instead a keccak256-hash of an encoding is stored.
259+
//
260+
// We only convert stringS and bytes to hash, still need to deal with
261+
// array(both fixed-size and dynamic-size) and struct.
258262
if bound == "string" || bound == "[]byte" {
259263
bound = "common.Hash"
260264
}
@@ -273,16 +277,26 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct
273277
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
274278
switch kind.T {
275279
case abi.TupleTy:
276-
if s, exist := structs[kind.String()]; exist {
280+
// We compose raw struct name and canonical parameter expression
281+
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
282+
// is empty, so we use canonical parameter expression to distinguish
283+
// different struct definition. From the consideration of backward
284+
// compatibility, we concat these two together so that if kind.TupleRawName
285+
// is not empty, it can have unique id.
286+
id := kind.TupleRawName + kind.String()
287+
if s, exist := structs[id]; exist {
277288
return s.Name
278289
}
279290
var fields []*tmplField
280291
for i, elem := range kind.TupleElems {
281292
field := bindStructTypeGo(*elem, structs)
282293
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
283294
}
284-
name := fmt.Sprintf("Struct%d", len(structs))
285-
structs[kind.String()] = &tmplStruct{
295+
name := kind.TupleRawName
296+
if name == "" {
297+
name = fmt.Sprintf("Struct%d", len(structs))
298+
}
299+
structs[id] = &tmplStruct{
286300
Name: name,
287301
Fields: fields,
288302
}
@@ -346,6 +360,21 @@ func structured(args abi.Arguments) bool {
346360
return true
347361
}
348362

363+
// hasStruct returns an indicator whether the given type is struct, struct slice
364+
// or struct array.
365+
func hasStruct(t abi.Type) bool {
366+
switch t.T {
367+
case abi.SliceTy:
368+
return hasStruct(*t.Elem)
369+
case abi.ArrayTy:
370+
return hasStruct(*t.Elem)
371+
case abi.TupleTy:
372+
return true
373+
default:
374+
return false
375+
}
376+
}
377+
349378
// resolveArgName converts a raw argument representation into a user friendly format.
350379
func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string {
351380
var (
@@ -361,7 +390,7 @@ loop:
361390
case abi.ArrayTy:
362391
prefix += fmt.Sprintf("[%d]", typ.Size)
363392
default:
364-
embedded = typ.String()
393+
embedded = typ.TupleRawName + typ.String()
365394
break loop
366395
}
367396
typ = typ.Elem

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

@@ -481,7 +481,7 @@ var (
481481
482482
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
483483
//
484-
// Solidity: {{.Original.String}}
484+
// Solidity: {{formatevent .Original $structs}}
485485
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
486486
event := new({{$contract.Type}}{{.Normalized.Name}})
487487
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
@@ -612,7 +612,7 @@ func TestPack(t *testing.T) {
612612
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
613613
},
614614
} {
615-
typ, err := NewType(test.typ, test.components)
615+
typ, err := NewType(test.typ, "", test.components)
616616
if err != nil {
617617
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
618618
}

accounts/abi/type.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type Type struct {
5353
stringKind string // holds the unparsed string for deriving signatures
5454

5555
// Tuple relative fields
56+
TupleRawName string // Raw struct name defined in source code, may be empty.
5657
TupleElems []*Type // Type information of all tuple fields
5758
TupleRawNames []string // Raw field name of all tuple fields
5859
}
@@ -63,7 +64,7 @@ var (
6364
)
6465

6566
// NewType creates a new reflection type of abi type given in t.
66-
func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
67+
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
6768
// check that array brackets are equal if they exist
6869
if strings.Count(t, "[") != strings.Count(t, "]") {
6970
return Type{}, errors.New("invalid arg type in abi")
@@ -73,9 +74,14 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
7374
// if there are brackets, get ready to go into slice/array mode and
7475
// recursively create the type
7576
if strings.Count(t, "[") != 0 {
76-
i := strings.LastIndex(t, "[")
77+
// Note internalType can be empty here.
78+
subInternal := internalType
79+
if i := strings.LastIndex(internalType, "["); i != -1 {
80+
subInternal = subInternal[:i]
81+
}
7782
// recursively embed the type
78-
embeddedType, err := NewType(t[:i], components)
83+
i := strings.LastIndex(t, "[")
84+
embeddedType, err := NewType(t[:i], subInternal, components)
7985
if err != nil {
8086
return Type{}, err
8187
}
@@ -172,7 +178,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
172178
)
173179
expression += "("
174180
for idx, c := range components {
175-
cType, err := NewType(c.Type, c.Components)
181+
cType, err := NewType(c.Type, c.InternalType, c.Components)
176182
if err != nil {
177183
return Type{}, err
178184
}
@@ -198,6 +204,17 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
198204
typ.TupleRawNames = names
199205
typ.T = TupleTy
200206
typ.stringKind = expression
207+
208+
const structPrefix = "struct "
209+
// After solidity 0.5.10, a new field of abi "internalType"
210+
// is introduced. From that we can obtain the struct name
211+
// user defined in the source code.
212+
if internalType != "" && strings.HasPrefix(internalType, structPrefix) {
213+
// Foo.Bar type definition is not allowed in golang,
214+
// convert the format to FooBar
215+
typ.TupleRawName = strings.Replace(internalType[len(structPrefix):], ".", "", -1)
216+
}
217+
201218
case "function":
202219
typ.Kind = reflect.Array
203220
typ.T = FunctionTy

0 commit comments

Comments
 (0)