forked from cerc-io/plugeth
accounts/abi: added output parsing & added call mechanism
Added calling mechanism and return value parsing
This commit is contained in:
parent
b0fb48c389
commit
a0bf2ea7e7
@ -20,75 +20,17 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"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.
|
// Executer is an executer method for performing state executions. It takes one
|
||||||
// If the method is `Const` no transaction needs to be created for this
|
// argument which is the input data and expects output data to be returned as
|
||||||
// particular Method call. It can easily be simulated using a local VM.
|
// multiple 32 byte word length concatenated slice
|
||||||
// For example a `Balance()` method only needs to retrieve something
|
type Executer func(datain []byte) []byte
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
// invokable methods. It will allow you to type check function calls and
|
// invokable methods. It will allow you to type check function calls and
|
||||||
@ -97,6 +39,18 @@ type ABI struct {
|
|||||||
Methods map[string]Method
|
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
|
// tests, tests whether the given input would result in a successful
|
||||||
// call. Checks argument list count and matches input to `input`.
|
// call. Checks argument list count and matches input to `input`.
|
||||||
func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) {
|
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
|
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 {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
var methods []Method
|
var methods []Method
|
||||||
if err := json.Unmarshal(data, &methods); err != nil {
|
if err := json.Unmarshal(data, &methods); err != nil {
|
||||||
@ -158,14 +161,3 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
@ -92,12 +92,12 @@ func TestReader(t *testing.T) {
|
|||||||
exp := ABI{
|
exp := ABI{
|
||||||
Methods: map[string]Method{
|
Methods: map[string]Method{
|
||||||
"balance": Method{
|
"balance": Method{
|
||||||
"balance", true, nil, Type{},
|
"balance", true, nil, nil,
|
||||||
},
|
},
|
||||||
"send": Method{
|
"send": Method{
|
||||||
"send", false, []Argument{
|
"send", false, []Argument{
|
||||||
Argument{"amount", Uint256},
|
Argument{"amount", Uint256},
|
||||||
}, Type{},
|
}, nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -238,10 +238,10 @@ func TestTestAddress(t *testing.T) {
|
|||||||
func TestMethodSignature(t *testing.T) {
|
func TestMethodSignature(t *testing.T) {
|
||||||
String, _ := NewType("string")
|
String, _ := NewType("string")
|
||||||
String32, _ := NewType("string32")
|
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)"
|
exp := "foo(string32,string)"
|
||||||
if m.String() != exp {
|
if m.Sig() != exp {
|
||||||
t.Error("signature mismatch", exp, "!=", m.String())
|
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||||
}
|
}
|
||||||
|
|
||||||
idexp := crypto.Sha3([]byte(exp))[:4]
|
idexp := crypto.Sha3([]byte(exp))[:4]
|
||||||
@ -250,10 +250,10 @@ func TestMethodSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uintt, _ := NewType("uint")
|
uintt, _ := NewType("uint")
|
||||||
m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, Type{}}
|
m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil}
|
||||||
exp = "foo(uint256)"
|
exp = "foo(uint256)"
|
||||||
if m.String() != exp {
|
if m.Sig() != exp {
|
||||||
t.Error("signature mismatch", exp, "!=", m.String())
|
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,3 +393,34 @@ func TestBytes(t *testing.T) {
|
|||||||
t.Error("expected error")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
48
accounts/abi/argument.go
Normal file
48
accounts/abi/argument.go
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
76
accounts/abi/method.go
Normal file
76
accounts/abi/method.go
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
@ -37,6 +37,8 @@ var int8_t = reflect.TypeOf(int8(0))
|
|||||||
var int16_t = reflect.TypeOf(int16(0))
|
var int16_t = reflect.TypeOf(int16(0))
|
||||||
var int32_t = reflect.TypeOf(int32(0))
|
var int32_t = reflect.TypeOf(int32(0))
|
||||||
var int64_t = reflect.TypeOf(int64(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 uint_ts = reflect.TypeOf([]uint(nil))
|
||||||
var uint8_ts = reflect.TypeOf([]uint8(nil))
|
var uint8_ts = reflect.TypeOf([]uint8(nil))
|
||||||
|
@ -31,6 +31,7 @@ const (
|
|||||||
BoolTy
|
BoolTy
|
||||||
SliceTy
|
SliceTy
|
||||||
AddressTy
|
AddressTy
|
||||||
|
HashTy
|
||||||
RealTy
|
RealTy
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ func NewType(t string) (typ Type, err error) {
|
|||||||
typ.Kind = reflect.Invalid
|
typ.Kind = reflect.Invalid
|
||||||
case "address":
|
case "address":
|
||||||
typ.Kind = reflect.Slice
|
typ.Kind = reflect.Slice
|
||||||
typ.Type = byte_ts
|
typ.Type = address_t
|
||||||
typ.Size = 20
|
typ.Size = 20
|
||||||
typ.T = AddressTy
|
typ.T = AddressTy
|
||||||
case "string":
|
case "string":
|
||||||
@ -130,6 +131,11 @@ func NewType(t string) (typ Type, err error) {
|
|||||||
if vsize > 0 {
|
if vsize > 0 {
|
||||||
typ.Size = 32
|
typ.Size = 32
|
||||||
}
|
}
|
||||||
|
case "hash":
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Size = 32
|
||||||
|
typ.Type = hash_t
|
||||||
|
typ.T = HashTy
|
||||||
case "bytes":
|
case "bytes":
|
||||||
typ.Kind = reflect.Slice
|
typ.Kind = reflect.Slice
|
||||||
typ.Type = byte_ts
|
typ.Type = byte_ts
|
||||||
@ -206,9 +212,9 @@ func (t Type) pack(v interface{}) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
if v, ok := value.Interface().(common.Address); ok {
|
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 {
|
} else if v, ok := value.Interface().(common.Hash); ok {
|
||||||
return t.pack(v[:])
|
return v[:], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user