diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index 77436aa7..5a9cd711 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -18,6 +18,9 @@ thiserror = { version = "1.0.19", default-features = false }
once_cell = "1.4.0"
tokio = { version = "0.2.21", default-features = false }
+[dev-dependencies]
+tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
+
[features]
default = ["abigen"]
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index 86c36267..ec8eac15 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -2,22 +2,43 @@ use ethers_core::{
abi::{Detokenize, Error as AbiError, Function, InvalidOutputType},
types::{Address, BlockNumber, TransactionRequest, H256, U256},
};
-use ethers_providers::{networks::Network, JsonRpcClient};
-use ethers_signers::{Client, Signer};
+use ethers_providers::{JsonRpcClient, ProviderError};
+use ethers_signers::{Client, ClientError, Signer};
use std::{fmt::Debug, marker::PhantomData};
use thiserror::Error as ThisError;
-pub struct ContractCall<'a, P, N, S, D> {
+#[derive(ThisError, Debug)]
+pub enum ContractError {
+ #[error(transparent)]
+ DecodingError(#[from] AbiError),
+
+ #[error(transparent)]
+ DetokenizationError(#[from] InvalidOutputType),
+
+ #[error(transparent)]
+ ClientError(#[from] ClientError),
+
+ #[error(transparent)]
+ ProviderError(#[from] ProviderError),
+
+ #[error("constructor is not defined in the ABI")]
+ ConstructorError,
+
+ #[error("Contract was not deployed")]
+ ContractNotDeployed,
+}
+
+pub struct ContractCall<'a, P, S, D> {
pub(crate) tx: TransactionRequest,
pub(crate) function: Function,
- pub(crate) client: &'a Client<'a, P, N, S>,
+ pub(crate) client: &'a Client
,
pub(crate) block: Option,
pub(crate) datatype: PhantomData,
}
-impl<'a, P, N, S, D: Detokenize> ContractCall<'a, S, P, N, D> {
+impl<'a, P, S, D: Detokenize> ContractCall<'a, P, S, D> {
/// 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());
@@ -41,32 +62,18 @@ impl<'a, P, N, S, D: Detokenize> ContractCall<'a, S, P, N, D> {
self.tx.value = Some(value.into());
self
}
+
+ /// Sets the `block` field for sending the tx to the chain
+ pub fn block>(mut self, block: T) -> Self {
+ self.block = Some(block.into());
+ self
+ }
}
-#[derive(ThisError, Debug)]
-// TODO: Can we get rid of this static?
-pub enum ContractError
-where
- P::Error: 'static,
-{
- #[error(transparent)]
- DecodingError(#[from] AbiError),
- #[error(transparent)]
- DetokenizationError(#[from] InvalidOutputType),
- #[error(transparent)]
- CallError(P::Error),
- #[error("constructor is not defined in the ABI")]
- ConstructorError,
- #[error("Contract was not deployed")]
- ContractNotDeployed,
-}
-
-impl<'a, P, N, S, D> ContractCall<'a, P, N, S, D>
+impl<'a, P, S, D> ContractCall<'a, P, S, D>
where
S: Signer,
P: JsonRpcClient,
- P::Error: 'static,
- N: Network,
D: Detokenize,
{
/// Queries the blockchain via an `eth_call` for the provided transaction.
@@ -78,12 +85,8 @@ 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
- .map_err(ContractError::CallError)?;
+ pub async fn call(self) -> Result {
+ let bytes = self.client.call(self.tx, self.block).await?;
let tokens = self.function.decode_output(&bytes.0)?;
@@ -93,7 +96,7 @@ where
}
/// Signs and broadcasts the provided transaction
- pub async fn send(self) -> Result {
- self.client.send_transaction(self.tx, self.block).await
+ pub async fn send(self) -> Result {
+ Ok(self.client.send_transaction(self.tx, self.block).await?)
}
}
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index 44467b94..42b8ac44 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -4,7 +4,7 @@ use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest},
};
-use ethers_providers::{networks::Network, JsonRpcClient};
+use ethers_providers::JsonRpcClient;
use ethers_signers::{Client, Signer};
use rustc_hex::ToHex;
@@ -15,8 +15,8 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
// TODO: Should we separate the lifetimes for the two references?
// https://stackoverflow.com/a/29862184
#[derive(Debug, Clone)]
-pub struct Contract<'a, P, N, S> {
- client: &'a Client<'a, P, N, S>,
+pub struct Contract<'a, P, S> {
+ client: &'a Client,
abi: &'a Abi,
address: Address,
@@ -27,9 +27,13 @@ pub struct Contract<'a, P, N, S> {
methods: HashMap,
}
-impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> {
+impl<'a, P, S> Contract<'a, P, S>
+where
+ S: Signer,
+ P: JsonRpcClient,
+{
/// Creates a new contract from the provided client, abi and address
- pub fn new(client: &'a Client<'a, P, N, S>, abi: &'a Abi, address: Address) -> Self {
+ pub fn new(address: Address, abi: &'a Abi, client: &'a Client) -> Self {
let methods = create_mapping(&abi.functions, |function| function.selector());
Self {
@@ -43,7 +47,7 @@ impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> {
/// Returns an `Event` builder for the provided event name. If there are
/// multiple functions with the same name due to overloading, consider using
/// the `method_hash` method instead, since this will use the first match.
- pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result, Error>
+ pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result, Error>
where
'a: 'b,
{
@@ -64,7 +68,7 @@ impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> {
&self,
name: &str,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
// get the function
let function = self.abi.function(name)?;
self.method_func(function, args)
@@ -76,7 +80,7 @@ impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> {
&self,
signature: Selector,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
let function = self
.methods
.get(&signature)
@@ -89,7 +93,7 @@ impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> {
&self,
function: &Function,
args: T,
- ) -> Result, Error> {
+ ) -> Result, Error> {
// create the calldata
let data = function.encode_input(&args.into_tokens())?;
diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs
index f59caaf9..cbbed4fa 100644
--- a/ethers-contract/src/event.rs
+++ b/ethers-contract/src/event.rs
@@ -1,6 +1,6 @@
use crate::ContractError;
-use ethers_providers::{networks::Network, JsonRpcClient, Provider};
+use ethers_providers::{JsonRpcClient, Provider};
use ethers_core::{
abi::{Detokenize, Event as AbiEvent, RawLog},
@@ -9,15 +9,15 @@ use ethers_core::{
use std::{collections::HashMap, marker::PhantomData};
-pub struct Event<'a, 'b, P, N, D> {
+pub struct Event<'a, 'b, P, D> {
pub filter: Filter,
- pub(crate) provider: &'a Provider,
+ pub(crate) provider: &'a Provider
,
pub(crate) event: &'b AbiEvent,
pub(crate) datatype: PhantomData,
}
// TODO: Improve these functions
-impl<'a, 'b, P, N, D: Detokenize> Event<'a, 'b, P, N, D> {
+impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> {
#[allow(clippy::wrong_self_convention)]
pub fn from_block>(mut self, block: T) -> Self {
self.filter.from_block = Some(block.into());
@@ -41,26 +41,22 @@ impl<'a, 'b, P, N, D: Detokenize> Event<'a, 'b, P, N, D> {
}
}
-// TODO: Can we get rid of the static?
-impl<'a, 'b, P: JsonRpcClient, N: Network, D: Detokenize + Clone> Event<'a, 'b, P, N, D>
+impl<'a, 'b, P, D> Event<'a, 'b, P, D>
where
- P::Error: 'static,
+ P: JsonRpcClient,
+ 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> {
+ pub async fn query(self) -> Result, ContractError> {
Ok(self.query_with_hashes().await?.values().cloned().collect())
}
/// Queries the blockchain for the selected filter and returns a vector of matching
/// event logs
- pub async fn query_with_hashes(self) -> Result, ContractError> {
+ pub async fn query_with_hashes(self) -> Result, ContractError> {
// get the logs
- let logs = self
- .provider
- .get_logs(&self.filter)
- .await
- .map_err(ContractError::CallError)?;
+ let logs = self.provider.get_logs(&self.filter).await?;
let events = logs
.into_iter()
@@ -79,7 +75,7 @@ where
.collect::>();
// convert the tokens to the requested datatype
- Ok::<_, ContractError>((
+ Ok::<_, ContractError>((
log.transaction_hash.expect("should have tx hash"),
D::from_tokens(tokens)?,
))
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index 1462cda5..ed2cfa5b 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -4,7 +4,7 @@ use ethers_core::{
abi::{Abi, Tokenize},
types::{Bytes, TransactionRequest},
};
-use ethers_providers::{networks::Network, JsonRpcClient};
+use ethers_providers::JsonRpcClient;
use ethers_signers::{Client, Signer};
use std::time::Duration;
@@ -15,20 +15,18 @@ use tokio::time;
const POLL_INTERVAL: u64 = 7000;
#[derive(Debug, Clone)]
-pub struct Deployer<'a, P, N, S> {
- client: &'a Client<'a, P, N, S>,
+pub struct Deployer<'a, P, S> {
+ client: &'a Client
,
abi: &'a Abi,
tx: TransactionRequest,
confs: usize,
poll_interval: Duration,
}
-impl<'a, P, N, S> Deployer<'a, P, N, S>
+impl<'a, P, S> Deployer<'a, P, S>
where
S: Signer,
P: JsonRpcClient,
- P::Error: 'static,
- N: Network,
{
pub fn poll_interval>(mut self, interval: T) -> Self {
self.poll_interval = interval.into();
@@ -40,12 +38,8 @@ where
self
}
- pub async fn send(self) -> Result, ContractError> {
- let tx_hash = self
- .client
- .send_transaction(self.tx, None)
- .await
- .map_err(ContractError::CallError)?;
+ pub async fn send(self) -> Result, ContractError> {
+ let tx_hash = self.client.send_transaction(self.tx, None).await?;
// poll for the receipt
let address;
@@ -60,27 +54,25 @@ where
time::delay_for(Duration::from_millis(POLL_INTERVAL)).await;
}
- let contract = Contract::new(self.client, self.abi, address);
+ let contract = Contract::new(address, self.abi, self.client);
Ok(contract)
}
}
#[derive(Debug, Clone)]
-pub struct ContractFactory<'a, P, N, S> {
- client: &'a Client<'a, P, N, S>,
+pub struct ContractFactory<'a, P, S> {
+ client: &'a Client,
abi: &'a Abi,
bytecode: &'a Bytes,
}
-impl<'a, P, N, S> ContractFactory<'a, P, N, S>
+impl<'a, P, S> ContractFactory<'a, P, S>
where
S: Signer,
P: JsonRpcClient,
- P::Error: 'static,
- N: Network,
{
/// Instantiate a new contract factory
- pub fn new(client: &'a Client<'a, P, N, S>, abi: &'a Abi, bytecode: &'a Bytes) -> Self {
+ pub fn new(client: &'a Client
, abi: &'a Abi, bytecode: &'a Bytes) -> Self {
Self {
client,
abi,
@@ -93,7 +85,7 @@ where
pub fn deploy(
&self,
constructor_args: T,
- ) -> Result, ContractError> {
+ ) -> 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()) {
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
new file mode 100644
index 00000000..8eefba4e
--- /dev/null
+++ b/ethers-contract/tests/contract.rs
@@ -0,0 +1,78 @@
+use ethers_contract::{Contract, ContractFactory};
+use ethers_core::{
+ types::H256,
+ utils::{GanacheBuilder, Solc},
+};
+use ethers_providers::{Http, Provider};
+use ethers_signers::Wallet;
+use std::convert::TryFrom;
+
+#[tokio::test]
+async fn deploy_and_call_contract() {
+ // 1. compile the contract
+ let compiled = Solc::new("./tests/contract.sol").build().unwrap();
+ let contract = compiled
+ .get("SimpleStorage")
+ .expect("could not find contract");
+
+ // 2. launch ganache
+ let port = 8546u64;
+ let url = format!("http://localhost:{}", port).to_string();
+ let _ganache = GanacheBuilder::new().port(port)
+ .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
+ .spawn();
+
+ // 3. instantiate our wallet
+ let wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
+ .parse::()
+ .unwrap();
+
+ // 4. connect to the network
+ let provider = Provider::::try_from(url.as_str()).unwrap();
+
+ // 5. instantiate the client with the wallet
+ let client = wallet.connect(provider);
+
+ // 6. create a factory which will be used to deploy instances of the contract
+ let factory = ContractFactory::new(&client, &contract.abi, &contract.bytecode);
+
+ // 7. deploy it with the constructor arguments
+ let contract = factory
+ .deploy("initial value".to_string())
+ .unwrap()
+ .send()
+ .await
+ .unwrap();
+
+ // 8. get the contract's address
+ let addr = contract.address();
+
+ // 9. instantiate the contract
+ let contract = Contract::new(*addr, contract.abi(), &client);
+
+ // 10. the initial value must be the one set in the constructor
+ let value: String = contract
+ .method("getValue", ())
+ .unwrap()
+ .call()
+ .await
+ .unwrap();
+ assert_eq!(value, "initial value");
+
+ // 11. call the `setValue` method (ugly API here)
+ let _tx_hash = contract
+ .method::<_, H256>("setValue", "hi".to_owned())
+ .unwrap()
+ .send()
+ .await
+ .unwrap();
+
+ // 12. get the new value
+ let value: String = contract
+ .method("getValue", ())
+ .unwrap()
+ .call()
+ .await
+ .unwrap();
+ assert_eq!(value, "hi");
+}
diff --git a/ethers-contract/tests/contract.sol b/ethers-contract/tests/contract.sol
new file mode 100644
index 00000000..9d04f2f4
--- /dev/null
+++ b/ethers-contract/tests/contract.sol
@@ -0,0 +1,22 @@
+pragma solidity >=0.4.24;
+
+contract SimpleStorage {
+
+ event ValueChanged(address indexed author, string oldValue, string newValue);
+
+ string _value;
+
+ constructor(string memory value) public {
+ emit ValueChanged(msg.sender, _value, value);
+ _value = value;
+ }
+
+ function getValue() view public returns (string memory) {
+ return _value;
+ }
+
+ function setValue(string memory value) public {
+ emit ValueChanged(msg.sender, _value, value);
+ _value = value;
+ }
+}
diff --git a/ethers/src/lib.rs b/ethers/src/lib.rs
index 5721e134..25eec3a3 100644
--- a/ethers/src/lib.rs
+++ b/ethers/src/lib.rs
@@ -187,10 +187,6 @@ pub mod core {
#[cfg(feature = "core")]
pub use ethers_core::utils;
-// Re-export ethers_providers::networks
-#[cfg(feature = "providers")]
-pub use ethers_providers::networks;
-
/// Easy import of frequently used type definitions and traits
pub mod prelude {
#[cfg(feature = "contract")]