From 1580ec180414bce1e37acc614bc2445f778efb75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= <peterke@gmail.com>
Date: Fri, 20 May 2016 12:29:28 +0300
Subject: accounts/abi/bind, eth: rely on getCode for sanity checks, not
 estimate and call

---
 accounts/abi/bind/backend.go            | 49 +++++++++++++++++++++++++++++----
 accounts/abi/bind/backends/nil.go       |  1 +
 accounts/abi/bind/backends/remote.go    | 20 ++++++++++++++
 accounts/abi/bind/backends/simulated.go | 10 +++++++
 accounts/abi/bind/base.go               | 27 ++++++++++++++++++
 5 files changed, 101 insertions(+), 6 deletions(-)

(limited to 'accounts')

diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go
index 604e1ef26..65806aef4 100644
--- a/accounts/abi/bind/backend.go
+++ b/accounts/abi/bind/backend.go
@@ -27,15 +27,16 @@ import (
 // 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")
 
 // ContractCaller defines the methods needed to allow operating with contract on a read
 // only basis.
 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
 	// the input. The pending flag requests execution against the pending block, not
 	// the stable head of the chain.
@@ -55,6 +56,11 @@ type ContractTransactor interface {
 	// execution of a transaction.
 	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
 	// 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
@@ -68,7 +74,38 @@ type ContractTransactor interface {
 
 // ContractBackend defines the methods needed to allow operating with contract
 // 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 {
-	ContractCaller
-	ContractTransactor
+	// 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
+	// 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
 }
diff --git a/accounts/abi/bind/backends/nil.go b/accounts/abi/bind/backends/nil.go
index 3b1e6dce7..f10bb61ac 100644
--- a/accounts/abi/bind/backends/nil.go
+++ b/accounts/abi/bind/backends/nil.go
@@ -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) {
 	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) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") }
 func (*nilBackend) SendTransaction(*types.Transaction) error           { panic("not implemented") }
diff --git a/accounts/abi/bind/backends/remote.go b/accounts/abi/bind/backends/remote.go
index 9b3647192..d903cbc8f 100644
--- a/accounts/abi/bind/backends/remote.go
+++ b/accounts/abi/bind/backends/remote.go
@@ -111,6 +111,26 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
 	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
 // 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) {
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 4866c4f58..54b1ce603 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -78,6 +78,16 @@ func (b *SimulatedBackend) Rollback() {
 	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
 // contract with the given input data.
 func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go
index 06621c5ad..75e8d5bc8 100644
--- a/accounts/abi/bind/base.go
+++ b/accounts/abi/bind/base.go
@@ -20,6 +20,7 @@ import (
 	"errors"
 	"fmt"
 	"math/big"
+	"sync/atomic"
 
 	"github.com/ethereum/go-ethereum/accounts/abi"
 	"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
 	caller     ContractCaller     // Read 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
@@ -96,6 +100,19 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
 	if opts == nil {
 		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
 	input, err := c.abi.Pack(method, params...)
 	if err != nil {
@@ -153,6 +170,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
 	}
 	gasLimit := opts.GasLimit
 	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)
 		if err != nil {
 			return nil, fmt.Errorf("failed to exstimate gas needed: %v", err)
-- 
cgit v1.2.3