Skip to content

Commit b5606fa

Browse files
committed
accounts/abi: add basic support for error types (ethereum#23161)
This is the initial step for support of Solidity errors in contract bindings. As of this change, errors can be decoded, but are not supported in bindings yet. Closes ethereum#23157
1 parent d010f96 commit b5606fa

File tree

5 files changed

+230
-79
lines changed

5 files changed

+230
-79
lines changed

accounts/abi/abi.go

+23-32
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type ABI struct {
3434
Constructor Method
3535
Methods map[string]Method
3636
Events map[string]Event
37+
Errors map[string]Error
3738

3839
// Additional "special" functions introduced in solidity v0.6.0.
3940
// It's separated from the original default fallback. Each contract
@@ -157,12 +158,13 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
157158
}
158159
abi.Methods = make(map[string]Method)
159160
abi.Events = make(map[string]Event)
161+
abi.Errors = make(map[string]Error)
160162
for _, field := range fields {
161163
switch field.Type {
162164
case "constructor":
163165
abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil)
164166
case "function":
165-
name := abi.overloadedMethodName(field.Name)
167+
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok })
166168
abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs)
167169
case "fallback":
168170
// New introduced function type in v0.6.0, check more detail
@@ -182,45 +184,17 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
182184
}
183185
abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil)
184186
case "event":
185-
name := abi.overloadedEventName(field.Name)
187+
name := overloadedName(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok })
186188
abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs)
189+
case "error":
190+
abi.Errors[field.Name] = NewError(field.Name, field.Inputs)
187191
default:
188192
return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name)
189193
}
190194
}
191195
return nil
192196
}
193197

194-
// overloadedMethodName returns the next available name for a given function.
195-
// Needed since solidity allows for function overload.
196-
//
197-
// e.g. if the abi contains Methods send, send1
198-
// overloadedMethodName would return send2 for input send.
199-
func (abi *ABI) overloadedMethodName(rawName string) string {
200-
name := rawName
201-
_, ok := abi.Methods[name]
202-
for idx := 0; ok; idx++ {
203-
name = fmt.Sprintf("%s%d", rawName, idx)
204-
_, ok = abi.Methods[name]
205-
}
206-
return name
207-
}
208-
209-
// overloadedEventName returns the next available name for a given event.
210-
// Needed since solidity allows for event overload.
211-
//
212-
// e.g. if the abi contains events received, received1
213-
// overloadedEventName would return received2 for input received.
214-
func (abi *ABI) overloadedEventName(rawName string) string {
215-
name := rawName
216-
_, ok := abi.Events[name]
217-
for idx := 0; ok; idx++ {
218-
name = fmt.Sprintf("%s%d", rawName, idx)
219-
_, ok = abi.Events[name]
220-
}
221-
return name
222-
}
223-
224198
// MethodById looks up a method by the 4-byte id,
225199
// returns nil if none found.
226200
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
@@ -277,3 +251,20 @@ func UnpackRevert(data []byte) (string, error) {
277251
}
278252
return unpacked[0].(string), nil
279253
}
254+
255+
// overloadedName returns the next available name for a given thing.
256+
// Needed since solidity allows for overloading.
257+
//
258+
// e.g. if the abi contains Methods send, send1
259+
// overloadedName would return send2 for input send.
260+
//
261+
// overloadedName works for methods, events and errors.
262+
func overloadedName(rawName string, isAvail func(string) bool) string {
263+
name := rawName
264+
ok := isAvail(name)
265+
for idx := 0; ok; idx++ {
266+
name = fmt.Sprintf("%s%d", rawName, idx)
267+
ok = isAvail(name)
268+
}
269+
return name
270+
}

accounts/abi/abi_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,20 @@ func TestOverloadedMethodSignature(t *testing.T) {
295295
check("bar0", "bar(uint256,uint256)", false)
296296
}
297297

298+
func TestCustomErrors(t *testing.T) {
299+
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
300+
abi, err := JSON(strings.NewReader(json))
301+
if err != nil {
302+
t.Fatal(err)
303+
}
304+
check := func(name string, expect string) {
305+
if abi.Errors[name].Sig != expect {
306+
t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig)
307+
}
308+
}
309+
check("MyError", "MyError(uint256)")
310+
}
311+
298312
func TestMultiPack(t *testing.T) {
299313
abi, err := JSON(strings.NewReader(jsondata))
300314
if err != nil {

accounts/abi/bind/bind_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,61 @@ var bindTests = []struct {
18021802
if count != 1 {
18031803
t.Fatal("Unexpected contract event number")
18041804
}
1805+
`,
1806+
nil,
1807+
nil,
1808+
nil,
1809+
nil,
1810+
},
1811+
// Test errors introduced in v0.8.4
1812+
{
1813+
`NewErrors`,
1814+
`
1815+
pragma solidity >0.8.4;
1816+
1817+
contract NewErrors {
1818+
error MyError(uint256);
1819+
error MyError1(uint256);
1820+
error MyError2(uint256, uint256);
1821+
error MyError3(uint256 a, uint256 b, uint256 c);
1822+
function Error() public pure {
1823+
revert MyError3(1,2,3);
1824+
}
1825+
}
1826+
`,
1827+
[]string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"},
1828+
[]string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`},
1829+
`
1830+
"math/big"
1831+
1832+
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
1833+
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
1834+
"github.com/XinFinOrg/XDPoSChain/core"
1835+
"github.com/XinFinOrg/XDPoSChain/crypto"
1836+
"github.com/XinFinOrg/XDPoSChain/params"
1837+
`,
1838+
`
1839+
var (
1840+
key, _ = crypto.GenerateKey()
1841+
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
1842+
sim = backends.NewXDCSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, 10000000, params.TestXDPoSMockChainConfig)
1843+
)
1844+
defer sim.Close()
1845+
1846+
_, tx, contract, err := DeployNewErrors(user, sim)
1847+
if err != nil {
1848+
t.Fatal(err)
1849+
}
1850+
sim.Commit()
1851+
_, err = bind.WaitDeployed(nil, sim, tx)
1852+
if err != nil {
1853+
t.Error(err)
1854+
}
1855+
if err := contract.Error(new(bind.CallOpts)); err == nil {
1856+
t.Fatalf("expected contract to throw error")
1857+
}
1858+
// TODO (MariusVanDerWijden unpack error using abigen
1859+
// once that is implemented
18051860
`,
18061861
nil,
18071862
nil,

accounts/abi/error.go

+56-47
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2016 The go-ethereum Authors
1+
// Copyright 2021 The go-ethereum Authors
22
// This file is part of the go-ethereum library.
33
//
44
// The go-ethereum library is free software: you can redistribute it and/or modify
@@ -17,66 +17,75 @@
1717
package abi
1818

1919
import (
20+
"bytes"
2021
"errors"
2122
"fmt"
22-
"reflect"
23-
)
23+
"strings"
2424

25-
var (
26-
errBadBool = errors.New("abi: improperly encoded boolean value")
25+
"github.com/XinFinOrg/XDPoSChain/common"
26+
"github.com/XinFinOrg/XDPoSChain/crypto"
2727
)
2828

29-
// formatSliceString formats the reflection kind with the given slice size
30-
// and returns a formatted string representation.
31-
func formatSliceString(kind reflect.Kind, sliceSize int) string {
32-
if sliceSize == -1 {
33-
return fmt.Sprintf("[]%v", kind)
34-
}
35-
return fmt.Sprintf("[%d]%v", sliceSize, kind)
29+
type Error struct {
30+
Name string
31+
Inputs Arguments
32+
str string
33+
// Sig contains the string signature according to the ABI spec.
34+
// e.g. event foo(uint32 a, int b) = "foo(uint32,int256)"
35+
// Please note that "int" is substitute for its canonical representation "int256"
36+
Sig string
37+
// ID returns the canonical representation of the event's signature used by the
38+
// abi definition to identify event names and types.
39+
ID common.Hash
3640
}
3741

38-
// sliceTypeCheck checks that the given slice can by assigned to the reflection
39-
// type in t.
40-
func sliceTypeCheck(t Type, val reflect.Value) error {
41-
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
42-
return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type())
42+
func NewError(name string, inputs Arguments) Error {
43+
// sanitize inputs to remove inputs without names
44+
// and precompute string and sig representation.
45+
names := make([]string, len(inputs))
46+
types := make([]string, len(inputs))
47+
for i, input := range inputs {
48+
if input.Name == "" {
49+
inputs[i] = Argument{
50+
Name: fmt.Sprintf("arg%d", i),
51+
Indexed: input.Indexed,
52+
Type: input.Type,
53+
}
54+
} else {
55+
inputs[i] = input
56+
}
57+
// string representation
58+
names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name)
59+
if input.Indexed {
60+
names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name)
61+
}
62+
// sig representation
63+
types[i] = input.Type.String()
4364
}
4465

45-
if t.T == ArrayTy && val.Len() != t.Size {
46-
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
47-
}
66+
str := fmt.Sprintf("error %v(%v)", name, strings.Join(names, ", "))
67+
sig := fmt.Sprintf("%v(%v)", name, strings.Join(types, ","))
68+
id := common.BytesToHash(crypto.Keccak256([]byte(sig)))
4869

49-
if t.Elem.T == SliceTy || t.Elem.T == ArrayTy {
50-
if val.Len() > 0 {
51-
return sliceTypeCheck(*t.Elem, val.Index(0))
52-
}
70+
return Error{
71+
Name: name,
72+
Inputs: inputs,
73+
str: str,
74+
Sig: sig,
75+
ID: id,
5376
}
77+
}
5478

55-
if val.Type().Elem().Kind() != t.Elem.GetType().Kind() {
56-
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type())
57-
}
58-
return nil
79+
func (e *Error) String() string {
80+
return e.str
5981
}
6082

61-
// typeCheck checks that the given reflection value can be assigned to the reflection
62-
// type in t.
63-
func typeCheck(t Type, value reflect.Value) error {
64-
if t.T == SliceTy || t.T == ArrayTy {
65-
return sliceTypeCheck(t, value)
83+
func (e *Error) Unpack(data []byte) (interface{}, error) {
84+
if len(data) < 4 {
85+
return "", errors.New("invalid data for unpacking")
6686
}
67-
68-
// Check base type validity. Element types will be checked later on.
69-
if t.GetType().Kind() != value.Kind() {
70-
return typeErr(t.GetType().Kind(), value.Kind())
71-
} else if t.T == FixedBytesTy && t.Size != value.Len() {
72-
return typeErr(t.GetType(), value.Type())
73-
} else {
74-
return nil
87+
if !bytes.Equal(data[:4], e.ID[:4]) {
88+
return "", errors.New("invalid data for unpacking")
7589
}
76-
77-
}
78-
79-
// typeErr returns a formatted type casting error.
80-
func typeErr(expected, got interface{}) error {
81-
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected)
90+
return e.Inputs.Unpack(data[4:])
8291
}

accounts/abi/error_handling.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2016 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package abi
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"reflect"
23+
)
24+
25+
var (
26+
errBadBool = errors.New("abi: improperly encoded boolean value")
27+
)
28+
29+
// formatSliceString formats the reflection kind with the given slice size
30+
// and returns a formatted string representation.
31+
func formatSliceString(kind reflect.Kind, sliceSize int) string {
32+
if sliceSize == -1 {
33+
return fmt.Sprintf("[]%v", kind)
34+
}
35+
return fmt.Sprintf("[%d]%v", sliceSize, kind)
36+
}
37+
38+
// sliceTypeCheck checks that the given slice can by assigned to the reflection
39+
// type in t.
40+
func sliceTypeCheck(t Type, val reflect.Value) error {
41+
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
42+
return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type())
43+
}
44+
45+
if t.T == ArrayTy && val.Len() != t.Size {
46+
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
47+
}
48+
49+
if t.Elem.T == SliceTy || t.Elem.T == ArrayTy {
50+
if val.Len() > 0 {
51+
return sliceTypeCheck(*t.Elem, val.Index(0))
52+
}
53+
}
54+
55+
if val.Type().Elem().Kind() != t.Elem.GetType().Kind() {
56+
return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type())
57+
}
58+
return nil
59+
}
60+
61+
// typeCheck checks that the given reflection value can be assigned to the reflection
62+
// type in t.
63+
func typeCheck(t Type, value reflect.Value) error {
64+
if t.T == SliceTy || t.T == ArrayTy {
65+
return sliceTypeCheck(t, value)
66+
}
67+
68+
// Check base type validity. Element types will be checked later on.
69+
if t.GetType().Kind() != value.Kind() {
70+
return typeErr(t.GetType().Kind(), value.Kind())
71+
} else if t.T == FixedBytesTy && t.Size != value.Len() {
72+
return typeErr(t.GetType(), value.Type())
73+
} else {
74+
return nil
75+
}
76+
77+
}
78+
79+
// typeErr returns a formatted type casting error.
80+
func typeErr(expected, got interface{}) error {
81+
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected)
82+
}

0 commit comments

Comments
 (0)