diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 1b07b2f68e..0e54c48178 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -232,8 +232,6 @@ var ( // Unpack output in v according to the abi specification func (abi ABI) Unpack(v interface{}, name string, output []byte) error { - var method = abi.Methods[name] - if len(output) == 0 { return fmt.Errorf("abi: unmarshalling empty output") } @@ -244,18 +242,31 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { return fmt.Errorf("abi: Unpack(non-pointer %T)", v) } + // get information about the method or event + var args []Argument + method, ok := abi.Methods[name] + if ok { + args = method.Outputs + } else { + event, ok := abi.Events[name] + if !ok { + return fmt.Errorf("abi: method or event not found (%v)", name) + } + args = event.Inputs + } + var ( value = valueOf.Elem() typ = value.Type() ) - if len(method.Outputs) > 1 { + if len(args) > 1 { switch value.Kind() { // struct will match named return values to the struct's field // names case reflect.Struct: - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) + for i := 0; i < len(args); i++ { + marshalledValue, err := toGoType(i, args[i], output) if err != nil { return err } @@ -264,8 +275,8 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { for j := 0; j < typ.NumField(); j++ { field := typ.Field(j) // TODO read tags: `abi:"fieldName"` - if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] { - if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil { + if field.Name == strings.ToUpper(args[i].Name[:1])+method.Outputs[i].Name[1:] { + if err := set(value.Field(j), reflectValue, args[i]); err != nil { return err } } @@ -278,17 +289,17 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { // if the slice already contains values, set those instead of the interface slice itself. if value.Len() > 0 { - if len(method.Outputs) > value.Len() { - return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(method.Outputs), value.Len()) + if len(args) > value.Len() { + return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(args), value.Len()) } - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) + for i := 0; i < len(args); i++ { + marshalledValue, err := toGoType(i, args[i], output) if err != nil { return err } reflectValue := reflect.ValueOf(marshalledValue) - if err := set(value.Index(i).Elem(), reflectValue, method.Outputs[i]); err != nil { + if err := set(value.Index(i).Elem(), reflectValue, args[i]); err != nil { return err } } @@ -297,9 +308,9 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { // create a new slice and start appending the unmarshalled // values to the new interface slice. - z := reflect.MakeSlice(typ, 0, len(method.Outputs)) - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) + z := reflect.MakeSlice(typ, 0, len(args)) + for i := 0; i < len(args); i++ { + marshalledValue, err := toGoType(i, args[i], output) if err != nil { return err } @@ -311,11 +322,11 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { } } else { - marshalledValue, err := toGoType(0, method.Outputs[0], output) + marshalledValue, err := toGoType(0, args[0], output) if err != nil { return err } - if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil { + if err := set(value, reflect.ValueOf(marshalledValue), args[0]); err != nil { return err } } diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index bec742c29c..90d562f977 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -18,11 +18,8 @@ package bind import ( "errors" - "math/big" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "golang.org/x/net/context" + "github.com/ethereum/go-ethereum" ) // ErrNoCode is returned by call and transact operations for which the requested @@ -30,83 +27,15 @@ import ( // have any code associated with it (i.e. suicided). 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(ctx context.Context, 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(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) -} - -// ContractTransactor defines the methods needed to allow operating with contract -// on a write only basis. Beside the transacting method, the remainder are helpers -// used when the user does not provide some needed values, but rather leaves it up -// to the transactor to decide. -type ContractTransactor interface { - // PendingAccountNonce retrieves the current pending nonce associated with an - // account. - PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) - - // SuggestGasPrice retrieves the currently suggested gas price to allow a timely - // execution of a transaction. - SuggestGasPrice(ctx context.Context) (*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(ctx context.Context, 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 - // transactions may be added or removed by miners, but it should provide a basis - // for setting a reasonable default. - EstimateGasLimit(ctx context.Context, 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(ctx context.Context, tx *types.Transaction) error -} - // 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 { - // 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(ctx context.Context, 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(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) - - // PendingAccountNonce retrieves the current pending nonce associated with an - // account. - PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) - - // SuggestGasPrice retrieves the currently suggested gas price to allow a timely - // execution of a transaction. - SuggestGasPrice(ctx context.Context) (*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(ctx context.Context, 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(ctx context.Context, tx *types.Transaction) error + ethereum.ChainStateReader + ethereum.ContractCaller + ethereum.LogFilterer + ethereum.GasPricer + ethereum.TransactionSender + ethereum.PendingStateReader + ethereum.PendingContractCaller + ethereum.GasEstimator } diff --git a/accounts/abi/bind/backends/nil.go b/accounts/abi/bind/backends/nil.go deleted file mode 100644 index 54b222f1f2..0000000000 --- a/accounts/abi/bind/backends/nil.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package backends - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "golang.org/x/net/context" -) - -// This nil assignment ensures compile time that nilBackend implements bind.ContractBackend. -var _ bind.ContractBackend = (*nilBackend)(nil) - -// nilBackend implements bind.ContractBackend, but panics on any method call. -// Its sole purpose is to support the binding tests to construct the generated -// wrappers without calling any methods on them. -type nilBackend struct{} - -func (*nilBackend) ContractCall(context.Context, common.Address, []byte, bool) ([]byte, error) { - panic("not implemented") -} -func (*nilBackend) EstimateGasLimit(context.Context, common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) { - panic("not implemented") -} -func (*nilBackend) HasCode(context.Context, common.Address, bool) (bool, error) { - panic("not implemented") -} -func (*nilBackend) SuggestGasPrice(context.Context) (*big.Int, error) { panic("not implemented") } -func (*nilBackend) PendingAccountNonce(context.Context, common.Address) (uint64, error) { - panic("not implemented") -} -func (*nilBackend) SendTransaction(context.Context, *types.Transaction) error { - panic("not implemented") -} - -// NewNilBackend creates a new binding backend that can be used for instantiation -// but will panic on any invocation. Its sole purpose is to help testing. -func NewNilBackend() bind.ContractBackend { - return new(nilBackend) -} diff --git a/accounts/abi/bind/backends/remote.go b/accounts/abi/bind/backends/remote.go deleted file mode 100644 index 58edd791ac..0000000000 --- a/accounts/abi/bind/backends/remote.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package backends - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "golang.org/x/net/context" -) - -// This nil assignment ensures compile time that rpcBackend implements bind.ContractBackend. -var _ bind.ContractBackend = (*rpcBackend)(nil) - -// rpcBackend implements bind.ContractBackend, and acts as the data provider to -// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate -// all its functionality. -type rpcBackend struct { - client *rpc.Client // RPC client connection to interact with an API server -} - -// NewRPCBackend creates a new binding backend to an RPC provider that can be -// used to interact with remote contracts. -func NewRPCBackend(client *rpc.Client) bind.ContractBackend { - return &rpcBackend{client: client} -} - -// HasCode implements ContractVerifier.HasCode by retrieving any code associated -// with the contract from the remote node, and checking its size. -func (b *rpcBackend) HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) { - block := "latest" - if pending { - block = "pending" - } - var hex string - err := b.client.CallContext(ctx, &hex, "eth_getCode", contract, block) - if err != nil { - return false, err - } - 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(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) { - args := struct { - To common.Address `json:"to"` - Data string `json:"data"` - }{ - To: contract, - Data: common.ToHex(data), - } - block := "latest" - if pending { - block = "pending" - } - var hex string - err := b.client.CallContext(ctx, &hex, "eth_call", args, block) - if err != nil { - return nil, err - } - return common.FromHex(hex), nil - -} - -// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, delegating -// the current account nonce retrieval to the remote node. -func (b *rpcBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) { - var hex rpc.HexNumber - err := b.client.CallContext(ctx, &hex, "eth_getTransactionCount", account.Hex(), "pending") - if err != nil { - return 0, err - } - return hex.Uint64(), nil -} - -// SuggestGasPrice implements ContractTransactor.SuggestGasPrice, delegating the -// gas price oracle request to the remote node. -func (b *rpcBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - var hex rpc.HexNumber - if err := b.client.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { - return nil, err - } - return (*big.Int)(&hex), nil -} - -// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, delegating -// the gas estimation to the remote node. -func (b *rpcBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { - args := struct { - From common.Address `json:"from"` - To *common.Address `json:"to"` - Value *rpc.HexNumber `json:"value"` - Data string `json:"data"` - }{ - From: sender, - To: contract, - Data: common.ToHex(data), - Value: rpc.NewHexNumber(value), - } - // Execute the RPC call and retrieve the response - var hex rpc.HexNumber - err := b.client.CallContext(ctx, &hex, "eth_estimateGas", args) - if err != nil { - return nil, err - } - return (*big.Int)(&hex), nil -} - -// SendTransaction implements ContractTransactor.SendTransaction, delegating the -// raw transaction injection to the remote node. -func (b *rpcBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { - data, err := rlp.EncodeToBytes(tx) - if err != nil { - return err - } - return b.client.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) -} diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go deleted file mode 100644 index 687a31bf11..0000000000 --- a/accounts/abi/bind/backends/simulated.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package backends - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "golang.org/x/net/context" -) - -// Default chain configuration which sets homestead phase at block 0 (i.e. no frontier) -var chainConfig = &core.ChainConfig{HomesteadBlock: big.NewInt(0)} - -// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. -var _ bind.ContractBackend = (*SimulatedBackend)(nil) - -// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in -// the background. Its main purpose is to allow easily testing contract bindings. -type SimulatedBackend struct { - database ethdb.Database // In memory database to store our testing data - blockchain *core.BlockChain // Ethereum blockchain to handle the consensus - - pendingBlock *types.Block // Currently pending block that will be imported on request - pendingState *state.StateDB // Currently pending state that will be the active on on request -} - -// NewSimulatedBackend creates a new binding backend using a simulated blockchain -// for testing purposes. -func NewSimulatedBackend(accounts ...core.GenesisAccount) *SimulatedBackend { - database, _ := ethdb.NewMemDatabase() - core.WriteGenesisBlockForTesting(database, accounts...) - blockchain, _ := core.NewBlockChain(database, chainConfig, new(core.FakePow), new(event.TypeMux)) - - backend := &SimulatedBackend{ - database: database, - blockchain: blockchain, - } - backend.Rollback() - - return backend -} - -// Commit imports all the pending transactions as a single block and starts a -// fresh new state. -func (b *SimulatedBackend) Commit() { - if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { - panic(err) // This cannot happen unless the simulator is wrong, fail in that case - } - b.Rollback() -} - -// Rollback aborts all pending transactions, reverting to the last committed state. -func (b *SimulatedBackend) Rollback() { - blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {}) - - b.pendingBlock = blocks[0] - 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(ctx context.Context, 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(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) { - // Create a copy of the current state db to screw around with - var ( - block *types.Block - statedb *state.StateDB - ) - if pending { - block, statedb = b.pendingBlock, b.pendingState.Copy() - } else { - 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) - - // Assemble the call invocation to measure the gas usage - msg := callmsg{ - from: from, - to: &contract, - gasPrice: new(big.Int), - gasLimit: common.MaxBig, - value: new(big.Int), - data: data, - } - // Execute the call and return - vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) - gaspool := new(core.GasPool).AddGas(common.MaxBig) - - out, _, err := core.ApplyMessage(vmenv, msg, gaspool) - return out, err -} - -// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, retrieving -// the nonce currently pending for the account. -func (b *SimulatedBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) { - return b.pendingState.GetOrNewStateObject(account).Nonce(), nil -} - -// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated -// chain doens't have miners, we just return a gas price of 1 for any call. -func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - return big.NewInt(1), nil -} - -// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, executing the -// requested code against the currently pending block/state and returning the used -// gas. -func (b *SimulatedBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { - // Create a copy of the currently pending state db to screw around with - var ( - 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) - - // Assemble the call invocation to measure the gas usage - msg := callmsg{ - from: from, - to: contract, - gasPrice: new(big.Int), - gasLimit: common.MaxBig, - value: value, - data: data, - } - // Execute the call and return - vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) - gaspool := new(core.GasPool).AddGas(common.MaxBig) - - _, gas, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() - return gas, err -} - -// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw -// transaction injection to the remote node. -func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { - blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { - for _, tx := range b.pendingBlock.Transactions() { - block.AddTx(tx) - } - block.AddTx(tx) - }) - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) - - return nil -} - -// callmsg implements core.Message to allow passing it as a transaction simulator. -type callmsg struct { - from *state.StateObject - to *common.Address - gasLimit *big.Int - gasPrice *big.Int - value *big.Int - data []byte -} - -func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) Nonce() uint64 { return 0 } -func (m callmsg) CheckNonce() bool { return false } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gasLimit } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 80948d3f1d..1342f64a5d 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -20,11 +20,14 @@ import ( "errors" "fmt" "math/big" + "reflect" "sync/atomic" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "golang.org/x/net/context" ) @@ -54,27 +57,47 @@ type TransactOpts struct { Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } +// SubscribeOpts is the collection of options to fine tune the contract subscription +// and unsubscription requests. +type SubscribeOpts struct { + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// EventOpts is a collection of options to fine tune the retrieval of events that +// happened on a contract. +type EventOpts struct { + FromBlock *big.Int + ToBlock *big.Int + + Context context.Context +} + // BoundContract is the base wrapper object that reflects a contract on the // Ethereum network. It contains a collection of methods that are used by the // higher level contract bindings to operate. type BoundContract struct { - address common.Address // Deployment address of the contract on the Ethereum blockchain - 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 + address common.Address // Deployment address of the contract on the Ethereum blockchain + abi abi.ABI // Reflect based ABI to access the correct Ethereum methods + + backend ContractBackend 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 + + subscriptions map[uint32]ethereum.Subscription + errors map[uint32]error + nextID uint32 } // NewBoundContract creates a low level contract interface through which calls // and transactions may be made through. -func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract { +func NewBoundContract(address common.Address, abi abi.ABI, backend ContractBackend) *BoundContract { return &BoundContract{ - address: address, - abi: abi, - caller: caller, - transactor: transactor, + address: address, + abi: abi, + backend: backend, + subscriptions: make(map[uint32]ethereum.Subscription), + errors: make(map[uint32]error), } } @@ -82,7 +105,7 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller // deployment address with a Go wrapper. func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) { // Otherwise try to deploy the contract - c := NewBoundContract(common.Address{}, abi, backend, backend) + c := NewBoundContract(common.Address{}, abi, backend) input, err := c.abi.Pack("", params...) if err != nil { @@ -106,24 +129,40 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, 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(opts.Context, 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) - } + var code []byte + var err error + if opts.Pending && atomic.LoadUint32(&c.pendingHasCode) == 0 { + code, err = c.backend.PendingCodeAt(opts.Context, c.address) + } else if !opts.Pending && atomic.LoadUint32(&c.latestHasCode) == 0 { + code, err = c.backend.CodeAt(opts.Context, c.address, nil) + } + if err != nil { + return err + } else if len(code) == 0 { + 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 { return err } - output, err := c.caller.ContractCall(opts.Context, c.address, input, opts.Pending) + // Create the call message we use to make the call + callMsg := ethereum.CallMsg{ + To: c.address, + Data: input, + } + var output []byte + // Call pending or not depending on options + if opts.Pending { + output, err = c.backend.PendingCallContract(opts.Context, callMsg) + } else { + output, err = c.backend.CallContract(opts.Context, callMsg, nil) + } if err != nil { return err } @@ -158,7 +197,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i } nonce := uint64(0) if opts.Nonce == nil { - nonce, err = c.transactor.PendingAccountNonce(opts.Context, opts.From) + nonce, err = c.backend.PendingNonceAt(opts.Context, opts.From) if err != nil { return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) } @@ -168,7 +207,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i // Figure out the gas allowance and gas price values gasPrice := opts.GasPrice if gasPrice == nil { - gasPrice, err = c.transactor.SuggestGasPrice(opts.Context) + gasPrice, err = c.backend.SuggestGasPrice(opts.Context) if err != nil { return nil, fmt.Errorf("failed to suggest gas price: %v", err) } @@ -177,17 +216,25 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i 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(opts.Context, c.address, true); err != nil { + var code []byte + if code, err = c.backend.CodeAt(opts.Context, c.address, nil); err != nil { return nil, err - } else if !code { + } else if len(code) == 0 { 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.Context, opts.From, contract, value, input) + callMsg := ethereum.CallMsg{ + From: opts.From, + To: *contract, + GasPrice: gasPrice, + Value: value, + Data: input, + } + gasLimit, err = c.backend.EstimateGas(opts.Context, callMsg) if err != nil { - return nil, fmt.Errorf("failed to exstimate gas needed: %v", err) + return nil, fmt.Errorf("failed to estimate gas needed: %v", err) } } // Create the transaction, sign it and schedule it for execution @@ -204,8 +251,166 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if err != nil { return nil, err } - if err := c.transactor.SendTransaction(opts.Context, signedTx); err != nil { + if err := c.backend.SendTransaction(opts.Context, signedTx); err != nil { return nil, err } return signedTx, nil } + +// Events will return a list of events for this contract for the given topics and +// the given start & end blocks. +func (c *BoundContract) Events(opts *EventOpts, name string, output interface{}, topics ...[]common.Hash) error { + + // check that output is a pointer + ptr := reflect.ValueOf(output) + if ptr.Kind() != reflect.Ptr { + return fmt.Errorf("need pointer to slice as output parameter, have %T", output) + } + + // check that it points to a slice + slice := ptr.Elem() + if slice.Kind() != reflect.Slice { + return fmt.Errorf("need pointer to slice as output parameter, have %T", output) + } + + // get the event so we can encode the name into the first topic + event, ok := c.abi.Events[name] + if !ok { + return fmt.Errorf("unknown event name: %v", name) + } + names := []common.Hash{event.Id()} + topics = append([][]common.Hash{names}, topics...) + + // create the filter query and retrieve the events through the backend + filterQuery := ethereum.FilterQuery{ + FromBlock: opts.FromBlock, + ToBlock: opts.ToBlock, + Addresses: []common.Address{c.address}, + Topics: topics, + } + logs, err := c.backend.FilterLogs(opts.Context, filterQuery) + if err != nil { + return fmt.Errorf("could not retrieve events (%v)", err) + } + + // for each log entry, create an event, unpack the data and append it + item := slice.Elem() + events := reflect.MakeSlice(slice.Type(), 0, len(logs)) + for _, log := range logs { + event := reflect.New(item.Type()) + err = c.abi.Unpack(item.Interface(), name, log.Data) + if err != nil { + return fmt.Errorf("could not unpack event data (%v)", err) + } + if len(log.Topics) > 1 { + // TODO: extract indexed parameters from topics 1-3 + } + events = reflect.Append(events, event) + } + + // set the output slice to our slice of events + slice.Set(events) + return nil +} + +// Subscribe subscribes to the contract for the given topics. Subscription events +// will be submitted to the provided channel. Upon cancelation of the subscription, +// the channel will be closed. A channel should only be used for one subscription. +func (c *BoundContract) Subscribe(opts *SubscribeOpts, name string, output interface{}, topics ...[]common.Hash) (uint32, error) { + + // check that output is a pointer + channel := reflect.ValueOf(output) + if channel.Kind() != reflect.Chan { + return 0, fmt.Errorf("need channel as output parameter, have %T", output) + } + message := channel.Elem() + + // get the event so we can encode the name into the first topic + event, ok := c.abi.Events[name] + if !ok { + return 0, fmt.Errorf("unknown event name: %v", name) + } + names := []common.Hash{event.Id()} + topics = append([][]common.Hash{names}, topics...) + + // create the filter query and retrieve the events through the backend + filterQuery := ethereum.FilterQuery{ + FromBlock: nil, + ToBlock: nil, + Addresses: []common.Address{c.address}, + Topics: topics, + } + + // create a channel of log messages that we can decode into events + logs := make(chan vm.Log) + subscription, err := c.backend.SubscribeFilterLogs(opts.Context, filterQuery, logs) + if err != nil { + return 0, fmt.Errorf("could not subscribe (%v)", err) + } + id := c.nextID + c.subscriptions[id] = subscription + c.nextID++ + + // retrieve logs and transcribe them into typed events + go func() { + Loop: + for { + select { + case err, ok = <-subscription.Err(): + + // error was closed, so we shut down + if !ok { + break Loop + } + + // otherwise just save the error for this subscription + c.errors[id] = err + + // receive log and transcribe + case log := <-logs: + item := reflect.New(message.Type()) + c.abi.Unpack(item.Interface(), name, log.Data) + channel.Send(item) + } + } + + // drain the logs channel; this might not always work correctly because we + // are not sure all logs were put in the channel already + Drain: + for { + select { + case log := <-logs: + item := reflect.New(message.Type()) + c.abi.Unpack(item.Interface(), name, log.Data) + channel.Send(item) + default: + break Drain + } + } + + // close the output channel + channel.Close() + }() + + return id, nil +} + +// Error returns the error that was encountered for the subscription on the given +// channel. It will reset the error upon retrieval. +func (c *BoundContract) Error(id uint32) error { + err := c.errors[id] + delete(c.errors, id) + return err +} + +// Unsubscribe cancels the subscription if one exists on the given channel. The +// subscription channel will be closed once the subscription was successfully +// canceled. +func (c *BoundContract) Unsubscribe(id uint32) error { + subscription, ok := c.subscriptions[id] + if !ok { + return fmt.Errorf("subscription doesn't exist or already closed") + } + subscription.Unsubscribe() + return nil +} diff --git a/interfaces.go b/interfaces.go new file mode 100644 index 0000000000..e95740a06d --- /dev/null +++ b/interfaces.go @@ -0,0 +1,168 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package ethereum defines interfaces for interacting with Ethereum. +package ethereum + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "golang.org/x/net/context" +) + +// TODO: move subscription to package event + +// Subscription represents an event subscription where events are +// delivered on a data channel. +type Subscription interface { + // Unsubscribe cancels the sending of events to the data channel + // and closes the error channel. + Unsubscribe() + // Err returns the subscription error channel. The error channel receives + // a value if there is an issue with the subscription (e.g. the network connection + // delivering the events has been closed). Only one value will ever be sent. + // The error channel is closed by Unsubscribe. + Err() <-chan error +} + +// ChainReader provides access to the blockchain. The methods in this interface access raw +// data from either the canonical chain (when requesting by block number) or any +// blockchain fork that was previously downloaded and processed by the node. The block +// number argument can be nil to select the latest canonical block. Reading block headers +// should be preferred over full blocks whenever possible. +type ChainReader interface { + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) + TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) + TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error) + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) +} + +// ChainStateReader wraps access to the state trie of the canonical blockchain. Note that +// implementations of the interface may be unable to return state values for old blocks. +// In many cases, using CallContract can be preferable to reading raw contract storage. +type ChainStateReader interface { + BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) + StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) + NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) +} + +// A ChainHeadEventer returns notifications whenever the canonical head block is updated. +type ChainHeadEventer interface { + SubscribeNewHeaders(ctx context.Context, ch chan<- *types.Header) (Subscription, error) +} + +// CallMsg contains parameters for contract calls. +type CallMsg struct { + From common.Address // the sender of the 'transaction' + To common.Address // the destination contract + Gas *big.Int // if nil, the call executes with near-infinite gas + GasPrice *big.Int // wei <-> gas exchange ratio + Value *big.Int // amount of wei sent along with the call + Data []byte // input data, usually an ABI-encoded contract method invocation +} + +// A ContractCaller provides contract calls, essentially transactions that are executed by +// the EVM but not mined into the blockchain. ContractCall is a low-level method to +// execute such calls. For applications which are structured around specific contracts, +// the abigen tool provides a nicer, properly typed way to perform calls. +type ContractCaller interface { + CallContract(ctx context.Context, call CallMsg, blockNumber *big.Int) ([]byte, error) +} + +// FilterQuery contains options for contact log filtering. +type FilterQuery struct { + FromBlock *big.Int // beginning of the queried range, nil means genesis block + ToBlock *big.Int // end of the range, nil means latest block + Addresses []common.Address // restricts matches to events created by specific contracts + + // The Topic list restricts matches to particular event topics. Each event has a list + // of topics. Topics matches a prefix of that list. An empty element slice matches any + // topic. Non-empty elements represent an alternative that matches any of the + // contained topics. + // + // Examples: + // {} or nil matches any topic list + // {{A}} matches topic A in first position + // {{}, {B}} matches any topic in first position, B in second position + // {{A}}, {B}} matches topic A in first position, B in second position + // {{A, B}}, {C, D}} matches topic (A OR B) in first position, (C OR D) in second position + Topics [][]common.Hash +} + +// LogFilterer provides access to contract log events using a one-off query or continuous +// event subscription. +type LogFilterer interface { + FilterLogs(ctx context.Context, q FilterQuery) ([]vm.Log, error) + SubscribeFilterLogs(ctx context.Context, q FilterQuery, ch chan<- vm.Log) (Subscription, error) +} + +// TransactionSender wraps transaction sending. The SendTransaction method injects a +// signed transaction into the pending transaction pool for execution. If the transaction +// was a contract creation, the TransactionReceipt method can be used to retrieve the +// contract address after the transaction has been mined. +// +// The transaction must be signed and have a valid nonce to be included. Consumers of the +// API can use package accounts to maintain local private keys and need can retrieve the +// next available nonce using PendingNonceAt. +type TransactionSender interface { + SendTransaction(ctx context.Context, tx *types.Transaction) error +} + +// GasPricer wraps the gas price oracle, which monitors the blockchain to determine the +// optimal gas price given current fee market conditions. +type GasPricer interface { + SuggestGasPrice(ctx context.Context) (*big.Int, error) +} + +// A PendingStateReader provides access to the pending state, which is the result of all +// known executable transactions which have not yet been included in the blockchain. It is +// commonly used to display the result of ’unconfirmed’ actions (e.g. wallet value +// transfers) initiated by the user. The PendingNonceAt operation is a good way to +// retrieve the next available transaction nonce for a specific account. +type PendingStateReader interface { + PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) + PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) + PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + PendingTransactionCount(ctx context.Context) (uint, error) +} + +// PendingContractCaller can be used to perform calls against the pending state. +type PendingContractCaller interface { + PendingCallContract(ctx context.Context, call CallMsg) ([]byte, error) +} + +// GasEstimator wraps EstimateGas, which tries to estimate the gas needed to execute a +// specific transaction based on the pending state. 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. +type GasEstimator interface { + EstimateGas(ctx context.Context, call CallMsg) (usedGas *big.Int, err error) +} + +// A PendingStateEventer provides access to real time notifications about changes to the +// pending state. +type PendingStateEventer interface { + SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) +}