From 68decb69e38d30ea7e560a425cd86fdb1fd4f748 Mon Sep 17 00:00:00 2001 From: Owleksiy Date: Sun, 27 Mar 2022 18:05:32 -0700 Subject: [PATCH 1/3] cast: s/build_tx/TxBuilder/ Fixes https://github.com/gakonst/foundry/issues/937 Refactoring to move away from function-with-hundred-arguments to a Builder pattern --- cast/src/lib.rs | 174 +++++++---------------------------------------- cast/src/tx.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++ cli/src/cast.rs | 67 +++++++++--------- utils/src/lib.rs | 2 +- 4 files changed, 233 insertions(+), 184 deletions(-) create mode 100644 cast/src/tx.rs diff --git a/cast/src/lib.rs b/cast/src/lib.rs index a59cea2b227d..e8bfe5d22d1c 100644 --- a/cast/src/lib.rs +++ b/cast/src/lib.rs @@ -17,9 +17,12 @@ use eyre::{Context, Result}; use futures::future::join_all; use rustc_hex::{FromHexIter, ToHex}; use std::{path::PathBuf, str::FromStr}; +pub use tx::TxBuilder; use foundry_utils::{encode_args, get_func, get_func_etherscan, to_table}; +mod tx; + // TODO: CastContract with common contract initializers? Same for CastProviders? pub struct Cast { @@ -50,7 +53,7 @@ where /// Makes a read-only call to the specified address /// /// ```no_run - /// + /// /// use cast::Cast; /// use ethers_core::types::{Address, Chain}; /// use ethers_providers::{Provider, Http}; @@ -67,22 +70,15 @@ where /// # Ok(()) /// # } /// ``` - pub async fn call, T: Into>( + pub async fn call( &self, - from: F, - to: T, - args: (&str, Vec), - chain: Chain, - etherscan_api_key: Option, + builder: TxBuilder, block: Option, ) -> Result { - let (tx, func) = self - .build_tx(from, to, Some(args), None, None, None, None, chain, etherscan_api_key, false) - .await?; - let res = self.provider.call(&tx, block).await?; + let res = self.provider.call(&builder.tx, block).await?; // decode args into tokens - let func = func.expect("no valid function signature was provided."); + let func = builder.func.expect("no valid function signature was provided."); let decoded = func.decode_output(res.as_ref()).wrap_err( "could not decode output. did you specify the wrong function return data type perhaps?", )?; @@ -113,7 +109,7 @@ where /// Generates an access list for the specified transaction /// /// ```no_run - /// + /// /// use cast::Cast; /// use ethers_core::types::{Address, Chain}; /// use ethers_providers::{Provider, Http}; @@ -130,19 +126,13 @@ where /// # Ok(()) /// # } /// ``` - pub async fn access_list, T: Into>( + pub async fn access_list( &self, - from: F, - to: T, - args: (&str, Vec), - chain: Chain, + builder: &TxBuilder, block: Option, to_json: bool, ) -> Result { - let (tx, _) = - self.build_tx(from, to, Some(args), None, None, None, None, chain, None, false).await?; - - let access_list = self.provider.create_access_list(&tx, block).await?; + let access_list = self.provider.create_access_list(&builder.tx, block).await?; let res = if to_json { serde_json::to_string(&access_list)? } else { @@ -195,34 +185,11 @@ where /// # } /// ``` #[allow(clippy::too_many_arguments)] - pub async fn send, T: Into>( + pub async fn send( &self, - from: F, - to: T, - args: Option<(&str, Vec)>, - gas: Option, - gas_price: Option, - value: Option, - nonce: Option, - chain: Chain, - etherscan_api_key: Option, - legacy: bool, + builder: TxBuilder, ) -> Result> { - let (tx, _) = self - .build_tx( - from, - to, - args, - gas, - gas_price, - value, - nonce, - chain, - etherscan_api_key, - legacy, - ) - .await?; - let res = self.provider.send_transaction(tx, None).await?; + let res = self.provider.send_transaction(builder.tx, None).await?; Ok::<_, eyre::Error>(res) } @@ -282,96 +249,16 @@ where chain: Chain, etherscan_api_key: Option, ) -> Result { - let (tx, _) = self - .build_tx(from, to, args, None, None, value, None, chain, etherscan_api_key, false) + let mut builder = TxBuilder::new(&self.provider, from, to, chain, false).await?; + builder + .value(value) + .etherscan_api_key(etherscan_api_key) + .args(&self.provider, args) .await?; - let res = self.provider.estimate_gas(&tx).await?; - Ok::<_, eyre::Error>(res) - } + let res = self.provider.estimate_gas(&builder.tx).await?; - #[allow(clippy::too_many_arguments)] - async fn build_tx, T: Into>( - &self, - from: F, - to: T, - args: Option<(&str, Vec)>, - gas: Option, - gas_price: Option, - value: Option, - nonce: Option, - chain: Chain, - etherscan_api_key: Option, - legacy: bool, - ) -> Result<(TypedTransaction, Option)> { - let from = match from.into() { - NameOrAddress::Name(ref ens_name) => self.provider.resolve_name(ens_name).await?, - NameOrAddress::Address(addr) => addr, - }; - - // Queries the addressbook for the address if present. - let to = foundry_utils::resolve_addr(to, chain)?; - - let to = match to { - NameOrAddress::Name(ref ens_name) => self.provider.resolve_name(ens_name).await?, - NameOrAddress::Address(addr) => addr, - }; - - // make the call - let mut tx: TypedTransaction = if chain.is_legacy() || legacy { - TransactionRequest::new().from(from).to(to).into() - } else { - Eip1559TransactionRequest::new().from(from).to(to).into() - }; - - let func = if let Some((sig, args)) = args { - let args = resolve_name_args(&args, &self.provider).await; - - let func = if sig.contains('(') { - get_func(sig)? - } else if sig.starts_with("0x") { - // if only calldata is provided, returning a dummy function - get_func("x()")? - } else { - get_func_etherscan( - sig, - to, - &args, - chain, - etherscan_api_key.expect("Must set ETHERSCAN_API_KEY"), - ) - .await? - }; - - let data = if sig.starts_with("0x") { - hex::decode(strip_0x(sig))? - } else { - encode_args(&func, &args)? - }; - - tx.set_data(data.into()); - Some(func) - } else { - None - }; - - if let Some(gas) = gas { - tx.set_gas(gas); - } - - if let Some(gas_price) = gas_price { - tx.set_gas_price(gas_price) - } - - if let Some(value) = value { - tx.set_value(value); - } - - if let Some(nonce) = nonce { - tx.set_nonce(nonce); - } - - Ok((tx, func)) + Ok::<_, eyre::Error>(res) } /// ```no_run @@ -1289,7 +1176,7 @@ impl SimpleCast { let code = meta.source_code(); if code.is_empty() { - return Err(eyre::eyre!("unverified contract")) + return Err(eyre::eyre!("unverified contract")); } Ok(code) @@ -1351,21 +1238,6 @@ fn strip_0x(s: &str) -> &str { s.strip_prefix("0x").unwrap_or(s) } -async fn resolve_name_args(args: &[String], provider: &M) -> Vec { - join_all(args.iter().map(|arg| async { - if arg.contains('.') { - let addr = provider.resolve_name(arg).await; - match addr { - Ok(addr) => format!("0x{}", hex::encode(addr.as_bytes())), - Err(_) => arg.to_string(), - } - } else { - arg.to_string() - } - })) - .await -} - #[cfg(test)] mod tests { use super::SimpleCast as Cast; diff --git a/cast/src/tx.rs b/cast/src/tx.rs new file mode 100644 index 000000000000..7115833499cc --- /dev/null +++ b/cast/src/tx.rs @@ -0,0 +1,174 @@ +use ethers_core::{ + abi::Function, + types::{ + transaction::eip2718::TypedTransaction, Chain, Eip1559TransactionRequest, NameOrAddress, + TransactionRequest, H160, U256, + }, +}; +use ethers_providers::Middleware; + +use eyre::{eyre, Result}; +use foundry_utils::{encode_args, get_func, get_func_etherscan}; +use futures::future::join_all; + +use crate::strip_0x; + +pub struct TxBuilder { + pub from: H160, + pub to: H160, + pub chain: Chain, + pub tx: TypedTransaction, + pub func: Option, + pub etherscan_api_key: Option, +} + +impl TxBuilder { + pub async fn new, T: Into>( + provider: &M, + from: F, + to: T, + chain: Chain, + legacy: bool, + ) -> Result { + let from_addr = resolve_ens(provider, from).await?; + let to_addr = resolve_ens(provider, foundry_utils::resolve_addr(to, chain)?).await?; + + let tx: TypedTransaction = if chain.is_legacy() || legacy { + TransactionRequest::new().from(from_addr).to(to_addr).into() + } else { + Eip1559TransactionRequest::new().from(from_addr).to(to_addr).into() + }; + + Ok(TxBuilder { + from: from_addr, + to: to_addr, + chain, + tx, + func: None, + etherscan_api_key: None, + }) + } + pub fn set_gas<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + self.tx.set_gas(v); + self + } + pub fn gas<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + match v { + Some(x) => self.set_gas(x), + None => self, + } + } + + pub fn set_gas_price<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + self.tx.set_gas_price(v); + self + } + + pub fn gas_price<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + match v { + Some(x) => self.set_gas_price(x), + None => self, + } + } + + pub fn set_value<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + self.tx.set_value(v); + self + } + pub fn value<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + match v { + Some(x) => self.set_value(x), + None => self, + } + } + pub fn set_nonce<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + self.tx.set_nonce(v); + self + } + pub fn nonce<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + match v { + Some(x) => self.set_nonce(x), + None => self, + } + } + + pub fn set_etherscan_api_key<'a>(&'a mut self, v: String) -> &'a mut TxBuilder { + self.etherscan_api_key = Some(v); + self + } + pub fn etherscan_api_key<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + match v { + Some(x) => self.set_etherscan_api_key(x), + None => self, + } + } + + pub async fn set_args<'a, M: Middleware>( + &'a mut self, + provider: &M, + sig: &str, + args: Vec, + ) -> Result<&'a mut TxBuilder> { + let args = resolve_name_args(&args, provider).await; + + let func = if sig.contains('(') { + get_func(&sig)? + } else if sig.starts_with("0x") { + // if only calldata is provided, returning a dummy function + get_func("x()")? + } else { + get_func_etherscan( + &sig, + self.to, + &args, + self.chain, + self.etherscan_api_key.as_ref().expect("Must set ETHERSCAN_API_KEY"), + ) + .await? + }; + + let data = if sig.starts_with("0x") { + hex::decode(strip_0x(&sig))? + } else { + encode_args(&func, &args)? + }; + + self.tx.set_data(data.into()); + self.func = Some(func); + Ok(self) + } + pub async fn args<'a, M: Middleware>( + &'a mut self, + provider: &M, + value: Option<(&str, Vec)>, + ) -> Result<&'a mut TxBuilder> { + match value { + Some((sig, args)) => self.set_args(provider, sig, args).await, + None => Ok(self), + } + } +} + +async fn resolve_ens>(provider: &M, addr: T) -> Result { + let from_addr = match addr.into() { + NameOrAddress::Name(ref ens_name) => provider.resolve_name(ens_name).await, + NameOrAddress::Address(addr) => Ok(addr), + } + .map_err(|x| eyre!("Failed to resolve ENS name: {}", x))?; + return Ok(from_addr); +} + +async fn resolve_name_args(args: &[String], provider: &M) -> Vec { + join_all(args.iter().map(|arg| async { + if arg.contains('.') { + let addr = provider.resolve_name(arg).await; + match addr { + Ok(addr) => format!("0x{}", hex::encode(addr.as_bytes())), + Err(_) => arg.to_string(), + } + } else { + arg.to_string() + } + })) + .await +} diff --git a/cli/src/cast.rs b/cli/src/cast.rs index 49556a10ff9e..b5ac5362b074 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -3,7 +3,7 @@ pub mod cmd; mod term; mod utils; -use cast::{Cast, SimpleCast}; +use cast::{Cast, SimpleCast, TxBuilder}; mod opts; use cast::InterfacePath; use ethers::{ @@ -152,19 +152,17 @@ async fn main() -> eyre::Result<()> { } Subcommands::AccessList { eth, address, sig, args, block, to_json } => { let provider = Provider::try_from(eth.rpc_url()?)?; - println!( - "{}", - Cast::new(provider) - .access_list( - eth.from.unwrap_or(Address::zero()), - address, - (&sig, args), - eth.chain, - block, - to_json, - ) - .await? - ); + let mut builder = TxBuilder::new( + &provider, + eth.from.unwrap_or(Address::zero()), + address, + eth.chain, + false, + ) + .await?; + builder.set_args(&provider, &sig, args); + + println!("{}", Cast::new(provider).access_list(&builder, block, to_json).await?); } Subcommands::Block { rpc_url, block, full, field, to_json } => { let provider = Provider::try_from(rpc_url)?; @@ -176,19 +174,16 @@ async fn main() -> eyre::Result<()> { } Subcommands::Call { eth, address, sig, args, block } => { let provider = Provider::try_from(eth.rpc_url()?)?; - println!( - "{}", - Cast::new(provider) - .call( - eth.from.unwrap_or(Address::zero()), - address, - (&sig, args), - eth.chain, - eth.etherscan_api_key, - block - ) - .await? - ); + let mut builder = TxBuilder::new( + &provider, + eth.from.unwrap_or(Address::zero()), + address, + eth.chain, + false, + ) + .await?; + builder.set_args(&provider, &sig, args).await?.etherscan_api_key(eth.etherscan_api_key); + println!("{}", Cast::new(provider).call(builder, block).await?); } Subcommands::Calldata { sig, args } => { println!("{}", SimpleCast::calldata(sig, &args)?); @@ -738,14 +733,22 @@ async fn cast_send, T: Into where M::Error: 'static, { - let cast = Cast::new(provider); - 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, gas, gas_price, value, nonce, chain, etherscan_api_key, legacy) - .await?; + let mut builder = TxBuilder::new(&provider, from, to, chain, legacy).await?; + builder + .args(&provider, params) + .await? + .gas(gas) + .gas_price(gas_price) + .value(value) + .nonce(nonce) + .etherscan_api_key(etherscan_api_key); + + let cast = Cast::new(provider); + + let pending_tx = cast.send(builder).await?; let tx_hash = *pending_tx; if cast_async { diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 25ca6716d1fe..29cd3dbc06f5 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -369,7 +369,7 @@ pub async fn get_func_etherscan( contract: Address, args: &[String], chain: Chain, - etherscan_api_key: String, + etherscan_api_key: &String, ) -> Result { let client = Client::new(chain, etherscan_api_key)?; let metadata = &client.contract_source_code(contract).await?.items[0]; From 7d14355317188d7a2588e27c8985d54e5d255033 Mon Sep 17 00:00:00 2001 From: Owleksiy Date: Wed, 30 Mar 2022 22:35:02 -0700 Subject: [PATCH 2/3] TxBuilder: cleaned up interface, added tests/comments --- Cargo.lock | 8 +- cast/Cargo.toml | 6 ++ cast/src/lib.rs | 66 +++++++------ cast/src/tx.rs | 254 +++++++++++++++++++++++++++++++++++++++++------- cli/src/cast.rs | 2 +- 5 files changed, 270 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7b165cde62c..e96266cf1bef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", @@ -475,6 +475,7 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" name = "cast" version = "0.2.0" dependencies = [ + "async-trait", "chrono 0.2.25", "ethers-core", "ethers-etherscan", @@ -485,7 +486,10 @@ dependencies = [ "futures", "hex", "rustc-hex", + "serde", "serde_json", + "thiserror", + "tokio", ] [[package]] diff --git a/cast/Cargo.toml b/cast/Cargo.toml index f23b98eece04..de5ef0cdde1d 100644 --- a/cast/Cargo.toml +++ b/cast/Cargo.toml @@ -19,6 +19,12 @@ serde_json = "1.0.67" chrono = "0.2" hex = "0.4.3" +[dev-dependencies] +async-trait = "0.1.53" +serde = "1.0.136" +tokio = "1.17.0" +thiserror = "1.0.30" + [features] default = ["ledger", "trezor"] ledger = ["ethers-signers/ledger"] diff --git a/cast/src/lib.rs b/cast/src/lib.rs index e8bfe5d22d1c..a0a305c10174 100644 --- a/cast/src/lib.rs +++ b/cast/src/lib.rs @@ -7,19 +7,18 @@ use ethers_core::{ token::{LenientTokenizer, Tokenizer}, Abi, AbiParser, Token, }, - types::{transaction::eip2718::TypedTransaction, Chain, *}, + types::{Chain, *}, utils::{self, get_contract_address, keccak256, parse_units}, }; use ethers_etherscan::Client; use ethers_providers::{Middleware, PendingTransaction}; use eyre::{Context, Result}; -use futures::future::join_all; use rustc_hex::{FromHexIter, ToHex}; use std::{path::PathBuf, str::FromStr}; pub use tx::TxBuilder; -use foundry_utils::{encode_args, get_func, get_func_etherscan, to_table}; +use foundry_utils::{encode_args, to_table}; mod tx; @@ -53,32 +52,32 @@ where /// Makes a read-only call to the specified address /// /// ```no_run - /// - /// use cast::Cast; + /// + /// use cast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let cast = Cast::new(provider); /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; /// let sig = "function greeting(uint256 i) public returns (string)"; /// let args = vec!["5".to_owned()]; - /// let data = cast.call(Address::zero(), to, (sig, args), Chain::Mainnet, None, None).await?; + /// let mut builder = TxBuilder::new(&provider, Address::zero(), to, Chain::Mainnet, false).await?; + /// builder + /// .set_args(&provider, sig, args).await?; + /// let cast = Cast::new(provider); + /// let data = cast.call(builder, None).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` - pub async fn call( - &self, - builder: TxBuilder, - block: Option, - ) -> Result { - let res = self.provider.call(&builder.tx, block).await?; + pub async fn call(&self, builder: TxBuilder, block: Option) -> Result { + let (tx, func) = builder.build(); + let res = self.provider.call(&tx, block).await?; // decode args into tokens - let func = builder.func.expect("no valid function signature was provided."); + let func = func.expect("no valid function signature was provided."); let decoded = func.decode_output(res.as_ref()).wrap_err( "could not decode output. did you specify the wrong function return data type perhaps?", )?; @@ -109,19 +108,22 @@ where /// Generates an access list for the specified transaction /// /// ```no_run - /// - /// use cast::Cast; + /// + /// use cast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let cast = Cast::new(provider); /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; /// let sig = "greeting(uint256)(string)"; /// let args = vec!["5".to_owned()]; - /// let access_list = cast.access_list(Address::zero(), to, (sig, args), Chain::Mainnet, None, false).await?; + /// let mut builder = TxBuilder::new(&provider, Address::zero(), to, Chain::Mainnet, false).await?; + /// builder + /// .set_args(&provider, sig, args).await?; + /// let cast = Cast::new(provider); + /// let access_list = cast.access_list(&builder, None, false).await?; /// println!("{}", access_list); /// # Ok(()) /// # } @@ -132,7 +134,8 @@ where block: Option, to_json: bool, ) -> Result { - let access_list = self.provider.create_access_list(&builder.tx, block).await?; + let (tx, _) = builder.peek(); + let access_list = self.provider.create_access_list(tx, block).await?; let res = if to_json { serde_json::to_string(&access_list)? } else { @@ -164,14 +167,13 @@ where /// Sends a transaction to the specified address /// /// ```no_run - /// use cast::Cast; + /// use cast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain, U256}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let cast = Cast::new(provider); /// let from = "vitalik.eth"; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; /// let sig = "greet(string)()"; @@ -179,17 +181,22 @@ where /// let gas = U256::from_str("200000").unwrap(); /// let value = U256::from_str("1").unwrap(); /// let nonce = U256::from_str("1").unwrap(); - /// let data = cast.send(from, to, Some((sig, args)), Some(gas), None, Some(value), Some(nonce), Chain::Mainnet, None, false).await?; + /// let mut builder = TxBuilder::new(&provider, Address::zero(), to, Chain::Mainnet, false).await?; + /// builder + /// .set_args(&provider, sig, args).await? + /// .set_gas(gas) + /// .set_value(value) + /// .set_nonce(nonce); + /// let cast = Cast::new(provider); + /// let data = cast.send(builder).await?; /// println!("{}", *data); /// # Ok(()) /// # } /// ``` #[allow(clippy::too_many_arguments)] - pub async fn send( - &self, - builder: TxBuilder, - ) -> Result> { - let res = self.provider.send_transaction(builder.tx, None).await?; + pub async fn send(&self, builder: TxBuilder) -> Result> { + let (tx, _) = builder.build(); + let res = self.provider.send_transaction(tx, None).await?; Ok::<_, eyre::Error>(res) } @@ -255,8 +262,9 @@ where .etherscan_api_key(etherscan_api_key) .args(&self.provider, args) .await?; + let (tx, _) = builder.build(); - let res = self.provider.estimate_gas(&builder.tx).await?; + let res = self.provider.estimate_gas(&tx).await?; Ok::<_, eyre::Error>(res) } @@ -1176,7 +1184,7 @@ impl SimpleCast { let code = meta.source_code(); if code.is_empty() { - return Err(eyre::eyre!("unverified contract")); + return Err(eyre::eyre!("unverified contract")) } Ok(code) diff --git a/cast/src/tx.rs b/cast/src/tx.rs index 7115833499cc..02038714d197 100644 --- a/cast/src/tx.rs +++ b/cast/src/tx.rs @@ -14,15 +14,35 @@ use futures::future::join_all; use crate::strip_0x; pub struct TxBuilder { - pub from: H160, - pub to: H160, - pub chain: Chain, - pub tx: TypedTransaction, - pub func: Option, - pub etherscan_api_key: Option, + to: H160, + chain: Chain, + tx: TypedTransaction, + func: Option, + etherscan_api_key: Option, } +/// +/// Transaction builder +/// ``` +/// async fn foo() -> eyre::Result<()> { +/// use ethers_core::types::{Chain, U256}; +/// use cast::{TxBuilder}; +/// let provider = ethers_providers::test_provider::MAINNET.provider(); +/// let mut builder = TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await?; +/// builder +/// .gas(Some(U256::from(1))); +/// let (tx, _) = builder.build(); +/// Ok(()) +/// } +/// ``` impl TxBuilder { + /// + /// Create a new TxBuilder + /// `provider` - provider to use + /// `from` - 'from' field. Could be an ENS name + /// `to` - `to`. Could be a ENS + /// `chain` - chain to construct the tx for + /// `legacy` - use type 1 transaction pub async fn new, T: Into>( provider: &M, from: F, @@ -39,86 +59,112 @@ impl TxBuilder { Eip1559TransactionRequest::new().from(from_addr).to(to_addr).into() }; - Ok(TxBuilder { - from: from_addr, - to: to_addr, - chain, - tx, - func: None, - etherscan_api_key: None, - }) + Ok(TxBuilder { to: to_addr, chain, tx, func: None, etherscan_api_key: None }) } - pub fn set_gas<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + /// + /// Set gas for tx + pub fn set_gas(&mut self, v: U256) -> &mut TxBuilder { self.tx.set_gas(v); self } - pub fn gas<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + + /// + /// Set gas for tx, if `v` is not None + pub fn gas(&mut self, v: Option) -> &mut TxBuilder { match v { Some(x) => self.set_gas(x), None => self, } } - pub fn set_gas_price<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + /// + /// Set gas price + pub fn set_gas_price(&mut self, v: U256) -> &mut TxBuilder { self.tx.set_gas_price(v); self } - pub fn gas_price<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + /// + /// Set gas price, if `v` is not None + pub fn gas_price(&mut self, v: Option) -> &mut TxBuilder { match v { Some(x) => self.set_gas_price(x), None => self, } } - pub fn set_value<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + /// + /// Set value + pub fn set_value(&mut self, v: U256) -> &mut TxBuilder { self.tx.set_value(v); self } - pub fn value<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + + /// + /// Set value, if `v` is not None + pub fn value(&mut self, v: Option) -> &mut TxBuilder { match v { Some(x) => self.set_value(x), None => self, } } - pub fn set_nonce<'a>(&'a mut self, v: U256) -> &'a mut TxBuilder { + + /// + /// Set nonce + pub fn set_nonce(&mut self, v: U256) -> &mut TxBuilder { self.tx.set_nonce(v); self } - pub fn nonce<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + + /// + /// Set nonce, if `v` is not None + pub fn nonce(&mut self, v: Option) -> &mut TxBuilder { match v { Some(x) => self.set_nonce(x), None => self, } } - pub fn set_etherscan_api_key<'a>(&'a mut self, v: String) -> &'a mut TxBuilder { + /// + /// Set etherscan API key. Used to look up function signature buy name + pub fn set_etherscan_api_key(&mut self, v: String) -> &mut TxBuilder { self.etherscan_api_key = Some(v); self } - pub fn etherscan_api_key<'a>(&'a mut self, v: Option) -> &'a mut TxBuilder { + + /// + /// Set etherscan API key, if `v` is not None + pub fn etherscan_api_key(&mut self, v: Option) -> &mut TxBuilder { match v { Some(x) => self.set_etherscan_api_key(x), None => self, } } - pub async fn set_args<'a, M: Middleware>( - &'a mut self, + /// + /// Set function arguments + /// `sig` can be: + /// * a fragment (`do(uint32,string)`) + /// * selector + abi-encoded calldata + /// (`0xcdba2fd40000000000000000000000000000000000000000000000000000000000007a69`) + /// * only function name (`do`) - in this case, etherscan lookup is performed on `tx.to`'s + /// contract + pub async fn set_args( + &mut self, provider: &M, sig: &str, args: Vec, - ) -> Result<&'a mut TxBuilder> { + ) -> Result<&mut TxBuilder> { let args = resolve_name_args(&args, provider).await; let func = if sig.contains('(') { - get_func(&sig)? + get_func(sig)? } else if sig.starts_with("0x") { // if only calldata is provided, returning a dummy function get_func("x()")? } else { get_func_etherscan( - &sig, + sig, self.to, &args, self.chain, @@ -128,7 +174,7 @@ impl TxBuilder { }; let data = if sig.starts_with("0x") { - hex::decode(strip_0x(&sig))? + hex::decode(strip_0x(sig))? } else { encode_args(&func, &args)? }; @@ -137,16 +183,31 @@ impl TxBuilder { self.func = Some(func); Ok(self) } - pub async fn args<'a, M: Middleware>( - &'a mut self, + + /// + /// Set function arguments, if `value` is not None + pub async fn args( + &mut self, provider: &M, value: Option<(&str, Vec)>, - ) -> Result<&'a mut TxBuilder> { + ) -> Result<&mut TxBuilder> { match value { Some((sig, args)) => self.set_args(provider, sig, args).await, None => Ok(self), } } + + /// + /// Consuming build: returns typed transaction and optional function call + pub fn build(self) -> (TypedTransaction, Option) { + (self.tx, self.func) + } + + /// + /// Non-consuming build: peek into the tx content + pub fn peek(&self) -> (&TypedTransaction, &Option) { + (&self.tx, &self.func) + } } async fn resolve_ens>(provider: &M, addr: T) -> Result { @@ -155,7 +216,7 @@ async fn resolve_ens>(provider: &M, addr: NameOrAddress::Address(addr) => Ok(addr), } .map_err(|x| eyre!("Failed to resolve ENS name: {}", x))?; - return Ok(from_addr); + Ok(from_addr) } async fn resolve_name_args(args: &[String], provider: &M) -> Vec { @@ -172,3 +233,128 @@ async fn resolve_name_args(args: &[String], provider: &M) -> Vec< })) .await } + +#[cfg(test)] +mod tests { + use crate::TxBuilder; + + use ethers_core::types::{ + transaction::eip2718::TypedTransaction, Address, Chain, NameOrAddress, H160, U256, + }; + use ethers_providers::{JsonRpcClient, Middleware, ProviderError}; + + use serde::{de::DeserializeOwned, Serialize}; + + use async_trait::async_trait; + + use std::str::FromStr; + + const ADDR_1: &str = "0000000000000000000000000000000000000001"; + const ADDR_2: &str = "0000000000000000000000000000000000000002"; + + #[derive(Debug)] + struct MyProvider {} + + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] + #[cfg_attr(not(target_arch = "wasm32"), async_trait)] + impl JsonRpcClient for MyProvider { + type Error = ProviderError; + + async fn request( + &self, + _method: &str, + _params: T, + ) -> Result { + unreachable!("There is no `request`"); + } + } + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] + #[cfg_attr(not(target_arch = "wasm32"), async_trait)] + impl Middleware for MyProvider { + type Error = ProviderError; + type Provider = MyProvider; + type Inner = MyProvider; + + fn inner(&self) -> &Self::Inner { + &self + } + + async fn resolve_name(&self, ens_name: &str) -> Result { + match ens_name { + "a.eth" => Ok(H160::from_str(ADDR_1).unwrap()), + "b.eth" => Ok(H160::from_str(ADDR_2).unwrap()), + _ => unreachable!("don't know how to resolve {}", ens_name), + } + } + } + #[tokio::test] + async fn builder_new_non_legacy() -> eyre::Result<()> { + let provider = MyProvider {}; + let builder = TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await?; + let (tx, args) = builder.build(); + assert_eq!(*tx.from().unwrap(), H160::from_str(ADDR_1).unwrap()); + assert_eq!(*tx.to().unwrap(), NameOrAddress::Address(H160::from_str(ADDR_2).unwrap())); + assert_eq!(args, None); + + match tx { + TypedTransaction::Eip1559(_) => {} + _ => { + assert!(false, "Wrong tx type"); + } + } + Ok(()) + } + + #[tokio::test] + async fn builder_new_legacy() -> eyre::Result<()> { + let provider = MyProvider {}; + let builder = TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, true).await?; + // don't check anything other than the tx type - the rest is covered in the non-legacy case + let (tx, _) = builder.build(); + match tx { + TypedTransaction::Legacy(_) => {} + _ => { + assert!(false, "Wrong tx type"); + } + } + Ok(()) + } + + async fn a_builder() -> (MyProvider, TxBuilder) { + let provider = MyProvider {}; + let builder = + TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await.unwrap(); + (provider, builder) + } + + #[tokio::test] + async fn builder_fields() -> eyre::Result<()> { + let (_, mut builder) = a_builder().await; + builder.gas(Some(U256::from(12u32))); + builder.gas_price(Some(U256::from(34u32))); + builder.value(Some(U256::from(56u32))); + builder.nonce(Some(U256::from(78u32))); + + builder.etherscan_api_key(Some(String::from("what a lovely day"))); // not testing for this :-/ + let (tx, _) = builder.build(); + + assert_eq!(tx.gas().unwrap().as_u32(), 12); + assert_eq!(tx.gas_price().unwrap().as_u32(), 34); + assert_eq!(tx.value().unwrap().as_u32(), 56); + assert_eq!(tx.nonce().unwrap().as_u32(), 78); + Ok(()) + } + + #[tokio::test] + async fn builder_args() -> eyre::Result<()> { + let (provider, mut builder) = a_builder().await; + builder.args(&provider, Some(("what_a_day(int)", vec![String::from("31337")]))).await?; + let (_, function_maybe) = builder.build(); + + assert_ne!(function_maybe, None); + let function = function_maybe.unwrap(); + assert_eq!(function.name, String::from("what_a_day")); + // could test function.inputs() but that should be covered by utils's unit test + Ok(()) + } +} diff --git a/cli/src/cast.rs b/cli/src/cast.rs index b5ac5362b074..f6734ed72a74 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -160,7 +160,7 @@ async fn main() -> eyre::Result<()> { false, ) .await?; - builder.set_args(&provider, &sig, args); + builder.set_args(&provider, &sig, args).await?; println!("{}", Cast::new(provider).access_list(&builder, block, to_json).await?); } From 89cfc3c49e03635021e02cfd70ee777bebd68c63 Mon Sep 17 00:00:00 2001 From: Owleksiy Date: Fri, 1 Apr 2022 22:47:10 -0700 Subject: [PATCH 3/3] TxBuilder: keep provider ref --- cast/src/lib.rs | 73 +++++++++++++------------- cast/src/tx.rs | 133 +++++++++++++++++++++-------------------------- cli/src/cast.rs | 35 +++++++------ utils/src/lib.rs | 2 +- 4 files changed, 116 insertions(+), 127 deletions(-) diff --git a/cast/src/lib.rs b/cast/src/lib.rs index a0a305c10174..a745de344c4f 100644 --- a/cast/src/lib.rs +++ b/cast/src/lib.rs @@ -17,6 +17,7 @@ use eyre::{Context, Result}; use rustc_hex::{FromHexIter, ToHex}; use std::{path::PathBuf, str::FromStr}; pub use tx::TxBuilder; +use tx::{TxBuilderOutput, TxBuilderPeekOutput}; use foundry_utils::{encode_args, to_table}; @@ -65,15 +66,20 @@ where /// let args = vec!["5".to_owned()]; /// let mut builder = TxBuilder::new(&provider, Address::zero(), to, Chain::Mainnet, false).await?; /// builder - /// .set_args(&provider, sig, args).await?; + /// .set_args(sig, args).await?; + /// let builder_output = builder.build(); /// let cast = Cast::new(provider); - /// let data = cast.call(builder, None).await?; + /// let data = cast.call(builder_output, None).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` - pub async fn call(&self, builder: TxBuilder, block: Option) -> Result { - let (tx, func) = builder.build(); + pub async fn call<'a>( + &self, + builder_output: TxBuilderOutput, + block: Option, + ) -> Result { + let (tx, func) = builder_output; let res = self.provider.call(&tx, block).await?; // decode args into tokens @@ -121,20 +127,21 @@ where /// let args = vec!["5".to_owned()]; /// let mut builder = TxBuilder::new(&provider, Address::zero(), to, Chain::Mainnet, false).await?; /// builder - /// .set_args(&provider, sig, args).await?; - /// let cast = Cast::new(provider); - /// let access_list = cast.access_list(&builder, None, false).await?; + /// .set_args(sig, args).await?; + /// let builder_output = builder.peek(); + /// let cast = Cast::new(&provider); + /// let access_list = cast.access_list(builder_output, None, false).await?; /// println!("{}", access_list); /// # Ok(()) /// # } /// ``` - pub async fn access_list( + pub async fn access_list<'a>( &self, - builder: &TxBuilder, + builder_output: TxBuilderPeekOutput<'a>, block: Option, to_json: bool, ) -> Result { - let (tx, _) = builder.peek(); + let (tx, _) = builder_output; let access_list = self.provider.create_access_list(tx, block).await?; let res = if to_json { serde_json::to_string(&access_list)? @@ -183,19 +190,22 @@ where /// let nonce = U256::from_str("1").unwrap(); /// let mut builder = TxBuilder::new(&provider, Address::zero(), to, Chain::Mainnet, false).await?; /// builder - /// .set_args(&provider, sig, args).await? + /// .set_args(sig, args).await? /// .set_gas(gas) /// .set_value(value) /// .set_nonce(nonce); + /// let builder_output = builder.build(); /// let cast = Cast::new(provider); - /// let data = cast.send(builder).await?; + /// let data = cast.send(builder_output).await?; /// println!("{}", *data); /// # Ok(()) /// # } /// ``` - #[allow(clippy::too_many_arguments)] - pub async fn send(&self, builder: TxBuilder) -> Result> { - let (tx, _) = builder.build(); + pub async fn send<'a>( + &self, + builder_output: TxBuilderOutput, + ) -> Result> { + let (tx, _) = builder_output; let res = self.provider.send_transaction(tx, None).await?; Ok::<_, eyre::Error>(res) @@ -229,42 +239,33 @@ where /// Estimates the gas cost of a transaction /// /// ```no_run - /// use cast::Cast; + /// use cast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain, U256}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; /// /// # async fn foo() -> eyre::Result<()> { /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let cast = Cast::new(provider); /// let from = "vitalik.eth"; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; /// let sig = "greet(string)()"; /// let args = vec!["5".to_owned()]; /// let value = U256::from_str("1").unwrap(); - /// let data = cast.estimate(from, to, Some((sig, args)), Some(value), Chain::Mainnet, None).await?; + /// let mut builder = TxBuilder::new(&provider, from, to, Chain::Mainnet, false).await?; + /// builder + /// .set_value(value) + /// .set_args(sig, args).await?; + /// let builder_output = builder.peek(); + /// let cast = Cast::new(&provider); + /// let data = cast.estimate(builder_output).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` - pub async fn estimate, T: Into>( - &self, - from: F, - to: T, - args: Option<(&str, Vec)>, - value: Option, - chain: Chain, - etherscan_api_key: Option, - ) -> Result { - let mut builder = TxBuilder::new(&self.provider, from, to, chain, false).await?; - builder - .value(value) - .etherscan_api_key(etherscan_api_key) - .args(&self.provider, args) - .await?; - let (tx, _) = builder.build(); - - let res = self.provider.estimate_gas(&tx).await?; + pub async fn estimate<'a>(&self, builder_output: TxBuilderPeekOutput<'a>) -> Result { + let (tx, _) = builder_output; + + let res = self.provider.estimate_gas(tx).await?; Ok::<_, eyre::Error>(res) } diff --git a/cast/src/tx.rs b/cast/src/tx.rs index 02038714d197..8f0aa6e504c1 100644 --- a/cast/src/tx.rs +++ b/cast/src/tx.rs @@ -13,20 +13,23 @@ use futures::future::join_all; use crate::strip_0x; -pub struct TxBuilder { +pub struct TxBuilder<'a, M: Middleware> { to: H160, chain: Chain, tx: TypedTransaction, func: Option, etherscan_api_key: Option, + provider: &'a M, } -/// +pub type TxBuilderOutput = (TypedTransaction, Option); +pub type TxBuilderPeekOutput<'a> = (&'a TypedTransaction, &'a Option); + /// Transaction builder /// ``` /// async fn foo() -> eyre::Result<()> { /// use ethers_core::types::{Chain, U256}; -/// use cast::{TxBuilder}; +/// use cast::TxBuilder; /// let provider = ethers_providers::test_provider::MAINNET.provider(); /// let mut builder = TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await?; /// builder @@ -35,21 +38,20 @@ pub struct TxBuilder { /// Ok(()) /// } /// ``` -impl TxBuilder { - /// +impl<'a, M: Middleware> TxBuilder<'a, M> { /// Create a new TxBuilder /// `provider` - provider to use /// `from` - 'from' field. Could be an ENS name /// `to` - `to`. Could be a ENS /// `chain` - chain to construct the tx for /// `legacy` - use type 1 transaction - pub async fn new, T: Into>( - provider: &M, + pub async fn new, T: Into>( + provider: &'a M, from: F, to: T, chain: Chain, legacy: bool, - ) -> Result { + ) -> Result> { let from_addr = resolve_ens(provider, from).await?; let to_addr = resolve_ens(provider, foundry_utils::resolve_addr(to, chain)?).await?; @@ -59,89 +61,79 @@ impl TxBuilder { Eip1559TransactionRequest::new().from(from_addr).to(to_addr).into() }; - Ok(TxBuilder { to: to_addr, chain, tx, func: None, etherscan_api_key: None }) + Ok(Self { to: to_addr, chain, tx, func: None, etherscan_api_key: None, provider }) } - /// + /// Set gas for tx - pub fn set_gas(&mut self, v: U256) -> &mut TxBuilder { + pub fn set_gas(&mut self, v: U256) -> &mut Self { self.tx.set_gas(v); self } - /// /// Set gas for tx, if `v` is not None - pub fn gas(&mut self, v: Option) -> &mut TxBuilder { - match v { - Some(x) => self.set_gas(x), - None => self, + pub fn gas(&mut self, v: Option) -> &mut Self { + if let Some(value) = v { + self.set_gas(value); } + self } - /// /// Set gas price - pub fn set_gas_price(&mut self, v: U256) -> &mut TxBuilder { + pub fn set_gas_price(&mut self, v: U256) -> &mut Self { self.tx.set_gas_price(v); self } - /// /// Set gas price, if `v` is not None - pub fn gas_price(&mut self, v: Option) -> &mut TxBuilder { - match v { - Some(x) => self.set_gas_price(x), - None => self, + pub fn gas_price(&mut self, v: Option) -> &mut Self { + if let Some(value) = v { + self.set_gas_price(value); } + self } - /// /// Set value - pub fn set_value(&mut self, v: U256) -> &mut TxBuilder { + pub fn set_value(&mut self, v: U256) -> &mut Self { self.tx.set_value(v); self } - /// /// Set value, if `v` is not None - pub fn value(&mut self, v: Option) -> &mut TxBuilder { - match v { - Some(x) => self.set_value(x), - None => self, + pub fn value(&mut self, v: Option) -> &mut Self { + if let Some(value) = v { + self.set_value(value); } + self } - /// /// Set nonce - pub fn set_nonce(&mut self, v: U256) -> &mut TxBuilder { + pub fn set_nonce(&mut self, v: U256) -> &mut Self { self.tx.set_nonce(v); self } - /// /// Set nonce, if `v` is not None - pub fn nonce(&mut self, v: Option) -> &mut TxBuilder { - match v { - Some(x) => self.set_nonce(x), - None => self, + pub fn nonce(&mut self, v: Option) -> &mut Self { + if let Some(value) = v { + self.set_nonce(value); } + self } - /// /// Set etherscan API key. Used to look up function signature buy name - pub fn set_etherscan_api_key(&mut self, v: String) -> &mut TxBuilder { + pub fn set_etherscan_api_key(&mut self, v: String) -> &mut Self { self.etherscan_api_key = Some(v); self } - /// /// Set etherscan API key, if `v` is not None - pub fn etherscan_api_key(&mut self, v: Option) -> &mut TxBuilder { - match v { - Some(x) => self.set_etherscan_api_key(x), - None => self, + pub fn etherscan_api_key(&mut self, v: Option) -> &mut Self { + if let Some(value) = v { + self.set_etherscan_api_key(value); } + self } - /// /// Set function arguments /// `sig` can be: /// * a fragment (`do(uint32,string)`) @@ -149,13 +141,12 @@ impl TxBuilder { /// (`0xcdba2fd40000000000000000000000000000000000000000000000000000000000007a69`) /// * only function name (`do`) - in this case, etherscan lookup is performed on `tx.to`'s /// contract - pub async fn set_args( + pub async fn set_args( &mut self, - provider: &M, sig: &str, args: Vec, - ) -> Result<&mut TxBuilder> { - let args = resolve_name_args(&args, provider).await; + ) -> Result<&mut TxBuilder<'a, M>> { + let args = resolve_name_args(&args, self.provider).await; let func = if sig.contains('(') { get_func(sig)? @@ -184,28 +175,24 @@ impl TxBuilder { Ok(self) } - /// /// Set function arguments, if `value` is not None - pub async fn args( + pub async fn args( &mut self, - provider: &M, value: Option<(&str, Vec)>, - ) -> Result<&mut TxBuilder> { - match value { - Some((sig, args)) => self.set_args(provider, sig, args).await, - None => Ok(self), + ) -> Result<&mut TxBuilder<'a, M>> { + if let Some((sig, args)) = value { + return self.set_args(sig, args).await } + Ok(self) } - /// /// Consuming build: returns typed transaction and optional function call - pub fn build(self) -> (TypedTransaction, Option) { + pub fn build(self) -> TxBuilderOutput { (self.tx, self.func) } - /// /// Non-consuming build: peek into the tx content - pub fn peek(&self) -> (&TypedTransaction, &Option) { + pub fn peek(&self) -> TxBuilderPeekOutput { (&self.tx, &self.func) } } @@ -320,20 +307,16 @@ mod tests { Ok(()) } - async fn a_builder() -> (MyProvider, TxBuilder) { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await.unwrap(); - (provider, builder) - } - #[tokio::test] async fn builder_fields() -> eyre::Result<()> { - let (_, mut builder) = a_builder().await; - builder.gas(Some(U256::from(12u32))); - builder.gas_price(Some(U256::from(34u32))); - builder.value(Some(U256::from(56u32))); - builder.nonce(Some(U256::from(78u32))); + let provider = MyProvider {}; + let mut builder = + TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await.unwrap(); + builder + .gas(Some(U256::from(12u32))) + .gas_price(Some(U256::from(34u32))) + .value(Some(U256::from(56u32))) + .nonce(Some(U256::from(78u32))); builder.etherscan_api_key(Some(String::from("what a lovely day"))); // not testing for this :-/ let (tx, _) = builder.build(); @@ -347,8 +330,10 @@ mod tests { #[tokio::test] async fn builder_args() -> eyre::Result<()> { - let (provider, mut builder) = a_builder().await; - builder.args(&provider, Some(("what_a_day(int)", vec![String::from("31337")]))).await?; + let provider = MyProvider {}; + let mut builder = + TxBuilder::new(&provider, "a.eth", "b.eth", Chain::Mainnet, false).await.unwrap(); + builder.args(Some(("what_a_day(int)", vec![String::from("31337")]))).await?; let (_, function_maybe) = builder.build(); assert_ne!(function_maybe, None); diff --git a/cli/src/cast.rs b/cli/src/cast.rs index f6734ed72a74..13cc2e38bfd2 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -160,9 +160,10 @@ async fn main() -> eyre::Result<()> { false, ) .await?; - builder.set_args(&provider, &sig, args).await?; + builder.set_args(&sig, args).await?; + let builder_output = builder.peek(); - println!("{}", Cast::new(provider).access_list(&builder, block, to_json).await?); + println!("{}", Cast::new(&provider).access_list(builder_output, block, to_json).await?); } Subcommands::Block { rpc_url, block, full, field, to_json } => { let provider = Provider::try_from(rpc_url)?; @@ -182,8 +183,9 @@ async fn main() -> eyre::Result<()> { false, ) .await?; - builder.set_args(&provider, &sig, args).await?.etherscan_api_key(eth.etherscan_api_key); - println!("{}", Cast::new(provider).call(builder, block).await?); + builder.set_args(&sig, args).await?.etherscan_api_key(eth.etherscan_api_key); + let builder_output = builder.build(); + println!("{}", Cast::new(provider).call(builder_output, block).await?); } Subcommands::Calldata { sig, args } => { println!("{}", SimpleCast::calldata(sig, &args)?); @@ -333,18 +335,18 @@ async fn main() -> eyre::Result<()> { } Subcommands::Estimate { eth, to, sig, args, value } => { let provider = Provider::try_from(eth.rpc_url()?)?; - let cast = Cast::new(&provider); let from = eth.sender().await; - let gas = cast - .estimate( - from, - to, - Some((sig.as_str(), args)), - value, - eth.chain, - eth.etherscan_api_key, - ) + + let mut builder = TxBuilder::new(&provider, from, to, eth.chain, false).await?; + builder + .etherscan_api_key(eth.etherscan_api_key) + .value(value) + .set_args(sig.as_str(), args) .await?; + + let builder_output = builder.peek(); + + let gas = Cast::new(&provider).estimate(builder_output).await?; println!("{}", gas); } Subcommands::CalldataDecode { sig, calldata } => { @@ -738,17 +740,18 @@ where let params = if !sig.is_empty() { Some((&sig[..], params)) } else { None }; let mut builder = TxBuilder::new(&provider, from, to, chain, legacy).await?; builder - .args(&provider, params) + .args(params) .await? .gas(gas) .gas_price(gas_price) .value(value) .nonce(nonce) .etherscan_api_key(etherscan_api_key); + let builder_output = builder.build(); let cast = Cast::new(provider); - let pending_tx = cast.send(builder).await?; + let pending_tx = cast.send(builder_output).await?; let tx_hash = *pending_tx; if cast_async { diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 29cd3dbc06f5..8501f3bbe0b3 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -369,7 +369,7 @@ pub async fn get_func_etherscan( contract: Address, args: &[String], chain: Chain, - etherscan_api_key: &String, + etherscan_api_key: &str, ) -> Result { let client = Client::new(chain, etherscan_api_key)?; let metadata = &client.contract_source_code(contract).await?.items[0];