Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 69 additions & 77 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
47 changes: 39 additions & 8 deletions accounts/abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
}
Expand Down Expand Up @@ -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]
Expand All @@ -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())
}
}

Expand Down Expand Up @@ -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)
}
}
48 changes: 48 additions & 0 deletions accounts/abi/argument.go
Original file line number Diff line number Diff line change
@@ -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 changes: 76 additions & 0 deletions accounts/abi/method.go
Original file line number Diff line number Diff line change
@@ -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]
}
2 changes: 2 additions & 0 deletions accounts/abi/numbers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Loading