accounts/abi/bind: constructor, auth utils and various backends

This commit is contained in:
Péter Szilágyi 2016-03-17 19:27:37 +02:00
parent 72826bb5ad
commit 86cfc22c79
10 changed files with 736 additions and 267 deletions

View File

@ -30,8 +30,9 @@ import (
// invokable methods. It will allow you to type check function calls and // invokable methods. It will allow you to type check function calls and
// packs data accordingly. // packs data accordingly.
type ABI struct { type ABI struct {
Methods map[string]Method Constructor Method
Events map[string]Event Methods map[string]Method
Events map[string]Event
} }
// JSON returns a parsed ABI interface and error if it failed. // JSON returns a parsed ABI interface and error if it failed.
@ -48,9 +49,7 @@ func JSON(reader io.Reader) (ABI, error) {
// 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(method Method, args ...interface{}) ([]byte, error) {
method := abi.Methods[name]
// variable input is the output appended at the end of packed // variable input is the output appended at the end of packed
// output. This is used for strings and bytes types input. // output. This is used for strings and bytes types input.
var variableInput []byte var variableInput []byte
@ -61,7 +60,7 @@ func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) {
// pack the input // pack the input
packed, err := input.Type.pack(a) packed, err := input.Type.pack(a)
if err != nil { if err != nil {
return nil, fmt.Errorf("`%s` %v", name, err) return nil, fmt.Errorf("`%s` %v", method.Name, err)
} }
// check for a string or bytes input type // check for a string or bytes input type
@ -91,26 +90,31 @@ func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) {
// Method ids are created from the first 4 bytes of the hash of the // Method ids are created from the first 4 bytes of the hash of the
// methods string signature. (signature = baz(uint32,string32)) // methods string signature. (signature = baz(uint32,string32))
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
method, exist := abi.Methods[name] // Fetch the ABI of the requested method
if !exist { var method Method
return nil, fmt.Errorf("method '%s' not found", name)
}
// start with argument count match if name == "" {
method = abi.Constructor
} else {
m, exist := abi.Methods[name]
if !exist {
return nil, fmt.Errorf("method '%s' not found", name)
}
method = m
}
// Make sure arguments match up and pack them
if len(args) != len(method.Inputs) { if len(args) != len(method.Inputs) {
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs)) return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
} }
arguments, err := abi.pack(method, args...)
arguments, err := abi.pack(name, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Pack up the method ID too if not a constructor and return
// Set function id if name == "" {
packed := abi.Methods[name].Id() return arguments, nil
packed = append(packed, arguments...) }
return append(method.Id(), arguments...), nil
return packed, nil
} }
// toGoType parses the input and casts it to the proper type defined by the ABI // toGoType parses the input and casts it to the proper type defined by the ABI
@ -283,6 +287,10 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
abi.Events = make(map[string]Event) abi.Events = make(map[string]Event)
for _, field := range fields { for _, field := range fields {
switch field.Type { switch field.Type {
case "constructor":
abi.Constructor = Method{
Inputs: field.Inputs,
}
// empty defaults to function according to the abi spec // empty defaults to function according to the abi spec
case "function", "": case "function", "":
abi.Methods[field.Name] = Method{ abi.Methods[field.Name] = Method{

53
accounts/abi/bind/auth.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright 2016 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 bind
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
// NewTransactor is a utility method to easily create a transaction signer from
// an encrypted json key file and the associated passphrase.
func NewTransactor(keyjson string, passphrase string) (*TransactOpts, error) {
key, err := crypto.DecryptKey([]byte(keyjson), passphrase)
if err != nil {
return nil, err
}
return NewKeyedTransactor(key), nil
}
// NewKeyedTransactor is a utility method to easily create a transaction signer
// from a plain go-ethereum crypto key.
func NewKeyedTransactor(key *crypto.Key) *TransactOpts {
return &TransactOpts{
Account: key.Address,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != key.Address {
return nil, errors.New("not authorized to sign this account")
}
signature, err := crypto.Sign(tx.SigHash().Bytes(), key.PrivateKey)
if err != nil {
return nil, err
}
return tx.WithSignature(signature)
},
}
}

View File

@ -17,24 +17,19 @@
package bind package bind
import ( import (
"encoding/json"
"fmt"
"math/big" "math/big"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
) )
// ContractCaller defines the methods needed to allow operating with contract on a read // ContractCaller defines the methods needed to allow operating with contract on a read
// only basis. // only basis.
type ContractCaller interface { type ContractCaller interface {
// ContractCall executes an Ethereum contract call with the specified data as // ContractCall executes an Ethereum contract call with the specified data as
// the input. // the input. The pending flag requests execution against the pending block, not
ContractCall(contract common.Address, data []byte) ([]byte, error) // the stable head of the chain.
ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error)
} }
// ContractTransactor defines the methods needed to allow operating with contract // ContractTransactor defines the methods needed to allow operating with contract
@ -50,7 +45,7 @@ type ContractTransactor interface {
GasPrice() (*big.Int, error) GasPrice() (*big.Int, error)
// GasLimit tries to estimate the gas needed to execute a specific transaction. // GasLimit tries to estimate the gas needed to execute a specific transaction.
GasLimit(sender, contract common.Address, value *big.Int, data []byte) (*big.Int, error) GasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error)
// SendTransaction injects the transaction into the pending pool for execution. // SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(*types.Transaction) error SendTransaction(*types.Transaction) error
@ -62,186 +57,3 @@ type ContractBackend interface {
ContractCaller ContractCaller
ContractTransactor ContractTransactor
} }
// nilBackend implements bind.ContractBackend, but panics on any method call.
// Its sole purpose is to support the binding tests to construct the generated
// wrappers without calling any methods on them.
type nilBackend struct{}
func (*nilBackend) ContractCall(common.Address, []byte) ([]byte, error) { panic("not implemented") }
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }
func (*nilBackend) AccountNonce(common.Address) (uint64, error) { panic("not implemented") }
func (*nilBackend) GasPrice() (*big.Int, error) { panic("not implemented") }
func (*nilBackend) GasLimit(common.Address, common.Address, *big.Int, []byte) (*big.Int, error) {
panic("not implemented")
}
// Helper backend for internal tests. Will panic on any invocation!
var NilBackend = new(nilBackend)
// rpcBackend implements bind.ContractBackend, and acts as the data provider to
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
// all its functionality.
//
// Note: The current implementation is a blocking one. This should be replaced
// by a proper async version when a real RPC client is created.
type rpcBackend struct {
client rpc.Client // RPC client connection to interact with an API server
autoid uint32 // ID number to use for the next API request
lock sync.Mutex // Singleton access until we get to request multiplexing
}
// NewRPCBackend creates a new binding backend to an RPC provider that can be
// used to interact with remote contracts.
func NewRPCBackend(client rpc.Client) ContractBackend {
return &rpcBackend{
client: client,
}
}
// request is a JSON RPC request package assembled internally from the client
// method calls.
type request struct {
JsonRpc string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
Id int `json:"id"` // Auto incrementing ID number for this request
Method string `json:"method"` // Remote procedure name to invoke on the server
Params []interface{} `json:"params"` // List of parameters to pass through (keep types simple)
}
// response is a JSON RPC response package sent back from the API server.
type response struct {
JsonRpc string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
Id int `json:"id"` // Auto incrementing ID number for this request
Error json.RawMessage `json:"error"` // Any error returned by the remote side
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
}
// request forwards an API request to the RPC server, and parses the response.
//
// This is currently painfully non-concurrent, but it will have to do until we
// find the time for niceties like this :P
func (backend *rpcBackend) request(method string, params []interface{}) (json.RawMessage, error) {
backend.lock.Lock()
defer backend.lock.Unlock()
// Ugly hack to serialize an empty list properly
if params == nil {
params = []interface{}{}
}
// Assemble the request object
req := &request{
JsonRpc: "2.0",
Id: int(atomic.AddUint32(&backend.autoid, 1)),
Method: method,
Params: params,
}
if err := backend.client.Send(req); err != nil {
return nil, err
}
res := new(response)
if err := backend.client.Recv(res); err != nil {
return nil, err
}
if len(res.Error) > 0 {
return nil, fmt.Errorf("remote error: %s", string(res.Error))
}
return res.Result, nil
}
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
// a contract call to the remote node, returning the reply to for local processing.
func (b *rpcBackend) ContractCall(contract common.Address, data []byte) ([]byte, error) {
// Pack up the request into an RPC argument
args := struct {
To common.Address `json:"to"`
Data string `json:"data"`
}{
To: contract,
Data: common.ToHex(data),
}
// Execute the RPC call and retrieve the response
res, err := b.request("eth_call", []interface{}{args, "pending"})
if err != nil {
return nil, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return nil, err
}
// Convert the response back to a Go byte slice and return
return common.FromHex(hex), nil
}
// AccountNonce implements ContractTransactor.AccountNonce, delegating the
// current account nonce retrieval to the remote node.
func (b *rpcBackend) AccountNonce(account common.Address) (uint64, error) {
res, err := b.request("eth_getTransactionCount", []interface{}{account.Hex(), "pending"})
if err != nil {
return 0, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return 0, err
}
return new(big.Int).SetBytes(common.FromHex(hex)).Uint64(), nil
}
// GasPrice implements ContractTransactor.GasPrice, delegating the gas price
// oracle request to the remote node.
func (b *rpcBackend) GasPrice() (*big.Int, error) {
res, err := b.request("eth_gasPrice", nil)
if err != nil {
return nil, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return nil, err
}
return new(big.Int).SetBytes(common.FromHex(hex)), nil
}
// GasLimit implements ContractTransactor.GasLimit, delegating the gas estimation
// to the remote node.
func (b *rpcBackend) GasLimit(sender, contract common.Address, value *big.Int, data []byte) (*big.Int, error) {
// Pack up the request into an RPC argument
args := struct {
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *rpc.HexNumber `json:"value"`
Data string `json:"data"`
}{
From: sender,
To: contract,
Data: common.ToHex(data),
Value: rpc.NewHexNumber(value),
}
// Execute the RPC call and retrieve the response
res, err := b.request("eth_estimateGas", []interface{}{args})
if err != nil {
return nil, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return nil, err
}
// Convert the response back to a Go byte slice and return
return new(big.Int).SetBytes(common.FromHex(hex)), nil
}
// Transact implements ContractTransactor.SendTransaction, delegating the raw
// transaction injection to the remote node.
func (b *rpcBackend) SendTransaction(tx *types.Transaction) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
res, err := b.request("eth_sendRawTransaction", []interface{}{common.ToHex(data)})
if err != nil {
return err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,46 @@
// Copyright 2016 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 backends
import (
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// nilBackend implements bind.ContractBackend, but panics on any method call.
// Its sole purpose is to support the binding tests to construct the generated
// wrappers without calling any methods on them.
type nilBackend struct{}
func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) {
panic("not implemented")
}
func (*nilBackend) GasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
panic("not implemented")
}
func (*nilBackend) GasPrice() (*big.Int, error) { panic("not implemented") }
func (*nilBackend) AccountNonce(common.Address) (uint64, error) { panic("not implemented") }
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }
// NewNilBackend creates a new binding backend that can be used for instantiation
// but will panic on any invocation. Its sole purpose is to help testing.
func NewNilBackend() bind.ContractBackend {
return new(nilBackend)
}

View File

@ -0,0 +1,202 @@
// Copyright 2016 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 backends
import (
"encoding/json"
"fmt"
"math/big"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
// rpcBackend implements bind.ContractBackend, and acts as the data provider to
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
// all its functionality.
//
// Note: The current implementation is a blocking one. This should be replaced
// by a proper async version when a real RPC client is created.
type rpcBackend struct {
client rpc.Client // RPC client connection to interact with an API server
autoid uint32 // ID number to use for the next API request
lock sync.Mutex // Singleton access until we get to request multiplexing
}
// NewRPCBackend creates a new binding backend to an RPC provider that can be
// used to interact with remote contracts.
func NewRPCBackend(client rpc.Client) bind.ContractBackend {
return &rpcBackend{
client: client,
}
}
// request is a JSON RPC request package assembled internally from the client
// method calls.
type request struct {
JsonRpc string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
Id int `json:"id"` // Auto incrementing ID number for this request
Method string `json:"method"` // Remote procedure name to invoke on the server
Params []interface{} `json:"params"` // List of parameters to pass through (keep types simple)
}
// response is a JSON RPC response package sent back from the API server.
type response struct {
JsonRpc string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
Id int `json:"id"` // Auto incrementing ID number for this request
Error json.RawMessage `json:"error"` // Any error returned by the remote side
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
}
// request forwards an API request to the RPC server, and parses the response.
//
// This is currently painfully non-concurrent, but it will have to do until we
// find the time for niceties like this :P
func (backend *rpcBackend) request(method string, params []interface{}) (json.RawMessage, error) {
backend.lock.Lock()
defer backend.lock.Unlock()
// Ugly hack to serialize an empty list properly
if params == nil {
params = []interface{}{}
}
// Assemble the request object
req := &request{
JsonRpc: "2.0",
Id: int(atomic.AddUint32(&backend.autoid, 1)),
Method: method,
Params: params,
}
if err := backend.client.Send(req); err != nil {
return nil, err
}
res := new(response)
if err := backend.client.Recv(res); err != nil {
return nil, err
}
if len(res.Error) > 0 {
return nil, fmt.Errorf("remote error: %s", string(res.Error))
}
return res.Result, nil
}
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
// a contract call to the remote node, returning the reply to for local processing.
func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
// Pack up the request into an RPC argument
args := struct {
To common.Address `json:"to"`
Data string `json:"data"`
}{
To: contract,
Data: common.ToHex(data),
}
// Execute the RPC call and retrieve the response
block := "latest"
if pending {
block = "pending"
}
res, err := b.request("eth_call", []interface{}{args, block})
if err != nil {
return nil, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return nil, err
}
// Convert the response back to a Go byte slice and return
return common.FromHex(hex), nil
}
// AccountNonce implements ContractTransactor.AccountNonce, delegating the
// current account nonce retrieval to the remote node.
func (b *rpcBackend) AccountNonce(account common.Address) (uint64, error) {
res, err := b.request("eth_getTransactionCount", []interface{}{account.Hex(), "pending"})
if err != nil {
return 0, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return 0, err
}
return new(big.Int).SetBytes(common.FromHex(hex)).Uint64(), nil
}
// GasPrice implements ContractTransactor.GasPrice, delegating the gas price
// oracle request to the remote node.
func (b *rpcBackend) GasPrice() (*big.Int, error) {
res, err := b.request("eth_gasPrice", nil)
if err != nil {
return nil, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return nil, err
}
return new(big.Int).SetBytes(common.FromHex(hex)), nil
}
// GasLimit implements ContractTransactor.GasLimit, delegating the gas estimation
// to the remote node.
func (b *rpcBackend) GasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
// Pack up the request into an RPC argument
args := struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Value *rpc.HexNumber `json:"value"`
Data string `json:"data"`
}{
From: sender,
To: contract,
Data: common.ToHex(data),
Value: rpc.NewHexNumber(value),
}
// Execute the RPC call and retrieve the response
res, err := b.request("eth_estimateGas", []interface{}{args})
if err != nil {
return nil, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return nil, err
}
// Convert the response back to a Go byte slice and return
return new(big.Int).SetBytes(common.FromHex(hex)), nil
}
// Transact implements ContractTransactor.SendTransaction, delegating the raw
// transaction injection to the remote node.
func (b *rpcBackend) SendTransaction(tx *types.Transaction) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
res, err := b.request("eth_sendRawTransaction", []interface{}{common.ToHex(data)})
if err != nil {
return err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,184 @@
// Copyright 2016 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 backends
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
)
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
// the background. Its main purpose is to allow easily testing contract bindings.
type SimulatedBackend struct {
database ethdb.Database // In memory database to store our testing data
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
pendingBlock *types.Block // Currently pending block that will be imported on request
pendingState *state.StateDB // Currently pending state that will be the active on on request
}
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes.
func NewSimulatedBackend(accounts ...core.GenesisAccount) *SimulatedBackend {
database, _ := ethdb.NewMemDatabase()
core.WriteGenesisBlockForTesting(database, accounts...)
blockchain, _ := core.NewBlockChain(database, new(core.FakePow), new(event.TypeMux))
backend := &SimulatedBackend{
database: database,
blockchain: blockchain,
}
backend.Rollback()
return backend
}
// Commit imports all the pending transactions as a single block and starts a
// fresh new state.
func (b *SimulatedBackend) Commit() {
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
}
b.Rollback()
}
// Rollback aborts all pending transactions, reverting to the last committed state.
func (b *SimulatedBackend) Rollback() {
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
}
// ContractCall implements ContractCaller.ContractCall, executing the specified
// contract with the given input data.
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
// Create a copy of the current state db to screw around with
var (
block *types.Block
statedb *state.StateDB
)
if pending {
block, statedb = b.pendingBlock, b.pendingState
} else {
block = b.blockchain.CurrentBlock()
statedb, _ = b.blockchain.State()
}
statedb = statedb.Copy()
// Set infinite balance to the a fake caller account
from := statedb.GetOrNewStateObject(common.Address{})
from.SetBalance(common.MaxBig)
// Assemble the call invocation to measure the gas usage
msg := callmsg{
from: from,
to: &contract,
gasPrice: new(big.Int),
gasLimit: common.MaxBig,
value: new(big.Int),
data: data,
}
// Execute the call and return
vmenv := core.NewEnv(statedb, b.blockchain, msg, block.Header())
gaspool := new(core.GasPool).AddGas(common.MaxBig)
out, _, err := core.ApplyMessage(vmenv, msg, gaspool)
return out, err
}
// AccountNonce implements ContractTransactor.AccountNonce, retrieving the nonce
// currently pending for the account.
func (b *SimulatedBackend) AccountNonce(account common.Address) (uint64, error) {
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil
}
// GasPrice implements ContractTransactor.GasPrice. Since the simulated chain
// doens't have miners, we just return a gas price of 1 for any call.
func (b *SimulatedBackend) GasPrice() (*big.Int, error) {
return big.NewInt(1), nil
}
// GasLimit implements ContractTransactor.GasLimit, executing the requested code
// against the currently pending block/state and returning the used gas.
func (b *SimulatedBackend) GasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
// Create a copy of the currently pending state db to screw around with
var (
block = b.pendingBlock
statedb = b.pendingState.Copy()
)
// Set infinite balance to the a fake caller account
from := statedb.GetOrNewStateObject(sender)
from.SetBalance(common.MaxBig)
// Assemble the call invocation to measure the gas usage
msg := callmsg{
from: from,
to: contract,
gasPrice: new(big.Int),
gasLimit: common.MaxBig,
value: value,
data: data,
}
// Execute the call and return
vmenv := core.NewEnv(statedb, b.blockchain, msg, block.Header())
gaspool := new(core.GasPool).AddGas(common.MaxBig)
_, gas, err := core.ApplyMessage(vmenv, msg, gaspool)
return gas, err
}
// Transact implements ContractTransactor.SendTransaction, delegating the raw
// transaction injection to the remote node.
func (b *SimulatedBackend) SendTransaction(tx *types.Transaction) error {
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx)
}
block.AddTx(tx)
})
b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
return nil
}
// callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct {
from *state.StateObject
to *common.Address
gasLimit *big.Int
gasPrice *big.Int
value *big.Int
data []byte
}
func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil }
func (m callmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil }
func (m callmsg) Nonce() uint64 { return m.from.Nonce() }
func (m callmsg) To() *common.Address { return m.to }
func (m callmsg) GasPrice() *big.Int { return m.gasPrice }
func (m callmsg) Gas() *big.Int { return m.gasLimit }
func (m callmsg) Value() *big.Int { return m.value }
func (m callmsg) Data() []byte { return m.data }

View File

@ -24,14 +24,21 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
) )
// SignerFn is a signer function callback when a contract requires a method to // SignerFn is a signer function callback when a contract requires a method to
// sign the transaction before submission. // sign the transaction before submission.
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error)
// AuthOpts is the authorization data required to create a valid Ethereum transaction. // CallOpts is the collection of options to fine tune a contract call request.
type AuthOpts struct { type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one
}
// TransactOpts is the collection of authorization data required to create a
// valid Ethereum transaction.
type TransactOpts struct {
Account common.Address // Ethereum account to send the transaction from Account common.Address // Ethereum account to send the transaction from
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
Signer SignerFn // Method to use for signing the transaction (mandatory) Signer SignerFn // Method to use for signing the transaction (mandatory)
@ -62,16 +69,43 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller
} }
} }
// DeployContract deploys a contract onto the Ethereum blockchain and binds the
// deployment address with a Go wrapper.
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
// Sanity check the authorization options
if opts == nil {
return common.Address{}, nil, nil, errors.New("transaction options missing")
}
// Otherwise try to deploy the contract
c := NewBoundContract(common.Address{}, abi, backend.(ContractCaller), backend.(ContractTransactor))
input, err := c.abi.Pack("", params...)
if err != nil {
return common.Address{}, nil, nil, err
}
tx, err := c.transact(opts, nil, append(bytecode, input...))
if err != nil {
return common.Address{}, nil, nil, err
}
c.address = crypto.CreateAddress(opts.Account, tx.Nonce())
return c.address, tx, c, nil
}
// Call invokes the (constant) contract method with params as input values and // Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple // sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named // returns, a slice of interfaces for anonymous returns and a struct for named
// returns. // returns.
func (c *BoundContract) Call(result interface{}, method string, params ...interface{}) error { func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error {
// Don't crash on a lazy user
if opts == nil {
opts = new(CallOpts)
}
// Pack the input, call and unpack the results
input, err := c.abi.Pack(method, params...) input, err := c.abi.Pack(method, params...)
if err != nil { if err != nil {
return err return err
} }
output, err := c.caller.ContractCall(c.address, input) output, err := c.caller.ContractCall(c.address, input, opts.Pending)
if err != nil { if err != nil {
return err return err
} }
@ -80,11 +114,24 @@ func (c *BoundContract) Call(result interface{}, method string, params ...interf
// Transact invokes the (paid) contract method with params as input values and // Transact invokes the (paid) contract method with params as input values and
// value as the fund transfer to the contract. // value as the fund transfer to the contract.
func (c *BoundContract) Transact(opts *AuthOpts, method string, params ...interface{}) (*types.Transaction, error) { func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
// Sanity check the authorization options
if opts == nil {
return nil, errors.New("transaction options missing")
}
// Otherwise pack up the parameters and invoke the contract
input, err := c.abi.Pack(method, params...) input, err := c.abi.Pack(method, params...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.transact(opts, &c.address, input)
}
// transact executes an actual transaction invocation, first deriving any missing
// authorization fields, and then scheduling the transaction for execution.
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
var err error
// Ensure a valid value field and resolve the account nonce // Ensure a valid value field and resolve the account nonce
value := opts.Value value := opts.Value
if value == nil { if value == nil {
@ -109,13 +156,18 @@ func (c *BoundContract) Transact(opts *AuthOpts, method string, params ...interf
} }
gasLimit := opts.GasLimit gasLimit := opts.GasLimit
if gasLimit == nil { if gasLimit == nil {
gasLimit, err = c.transactor.GasLimit(opts.Account, c.address, value, input) gasLimit, err = c.transactor.GasLimit(opts.Account, contract, value, input)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err) return nil, fmt.Errorf("failed to exstimate gas needed: %v", err)
} }
} }
// Create the transaction, sign it and schedule it for execution // Create the transaction, sign it and schedule it for execution
rawTx := types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) var rawTx *types.Transaction
if contract == nil {
rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input)
} else {
rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input)
}
if opts.Signer == nil { if opts.Signer == nil {
return nil, errors.New("no signer to authorize the transaction with") return nil, errors.New("no signer to authorize the transaction with")
} }

View File

@ -31,16 +31,18 @@ import (
// to be used as is in client code, but rather as an intermediate struct which // to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention opposed to having to // enforces compile time type safety and naming convention opposed to having to
// manually maintain hard coded strings that break on runtime. // manually maintain hard coded strings that break on runtime.
func Bind(jsonABI string, pkg string, kind string) (string, error) { func Bind(abijson string, bytecode string, pkg string, kind string) (string, error) {
// Parse the actual ABI to generate the binding for // Parse the actual ABI to generate the binding for
abi, err := abi.JSON(strings.NewReader(jsonABI)) abi, err := abi.JSON(strings.NewReader(abijson))
if err != nil { if err != nil {
return "", err return "", err
} }
// Generate the contract type, fields and methods // Generate the contract type, fields and methods
code := new(bytes.Buffer) code := new(bytes.Buffer)
kind = strings.ToUpper(kind[:1]) + kind[1:] kind = strings.ToUpper(kind[:1]) + kind[1:]
fmt.Fprintf(code, "%s\n", bindContract(kind, jsonABI))
fmt.Fprintf(code, "%s\n", bindContract(kind, strings.TrimSpace(abijson)))
fmt.Fprintf(code, "%s\n", bindConstructor(kind, strings.TrimSpace(bytecode), abi.Constructor))
methods := make([]string, 0, len(abi.Methods)) methods := make([]string, 0, len(abi.Methods))
for name, _ := range abi.Methods { for name, _ := range abi.Methods {
@ -54,12 +56,14 @@ func Bind(jsonABI string, pkg string, kind string) (string, error) {
// Format the code with goimports and return // Format the code with goimports and return
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
fmt.Fprintf(buffer, "// This file is an automatically generated Go binding based on the contract ABI\n")
fmt.Fprintf(buffer, "// defined in %sABI. Do not modify as any change will likely be lost!\n\n", kind)
fmt.Fprintf(buffer, "package %s\n\n", pkg) fmt.Fprintf(buffer, "package %s\n\n", pkg)
fmt.Fprintf(buffer, "%s\n\n", string(code.Bytes())) fmt.Fprintf(buffer, "%s\n\n", string(code.Bytes()))
blob, err := imports.Process("", buffer.Bytes(), nil) blob, err := imports.Process("", buffer.Bytes(), nil)
if err != nil { if err != nil {
return "", err return "", fmt.Errorf("%v\n%s", err, code)
} }
return string(blob), nil return string(blob), nil
} }
@ -67,13 +71,13 @@ func Bind(jsonABI string, pkg string, kind string) (string, error) {
// bindContract generates the basic wrapper code for interacting with an Ethereum // bindContract generates the basic wrapper code for interacting with an Ethereum
// contract via the abi package. All contract methods will call into the generic // contract via the abi package. All contract methods will call into the generic
// ones generated here. // ones generated here.
func bindContract(kind string, abi string) string { func bindContract(kind string, abijson string) string {
code := "" code := ""
// Generate the hard coded ABI used for Ethereum interaction // Generate the hard coded ABI used for Ethereum interaction
code += fmt.Sprintf("// Ethereum ABI used to generate the binding from.\nconst %sABI = `%s`\n\n", kind, strings.TrimSpace(abi)) code += fmt.Sprintf("// Ethereum ABI used to generate the binding from.\nconst %sABI = `%s`\n\n", kind, abijson)
// Generate the Go struct with all the maintenance fields // Generate the high level contract wrapper types
code += fmt.Sprintf("// %s is an auto generated Go binding around an Ethereum contract.\n", kind) code += fmt.Sprintf("// %s is an auto generated Go binding around an Ethereum contract.\n", kind)
code += fmt.Sprintf("type %s struct {\n", kind) code += fmt.Sprintf("type %s struct {\n", kind)
code += fmt.Sprintf(" %sCaller // Read-only binding to the contract\n", kind) code += fmt.Sprintf(" %sCaller // Read-only binding to the contract\n", kind)
@ -82,61 +86,122 @@ func bindContract(kind string, abi string) string {
code += fmt.Sprintf("// %sCaller is an auto generated read-only Go binding around an Ethereum contract.\n", kind) code += fmt.Sprintf("// %sCaller is an auto generated read-only Go binding around an Ethereum contract.\n", kind)
code += fmt.Sprintf("type %sCaller struct {\n", kind) code += fmt.Sprintf("type %sCaller struct {\n", kind)
code += fmt.Sprintf(" common *common%s // Contract binding common to callers and transactors\n", kind) code += fmt.Sprintf(" contract *bind.BoundContract // Generic contract wrapper for the low level calls\n")
code += fmt.Sprintf("}\n\n") code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// %sTransactor is an auto generated write-only Go binding around an Ethereum contract.\n", kind) code += fmt.Sprintf("// %sTransactor is an auto generated write-only Go binding around an Ethereum contract.\n", kind)
code += fmt.Sprintf("type %sTransactor struct {\n", kind) code += fmt.Sprintf("type %sTransactor struct {\n", kind)
code += fmt.Sprintf(" common *common%s // Contract binding common to callers and transactors\n", kind) code += fmt.Sprintf(" contract *bind.BoundContract // Generic contract wrapper for the low level calls\n")
code += fmt.Sprintf("}\n\n") code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// common%s is an auto generated Go binding around an Ethereum contract.\n", kind) // Generate the high level contract session wrapper types
code += fmt.Sprintf("type common%s struct {\n", kind) code += fmt.Sprintf("// %sSession is an auto generated Go binding around an Ethereum contract,\n// with pre-set call and transact options.\n", kind)
code += fmt.Sprintf(" contract *bind.BoundContract // Generic contract wrapper for the low level calls\n") code += fmt.Sprintf("type %sSession struct {\n", kind)
code += fmt.Sprintf(" Contract *%s // Generic contract binding to set the session for\n", kind)
code += fmt.Sprintf(" CallOpts bind.CallOpts // Call options to use throughout this session\n")
code += fmt.Sprintf(" TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session\n")
code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// %sCallerSession is an auto generated read-only Go binding around an Ethereum contract,\n// with pre-set call options.\n", kind)
code += fmt.Sprintf("type %sCallerSession struct {\n", kind)
code += fmt.Sprintf(" Contract *%sCaller // Generic contract caller binding to set the session for\n", kind)
code += fmt.Sprintf(" CallOpts bind.CallOpts // Call options to use throughout this session\n")
code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// %sTransactorSession is an auto generated write-only Go binding around an Ethereum contract,\n// with pre-set transact options.\n", kind)
code += fmt.Sprintf("type %sTransactorSession struct {\n", kind)
code += fmt.Sprintf(" Contract *%sTransactor // Generic contract transactor binding to set the session for\n", kind)
code += fmt.Sprintf(" TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session\n")
code += fmt.Sprintf("}\n\n") code += fmt.Sprintf("}\n\n")
// Generate the constructor to create a bound contract // Generate the constructor to create a bound contract
code += fmt.Sprintf("// New%s creates a new instance of %s, bound to a specific deployed contract.\n", kind, kind) code += fmt.Sprintf("// New%s creates a new instance of %s, bound to a specific deployed contract.\n", kind, kind)
code += fmt.Sprintf("func New%s(address common.Address, backend bind.ContractBackend) (*%s, error) {\n", kind, kind) code += fmt.Sprintf("func New%s(address common.Address, backend bind.ContractBackend) (*%s, error) {\n", kind, kind)
code += fmt.Sprintf(" common, err := newCommon%s(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor))\n", kind) code += fmt.Sprintf(" contract, err := bind%s(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor))\n", kind)
code += fmt.Sprintf(" if err != nil {\n") code += fmt.Sprintf(" if err != nil {\n")
code += fmt.Sprintf(" return nil, err\n") code += fmt.Sprintf(" return nil, err\n")
code += fmt.Sprintf(" }\n") code += fmt.Sprintf(" }\n")
code += fmt.Sprintf(" return &%s{%sCaller: %sCaller{common: common}, %sTransactor: %sTransactor{common: common}}, nil\n", kind, kind, kind, kind, kind) code += fmt.Sprintf(" return &%s{%sCaller: %sCaller{contract: contract}, %sTransactor: %sTransactor{contract: contract}}, nil\n", kind, kind, kind, kind, kind)
code += fmt.Sprintf("}\n\n") code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// New%sCaller creates a new read-only instance of %s, bound to a specific deployed contract.\n", kind, kind) code += fmt.Sprintf("// New%sCaller creates a new read-only instance of %s, bound to a specific deployed contract.\n", kind, kind)
code += fmt.Sprintf("func New%sCaller(address common.Address, caller bind.ContractCaller) (*%sCaller, error) {\n", kind, kind) code += fmt.Sprintf("func New%sCaller(address common.Address, caller bind.ContractCaller) (*%sCaller, error) {\n", kind, kind)
code += fmt.Sprintf(" common, err := newCommon%s(address, caller, nil)\n", kind) code += fmt.Sprintf(" contract, err := bind%s(address, caller, nil)\n", kind)
code += fmt.Sprintf(" if err != nil {\n") code += fmt.Sprintf(" if err != nil {\n")
code += fmt.Sprintf(" return nil, err\n") code += fmt.Sprintf(" return nil, err\n")
code += fmt.Sprintf(" }\n") code += fmt.Sprintf(" }\n")
code += fmt.Sprintf(" return &%sCaller{common: common}, nil\n", kind) code += fmt.Sprintf(" return &%sCaller{contract: contract}, nil\n", kind)
code += fmt.Sprintf("}\n\n") code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// New%sTransactor creates a new write-only instance of %s, bound to a specific deployed contract.\n", kind, kind) code += fmt.Sprintf("// New%sTransactor creates a new write-only instance of %s, bound to a specific deployed contract.\n", kind, kind)
code += fmt.Sprintf("func New%sTransactor(address common.Address, transactor bind.ContractTransactor) (*%sTransactor, error) {\n", kind, kind) code += fmt.Sprintf("func New%sTransactor(address common.Address, transactor bind.ContractTransactor) (*%sTransactor, error) {\n", kind, kind)
code += fmt.Sprintf(" common, err := newCommon%s(address, nil, transactor)\n", kind) code += fmt.Sprintf(" contract, err := bind%s(address, nil, transactor)\n", kind)
code += fmt.Sprintf(" if err != nil {\n") code += fmt.Sprintf(" if err != nil {\n")
code += fmt.Sprintf(" return nil, err\n") code += fmt.Sprintf(" return nil, err\n")
code += fmt.Sprintf(" }\n") code += fmt.Sprintf(" }\n")
code += fmt.Sprintf(" return &%sTransactor{common: common}, nil\n", kind) code += fmt.Sprintf(" return &%sTransactor{contract: contract}, nil\n", kind)
code += fmt.Sprintf("}\n\n") code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("// newCommon%s creates an internal instance of %s, bound to a specific deployed contract.\n", kind, kind) code += fmt.Sprintf("// bind%s binds a generic wrapper to an already deployed contract.\n", kind)
code += fmt.Sprintf("func newCommon%s(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*common%s, error) {\n", kind, kind) code += fmt.Sprintf("func bind%s(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) {\n", kind)
code += fmt.Sprintf(" parsed, err := abi.JSON(strings.NewReader(%sABI))\n", kind) code += fmt.Sprintf(" parsed, err := abi.JSON(strings.NewReader(%sABI))\n", kind)
code += fmt.Sprintf(" if err != nil {\n") code += fmt.Sprintf(" if err != nil {\n")
code += fmt.Sprintf(" return nil, err\n") code += fmt.Sprintf(" return nil, err\n")
code += fmt.Sprintf(" }\n") code += fmt.Sprintf(" }\n")
code += fmt.Sprintf(" return &common%s{\n", kind) code += fmt.Sprintf(" return bind.NewBoundContract(address, parsed, caller, transactor), nil\n")
code += fmt.Sprintf(" contract: bind.NewBoundContract(address, parsed, caller, transactor),\n")
code += fmt.Sprintf(" }, nil\n")
code += fmt.Sprintf("}") code += fmt.Sprintf("}")
return code return code
} }
// bindConstructor
func bindConstructor(kind string, bytecode string, constructor abi.Method) string {
// If no byte code was supplied, we cannot deploy
if bytecode == "" {
return ""
}
// Otherwise store the bytecode into a global constant
code := fmt.Sprintf("// Ethereum VM bytecode used for deploying new contracts.\nconst %sBin = `%s`\n\n", kind, bytecode)
// Generate the argument list for the constructor
args := make([]string, 0, len(constructor.Inputs))
for i, arg := range constructor.Inputs {
param := arg.Name
if param == "" {
param = fmt.Sprintf("arg%d", i)
}
args = append(args, fmt.Sprintf("%s %s", param, bindType(arg.Type)))
}
arglist := ""
if len(args) > 0 {
arglist = "," + strings.Join(args, ",")
}
// Generate the cal parameter list for the dpeloyer
params := make([]string, len(args))
for i, param := range args {
params[i] = strings.Split(param, " ")[0]
}
paramlist := ""
if len(params) > 0 {
paramlist = "," + strings.Join(params, ",")
}
// And generate the global deployment function
code += fmt.Sprintf("// Deploy%s deploys a new contract, binding an instance of %s to it.\n", kind, kind)
code += fmt.Sprintf("func Deploy%s(auth *bind.TransactOpts, backend bind.ContractBackend %s) (common.Address, *types.Transaction, *%s, error) {\n", kind, arglist, kind)
code += fmt.Sprintf(" parsed, err := abi.JSON(strings.NewReader(%sABI))\n", kind)
code += fmt.Sprintf(" if err != nil {\n")
code += fmt.Sprintf(" return common.Address{}, nil, nil, err\n")
code += fmt.Sprintf(" }\n")
code += fmt.Sprintf(" address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(%sBin), backend %s)\n", kind, paramlist)
code += fmt.Sprintf(" if err != nil {\n")
code += fmt.Sprintf(" return common.Address{}, nil, nil, err\n")
code += fmt.Sprintf(" }\n")
code += fmt.Sprintf(" return address, tx, &%s{%sCaller: %sCaller{contract: contract}, %sTransactor: %sTransactor{contract: contract}}, nil\n", kind, kind, kind, kind, kind)
code += fmt.Sprintf("}\n\n")
return code
}
// bindMethod // bindMethod
func bindMethod(kind string, method abi.Method) string { func bindMethod(kind string, method abi.Method) string {
var ( var (
@ -163,13 +228,55 @@ func bindMethod(kind string, method abi.Method) string {
docs += fmt.Sprintf("// \n") docs += fmt.Sprintf("// \n")
docs += fmt.Sprintf("// Solidity: %s", strings.TrimPrefix(method.String(), "function ")) docs += fmt.Sprintf("// Solidity: %s", strings.TrimPrefix(method.String(), "function "))
// Generate the passthrough argument list for sessions
params := make([]string, len(args))
for i, param := range args {
params[i] = strings.Split(param, " ")[0]
}
sessargs := ""
if len(params) > 0 {
sessargs = "," + strings.Join(params, ",")
}
// Generate the method itself for both the read/write version and the combo too // Generate the method itself for both the read/write version and the combo too
code := fmt.Sprintf("%s\n", prologue) code := fmt.Sprintf("%s\n", prologue)
if method.Const { if method.Const {
code += fmt.Sprintf("%s\nfunc (_%s *%sCaller) %s(%s) (%s) {\n%s\n}\n", docs, kind, kind, name, strings.Join(args, ","), strings.Join(returns, ","), bindCallBody(kind, method.Name, args, returns)) // Create the main call implementation
callargs := append([]string{"opts *bind.CallOpts"}, args...)
code += fmt.Sprintf("%s\n", docs)
code += fmt.Sprintf("func (_%s *%sCaller) %s(%s) (%s) {\n", kind, kind, name, strings.Join(callargs, ","), strings.Join(returns, ","))
code += fmt.Sprintf(" %s\n", bindCallBody(kind, method.Name, callargs, returns))
code += fmt.Sprintf("}\n\n")
// Create the wrapping session call implementation
code += fmt.Sprintf("%s\n", docs)
code += fmt.Sprintf("func (_%s *%sSession) %s(%s) (%s) {\n", kind, kind, name, strings.Join(args, ","), strings.Join(returns, ","))
code += fmt.Sprintf(" return _%s.Contract.%s(&_%s.CallOpts %s)\n", kind, name, kind, sessargs)
code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("%s\n", docs)
code += fmt.Sprintf("func (_%s *%sCallerSession) %s(%s) (%s) {\n", kind, kind, name, strings.Join(args, ","), strings.Join(returns, ","))
code += fmt.Sprintf(" return _%s.Contract.%s(&_%s.CallOpts %s)\n", kind, name, kind, sessargs)
code += fmt.Sprintf("}\n\n")
} else { } else {
args = append([]string{"auth *bind.AuthOpts"}, args...) // Create the main transaction implementation
code += fmt.Sprintf("%s\nfunc (_%s *%sTransactor) %s(%s) (*types.Transaction, error) {\n%s\n}\n", docs, kind, kind, name, strings.Join(args, ","), bindTransactionBody(kind, method.Name, args)) txargs := append([]string{"opts *bind.TransactOpts"}, args...)
code += fmt.Sprintf("%s\n", docs)
code += fmt.Sprintf("func (_%s *%sTransactor) %s(%s) (*types.Transaction, error) {\n", kind, kind, name, strings.Join(txargs, ","))
code += fmt.Sprintf(" %s\n", bindTransactionBody(kind, method.Name, txargs))
code += fmt.Sprintf("}\n\n")
// Create the wrapping session call implementation
code += fmt.Sprintf("%s\n", docs)
code += fmt.Sprintf("func (_%s *%sSession) %s(%s) (*types.Transaction, error) {\n", kind, kind, name, strings.Join(args, ","))
code += fmt.Sprintf(" return _%s.Contract.%s(&_%s.TransactOpts %s)\n", kind, name, kind, sessargs)
code += fmt.Sprintf("}\n\n")
code += fmt.Sprintf("%s\n", docs)
code += fmt.Sprintf("func (_%s *%sTransactorSession) %s(%s) (*types.Transaction, error) {\n", kind, kind, name, strings.Join(args, ","))
code += fmt.Sprintf(" return _%s.Contract.%s(&_%s.TransactOpts %s)\n", kind, name, kind, sessargs)
code += fmt.Sprintf("}\n\n")
} }
return code return code
} }
@ -264,7 +371,7 @@ func bindCallBody(kind string, method string, params []string, returns []string)
name := fmt.Sprintf("ret%d", i) name := fmt.Sprintf("ret%d", i)
rets = append(rets, name) rets = append(rets, name)
body += fmt.Sprintf("%s = new(%s)\n", name, strings.TrimPrefix(kind, "*")) body += fmt.Sprintf("%s = new(%s)\n", name, kind)
} }
body += ")\n" body += ")\n"
} }
@ -274,8 +381,8 @@ func bindCallBody(kind string, method string, params []string, returns []string)
result = "[]interface{}{" + result + "}" result = "[]interface{}{" + result + "}"
} }
// Extract the parameter list into a flat variable name list // Extract the parameter list into a flat variable name list
inputs := make([]string, len(params)) inputs := make([]string, len(params)-1) // Omit the call options
for i, param := range params { for i, param := range params[1:] {
inputs[i] = strings.Split(param, " ")[0] inputs[i] = strings.Split(param, " ")[0]
} }
input := "" input := ""
@ -283,15 +390,11 @@ func bindCallBody(kind string, method string, params []string, returns []string)
input = "," + strings.Join(inputs, ",") input = "," + strings.Join(inputs, ",")
} }
// Request executing the contract call and return the results with the errors // Request executing the contract call and return the results with the errors
body += fmt.Sprintf("err := _%s.common.contract.Call(%s, \"%s\" %s)\n", kind, result, method, input) body += fmt.Sprintf("err := _%s.contract.Call(opts, %s, \"%s\" %s)\n", kind, result, method, input)
outs := make([]string, 0, len(returns)) outs := make([]string, 0, len(returns))
for i, ret := range returns[:len(returns)-1] { // Handle th final error separately for _, ret := range rets { // Handle th final error separately
if strings.HasPrefix(ret, "*") { outs = append(outs, "*"+ret)
outs = append(outs, rets[i])
} else {
outs = append(outs, "*"+rets[i])
}
} }
outs = append(outs, "err") outs = append(outs, "err")
@ -313,5 +416,5 @@ func bindTransactionBody(kind string, method string, params []string) string {
input = "," + strings.Join(inputs, ",") input = "," + strings.Join(inputs, ",")
} }
// Request executing the contract call and return the results with the errors // Request executing the contract call and return the results with the errors
return fmt.Sprintf("return _%s.common.contract.Transact(auth, \"%s\" %s)", kind, method, input) return fmt.Sprintf("return _%s.contract.Transact(opts, \"%s\" %s)", kind, method, input)
} }

File diff suppressed because one or more lines are too long

View File

@ -27,9 +27,10 @@ import (
var ( var (
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind") abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind")
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)")
pkgFlag = flag.String("pkg", "", "Go package name to generate the binding into") pkgFlag = flag.String("pkg", "", "Go package name to generate the binding into")
typFlag = flag.String("type", "", "Go struct name for the binding (default = package name)") typFlag = flag.String("type", "", "Go struct name for the binding (default = package name)")
outFlag = flag.String("out", "", "Output path for the generated binding") outFlag = flag.String("out", "", "Output path for the generated binding (default = stdout)")
) )
func main() { func main() {
@ -44,17 +45,25 @@ func main() {
fmt.Printf("No destination Go package specified (--pkg)\n") fmt.Printf("No destination Go package specified (--pkg)\n")
os.Exit(-1) os.Exit(-1)
} }
// Generate the contract binding // Read the ABI json from disk and optionally the contract bytecode too
in, err := ioutil.ReadFile(*abiFlag) abi, err := ioutil.ReadFile(*abiFlag)
if err != nil { if err != nil {
fmt.Printf("Failed to read input ABI: %v\n", err) fmt.Printf("Failed to read input ABI: %v\n", err)
os.Exit(-1) os.Exit(-1)
} }
bin := []byte{}
if *binFlag != "" {
if bin, err = ioutil.ReadFile(*binFlag); err != nil {
fmt.Printf("Failed to read input bytecode: %v\n", err)
os.Exit(-1)
}
}
// Generate the contract binding
kind := *typFlag kind := *typFlag
if kind == "" { if kind == "" {
kind = *pkgFlag kind = *pkgFlag
} }
code, err := bind.Bind(string(in), *pkgFlag, kind) code, err := bind.Bind(string(abi), string(bin), *pkgFlag, kind)
if err != nil { if err != nil {
fmt.Printf("Failed to generate ABI binding: %v\n", err) fmt.Printf("Failed to generate ABI binding: %v\n", err)
os.Exit(-1) os.Exit(-1)