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:
parent
056f15aa53
commit
d62d5fe59a
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
96
eth/bind.go
96
eth/bind.go
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user