fix(signers): make wallet non-optional

This commit is contained in:
Georgios Konstantopoulos 2020-06-10 15:21:16 +03:00
parent 6156c4bf90
commit 469c0cb96b
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
4 changed files with 66 additions and 72 deletions

View File

@ -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. /// 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"); let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli");
// wait a couple of seconds for ganache to boot up // wait a couple of seconds for ganache to boot up
// TODO: Change this to poll for `port` std::thread::sleep(SLEEP_TIME);
let sleep_time = std::time::Duration::from_secs(2);
std::thread::sleep(sleep_time);
GanacheInstance(ganache_pid) GanacheInstance(ganache_pid)
} }
} }

View File

@ -282,8 +282,12 @@ impl<P: JsonRpcClient> Provider<P> {
} }
/// Signs data using a specific account. This account needs to be unlocked. /// Signs data using a specific account. This account needs to be unlocked.
pub async fn sign(&self, data: &Bytes, from: &Address) -> Result<Signature, ProviderError> { pub async fn sign<T: Into<Bytes>>(
let data = utils::serialize(data); &self,
data: T,
from: &Address,
) -> Result<Signature, ProviderError> {
let data = utils::serialize(&data.into());
let from = utils::serialize(from); let from = utils::serialize(from);
Ok(self Ok(self
.0 .0

View File

@ -12,7 +12,7 @@ use thiserror::Error;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// A client provides an interface for signing and broadcasting locally signed transactions /// 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 /// 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. /// not be using a local signer, it is recommended to use a [`Provider`] instead.
/// ///
/// # Example /// # Example
@ -24,29 +24,35 @@ use thiserror::Error;
/// use std::convert::TryFrom; /// use std::convert::TryFrom;
/// ///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> { /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let mut client: Client<_, _> = Provider::<Http>::try_from("http://localhost:8545") /// let provider = Provider::<Http>::try_from("http://localhost:8545")
/// .expect("could not instantiate HTTP Provider").into(); /// .expect("could not instantiate HTTP Provider");
/// ///
/// // since it derefs to `Provider`, we can just call any of the JSON-RPC API methods /// // By default, signing of messages and transactions is done locally
/// 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
/// // (transactions will be broadcast via the eth_sendRawTransaction API) /// // (transactions will be broadcast via the eth_sendRawTransaction API)
/// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc" /// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
/// .parse() /// .parse()
/// .unwrap(); /// .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() /// let tx2 = TransactionRequest::new()
/// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::<Address>()?) /// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::<Address>()?)
/// .value(200); /// .value(200);
@ -57,23 +63,13 @@ use thiserror::Error;
/// ///
/// ``` /// ```
/// ///
/// [`Provider`](../ethers_providers/struct.Provider.html) /// [`Provider`]: ../ethers_providers/struct.Provider.html
pub struct Client<P, S> { pub struct Client<P, S> {
pub(crate) provider: Provider<P>, pub(crate) provider: Provider<P>,
pub(crate) signer: Option<S>, pub(crate) signer: S,
pub(crate) address: Address, pub(crate) address: Address,
} }
impl<P, S> From<Provider<P>> for Client<P, S> {
fn from(provider: Provider<P>) -> Self {
Client {
provider,
signer: None,
address: Address::zero(),
}
}
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
/// Error thrown when the client interacts with the blockchain /// Error thrown when the client interacts with the blockchain
pub enum ClientError { pub enum ClientError {
@ -96,18 +92,25 @@ where
S: Signer, S: Signer,
P: JsonRpcClient, P: JsonRpcClient,
{ {
/// Creates a new client from the provider and signer.
pub fn new(provider: Provider<P>, 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 /// 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. /// the connected node's `eth_call` API.
pub async fn sign_message<T: Into<Bytes>>(&self, msg: T) -> Result<Signature, ClientError> { pub async fn sign_message<T: Into<Bytes>>(&self, msg: T) -> Result<Signature, ClientError> {
let msg = msg.into(); Ok(self.signer.sign_message(msg.into()))
Ok(if let Some(ref signer) = self.signer {
signer.sign_message(msg)
} else {
self.provider.sign(&msg, &self.address).await?
})
} }
/// 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( pub async fn send_transaction(
&self, &self,
mut tx: TransactionRequest, 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 // fill any missing fields
self.fill_transaction(&mut tx, block).await?; self.fill_transaction(&mut tx, block).await?;
// sign the transaction with the network // 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 // broadcast it
self.provider.send_raw_transaction(&signed_tx).await?; self.provider.send_raw_transaction(&signed_tx).await?;
@ -148,7 +143,10 @@ where
tx: &mut TransactionRequest, tx: &mut TransactionRequest,
block: Option<BlockNumber>, block: Option<BlockNumber>,
) -> Result<(), ClientError> { ) -> 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 // will poll and await the futures concurrently
let (gas_price, gas, nonce) = join!( let (gas_price, gas, nonce) = join!(
@ -166,12 +164,9 @@ where
Ok(()) Ok(())
} }
/// Returns the client's address (or `address(0)` if no signer is set) /// Returns the client's address
pub fn address(&self) -> Address { pub fn address(&self) -> Address {
self.signer self.signer.address()
.as_ref()
.map(|s| s.address())
.unwrap_or_default()
} }
/// Returns a reference to the client's provider /// Returns a reference to the client's provider
@ -180,31 +175,23 @@ where
} }
/// Returns a reference to the client's signer /// Returns a reference to the client's signer
/// pub fn signer(&self) -> &S {
/// # Panics &self.signer
///
/// If `self.signer` is `None`
pub fn signer_unchecked(&self) -> &S {
self.signer.as_ref().expect("no signer is configured")
} }
/// 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 { pub fn with_signer(&mut self, signer: S) -> &mut Self {
self.signer = Some(signer); self.signer = signer;
self 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<P>) -> &mut Self { pub fn with_provider(&mut self, provider: Provider<P>) -> &mut Self {
self.provider = provider; self.provider = provider;
self 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` /// Calls the future if `item` is None, otherwise returns a `futures::ok`

View File

@ -67,7 +67,7 @@ impl Wallet {
let address = self.address(); let address = self.address();
Client { Client {
address, address,
signer: Some(self), signer: self,
provider, provider,
} }
} }