accounts/abi: add new abi field internalType

This commit is contained in:
rjl493456442 2019-10-18 18:35:17 +08:00
parent 761c731ab2
commit 917b8f564f
10 changed files with 106 additions and 43 deletions

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
} }

View file

@ -241,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:
@ -318,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:
@ -337,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 only convert string 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"
} }
@ -347,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 only convert string 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"
} }
@ -366,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 composite 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
@ -374,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,
} }
@ -395,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 composite 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
@ -403,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,
} }
@ -524,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.
} }

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 only convert string 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.ReplaceAll(internalType[len(structPrefix):], ".", "")
}
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 {