From a0bf2ea7e732b114518c3d8c66db337e0a7932f1 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 20 Nov 2015 13:45:37 +0100 Subject: accounts/abi: added output parsing & added call mechanism Added calling mechanism and return value parsing --- accounts/abi/abi.go | 146 ++++++++++++++++++++++------------------------- accounts/abi/abi_test.go | 47 ++++++++++++--- accounts/abi/argument.go | 48 ++++++++++++++++ accounts/abi/method.go | 76 ++++++++++++++++++++++++ accounts/abi/numbers.go | 2 + accounts/abi/type.go | 12 +++- 6 files changed, 243 insertions(+), 88 deletions(-) create mode 100644 accounts/abi/argument.go create mode 100644 accounts/abi/method.go diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 3f05bfe2d..635dc43fe 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -20,75 +20,17 @@ import ( "encoding/json" "fmt" "io" - "strings" + "math" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" ) -// Callable method given a `Name` and whether the method is a constant. -// If the method is `Const` no transaction needs to be created for this -// particular Method call. It can easily be simulated using a local VM. -// For example a `Balance()` method only needs to retrieve something -// from the storage and therefor requires no Tx to be send to the -// network. A method such as `Transact` does require a Tx and thus will -// be flagged `true`. -// Input specifies the required input parameters for this gives method. -type Method struct { - Name string - Const bool - Inputs []Argument - Return Type // not yet implemented -} - -// Returns the methods string signature according to the ABI spec. -// -// Example -// -// function foo(uint32 a, int b) = "foo(uint32,int256)" -// -// Please note that "int" is substitute for its canonical representation "int256" -func (m Method) String() (out string) { - out += m.Name - types := make([]string, len(m.Inputs)) - i := 0 - for _, input := range m.Inputs { - types[i] = input.Type.String() - i++ - } - out += "(" + strings.Join(types, ",") + ")" - - return -} - -func (m Method) Id() []byte { - return crypto.Sha3([]byte(m.String()))[:4] -} - -// Argument holds the name of the argument and the corresponding type. -// Types are used when packing and testing arguments. -type Argument struct { - Name string - Type Type -} - -func (a *Argument) UnmarshalJSON(data []byte) error { - var extarg struct { - Name string - Type string - } - err := json.Unmarshal(data, &extarg) - if err != nil { - return fmt.Errorf("argument json err: %v", err) - } - - a.Type, err = NewType(extarg.Type) - if err != nil { - return err - } - a.Name = extarg.Name - - return nil -} +// Executer is an executer method for performing state executions. It takes one +// argument which is the input data and expects output data to be returned as +// multiple 32 byte word length concatenated slice +type Executer func(datain []byte) []byte // The ABI holds information about a contract's context and available // invokable methods. It will allow you to type check function calls and @@ -97,6 +39,18 @@ type ABI struct { Methods map[string]Method } +// JSON returns a parsed ABI interface and error if it failed. +func JSON(reader io.Reader) (ABI, error) { + dec := json.NewDecoder(reader) + + var abi ABI + if err := dec.Decode(&abi); err != nil { + return ABI{}, err + } + + return abi, nil +} + // tests, tests whether the given input would result in a successful // call. Checks argument list count and matches input to `input`. func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) { @@ -145,6 +99,55 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return packed, nil } +// toGoType parses the input and casts it to the proper type defined by the ABI +// argument in t. +func toGoType(t Argument, input []byte) interface{} { + switch t.Type.T { + case IntTy: + return common.BytesToBig(input) + case UintTy: + return common.BytesToBig(input) + case BoolTy: + return common.BytesToBig(input).Uint64() > 0 + case AddressTy: + return common.BytesToAddress(input) + case HashTy: + return common.BytesToHash(input) + } + return nil +} + +// Call executes a call and attemps to parse the return values and returns it as +// an interface. It uses the executer method to perform the actual call since +// the abi knows nothing of the lower level calling mechanism. +// +// Call supports all abi types and includes multiple return values. When only +// one item is returned a single interface{} will be returned, if a contract +// method returns multiple values an []interface{} slice is returned. +func (abi ABI) Call(executer Executer, name string, args ...interface{}) interface{} { + callData, err := abi.Pack(name, args...) + if err != nil { + glog.V(logger.Debug).Infoln("pack error:", err) + return nil + } + + output := executer(callData) + + method := abi.Methods[name] + ret := make([]interface{}, int(math.Max(float64(len(method.Outputs)), float64(len(output)/32)))) + for i := 0; i < len(ret); i += 32 { + index := i / 32 + ret[index] = toGoType(method.Outputs[index], output[i:i+32]) + } + + // return single interface + if len(ret) == 1 { + return ret[0] + } + + return ret +} + func (abi *ABI) UnmarshalJSON(data []byte) error { var methods []Method if err := json.Unmarshal(data, &methods); err != nil { @@ -158,14 +161,3 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { return nil } - -func JSON(reader io.Reader) (ABI, error) { - dec := json.NewDecoder(reader) - - var abi ABI - if err := dec.Decode(&abi); err != nil { - return ABI{}, err - } - - return abi, nil -} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 96dd3ee62..d514fb8c7 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -92,12 +92,12 @@ func TestReader(t *testing.T) { exp := ABI{ Methods: map[string]Method{ "balance": Method{ - "balance", true, nil, Type{}, + "balance", true, nil, nil, }, "send": Method{ "send", false, []Argument{ Argument{"amount", Uint256}, - }, Type{}, + }, nil, }, }, } @@ -238,10 +238,10 @@ func TestTestAddress(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") - m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, Type{}} + m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, nil} exp := "foo(string32,string)" - if m.String() != exp { - t.Error("signature mismatch", exp, "!=", m.String()) + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) } idexp := crypto.Sha3([]byte(exp))[:4] @@ -250,10 +250,10 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint") - m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, Type{}} + m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil} exp = "foo(uint256)" - if m.String() != exp { - t.Error("signature mismatch", exp, "!=", m.String()) + if m.Sig() != exp { + t.Error("signature mismatch", exp, "!=", m.Sig()) } } @@ -393,3 +393,34 @@ func TestBytes(t *testing.T) { t.Error("expected error") } } + +func TestReturn(t *testing.T) { + const definition = `[ + { "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, + { "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] } + +]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + r := abi.Call(func([]byte) []byte { + t := make([]byte, 32) + t[0] = 1 + return t + }, "balance") + if _, ok := r.(common.Hash); !ok { + t.Errorf("expected type common.Hash, got %T", r) + } + + r = abi.Call(func([]byte) []byte { + t := make([]byte, 32) + t[0] = 1 + return t + }, "name") + if _, ok := r.(common.Address); !ok { + t.Errorf("expected type common.Address, got %T", r) + } +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go new file mode 100644 index 000000000..8907b2979 --- /dev/null +++ b/accounts/abi/argument.go @@ -0,0 +1,48 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "encoding/json" + "fmt" +) + +// Argument holds the name of the argument and the corresponding type. +// Types are used when packing and testing arguments. +type Argument struct { + Name string + Type Type +} + +func (a *Argument) UnmarshalJSON(data []byte) error { + var extarg struct { + Name string + Type string + } + err := json.Unmarshal(data, &extarg) + if err != nil { + return fmt.Errorf("argument json err: %v", err) + } + + a.Type, err = NewType(extarg.Type) + if err != nil { + return err + } + a.Name = extarg.Name + + return nil +} diff --git a/accounts/abi/method.go b/accounts/abi/method.go new file mode 100644 index 000000000..63194e788 --- /dev/null +++ b/accounts/abi/method.go @@ -0,0 +1,76 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/crypto" +) + +// Callable method given a `Name` and whether the method is a constant. +// If the method is `Const` no transaction needs to be created for this +// particular Method call. It can easily be simulated using a local VM. +// For example a `Balance()` method only needs to retrieve something +// from the storage and therefor requires no Tx to be send to the +// network. A method such as `Transact` does require a Tx and thus will +// be flagged `true`. +// Input specifies the required input parameters for this gives method. +type Method struct { + Name string + Const bool + Inputs []Argument + Outputs []Argument +} + +// Sig returns the methods string signature according to the ABI spec. +// +// Example +// +// function foo(uint32 a, int b) = "foo(uint32,int256)" +// +// Please note that "int" is substitute for its canonical representation "int256" +func (m Method) Sig() string { + types := make([]string, len(m.Inputs)) + i := 0 + for _, input := range m.Inputs { + types[i] = input.Type.String() + i++ + } + return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ",")) +} + +func (m Method) String() string { + inputs := make([]string, len(m.Inputs)) + for i, input := range m.Inputs { + inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type) + } + outputs := make([]string, len(m.Outputs)) + for i, output := range m.Outputs { + if len(output.Name) > 0 { + outputs[i] = fmt.Sprintf("%v ", output.Name) + } + outputs[i] += output.Type.String() + } + + return fmt.Sprintf("function %v(%v) returns(%v)", m.Name, strings.Join(inputs, ", "), strings.Join(outputs, ", ")) +} + +func (m Method) Id() []byte { + return crypto.Sha3([]byte(m.Sig()))[:4] +} diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index 2a7049425..c37cd5f68 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -37,6 +37,8 @@ var int8_t = reflect.TypeOf(int8(0)) var int16_t = reflect.TypeOf(int16(0)) var int32_t = reflect.TypeOf(int32(0)) var int64_t = reflect.TypeOf(int64(0)) +var hash_t = reflect.TypeOf(common.Hash{}) +var address_t = reflect.TypeOf(common.Address{}) var uint_ts = reflect.TypeOf([]uint(nil)) var uint8_ts = reflect.TypeOf([]uint8(nil)) diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 16d7491e7..8f0238fc9 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -31,6 +31,7 @@ const ( BoolTy SliceTy AddressTy + HashTy RealTy ) @@ -121,7 +122,7 @@ func NewType(t string) (typ Type, err error) { typ.Kind = reflect.Invalid case "address": typ.Kind = reflect.Slice - typ.Type = byte_ts + typ.Type = address_t typ.Size = 20 typ.T = AddressTy case "string": @@ -130,6 +131,11 @@ func NewType(t string) (typ Type, err error) { if vsize > 0 { typ.Size = 32 } + case "hash": + typ.Kind = reflect.Slice + typ.Size = 32 + typ.Type = hash_t + typ.T = HashTy case "bytes": typ.Kind = reflect.Slice typ.Type = byte_ts @@ -206,9 +212,9 @@ func (t Type) pack(v interface{}) ([]byte, error) { } case reflect.Array: if v, ok := value.Interface().(common.Address); ok { - return t.pack(v[:]) + return common.LeftPadBytes(v[:], 32), nil } else if v, ok := value.Interface().(common.Hash); ok { - return t.pack(v[:]) + return v[:], nil } } -- cgit v1.2.3