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

This commit is contained in:
Péter Szilágyi 2016-04-27 16:29:13 +03:00
parent db62979514
commit cdcbb2f160
5 changed files with 75 additions and 4 deletions

View File

@ -17,12 +17,22 @@
package bind package bind
import ( import (
"errors"
"math/big" "math/big"
"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"
) )
// 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 // ContractCaller defines the methods needed to allow operating with contract on a read
// only basis. // only basis.
type ContractCaller interface { type ContractCaller interface {

View File

@ -66,10 +66,16 @@ type request struct {
type response struct { type response struct {
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0 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 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 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. // 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 // 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 { if err := b.client.Recv(res); err != nil {
return nil, err return nil, err
} }
if len(res.Error) > 0 { if res.Error != nil {
return nil, fmt.Errorf("remote error: %s", string(res.Error)) 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 return res.Result, nil
} }

View File

@ -92,6 +92,10 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
block = b.blockchain.CurrentBlock() block = b.blockchain.CurrentBlock()
statedb, _ = b.blockchain.State() 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 // Set infinite balance to the a fake caller account
from := statedb.GetOrNewStateObject(common.Address{}) from := statedb.GetOrNewStateObject(common.Address{})
from.SetBalance(common.MaxBig) from.SetBalance(common.MaxBig)
@ -134,7 +138,12 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
block = b.pendingBlock block = b.pendingBlock
statedb = b.pendingState.Copy() 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 // Set infinite balance to the a fake caller account
from := statedb.GetOrNewStateObject(sender) from := statedb.GetOrNewStateObject(sender)
from.SetBalance(common.MaxBig) from.SetBalance(common.MaxBig)

View File

@ -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 // Tests that packages generated by the binder can be successfully compiled and

View File

@ -51,6 +51,15 @@ 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
@ -694,6 +703,12 @@ 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{}) {