d62d5fe59a
In this commit, contract bindings and their backend start using the Ethereum Go API interfaces offered by ethclient. This makes ethclient a suitable replacement for the old remote backend and gets us one step closer to the final stable Go API that is planned for go-ethereum 1.5. The changes in detail: * Pending state is optional for read only contract bindings. BoundContract attempts to discover the Pending* methods via an interface assertion. There are a couple of advantages to this: ContractCaller is just two methods and can be implemented on top of pretty much anything that provides Ethereum data. Since the backend interfaces are now disjoint, ContractBackend can simply be declared as a union of the reader and writer side. * Caching of HasCode is removed. The caching could go wrong in case of chain reorganisations and removing it simplifies the code a lot. We'll figure out a performant way of providing ErrNoCode before the 1.5 release. * BoundContract now ensures that the backend receives a non-nil context with every call.
233 lines
8.3 KiB
Go
233 lines
8.3 KiB
Go
// 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"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// SignerFn is a signer function callback when a contract requires a method to
|
|
// sign the transaction before submission.
|
|
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error)
|
|
|
|
// CallOpts is the collection of options to fine tune a contract call request.
|
|
type CallOpts struct {
|
|
Pending bool // Whether to operate on the pending state or the last known one
|
|
|
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
|
}
|
|
|
|
// TransactOpts is the collection of authorization data required to create a
|
|
// valid Ethereum transaction.
|
|
type TransactOpts struct {
|
|
From common.Address // Ethereum account to send the transaction from
|
|
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
|
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
|
|
|
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
|
|
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
|
GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%)
|
|
|
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
|
}
|
|
|
|
// BoundContract is the base wrapper object that reflects a contract on the
|
|
// Ethereum network. It contains a collection of methods that are used by the
|
|
// higher level contract bindings to operate.
|
|
type BoundContract struct {
|
|
address common.Address // Deployment address of the contract on the Ethereum blockchain
|
|
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
|
caller ContractCaller // Read interface to interact with the blockchain
|
|
transactor ContractTransactor // Write interface to interact with the blockchain
|
|
}
|
|
|
|
// NewBoundContract creates a low level contract interface through which calls
|
|
// and transactions may be made through.
|
|
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract {
|
|
return &BoundContract{
|
|
address: address,
|
|
abi: abi,
|
|
caller: caller,
|
|
transactor: transactor,
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
// Otherwise try to deploy the contract
|
|
c := NewBoundContract(common.Address{}, abi, backend, backend)
|
|
|
|
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.From, tx.Nonce())
|
|
return c.address, tx, c, nil
|
|
}
|
|
|
|
// 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
|
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
|
// returns.
|
|
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...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
msg = ethereum.CallMsg{To: &c.address, Data: input}
|
|
ctx = ensureContext(opts.Context)
|
|
code []byte
|
|
output []byte
|
|
)
|
|
if opts.Pending {
|
|
pb, ok := c.caller.(PendingContractCaller)
|
|
if !ok {
|
|
return ErrNoPendingState
|
|
}
|
|
output, err = pb.PendingCallContract(ctx, msg)
|
|
if err == nil && len(output) == 0 {
|
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
|
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
|
return err
|
|
} else if len(code) == 0 {
|
|
return ErrNoCode
|
|
}
|
|
}
|
|
} else {
|
|
output, err = c.caller.CallContract(ctx, msg, nil)
|
|
if err == nil && len(output) == 0 {
|
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
|
if code, err = c.caller.CodeAt(ctx, c.address, nil); err != nil {
|
|
return err
|
|
} else if len(code) == 0 {
|
|
return ErrNoCode
|
|
}
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.abi.Unpack(result, method, output)
|
|
}
|
|
|
|
// Transact invokes the (paid) contract method with params as input values.
|
|
func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
|
// Otherwise pack up the parameters and invoke the contract
|
|
input, err := c.abi.Pack(method, params...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.transact(opts, &c.address, input)
|
|
}
|
|
|
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
|
// its default method if one is available.
|
|
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
|
return c.transact(opts, &c.address, nil)
|
|
}
|
|
|
|
// 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
|
|
value := opts.Value
|
|
if value == nil {
|
|
value = new(big.Int)
|
|
}
|
|
nonce := uint64(0)
|
|
if opts.Nonce == nil {
|
|
nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err)
|
|
}
|
|
} else {
|
|
nonce = opts.Nonce.Uint64()
|
|
}
|
|
// Figure out the gas allowance and gas price values
|
|
gasPrice := opts.GasPrice
|
|
if gasPrice == nil {
|
|
gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to suggest gas price: %v", err)
|
|
}
|
|
}
|
|
gasLimit := opts.GasLimit
|
|
if gasLimit == nil {
|
|
// Gas estimation cannot succeed without code for method invocations
|
|
if contract != nil {
|
|
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
|
|
return nil, err
|
|
} else if len(code) == 0 {
|
|
return nil, ErrNoCode
|
|
}
|
|
}
|
|
// If the contract surely has code (or code is not needed), estimate the transaction
|
|
msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
|
|
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
|
|
}
|
|
}
|
|
// Create the transaction, sign it and schedule it for execution
|
|
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 {
|
|
return nil, errors.New("no signer to authorize the transaction with")
|
|
}
|
|
signedTx, err := opts.Signer(opts.From, rawTx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
|
|
return nil, err
|
|
}
|
|
return signedTx, nil
|
|
}
|
|
|
|
func ensureContext(ctx context.Context) context.Context {
|
|
if ctx == nil {
|
|
return context.TODO()
|
|
}
|
|
return ctx
|
|
}
|