Skip to content

Commit

Permalink
Fix parse func types in NewMethod (umbracle#134)
Browse files Browse the repository at this point in the history
* Fix parse func types in NewMethod

* Add error and constructor types

* Fix test

* Rebase + overload functions in NewMethod

* Add overload to human readable abi
  • Loading branch information
ferranbt authored Jan 12, 2022
1 parent 118e6ef commit 157826f
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 30 deletions.
93 changes: 68 additions & 25 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type ABI struct {
Methods map[string]*Method
MethodsBySignature map[string]*Method
Events map[string]*Event
Errors map[string]*Error
}

func (a *ABI) GetMethod(name string) *Method {
Expand All @@ -32,11 +33,22 @@ func (a *ABI) GetMethodBySignature(methodSignature string) *Method {
return m
}

func (a *ABI) addError(e *Error) {
if len(a.Errors) == 0 {
a.Errors = map[string]*Error{}
}
a.Errors[e.Name] = e
}

func (a *ABI) addEvent(e *Event) {
if len(a.Methods) == 0 {
if len(a.Events) == 0 {
a.Events = map[string]*Event{}
}
a.Events[e.Name] = e
name := overloadedName(e.Name, func(s string) bool {
_, ok := a.Events[s]
return ok
})
a.Events[name] = e
}

func (a *ABI) addMethod(m *Method) {
Expand All @@ -46,10 +58,24 @@ func (a *ABI) addMethod(m *Method) {
if len(a.MethodsBySignature) == 0 {
a.MethodsBySignature = map[string]*Method{}
}
a.Methods[m.Name] = m
name := overloadedName(m.Name, func(s string) bool {
_, ok := a.Methods[s]
return ok
})
a.Methods[name] = m
a.MethodsBySignature[m.Sig()] = m
}

func overloadedName(rawName string, isAvail func(string) bool) string {
name := rawName
ok := isAvail(name)
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
ok = isAvail(name)
}
return name
}

// NewABI returns a parsed ABI struct
func NewABI(s string) (*ABI, error) {
return NewABIFromReader(bytes.NewReader([]byte(s)))
Expand Down Expand Up @@ -109,24 +135,21 @@ func (a *ABI) UnmarshalJSON(data []byte) error {
if field.StateMutability == "view" || field.StateMutability == "pure" {
c = true
}

name := overloadedName(field.Name, func(s string) bool { _, ok := a.Methods[s]; return ok })
method := &Method{
Name: field.Name,
Const: c,
Inputs: field.Inputs.Type(),
Outputs: field.Outputs.Type(),
}
a.Methods[name] = method
a.MethodsBySignature[method.Sig()] = method
a.addMethod(method)

case "event":
name := overloadedName(field.Name, func(s string) bool { _, ok := a.Events[s]; return ok })
a.Events[name] = &Event{
event := &Event{
Name: field.Name,
Anonymous: field.Anonymous,
Inputs: field.Inputs.Type(),
}
a.addEvent(event)

case "fallback":
case "receive":
Expand Down Expand Up @@ -194,8 +217,8 @@ func NewMethod(name string) (*Method, error) {
}

var (
funcRegexpWithReturn = regexp.MustCompile(`(.*)\((.*)\)(.*) returns \((.*)\)`)
funcRegexpWithoutReturn = regexp.MustCompile(`(.*)\((.*)\)(.*)`)
funcRegexpWithReturn = regexp.MustCompile(`(\w*)\((.*)\)(.*) returns \((.*)\)`)
funcRegexpWithoutReturn = regexp.MustCompile(`(\w*)\((.*)\)(.*)`)
)

func parseMethodSignature(name string) (string, *Type, *Type, error) {
Expand Down Expand Up @@ -265,15 +288,30 @@ func MustNewEvent(name string) *Event {

// NewEvent creates a new solidity event object using the signature
func NewEvent(name string) (*Event, error) {
name, typ, err := parseEventSignature(name)
name, typ, err := parseEventSignature("event", name)
if err != nil {
return nil, err
}
return NewEventFromType(name, typ), nil
}

func parseEventSignature(name string) (string, *Type, error) {
name = strings.TrimPrefix(name, "event ")
// Error is a solidity error object
type Error struct {
Name string
Inputs *Type
}

// NewError creates a new solidity error object
func NewError(name string) (*Error, error) {
name, typ, err := parseEventSignature("error", name)
if err != nil {
return nil, err
}
return &Error{Name: name, Inputs: typ}, nil
}

func parseEventSignature(prefix string, name string) (string, *Type, error) {
name = strings.TrimPrefix(name, prefix+" ")
if !strings.HasSuffix(name, ")") {
return "", nil, fmt.Errorf("failed to parse input, expected 'name(types)'")
}
Expand Down Expand Up @@ -393,7 +431,16 @@ func releaseKeccak(k hash.Hash) {
func NewABIFromList(humanReadableAbi []string) (*ABI, error) {
res := &ABI{}
for _, c := range humanReadableAbi {
if strings.HasPrefix(c, "function ") {
if strings.HasPrefix(c, "constructor") {
typ, err := NewType("tuple" + strings.TrimPrefix(c, "constructor"))
if err != nil {
return nil, err
}
res.Constructor = &Method{
Inputs: typ,
}

} else if strings.HasPrefix(c, "function ") {
method, err := NewMethod(c)
if err != nil {
return nil, err
Expand All @@ -405,19 +452,15 @@ func NewABIFromList(humanReadableAbi []string) (*ABI, error) {
return nil, err
}
res.addEvent(evnt)
} else if strings.HasPrefix(c, "error ") {
errTyp, err := NewError(c)
if err != nil {
return nil, err
}
res.addError(errTyp)
} else {
return nil, fmt.Errorf("either event or function expected")
}
}
return res, nil
}

func overloadedName(rawName string, isAvail func(string) bool) string {
name := rawName
ok := isAvail(name)
for idx := 0; ok; idx++ {
name = fmt.Sprintf("%s%d", rawName, idx)
ok = isAvail(name)
}
return name
}
71 changes: 67 additions & 4 deletions abi/abi_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package abi

import (
"fmt"
"reflect"
"testing"

Expand Down Expand Up @@ -123,13 +122,77 @@ func TestAbi_Polymorphism(t *testing.T) {

func TestAbi_HumanReadable(t *testing.T) {
cases := []string{
"event Transfer(address from, address to, uint256 amount)",
"function symbol() returns (string)",
"constructor(string symbol, string name)",
"function transferFrom(address from, address to, uint256 value)",
"function balanceOf(address owner) view returns (uint256 balance)",
"function balanceOf() view returns ()",
"event Transfer(address indexed from, address indexed to, address value)",
"error InsufficientBalance(address owner, uint256 balance)",
"function addPerson(tuple(string name, uint16 age) person)",
"function addPeople(tuple(string name, uint16 age)[] person)",
"function getPerson(uint256 id) view returns (tuple(string name, uint16 age))",
"event PersonAdded(uint256 indexed id, tuple(string name, uint16 age) person)",
}
vv, err := NewABIFromList(cases)
assert.NoError(t, err)

fmt.Println(vv.Methods["symbol"].Inputs.String())
// make it nil to not compare it and avoid writing each method twice for the test
vv.MethodsBySignature = nil

expect := &ABI{
Constructor: &Method{
Inputs: MustNewType("tuple(string symbol, string name)"),
},
Methods: map[string]*Method{
"transferFrom": &Method{
Name: "transferFrom",
Inputs: MustNewType("tuple(address from, address to, uint256 value)"),
Outputs: MustNewType("tuple()"),
},
"balanceOf": &Method{
Name: "balanceOf",
Inputs: MustNewType("tuple(address owner)"),
Outputs: MustNewType("tuple(uint256 balance)"),
},
"balanceOf0": &Method{
Name: "balanceOf",
Inputs: MustNewType("tuple()"),
Outputs: MustNewType("tuple()"),
},
"addPerson": &Method{
Name: "addPerson",
Inputs: MustNewType("tuple(tuple(string name, uint16 age) person)"),
Outputs: MustNewType("tuple()"),
},
"addPeople": &Method{
Name: "addPeople",
Inputs: MustNewType("tuple(tuple(string name, uint16 age)[] person)"),
Outputs: MustNewType("tuple()"),
},
"getPerson": &Method{
Name: "getPerson",
Inputs: MustNewType("tuple(uint256 id)"),
Outputs: MustNewType("tuple(tuple(string name, uint16 age))"),
},
},
Events: map[string]*Event{
"Transfer": &Event{
Name: "Transfer",
Inputs: MustNewType("tuple(address indexed from, address indexed to, address value)"),
},
"PersonAdded": &Event{
Name: "PersonAdded",
Inputs: MustNewType("tuple(uint256 indexed id, tuple(string name, uint16 age) person)"),
},
},
Errors: map[string]*Error{
"InsufficientBalance": &Error{
Name: "InsufficientBalance",
Inputs: MustNewType("tuple(address owner, uint256 balance)"),
},
},
}
assert.Equal(t, expect, vv)
}

func TestAbi_ParseMethodSignature(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion contract/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestContractNoInput(t *testing.T) {
assert.Equal(t, vals["0"], big.NewInt(1))

abi1, err := abi.NewABIFromList([]string{
"function set () view returns (uint256)",
"function set() view returns (uint256)",
})
assert.NoError(t, err)

Expand Down

0 comments on commit 157826f

Please sign in to comment.