From 6766b4bd96b833f57097a80cfbc6ecba600a6bb1 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Mon, 24 Jul 2017 12:14:12 -0500 Subject: [PATCH 1/2] accounts/abi,accounts/abi/bind: add payable features into contract bindings Signed-off-by: RJ Catalano --- accounts/abi/abi.go | 9 ++++++++- accounts/abi/bind/base.go | 5 +++++ accounts/abi/bind/bind.go | 7 ++++++- accounts/abi/bind/template.go | 1 + accounts/abi/method.go | 1 + 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 2a06d474b8..1cb22abe36 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -33,6 +33,7 @@ type ABI struct { Constructor Method Methods map[string]Method Events map[string]Event + Fallback Method } // JSON returns a parsed ABI interface and error if it failed. @@ -185,6 +186,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { Constant bool Indexed bool Anonymous bool + Payable bool Inputs []Argument Outputs []Argument } @@ -202,12 +204,13 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { Inputs: field.Inputs, } // empty defaults to function according to the abi spec - case "function", "": + case "function": abi.Methods[field.Name] = Method{ Name: field.Name, Const: field.Constant, Inputs: field.Inputs, Outputs: field.Outputs, + Payable: field.Payable, } case "event": abi.Events[field.Name] = Event{ @@ -215,6 +218,10 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { Anonymous: field.Anonymous, Inputs: field.Inputs, } + case "fallback": + abi.Fallback = Method{ + Payable: field.Payable, + } } } diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index b40bd65e80..3828a96647 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -152,6 +152,11 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in if err != nil { return nil, err } + // check to make sure method is payable and if not, make sure that there is no + // value being transported with this transaction + if !c.abi.Methods[method].Payable && 0 != opts.Value.Cmp(common.Big0) { + return nil, fmt.Errorf("bind: value %v sent to non payable method %v", opts.Value.String(), method) + } return c.transact(opts, &c.address, input) } diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 73e95e02a1..57331665bf 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -67,6 +67,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La var ( calls = make(map[string]*tmplMethod) transacts = make(map[string]*tmplMethod) + writes = make(map[string]*tmplMethod) ) for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs @@ -90,8 +91,10 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La // Append the methods to the call or transact lists if original.Const { calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} - } else { + } else if original.Payable { transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} + } else { + writes[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} } } contracts[types[i]] = &tmplContract{ @@ -99,8 +102,10 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), InputBin: strings.TrimSpace(bytecodes[i]), Constructor: evmABI.Constructor, + Fallback: evmABI.Fallback, Calls: calls, Transacts: transacts, + Writes: writes, } } // Generate the contract template data content and render it diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 64dd598c0b..e85524ae89 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -30,6 +30,7 @@ type tmplContract struct { InputABI string // JSON ABI used as the input to generate the binding from InputBin string // Optional EVM bytecode used to denetare deploy code from Constructor abi.Method // Contract constructor for deploy parametrization + Fallback abi.Method // Contract fallback method for direct interaction with contract Calls map[string]*tmplMethod // Contract calls that only read state data Transacts map[string]*tmplMethod // Contract calls that write state data } diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 62b3d29575..805f7c2337 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -34,6 +34,7 @@ import ( // Input specifies the required input parameters for this gives method. type Method struct { Name string + Payable bool Const bool Inputs []Argument Outputs []Argument From b43f4783d749193721b026441e933ddd178c1030 Mon Sep 17 00:00:00 2001 From: RJ Catalano Date: Wed, 26 Jul 2017 15:02:21 -0500 Subject: [PATCH 2/2] accounts/abi: fix the tests as they are, hard requirement on the type in the abi Signed-off-by: RJ Catalano --- accounts/abi/abi.go | 5 +++-- accounts/abi/abi_test.go | 25 ++++++------------------- accounts/abi/unpack_test.go | 28 ++++++++++++++-------------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 1cb22abe36..04d8e842eb 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -203,14 +203,13 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { abi.Constructor = Method{ Inputs: field.Inputs, } - // empty defaults to function according to the abi spec case "function": abi.Methods[field.Name] = Method{ Name: field.Name, Const: field.Constant, + Payable: field.Payable, Inputs: field.Inputs, Outputs: field.Outputs, - Payable: field.Payable, } case "event": abi.Events[field.Name] = Event{ @@ -222,6 +221,8 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { abi.Fallback = Method{ Payable: field.Payable, } + default: + return fmt.Errorf("abi: contract interaction type not referenced") } } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index a3aa9446ef..1e651c9645 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -56,8 +56,8 @@ const jsondata = ` const jsondata2 = ` [ - { "type" : "function", "name" : "balance", "constant" : true }, - { "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "balance", "constant" : true, "payable" : false }, + { "type" : "function", "name" : "send", "constant" : false, "payable" : 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" } ] }, @@ -77,10 +77,10 @@ func TestReader(t *testing.T) { exp := ABI{ Methods: map[string]Method{ "balance": { - "balance", true, nil, nil, + "balance", false, true, nil, nil, }, "send": { - "send", false, []Argument{ + "send", false, false, []Argument{ {"amount", Uint256, false}, }, nil, }, @@ -180,7 +180,7 @@ func TestTestSlice(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string") - m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} + m := Method{"foo", 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()) @@ -192,7 +192,7 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint") - m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil} + m = Method{"foo", false, false, []Argument{{"bar", uintt, false}}, nil} exp = "foo(uint256)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -367,19 +367,6 @@ func TestInputVariableInputLength(t *testing.T) { } } -func TestDefaultFunctionParsing(t *testing.T) { - const definition = `[{ "name" : "balance" }]` - - abi, err := JSON(strings.NewReader(definition)) - if err != nil { - t.Fatal(err) - } - - if _, ok := abi.Methods["balance"]; !ok { - t.Error("expected 'balance' to be present") - } -} - func TestBareEvents(t *testing.T) { const definition = `[ { "type" : "event", "name" : "balance" }, diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 8e3afee4e6..9295cede26 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -136,7 +136,7 @@ func TestSimpleMethodUnpack(t *testing.T) { "", }, } { - abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + abiDefinition := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(abiDefinition)) if err != nil { t.Errorf("%d failed. %v", i, err) @@ -286,7 +286,7 @@ func TestUnpackSetInterfaceArrayOutput(t *testing.T) { func TestMultiReturnWithStruct(t *testing.T) { const definition = `[ - { "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` + { "name" : "multi", "type": "function", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -339,7 +339,7 @@ func TestMultiReturnWithStruct(t *testing.T) { func TestMultiReturnWithSlice(t *testing.T) { const definition = `[ - { "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` + { "name" : "multi", "type": "function", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -375,8 +375,8 @@ func TestMultiReturnWithSlice(t *testing.T) { func TestMarshalArrays(t *testing.T) { const definition = `[ - { "name" : "bytes32", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, - { "name" : "bytes10", "constant" : false, "outputs": [ { "type": "bytes10" } ] } + { "name" : "bytes32", "type": "function", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, + { "name" : "bytes10", "type": "function", "constant" : false, "outputs": [ { "type": "bytes10" } ] } ]` abi, err := JSON(strings.NewReader(definition)) @@ -434,15 +434,15 @@ func TestMarshalArrays(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", "constant" : false, "outputs": [ { "type": "uint256" } ] }, + { "name" : "bool", "type": "function", "constant" : false, "outputs": [ { "type": "bool" } ] }, + { "name" : "bytes", "type": "function", "constant" : false, "outputs": [ { "type": "bytes" } ] }, + { "name" : "fixed", "type": "function", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, + { "name" : "multi", "type": "function", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, + { "name" : "intArraySingle", "type": "function", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] }, + { "name" : "addressSliceSingle", "type": "function", "constant" : false, "outputs": [ { "type": "address[]" } ] }, + { "name" : "addressSliceDouble", "type": "function", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, + { "name" : "mixedBytes", "type": "function", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil {