Explorar el Código

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 <gballet@gmail.com>
gary rong hace 5 años
padre
commit
00064ddcfb

+ 100 - 16
accounts/abi/abi.go

@@ -19,6 +19,7 @@ package abi
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 
@@ -32,6 +33,12 @@ type ABI struct {
 	Constructor Method
 	Methods     map[string]Method
 	Events      map[string]Event
+
+	// Additional "special" functions introduced in solidity v0.6.0.
+	// It's separated from the original default fallback. Each contract
+	// can only define one fallback and receive function.
+	Fallback Method // Note it's also used to represent legacy fallback before v0.6.0
+	Receive  Method
 }
 
 // JSON returns a parsed ABI interface and error if it failed.
@@ -42,7 +49,6 @@ func JSON(reader io.Reader) (ABI, error) {
 	if err := dec.Decode(&abi); err != nil {
 		return ABI{}, err
 	}
-
 	return abi, nil
 }
 
@@ -108,13 +114,22 @@ func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte)
 // UnmarshalJSON implements json.Unmarshaler interface
 func (abi *ABI) UnmarshalJSON(data []byte) error {
 	var fields []struct {
-		Type            string
-		Name            string
-		Constant        bool
+		Type    string
+		Name    string
+		Inputs  []Argument
+		Outputs []Argument
+
+		// Status indicator which can be: "pure", "view",
+		// "nonpayable" or "payable".
 		StateMutability string
-		Anonymous       bool
-		Inputs          []Argument
-		Outputs         []Argument
+
+		// Deprecated Status indicators, but removed in v0.6.0.
+		Constant bool // True if function is either pure or view
+		Payable  bool // True if function is payable
+
+		// Event relevant indicator represents the event is
+		// declared as anonymous.
+		Anonymous bool
 	}
 	if err := json.Unmarshal(data, &fields); err != nil {
 		return err
@@ -126,22 +141,82 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
 		case "constructor":
 			abi.Constructor = Method{
 				Inputs: field.Inputs,
+
+				// Note for constructor the `StateMutability` can only
+				// be payable or nonpayable according to the output of
+				// compiler. So constant is always false.
+				StateMutability: field.StateMutability,
+
+				// Legacy fields, keep them for backward compatibility
+				Constant: field.Constant,
+				Payable:  field.Payable,
 			}
-		// empty defaults to function according to the abi spec
-		case "function", "":
+		case "function":
 			name := field.Name
 			_, ok := abi.Methods[name]
 			for idx := 0; ok; idx++ {
 				name = fmt.Sprintf("%s%d", field.Name, idx)
 				_, ok = abi.Methods[name]
 			}
-			isConst := field.Constant || field.StateMutability == "pure" || field.StateMutability == "view"
 			abi.Methods[name] = Method{
-				Name:    name,
-				RawName: field.Name,
-				Const:   isConst,
-				Inputs:  field.Inputs,
-				Outputs: field.Outputs,
+				Name:            name,
+				RawName:         field.Name,
+				StateMutability: field.StateMutability,
+				Inputs:          field.Inputs,
+				Outputs:         field.Outputs,
+
+				// Legacy fields, keep them for backward compatibility
+				Constant: field.Constant,
+				Payable:  field.Payable,
+			}
+		case "fallback":
+			// New introduced function type in v0.6.0, check more detail
+			// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
+			if abi.HasFallback() {
+				return errors.New("only single fallback is allowed")
+			}
+			abi.Fallback = Method{
+				Name:    "",
+				RawName: "",
+
+				// The `StateMutability` can only be payable or nonpayable,
+				// so the constant is always false.
+				StateMutability: field.StateMutability,
+				IsFallback:      true,
+
+				// Fallback doesn't have any input or output
+				Inputs:  nil,
+				Outputs: nil,
+
+				// Legacy fields, keep them for backward compatibility
+				Constant: field.Constant,
+				Payable:  field.Payable,
+			}
+		case "receive":
+			// New introduced function type in v0.6.0, check more detail
+			// here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function
+			if abi.HasReceive() {
+				return errors.New("only single receive is allowed")
+			}
+			if field.StateMutability != "payable" {
+				return errors.New("the statemutability of receive can only be payable")
+			}
+			abi.Receive = Method{
+				Name:    "",
+				RawName: "",
+
+				// The `StateMutability` can only be payable, so constant
+				// is always true while payable is always false.
+				StateMutability: field.StateMutability,
+				IsReceive:       true,
+
+				// Receive doesn't have any input or output
+				Inputs:  nil,
+				Outputs: nil,
+
+				// Legacy fields, keep them for backward compatibility
+				Constant: field.Constant,
+				Payable:  field.Payable,
 			}
 		case "event":
 			name := field.Name
@@ -158,7 +233,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
 			}
 		}
 	}
-
 	return nil
 }
 
@@ -186,3 +260,13 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
 	}
 	return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
 }
+
+// HasFallback returns an indicator whether a fallback function is included.
+func (abi *ABI) HasFallback() bool {
+	return abi.Fallback.IsFallback
+}
+
+// HasReceive returns an indicator whether a receive function is included.
+func (abi *ABI) HasReceive() bool {
+	return abi.Receive.IsReceive
+}

+ 25 - 25
accounts/abi/abi_test.go

@@ -31,29 +31,29 @@ import (
 
 const jsondata = `
 [
-	{ "type" : "function", "name" : "balance", "constant" : true },
-	{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
+	{ "type" : "function", "name" : "balance", "stateMutability" : "view" },
+	{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
 ]`
 
 const jsondata2 = `
 [
-	{ "type" : "function", "name" : "balance", "constant" : true },
-	{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
-	{ "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
-	{ "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
-	{ "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
-	{ "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
-	{ "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
-	{ "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
-	{ "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
-	{ "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
-	{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
-	{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
-	{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
-	{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
-	{ "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
-	{ "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
-	{ "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }
+	{ "type" : "function", "name" : "balance", "stateMutability" : "view" },
+	{ "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
+	{ "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
+	{ "type" : "function", "name" : "string", "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
+	{ "type" : "function", "name" : "bool", "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
+	{ "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
+	{ "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
+	{ "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
+	{ "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
+	{ "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
+	{ "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
+	{ "type" : "function", "name" : "slice256", "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
+	{ "type" : "function", "name" : "sliceAddress", "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
+	{ "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
+	{ "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
+	{ "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
+	{ "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }
 ]`
 
 func TestReader(t *testing.T) {
@@ -61,10 +61,10 @@ func TestReader(t *testing.T) {
 	exp := ABI{
 		Methods: map[string]Method{
 			"balance": {
-				"balance", "balance", true, nil, nil,
+				"balance", "balance", "view", false, false, false, false, nil, nil,
 			},
 			"send": {
-				"send", "send", false, []Argument{
+				"send", "send", "", false, false, false, false, []Argument{
 					{"amount", Uint256, false},
 				}, nil,
 			},
@@ -173,7 +173,7 @@ func TestTestSlice(t *testing.T) {
 
 func TestMethodSignature(t *testing.T) {
 	String, _ := NewType("string", "", nil)
-	m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
+	m := Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
 	exp := "foo(string,string)"
 	if m.Sig() != exp {
 		t.Error("signature mismatch", exp, "!=", m.Sig())
@@ -185,7 +185,7 @@ func TestMethodSignature(t *testing.T) {
 	}
 
 	uintt, _ := NewType("uint256", "", nil)
-	m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil}
+	m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", uintt, false}}, nil}
 	exp = "foo(uint256)"
 	if m.Sig() != exp {
 		t.Error("signature mismatch", exp, "!=", m.Sig())
@@ -204,7 +204,7 @@ func TestMethodSignature(t *testing.T) {
 			{Name: "y", Type: "int256"},
 		}},
 	})
-	m = Method{"foo", "foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil}
+	m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil}
 	exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)"
 	if m.Sig() != exp {
 		t.Error("signature mismatch", exp, "!=", m.Sig())
@@ -582,7 +582,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
 }
 
 func TestDefaultFunctionParsing(t *testing.T) {
-	const definition = `[{ "name" : "balance" }]`
+	const definition = `[{ "name" : "balance", "type" : "function" }]`
 
 	abi, err := JSON(strings.NewReader(definition))
 	if err != nil {

+ 12 - 0
accounts/abi/bind/base.go

@@ -171,12 +171,24 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
 	if err != nil {
 		return nil, err
 	}
+	// todo(rjl493456442) check the method is payable or not,
+	// reject invalid transaction at the first place
 	return c.transact(opts, &c.address, input)
 }
 
+// RawTransact initiates a transaction with the given raw calldata as the input.
+// It's usually used to initiates transaction for invoking **Fallback** function.
+func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
+	// todo(rjl493456442) check the method is payable or not,
+	// reject invalid transaction at the first place
+	return c.transact(opts, &c.address, calldata)
+}
+
 // Transfer initiates a plain transaction to move funds to the contract, calling
 // its default method if one is available.
 func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
+	// todo(rjl493456442) check the payable fallback or receive is defined
+	// or not, reject invalid transaction at the first place
 	return c.transact(opts, &c.address, nil)
 }
 

+ 28 - 7
accounts/abi/bind/bind.go

@@ -77,6 +77,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
 			calls     = make(map[string]*tmplMethod)
 			transacts = make(map[string]*tmplMethod)
 			events    = make(map[string]*tmplEvent)
+			fallback  *tmplMethod
+			receive   *tmplMethod
 
 			// identifiers are used to detect duplicated identifier of function
 			// 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]
 			normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
 			// Ensure there is no duplicated identifier
 			var identifiers = callIdentifiers
-			if !original.Const {
+			if !original.IsConstant() {
 				identifiers = transactIdentifiers
 			}
 			if identifiers[normalizedName] {
@@ -121,7 +123,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
 				}
 			}
 			// Append the methods to the call or transact lists
-			if original.Const {
+			if original.IsConstant() {
 				calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
 			} else {
 				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]
 			// Append the event to the accumulator list
 			events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
 		}
-
+		// Add two special fallback functions if they exist
+		if evmABI.HasFallback() {
+			fallback = &tmplMethod{Original: evmABI.Fallback}
+		}
+		if evmABI.HasReceive() {
+			receive = &tmplMethod{Original: evmABI.Receive}
+		}
 		// There is no easy way to pass arbitrary java objects to the Go side.
 		if len(structs) > 0 && lang == LangJava {
 			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]
 			Constructor: evmABI.Constructor,
 			Calls:       calls,
 			Transacts:   transacts,
+			Fallback:    fallback,
+			Receive:     receive,
 			Events:      events,
 			Libraries:   make(map[string]string),
 		}
@@ -619,11 +629,22 @@ func formatMethod(method abi.Method, structs map[string]*tmplStruct) string {
 			outputs[i] += fmt.Sprintf(" %v", output.Name)
 		}
 	}
-	constant := ""
-	if method.Const {
-		constant = "constant "
+	// Extract meaningful state mutability of solidity method.
+	// If it's default value, never print it.
+	state := method.StateMutability
+	if state == "nonpayable" {
+		state = ""
+	}
+	if state != "" {
+		state = state + " "
+	}
+	identity := fmt.Sprintf("function %v", method.RawName)
+	if method.IsFallback {
+		identity = "fallback"
+	} else if method.IsReceive {
+		identity = "receive"
 	}
-	return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
+	return fmt.Sprintf("%s(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", "))
 }
 
 // formatEvent transforms raw event representation into a user friendly one.

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 93 - 2
accounts/abi/bind/bind_test.go


+ 66 - 0
accounts/abi/bind/template.go

@@ -35,6 +35,8 @@ type tmplContract struct {
 	Constructor abi.Method             // Contract constructor for deploy parametrization
 	Calls       map[string]*tmplMethod // Contract calls that only read state data
 	Transacts   map[string]*tmplMethod // Contract calls that write state data
+	Fallback    *tmplMethod            // Additional special fallback function
+	Receive     *tmplMethod            // Additional special receive function
 	Events      map[string]*tmplEvent  // Contract events accessors
 	Libraries   map[string]string      // Same as tmplData, but filtered to only keep what the contract needs
 	Library     bool                   // Indicator whether the contract is a library
@@ -351,6 +353,52 @@ var (
 		}
 	{{end}}
 
+	{{if .Fallback}} 
+		// Fallback is a paid mutator transaction binding the contract fallback function.
+		//
+		// Solidity: {{formatmethod .Fallback.Original $structs}}
+		func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
+			return _{{$contract.Type}}.contract.RawTransact(opts, calldata)
+		}
+
+		// Fallback is a paid mutator transaction binding the contract fallback function.
+		//
+		// Solidity: {{formatmethod .Fallback.Original $structs}}
+		func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) {
+		  return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
+		}
+	
+		// Fallback is a paid mutator transaction binding the contract fallback function.
+		// 
+		// Solidity: {{formatmethod .Fallback.Original $structs}}
+		func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
+		  return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
+		}
+	{{end}}
+
+	{{if .Receive}} 
+		// Receive is a paid mutator transaction binding the contract receive function.
+		//
+		// Solidity: {{formatmethod .Receive.Original $structs}}
+		func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
+			return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
+		}
+
+		// Receive is a paid mutator transaction binding the contract receive function.
+		//
+		// Solidity: {{formatmethod .Receive.Original $structs}}
+		func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) {
+		  return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
+		}
+	
+		// Receive is a paid mutator transaction binding the contract receive function.
+		// 
+		// Solidity: {{formatmethod .Receive.Original $structs}}
+		func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) {
+		  return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
+		}
+	{{end}}
+
 	{{range .Events}}
 		// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
 		type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
@@ -611,6 +659,24 @@ import java.util.*;
 		return this.Contract.transact(opts, "{{.Original.Name}}"	, args);
 	}
 	{{end}}
+
+    {{if .Fallback}}
+	// Fallback is a paid mutator transaction binding the contract fallback function.
+	//
+	// Solidity: {{formatmethod .Fallback.Original $structs}}
+	public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { 
+		return this.Contract.rawTransact(opts, calldata);
+	}
+    {{end}}
+
+    {{if .Receive}}
+	// Receive is a paid mutator transaction binding the contract receive function.
+	//
+	// Solidity: {{formatmethod .Receive.Original $structs}}
+	public Transaction Receive(TransactOpts opts) throws Exception { 
+		return this.Contract.rawTransact(opts, null);
+	}
+    {{end}}
 }
 {{end}}
 `

+ 48 - 8
accounts/abi/method.go

@@ -41,10 +41,23 @@ type Method struct {
 	// * foo(uint,uint)
 	// The method name of the first one will be resolved as foo while the second one
 	// will be resolved as foo0.
-	Name string
-	// RawName is the raw method name parsed from ABI.
-	RawName string
-	Const   bool
+	Name    string
+	RawName string // RawName is the raw method name parsed from ABI
+
+	// StateMutability indicates the mutability state of method,
+	// the default value is nonpayable. It can be empty if the abi
+	// is generated by legacy compiler.
+	StateMutability string
+
+	// Legacy indicators generated by compiler before v0.6.0
+	Constant bool
+	Payable  bool
+
+	// The following two flags indicates whether the method is a
+	// special fallback introduced in solidity v0.6.0
+	IsFallback bool
+	IsReceive  bool
+
 	Inputs  Arguments
 	Outputs Arguments
 }
@@ -57,6 +70,11 @@ type Method struct {
 //
 // Please note that "int" is substitute for its canonical representation "int256"
 func (method Method) Sig() string {
+	// Short circuit if the method is special. Fallback
+	// and Receive don't have signature at all.
+	if method.IsFallback || method.IsReceive {
+		return ""
+	}
 	types := make([]string, len(method.Inputs))
 	for i, input := range method.Inputs {
 		types[i] = input.Type.String()
@@ -76,11 +94,22 @@ func (method Method) String() string {
 			outputs[i] += fmt.Sprintf(" %v", output.Name)
 		}
 	}
-	constant := ""
-	if method.Const {
-		constant = "constant "
+	// Extract meaningful state mutability of solidity method.
+	// If it's default value, never print it.
+	state := method.StateMutability
+	if state == "nonpayable" {
+		state = ""
+	}
+	if state != "" {
+		state = state + " "
 	}
-	return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
+	identity := fmt.Sprintf("function %v", method.RawName)
+	if method.IsFallback {
+		identity = "fallback"
+	} else if method.IsReceive {
+		identity = "receive"
+	}
+	return fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", "))
 }
 
 // ID returns the canonical representation of the method's signature used by the
@@ -88,3 +117,14 @@ func (method Method) String() string {
 func (method Method) ID() []byte {
 	return crypto.Keccak256([]byte(method.Sig()))[:4]
 }
+
+// IsConstant returns the indicator whether the method is read-only.
+func (method Method) IsConstant() bool {
+	return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant
+}
+
+// IsPayable returns the indicator whether the method can process
+// plain ether transfers.
+func (method Method) IsPayable() bool {
+	return method.StateMutability == "payable" || method.Payable
+}

+ 23 - 6
accounts/abi/method_test.go

@@ -23,13 +23,15 @@ import (
 
 const methoddata = `
 [
-	{"type": "function", "name": "balance", "constant": true },
-	{"type": "function", "name": "send", "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }]},
-	{"type": "function", "name": "transfer", "constant": false, "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]},
+	{"type": "function", "name": "balance", "stateMutability": "view"},
+	{"type": "function", "name": "send", "inputs": [{ "name": "amount", "type": "uint256" }]},
+	{"type": "function", "name": "transfer", "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]},
 	{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
 	{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
 	{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
-	{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}
+	{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
+	{"stateMutability":"nonpayable","type":"fallback"},
+	{"stateMutability":"payable","type":"receive"}
 ]`
 
 func TestMethodString(t *testing.T) {
@@ -39,7 +41,7 @@ func TestMethodString(t *testing.T) {
 	}{
 		{
 			method:      "balance",
-			expectation: "function balance() constant returns()",
+			expectation: "function balance() view returns()",
 		},
 		{
 			method:      "send",
@@ -65,6 +67,14 @@ func TestMethodString(t *testing.T) {
 			method:      "complexTuple",
 			expectation: "function complexTuple((uint256,uint256)[5][] a) returns()",
 		},
+		{
+			method:      "fallback",
+			expectation: "fallback() returns()",
+		},
+		{
+			method:      "receive",
+			expectation: "receive() payable returns()",
+		},
 	}
 
 	abi, err := JSON(strings.NewReader(methoddata))
@@ -73,7 +83,14 @@ func TestMethodString(t *testing.T) {
 	}
 
 	for _, test := range table {
-		got := abi.Methods[test.method].String()
+		var got string
+		if test.method == "fallback" {
+			got = abi.Fallback.String()
+		} else if test.method == "receive" {
+			got = abi.Receive.String()
+		} else {
+			got = abi.Methods[test.method].String()
+		}
 		if got != test.expectation {
 			t.Errorf("expected string to be %s, got %s", test.expectation, got)
 		}

+ 18 - 18
accounts/abi/unpack_test.go

@@ -443,7 +443,7 @@ var unpackTests = []unpackTest{
 func TestUnpack(t *testing.T) {
 	for i, test := range unpackTests {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
-			def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
+			def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
 			abi, err := JSON(strings.NewReader(def))
 			if err != nil {
 				t.Fatalf("invalid ABI definition %s: %v", def, err)
@@ -522,7 +522,7 @@ type methodMultiOutput struct {
 
 func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
 	const definition = `[
-	{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
+	{ "name" : "multi", "type": "function", "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
 	var expected = methodMultiOutput{big.NewInt(1), "hello"}
 
 	abi, err := JSON(strings.NewReader(definition))
@@ -611,7 +611,7 @@ func TestMethodMultiReturn(t *testing.T) {
 }
 
 func TestMultiReturnWithArray(t *testing.T) {
-	const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
+	const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
 	abi, err := JSON(strings.NewReader(definition))
 	if err != nil {
 		t.Fatal(err)
@@ -634,7 +634,7 @@ func TestMultiReturnWithArray(t *testing.T) {
 }
 
 func TestMultiReturnWithStringArray(t *testing.T) {
-	const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
+	const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
 	abi, err := JSON(strings.NewReader(definition))
 	if err != nil {
 		t.Fatal(err)
@@ -664,7 +664,7 @@ func TestMultiReturnWithStringArray(t *testing.T) {
 }
 
 func TestMultiReturnWithStringSlice(t *testing.T) {
-	const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
+	const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
 	abi, err := JSON(strings.NewReader(definition))
 	if err != nil {
 		t.Fatal(err)
@@ -700,7 +700,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
 	//  values of nested static arrays count towards the size as well, and any element following
 	//  after such nested array argument should be read with the correct offset,
 	//  so that it does not read content from the previous array argument.
-	const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]`
+	const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]`
 	abi, err := JSON(strings.NewReader(definition))
 	if err != nil {
 		t.Fatal(err)
@@ -737,15 +737,15 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
 
 func TestUnmarshal(t *testing.T) {
 	const definition = `[
-	{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
-	{ "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] },
-	{ "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] },
-	{ "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
-	{ "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
-	{ "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] },
-	{ "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] },
-	{ "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
-	{ "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
+	{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
+	{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
+	{ "name" : "bytes", "type": "function", "outputs": [ { "type": "bytes" } ] },
+	{ "name" : "fixed", "type": "function", "outputs": [ { "type": "bytes32" } ] },
+	{ "name" : "multi", "type": "function", "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
+	{ "name" : "intArraySingle", "type": "function", "outputs": [ { "type": "uint256[3]" } ] },
+	{ "name" : "addressSliceSingle", "type": "function", "outputs": [ { "type": "address[]" } ] },
+	{ "name" : "addressSliceDouble", "type": "function", "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
+	{ "name" : "mixedBytes", "type": "function", "stateMutability" : "view", "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
 
 	abi, err := JSON(strings.NewReader(definition))
 	if err != nil {
@@ -985,7 +985,7 @@ func TestUnmarshal(t *testing.T) {
 }
 
 func TestUnpackTuple(t *testing.T) {
-	const simpleTuple = `[{"name":"tuple","constant":false,"outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
+	const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
 	abi, err := JSON(strings.NewReader(simpleTuple))
 	if err != nil {
 		t.Fatal(err)
@@ -1014,7 +1014,7 @@ func TestUnpackTuple(t *testing.T) {
 	}
 
 	// Test nested tuple
-	const nestedTuple = `[{"name":"tuple","constant":false,"outputs":[
+	const nestedTuple = `[{"name":"tuple","type":"function","outputs":[
 		{"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]},
 		{"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]},
 		{"type":"uint256","name":"a"}
@@ -1136,7 +1136,7 @@ func TestOOMMaliciousInput(t *testing.T) {
 		},
 	}
 	for i, test := range oomTests {
-		def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
+		def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def)
 		abi, err := JSON(strings.NewReader(def))
 		if err != nil {
 			t.Fatalf("invalid ABI definition %s: %v", def, err)

+ 4 - 1
les/utils/expiredvalue.go

@@ -92,7 +92,10 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 {
 		e.Exp = integer
 	}
 	if base >= 0 || uint64(-base) <= e.Base {
-		e.Base += uint64(base)
+		// This is a temporary fix to circumvent a golang
+		// uint conversion issue on arm64, which needs to
+		// be investigated further. FIXME
+		e.Base = uint64(int64(e.Base) + int64(base))
 		return amount
 	}
 	net := int64(-float64(e.Base) / factor)

+ 3 - 1
les/utils/expiredvalue_test.go

@@ -16,7 +16,9 @@
 
 package utils
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestValueExpiration(t *testing.T) {
 	var cases = []struct {

+ 9 - 0
mobile/bind.go

@@ -197,6 +197,15 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, args *Interf
 	return &Transaction{rawTx}, nil
 }
 
+// RawTransact invokes the (paid) contract method with raw calldata as input values.
+func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (tx *Transaction, _ error) {
+	rawTx, err := c.contract.RawTransact(&opts.opts, calldata)
+	if err != nil {
+		return nil, err
+	}
+	return &Transaction{rawTx}, nil
+}
+
 // Transfer initiates a plain transaction to move funds to the contract, calling
 // its default method if one is available.
 func (c *BoundContract) Transfer(opts *TransactOpts) (tx *Transaction, _ error) {

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio