Skip to content
This repository has been archived by the owner on May 11, 2023. It is now read-only.

Fix a few Ethereum & WalletConnect related issues #91

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 113 additions & 30 deletions common/src/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::convert::TryFrom;
use std::env;
use std::ffi::OsString;
use std::fmt::Debug;
use std::path::PathBuf;
use std::str::FromStr;

Expand All @@ -10,16 +11,18 @@ use coins_bip32::path::DerivationPath;
use anyhow::anyhow;
use anyhow::Context as _;
use ethers::abi::Detokenize;
use ethers::prelude::builders::ContractCall;
use ethers::prelude::*;
use ethers::types::transaction::eip712::Eip712;
use ethers::types::Chain;

use rad_terminal::args;
use rad_terminal::components as term;

pub mod signer;

mod walletconnect;

use self::signer::{ContractCall, ExtendedMiddleware, ExtendedSigner};
use self::walletconnect::WalletConnect;

/// Radicle's ENS domain.
Expand All @@ -29,6 +32,7 @@ pub const SIGNER_OPTIONS: &str = r#"
--ledger-hdpath <hdpath> Account derivation path when using a Ledger hardware device
--keystore <file> Keystore file containing encrypted private key (default: none)
--walletconnect Use WalletConnect
--legacy Send transactions in legacy mode
"#;

pub const PROVIDER_OPTIONS: &str = r#"
Expand All @@ -49,6 +53,8 @@ pub struct SignerOptions {
pub keystore: Option<PathBuf>,
/// Walletconnect account (default: false).
pub walletconnect: bool,
/// Legacy transaction (default: false).
pub legacy: bool,
}

impl SignerOptions {
Expand All @@ -62,6 +68,7 @@ impl SignerOptions {
.ok()
.and_then(|v| DerivationPath::from_str(v.as_str()).ok()),
walletconnect: false,
legacy: false,
};

while let Some(arg) = parser.next()? {
Expand All @@ -81,6 +88,9 @@ impl SignerOptions {
Long("walletconnect") => {
options.walletconnect = true;
}
Long("legacy") => {
options.legacy = true;
}
_ => unparsed.push(args::format(arg)),
}
}
Expand Down Expand Up @@ -142,7 +152,7 @@ pub enum WalletError {
#[error(transparent)]
Local(#[from] ethers::signers::WalletError),
#[error(transparent)]
WalletConnect(#[from] ::walletconnect::client::CallError),
WalletConnect(#[from] walletconnect::WalletError),
#[error("no wallet specified")]
NoWallet,
}
Expand All @@ -155,77 +165,143 @@ pub enum Wallet {
WalletConnect(WalletConnect),
}

#[derive(Debug)]
pub enum TypedWallet {
Legacy(Wallet),
Modern(Wallet),
}

impl TypedWallet {
fn wallet(&self) -> &Wallet {
match self {
Self::Legacy(wallet) => wallet,
Self::Modern(wallet) => wallet,
}
}

fn own_wallet(self) -> Wallet {
match self {
Self::Legacy(wallet) => wallet,
Self::Modern(wallet) => wallet,
}
}

fn wrapper(&self) -> fn(Wallet) -> Self {
match self {
Self::Legacy(_) => Self::Legacy,
Self::Modern(_) => Self::Modern,
}
}
}

#[async_trait::async_trait]
impl Signer for Wallet {
impl Signer for TypedWallet {
type Error = WalletError;

fn chain_id(&self) -> u64 {
match self {
Self::Ledger(s) => s.chain_id(),
Self::Local(s) => s.chain_id(),
Self::WalletConnect(s) => s.chain_id(),
match self.wallet() {
Wallet::Ledger(s) => s.chain_id(),
Wallet::Local(s) => s.chain_id(),
Wallet::WalletConnect(s) => s.chain_id(),
}
}

fn address(&self) -> Address {
match self {
Self::Ledger(s) => s.address(),
Self::Local(s) => s.address(),
Self::WalletConnect(s) => s.address(),
match self.wallet() {
Wallet::Ledger(s) => s.address(),
Wallet::Local(s) => s.address(),
Wallet::WalletConnect(s) => s.address(),
}
}

fn with_chain_id<T: Into<u64>>(self, chain_id: T) -> Self {
match self {
Self::Ledger(s) => Self::Ledger(s.with_chain_id(chain_id)),
Self::Local(s) => Self::Local(s.with_chain_id(chain_id)),
Self::WalletConnect(_s) => unimplemented!(),
let wrapper = self.wrapper();
match self.own_wallet() {
Wallet::Ledger(s) => (wrapper)(Wallet::Ledger(s.with_chain_id(chain_id))),
Wallet::Local(s) => (wrapper)(Wallet::Local(s.with_chain_id(chain_id))),
Wallet::WalletConnect(_s) => unimplemented!(),
}
}

async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error> {
match self {
Self::Ledger(s) => s.sign_typed_data(payload).await.map_err(WalletError::from),
Self::Local(s) => s.sign_typed_data(payload).await.map_err(WalletError::from),
Self::WalletConnect(_s) => unimplemented!(),
match self.wallet() {
Wallet::Ledger(s) => s.sign_typed_data(payload).await.map_err(WalletError::from),
Wallet::Local(s) => s.sign_typed_data(payload).await.map_err(WalletError::from),
Wallet::WalletConnect(_s) => unimplemented!(),
}
}

async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, Self::Error> {
match self {
Self::Ledger(s) => s.sign_message(message).await.map_err(WalletError::from),
Self::Local(s) => s.sign_message(message).await.map_err(WalletError::from),
Self::WalletConnect(s) => s.sign_message(message).await.map_err(WalletError::from),
match self.wallet() {
Wallet::Ledger(s) => s.sign_message(message).await.map_err(WalletError::from),
Wallet::Local(s) => s.sign_message(message).await.map_err(WalletError::from),
Wallet::WalletConnect(s) => s.sign_message(message).await.map_err(WalletError::from),
}
}

async fn sign_transaction(
&self,
message: &ethers::types::transaction::eip2718::TypedTransaction,
) -> Result<Signature, Self::Error> {
match self.wallet() {
Wallet::Ledger(s) => s.sign_transaction(message).await.map_err(WalletError::from),
Wallet::Local(s) => s.sign_transaction(message).await.map_err(WalletError::from),
Wallet::WalletConnect(s) => {
s.sign_transaction(message).await.map_err(WalletError::from)
}
}
}
}

#[async_trait::async_trait]
impl ExtendedSigner for TypedWallet {
async fn send_transaction(
&self,
message: &ethers::types::transaction::eip2718::TypedTransaction,
) -> Result<H256, Self::Error> {
match self.wallet() {
Wallet::Ledger(_) => unimplemented!(),
Wallet::Local(_) => unimplemented!(),
Wallet::WalletConnect(s) => {
s.send_transaction(message).await.map_err(WalletError::from)
}
}
}

fn is_walletconnect(&self) -> bool {
match self.wallet() {
Wallet::Ledger(_) => false,
Wallet::Local(_) => false,
Wallet::WalletConnect(_) => true,
}
}

fn is_legacy(&self) -> bool {
match self {
Self::Ledger(s) => s.sign_transaction(message).await.map_err(WalletError::from),
Self::Local(s) => s.sign_transaction(message).await.map_err(WalletError::from),
Self::WalletConnect(s) => s.sign_transaction(message).await.map_err(WalletError::from),
Self::Legacy(_) => true,
Self::Modern(_) => false,
}
}
}

impl Wallet {
/// Open a wallet from the given options and provider.
pub async fn open<P>(options: SignerOptions, provider: Provider<P>) -> anyhow::Result<Wallet>
pub async fn open<P>(
options: SignerOptions,
provider: Provider<P>,
) -> anyhow::Result<TypedWallet>
where
P: JsonRpcClient + Clone + 'static,
{
let chain_id = provider.get_chainid().await?.as_u64();

if let Some(keypath) = &options.keystore {
let wallet = if let Some(keypath) = &options.keystore {
let password = term::secret_input_with_prompt("Keystore password");
let spinner = term::spinner("Decrypting keystore...");
let signer = LocalWallet::decrypt_keystore(keypath, password.unsecure())
Expand All @@ -252,6 +328,12 @@ impl Wallet {
Ok(Wallet::WalletConnect(signer))
} else {
Err(WalletError::NoWallet.into())
};

if options.legacy {
wallet.map(TypedWallet::Legacy)
} else {
wallet.map(TypedWallet::Modern)
}
}
}
Expand All @@ -260,8 +342,9 @@ impl Wallet {
pub async fn transaction<M, D>(call: ContractCall<M, D>) -> anyhow::Result<TransactionReceipt>
where
D: Detokenize,
M: Middleware + 'static,
M: ExtendedMiddleware + 'static,
{
let call = call.set_tx_type();
let receipt = loop {
let spinner = term::spinner("Waiting for transaction to be signed...");
let tx = match call.send().await {
Expand Down Expand Up @@ -318,7 +401,7 @@ pub fn hex(bytes: impl AsRef<[u8]>) -> String {
pub async fn get_wallet(
signer_opts: SignerOptions,
provider: Provider<Http>,
) -> anyhow::Result<(Wallet, Provider<Http>)> {
) -> anyhow::Result<(TypedWallet, Provider<Http>)> {
use rad_terminal::args::Error;

term::tip!("Accessing your wallet...");
Expand Down
Loading