Skip to content

Commit

Permalink
feat(cast): allow sending using just function name (gakonst#390)
Browse files Browse the repository at this point in the history
* abi detection when sending

* fix doctests
  • Loading branch information
ncitron authored Jan 6, 2022
1 parent f3a49ee commit 7740023
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 30 additions & 8 deletions cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use eyre::{Context, Result};
use rustc_hex::{FromHexIter, ToHex};
use std::str::FromStr;

use foundry_utils::{encode_args, get_func, to_table};
use foundry_utils::{encode_args, get_func, get_func_etherscan, to_table};

// TODO: CastContract with common contract initializers? Same for CastProviders?

Expand Down Expand Up @@ -115,7 +115,7 @@ where
///
/// ```no_run
/// use cast::Cast;
/// use ethers_core::types::Address;
/// use ethers_core::types::{Address, Chain};
/// use ethers_providers::{Provider, Http};
/// use std::{str::FromStr, convert::TryFrom};
///
Expand All @@ -126,7 +126,7 @@ where
/// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
/// let sig = "greet(string)()";
/// let args = vec!["hello".to_owned()];
/// let data = cast.send(from, to, Some((sig, args))).await?;
/// let data = cast.send(from, to, Some((sig, args)), Chain::Mainnet, None).await?;
/// println!("{}", *data);
/// # Ok(())
/// # }
Expand All @@ -136,8 +136,10 @@ where
from: F,
to: T,
args: Option<(&str, Vec<String>)>,
chain: Chain,
etherscan_api_key: Option<String>,
) -> Result<PendingTransaction<'_, M::Provider>> {
let tx = self.build_tx(from, to, args).await?;
let tx = self.build_tx(from, to, args, chain, etherscan_api_key).await?;
let res = self.provider.send_transaction(tx, None).await?;

Ok::<_, eyre::Error>(res)
Expand All @@ -147,7 +149,7 @@ where
///
/// ```no_run
/// use cast::Cast;
/// use ethers_core::types::Address;
/// use ethers_core::types::{Address, Chain};
/// use ethers_providers::{Provider, Http};
/// use std::{str::FromStr, convert::TryFrom};
///
Expand All @@ -158,7 +160,7 @@ where
/// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
/// let sig = "greet(string)()";
/// let args = vec!["5".to_owned()];
/// let data = cast.estimate(from, to, Some((sig, args))).await?;
/// let data = cast.estimate(from, to, Some((sig, args)), Chain::Mainnet, None).await?;
/// println!("{}", data);
/// # Ok(())
/// # }
Expand All @@ -168,8 +170,10 @@ where
from: F,
to: T,
args: Option<(&str, Vec<String>)>,
chain: Chain,
etherscan_api_key: Option<String>,
) -> Result<U256> {
let tx = self.build_tx(from, to, args).await?.into();
let tx = self.build_tx(from, to, args, chain, etherscan_api_key).await?.into();
let res = self.provider.estimate_gas(&tx).await?;

Ok::<_, eyre::Error>(res)
Expand All @@ -180,17 +184,35 @@ where
from: F,
to: T,
args: Option<(&str, Vec<String>)>,
chain: Chain,
etherscan_api_key: Option<String>,
) -> Result<Eip1559TransactionRequest> {
let from = match from.into() {
NameOrAddress::Name(ref ens_name) => self.provider.resolve_name(ens_name).await?,
NameOrAddress::Address(addr) => addr,
};

let to = match to.into() {
NameOrAddress::Name(ref ens_name) => self.provider.resolve_name(ens_name).await?,
NameOrAddress::Address(addr) => addr,
};

// make the call
let mut tx = Eip1559TransactionRequest::new().from(from).to(to);

if let Some((sig, args)) = args {
let func = get_func(sig)?;
let func = if sig.contains('(') {
get_func(sig)?
} else {
get_func_etherscan(
sig,
to,
args.clone(),
chain,
etherscan_api_key.expect("Must set ETHERSCAN_API_KEY"),
)
.await?
};
let data = encode_args(&func, &args)?;
tx = tx.data(data);
}
Expand Down
58 changes: 46 additions & 12 deletions cli/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use ethers::{
},
providers::{Middleware, Provider},
signers::{LocalWallet, Signer},
types::{Address, NameOrAddress, Signature, U256},
types::{Address, Chain, NameOrAddress, Signature, U256},
};
use rayon::prelude::*;
use regex::RegexSet;
Expand Down Expand Up @@ -157,28 +157,56 @@ async fn main() -> eyre::Result<()> {
let provider = Provider::try_from(rpc_url)?;
println!("{}", Cast::new(&provider).transaction(hash, field, to_json).await?)
}
Subcommands::SendTx { eth, to, sig, cast_async, args } => {
Subcommands::SendTx { eth, to, sig, cast_async, args, chain, etherscan_api_key } => {
let provider = Provider::try_from(eth.rpc_url()?)?;
let chain_id = Cast::new(&provider).chain_id().await?;

if let Some(signer) = eth.signer_with(chain_id, provider.clone()).await? {
match signer {
WalletType::Ledger(signer) => {
cast_send(&signer, signer.address(), to, sig, args, cast_async).await?;
cast_send(
&signer,
signer.address(),
to,
(sig, args),
chain,
etherscan_api_key,
cast_async,
)
.await?;
}
WalletType::Local(signer) => {
cast_send(&signer, signer.address(), to, sig, args, cast_async).await?;
cast_send(
&signer,
signer.address(),
to,
(sig, args),
chain,
etherscan_api_key,
cast_async,
)
.await?;
}
WalletType::Trezor(signer) => {
cast_send(&signer, signer.address(), to, sig, args, cast_async).await?;
cast_send(
&signer,
signer.address(),
to,
(sig, args),
chain,
etherscan_api_key,
cast_async,
)
.await?;
}
}
} else {
let from = eth.from.expect("No ETH_FROM or signer specified");
cast_send(provider, from, to, sig, args, cast_async).await?;
cast_send(provider, from, to, (sig, args), chain, etherscan_api_key, cast_async)
.await?;
}
}
Subcommands::Estimate { eth, to, sig, args } => {
Subcommands::Estimate { eth, to, sig, args, chain, etherscan_api_key } => {
let provider = Provider::try_from(eth.rpc_url()?)?;
let cast = Cast::new(&provider);
// chain id does not matter here, we're just trying to get the address
Expand All @@ -191,7 +219,9 @@ async fn main() -> eyre::Result<()> {
} else {
eth.from.expect("No ETH_FROM or signer specified")
};
let gas = cast.estimate(from, to, Some((sig.as_str(), args))).await?;
let gas = cast
.estimate(from, to, Some((sig.as_str(), args)), chain, etherscan_api_key)
.await?;
println!("{}", gas);
}
Subcommands::CalldataDecode { sig, calldata } => {
Expand Down Expand Up @@ -441,16 +471,20 @@ async fn cast_send<M: Middleware, F: Into<NameOrAddress>, T: Into<NameOrAddress>
provider: M,
from: F,
to: T,
sig: String,
args: Vec<String>,
args: (String, Vec<String>),
chain: Chain,
etherscan_api_key: Option<String>,
cast_async: bool,
) -> eyre::Result<()>
where
M::Error: 'static,
{
let cast = Cast::new(provider);
let pending_tx =
cast.send(from, to, if !sig.is_empty() { Some((&sig, args)) } else { None }).await?;

let sig = args.0;
let params = args.1;
let params = if !sig.is_empty() { Some((&sig[..], params)) } else { None };
let pending_tx = cast.send(from, to, params, chain, etherscan_api_key).await?;
let tx_hash = *pending_tx;

if cast_async {
Expand Down
14 changes: 11 additions & 3 deletions cli/src/opts/cast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::str::FromStr;

use ethers::types::{Address, BlockId, BlockNumber, NameOrAddress, H256};
use ethers::types::{Address, BlockId, BlockNumber, Chain, NameOrAddress, H256};
use structopt::StructOpt;

use super::{EthereumOpts, Wallet};
Expand Down Expand Up @@ -131,12 +131,16 @@ pub enum Subcommands {
SendTx {
#[structopt(help = "the address you want to transact with", parse(try_from_str = parse_name_or_address))]
to: NameOrAddress,
#[structopt(help = "the function signature you want to call")]
#[structopt(help = "the function signature or name you want to call")]
sig: String,
#[structopt(help = "the list of arguments you want to call the function with")]
args: Vec<String>,
#[structopt(long, env = "CAST_ASYNC")]
cast_async: bool,
#[structopt(long, env = "ETHERSCAN_API_KEY")]
etherscan_api_key: Option<String>,
#[structopt(long, env = "CHAIN", default_value = "mainnet")]
chain: Chain,
#[structopt(flatten)]
eth: EthereumOpts,
},
Expand All @@ -145,10 +149,14 @@ pub enum Subcommands {
Estimate {
#[structopt(help = "the address you want to transact with", parse(try_from_str = parse_name_or_address))]
to: NameOrAddress,
#[structopt(help = "the function signature you want to call")]
#[structopt(help = "the function signature or name you want to call")]
sig: String,
#[structopt(help = "the list of arguments you want to call the function with")]
args: Vec<String>,
#[structopt(long, env = "ETHERSCAN_API_KEY")]
etherscan_api_key: Option<String>,
#[structopt(long, env = "CHAIN", default_value = "mainnet")]
chain: Chain,
#[structopt(flatten)]
eth: EthereumOpts,
},
Expand Down
1 change: 1 addition & 0 deletions utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
# ethers = "0.5"
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs", default-features = false }

eyre = { version = "0.6.5", default-features = false }
hex = "0.4.3"
Expand Down
22 changes: 22 additions & 0 deletions utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ethers_core::{
},
types::*,
};
use ethers_etherscan::Client;
use eyre::{Result, WrapErr};
use serde::Deserialize;

Expand Down Expand Up @@ -175,6 +176,27 @@ pub fn get_func(sig: &str) -> Result<Function> {
Ok(func.clone())
}

pub async fn get_func_etherscan(
function_name: &str,
contract: Address,
args: Vec<String>,
chain: Chain,
etherscan_api_key: String,
) -> Result<Function> {
let client = Client::new(chain, etherscan_api_key)?;
let abi = client.contract_abi(contract).await?;
let funcs = abi.functions.get(function_name).unwrap();

for func in funcs {
let res = encode_args(func, &args);
if res.is_ok() {
return Ok(func.clone())
}
}

Err(eyre::eyre!("Function not found"))
}

/// Parses string input as Token against the expected ParamType
pub fn parse_tokens<'a, I: IntoIterator<Item = (&'a ParamType, &'a str)>>(
params: I,
Expand Down

0 comments on commit 7740023

Please sign in to comment.