This commit is contained in:
Georgios Konstantopoulos 2020-05-24 17:41:12 +03:00
parent 913b917620
commit 07daa1a891
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
9 changed files with 186 additions and 214 deletions

View File

@ -1,20 +1,21 @@
use ethers::providers::{Provider, ProviderTrait}; use ethers::providers::{Provider, ProviderTrait};
use ethers::types::{BlockNumber, UnsignedTransaction}; use ethers::types::{BlockNumber, UnsignedTransaction};
use ethers::wallet::{Mainnet, Wallet}; use ethers::wallet::MainnetWallet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::str::FromStr; use std::str::FromStr;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), failure::Error> { async fn main() -> Result<(), failure::Error> {
let provider = Provider::try_from("http://localhost:8545")?; let provider = Provider::try_from("http://localhost:8545")?;
let signer = Wallet::<Mainnet>::from_str( let client = MainnetWallet::from_str(
"d8ebe1e50cfea1f9961908d9df28e64bb163fee9ee48320361b2eb0a54974269", "d8ebe1e50cfea1f9961908d9df28e64bb163fee9ee48320361b2eb0a54974269",
)? )?
.connect(&provider); .connect(&provider);
let nonce = provider let nonce = provider
.get_transaction_count(signer.inner.address, Some(BlockNumber::Latest)) .get_transaction_count(client.signer.address, None)
.await?; .await?;
let tx = UnsignedTransaction { let tx = UnsignedTransaction {
to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()), to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()),
gas: 21000.into(), gas: 21000.into(),
@ -24,10 +25,9 @@ async fn main() -> Result<(), failure::Error> {
nonce, nonce,
}; };
let tx = signer.send_transaction(tx).await?; let tx = client.send_transaction(tx).await?;
dbg!(tx.hash); let tx = client.get_transaction(tx.hash).await?;
let tx = provider.get_transaction(tx.hash).await?;
println!("{}", serde_json::to_string(&tx)?); println!("{}", serde_json::to_string(&tx)?);

View File

@ -58,7 +58,7 @@ impl ProviderTrait for Provider {
block: Option<BlockNumber>, block: Option<BlockNumber>,
) -> Result<U256, Self::Error> { ) -> Result<U256, Self::Error> {
let from = utils::serialize(&from); let from = utils::serialize(&from);
let block = utils::serialize(&block); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
self.0 self.0
.request("eth_getTransactionCount", Some(&[from, block])) .request("eth_getTransactionCount", Some(&[from, block]))
.await .await

View File

@ -1,4 +1,5 @@
use super::U64; use super::U64;
use serde::{Serialize, Serializer};
/// Block Number /// Block Number
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
@ -13,8 +14,6 @@ pub enum BlockNumber {
Number(U64), Number(U64),
} }
use serde::{Serialize, Serializer};
impl<T: Into<U64>> From<T> for BlockNumber { impl<T: Into<U64>> From<T> for BlockNumber {
fn from(num: T) -> Self { fn from(num: T) -> Self {
BlockNumber::Number(num.into()) BlockNumber::Number(num.into())

View File

@ -1,6 +1,7 @@
//! Various Ethereum Related Datatypes //! Various Ethereum Related Datatypes
// Re-export common ethereum datatypes with more specific names // Re-export common ethereum datatypes with more specific names
pub use ethereum_types::H256 as TxHash;
pub use ethereum_types::{Address, H256, U256, U64}; pub use ethereum_types::{Address, H256, U256, U64};
mod transaction; mod transaction;
@ -19,49 +20,3 @@ pub use bytes::Bytes;
mod block; mod block;
pub use block::BlockNumber; pub use block::BlockNumber;
use rustc_hex::{FromHex, ToHex};
use serde::{
de::{Error, Unexpected},
Deserialize, Deserializer, Serialize, Serializer,
};
/// Wrapper type 0round Vec<u8> 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<H256> for TxHash {
fn from(src: H256) -> TxHash {
TxHash(src)
}
}
pub fn serialize_h256<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
s.serialize_str(&format!("0x{}", x.as_ref().to_hex::<String>()))
}
pub fn deserialize_h256<'de, D>(d: D) -> Result<H256, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(d)?;
if value.len() >= 2 && &value[0..2] == "0x" {
let slice: Vec<u8> = 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"))
}
}

View File

@ -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<N> {
pub private_key: PrivateKey,
pub public_key: PublicKey,
pub address: Address,
network: PhantomData<N>,
}
pub trait Network {
const CHAIN_ID: Option<U64>;
// TODO: Default providers?
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Mainnet;
impl Network for Mainnet {
const CHAIN_ID: Option<U64> = Some(U64([1]));
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct AnyNet;
impl Network for AnyNet {
const CHAIN_ID: Option<U64> = None;
}
// No EIP-155 used
pub type AnyWallet = Wallet<AnyNet>;
impl<N: Network> Wallet<N> {
/// Creates a new keypair
pub fn new<R: Rng>(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<Wallet<N>> {
Signer {
inner: self,
provider: Some(provider),
}
}
}
impl<N: Network> FromStr for Wallet<N> {
type Err = secp256k1::Error;
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(PrivateKey::from_str(src)?.into())
}
}
impl<N: Network> From<PrivateKey> for Wallet<N> {
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<N>> {
/// Generates a random signer with no provider. Should be combined with the
/// `connect` method like `Signer::random(rng).connect(provider)`
pub fn random<R: Rng>(rng: &mut R) -> Self {
Signer {
provider: None,
inner: Wallet::new(rng),
}
}
pub async fn send_transaction(
&self,
tx: UnsignedTransaction,
) -> Result<Transaction, SignerError> {
// 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<S: AsRef<[u8]>>(&self, message: S) -> Signature;
fn sign_transaction(&self, message: UnsignedTransaction) -> Transaction;
}
impl<'a, N: Network> SignerC for Signer<'a, Wallet<N>> {
fn sign_message<S: AsRef<[u8]>>(&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)
}
}

45
src/wallet/client.rs Normal file
View File

@ -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<Transaction, SignerError> {
// 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<T: Send + Sync + Into<TxHash>>(
&self,
hash: T,
) -> Result<Transaction, ClientError> {
self.provider.get_transaction(hash).await
}
}

22
src/wallet/mod.rs Normal file
View File

@ -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<S: AsRef<[u8]>>(&self, message: S) -> Signature;
/// Signs the transaction
fn sign_transaction(&self, message: UnsignedTransaction) -> Transaction;
}

35
src/wallet/networks.rs Normal file
View File

@ -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<U64>;
// 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<U64> = 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<U64> = None;
}
pub mod instantiated {
use super::*;
use crate::wallet::Wallet;
pub type MainnetWallet = Wallet<Mainnet>;
pub type AnyWallet = Wallet<EIP155Disabled>;
}

75
src/wallet/wallet.rs Normal file
View File

@ -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<N> {
pub private_key: PrivateKey,
pub public_key: PublicKey,
pub address: Address,
network: PhantomData<N>,
}
impl<'a, N: Network> Signer for Wallet<N> {
fn sign_message<S: AsRef<[u8]>>(&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<N: Network> Wallet<N> {
// TODO: Add support for mnemonic and encrypted JSON
/// Creates a new random keypair seeded with the provided RNG
pub fn new<R: Rng>(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<Wallet<N>> {
Client {
signer: self,
provider,
}
}
}
impl<N: Network> From<PrivateKey> for Wallet<N> {
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<N: Network> FromStr for Wallet<N> {
type Err = secp256k1::Error;
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(PrivateKey::from_str(src)?.into())
}
}