Skip to content

Commit 9d187f0

Browse files
authored
Merge pull request ethereum#15731 from holiman/revamp_abi
accounts/abi refactor
2 parents 5f8888e + c095c87 commit 9d187f0

File tree

11 files changed

+666
-379
lines changed

11 files changed

+666
-379
lines changed

accounts/abi/abi.go

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package abi
1818

1919
import (
20+
"bytes"
2021
"encoding/json"
2122
"fmt"
2223
"io"
@@ -50,25 +51,25 @@ func JSON(reader io.Reader) (ABI, error) {
5051
// methods string signature. (signature = baz(uint32,string32))
5152
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
5253
// Fetch the ABI of the requested method
53-
var method Method
54-
5554
if name == "" {
56-
method = abi.Constructor
57-
} else {
58-
m, exist := abi.Methods[name]
59-
if !exist {
60-
return nil, fmt.Errorf("method '%s' not found", name)
55+
// constructor
56+
arguments, err := abi.Constructor.Inputs.Pack(args...)
57+
if err != nil {
58+
return nil, err
6159
}
62-
method = m
60+
return arguments, nil
61+
62+
}
63+
method, exist := abi.Methods[name]
64+
if !exist {
65+
return nil, fmt.Errorf("method '%s' not found", name)
6366
}
64-
arguments, err := method.pack(args...)
67+
68+
arguments, err := method.Inputs.Pack(args...)
6569
if err != nil {
6670
return nil, err
6771
}
6872
// Pack up the method ID too if not a constructor and return
69-
if name == "" {
70-
return arguments, nil
71-
}
7273
return append(method.Id(), arguments...), nil
7374
}
7475

@@ -77,28 +78,20 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
7778
if len(output) == 0 {
7879
return fmt.Errorf("abi: unmarshalling empty output")
7980
}
80-
8181
// since there can't be naming collisions with contracts and events,
8282
// we need to decide whether we're calling a method or an event
83-
var unpack unpacker
8483
if method, ok := abi.Methods[name]; ok {
8584
if len(output)%32 != 0 {
8685
return fmt.Errorf("abi: improperly formatted output")
8786
}
88-
unpack = method
87+
return method.Outputs.Unpack(v, output)
8988
} else if event, ok := abi.Events[name]; ok {
90-
unpack = event
91-
} else {
92-
return fmt.Errorf("abi: could not locate named method or event.")
93-
}
94-
95-
// requires a struct to unpack into for a tuple return...
96-
if unpack.isTupleReturn() {
97-
return unpack.tupleUnpack(v, output)
89+
return event.Inputs.Unpack(v, output)
9890
}
99-
return unpack.singleUnpack(v, output)
91+
return fmt.Errorf("abi: could not locate named method or event")
10092
}
10193

94+
// UnmarshalJSON implements json.Unmarshaler interface
10295
func (abi *ABI) UnmarshalJSON(data []byte) error {
10396
var fields []struct {
10497
Type string
@@ -141,3 +134,14 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
141134

142135
return nil
143136
}
137+
138+
// MethodById looks up a method by the 4-byte id
139+
// returns nil if none found
140+
func (abi *ABI) MethodById(sigdata []byte) *Method {
141+
for _, method := range abi.Methods {
142+
if bytes.Equal(method.Id(), sigdata[:4]) {
143+
return &method
144+
}
145+
}
146+
return nil
147+
}

accounts/abi/abi_test.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ import (
2222
"fmt"
2323
"log"
2424
"math/big"
25-
"reflect"
2625
"strings"
2726
"testing"
2827

28+
"reflect"
29+
2930
"github.com/ethereum/go-ethereum/common"
3031
"github.com/ethereum/go-ethereum/crypto"
3132
)
@@ -75,9 +76,24 @@ func TestReader(t *testing.T) {
7576
}
7677

7778
// deep equal fails for some reason
78-
t.Skip()
79-
if !reflect.DeepEqual(abi, exp) {
80-
t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp)
79+
for name, expM := range exp.Methods {
80+
gotM, exist := abi.Methods[name]
81+
if !exist {
82+
t.Errorf("Missing expected method %v", name)
83+
}
84+
if !reflect.DeepEqual(gotM, expM) {
85+
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
86+
}
87+
}
88+
89+
for name, gotM := range abi.Methods {
90+
expM, exist := exp.Methods[name]
91+
if !exist {
92+
t.Errorf("Found extra method %v", name)
93+
}
94+
if !reflect.DeepEqual(gotM, expM) {
95+
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
96+
}
8197
}
8298
}
8399

@@ -641,3 +657,42 @@ func TestUnpackEvent(t *testing.T) {
641657
t.Logf("len(data): %d; received event: %+v", len(data), ev)
642658
}
643659
}
660+
661+
func TestABI_MethodById(t *testing.T) {
662+
const abiJSON = `[
663+
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
664+
{"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]},
665+
{"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]},
666+
{"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]},
667+
{"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]},
668+
{"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]},
669+
{"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]},
670+
{"type":"function","name":"balance","constant":true},
671+
{"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]},
672+
{"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]},
673+
{"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]},
674+
{"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]},
675+
{"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]},
676+
{"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]},
677+
{"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]},
678+
{"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]},
679+
{"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]},
680+
{"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]},
681+
{"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]},
682+
{"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]},
683+
{"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]}
684+
]
685+
`
686+
abi, err := JSON(strings.NewReader(abiJSON))
687+
if err != nil {
688+
t.Fatal(err)
689+
}
690+
for name, m := range abi.Methods {
691+
a := fmt.Sprintf("%v", m)
692+
b := fmt.Sprintf("%v", abi.MethodById(m.Id()))
693+
if a != b {
694+
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
695+
}
696+
}
697+
698+
}

accounts/abi/argument.go

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package abi
1919
import (
2020
"encoding/json"
2121
"fmt"
22+
"reflect"
23+
"strings"
2224
)
2325

2426
// Argument holds the name of the argument and the corresponding type.
@@ -29,7 +31,10 @@ type Argument struct {
2931
Indexed bool // indexed is only used by events
3032
}
3133

32-
func (a *Argument) UnmarshalJSON(data []byte) error {
34+
type Arguments []Argument
35+
36+
// UnmarshalJSON implements json.Unmarshaler interface
37+
func (argument *Argument) UnmarshalJSON(data []byte) error {
3338
var extarg struct {
3439
Name string
3540
Type string
@@ -40,12 +45,180 @@ func (a *Argument) UnmarshalJSON(data []byte) error {
4045
return fmt.Errorf("argument json err: %v", err)
4146
}
4247

43-
a.Type, err = NewType(extarg.Type)
48+
argument.Type, err = NewType(extarg.Type)
4449
if err != nil {
4550
return err
4651
}
47-
a.Name = extarg.Name
48-
a.Indexed = extarg.Indexed
52+
argument.Name = extarg.Name
53+
argument.Indexed = extarg.Indexed
4954

5055
return nil
5156
}
57+
58+
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
59+
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
60+
func (arguments Arguments) LengthNonIndexed() int {
61+
out := 0
62+
for _, arg := range arguments {
63+
if !arg.Indexed {
64+
out++
65+
}
66+
}
67+
return out
68+
}
69+
70+
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
71+
func (arguments Arguments) isTuple() bool {
72+
return len(arguments) > 1
73+
}
74+
75+
// Unpack performs the operation hexdata -> Go format
76+
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
77+
if arguments.isTuple() {
78+
return arguments.unpackTuple(v, data)
79+
}
80+
return arguments.unpackAtomic(v, data)
81+
}
82+
83+
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
84+
// make sure the passed value is arguments pointer
85+
valueOf := reflect.ValueOf(v)
86+
if reflect.Ptr != valueOf.Kind() {
87+
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
88+
}
89+
90+
var (
91+
value = valueOf.Elem()
92+
typ = value.Type()
93+
kind = value.Kind()
94+
)
95+
96+
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
97+
return err
98+
}
99+
// `i` counts the nonindexed arguments.
100+
// `j` counts the number of complex types.
101+
// both `i` and `j` are used to to correctly compute `data` offset.
102+
103+
i, j := -1, 0
104+
for _, arg := range arguments {
105+
106+
if arg.Indexed {
107+
// can't read, continue
108+
continue
109+
}
110+
i++
111+
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
112+
if err != nil {
113+
return err
114+
}
115+
116+
if arg.Type.T == ArrayTy {
117+
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
118+
// we need to decrement 'j' because 'i' was incremented
119+
j += arg.Type.Size - 1
120+
}
121+
122+
reflectValue := reflect.ValueOf(marshalledValue)
123+
124+
switch kind {
125+
case reflect.Struct:
126+
for j := 0; j < typ.NumField(); j++ {
127+
field := typ.Field(j)
128+
// TODO read tags: `abi:"fieldName"`
129+
if field.Name == strings.ToUpper(arg.Name[:1])+arg.Name[1:] {
130+
if err := set(value.Field(j), reflectValue, arg); err != nil {
131+
return err
132+
}
133+
}
134+
}
135+
case reflect.Slice, reflect.Array:
136+
if value.Len() < i {
137+
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
138+
}
139+
v := value.Index(i)
140+
if err := requireAssignable(v, reflectValue); err != nil {
141+
return err
142+
}
143+
144+
if err := set(v.Elem(), reflectValue, arg); err != nil {
145+
return err
146+
}
147+
default:
148+
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
149+
}
150+
}
151+
return nil
152+
}
153+
154+
// unpackAtomic unpacks ( hexdata -> go ) a single value
155+
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
156+
// make sure the passed value is arguments pointer
157+
valueOf := reflect.ValueOf(v)
158+
if reflect.Ptr != valueOf.Kind() {
159+
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
160+
}
161+
arg := arguments[0]
162+
if arg.Indexed {
163+
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
164+
}
165+
166+
value := valueOf.Elem()
167+
168+
marshalledValue, err := toGoType(0, arg.Type, output)
169+
if err != nil {
170+
return err
171+
}
172+
return set(value, reflect.ValueOf(marshalledValue), arg)
173+
}
174+
175+
// Unpack performs the operation Go format -> Hexdata
176+
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
177+
// Make sure arguments match up and pack them
178+
abiArgs := arguments
179+
if len(args) != len(abiArgs) {
180+
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
181+
}
182+
183+
// variable input is the output appended at the end of packed
184+
// output. This is used for strings and bytes types input.
185+
var variableInput []byte
186+
187+
// input offset is the bytes offset for packed output
188+
inputOffset := 0
189+
for _, abiArg := range abiArgs {
190+
if abiArg.Type.T == ArrayTy {
191+
inputOffset += (32 * abiArg.Type.Size)
192+
} else {
193+
inputOffset += 32
194+
}
195+
}
196+
197+
var ret []byte
198+
for i, a := range args {
199+
input := abiArgs[i]
200+
// pack the input
201+
packed, err := input.Type.pack(reflect.ValueOf(a))
202+
if err != nil {
203+
return nil, err
204+
}
205+
206+
// check for a slice type (string, bytes, slice)
207+
if input.Type.requiresLengthPrefix() {
208+
// calculate the offset
209+
offset := inputOffset + len(variableInput)
210+
// set the offset
211+
ret = append(ret, packNum(reflect.ValueOf(offset))...)
212+
// Append the packed output to the variable input. The variable input
213+
// will be appended at the end of the input.
214+
variableInput = append(variableInput, packed...)
215+
} else {
216+
// append the packed value to the input
217+
ret = append(ret, packed...)
218+
}
219+
}
220+
// append the variable input at the end of the packed input
221+
ret = append(ret, variableInput...)
222+
223+
return ret, nil
224+
}

0 commit comments

Comments
 (0)