diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 328f9f3b7..7442557cc 100644 --- a/accounts/abi/bind/backend.go +++ b/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 { diff --git a/accounts/abi/bind/backends/remote.go b/accounts/abi/bind/backends/remote.go index 8e990f076..9b3647192 100644 --- a/accounts/abi/bind/backends/remote.go +++ b/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 } diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 6cdb9a0cc..4866c4f58 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/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) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 5c36bc48f..f9cc8aba4 100644 --- a/accounts/abi/bind/bind_test.go +++ b/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 diff --git a/eth/api.go b/eth/api.go index a0b1f8ac2..02b34541f 100644 --- a/eth/api.go +++ b/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{}) {