accounts/abi/bind, eth: rely on getCode for sanity checks, not estimate and call
This commit is contained in:
parent
e798e4fd75
commit
1580ec1804
@ -27,15 +27,16 @@ import (
|
|||||||
// ErrNoCode is returned by call and transact operations for which the requested
|
// ErrNoCode is returned by call and transact operations for which the requested
|
||||||
// recipient contract to operate on does not exist in the state db or does not
|
// recipient contract to operate on does not exist in the state db or does not
|
||||||
// have any code associated with it (i.e. suicided).
|
// have any code associated with it (i.e. suicided).
|
||||||
//
|
|
||||||
// Please note, this error string is part of the RPC API and is expected by the
|
|
||||||
// native contract bindings to signal this particular error. Do not change this
|
|
||||||
// as it will break all dependent code!
|
|
||||||
var ErrNoCode = errors.New("no contract code at given address")
|
var ErrNoCode = errors.New("no contract code at given address")
|
||||||
|
|
||||||
// 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
|
||||||
|
// with it or not. This is needed to differentiate between contract internal
|
||||||
|
// errors and the local chain being out of sync.
|
||||||
|
HasCode(contract common.Address, pending bool) (bool, error)
|
||||||
|
|
||||||
// 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 pending flag requests execution against the pending block, not
|
// the input. The pending flag requests execution against the pending block, not
|
||||||
// the stable head of the chain.
|
// the stable head of the chain.
|
||||||
@ -55,6 +56,11 @@ type ContractTransactor interface {
|
|||||||
// execution of a transaction.
|
// execution of a transaction.
|
||||||
SuggestGasPrice() (*big.Int, error)
|
SuggestGasPrice() (*big.Int, error)
|
||||||
|
|
||||||
|
// 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(contract common.Address, pending bool) (bool, error)
|
||||||
|
|
||||||
// EstimateGasLimit tries to estimate the gas needed to execute a specific
|
// 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
|
||||||
@ -68,7 +74,38 @@ type ContractTransactor interface {
|
|||||||
|
|
||||||
// ContractBackend defines the methods needed to allow operating with contract
|
// ContractBackend defines the methods needed to allow operating with contract
|
||||||
// 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 {
|
||||||
ContractCaller
|
// HasCode checks if the contract at the given address has any code associated
|
||||||
ContractTransactor
|
// with it or not. This is needed to differentiate between contract internal
|
||||||
|
// errors and the local chain being out of sync.
|
||||||
|
HasCode(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(contract common.Address, data []byte, pending bool) ([]byte, error)
|
||||||
|
|
||||||
|
// PendingAccountNonce retrieves the current pending nonce associated with an
|
||||||
|
// account.
|
||||||
|
PendingAccountNonce(account common.Address) (uint64, error)
|
||||||
|
|
||||||
|
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
||||||
|
// execution of a transaction.
|
||||||
|
SuggestGasPrice() (*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(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(tx *types.Transaction) error
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) {
|
|||||||
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
|
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
func (*nilBackend) HasCode(common.Address, bool) (bool, error) { panic("not implemented") }
|
||||||
func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") }
|
func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") }
|
||||||
func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") }
|
func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") }
|
||||||
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }
|
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }
|
||||||
|
@ -111,6 +111,26 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
|
|||||||
return res.Result, nil
|
return res.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCode implements ContractVerifier.HasCode by retrieving any code associated
|
||||||
|
// with the contract from the remote node, and checking its size.
|
||||||
|
func (b *rpcBackend) HasCode(contract common.Address, pending bool) (bool, error) {
|
||||||
|
// Execute the RPC code retrieval
|
||||||
|
block := "latest"
|
||||||
|
if pending {
|
||||||
|
block = "pending"
|
||||||
|
}
|
||||||
|
res, err := b.request("eth_getCode", []interface{}{contract.Hex(), block})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
var hex string
|
||||||
|
if err := json.Unmarshal(res, &hex); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// Convert the response back to a Go byte slice and return
|
||||||
|
return len(common.FromHex(hex)) > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
||||||
// a contract call to the remote node, returning the reply to for local processing.
|
// 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) {
|
func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
|
||||||
|
@ -78,6 +78,16 @@ 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
|
||||||
|
// code associated with a certain account in the blockchain.
|
||||||
|
func (b *SimulatedBackend) HasCode(contract common.Address, pending bool) (bool, error) {
|
||||||
|
if pending {
|
||||||
|
return len(b.pendingState.GetCode(contract)) > 0, nil
|
||||||
|
}
|
||||||
|
statedb, _ := b.blockchain.State()
|
||||||
|
return len(statedb.GetCode(contract)) > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ContractCall implements ContractCaller.ContractCall, executing the specified
|
// ContractCall implements ContractCaller.ContractCall, executing the specified
|
||||||
// contract with the given input data.
|
// contract with the given input data.
|
||||||
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
|
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"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"
|
||||||
@ -56,6 +57,9 @@ 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
|
||||||
@ -96,6 +100,19 @@ 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(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 {
|
||||||
@ -153,6 +170,16 @@ 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
|
||||||
|
if contract != nil && atomic.LoadUint32(&c.pendingHasCode) == 0 {
|
||||||
|
if code, err := c.transactor.HasCode(c.address, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !code {
|
||||||
|
return nil, ErrNoCode
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&c.pendingHasCode, 1)
|
||||||
|
}
|
||||||
|
// If the contract surely has code (or code is not needed), estimate the transaction
|
||||||
gasLimit, err = c.transactor.EstimateGasLimit(opts.From, contract, value, input)
|
gasLimit, err = c.transactor.EstimateGasLimit(opts.From, 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)
|
||||||
|
15
eth/api.go
15
eth/api.go
@ -52,15 +52,6 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errNoCode is returned by call and transact operations for which the requested
|
|
||||||
// recipient contract to operate on does not exist in the state db or does not
|
|
||||||
// have any code associated with it (i.e. suicided).
|
|
||||||
//
|
|
||||||
// Please note, this error string is part of the RPC API and is expected by the
|
|
||||||
// native contract bindings to signal this particular error. Do not change this
|
|
||||||
// as it will break all dependent code!
|
|
||||||
var errNoCode = errors.New("no contract code at given address")
|
|
||||||
|
|
||||||
const defaultGas = uint64(90000)
|
const defaultGas = uint64(90000)
|
||||||
|
|
||||||
// blockByNumber is a commonly used helper function which retrieves and returns
|
// blockByNumber is a commonly used helper function which retrieves and returns
|
||||||
@ -717,12 +708,6 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st
|
|||||||
}
|
}
|
||||||
stateDb = stateDb.Copy()
|
stateDb = stateDb.Copy()
|
||||||
|
|
||||||
// If there's no code to interact with, respond with an appropriate error
|
|
||||||
if args.To != nil {
|
|
||||||
if code := stateDb.GetCode(*args.To); len(code) == 0 {
|
|
||||||
return "0x", nil, errNoCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retrieve the account state object to interact with
|
// Retrieve the account state object to interact with
|
||||||
var from *state.StateObject
|
var from *state.StateObject
|
||||||
if args.From == (common.Address{}) {
|
if args.From == (common.Address{}) {
|
||||||
|
18
eth/bind.go
18
eth/bind.go
@ -19,7 +19,6 @@ package eth
|
|||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"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/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
@ -49,6 +48,17 @@ func NewContractBackend(eth *Ethereum) *ContractBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasCode implements bind.ContractVerifier.HasCode by retrieving any code associated
|
||||||
|
// with the contract from the local API, and checking its size.
|
||||||
|
func (b *ContractBackend) HasCode(contract common.Address, pending bool) (bool, error) {
|
||||||
|
block := rpc.LatestBlockNumber
|
||||||
|
if pending {
|
||||||
|
block = rpc.PendingBlockNumber
|
||||||
|
}
|
||||||
|
out, err := b.bcapi.GetCode(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.
|
||||||
@ -64,9 +74,6 @@ func (b *ContractBackend) ContractCall(contract common.Address, data []byte, pen
|
|||||||
}
|
}
|
||||||
// Execute the call and convert the output back to Go types
|
// Execute the call and convert the output back to Go types
|
||||||
out, err := b.bcapi.Call(args, block)
|
out, err := b.bcapi.Call(args, block)
|
||||||
if err == errNoCode {
|
|
||||||
err = bind.ErrNoCode
|
|
||||||
}
|
|
||||||
return common.FromHex(out), err
|
return common.FromHex(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,9 +102,6 @@ func (b *ContractBackend) EstimateGasLimit(sender common.Address, contract *comm
|
|||||||
Value: *rpc.NewHexNumber(value),
|
Value: *rpc.NewHexNumber(value),
|
||||||
Data: common.ToHex(data),
|
Data: common.ToHex(data),
|
||||||
})
|
})
|
||||||
if err == errNoCode {
|
|
||||||
err = bind.ErrNoCode
|
|
||||||
}
|
|
||||||
return out.BigInt(), err
|
return out.BigInt(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user