瀏覽代碼

accounts/abi/bind, eth: add contract non-existent error

Péter Szilágyi 9 年之前
父節點
當前提交
cdcbb2f160

+ 10 - 0
accounts/abi/bind/backend.go

@@ -17,12 +17,22 @@
 package bind
 
 import (
+	"errors"
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
 )
 
+// 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 {

+ 12 - 3
accounts/abi/bind/backends/remote.go

@@ -66,10 +66,16 @@ type request struct {
 type response struct {
 	JSONRPC string          `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
 	ID      int             `json:"id"`      // Auto incrementing ID number for this request
-	Error   json.RawMessage `json:"error"`   // Any error returned by the remote side
+	Error   *failure        `json:"error"`   // Any error returned by the remote side
 	Result  json.RawMessage `json:"result"`  // Whatever the remote side sends us in reply
 }
 
+// failure is a JSON RPC response error field sent back from the API server.
+type failure struct {
+	Code    int    `json:"code"`    // JSON RPC error code associated with the failure
+	Message string `json:"message"` // Specific error message of the failure
+}
+
 // request forwards an API request to the RPC server, and parses the response.
 //
 // This is currently painfully non-concurrent, but it will have to do until we
@@ -96,8 +102,11 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
 	if err := b.client.Recv(res); err != nil {
 		return nil, err
 	}
-	if len(res.Error) > 0 {
-		return nil, fmt.Errorf("remote error: %s", string(res.Error))
+	if res.Error != nil {
+		if res.Error.Message == bind.ErrNoCode.Error() {
+			return nil, bind.ErrNoCode
+		}
+		return nil, fmt.Errorf("remote error: %s", res.Error.Message)
 	}
 	return res.Result, nil
 }

+ 10 - 1
accounts/abi/bind/backends/simulated.go

@@ -92,6 +92,10 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
 		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)
@@ -134,7 +138,12 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
 		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)

+ 28 - 0
accounts/abi/bind/bind_test.go

@@ -303,6 +303,34 @@ var bindTests = []struct {
 			}
 		`,
 	},
+	// Tests that non-existent contracts are reported as such (though only simulator test)
+	{
+		`NonExistent`,
+		`
+		contract NonExistent {
+			function String() constant returns(string) {
+				return "I don't exist";
+			}
+		}
+		`,
+		`6060604052609f8060106000396000f3606060405260e060020a6000350463f97a60058114601a575b005b600060605260c0604052600d60809081527f4920646f6e27742065786973740000000000000000000000000000000000000060a052602060c0908152600d60e081905281906101009060a09080838184600060046012f15050815172ffffffffffffffffffffffffffffffffffffff1916909152505060405161012081900392509050f3`,
+		`[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`,
+		`
+			// Create a simulator and wrap a non-deployed contract
+			sim := backends.NewSimulatedBackend()
+
+			nonexistent, err := NewNonExistent(common.Address{}, sim)
+			if err != nil {
+				t.Fatalf("Failed to access non-existent contract: %v", err)
+			}
+			// Ensure that contract calls fail with the appropriate error
+			if res, err := nonexistent.String(nil); err == nil {
+				t.Fatalf("Call succeeded on non-existent contract: %v", res)
+			} else if (err != bind.ErrNoCode) {
+				t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode)
+			}
+		`,
+	},
 }
 
 // Tests that packages generated by the binder can be successfully compiled and

+ 15 - 0
eth/api.go

@@ -51,6 +51,15 @@ import (
 	"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)
 
 // blockByNumber is a commonly used helper function which retrieves and returns
@@ -694,6 +703,12 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st
 	}
 	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
 	var from *state.StateObject
 	if args.From == (common.Address{}) {