Skip to content

Commit 00064dd

Browse files
accounts/abi: implement new fallback functions (#20764)
* accounts/abi: implement new fackball functions In Solidity v0.6.0, the original fallback is separated into two different sub types: fallback and receive. This PR addes the support for parsing new format abi and the relevant abigen functionalities. * accounts/abi: fix unit tests * accounts/abi: minor fixes * accounts/abi, mobile: support jave binding * accounts/abi: address marius's comment * accounts/abi: Work around the uin64 conversion issue Co-authored-by: Guillaume Ballet <[email protected]>
1 parent 2a836bb commit 00064dd

File tree

12 files changed

+445
-94
lines changed

12 files changed

+445
-94
lines changed

accounts/abi/abi.go

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package abi
1919
import (
2020
"bytes"
2121
"encoding/json"
22+
"errors"
2223
"fmt"
2324
"io"
2425

@@ -32,6 +33,12 @@ type ABI struct {
3233
Constructor Method
3334
Methods map[string]Method
3435
Events map[string]Event
36+
37+
// Additional "special" functions introduced in solidity v0.6.0.
38+
// It's separated from the original default fallback. Each contract
39+
// can only define one fallback and receive function.
40+
Fallback Method // Note it's also used to represent legacy fallback before v0.6.0
41+
Receive Method
3542
}
3643

3744
// JSON returns a parsed ABI interface and error if it failed.
@@ -42,7 +49,6 @@ func JSON(reader io.Reader) (ABI, error) {
4249
if err := dec.Decode(&abi); err != nil {
4350
return ABI{}, err
4451
}
45-
4652
return abi, nil
4753
}
4854

@@ -108,13 +114,22 @@ func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte)
108114
// UnmarshalJSON implements json.Unmarshaler interface
109115
func (abi *ABI) UnmarshalJSON(data []byte) error {
110116
var fields []struct {
111-
Type string
112-
Name string
113-
Constant bool
117+
Type string
118+
Name string
119+
Inputs []Argument
120+
Outputs []Argument
121+
122+
// Status indicator which can be: "pure", "view",
123+
// "nonpayable" or "payable".
114124
StateMutability string
115-
Anonymous bool
116-
Inputs []Argument
117-
Outputs []Argument
125+
126+
// Deprecated Status indicators, but removed in v0.6.0.
127+
Constant bool // True if function is either pure or view
128+
Payable bool // True if function is payable
129+
130+
// Event relevant indicator represents the event is
131+
// declared as anonymous.
132+
Anonymous bool
118133
}
119134
if err := json.Unmarshal(data, &fields); err != nil {
120135
return err
@@ -126,22 +141,82 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
126141
case "constructor":
127142
abi.Constructor = Method{
128143
Inputs: field.Inputs,
144+
145+
// Note for constructor the `StateMutability` can only
146+
// be payable or nonpayable according to the output of
147+
// compiler. So constant is always false.
148+
StateMutability: field.StateMutability,
149+
150+
// Legacy fields, keep them for backward compatibility
151+
Constant: field.Constant,
152+
Payable: field.Payable,
129153
}
130-
// empty defaults to function according to the abi spec
131-
case "function", "":
154+
case "function":
132155
name := field.Name
133156
_, ok := abi.Methods[name]
134157
for idx := 0; ok; idx++ {
135158
name = fmt.Sprintf("%s%d", field.Name, idx)
136159
_, ok = abi.Methods[name]
137160
}
138-
isConst := field.Constant || field.StateMutability == "pure" || field.StateMutability == "view"
139161
abi.Methods[name] = Method{
140-
Name: name,
141-
RawName: field.Name,
142-
Const: isConst,
143-
Inputs: field.Inputs,
144-
Outputs: field.Outputs,
162+
Name: name,
163+
RawName: field.Name,
164+
StateMutability: field.StateMutability,
165+
Inputs: field.Inputs,
166+
Outputs: field.Outputs,
167+
168+
// Legacy fields, keep them for backward compatibility
169+
Constant: field.Constant,
170+
Payable: field.Payable,
171+
}
172+
case "fallback":
173+
// New introduced function type in v0.6.0, check more detail
174+
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
175+
if abi.HasFallback() {
176+
return errors.New("only single fallback is allowed")
177+
}
178+
abi.Fallback = Method{
179+
Name: "",
180+
RawName: "",
181+
182+
// The `StateMutability` can only be payable or nonpayable,
183+
// so the constant is always false.
184+
StateMutability: field.StateMutability,
185+
IsFallback: true,
186+
187+
// Fallback doesn't have any input or output
188+
Inputs: nil,
189+
Outputs: nil,
190+
191+
// Legacy fields, keep them for backward compatibility
192+
Constant: field.Constant,
193+
Payable: field.Payable,
194+
}
195+
case "receive":
196+
// New introduced function type in v0.6.0, check more detail
197+
// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
198+
if abi.HasReceive() {
199+
return errors.New("only single receive is allowed")
200+
}
201+
if field.StateMutability != "payable" {
202+
return errors.New("the statemutability of receive can only be payable")
203+
}
204+
abi.Receive = Method{
205+
Name: "",
206+
RawName: "",
207+
208+
// The `StateMutability` can only be payable, so constant
209+
// is always true while payable is always false.
210+
StateMutability: field.StateMutability,
211+
IsReceive: true,
212+
213+
// Receive doesn't have any input or output
214+
Inputs: nil,
215+
Outputs: nil,
216+
217+
// Legacy fields, keep them for backward compatibility
218+
Constant: field.Constant,
219+
Payable: field.Payable,
145220
}
146221
case "event":
147222
name := field.Name
@@ -158,7 +233,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
158233
}
159234
}
160235
}
161-
162236
return nil
163237
}
164238

@@ -186,3 +260,13 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
186260
}
187261
return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
188262
}
263+
264+
// HasFallback returns an indicator whether a fallback function is included.
265+
func (abi *ABI) HasFallback() bool {
266+
return abi.Fallback.IsFallback
267+
}
268+
269+
// HasReceive returns an indicator whether a receive function is included.
270+
func (abi *ABI) HasReceive() bool {
271+
return abi.Receive.IsReceive
272+
}

accounts/abi/abi_test.go

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,40 +31,40 @@ import (
3131

3232
const jsondata = `
3333
[
34-
{ "type" : "function", "name" : "balance", "constant" : true },
35-
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
34+
{ "type" : "function", "name" : "balance", "stateMutability" : "view" },
35+
{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
3636
]`
3737

3838
const jsondata2 = `
3939
[
40-
{ "type" : "function", "name" : "balance", "constant" : true },
41-
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
42-
{ "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
43-
{ "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
44-
{ "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
45-
{ "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
46-
{ "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
47-
{ "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
48-
{ "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
49-
{ "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
50-
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
51-
{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
52-
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
53-
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
54-
{ "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
55-
{ "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
56-
{ "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }
40+
{ "type" : "function", "name" : "balance", "stateMutability" : "view" },
41+
{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
42+
{ "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
43+
{ "type" : "function", "name" : "string", "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
44+
{ "type" : "function", "name" : "bool", "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
45+
{ "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
46+
{ "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
47+
{ "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
48+
{ "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
49+
{ "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
50+
{ "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
51+
{ "type" : "function", "name" : "slice256", "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
52+
{ "type" : "function", "name" : "sliceAddress", "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
53+
{ "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
54+
{ "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
55+
{ "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
56+
{ "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }
5757
]`
5858

5959
func TestReader(t *testing.T) {
6060
Uint256, _ := NewType("uint256", "", nil)
6161
exp := ABI{
6262
Methods: map[string]Method{
6363
"balance": {
64-
"balance", "balance", true, nil, nil,
64+
"balance", "balance", "view", false, false, false, false, nil, nil,
6565
},
6666
"send": {
67-
"send", "send", false, []Argument{
67+
"send", "send", "", false, false, false, false, []Argument{
6868
{"amount", Uint256, false},
6969
}, nil,
7070
},
@@ -173,7 +173,7 @@ func TestTestSlice(t *testing.T) {
173173

174174
func TestMethodSignature(t *testing.T) {
175175
String, _ := NewType("string", "", nil)
176-
m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
176+
m := Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
177177
exp := "foo(string,string)"
178178
if m.Sig() != exp {
179179
t.Error("signature mismatch", exp, "!=", m.Sig())
@@ -185,7 +185,7 @@ func TestMethodSignature(t *testing.T) {
185185
}
186186

187187
uintt, _ := NewType("uint256", "", nil)
188-
m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil}
188+
m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", uintt, false}}, nil}
189189
exp = "foo(uint256)"
190190
if m.Sig() != exp {
191191
t.Error("signature mismatch", exp, "!=", m.Sig())
@@ -204,7 +204,7 @@ func TestMethodSignature(t *testing.T) {
204204
{Name: "y", Type: "int256"},
205205
}},
206206
})
207-
m = Method{"foo", "foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil}
207+
m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil}
208208
exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)"
209209
if m.Sig() != exp {
210210
t.Error("signature mismatch", exp, "!=", m.Sig())
@@ -582,7 +582,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
582582
}
583583

584584
func TestDefaultFunctionParsing(t *testing.T) {
585-
const definition = `[{ "name" : "balance" }]`
585+
const definition = `[{ "name" : "balance", "type" : "function" }]`
586586

587587
abi, err := JSON(strings.NewReader(definition))
588588
if err != nil {

accounts/abi/bind/base.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,24 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
171171
if err != nil {
172172
return nil, err
173173
}
174+
// todo(rjl493456442) check the method is payable or not,
175+
// reject invalid transaction at the first place
174176
return c.transact(opts, &c.address, input)
175177
}
176178

179+
// RawTransact initiates a transaction with the given raw calldata as the input.
180+
// It's usually used to initiates transaction for invoking **Fallback** function.
181+
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
182+
// todo(rjl493456442) check the method is payable or not,
183+
// reject invalid transaction at the first place
184+
return c.transact(opts, &c.address, calldata)
185+
}
186+
177187
// Transfer initiates a plain transaction to move funds to the contract, calling
178188
// its default method if one is available.
179189
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
190+
// todo(rjl493456442) check the payable fallback or receive is defined
191+
// or not, reject invalid transaction at the first place
180192
return c.transact(opts, &c.address, nil)
181193
}
182194

accounts/abi/bind/bind.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
7777
calls = make(map[string]*tmplMethod)
7878
transacts = make(map[string]*tmplMethod)
7979
events = make(map[string]*tmplEvent)
80+
fallback *tmplMethod
81+
receive *tmplMethod
8082

8183
// identifiers are used to detect duplicated identifier of function
8284
// and event. For all calls, transacts and events, abigen will generate
@@ -92,7 +94,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
9294
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
9395
// Ensure there is no duplicated identifier
9496
var identifiers = callIdentifiers
95-
if !original.Const {
97+
if !original.IsConstant() {
9698
identifiers = transactIdentifiers
9799
}
98100
if identifiers[normalizedName] {
@@ -121,7 +123,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
121123
}
122124
}
123125
// Append the methods to the call or transact lists
124-
if original.Const {
126+
if original.IsConstant() {
125127
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
126128
} else {
127129
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
@@ -156,7 +158,13 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
156158
// Append the event to the accumulator list
157159
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
158160
}
159-
161+
// Add two special fallback functions if they exist
162+
if evmABI.HasFallback() {
163+
fallback = &tmplMethod{Original: evmABI.Fallback}
164+
}
165+
if evmABI.HasReceive() {
166+
receive = &tmplMethod{Original: evmABI.Receive}
167+
}
160168
// There is no easy way to pass arbitrary java objects to the Go side.
161169
if len(structs) > 0 && lang == LangJava {
162170
return "", errors.New("java binding for tuple arguments is not supported yet")
@@ -169,6 +177,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
169177
Constructor: evmABI.Constructor,
170178
Calls: calls,
171179
Transacts: transacts,
180+
Fallback: fallback,
181+
Receive: receive,
172182
Events: events,
173183
Libraries: make(map[string]string),
174184
}
@@ -619,11 +629,22 @@ func formatMethod(method abi.Method, structs map[string]*tmplStruct) string {
619629
outputs[i] += fmt.Sprintf(" %v", output.Name)
620630
}
621631
}
622-
constant := ""
623-
if method.Const {
624-
constant = "constant "
632+
// Extract meaningful state mutability of solidity method.
633+
// If it's default value, never print it.
634+
state := method.StateMutability
635+
if state == "nonpayable" {
636+
state = ""
637+
}
638+
if state != "" {
639+
state = state + " "
640+
}
641+
identity := fmt.Sprintf("function %v", method.RawName)
642+
if method.IsFallback {
643+
identity = "fallback"
644+
} else if method.IsReceive {
645+
identity = "receive"
625646
}
626-
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
647+
return fmt.Sprintf("%s(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", "))
627648
}
628649

629650
// formatEvent transforms raw event representation into a user friendly one.

0 commit comments

Comments
 (0)