accounts/abi: add internalType information and fix issues (#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
This commit is contained in:
gary rong 2019-10-31 21:17:51 +08:00 committed by Guillaume Ballet
parent 9278951a62
commit 44b74cfc40
12 changed files with 175 additions and 63 deletions

View File

@ -75,9 +75,6 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
// Unpack output in v according to the abi specification // Unpack output in v according to the abi specification
func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
if len(data) == 0 {
return fmt.Errorf("abi: unmarshalling empty output")
}
// since there can't be naming collisions with contracts and events, // since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event // we need to decide whether we're calling a method or an event
if method, ok := abi.Methods[name]; ok { if method, ok := abi.Methods[name]; ok {
@ -94,9 +91,6 @@ func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
// UnpackIntoMap unpacks a log into the provided map[string]interface{} // UnpackIntoMap unpacks a log into the provided map[string]interface{}
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
if len(data) == 0 {
return fmt.Errorf("abi: unmarshalling empty output")
}
// since there can't be naming collisions with contracts and events, // since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event // we need to decide whether we're calling a method or an event
if method, ok := abi.Methods[name]; ok { if method, ok := abi.Methods[name]; ok {

View File

@ -57,7 +57,7 @@ const jsondata2 = `
]` ]`
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
Uint256, _ := NewType("uint256", nil) Uint256, _ := NewType("uint256", "", nil)
exp := ABI{ exp := ABI{
Methods: map[string]Method{ Methods: map[string]Method{
"balance": { "balance": {
@ -172,7 +172,7 @@ func TestTestSlice(t *testing.T) {
} }
func TestMethodSignature(t *testing.T) { func TestMethodSignature(t *testing.T) {
String, _ := NewType("string", nil) String, _ := NewType("string", "", nil)
m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
exp := "foo(string,string)" exp := "foo(string,string)"
if m.Sig() != exp { if m.Sig() != exp {
@ -184,7 +184,7 @@ func TestMethodSignature(t *testing.T) {
t.Errorf("expected ids to match %x != %x", m.ID(), idexp) t.Errorf("expected ids to match %x != %x", m.ID(), idexp)
} }
uintt, _ := NewType("uint256", nil) uintt, _ := NewType("uint256", "", nil)
m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil} m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil}
exp = "foo(uint256)" exp = "foo(uint256)"
if m.Sig() != exp { if m.Sig() != exp {
@ -192,7 +192,7 @@ func TestMethodSignature(t *testing.T) {
} }
// Method with tuple arguments // Method with tuple arguments
s, _ := NewType("tuple", []ArgumentMarshaling{ s, _ := NewType("tuple", "", []ArgumentMarshaling{
{Name: "a", Type: "int256"}, {Name: "a", Type: "int256"},
{Name: "b", Type: "int256[]"}, {Name: "b", Type: "int256[]"},
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{ {Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
@ -602,9 +602,9 @@ func TestBareEvents(t *testing.T) {
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] } { "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
]` ]`
arg0, _ := NewType("uint256", nil) arg0, _ := NewType("uint256", "", nil)
arg1, _ := NewType("address", nil) arg1, _ := NewType("address", "", nil)
tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}}) tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
expectedEvents := map[string]struct { expectedEvents := map[string]struct {
Anonymous bool Anonymous bool

View File

@ -36,6 +36,7 @@ type Arguments []Argument
type ArgumentMarshaling struct { type ArgumentMarshaling struct {
Name string Name string
Type string Type string
InternalType string
Components []ArgumentMarshaling Components []ArgumentMarshaling
Indexed bool Indexed bool
} }
@ -48,7 +49,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
return fmt.Errorf("argument json err: %v", err) return fmt.Errorf("argument json err: %v", err)
} }
argument.Type, err = NewType(arg.Type, arg.Components) argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components)
if err != nil { if err != nil {
return err return err
} }
@ -88,6 +89,13 @@ func (arguments Arguments) isTuple() bool {
// Unpack performs the operation hexdata -> Go format // Unpack performs the operation hexdata -> Go format
func (arguments Arguments) Unpack(v interface{}, data []byte) error { func (arguments Arguments) Unpack(v interface{}, data []byte) error {
if len(data) == 0 {
if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
} else {
return nil // Nothing to unmarshal, return
}
}
// make sure the passed value is arguments pointer // make sure the passed value is arguments pointer
if reflect.Ptr != reflect.ValueOf(v).Kind() { if reflect.Ptr != reflect.ValueOf(v).Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v) return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
@ -104,11 +112,17 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value // UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
if len(data) == 0 {
if len(arguments) != 0 {
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
} else {
return nil // Nothing to unmarshal, return
}
}
marshalledValues, err := arguments.UnpackValues(data) marshalledValues, err := arguments.UnpackValues(data)
if err != nil { if err != nil {
return err return err
} }
return arguments.unpackIntoMap(v, marshalledValues) return arguments.unpackIntoMap(v, marshalledValues)
} }

View File

@ -86,7 +86,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
if input.Name == "" { if input.Name == "" {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
} }
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist { if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs) bindStructType[lang](input.Type, structs)
} }
} }
@ -96,7 +96,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
if output.Name != "" { if output.Name != "" {
normalized.Outputs[j].Name = capitalise(output.Name) normalized.Outputs[j].Name = capitalise(output.Name)
} }
if _, exist := structs[output.Type.String()]; output.Type.T == abi.TupleTy && !exist { if hasStruct(output.Type) {
bindStructType[lang](output.Type, structs) bindStructType[lang](output.Type, structs)
} }
} }
@ -119,16 +119,13 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized.Inputs = make([]abi.Argument, len(original.Inputs)) normalized.Inputs = make([]abi.Argument, len(original.Inputs))
copy(normalized.Inputs, original.Inputs) copy(normalized.Inputs, original.Inputs)
for j, input := range normalized.Inputs { for j, input := range normalized.Inputs {
// Indexed fields are input, non-indexed ones are outputs
if input.Indexed {
if input.Name == "" { if input.Name == "" {
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
} }
if _, exist := structs[input.Type.String()]; input.Type.T == abi.TupleTy && !exist { if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs) bindStructType[lang](input.Type, structs)
} }
} }
}
// Append the event to the accumulator list // Append the event to the accumulator list
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
} }
@ -244,7 +241,7 @@ func bindBasicTypeGo(kind abi.Type) string {
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T { switch kind.T {
case abi.TupleTy: case abi.TupleTy:
return structs[kind.String()].Name return structs[kind.TupleRawName+kind.String()].Name
case abi.ArrayTy: case abi.ArrayTy:
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
case abi.SliceTy: case abi.SliceTy:
@ -321,7 +318,7 @@ func pluralizeJavaType(typ string) string {
func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T { switch kind.T {
case abi.TupleTy: case abi.TupleTy:
return structs[kind.String()].Name return structs[kind.TupleRawName+kind.String()].Name
case abi.ArrayTy, abi.SliceTy: case abi.ArrayTy, abi.SliceTy:
return pluralizeJavaType(bindTypeJava(*kind.Elem, structs)) return pluralizeJavaType(bindTypeJava(*kind.Elem, structs))
default: default:
@ -340,6 +337,13 @@ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct)
// funcionality as for simple types, but dynamic types get converted to hashes. // funcionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
bound := bindTypeGo(kind, structs) bound := bindTypeGo(kind, structs)
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.
if bound == "string" || bound == "[]byte" { if bound == "string" || bound == "[]byte" {
bound = "common.Hash" bound = "common.Hash"
} }
@ -350,6 +354,13 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
// funcionality as for simple types, but dynamic types get converted to hashes. // funcionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
bound := bindTypeJava(kind, structs) bound := bindTypeJava(kind, structs)
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.
if bound == "String" || bound == "byte[]" { if bound == "String" || bound == "byte[]" {
bound = "Hash" bound = "Hash"
} }
@ -369,7 +380,14 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T { switch kind.T {
case abi.TupleTy: case abi.TupleTy:
if s, exist := structs[kind.String()]; exist { // We compose raw struct name and canonical parameter expression
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
// is empty, so we use canonical parameter expression to distinguish
// different struct definition. From the consideration of backward
// compatibility, we concat these two together so that if kind.TupleRawName
// is not empty, it can have unique id.
id := kind.TupleRawName + kind.String()
if s, exist := structs[id]; exist {
return s.Name return s.Name
} }
var fields []*tmplField var fields []*tmplField
@ -377,8 +395,11 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
field := bindStructTypeGo(*elem, structs) field := bindStructTypeGo(*elem, structs)
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem}) fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
} }
name := fmt.Sprintf("Struct%d", len(structs)) name := kind.TupleRawName
structs[kind.String()] = &tmplStruct{ if name == "" {
name = fmt.Sprintf("Struct%d", len(structs))
}
structs[id] = &tmplStruct{
Name: name, Name: name,
Fields: fields, Fields: fields,
} }
@ -398,7 +419,14 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T { switch kind.T {
case abi.TupleTy: case abi.TupleTy:
if s, exist := structs[kind.String()]; exist { // We compose raw struct name and canonical parameter expression
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
// is empty, so we use canonical parameter expression to distinguish
// different struct definition. From the consideration of backward
// compatibility, we concat these two together so that if kind.TupleRawName
// is not empty, it can have unique id.
id := kind.TupleRawName + kind.String()
if s, exist := structs[id]; exist {
return s.Name return s.Name
} }
var fields []*tmplField var fields []*tmplField
@ -406,8 +434,11 @@ func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
field := bindStructTypeJava(*elem, structs) field := bindStructTypeJava(*elem, structs)
fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem}) fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem})
} }
name := fmt.Sprintf("Class%d", len(structs)) name := kind.TupleRawName
structs[kind.String()] = &tmplStruct{ if name == "" {
name = fmt.Sprintf("Class%d", len(structs))
}
structs[id] = &tmplStruct{
Name: name, Name: name,
Fields: fields, Fields: fields,
} }
@ -497,6 +528,21 @@ func structured(args abi.Arguments) bool {
return true return true
} }
// hasStruct returns an indicator whether the given type is struct, struct slice
// or struct array.
func hasStruct(t abi.Type) bool {
switch t.T {
case abi.SliceTy:
return hasStruct(*t.Elem)
case abi.ArrayTy:
return hasStruct(*t.Elem)
case abi.TupleTy:
return true
default:
return false
}
}
// resolveArgName converts a raw argument representation into a user friendly format. // resolveArgName converts a raw argument representation into a user friendly format.
func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string { func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string {
var ( var (
@ -512,7 +558,7 @@ loop:
case abi.ArrayTy: case abi.ArrayTy:
prefix += fmt.Sprintf("[%d]", typ.Size) prefix += fmt.Sprintf("[%d]", typ.Size)
default: default:
embedded = typ.String() embedded = typ.TupleRawName + typ.String()
break loop break loop
} }
typ = typ.Elem typ = typ.Elem

File diff suppressed because one or more lines are too long

View File

@ -65,7 +65,7 @@ type tmplField struct {
// tmplStruct is a wrapper around an abi.tuple contains a auto-generated // tmplStruct is a wrapper around an abi.tuple contains a auto-generated
// struct name. // struct name.
type tmplStruct struct { type tmplStruct struct {
Name string // Auto-generated struct name(We can't obtain the raw struct name through abi) Name string // Auto-generated struct name(before solidity v0.5.11) or raw name.
Fields []*tmplField // Struct fields definition depends on the binding language. Fields []*tmplField // Struct fields definition depends on the binding language.
} }
@ -483,7 +483,7 @@ var (
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}. // Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
// //
// Solidity: {{.Original.String}} // Solidity: {{formatevent .Original $structs}}
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
event := new({{$contract.Type}}{{.Normalized.Name}}) event := new({{$contract.Type}}{{.Normalized.Name}})
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {

View File

@ -80,15 +80,19 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
copy(topic[:], hash[:]) copy(topic[:], hash[:])
default: default:
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.
// Attempt to generate the topic from funky types // Attempt to generate the topic from funky types
val := reflect.ValueOf(rule) val := reflect.ValueOf(rule)
switch { switch {
// static byte array // static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8: case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val) reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default: default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule) return nil, fmt.Errorf("unsupported indexed type: %T", rule)
} }
@ -162,6 +166,7 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
default: default:
// Ran out of plain primitive types, try custom types // Ran out of plain primitive types, try custom types
switch field.Type() { switch field.Type() {
case reflectHash: // Also covers all dynamic types case reflectHash: // Also covers all dynamic types
field.Set(reflect.ValueOf(topics[0])) field.Set(reflect.ValueOf(topics[0]))
@ -178,11 +183,9 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
default: default:
// Ran out of custom types, try the crazies // Ran out of custom types, try the crazies
switch { switch {
// static byte array // static byte array
case arg.Type.T == abi.FixedBytesTy: case arg.Type.T == abi.FixedBytesTy:
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size])) reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
default: default:
return fmt.Errorf("unsupported indexed type: %v", arg.Type) return fmt.Errorf("unsupported indexed type: %v", arg.Type)
} }

View File

@ -59,7 +59,7 @@ func TestParseTopics(t *testing.T) {
type bytesStruct struct { type bytesStruct struct {
StaticBytes [5]byte StaticBytes [5]byte
} }
bytesType, _ := abi.NewType("bytes5", nil) bytesType, _ := abi.NewType("bytes5", "", nil)
type args struct { type args struct {
createObj func() interface{} createObj func() interface{}
resultObj func() interface{} resultObj func() interface{}

View File

@ -613,7 +613,7 @@ func TestPack(t *testing.T) {
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1] "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
}, },
} { } {
typ, err := NewType(test.typ, test.components) typ, err := NewType(test.typ, "", test.components)
if err != nil { if err != nil {
t.Fatalf("%v failed. Unexpected parse error: %v", i, err) t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
} }

View File

@ -53,6 +53,7 @@ type Type struct {
stringKind string // holds the unparsed string for deriving signatures stringKind string // holds the unparsed string for deriving signatures
// Tuple relative fields // Tuple relative fields
TupleRawName string // Raw struct name defined in source code, may be empty.
TupleElems []*Type // Type information of all tuple fields TupleElems []*Type // Type information of all tuple fields
TupleRawNames []string // Raw field name of all tuple fields TupleRawNames []string // Raw field name of all tuple fields
} }
@ -63,7 +64,7 @@ var (
) )
// NewType creates a new reflection type of abi type given in t. // NewType creates a new reflection type of abi type given in t.
func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) { func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
// check that array brackets are equal if they exist // check that array brackets are equal if they exist
if strings.Count(t, "[") != strings.Count(t, "]") { if strings.Count(t, "[") != strings.Count(t, "]") {
return Type{}, fmt.Errorf("invalid arg type in abi") return Type{}, fmt.Errorf("invalid arg type in abi")
@ -73,9 +74,14 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
// if there are brackets, get ready to go into slice/array mode and // if there are brackets, get ready to go into slice/array mode and
// recursively create the type // recursively create the type
if strings.Count(t, "[") != 0 { if strings.Count(t, "[") != 0 {
i := strings.LastIndex(t, "[") // Note internalType can be empty here.
subInternal := internalType
if i := strings.LastIndex(internalType, "["); i != -1 {
subInternal = subInternal[:i]
}
// recursively embed the type // recursively embed the type
embeddedType, err := NewType(t[:i], components) i := strings.LastIndex(t, "[")
embeddedType, err := NewType(t[:i], subInternal, components)
if err != nil { if err != nil {
return Type{}, err return Type{}, err
} }
@ -173,7 +179,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
) )
expression += "(" expression += "("
for idx, c := range components { for idx, c := range components {
cType, err := NewType(c.Type, c.Components) cType, err := NewType(c.Type, c.InternalType, c.Components)
if err != nil { if err != nil {
return Type{}, err return Type{}, err
} }
@ -199,6 +205,17 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
typ.TupleRawNames = names typ.TupleRawNames = names
typ.T = TupleTy typ.T = TupleTy
typ.stringKind = expression typ.stringKind = expression
const structPrefix = "struct "
// After solidity 0.5.10, a new field of abi "internalType"
// is introduced. From that we can obtain the struct name
// user defined in the source code.
if internalType != "" && strings.HasPrefix(internalType, structPrefix) {
// Foo.Bar type definition is not allowed in golang,
// convert the format to FooBar
typ.TupleRawName = strings.Replace(internalType[len(structPrefix):], ".", "", -1)
}
case "function": case "function":
typ.Kind = reflect.Array typ.Kind = reflect.Array
typ.T = FunctionTy typ.T = FunctionTy

View File

@ -106,7 +106,7 @@ func TestTypeRegexp(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
typ, err := NewType(tt.blob, tt.components) typ, err := NewType(tt.blob, "", tt.components)
if err != nil { if err != nil {
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err) t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
} }
@ -281,7 +281,7 @@ func TestTypeCheck(t *testing.T) {
B *big.Int B *big.Int
}{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""}, }{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""},
} { } {
typ, err := NewType(test.typ, test.components) typ, err := NewType(test.typ, "", test.components)
if err != nil && len(test.err) == 0 { if err != nil && len(test.err) == 0 {
t.Fatal("unexpected parse error:", err) t.Fatal("unexpected parse error:", err)
} else if err != nil && len(test.err) != 0 { } else if err != nil && len(test.err) != 0 {

View File

@ -51,6 +51,7 @@ func (test unpackTest) checkError(err error) error {
} }
var unpackTests = []unpackTest{ var unpackTests = []unpackTest{
// Bools
{ {
def: `[{ "type": "bool" }]`, def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001", enc: "0000000000000000000000000000000000000000000000000000000000000001",
@ -73,6 +74,7 @@ var unpackTests = []unpackTest{
want: false, want: false,
err: "abi: improperly encoded boolean value", err: "abi: improperly encoded boolean value",
}, },
// Integers
{ {
def: `[{"type": "uint32"}]`, def: `[{"type": "uint32"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001", enc: "0000000000000000000000000000000000000000000000000000000000000001",
@ -122,11 +124,13 @@ var unpackTests = []unpackTest{
enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
want: big.NewInt(-1), want: big.NewInt(-1),
}, },
// Address
{ {
def: `[{"type": "address"}]`, def: `[{"type": "address"}]`,
enc: "0000000000000000000000000100000000000000000000000000000000000000", enc: "0000000000000000000000000100000000000000000000000000000000000000",
want: common.Address{1}, want: common.Address{1},
}, },
// Bytes
{ {
def: `[{"type": "bytes32"}]`, def: `[{"type": "bytes32"}]`,
enc: "0100000000000000000000000000000000000000000000000000000000000000", enc: "0100000000000000000000000000000000000000000000000000000000000000",
@ -154,23 +158,39 @@ var unpackTests = []unpackTest{
enc: "0100000000000000000000000000000000000000000000000000000000000000", enc: "0100000000000000000000000000000000000000000000000000000000000000",
want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}, },
// Functions
{ {
def: `[{"type": "function"}]`, def: `[{"type": "function"}]`,
enc: "0100000000000000000000000000000000000000000000000000000000000000", enc: "0100000000000000000000000000000000000000000000000000000000000000",
want: [24]byte{1}, want: [24]byte{1},
}, },
// slices // Slice and Array
{ {
def: `[{"type": "uint8[]"}]`, def: `[{"type": "uint8[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: []uint8{1, 2}, want: []uint8{1, 2},
}, },
{
def: `[{"type": "uint8[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: []uint8{},
},
{
def: `[{"type": "uint256[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: []*big.Int{},
},
{ {
def: `[{"type": "uint8[2]"}]`, def: `[{"type": "uint8[2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2]uint8{1, 2}, want: [2]uint8{1, 2},
}, },
// multi dimensional, if these pass, all types that don't require length prefix should pass // multi dimensional, if these pass, all types that don't require length prefix should pass
{
def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: [][]uint8{},
},
{ {
def: `[{"type": "uint8[][]"}]`, def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -186,11 +206,21 @@ var unpackTests = []unpackTest{
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [2][2]uint8{{1, 2}, {1, 2}}, want: [2][2]uint8{{1, 2}, {1, 2}},
}, },
{
def: `[{"type": "uint8[][2]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
want: [2][]uint8{{}, {}},
},
{ {
def: `[{"type": "uint8[][2]"}]`, def: `[{"type": "uint8[][2]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
want: [2][]uint8{{1}, {1}}, want: [2][]uint8{{1}, {1}},
}, },
{
def: `[{"type": "uint8[2][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
want: [][2]uint8{},
},
{ {
def: `[{"type": "uint8[2][]"}]`, def: `[{"type": "uint8[2][]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -420,7 +450,7 @@ func TestUnpack(t *testing.T) {
} }
encb, err := hex.DecodeString(test.enc) encb, err := hex.DecodeString(test.enc)
if err != nil { if err != nil {
t.Fatalf("invalid hex: %s" + test.enc) t.Fatalf("invalid hex %s: %v", test.enc, err)
} }
outptr := reflect.New(reflect.TypeOf(test.want)) outptr := reflect.New(reflect.TypeOf(test.want))
err = abi.Unpack(outptr.Interface(), "method", encb) err = abi.Unpack(outptr.Interface(), "method", encb)