accounts/abi/bind: use ethereum interfaces

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.
This commit is contained in:
Felix Lange 2016-08-22 14:01:28 +02:00
parent 056f15aa53
commit d62d5fe59a
4 changed files with 212 additions and 228 deletions

View File

@ -20,28 +20,42 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum"
"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"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// ErrNoCode is returned by call and transact operations for which the requested var (
// recipient contract to operate on does not exist in the state db or does not // ErrNoCode is returned by call and transact operations for which the requested
// have any code associated with it (i.e. suicided). // recipient contract to operate on does not exist in the state db or does not
var ErrNoCode = errors.New("no contract code at given address") // have any code associated with it (i.e. suicided).
ErrNoCode = errors.New("no contract code at given address")
// This error is raised when attempting to perform a pending state action
// on a backend that doesn't implement PendingContractCaller.
ErrNoPendingState = errors.New("backend does not support pending state")
)
// 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 {
// HasCode checks if the contract at the given address has any code associated // CodeAt returns the code of the given account. This is needed to differentiate
// with it or not. This is needed to differentiate between contract internal // between contract internal errors and the local chain being out of sync.
// errors and the local chain being out of sync. CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) // ContractCall executes an Ethereum contract call with the specified data as the
// input.
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
}
// ContractCall executes an Ethereum contract call with the specified data as // PendingContractCaller defines methods to perform contract calls on the pending state.
// the input. The pending flag requests execution against the pending block, not // Call will try to discover this interface when access to the pending state is requested.
// the stable head of the chain. // If the backend does not support the pending state, Call returns ErrNoPendingState.
ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) type PendingContractCaller interface {
// PendingCodeAt returns the code of the given account in the pending state.
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
// PendingCallContract executes an Ethereum contract call against the pending state.
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
} }
// ContractTransactor defines the methods needed to allow operating with contract // ContractTransactor defines the methods needed to allow operating with contract
@ -49,64 +63,25 @@ type ContractCaller interface {
// used when the user does not provide some needed values, but rather leaves it up // used when the user does not provide some needed values, but rather leaves it up
// to the transactor to decide. // to the transactor to decide.
type ContractTransactor interface { type ContractTransactor interface {
// PendingAccountNonce retrieves the current pending nonce associated with an // PendingCodeAt returns the code of the given account in the pending state.
// account. PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) // PendingNonceAt retrieves the current pending nonce associated with an account.
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely // SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction. // execution of a transaction.
SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasPrice(ctx context.Context) (*big.Int, error)
// EstimateGas tries to estimate the gas needed to execute a specific
// HasCode checks if the contract at the given address has any code associated
// with it or not. This is needed to differentiate between contract internal
// errors and the local chain being out of sync.
HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error)
// EstimateGasLimit tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain. // transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other // There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis // transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default. // for setting a reasonable default.
EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) EstimateGas(ctx context.Context, call ethereum.CallMsg) (usedGas *big.Int, err error)
// SendTransaction injects the transaction into the pending pool for execution. // SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error SendTransaction(ctx context.Context, tx *types.Transaction) error
} }
// ContractBackend defines the methods needed to allow operating with contract // ContractBackend defines the methods needed to work with contracts on a read-write basis.
// on a read-write basis.
//
// This interface is essentially the union of ContractCaller and ContractTransactor
// but due to a bug in the Go compiler (https://github.com/golang/go/issues/6977),
// we cannot simply list it as the two interfaces. The other solution is to add a
// third interface containing the common methods, but that convolutes the user API
// as it introduces yet another parameter to require for initialization.
type ContractBackend interface { type ContractBackend interface {
// HasCode checks if the contract at the given address has any code associated ContractCaller
// with it or not. This is needed to differentiate between contract internal ContractTransactor
// errors and the local chain being out of sync.
HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error)
// ContractCall executes an Ethereum contract call with the specified data as
// the input. The pending flag requests execution against the pending block, not
// the stable head of the chain.
ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error)
// PendingAccountNonce retrieves the current pending nonce associated with an
// account.
PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
SuggestGasPrice(ctx context.Context) (*big.Int, error)
// EstimateGasLimit tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default.
EstimateGasLimit(ctx context.Context, 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(ctx context.Context, tx *types.Transaction) error
} }

View File

@ -17,8 +17,10 @@
package backends package backends
import ( import (
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -79,58 +81,44 @@ func (b *SimulatedBackend) Rollback() {
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
} }
// HasCode implements ContractVerifier.HasCode, checking whether there is any // CodeAt implements ChainStateReader.CodeAt, returning the code associated with
// code associated with a certain account in the blockchain. // a certain account at a given block number in the blockchain.
func (b *SimulatedBackend) HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) { func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
if pending { if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
return len(b.pendingState.GetCode(contract)) > 0, nil return nil, fmt.Errorf("SimulatedBackend cannot access blocks other than the latest block")
} }
statedb, _ := b.blockchain.State() statedb, _ := b.blockchain.State()
return len(statedb.GetCode(contract)) > 0, nil return statedb.GetCode(contract), nil
} }
// ContractCall implements ContractCaller.ContractCall, executing the specified // PendingCodeAt implements PendingStateReader.PendingCodeAt, returning the
// contract with the given input data. // code associated with a certain account in the pending state of the blockchain.
func (b *SimulatedBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) { func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
// Create a copy of the current state db to screw around with return b.pendingState.GetCode(contract), nil
var (
block *types.Block
statedb *state.StateDB
)
if pending {
block, statedb = b.pendingBlock, b.pendingState.Copy()
} else {
block = b.blockchain.CurrentBlock()
statedb, _ = b.blockchain.State()
}
// If there's no code to interact with, respond with an appropriate error
if code := statedb.GetCode(contract); len(code) == 0 {
return nil, bind.ErrNoCode
}
// 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, chainConfig, b.blockchain, msg, block.Header(), vm.Config{})
gaspool := new(core.GasPool).AddGas(common.MaxBig)
out, _, err := core.ApplyMessage(vmenv, msg, gaspool)
return out, err
} }
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, retrieving // CallContract executes a contract call.
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
return nil, fmt.Errorf("SimulatedBackend cannot access blocks other than the latest block")
}
state, err := b.blockchain.State()
if err != nil {
return nil, err
}
rval, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
return rval, err
}
// PendingCallContract executes a contract call on the pending state.
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
rval, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState.Copy())
return rval, err
}
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
// the nonce currently pending for the account. // the nonce currently pending for the account.
func (b *SimulatedBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) { func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil return b.pendingState.GetOrNewStateObject(account).Nonce(), nil
} }
@ -140,45 +128,49 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error
return big.NewInt(1), nil return big.NewInt(1), nil
} }
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, executing the // EstimateGas executes the requested code against the currently pending block/state and
// requested code against the currently pending block/state and returning the used // returns the used amount of gas.
// gas. func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (*big.Int, error) {
func (b *SimulatedBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { _, gas, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState.Copy())
// Create a copy of the currently pending state db to screw around with
var (
block = b.pendingBlock
statedb = b.pendingState.Copy()
)
// If there's no code to interact with, respond with an appropriate error
if contract != nil {
if code := statedb.GetCode(*contract); len(code) == 0 {
return nil, bind.ErrNoCode
}
}
// 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, chainConfig, b.blockchain, msg, block.Header(), vm.Config{})
gaspool := new(core.GasPool).AddGas(common.MaxBig)
_, gas, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
return gas, err return gas, err
} }
// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw // callContract implemens common code between normal and pending contract calls.
// transaction injection to the remote node. // state is modified during execution, make sure to copy it if necessary.
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, *big.Int, error) {
// Ensure message is initialized properly.
if call.GasPrice == nil {
call.GasPrice = big.NewInt(1)
}
if call.Gas == nil || call.Gas.BitLen() == 0 {
call.Gas = big.NewInt(50000000)
}
if call.Value == nil {
call.Value = new(big.Int)
}
// Set infinite balance to the fake caller account.
from := statedb.GetOrNewStateObject(call.From)
from.SetBalance(common.MaxBig)
// Execute the call.
msg := callmsg{call}
vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{})
gaspool := new(core.GasPool).AddGas(common.MaxBig)
ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
return ret, gasUsed, err
}
// SendTransaction updates the pending block to include the given transaction.
// It panics if the transaction is invalid.
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
sender, err := tx.From()
if err != nil {
panic(fmt.Errorf("invalid transaction: %v", err))
}
nonce := b.pendingState.GetNonce(sender)
if tx.Nonce() != nonce {
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
}
blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() { for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx) block.AddTx(tx)
@ -187,26 +179,20 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
}) })
b.pendingBlock = blocks[0] b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
return nil return nil
} }
// callmsg implements core.Message to allow passing it as a transaction simulator. // callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct { type callmsg struct {
from *state.StateObject ethereum.CallMsg
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) From() (common.Address, error) { return m.CallMsg.From, nil }
func (m callmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil } func (m callmsg) FromFrontier() (common.Address, error) { return m.CallMsg.From, nil }
func (m callmsg) Nonce() uint64 { return 0 } func (m callmsg) Nonce() uint64 { return 0 }
func (m callmsg) CheckNonce() bool { return false } func (m callmsg) CheckNonce() bool { return false }
func (m callmsg) To() *common.Address { return m.to } func (m callmsg) To() *common.Address { return m.CallMsg.To }
func (m callmsg) GasPrice() *big.Int { return m.gasPrice } func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() *big.Int { return m.gasLimit } func (m callmsg) Gas() *big.Int { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.value } func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.data } func (m callmsg) Data() []byte { return m.CallMsg.Data }

View File

@ -20,8 +20,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"sync/atomic"
"github.com/ethereum/go-ethereum"
"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"
@ -62,9 +62,6 @@ type BoundContract struct {
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
caller ContractCaller // Read interface to interact with the blockchain caller ContractCaller // Read interface to interact with the blockchain
transactor ContractTransactor // Write interface to interact with the blockchain transactor ContractTransactor // Write interface to interact with the blockchain
latestHasCode uint32 // Cached verification that the latest state contains code for this contract
pendingHasCode uint32 // Cached verification that the pending state contains code for this contract
} }
// NewBoundContract creates a low level contract interface through which calls // NewBoundContract creates a low level contract interface through which calls
@ -105,25 +102,42 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
if opts == nil { if opts == nil {
opts = new(CallOpts) opts = new(CallOpts)
} }
// Make sure we have a contract to operate on, and bail out otherwise
if (opts.Pending && atomic.LoadUint32(&c.pendingHasCode) == 0) || (!opts.Pending && atomic.LoadUint32(&c.latestHasCode) == 0) {
if code, err := c.caller.HasCode(opts.Context, c.address, opts.Pending); err != nil {
return err
} else if !code {
return ErrNoCode
}
if opts.Pending {
atomic.StoreUint32(&c.pendingHasCode, 1)
} else {
atomic.StoreUint32(&c.latestHasCode, 1)
}
}
// Pack the input, call and unpack the results // 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(opts.Context, c.address, input, opts.Pending) 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 { if err != nil {
return err return err
} }
@ -158,7 +172,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
} }
nonce := uint64(0) nonce := uint64(0)
if opts.Nonce == nil { if opts.Nonce == nil {
nonce, err = c.transactor.PendingAccountNonce(opts.Context, opts.From) nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) return nil, fmt.Errorf("failed to retrieve account nonce: %v", err)
} }
@ -168,7 +182,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
// Figure out the gas allowance and gas price values // Figure out the gas allowance and gas price values
gasPrice := opts.GasPrice gasPrice := opts.GasPrice
if gasPrice == nil { if gasPrice == nil {
gasPrice, err = c.transactor.SuggestGasPrice(opts.Context) gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to suggest gas price: %v", err) return nil, fmt.Errorf("failed to suggest gas price: %v", err)
} }
@ -176,18 +190,18 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
gasLimit := opts.GasLimit gasLimit := opts.GasLimit
if gasLimit == nil { if gasLimit == nil {
// Gas estimation cannot succeed without code for method invocations // Gas estimation cannot succeed without code for method invocations
if contract != nil && atomic.LoadUint32(&c.pendingHasCode) == 0 { if contract != nil {
if code, err := c.transactor.HasCode(opts.Context, c.address, true); err != nil { if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
return nil, err return nil, err
} else if !code { } else if len(code) == 0 {
return nil, ErrNoCode return nil, ErrNoCode
} }
atomic.StoreUint32(&c.pendingHasCode, 1)
} }
// If the contract surely has code (or code is not needed), estimate the transaction // If the contract surely has code (or code is not needed), estimate the transaction
gasLimit, err = c.transactor.EstimateGasLimit(opts.Context, opts.From, contract, value, input) msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err) return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
} }
} }
// Create the transaction, sign it and schedule it for execution // Create the transaction, sign it and schedule it for execution
@ -204,8 +218,15 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := c.transactor.SendTransaction(opts.Context, signedTx); err != nil { if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
return nil, err return nil, err
} }
return signedTx, nil return signedTx, nil
} }
func ensureContext(ctx context.Context) context.Context {
if ctx == nil {
return context.TODO()
}
return ctx
}

View File

@ -19,6 +19,7 @@ package eth
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum"
"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/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
@ -50,47 +51,62 @@ func NewContractBackend(eth *Ethereum) *ContractBackend {
} }
} }
// HasCode implements bind.ContractVerifier.HasCode by retrieving any code associated // CodeAt retrieves any code associated with the contract from the local API.
// with the contract from the local API, and checking its size. func (b *ContractBackend) CodeAt(ctx context.Context, contract common.Address, blockNum *big.Int) ([]byte, error) {
func (b *ContractBackend) HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) { out, err := b.bcapi.GetCode(ctx, contract, toBlockNumber(blockNum))
if ctx == nil { return common.FromHex(out), err
ctx = context.Background() }
}
block := rpc.LatestBlockNumber // CodeAt retrieves any code associated with the contract from the local API.
if pending { func (b *ContractBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
block = rpc.PendingBlockNumber out, err := b.bcapi.GetCode(ctx, contract, rpc.PendingBlockNumber)
} return common.FromHex(out), err
out, err := b.bcapi.GetCode(ctx, contract, block)
return len(common.FromHex(out)) > 0, err
} }
// ContractCall implements bind.ContractCaller executing an Ethereum contract // ContractCall implements bind.ContractCaller executing an Ethereum contract
// call with the specified data as the input. The pending flag requests execution // call with the specified data as the input. The pending flag requests execution
// against the pending block, not the stable head of the chain. // against the pending block, not the stable head of the chain.
func (b *ContractBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) { func (b *ContractBackend) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNum *big.Int) ([]byte, error) {
if ctx == nil { out, err := b.bcapi.Call(ctx, toCallArgs(msg), toBlockNumber(blockNum))
ctx = context.Background()
}
// Convert the input args to the API spec
args := ethapi.CallArgs{
To: &contract,
Data: common.ToHex(data),
}
block := rpc.LatestBlockNumber
if pending {
block = rpc.PendingBlockNumber
}
// Execute the call and convert the output back to Go types
out, err := b.bcapi.Call(ctx, args, block)
return common.FromHex(out), err return common.FromHex(out), err
} }
// ContractCall implements bind.ContractCaller executing an Ethereum contract
// call with the specified data as the input. The pending flag requests execution
// against the pending block, not the stable head of the chain.
func (b *ContractBackend) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
out, err := b.bcapi.Call(ctx, toCallArgs(msg), rpc.PendingBlockNumber)
return common.FromHex(out), err
}
func toCallArgs(msg ethereum.CallMsg) ethapi.CallArgs {
args := ethapi.CallArgs{
To: msg.To,
From: msg.From,
Data: common.ToHex(msg.Data),
}
if msg.Gas != nil {
args.Gas = *rpc.NewHexNumber(msg.Gas)
}
if msg.GasPrice != nil {
args.GasPrice = *rpc.NewHexNumber(msg.GasPrice)
}
if msg.Value != nil {
args.Value = *rpc.NewHexNumber(msg.Value)
}
return args
}
func toBlockNumber(num *big.Int) rpc.BlockNumber {
if num == nil {
return rpc.LatestBlockNumber
}
return rpc.BlockNumber(num.Int64())
}
// PendingAccountNonce implements bind.ContractTransactor retrieving the current // PendingAccountNonce implements bind.ContractTransactor retrieving the current
// pending nonce associated with an account. // pending nonce associated with an account.
func (b *ContractBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) { func (b *ContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
if ctx == nil {
ctx = context.Background()
}
out, err := b.txapi.GetTransactionCount(ctx, account, rpc.PendingBlockNumber) out, err := b.txapi.GetTransactionCount(ctx, account, rpc.PendingBlockNumber)
return out.Uint64(), err return out.Uint64(), err
} }
@ -98,9 +114,6 @@ func (b *ContractBackend) PendingAccountNonce(ctx context.Context, account commo
// SuggestGasPrice implements bind.ContractTransactor retrieving the currently // SuggestGasPrice implements bind.ContractTransactor retrieving the currently
// suggested gas price to allow a timely execution of a transaction. // suggested gas price to allow a timely execution of a transaction.
func (b *ContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { func (b *ContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
if ctx == nil {
ctx = context.Background()
}
return b.eapi.GasPrice(ctx) return b.eapi.GasPrice(ctx)
} }
@ -109,25 +122,14 @@ func (b *ContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error)
// the backend blockchain. There is no guarantee that this is the true gas limit // the backend blockchain. There is no guarantee that this is the true gas limit
// requirement as other transactions may be added or removed by miners, but it // requirement as other transactions may be added or removed by miners, but it
// should provide a basis for setting a reasonable default. // should provide a basis for setting a reasonable default.
func (b *ContractBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { func (b *ContractBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (*big.Int, error) {
if ctx == nil { out, err := b.bcapi.EstimateGas(ctx, toCallArgs(msg))
ctx = context.Background()
}
out, err := b.bcapi.EstimateGas(ctx, ethapi.CallArgs{
From: sender,
To: contract,
Value: *rpc.NewHexNumber(value),
Data: common.ToHex(data),
})
return out.BigInt(), err return out.BigInt(), err
} }
// SendTransaction implements bind.ContractTransactor injects the transaction // SendTransaction implements bind.ContractTransactor injects the transaction
// into the pending pool for execution. // into the pending pool for execution.
func (b *ContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { func (b *ContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
if ctx == nil {
ctx = context.Background()
}
raw, _ := rlp.EncodeToBytes(tx) raw, _ := rlp.EncodeToBytes(tx)
_, err := b.txapi.SendRawTransaction(ctx, common.ToHex(raw)) _, err := b.txapi.SendRawTransaction(ctx, common.ToHex(raw))
return err return err