diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ee1396f3..5c5a0718 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,6 +22,8 @@ jobs:
node-version: 10
- name: Install ganache
run: npm install -g ganache-cli
+ - name: Install libusb (for Ledger)
+ run: sudo apt install pkg-config libudev-dev
- name: Install Solc
run: |
@@ -47,10 +49,7 @@ jobs:
- name: cargo test (Celo)
run: |
export PATH=$HOME/bin:$PATH
- cd ethers-core && cargo test --features="celo" && cd ../
- cd ethers-providers && cargo test --features="celo" && cd ../
- cd ethers-signers && cargo test --features="celo" && cd ../
- cd ethers-contract && cargo test --features="celo" && cd ../
+ cargo test --all-features
- name: cargo fmt
run: cargo fmt --all -- --check
diff --git a/Cargo.lock b/Cargo.lock
index cbb615d8..6264e537 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -455,6 +455,7 @@ dependencies = [
"anyhow",
"ethers-contract",
"ethers-core",
+ "ethers-middleware",
"ethers-providers",
"ethers-signers",
"rand",
@@ -471,6 +472,7 @@ dependencies = [
"ethers-contract-abigen",
"ethers-contract-derive",
"ethers-core",
+ "ethers-middleware",
"ethers-providers",
"ethers-signers",
"futures",
@@ -531,6 +533,25 @@ dependencies = [
"tiny-keccak 2.0.2",
]
+[[package]]
+name = "ethers-middleware"
+version = "0.1.3"
+dependencies = [
+ "async-trait",
+ "ethers",
+ "ethers-core",
+ "ethers-providers",
+ "ethers-signers",
+ "futures-util",
+ "reqwest",
+ "rustc-hex",
+ "serde",
+ "serde-aux",
+ "thiserror",
+ "tokio",
+ "url",
+]
+
[[package]]
name = "ethers-providers"
version = "0.1.3"
@@ -564,7 +585,6 @@ dependencies = [
"coins-ledger",
"ethers",
"ethers-core",
- "ethers-providers",
"futures-util",
"rustc-hex",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index f6e967a9..c4a9afbd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,4 +6,5 @@ members = [
"./ethers-providers",
"./ethers-signers",
"./ethers-core",
+ "./ethers-middleware",
]
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index 4d2552a5..f19cd253 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -14,7 +14,6 @@ ethers-contract-abigen = { version = "0.1.3", path = "ethers-contract-abigen", o
ethers-contract-derive = { version = "0.1.3", path = "ethers-contract-derive", optional = true }
ethers-providers = { version = "0.1.3", path = "../ethers-providers" }
-ethers-signers = { version = "0.1.3", path = "../ethers-signers" }
ethers-core = { version = "0.1.3", path = "../ethers-core" }
serde = { version = "1.0.110", default-features = false }
@@ -29,9 +28,12 @@ ethers = { version = "0.1.3", path = "../ethers" }
tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
serde_json = "1.0.55"
+ethers-signers = { version = "0.1.3", path = "../ethers-signers" }
+ethers-middleware = { version = "0.1.3", path = "../ethers-middleware" }
+
[features]
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
-celo = ["ethers-core/celo", "ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo"]
+celo = ["ethers-core/celo", "ethers-core/celo", "ethers-providers/celo"]
[package.metadata.docs.rs]
all-features = true
diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index 4f89938c..277f5150 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -67,12 +67,12 @@ impl Context {
#struct_decl
- impl<'a, P: JsonRpcClient, S: Signer> #name
{
+ impl<'a, M: Middleware> #name {
/// Creates a new contract instance with the specified `ethers`
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object
- pub fn new, C: Into>>>(address: T, client: C) -> Self {
- let contract = Contract::new(address.into(), #abi_name.clone(), client.into());
+ pub fn new>(address: T, client: Arc) -> Self {
+ let contract = Contract::new(address.into(), #abi_name.clone(), client);
Self(contract)
}
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index 2e969254..5fa9dcee 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -19,8 +19,7 @@ pub(crate) fn imports(name: &str) -> TokenStream {
types::*, // import all the types so that we can codegen for everything
},
contract::{Contract, builders::{ContractCall, Event}, Lazy},
- signers::{Client, Signer},
- providers::JsonRpcClient,
+ providers::Middleware,
};
}
}
@@ -36,17 +35,17 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
// Struct declaration
#[derive(Clone)]
- pub struct #name(Contract
);
+ pub struct #name(Contract);
// Deref to the inner contract in order to access more specific functions functions
- impl std::ops::Deref for #name
{
- type Target = Contract
;
+ impl std::ops::Deref for #name {
+ type Target = Contract;
fn deref(&self) -> &Self::Target { &self.0 }
}
- impl std::fmt::Debug for #name {
+ impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple(stringify!(#name))
.field(&self.address())
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
index 17fb8552..f1ab0fe7 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
@@ -56,7 +56,7 @@ fn expand_filter(event: &Event) -> Result {
Ok(quote! {
#doc
- pub fn #name(&self) -> Event {
+ pub fn #name(&self) -> Event {
self.0.event(#ev_name).expect("event not found (this should never happen)")
}
})
@@ -319,7 +319,7 @@ mod tests {
assert_quote!(expand_filter(&event).unwrap(), {
#[doc = "Gets the contract's `Transfer` event"]
- pub fn transfer_filter(&self) -> Event {
+ pub fn transfer_filter(&self) -> Event {
self.0
.event("Transfer")
.expect("event not found (this should never happen)")
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index 1f166a03..dc13fd35 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -40,7 +40,7 @@ fn expand_function(function: &Function, alias: Option) -> Result };
+ let result = quote! { ContractCall };
let arg = expand_inputs_call_arg(&function.inputs);
let doc = util::expand_doc(&format!(
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index 4ca09660..7b684219 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -2,8 +2,7 @@ use ethers_core::{
abi::{Detokenize, Error as AbiError, Function, InvalidOutputType},
types::{Address, BlockNumber, Bytes, TransactionRequest, TxHash, U256},
};
-use ethers_providers::{JsonRpcClient, ProviderError};
-use ethers_signers::{Client, ClientError, Signer};
+use ethers_providers::Middleware;
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
@@ -11,7 +10,7 @@ use thiserror::Error as ThisError;
#[derive(ThisError, Debug)]
/// An Error which is thrown when interacting with a smart contract
-pub enum ContractError {
+pub enum ContractError {
/// Thrown when the ABI decoding fails
#[error(transparent)]
DecodingError(#[from] AbiError),
@@ -20,13 +19,9 @@ pub enum ContractError {
#[error(transparent)]
DetokenizationError(#[from] InvalidOutputType),
- /// Thrown when a client call fails
- #[error(transparent)]
- ClientError(#[from] ClientError),
-
/// Thrown when a provider call fails
- #[error(transparent)]
- ProviderError(#[from] ProviderError),
+ #[error("{0}")]
+ MiddlewareError(M::Error),
/// Thrown during deployment if a constructor argument was passed in the `deploy`
/// call but a constructor was not present in the ABI
@@ -42,18 +37,18 @@ pub enum ContractError {
#[derive(Debug, Clone)]
#[must_use = "contract calls do nothing unless you `send` or `call` them"]
/// Helper for managing a transaction before submitting it to a node
-pub struct ContractCall {
+pub struct ContractCall {
/// The raw transaction object
pub tx: TransactionRequest,
/// The ABI of the function being called
pub function: Function,
/// Optional block number to be used when calculating the transaction's gas and nonce
pub block: Option,
- pub(crate) client: Arc>,
+ pub(crate) client: Arc,
pub(crate) datatype: PhantomData,
}
-impl ContractCall
{
+impl ContractCall {
/// Sets the `from` field in the transaction to the provided value
pub fn from>(mut self, from: T) -> Self {
self.tx.from = Some(from.into());
@@ -85,10 +80,9 @@ impl ContractCall
{
}
}
-impl
ContractCall
+impl ContractCall
where
- S: Signer,
- P: JsonRpcClient,
+ M: Middleware,
D: Detokenize,
{
/// Returns the underlying transaction's ABI encoded data
@@ -97,8 +91,11 @@ where
}
/// Returns the estimated gas cost for the underlying transaction to be executed
- pub async fn estimate_gas(&self) -> Result {
- Ok(self.client.estimate_gas(&self.tx).await?)
+ pub async fn estimate_gas(&self) -> Result> {
+ self.client
+ .estimate_gas(&self.tx)
+ .await
+ .map_err(ContractError::MiddlewareError)
}
/// Queries the blockchain via an `eth_call` for the provided transaction.
@@ -110,8 +107,12 @@ where
/// and return the return type of the transaction without mutating the state
///
/// Note: this function _does not_ send a transaction from your account
- pub async fn call(&self) -> Result {
- let bytes = self.client.call(&self.tx, self.block).await?;
+ pub async fn call(&self) -> Result> {
+ let bytes = self
+ .client
+ .call(&self.tx, self.block)
+ .await
+ .map_err(ContractError::MiddlewareError)?;
let tokens = self.function.decode_output(&bytes.0)?;
let data = D::from_tokens(tokens)?;
@@ -120,7 +121,10 @@ where
}
/// Signs and broadcasts the provided transaction
- pub async fn send(self) -> Result {
- Ok(self.client.send_transaction(self.tx, self.block).await?)
+ pub async fn send(self) -> Result> {
+ self.client
+ .send_transaction(self.tx, self.block)
+ .await
+ .map_err(ContractError::MiddlewareError)
}
}
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index cb31a07c..861c086d 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -4,8 +4,7 @@ use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest, TxHash},
};
-use ethers_providers::{JsonRpcClient, PendingTransaction};
-use ethers_signers::{Client, Signer};
+use ethers_providers::{Middleware, PendingTransaction};
use rustc_hex::ToHex;
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
@@ -74,9 +73,7 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, syn
/// let abi: Abi = serde_json::from_str(r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#)?;
///
/// // connect to the network
-/// let provider = Provider::::try_from("http://localhost:8545").unwrap();
-/// let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
-/// .parse::()?.connect(provider);
+/// let client = Provider::::try_from("http://localhost:8545").unwrap();
///
/// // create the contract object at the address
/// let contract = Contract::new(address, abi, client);
@@ -108,15 +105,14 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, syn
/// # async fn foo() -> Result<(), Box> {
/// use ethers_core::{abi::Abi, types::Address};
/// use ethers_contract::Contract;
-/// use ethers_providers::{Provider, Http};
+/// use ethers_providers::{Provider, Http, Middleware};
/// use ethers_signers::Wallet;
/// use std::convert::TryFrom;
/// use ethers_core::abi::{Detokenize, Token, InvalidOutputType};
/// # // this is a fake address used just for this example
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::()?;
/// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
-/// # let provider = Provider::::try_from("http://localhost:8545").unwrap();
-/// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::()?.connect(provider);
+/// # let client = Provider::::try_from("http://localhost:8545").unwrap();
/// # let contract = Contract::new(address, abi, client);
///
/// #[derive(Clone, Debug)]
@@ -163,8 +159,8 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, syn
/// [`event`]: method@crate::Contract::event
/// [`method`]: method@crate::Contract::method
#[derive(Debug, Clone)]
-pub struct Contract {
- client: Arc>,
+pub struct Contract {
+ client: Arc,
abi: Abi,
address: Address,
@@ -175,13 +171,9 @@ pub struct Contract {
methods: HashMap,
}
-impl Contract
-where
- S: Signer,
- P: JsonRpcClient,
-{
+impl Contract {
/// Creates a new contract from the provided client, abi and address
- pub fn new(address: Address, abi: Abi, client: impl Into>>) -> Self {
+ pub fn new(address: Address, abi: Abi, client: impl Into>) -> Self {
let methods = create_mapping(&abi.functions, |function| function.selector());
Self {
@@ -193,11 +185,11 @@ where
}
/// Returns an [`Event`](crate::builders::Event) builder for the provided event name.
- pub fn event(&self, name: &str) -> Result, Error> {
+ pub fn event(&self, name: &str) -> Result, Error> {
// get the event's full name
let event = self.abi.event(name)?;
Ok(Event {
- provider: &self.client.provider(),
+ provider: &self.client,
filter: Filter::new()
.event(&event.abi_signature())
.address(self.address),
@@ -213,7 +205,7 @@ where
&self,
name: &str,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
// get the function
let function = self.abi.function(name)?;
self.method_func(function, args)
@@ -225,7 +217,7 @@ where
&self,
signature: Selector,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
let function = self
.methods
.get(&signature)
@@ -238,7 +230,7 @@ where
&self,
function: &Function,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
let tokens = args.into_tokens();
// create the calldata
@@ -265,8 +257,7 @@ where
/// Clones `self` internally
pub fn at>(&self, address: T) -> Self
where
- P: Clone,
- S: Clone,
+ M: Clone,
{
let mut this = self.clone();
this.address = address.into();
@@ -276,10 +267,9 @@ where
/// Returns a new contract instance using the provided client
///
/// Clones `self` internally
- pub fn connect(&self, client: Arc>) -> Self
+ pub fn connect(&self, client: Arc) -> Self
where
- P: Clone,
- S: Clone,
+ M: Clone,
{
let mut this = self.clone();
this.client = client;
@@ -297,14 +287,12 @@ where
}
/// Returns a reference to the contract's client
- pub fn client(&self) -> &Client {
+ pub fn client(&self) -> &M {
&self.client
}
- /// Helper which creates a pending transaction object from a transaction hash
- /// using the provider's polling interval
- pub fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, P> {
- self.client.provider().pending_transaction(tx_hash)
+ pub fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, M::Provider> {
+ self.client.pending_transaction(tx_hash)
}
}
diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs
index 8f5997f9..401dc01b 100644
--- a/ethers-contract/src/event.rs
+++ b/ethers-contract/src/event.rs
@@ -1,6 +1,6 @@
use crate::ContractError;
-use ethers_providers::{JsonRpcClient, Provider};
+use ethers_providers::Middleware;
use ethers_core::{
abi::{Detokenize, Event as AbiEvent, RawLog},
@@ -13,17 +13,17 @@ use std::marker::PhantomData;
/// Helper for managing the event filter before querying or streaming its logs
#[derive(Debug)]
#[must_use = "event filters do nothing unless you `query` or `stream` them"]
-pub struct Event<'a: 'b, 'b, P, D> {
+pub struct Event<'a: 'b, 'b, M, D> {
/// The event filter's state
pub filter: Filter,
/// The ABI of the event which is being filtered
pub event: &'b AbiEvent,
- pub(crate) provider: &'a Provider
,
+ pub(crate) provider: &'a M,
pub(crate) datatype: PhantomData,
}
// TODO: Improve these functions
-impl Event<'_, '_, P, D> {
+impl Event<'_, '_, M, D> {
/// Sets the filter's `from` block
#[allow(clippy::wrong_self_convention)]
pub fn from_block>(mut self, block: T) -> Self {
@@ -63,41 +63,53 @@ impl Event<'_, '_, P, D> {
}
}
-impl<'a, 'b, P, D> Event<'a, 'b, P, D>
+impl<'a, 'b, M, D> Event<'a, 'b, M, D>
where
- P: JsonRpcClient,
+ M: Middleware,
D: 'b + Detokenize + Clone,
'a: 'b,
{
/// Returns a stream for the event
pub async fn stream(
self,
- ) -> Result> + 'b, ContractError> {
- let filter = self.provider.watch(&self.filter).await?;
+ ) -> Result>> + 'b, ContractError> {
+ let filter = self
+ .provider
+ .watch(&self.filter)
+ .await
+ .map_err(ContractError::MiddlewareError)?;
Ok(filter.stream().map(move |log| self.parse_log(log)))
}
}
-impl Event<'_, '_, P, D>
+impl Event<'_, '_, M, D>
where
- P: JsonRpcClient,
+ M: Middleware,
D: Detokenize + Clone,
{
/// Queries the blockchain for the selected filter and returns a vector of matching
/// event logs
- pub async fn query(&self) -> Result, ContractError> {
- let logs = self.provider.get_logs(&self.filter).await?;
+ pub async fn query(&self) -> Result, ContractError> {
+ let logs = self
+ .provider
+ .get_logs(&self.filter)
+ .await
+ .map_err(ContractError::MiddlewareError)?;
let events = logs
.into_iter()
.map(|log| self.parse_log(log))
- .collect::, ContractError>>()?;
+ .collect::, ContractError>>()?;
Ok(events)
}
/// Queries the blockchain for the selected filter and returns a vector of logs
/// along with their metadata
- pub async fn query_with_meta(&self) -> Result, ContractError> {
- let logs = self.provider.get_logs(&self.filter).await?;
+ pub async fn query_with_meta(&self) -> Result, ContractError> {
+ let logs = self
+ .provider
+ .get_logs(&self.filter)
+ .await
+ .map_err(ContractError::MiddlewareError)?;
let events = logs
.into_iter()
.map(|log| {
@@ -105,11 +117,11 @@ where
let event = self.parse_log(log)?;
Ok((event, meta))
})
- .collect::>()?;
+ .collect::>>()?;
Ok(events)
}
- fn parse_log(&self, log: Log) -> Result {
+ fn parse_log(&self, log: Log) -> Result> {
// ethabi parses the unindexed and indexed logs together to a
// vector of tokens
let tokens = self
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index c8d8f4b2..8e24e2c6 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -4,40 +4,28 @@ use ethers_core::{
abi::{Abi, Tokenize},
types::{BlockNumber, Bytes, TransactionRequest},
};
-use ethers_providers::JsonRpcClient;
-use ethers_signers::{Client, Signer};
+use ethers_providers::Middleware;
-use std::{sync::Arc, time::Duration};
+use std::sync::Arc;
#[derive(Debug, Clone)]
/// Helper which manages the deployment transaction of a smart contract
-pub struct Deployer {
+pub struct Deployer {
/// The deployer's transaction, exposed for overriding the defaults
pub tx: TransactionRequest,
abi: Abi,
- client: Arc>,
+ client: Arc,
confs: usize,
block: BlockNumber,
- interval: Duration,
}
-impl Deployer
-where
- S: Signer,
- P: JsonRpcClient,
-{
+impl Deployer {
/// Sets the number of confirmations to wait for the contract deployment transaction
pub fn confirmations>(mut self, confirmations: T) -> Self {
self.confs = confirmations.into();
self
}
- /// Sets the poll interval for the pending deployment transaction's inclusion
- pub fn interval>(mut self, interval: T) -> Self {
- self.interval = interval.into();
- self
- }
-
pub fn block>(mut self, block: T) -> Self {
self.block = block.into();
self
@@ -46,18 +34,20 @@ where
/// Broadcasts the contract deployment transaction and after waiting for it to
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
/// struct at the deployed contract's address.
- pub async fn send(self) -> Result, ContractError> {
+ pub async fn send(self) -> Result, ContractError> {
let tx_hash = self
.client
.send_transaction(self.tx, Some(self.block))
- .await?;
+ .await
+ .map_err(ContractError::MiddlewareError)?;
+
+ // TODO: Should this be calculated "optimistically" by address/nonce?
let receipt = self
.client
.pending_transaction(tx_hash)
- .interval(self.interval)
.confirmations(self.confs)
- .await?;
-
+ .await
+ .map_err(|_| ContractError::ContractNotDeployed)?;
let address = receipt
.contract_address
.ok_or(ContractError::ContractNotDeployed)?;
@@ -72,7 +62,7 @@ where
}
/// Returns a reference to the deployer's client
- pub fn client(&self) -> &Client {
+ pub fn client(&self) -> &M {
&self.client
}
}
@@ -105,9 +95,8 @@ where
/// .expect("could not find contract");
///
/// // connect to the network
-/// let provider = Provider::::try_from("http://localhost:8545").unwrap();
-/// let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
-/// .parse::()?.connect(provider);
+/// let client = Provider::::try_from("http://localhost:8545").unwrap();
+/// let client = std::sync::Arc::new(client);
///
/// // create a factory which will be used to deploy instances of the contract
/// let factory = ContractFactory::new(contract.abi.clone(), contract.bytecode.clone(), client);
@@ -122,23 +111,19 @@ where
/// println!("{}", contract.address());
/// # Ok(())
/// # }
-pub struct ContractFactory {
- client: Arc>,
+pub struct ContractFactory {
+ client: Arc,
abi: Abi,
bytecode: Bytes,
}
-impl ContractFactory
-where
- S: Signer,
- P: JsonRpcClient,
-{
+impl ContractFactory {
/// Creates a factory for deployment of the Contract with bytecode, and the
/// constructor defined in the abi. The client will be used to send any deployment
/// transaction.
- pub fn new(abi: Abi, bytecode: Bytes, client: impl Into>>) -> Self {
+ pub fn new(abi: Abi, bytecode: Bytes, client: Arc) -> Self {
Self {
- client: client.into(),
+ client,
abi,
bytecode,
}
@@ -152,7 +137,7 @@ where
/// 1. If there are no constructor arguments, you should pass `()` as the argument.
/// 1. The default poll duration is 7 seconds.
/// 1. The default number of confirmations is 1 block.
- pub fn deploy(self, constructor_args: T) -> Result, ContractError> {
+ pub fn deploy(self, constructor_args: T) -> Result, ContractError> {
// Encode the constructor args & concatenate with the bytecode if necessary
let params = constructor_args.into_tokens();
let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
@@ -167,7 +152,6 @@ where
// create the tx object. Since we're deploying a contract, `to` is `None`
let tx = TransactionRequest {
- from: Some(self.client.address()),
to: None,
data: Some(data),
..Default::default()
@@ -179,7 +163,6 @@ where
tx,
confs: 1,
block: BlockNumber::Latest,
- interval: self.client.get_interval(),
})
}
}
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 6ea2eaca..d33931e9 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -2,8 +2,7 @@ use ethers_core::{
abi::{Detokenize, Function, Token},
types::{Address, BlockNumber, NameOrAddress, TxHash, U256},
};
-use ethers_providers::JsonRpcClient;
-use ethers_signers::{Client, Signer};
+use ethers_providers::Middleware;
use std::{collections::HashMap, str::FromStr, sync::Arc};
@@ -62,8 +61,7 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| {
/// use ethers::{
/// abi::Abi,
/// contract::{Contract, Multicall},
-/// providers::{Http, Provider},
-/// signers::{Client, Wallet},
+/// providers::{Middleware, Http, Provider},
/// types::{Address, H256, U256},
/// };
/// use std::{convert::TryFrom, sync::Arc};
@@ -76,13 +74,11 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| {
/// let abi: Abi = serde_json::from_str(r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#)?;
///
/// // connect to the network
-/// let provider = Provider::::try_from("https://kovan.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")?;
-/// let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
-/// .parse::()?.connect(provider);
+/// let client = Provider::::try_from("https://kovan.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")?;
///
/// // create the contract object. This will be used to construct the calls for multicall
/// let client = Arc::new(client);
-/// let contract = Contract::new(address, abi, Arc::clone(&client));
+/// let contract = Contract::>::new(address, abi, Arc::clone(&client));
///
/// // note that these [`ContractCall`]s are futures, and need to be `.await`ed to resolve.
/// // But we will let `Multicall` to take care of that for us
@@ -114,7 +110,7 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| {
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also
/// // returns the transaction hash
/// let tx_hash = multicall.send().await?;
-/// let _tx_receipt = client.provider().pending_transaction(tx_hash).await?;
+/// let _tx_receipt = client.pending_transaction(tx_hash).await?;
///
/// // you can also query ETH balances of multiple addresses
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::()?;
@@ -132,10 +128,10 @@ pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| {
/// [`block`]: method@crate::Multicall::block
/// [`add_call`]: methond@crate::Multicall::add_call
#[derive(Clone)]
-pub struct Multicall {
+pub struct Multicall {
calls: Vec,
block: Option,
- contract: MulticallContract,
+ contract: MulticallContract,
}
#[derive(Clone)]
@@ -147,11 +143,7 @@ pub struct Call {
function: Function,
}
-impl Multicall
-where
- P: JsonRpcClient,
- S: Signer,
-{
+impl Multicall {
/// Creates a new Multicall instance from the provided client. If provided with an `address`,
/// it instantiates the Multicall contract with that address. Otherwise it fetches the address
/// from the address book.
@@ -159,10 +151,10 @@ where
/// # Panics
/// If a `None` address is provided, and the provided client also does not belong to one of
/// the supported network IDs (mainnet, kovan, rinkeby and goerli)
- pub async fn new>>>(
+ pub async fn new>>(
client: C,
address: Option,
- ) -> Result {
+ ) -> Result> {
let client = client.into();
// Fetch chain id and the corresponding address of Multicall contract
@@ -171,7 +163,10 @@ where
let address: Address = match address {
Some(addr) => addr,
None => {
- let chain_id = client.get_chainid().await?;
+ let chain_id = client
+ .get_chainid()
+ .await
+ .map_err(ContractError::MiddlewareError)?;
match ADDRESS_BOOK.get(&chain_id) {
Some(addr) => *addr,
None => panic!(
@@ -203,7 +198,7 @@ where
///
/// If more than the maximum number of supported calls are added. The maximum
/// limits is constrained due to tokenization/detokenization support for tuples
- pub fn add_call(&mut self, call: ContractCall) -> &mut Self {
+ pub fn add_call(&mut self, call: ContractCall) -> &mut Self {
if self.calls.len() >= 16 {
panic!("Cannot support more than {} calls", 16);
}
@@ -242,14 +237,12 @@ where
/// # use ethers::prelude::*;
/// # use std::{sync::Arc, convert::TryFrom};
/// #
- /// # let provider = Provider::::try_from("http://localhost:8545")?;
- /// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
- /// # .parse::()?.connect(provider);
+ /// # let client = Provider::::try_from("http://localhost:8545")?;
/// # let client = Arc::new(client);
/// #
/// # let abi = serde_json::from_str("")?;
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::()?;
- /// # let contract = Contract::new(address, abi, client.clone());
+ /// # let contract = Contract::>::new(address, abi, client.clone());
/// #
/// # let broadcast_1 = contract.method::<_, H256>("setValue", "some value".to_owned())?;
/// # let broadcast_2 = contract.method::<_, H256>("setValue", "new value".to_owned())?;
@@ -278,7 +271,7 @@ where
/// Queries the Ethereum blockchain via an `eth_call`, but via the Multicall contract.
///
- /// It returns a [`ContractError`] if there is any error in the RPC call or while
+ /// It returns a [`ContractError`] if there is any error in the RPC call or while
/// detokenizing the tokens back to the expected return type. The return type must be
/// annonated while calling this method.
///
@@ -287,9 +280,7 @@ where
/// # use ethers::prelude::*;
/// # use std::convert::TryFrom;
/// #
- /// # let provider = Provider::::try_from("http://localhost:8545")?;
- /// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
- /// # .parse::()?.connect(provider);
+ /// # let client = Provider::::try_from("http://localhost:8545")?;
/// #
/// # let multicall = Multicall::new(client, None).await?;
/// // If the Solidity function calls has the following return types:
@@ -303,8 +294,8 @@ where
///
/// Note: this method _does not_ send a transaction from your account
///
- /// [`ContractError`]: crate::ContractError
- pub async fn call(&self) -> Result {
+ /// [`ContractError`]: crate::ContractError
+ pub async fn call(&self) -> Result> {
let contract_call = self.as_contract_call();
// Fetch response from the Multicall contract
@@ -324,7 +315,7 @@ where
_ => Token::Tuple(tokens),
})
})
- .collect::, ContractError>>()?;
+ .collect::, ContractError>>()?;
// Form tokens that represent tuples
let tokens = vec![Token::Tuple(tokens)];
@@ -341,9 +332,7 @@ where
/// # async fn foo() -> Result<(), Box> {
/// # use ethers::prelude::*;
/// # use std::convert::TryFrom;
- /// # let provider = Provider::::try_from("http://localhost:8545")?;
- /// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
- /// # .parse::()?.connect(provider);
+ /// # let client = Provider::::try_from("http://localhost:8545")?;
/// # let multicall = Multicall::new(client, None).await?;
/// let tx_hash = multicall.send().await?;
/// # Ok(())
@@ -352,7 +341,7 @@ where
///
/// Note: this method sends a transaction from your account, and will return an error
/// if you do not have sufficient funds to pay for gas
- pub async fn send(&self) -> Result {
+ pub async fn send(&self) -> Result> {
let contract_call = self.as_contract_call();
// Broadcast transaction and return the transaction hash
@@ -361,7 +350,7 @@ where
Ok(tx_hash)
}
- fn as_contract_call(&self) -> ContractCall>)> {
+ fn as_contract_call(&self) -> ContractCall>)> {
// Map the Multicall struct into appropriate types for `aggregate` function
let calls: Vec<(Address, Vec)> = self
.calls
diff --git a/ethers-contract/src/multicall/multicall_contract.rs b/ethers-contract/src/multicall/multicall_contract.rs
index 6280c688..393eb913 100644
--- a/ethers-contract/src/multicall/multicall_contract.rs
+++ b/ethers-contract/src/multicall/multicall_contract.rs
@@ -10,33 +10,32 @@ mod multicallcontract_mod {
abi::{Abi, Detokenize, InvalidOutputType, Token, Tokenizable},
types::*,
};
- use ethers_providers::JsonRpcClient;
- use ethers_signers::{Client, Signer};
+ use ethers_providers::{JsonRpcClient, Middleware};
#[doc = "MulticallContract was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"]
use std::sync::Arc;
pub static MULTICALLCONTRACT_ABI: Lazy = Lazy::new(|| {
serde_json :: from_str ( "[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct MulticallContract.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"aggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"returnData\",\"type\":\"bytes[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockCoinbase\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"coinbase\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockDifficulty\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"difficulty\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"gaslimit\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getEthBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLastBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" ) . expect ( "invalid abi" )
});
#[derive(Clone)]
- pub struct MulticallContract(Contract
);
- impl
std::ops::Deref for MulticallContract
{
- type Target = Contract
;
+ pub struct MulticallContract(Contract);
+ impl std::ops::Deref for MulticallContract {
+ type Target = Contract;
fn deref(&self) -> &Self::Target {
&self.0
}
}
- impl std::fmt::Debug for MulticallContract {
+ impl std::fmt::Debug for MulticallContract {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_tuple(stringify!(MulticallContract))
.field(&self.address())
.finish()
}
}
- impl<'a, P: JsonRpcClient, S: Signer> MulticallContract {
+ impl<'a, M: Middleware> MulticallContract {
#[doc = r" Creates a new contract instance with the specified `ethers`"]
#[doc = r" client at the given `Address`. The contract derefs to a `ethers::Contract`"]
#[doc = r" object"]
- pub fn new, C: Into>>>(address: T, client: C) -> Self {
+ pub fn new, C: Into>>(address: T, client: C) -> Self {
let contract =
Contract::new(address.into(), MULTICALLCONTRACT_ABI.clone(), client.into());
Self(contract)
@@ -45,49 +44,49 @@ mod multicallcontract_mod {
pub fn aggregate(
&self,
calls: Vec<(Address, Vec)>,
- ) -> ContractCall>)> {
+ ) -> ContractCall>)> {
self.0
.method_hash([37, 45, 186, 66], calls)
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getCurrentBlockDifficulty` (0x72425d9d) function"]
- pub fn get_current_block_difficulty(&self) -> ContractCall {
+ pub fn get_current_block_difficulty(&self) -> ContractCall {
self.0
.method_hash([114, 66, 93, 157], ())
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getCurrentBlockGasLimit` (0x86d516e8) function"]
- pub fn get_current_block_gas_limit(&self) -> ContractCall {
+ pub fn get_current_block_gas_limit(&self) -> ContractCall {
self.0
.method_hash([134, 213, 22, 232], ())
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getCurrentBlockTimestamp` (0x0f28c97d) function"]
- pub fn get_current_block_timestamp(&self) -> ContractCall {
+ pub fn get_current_block_timestamp(&self) -> ContractCall {
self.0
.method_hash([15, 40, 201, 125], ())
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getCurrentBlockCoinbase` (0xa8b0574e) function"]
- pub fn get_current_block_coinbase(&self) -> ContractCall {
+ pub fn get_current_block_coinbase(&self) -> ContractCall {
self.0
.method_hash([168, 176, 87, 78], ())
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getBlockHash` (0xee82ac5e) function"]
- pub fn get_block_hash(&self, block_number: U256) -> ContractCall {
+ pub fn get_block_hash(&self, block_number: U256) -> ContractCall {
self.0
.method_hash([238, 130, 172, 94], block_number)
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getEthBalance` (0x4d2301cc) function"]
- pub fn get_eth_balance(&self, addr: Address) -> ContractCall {
+ pub fn get_eth_balance(&self, addr: Address) -> ContractCall {
self.0
.method_hash([77, 35, 1, 204], addr)
.expect("method not found (this should never happen)")
}
#[doc = "Calls the contract's `getLastBlockHash` (0x27e86d6e) function"]
- pub fn get_last_block_hash(&self) -> ContractCall {
+ pub fn get_last_block_hash(&self) -> ContractCall {
self.0
.method_hash([39, 232, 109, 110], ())
.expect("method not found (this should never happen)")
diff --git a/ethers-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs
index c22802b0..6697a240 100644
--- a/ethers-contract/tests/common/mod.rs
+++ b/ethers-contract/tests/common/mod.rs
@@ -5,8 +5,9 @@ use ethers_core::{
use ethers_contract::{Contract, ContractFactory};
use ethers_core::utils::{GanacheInstance, Solc};
-use ethers_providers::{Http, Provider};
-use ethers_signers::{Client, Wallet};
+use ethers_middleware::Client;
+use ethers_providers::{Http, Middleware, Provider};
+use ethers_signers::Wallet;
use std::{convert::TryFrom, sync::Arc, time::Duration};
// Note: We also provide the `abigen` macro for generating these bindings automatically
@@ -43,23 +44,19 @@ pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
(contract.abi.clone(), contract.bytecode.clone())
}
+type HttpWallet = Client, Wallet>;
+
/// connects the private key to http://localhost:8545
-pub fn connect(ganache: &GanacheInstance, idx: usize) -> Arc> {
- let provider = Provider::::try_from(ganache.endpoint()).unwrap();
+pub fn connect(ganache: &GanacheInstance, idx: usize) -> Arc {
+ let provider = Provider::::try_from(ganache.endpoint())
+ .unwrap()
+ .interval(Duration::from_millis(10u64));
let wallet: Wallet = ganache.keys()[idx].clone().into();
- Arc::new(
- wallet
- .connect(provider)
- .interval(Duration::from_millis(10u64)),
- )
+ Arc::new(Client::new(provider, wallet))
}
/// Launches a ganache instance and deploys the SimpleStorage contract
-pub async fn deploy(
- client: Arc>,
- abi: Abi,
- bytecode: Bytes,
-) -> Contract {
+pub async fn deploy(client: Arc, abi: Abi, bytecode: Bytes) -> Contract {
let factory = ContractFactory::new(abi, bytecode, client);
factory
.deploy("initial value".to_string())
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
index 824c91f8..a030b8df 100644
--- a/ethers-contract/tests/contract.rs
+++ b/ethers-contract/tests/contract.rs
@@ -8,8 +8,7 @@ mod eth_tests {
use super::*;
use ethers::{
contract::Multicall,
- providers::{Http, Provider, StreamExt},
- signers::Client,
+ providers::{Http, Middleware, Provider, StreamExt},
types::{Address, U256},
utils::Ganache,
};
@@ -169,7 +168,8 @@ mod eth_tests {
// get the first account
let deployer = provider.get_accounts().await.unwrap()[0];
- let client = Arc::new(Client::from(provider).with_sender(deployer));
+ let client = Arc::new(provider.with_sender(deployer));
+ dbg!(deployer);
let contract = deploy(client, abi, bytecode).await;
@@ -341,6 +341,7 @@ mod eth_tests {
mod celo_tests {
use super::*;
use ethers::{
+ middleware::Client,
providers::{Http, Provider},
signers::Wallet,
types::BlockNumber,
@@ -352,15 +353,16 @@ mod celo_tests {
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
// Celo testnet
- let provider =
- Provider::::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
+ let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org")
+ .unwrap()
+ .interval(Duration::from_millis(6000));
// Funded with https://celo.org/developers/faucet
- let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
+ let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
.parse::()
- .unwrap()
- .connect(provider)
- .interval(Duration::from_millis(6000));
+ .unwrap();
+
+ let client = Client::new(provider, wallet);
let client = Arc::new(client);
let factory = ContractFactory::new(abi, bytecode, client);
diff --git a/ethers-core/src/types/crypto/keys.rs b/ethers-core/src/types/crypto/keys.rs
index f38e7eb9..55361133 100644
--- a/ethers-core/src/types/crypto/keys.rs
+++ b/ethers-core/src/types/crypto/keys.rs
@@ -1,5 +1,5 @@
use crate::{
- types::{Address, NameOrAddress, Signature, Transaction, TransactionRequest, H256, U256},
+ types::{Address, Signature, TransactionRequest, H256},
utils::{hash_message, keccak256},
};
@@ -17,7 +17,6 @@ use serde::{
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{fmt, ops::Deref, str::FromStr};
-use thiserror::Error;
/// A private key on Secp256k1
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -60,21 +59,6 @@ impl FromStr for PrivateKey {
}
}
-/// An error which may be thrown when attempting to sign a transaction with
-/// missing fields
-#[derive(Clone, Debug, Error)]
-pub enum TxError {
- /// Thrown if the `nonce` field is missing
- #[error("no nonce was specified")]
- NonceMissing,
- /// Thrown if the `gas_price` field is missing
- #[error("no gas price was specified")]
- GasPriceMissing,
- /// Thrown if the `gas` field is missing
- #[error("no gas was specified")]
- GasMissing,
-}
-
impl PrivateKey {
pub fn new(rng: &mut R) -> Self {
PrivateKey(SecretKey::random(rng))
@@ -110,62 +94,13 @@ impl PrivateKey {
///
/// If `tx.to` is an ENS name. The caller MUST take care of name resolution before
/// calling this function.
- pub fn sign_transaction(
- &self,
- tx: TransactionRequest,
- chain_id: Option,
- ) -> Result {
- // The nonce, gas and gasprice fields must already be populated
- let nonce = tx.nonce.ok_or(TxError::NonceMissing)?;
- let gas_price = tx.gas_price.ok_or(TxError::GasPriceMissing)?;
- let gas = tx.gas.ok_or(TxError::GasMissing)?;
-
+ pub fn sign_transaction(&self, tx: &TransactionRequest, chain_id: Option) -> Signature {
// Get the transaction's sighash
let sighash = tx.sighash(chain_id);
let message =
Message::parse_slice(sighash.as_bytes()).expect("hash is non-zero 32-bytes; qed");
-
// Sign it (with replay protection if applicable)
- let signature = self.sign_with_eip155(&message, chain_id);
-
- // Get the actual transaction hash
- let rlp = tx.rlp_signed(&signature);
- let hash = keccak256(&rlp.0);
-
- // This function should not be called with ENS names
- let to = tx.to.map(|to| match to {
- NameOrAddress::Address(inner) => inner,
- NameOrAddress::Name(_) => {
- panic!("Expected `to` to be an Ethereum Address, not an ENS name")
- }
- });
-
- Ok(Transaction {
- hash: hash.into(),
- nonce,
- from: self.into(),
- to,
- value: tx.value.unwrap_or_default(),
- gas_price,
- gas,
- input: tx.data.unwrap_or_default(),
- v: signature.v.into(),
- r: U256::from_big_endian(signature.r.as_bytes()),
- s: U256::from_big_endian(signature.s.as_bytes()),
-
- // Leave these empty as they're only used for included transactions
- block_hash: None,
- block_number: None,
- transaction_index: None,
-
- // Celo support
- #[cfg(feature = "celo")]
- fee_currency: tx.fee_currency,
- #[cfg(feature = "celo")]
- gateway_fee: tx.gateway_fee,
- #[cfg(feature = "celo")]
- gateway_fee_recipient: tx.gateway_fee_recipient,
- })
+ self.sign_with_eip155(&message, chain_id)
}
fn sign_with_eip155(&self, message: &Message, chain_id: Option) -> Signature {
@@ -325,45 +260,6 @@ mod tests {
}
}
- #[test]
- #[cfg(not(feature = "celo"))]
- fn signs_tx() {
- use crate::types::{Address, Bytes};
- // retrieved test vector from:
- // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
- let tx = TransactionRequest {
- from: None,
- to: Some(
- "F0109fC8DF283027b6285cc889F5aA624EaC1F55"
- .parse::()
- .unwrap()
- .into(),
- ),
- value: Some(1_000_000_000.into()),
- gas: Some(2_000_000.into()),
- nonce: Some(0.into()),
- gas_price: Some(21_000_000_000u128.into()),
- data: None,
- };
- let chain_id = 1;
-
- let key: PrivateKey = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
- .parse()
- .unwrap();
-
- let tx = key.sign_transaction(tx, Some(chain_id)).unwrap();
-
- assert_eq!(
- tx.hash,
- "de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593"
- .parse()
- .unwrap()
- );
-
- let expected_rlp = Bytes("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68".from_hex().unwrap());
- assert_eq!(tx.rlp(), expected_rlp);
- }
-
#[test]
fn signs_data() {
// test vector taken from:
diff --git a/ethers-core/src/types/crypto/mod.rs b/ethers-core/src/types/crypto/mod.rs
index e7e1b4ee..20ffaa34 100644
--- a/ethers-core/src/types/crypto/mod.rs
+++ b/ethers-core/src/types/crypto/mod.rs
@@ -1,5 +1,5 @@
mod keys;
-pub use keys::{PrivateKey, PublicKey, TxError};
+pub use keys::{PrivateKey, PublicKey};
mod signature;
pub use signature::{Signature, SignatureError};
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
new file mode 100644
index 00000000..f6b6c6eb
--- /dev/null
+++ b/ethers-middleware/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "ethers-middleware"
+license = "MIT OR Apache-2.0"
+version = "0.1.3"
+authors = ["Georgios Konstantopoulos "]
+edition = "2018"
+description = "Middleware implementations for the ethers-rs crate"
+homepage = "https://docs.rs/ethers"
+repository = "https://github.com/gakonst/ethers-rs"
+keywords = ["ethereum", "web3", "celo", "ethers"]
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[dependencies]
+ethers-core = { version = "0.1.3", path = "../ethers-core" }
+ethers-providers = { version = "0.1.3", path = "../ethers-providers" }
+ethers-signers = { version = "0.1.3", path = "../ethers-signers" }
+
+async-trait = { version = "0.1.31", default-features = false }
+serde = { version = "1.0.110", default-features = false, features = ["derive"] }
+thiserror = { version = "1.0.15", default-features = false }
+
+futures-util = { version = "0.3.5", default-features = false }
+
+# for gas oracles
+serde-aux = "0.6.1"
+reqwest = { version = "0.10.4", default-features = false, features = ["json", "rustls-tls"] }
+url = { version = "2.1.1", default-features = false }
+
+[dev-dependencies]
+ethers = { version = "0.1.3", path = "../ethers" }
+
+rustc-hex = "2.1.0"
+tokio = { version = "0.2.21", default-features = false, features = ["rt-core", "macros"] }
+
+[features]
+celo = ["ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo"]
diff --git a/ethers-middleware/src/client.rs b/ethers-middleware/src/client.rs
new file mode 100644
index 00000000..1ff27042
--- /dev/null
+++ b/ethers-middleware/src/client.rs
@@ -0,0 +1,346 @@
+use ethers_signers::Signer;
+
+use ethers_core::{
+ types::{
+ Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest,
+ TxHash, U256,
+ },
+ utils::keccak256,
+};
+use ethers_providers::Middleware;
+
+use async_trait::async_trait;
+use futures_util::{future::ok, join};
+use std::future::Future;
+use thiserror::Error;
+
+#[derive(Clone, Debug)]
+/// Middleware used for locally signing transactions, compatible with any implementer
+/// of the [`Signer`] trait.
+///
+/// # Example
+///
+/// ```no_run
+/// use ethers::{
+/// providers::{Middleware, Provider, Http},
+/// signers::Wallet,
+/// middleware::Client,
+/// types::{Address, TransactionRequest},
+/// };
+/// use std::convert::TryFrom;
+///
+/// # async fn foo() -> Result<(), Box> {
+/// let provider = Provider::::try_from("http://localhost:8545")
+/// .expect("could not instantiate HTTP Provider");
+///
+/// // Transactions will be signed with the private key below and will be broadcast
+/// // via the eth_sendRawTransaction API)
+/// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
+/// .parse()?;
+///
+/// let mut client = Client::new(provider, wallet);
+///
+/// // since it derefs to `Provider`, we can just call any of the JSON-RPC API methods
+/// let block = client.get_block(100u64).await?;
+///
+/// // You can use the node's `eth_sign` and `eth_sendTransaction` calls by calling the
+/// // internal provider's method.
+/// let signed_msg = client.sign(b"hello".to_vec(), &client.address()).await?;
+///
+/// let tx = TransactionRequest::pay("vitalik.eth", 100);
+/// let tx_hash = client.send_transaction(tx, None).await?;
+///
+/// // You can `await` on the pending transaction to get the receipt with a pre-specified
+/// // number of confirmations
+/// let receipt = client.pending_transaction(tx_hash).confirmations(6).await?;
+///
+/// // You can connect with other wallets at runtime via the `with_signer` function
+/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
+/// .parse()?;
+///
+/// let signed_msg2 = client.with_signer(wallet2).sign(b"hello".to_vec(), &client.address()).await?;
+///
+/// // This call will be made with `wallet2` since `with_signer` takes a mutable reference.
+/// let tx2 = TransactionRequest::new()
+/// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::()?)
+/// .value(200);
+/// let tx_hash2 = client.send_transaction(tx2, None).await?;
+///
+/// # Ok(())
+/// # }
+///
+/// ```
+///
+/// [`Provider`]: ethers_providers::Provider
+pub struct Client {
+ pub(crate) inner: M,
+ pub(crate) signer: S,
+ pub(crate) address: Address,
+}
+
+use ethers_providers::FromErr;
+
+impl FromErr for ClientError {
+ fn from(src: M::Error) -> ClientError {
+ ClientError::MiddlewareError(src)
+ }
+}
+
+#[derive(Error, Debug)]
+/// Error thrown when the client interacts with the blockchain
+pub enum ClientError {
+ #[error("{0}")]
+ /// Thrown when the internal call to the signer fails
+ SignerError(S::Error),
+
+ #[error("{0}")]
+ /// Thrown when an internal middleware errors
+ MiddlewareError(M::Error),
+
+ /// Thrown if the `nonce` field is missing
+ #[error("no nonce was specified")]
+ NonceMissing,
+ /// Thrown if the `gas_price` field is missing
+ #[error("no gas price was specified")]
+ GasPriceMissing,
+ /// Thrown if the `gas` field is missing
+ #[error("no gas was specified")]
+ GasMissing,
+}
+
+// Helper functions for locally signing transactions
+impl Client
+where
+ M: Middleware,
+ S: Signer,
+{
+ /// Creates a new client from the provider and signer.
+ pub fn new(inner: M, signer: S) -> Self {
+ let address = signer.address();
+ Client {
+ inner,
+ signer,
+ address,
+ }
+ }
+
+ async fn sign_transaction(
+ &self,
+ tx: TransactionRequest,
+ ) -> Result> {
+ // The nonce, gas and gasprice fields must already be populated
+ let nonce = tx.nonce.ok_or(ClientError::NonceMissing)?;
+ let gas_price = tx.gas_price.ok_or(ClientError::GasPriceMissing)?;
+ let gas = tx.gas.ok_or(ClientError::GasMissing)?;
+
+ let signature = self
+ .signer
+ .sign_transaction(&tx)
+ .await
+ .map_err(ClientError::SignerError)?;
+
+ // Get the actual transaction hash
+ let rlp = tx.rlp_signed(&signature);
+ let hash = keccak256(&rlp.0);
+
+ // This function should not be called with ENS names
+ let to = tx.to.map(|to| match to {
+ NameOrAddress::Address(inner) => inner,
+ NameOrAddress::Name(_) => {
+ panic!("Expected `to` to be an Ethereum Address, not an ENS name")
+ }
+ });
+
+ Ok(Transaction {
+ hash: hash.into(),
+ nonce,
+ from: self.address(),
+ to,
+ value: tx.value.unwrap_or_default(),
+ gas_price,
+ gas,
+ input: tx.data.unwrap_or_default(),
+ v: signature.v.into(),
+ r: U256::from_big_endian(signature.r.as_bytes()),
+ s: U256::from_big_endian(signature.s.as_bytes()),
+
+ // Leave these empty as they're only used for included transactions
+ block_hash: None,
+ block_number: None,
+ transaction_index: None,
+
+ // Celo support
+ #[cfg(feature = "celo")]
+ fee_currency: tx.fee_currency,
+ #[cfg(feature = "celo")]
+ gateway_fee: tx.gateway_fee,
+ #[cfg(feature = "celo")]
+ gateway_fee_recipient: tx.gateway_fee_recipient,
+ })
+ }
+
+ async fn fill_transaction(
+ &self,
+ tx: &mut TransactionRequest,
+ block: Option,
+ ) -> Result<(), ClientError