From 469c0cb96b4ba42ce1d7542cfbf12d2dba7f3ce0 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Wed, 10 Jun 2020 15:21:16 +0300 Subject: [PATCH] fix(signers): make wallet non-optional --- ethers-core/src/utils/ganache.rs | 11 +-- ethers-providers/src/provider.rs | 8 ++- ethers-signers/src/client.rs | 117 ++++++++++++++----------------- ethers-signers/src/wallet.rs | 2 +- 4 files changed, 66 insertions(+), 72 deletions(-) diff --git a/ethers-core/src/utils/ganache.rs b/ethers-core/src/utils/ganache.rs index 19a67ef9..fd2a2594 100644 --- a/ethers-core/src/utils/ganache.rs +++ b/ethers-core/src/utils/ganache.rs @@ -1,4 +1,9 @@ -use std::process::{Child, Command}; +use std::{ + process::{Child, Command}, + time::Duration, +}; + +const SLEEP_TIME: Duration = Duration::from_secs(3); /// A ganache CLI instance. Will close the instance when dropped. /// @@ -74,9 +79,7 @@ impl Ganache { let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli"); // wait a couple of seconds for ganache to boot up - // TODO: Change this to poll for `port` - let sleep_time = std::time::Duration::from_secs(2); - std::thread::sleep(sleep_time); + std::thread::sleep(SLEEP_TIME); GanacheInstance(ganache_pid) } } diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index d0520415..cdb9f934 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -282,8 +282,12 @@ impl Provider

{ } /// Signs data using a specific account. This account needs to be unlocked. - pub async fn sign(&self, data: &Bytes, from: &Address) -> Result { - let data = utils::serialize(data); + pub async fn sign>( + &self, + data: T, + from: &Address, + ) -> Result { + let data = utils::serialize(&data.into()); let from = utils::serialize(from); Ok(self .0 diff --git a/ethers-signers/src/client.rs b/ethers-signers/src/client.rs index 06b45520..76f3bedf 100644 --- a/ethers-signers/src/client.rs +++ b/ethers-signers/src/client.rs @@ -12,7 +12,7 @@ use thiserror::Error; #[derive(Clone, Debug)] /// A client provides an interface for signing and broadcasting locally signed transactions /// It Derefs to [`Provider`], which allows interacting with the Ethereum JSON-RPC provider -/// via the same API. Sending transactions also supports using ENS as a receiver. If you will +/// via the same API. Sending transactions also supports using [ENS](https://ens.domains/) as a receiver. If you will /// not be using a local signer, it is recommended to use a [`Provider`] instead. /// /// # Example @@ -24,29 +24,35 @@ use thiserror::Error; /// use std::convert::TryFrom; /// /// # async fn foo() -> Result<(), Box> { -/// let mut client: Client<_, _> = Provider::::try_from("http://localhost:8545") -/// .expect("could not instantiate HTTP Provider").into(); +/// let provider = Provider::::try_from("http://localhost:8545") +/// .expect("could not instantiate HTTP Provider"); /// -/// // since it derefs to `Provider`, we can just call any of the JSON-RPC API methods -/// let block = client.get_block(100u64).await?; -/// -/// // calling `sign_message` and `send_transaction` will use the unlocked accounts -/// // on the node. -/// let signed_msg = client.sign_message(b"hello".to_vec()).await?; -/// -/// let tx = TransactionRequest::pay("vitalik.eth", 100); -/// let tx_hash = client.send_transaction(tx, None).await?; -/// -/// // if we set a signer, signing of messages and transactions will be done locally +/// // By default, signing of messages and transactions is done locally /// // (transactions will be broadcast via the eth_sendRawTransaction API) /// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc" /// .parse() /// .unwrap(); /// -/// let client = client.with_signer(wallet); +/// let mut client = Client::new(provider, wallet); /// -/// let signed_msg2 = client.sign_message(b"hello".to_vec()).await?; +/// // 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.provider().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 connect with other wallets at runtime via the `with_signer` function +/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7" +/// .parse() +/// .unwrap(); +/// +/// let signed_msg2 = client.with_signer(wallet2).sign_message(b"hello".to_vec()).await?; +/// +/// // This call will be made with `wallet2` since `with_signer` takes a mutable reference. /// let tx2 = TransactionRequest::new() /// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::

()?) /// .value(200); @@ -57,23 +63,13 @@ use thiserror::Error; /// /// ``` /// -/// [`Provider`](../ethers_providers/struct.Provider.html) +/// [`Provider`]: ../ethers_providers/struct.Provider.html pub struct Client { pub(crate) provider: Provider

, - pub(crate) signer: Option, + pub(crate) signer: S, pub(crate) address: Address, } -impl From> for Client { - fn from(provider: Provider

) -> Self { - Client { - provider, - signer: None, - address: Address::zero(), - } - } -} - #[derive(Debug, Error)] /// Error thrown when the client interacts with the blockchain pub enum ClientError { @@ -96,18 +92,25 @@ where S: Signer, P: JsonRpcClient, { + /// Creates a new client from the provider and signer. + pub fn new(provider: Provider

, signer: S) -> Self { + let address = signer.address(); + Client { + provider, + signer, + address, + } + } + /// Signs a message with the internal signer, or if none is present it will make a call to /// the connected node's `eth_call` API. pub async fn sign_message>(&self, msg: T) -> Result { - let msg = msg.into(); - Ok(if let Some(ref signer) = self.signer { - signer.sign_message(msg) - } else { - self.provider.sign(&msg, &self.address).await? - }) + Ok(self.signer.sign_message(msg.into())) } - /// Signs and broadcasts the transaction + /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that + /// gas cost and nonce calculations take it into account. For simple transactions this can be + /// left to `None`. pub async fn send_transaction( &self, mut tx: TransactionRequest, @@ -123,19 +126,11 @@ where } } - // if there is no local signer, then the transaction should use the - // node's signer which should already be unlocked - let signer = if let Some(ref signer) = self.signer { - signer - } else { - return Ok(self.provider.send_transaction(tx).await?); - }; - // fill any missing fields self.fill_transaction(&mut tx, block).await?; // sign the transaction with the network - let signed_tx = signer.sign_transaction(tx).map_err(Into::into)?; + let signed_tx = self.signer.sign_transaction(tx).map_err(Into::into)?; // broadcast it self.provider.send_raw_transaction(&signed_tx).await?; @@ -148,7 +143,10 @@ where tx: &mut TransactionRequest, block: Option, ) -> Result<(), ClientError> { - tx.from = Some(self.address()); + // set the `from` field + if tx.from.is_none() { + tx.from = Some(self.address()); + } // will poll and await the futures concurrently let (gas_price, gas, nonce) = join!( @@ -166,12 +164,9 @@ where Ok(()) } - /// Returns the client's address (or `address(0)` if no signer is set) + /// Returns the client's address pub fn address(&self) -> Address { - self.signer - .as_ref() - .map(|s| s.address()) - .unwrap_or_default() + self.signer.address() } /// Returns a reference to the client's provider @@ -180,31 +175,23 @@ where } /// Returns a reference to the client's signer - /// - /// # Panics - /// - /// If `self.signer` is `None` - pub fn signer_unchecked(&self) -> &S { - self.signer.as_ref().expect("no signer is configured") + pub fn signer(&self) -> &S { + &self.signer } - /// Sets the signer + /// Sets the signer and returns a mutable reference to self so that it can be used in chained + /// calls. pub fn with_signer(&mut self, signer: S) -> &mut Self { - self.signer = Some(signer); + self.signer = signer; self } - /// Sets the provider + /// Sets the provider and returns a mutable reference to self so that it can be used in chained + /// calls. pub fn with_provider(&mut self, provider: Provider

) -> &mut Self { self.provider = provider; self } - - /// Sets the default account to be used with the `eth_sign` API calls - pub fn from(&mut self, address: Address) -> &mut Self { - self.address = address; - self - } } /// Calls the future if `item` is None, otherwise returns a `futures::ok` diff --git a/ethers-signers/src/wallet.rs b/ethers-signers/src/wallet.rs index 03f78164..8aae0c0c 100644 --- a/ethers-signers/src/wallet.rs +++ b/ethers-signers/src/wallet.rs @@ -67,7 +67,7 @@ impl Wallet { let address = self.address(); Client { address, - signer: Some(self), + signer: self, provider, } }