From 07daa1a8913ad4400892e985d8b5ebd534f04285 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sun, 24 May 2020 17:41:12 +0300 Subject: [PATCH] refactor --- examples/local_signer.rs | 12 +-- src/providers.rs | 2 +- src/types/block.rs | 3 +- src/types/mod.rs | 47 +----------- src/wallet.rs | 159 --------------------------------------- src/wallet/client.rs | 45 +++++++++++ src/wallet/mod.rs | 22 ++++++ src/wallet/networks.rs | 35 +++++++++ src/wallet/wallet.rs | 75 ++++++++++++++++++ 9 files changed, 186 insertions(+), 214 deletions(-) delete mode 100644 src/wallet.rs create mode 100644 src/wallet/client.rs create mode 100644 src/wallet/mod.rs create mode 100644 src/wallet/networks.rs create mode 100644 src/wallet/wallet.rs diff --git a/examples/local_signer.rs b/examples/local_signer.rs index 89dd067f..dcbc01f5 100644 --- a/examples/local_signer.rs +++ b/examples/local_signer.rs @@ -1,20 +1,21 @@ use ethers::providers::{Provider, ProviderTrait}; use ethers::types::{BlockNumber, UnsignedTransaction}; -use ethers::wallet::{Mainnet, Wallet}; +use ethers::wallet::MainnetWallet; use std::convert::TryFrom; use std::str::FromStr; #[tokio::main] async fn main() -> Result<(), failure::Error> { let provider = Provider::try_from("http://localhost:8545")?; - let signer = Wallet::::from_str( + let client = MainnetWallet::from_str( "d8ebe1e50cfea1f9961908d9df28e64bb163fee9ee48320361b2eb0a54974269", )? .connect(&provider); let nonce = provider - .get_transaction_count(signer.inner.address, Some(BlockNumber::Latest)) + .get_transaction_count(client.signer.address, None) .await?; + let tx = UnsignedTransaction { to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()), gas: 21000.into(), @@ -24,10 +25,9 @@ async fn main() -> Result<(), failure::Error> { nonce, }; - let tx = signer.send_transaction(tx).await?; + let tx = client.send_transaction(tx).await?; - dbg!(tx.hash); - let tx = provider.get_transaction(tx.hash).await?; + let tx = client.get_transaction(tx.hash).await?; println!("{}", serde_json::to_string(&tx)?); diff --git a/src/providers.rs b/src/providers.rs index 2a603667..b02a6605 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -58,7 +58,7 @@ impl ProviderTrait for Provider { block: Option, ) -> Result { let from = utils::serialize(&from); - let block = utils::serialize(&block); + let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); self.0 .request("eth_getTransactionCount", Some(&[from, block])) .await diff --git a/src/types/block.rs b/src/types/block.rs index c178fc8e..404699d4 100644 --- a/src/types/block.rs +++ b/src/types/block.rs @@ -1,4 +1,5 @@ use super::U64; +use serde::{Serialize, Serializer}; /// Block Number #[derive(Copy, Clone, Debug, PartialEq)] @@ -13,8 +14,6 @@ pub enum BlockNumber { Number(U64), } -use serde::{Serialize, Serializer}; - impl> From for BlockNumber { fn from(num: T) -> Self { BlockNumber::Number(num.into()) diff --git a/src/types/mod.rs b/src/types/mod.rs index 1ab94fe0..9fa855bd 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,6 +1,7 @@ //! Various Ethereum Related Datatypes // Re-export common ethereum datatypes with more specific names +pub use ethereum_types::H256 as TxHash; pub use ethereum_types::{Address, H256, U256, U64}; mod transaction; @@ -19,49 +20,3 @@ pub use bytes::Bytes; mod block; pub use block::BlockNumber; - -use rustc_hex::{FromHex, ToHex}; -use serde::{ - de::{Error, Unexpected}, - Deserialize, Deserializer, Serialize, Serializer, -}; - -/// Wrapper type 0round Vec to deserialize/serialize "0x" prefixed ethereum hex strings -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct TxHash( - #[serde( - serialize_with = "serialize_h256", - deserialize_with = "deserialize_h256" - )] - pub H256, -); - -impl From for TxHash { - fn from(src: H256) -> TxHash { - TxHash(src) - } -} - -pub fn serialize_h256(x: T, s: S) -> Result -where - S: Serializer, - T: AsRef<[u8]>, -{ - s.serialize_str(&format!("0x{}", x.as_ref().to_hex::())) -} - -pub fn deserialize_h256<'de, D>(d: D) -> Result -where - D: Deserializer<'de>, -{ - let value = String::deserialize(d)?; - if value.len() >= 2 && &value[0..2] == "0x" { - let slice: Vec = FromHex::from_hex(&value[2..]) - .map_err(|e| Error::custom(format!("Invalid hex: {}", e)))?; - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(&slice[..32]); - Ok(bytes.into()) - } else { - Err(Error::invalid_value(Unexpected::Str(&value), &"0x prefix")) - } -} diff --git a/src/wallet.rs b/src/wallet.rs deleted file mode 100644 index 59062a52..00000000 --- a/src/wallet.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::{ - jsonrpc::ClientError, - providers::{Provider, ProviderTrait}, - types::{Address, PrivateKey, PublicKey, Signature, U64}, - types::{Transaction, UnsignedTransaction}, -}; -use rand::Rng; -use std::{marker::PhantomData, str::FromStr}; - -use thiserror::Error; - -/// A keypair -#[derive(Clone, Debug)] -pub struct Wallet { - pub private_key: PrivateKey, - pub public_key: PublicKey, - pub address: Address, - network: PhantomData, -} - -pub trait Network { - const CHAIN_ID: Option; - - // TODO: Default providers? -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Mainnet; - -impl Network for Mainnet { - const CHAIN_ID: Option = Some(U64([1])); -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct AnyNet; - -impl Network for AnyNet { - const CHAIN_ID: Option = None; -} - -// No EIP-155 used -pub type AnyWallet = Wallet; - -impl Wallet { - /// Creates a new keypair - pub fn new(rng: &mut R) -> Self { - let private_key = PrivateKey::new(rng); - let public_key = PublicKey::from(&private_key); - let address = Address::from(&private_key); - - Self { - private_key, - public_key, - address, - network: PhantomData, - } - } - - /// Connects to a provider and returns a signer - pub fn connect<'a>(self, provider: &'a Provider) -> Signer> { - Signer { - inner: self, - provider: Some(provider), - } - } -} - -impl FromStr for Wallet { - type Err = secp256k1::Error; - - fn from_str(src: &str) -> Result { - Ok(PrivateKey::from_str(src)?.into()) - } -} - -impl From for Wallet { - fn from(private_key: PrivateKey) -> Self { - let public_key = PublicKey::from(&private_key); - let address = Address::from(&private_key); - - Self { - private_key, - public_key, - address, - network: PhantomData, - } - } -} - -#[derive(Clone, Debug)] -pub struct Signer<'a, S> { - pub provider: Option<&'a Provider>, - pub inner: S, -} - -#[derive(Error, Debug)] -pub enum SignerError { - #[error(transparent)] - ClientError(#[from] ClientError), - #[error("no provider was found")] - NoProvider, -} - -impl<'a, N: Network> Signer<'a, Wallet> { - /// Generates a random signer with no provider. Should be combined with the - /// `connect` method like `Signer::random(rng).connect(provider)` - pub fn random(rng: &mut R) -> Self { - Signer { - provider: None, - inner: Wallet::new(rng), - } - } - - pub async fn send_transaction( - &self, - tx: UnsignedTransaction, - ) -> Result { - // TODO: Is there any nicer way to do this? - let provider = self.ensure_provider()?; - - let signed_tx = self.sign_transaction(tx.clone()); - - provider.send_raw_transaction(&signed_tx.rlp()).await?; - - Ok(signed_tx) - } -} - -impl<'a, S> Signer<'a, S> { - /// Sets the provider for the signer - pub fn connect(mut self, provider: &'a Provider) -> Self { - self.provider = Some(provider); - self - } - - pub fn ensure_provider(&self) -> Result<&Provider, SignerError> { - if let Some(provider) = self.provider { - Ok(provider) - } else { - Err(SignerError::NoProvider) - } - } -} - -trait SignerC { - /// Signs the hash of the provided message after prefixing it - fn sign_message>(&self, message: S) -> Signature; - fn sign_transaction(&self, message: UnsignedTransaction) -> Transaction; -} - -impl<'a, N: Network> SignerC for Signer<'a, Wallet> { - fn sign_message>(&self, message: S) -> Signature { - self.inner.private_key.sign(message) - } - - fn sign_transaction(&self, tx: UnsignedTransaction) -> Transaction { - self.inner.private_key.sign_transaction(tx, N::CHAIN_ID) - } -} diff --git a/src/wallet/client.rs b/src/wallet/client.rs new file mode 100644 index 00000000..bce6bb85 --- /dev/null +++ b/src/wallet/client.rs @@ -0,0 +1,45 @@ +use crate::{ + jsonrpc::ClientError, + providers::{Provider, ProviderTrait}, + types::{Transaction, TxHash, UnsignedTransaction}, + wallet::Signer, +}; + +use thiserror::Error; + +#[derive(Clone, Debug)] +pub struct Client<'a, S> { + pub(super) provider: &'a Provider, + pub signer: S, +} + +#[derive(Error, Debug)] +pub enum SignerError { + #[error(transparent)] + ClientError(#[from] ClientError), + #[error("no provider was found")] + NoProvider, +} + +impl<'a, S: Signer> Client<'a, S> { + pub async fn send_transaction( + &self, + tx: UnsignedTransaction, + ) -> Result { + // sign the transaction + let signed_tx = self.signer.sign_transaction(tx.clone()); + + // broadcast it + self.provider.send_raw_transaction(&signed_tx.rlp()).await?; + + Ok(signed_tx) + } + + // TODO: Forward all other calls to the provider + pub async fn get_transaction>( + &self, + hash: T, + ) -> Result { + self.provider.get_transaction(hash).await + } +} diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs new file mode 100644 index 00000000..0c42c4f4 --- /dev/null +++ b/src/wallet/mod.rs @@ -0,0 +1,22 @@ +mod networks; +pub use networks::instantiated::*; +use networks::Network; + +mod wallet; +pub use wallet::Wallet; + +mod client; +use client::Client; + +use crate::types::{Signature, Transaction, UnsignedTransaction}; + +/// Trait for signing transactions and messages +/// +/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc. +pub trait Signer { + /// Signs the hash of the provided message after prefixing it + fn sign_message>(&self, message: S) -> Signature; + + /// Signs the transaction + fn sign_transaction(&self, message: UnsignedTransaction) -> Transaction; +} diff --git a/src/wallet/networks.rs b/src/wallet/networks.rs new file mode 100644 index 00000000..3c655137 --- /dev/null +++ b/src/wallet/networks.rs @@ -0,0 +1,35 @@ +//! Networks are used inside wallets to ensure type-safety across networks. That way +//! a transaction that is designed to work with testnet does not accidentally work +//! with mainnet because the URL was changed. + +use crate::types::U64; + +pub trait Network { + const CHAIN_ID: Option; + + // TODO: Default providers? e.g. `mainnet.infura.io/XXX`? +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Mainnet; + +impl Network for Mainnet { + const CHAIN_ID: Option = Some(U64([1])); +} + +/// No EIP155 +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct EIP155Disabled; + +// EIP155 being disabled means no chainId will be used +impl Network for EIP155Disabled { + const CHAIN_ID: Option = None; +} + +pub mod instantiated { + use super::*; + use crate::wallet::Wallet; + + pub type MainnetWallet = Wallet; + pub type AnyWallet = Wallet; +} diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs new file mode 100644 index 00000000..4c4ea456 --- /dev/null +++ b/src/wallet/wallet.rs @@ -0,0 +1,75 @@ +use crate::{ + providers::Provider, + types::{Address, PrivateKey, PublicKey, Signature, Transaction, UnsignedTransaction}, + wallet::{Client, Network, Signer}, +}; + +use rand::Rng; +use std::{marker::PhantomData, str::FromStr}; + +/// A keypair +#[derive(Clone, Debug)] +pub struct Wallet { + pub private_key: PrivateKey, + pub public_key: PublicKey, + pub address: Address, + network: PhantomData, +} + +impl<'a, N: Network> Signer for Wallet { + fn sign_message>(&self, message: S) -> Signature { + self.private_key.sign(message) + } + + fn sign_transaction(&self, tx: UnsignedTransaction) -> Transaction { + self.private_key.sign_transaction(tx, N::CHAIN_ID) + } +} + +impl Wallet { + // TODO: Add support for mnemonic and encrypted JSON + + /// Creates a new random keypair seeded with the provided RNG + pub fn new(rng: &mut R) -> Self { + let private_key = PrivateKey::new(rng); + let public_key = PublicKey::from(&private_key); + let address = Address::from(&private_key); + + Self { + private_key, + public_key, + address, + network: PhantomData, + } + } + + /// Connects to a provider and returns a signer + pub fn connect(self, provider: &Provider) -> Client> { + Client { + signer: self, + provider, + } + } +} + +impl From for Wallet { + fn from(private_key: PrivateKey) -> Self { + let public_key = PublicKey::from(&private_key); + let address = Address::from(&private_key); + + Self { + private_key, + public_key, + address, + network: PhantomData, + } + } +} + +impl FromStr for Wallet { + type Err = secp256k1::Error; + + fn from_str(src: &str) -> Result { + Ok(PrivateKey::from_str(src)?.into()) + } +}