Skip to content

Commit

Permalink
accounts/abi: add internalType information and fix issues (20179)
Browse files Browse the repository at this point in the history
  • Loading branch information
JukLee0ira committed Oct 24, 2024
1 parent 4bd1b7e commit 0a219a5
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 30 deletions.
7 changes: 2 additions & 5 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,6 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {

// Unpack output in v according to the abi specification
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,
// we need to decide whether we're calling a method or an event
if method, ok := abi.Methods[name]; ok {
Expand Down Expand Up @@ -237,7 +234,7 @@ func UnpackRevert(data []byte) (string, error) {
}
switch {
case bytes.Equal(data[:4], revertSelector):
typ, err := NewType("string", nil)
typ, err := NewType("string", "", nil)
if err != nil {
return "", err
}
Expand All @@ -247,7 +244,7 @@ func UnpackRevert(data []byte) (string, error) {
}
return unpacked[0].(string), nil
case bytes.Equal(data[:4], panicSelector):
typ, err := NewType("uint256", nil)
typ, err := NewType("uint256", "", nil)
if err != nil {
return "", err
}
Expand Down
14 changes: 7 additions & 7 deletions accounts/abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const jsondata2 = `
]`

func TestReader(t *testing.T) {
Uint256, _ := NewType("uint256", nil)
Uint256, _ := NewType("uint256", "", nil)
exp := ABI{
Methods: map[string]Method{
"balance": {
Expand Down Expand Up @@ -178,7 +178,7 @@ func TestTestSlice(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}
exp := "foo(string,string)"
if m.Sig() != exp {
Expand All @@ -190,15 +190,15 @@ func TestMethodSignature(t *testing.T) {
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}
exp = "foo(uint256)"
if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig())
}

// Method with tuple arguments
s, _ := NewType("tuple", []ArgumentMarshaling{
s, _ := NewType("tuple", "", []ArgumentMarshaling{
{Name: "a", Type: "int256"},
{Name: "b", Type: "int256[]"},
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
Expand Down Expand Up @@ -611,9 +611,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" }] }
]`

arg0, _ := NewType("uint256", nil)
arg1, _ := NewType("address", nil)
tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
arg0, _ := NewType("uint256", "", nil)
arg1, _ := NewType("address", "", nil)
tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})

expectedEvents := map[string]struct {
Anonymous bool
Expand Down
25 changes: 20 additions & 5 deletions accounts/abi/argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ type Argument struct {
type Arguments []Argument

type ArgumentMarshaling struct {
Name string
Type string
Components []ArgumentMarshaling
Indexed bool
Name string
Type string
InternalType string
Components []ArgumentMarshaling
Indexed bool
}

// UnmarshalJSON implements json.Unmarshaler interface
Expand All @@ -49,7 +50,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
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 {
return err
}
Expand Down Expand Up @@ -89,6 +90,13 @@ func (arguments Arguments) isTuple() bool {

// Unpack performs the operation hexdata -> Go format
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
if reflect.Ptr != reflect.ValueOf(v).Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
Expand Down Expand Up @@ -116,6 +124,13 @@ func (arguments Arguments) Unpack2(data []byte) ([]interface{}, error) {

// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
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)
if err != nil {
return err
Expand Down
32 changes: 30 additions & 2 deletions accounts/abi/bind/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func arrayBindingGo(inner string, arraySizes []string) string {
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
// mapped will use an upscaled type (e.g. *big.Int).
func bindTypeGo(kind abi.Type) string {
stringKind := kind.String()
stringKind := kind.TupleRawName + kind.String()
innerLen, innerMapping := bindUnnestedTypeGo(stringKind)
return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping))
}
Expand Down Expand Up @@ -249,7 +249,7 @@ func arrayBindingJava(inner string, arraySizes []string) string {
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
// mapped will use an upscaled type (e.g. BigDecimal).
func bindTypeJava(kind abi.Type) string {
stringKind := kind.String()
stringKind := kind.TupleRawName + kind.String()
innerLen, innerMapping := bindUnnestedTypeJava(stringKind)
return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping))
}
Expand Down Expand Up @@ -319,6 +319,12 @@ var bindTopicType = map[Lang]func(kind abi.Type) string{
// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
// funcionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeGo(kind abi.Type) string {
// 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.
bound := bindTypeGo(kind)
if bound == "string" || bound == "[]byte" {
bound = "common.Hash"
Expand All @@ -329,6 +335,13 @@ func bindTopicTypeGo(kind abi.Type) string {
// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same
// funcionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeJava(kind abi.Type) string {

// 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.
bound := bindTypeJava(kind)
if bound == "String" || bound == "Bytes" {
bound = "Hash"
Expand Down Expand Up @@ -454,3 +467,18 @@ func structured(args abi.Arguments) bool {
}
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
}
}
9 changes: 7 additions & 2 deletions accounts/abi/bind/topics.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,18 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
copy(topic[:], hash[:])

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
val := reflect.ValueOf(rule)

switch {
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[common.HashLength-val.Len():]), val)

default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
}
Expand Down
2 changes: 1 addition & 1 deletion accounts/abi/pack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ func TestPack(t *testing.T) {
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
} {
typ, err := NewType(test.typ, nil)
typ, err := NewType(test.typ, "", nil)
if err != nil {
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
}
Expand Down
23 changes: 19 additions & 4 deletions accounts/abi/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Type struct {
stringKind string // holds the unparsed string for deriving signatures

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

// 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
if strings.Count(t, "[") != strings.Count(t, "]") {
return Type{}, errors.New("invalid arg type in abi")
Expand All @@ -74,9 +75,14 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
// if there are brackets, get ready to go into slice/array mode and
// recursively create the type
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
embeddedType, err := NewType(t[:i], components)
i := strings.LastIndex(t, "[")
embeddedType, err := NewType(t[:i], subInternal, components)
if err != nil {
return Type{}, err
}
Expand Down Expand Up @@ -177,7 +183,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
)
expression += "("
for idx, c := range components {
cType, err := NewType(c.Type, c.Components)
cType, err := NewType(c.Type, c.InternalType, c.Components)
if err != nil {
return Type{}, err
}
Expand All @@ -202,6 +208,15 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
typ.TupleRawNames = names
typ.T = TupleTy
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":
typ.Kind = reflect.Array
typ.T = FunctionTy
Expand Down
4 changes: 2 additions & 2 deletions accounts/abi/type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestTypeRegexp(t *testing.T) {
}

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

0 comments on commit 0a219a5

Please sign in to comment.