Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new type parse from string #114

Merged
merged 2 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions accounts/abi/type2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package abi

import (
"fmt"
"reflect"
"strings"
)

func NewTypeFromString(s string) (Type, error) {
if strings.HasPrefix(s, "tuple") {
sig, args, err := newTypeForTuple(s)
if err != nil {
return Type{}, err
}
return NewType(sig, "", args)
}
return NewType(s, "", nil)
}

func (t Type) Pack(v interface{}) ([]byte, error) {
return t.pack(reflect.ValueOf(v))
}

func (t Type) Unpack(data []byte, obj interface{}) error {
fmt.Println(toGoType(0, t, data))

return nil
}

// newTypeForTuple implements the format described in https://blog.ricmoo.com/human-readable-contract-abis-in-ethers-js-141902f4d917
func newTypeForTuple(s string) (string, []ArgumentMarshaling, error) {
if !strings.HasPrefix(s, "tuple") {
return "", nil, fmt.Errorf("'tuple' prefix not found")
}

pos := strings.Index(s, "(")
if pos == -1 {
return "", nil, fmt.Errorf("not a tuple, '(' not found")
}

// this is the type of the tuple. It can either be
// tuple, tuple[] or tuple[x]
sig := s[:pos]
s = s[pos:]

// Now, decode the arguments of the tuple
// tuple(arg1, arg2, tuple(arg3, arg4)).
// We need to find the commas that are not inside a nested tuple.
// We do this by keeping a counter of the number of open parens.

var (
parenthesisCount int
fields []string
)

lastComma := 1
for indx, c := range s {
switch c {
case '(':
parenthesisCount++
case ')':
parenthesisCount--
if parenthesisCount == 0 {
fields = append(fields, s[lastComma:indx])

// this should be the end of the tuple
if indx != len(s)-1 {
return "", nil, fmt.Errorf("invalid tuple, it does not end with ')'")
}
}
case ',':
if parenthesisCount == 1 {
fields = append(fields, s[lastComma:indx])
lastComma = indx + 1
}
}
}

// trim the args of spaces
for i := range fields {
fields[i] = strings.TrimSpace(fields[i])
}

// decode the type of each field
var args []ArgumentMarshaling
for _, field := range fields {
// anonymous fields are not supported so the first
// string should be the identifier of the field.

spacePos := strings.Index(field, " ")
if spacePos == -1 {
return "", nil, fmt.Errorf("invalid tuple field name not found '%s'", field)
}

name := field[:spacePos]
field = field[spacePos+1:]

if strings.HasPrefix(field, "tuple") {
// decode a recursive tuple
sig, elems, err := newTypeForTuple(field)
if err != nil {
return "", nil, err
}
args = append(args, ArgumentMarshaling{
Name: name,
Type: sig,
Components: elems,
})
} else {
// basic type. Try to decode it to see
// if it is a correct abi type.
if _, err := NewType(field, "", nil); err != nil {
return "", nil, fmt.Errorf("invalid tuple basic field '%s': %v", field, err)
}
args = append(args, ArgumentMarshaling{
Name: name,
Type: field,
})
}
}

return sig, args, nil
}
94 changes: 94 additions & 0 deletions accounts/abi/type2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package abi

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestNewType2FromString(t *testing.T) {
type struct1 struct {
A uint64
B []struct {
X struct {
C uint64
}
D uint64
}
E struct {
F uint64
G uint64
}
}

cases := []struct {
input string
want string
obj interface{}
}{
{
"uint64[]",
"uint64[]",
[]uint64{1, 2, 3},
},
{
"tuple(a uint64, b uint64)",
"(uint64,uint64)",
&struct {
A uint64
B uint64
}{A: 1, B: 2},
},
{
"tuple(a uint64, b tuple(c uint64), d uint64)",
"(uint64,(uint64),uint64)",
&struct {
A uint64
B struct {
C uint64
}
D uint64
}{A: 1, B: struct{ C uint64 }{C: 2}, D: 3},
},
{
"tuple(a uint64, b tuple[](x tuple(c uint64), d uint64), e tuple(f uint64, g uint64))",
"(uint64,((uint64),uint64)[],(uint64,uint64))",
&struct1{
A: 1,
B: []struct {
X struct {
C uint64
}
D uint64
}{
{
X: struct {
C uint64
}{C: 2},
D: 3,
},
{
X: struct {
C uint64
}{C: 4},
D: 5,
},
},
E: struct {
F uint64
G uint64
}{F: 6, G: 7},
},
},
}

for _, c := range cases {
typ, err := NewTypeFromString(c.input)

require.NoError(t, err)
require.Equal(t, c.want, typ.String())

_, err = typ.Pack(c.obj)
require.NoError(t, err)
}
}
Loading