diff --git a/Cargo.lock b/Cargo.lock index ddef20854bd3b..d542ad5f2e646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -720,7 +720,7 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b103d85ca6e209388771bfb7aa6b68a7aeec4afbf6f0a0264bfbf50360e5212e" dependencies = [ - "crossterm 0.23.0", + "crossterm 0.23.1", "strum", "strum_macros", "unicode-width", @@ -804,18 +804,18 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -834,10 +834,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", @@ -847,9 +848,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -889,9 +890,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432" +checksum = "f1fd7173631a4e9e2ca8b32ae2fad58aab9843ea5aaf56642661937d87e28a3e" dependencies = [ "bitflags", "crossterm_winapi 0.9.0", @@ -971,9 +972,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ "quote", "syn", @@ -1066,9 +1067,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1241,7 +1242,7 @@ dependencies = [ [[package]] name = "ethers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -1256,7 +1257,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "0.1.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "ethers-core", "once_cell", @@ -1267,7 +1268,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -1285,7 +1286,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "Inflector", "cfg-if 1.0.0", @@ -1307,7 +1308,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -1321,7 +1322,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "arrayvec 0.7.2", "bytes", @@ -1347,7 +1348,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "0.2.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "ethers-core", "ethers-solc", @@ -1361,7 +1362,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "async-trait", "ethers-contract", @@ -1384,7 +1385,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "async-trait", "auto_impl", @@ -1416,7 +1417,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "async-trait", "coins-bip32", @@ -1439,7 +1440,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "0.3.0" -source = "git+https://github.com/gakonst/ethers-rs#679ba09b92dc8b61963721354fea8490d9d9e04b" +source = "git+https://github.com/gakonst/ethers-rs#4ce373b8569c927f64af2074b495de92a73bd419" dependencies = [ "colored", "dunce", @@ -2411,9 +2412,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.119" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" @@ -2585,9 +2586,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba42135c6a5917b9db9cd7b293e5409e1c6b041e6f9825e92e55a894c63b6f8" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", @@ -2668,20 +2669,19 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] name = "notify" -version = "5.0.0-pre.13" +version = "5.0.0-pre.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245d358380e2352c2d020e8ee62baac09b3420f1f6c012a31326cfced4ad487d" +checksum = "d13c22db70a63592e098fb51735bab36646821e6389a0ba171f3549facdf0b74" dependencies = [ "bitflags", "crossbeam-channel", @@ -2690,7 +2690,7 @@ dependencies = [ "inotify", "kqueue", "libc", - "mio 0.7.14", + "mio 0.8.2", "walkdir", "winapi", ] @@ -2914,9 +2914,9 @@ checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" [[package]] name = "parity-scale-codec" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8483b84fb12de1dc23bf95d26030d16cea56391d136db0db37f749508104e3e6" +checksum = "e8b44461635bbb1a0300f100a841e571e7d919c81c73075ef5d152ffdb521066" dependencies = [ "arrayvec 0.7.2", "bitvec 1.0.0", @@ -2928,9 +2928,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259388ceb4c23bc09caef272c9e7a732b3b8f9fbd0b41f0009a91d6548cc1d9" +checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3503,12 +3503,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55" dependencies = [ "getrandom 0.2.5", "redox_syscall", + "thiserror", ] [[package]] @@ -4191,7 +4192,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "svm-rs" version = "0.2.9" -source = "git+https://github.com/roynalnaruto/svm-rs#c32014f34308629cb1d98a8fdad29e527a8066d7" +source = "git+https://github.com/roynalnaruto/svm-rs#07fa907f1ddffca8902ae208b3a8d472a371a035" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -4219,7 +4220,7 @@ dependencies = [ [[package]] name = "svm-rs-builds" version = "0.1.0" -source = "git+https://github.com/roynalnaruto/svm-rs#c32014f34308629cb1d98a8fdad29e527a8066d7" +source = "git+https://github.com/roynalnaruto/svm-rs#07fa907f1ddffca8902ae208b3a8d472a371a035" dependencies = [ "build_const", "hex", @@ -4384,7 +4385,7 @@ dependencies = [ "bytes", "libc", "memchr", - "mio 0.8.1", + "mio 0.8.2", "num_cpus", "once_cell", "parking_lot 0.12.0", @@ -4418,9 +4419,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" dependencies = [ "rustls", "tokio", @@ -4939,9 +4940,9 @@ dependencies = [ [[package]] name = "watchexec" -version = "2.0.0-pre.11" +version = "2.0.0-pre.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3115ed7db83103ab13d6ada8892372bf596eca52cf2ae06571b906fd7dcba4c6" +checksum = "ba93308565f295d9452043c584175f92c8851d60849c3bf0287116ed69ba4b33" dependencies = [ "async-recursion", "async-stream", @@ -4955,7 +4956,7 @@ dependencies = [ "ignore", "libc", "miette", - "nom 7.1.0", + "nom 7.1.1", "notify", "once_cell", "regex", @@ -4997,9 +4998,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.4" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -5124,6 +5125,6 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" [[package]] name = "zeroize" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" +checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" diff --git a/cli/src/cast.rs b/cli/src/cast.rs index 103f2aa5bea3a..5c8c82211e6c0 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -15,7 +15,7 @@ use ethers::{ }, providers::{Middleware, Provider}, signers::{LocalWallet, Signer}, - types::{Address, Chain, NameOrAddress, Signature, U256, U64}, + types::{Address, Chain, NameOrAddress, Signature, U256}, utils::get_contract_address, }; use opts::{ @@ -36,9 +36,8 @@ use std::{ use clap::{IntoApp, Parser}; use clap_complete::generate; -use crate::utils::read_secret; +use crate::{cmd::Cmd, utils::read_secret}; use eyre::WrapErr; -use futures::join; #[tokio::main] async fn main() -> eyre::Result<()> { @@ -542,64 +541,7 @@ async fn main() -> eyre::Result<()> { let selector = contract.abi().functions().last().unwrap().short_signature(); println!("0x{}", hex::encode(selector)); } - Subcommands::FindBlock { timestamp, rpc_url } => { - let ts_target = U256::from(timestamp); - let provider = Provider::try_from(rpc_url)?; - let last_block_num = provider.get_block_number().await?; - let cast_provider = Cast::new(provider); - - let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1)); - let ts_block_latest = res.0.unwrap(); - let ts_block_1 = res.1.unwrap(); - - let block_num = if ts_block_latest.lt(&ts_target) { - // If the most recent block's timestamp is below the target, return it - last_block_num - } else if ts_block_1.gt(&ts_target) { - // If the target timestamp is below block 1's timestamp, return that - U64::from(1) - } else { - // Otherwise, find the block that is closest to the timestamp - let mut low_block = U64::from(1); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 - let mut high_block = last_block_num; - let mut matching_block: Option = None; - while high_block.gt(&low_block) && matching_block.is_none() { - // Get timestamp of middle block (this approach approach to avoids overflow) - let high_minus_low_over_2 = high_block - .checked_sub(low_block) - .ok_or_else(|| eyre::eyre!("unexpected underflow")) - .unwrap() - .checked_div(U64::from(2)) - .unwrap(); - let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap(); - let ts_mid_block = cast_provider.timestamp(mid_block).await?; - - // Check if we've found a match or should keep searching - if ts_mid_block.eq(&ts_target) { - matching_block = Some(mid_block) - } else if high_block.checked_sub(low_block).unwrap().eq(&U64::from(1)) { - // The target timestamp is in between these blocks. This rounds to the - // highest block if timestamp is equidistant between blocks - let res = join!( - cast_provider.timestamp(high_block), - cast_provider.timestamp(low_block) - ); - let ts_high = res.0.unwrap(); - let ts_low = res.1.unwrap(); - let high_diff = ts_high.checked_sub(ts_target).unwrap(); - let low_diff = ts_target.checked_sub(ts_low).unwrap(); - let is_low = low_diff.lt(&high_diff); - matching_block = if is_low { Some(low_block) } else { Some(high_block) } - } else if ts_mid_block.lt(&ts_target) { - low_block = mid_block; - } else { - high_block = mid_block; - } - } - matching_block.unwrap_or(low_block) - }; - println!("{}", block_num); - } + Subcommands::FindBlock(cmd) => cmd.run()?, Subcommands::Wallet { command } => match command { WalletSubcommands::New { path, password, unsafe_password } => { let mut rng = thread_rng(); diff --git a/cli/src/cmd/cast/find_block.rs b/cli/src/cmd/cast/find_block.rs new file mode 100644 index 0000000000000..8daf2d4bb0bd0 --- /dev/null +++ b/cli/src/cmd/cast/find_block.rs @@ -0,0 +1,90 @@ +//! cast find-block subcommand + +use crate::cmd::Cmd; +use cast::Cast; +use clap::Parser; +use ethers::prelude::*; +use eyre::Result; +use futures::join; + +#[derive(Debug, Clone, Parser)] +pub struct FindBlockArgs { + #[clap(help = "The UNIX timestamp to search for (in seconds)")] + timestamp: u64, + #[clap(long, env = "ETH_RPC_URL")] + rpc_url: String, +} + +impl Cmd for FindBlockArgs { + type Output = (); + + fn run(self) -> Result { + let FindBlockArgs { timestamp, rpc_url } = self; + let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt"); + rt.block_on(Self::query_block(timestamp, rpc_url))?; + Ok(()) + } +} + +impl FindBlockArgs { + async fn query_block(timestamp: u64, rpc_url: String) -> Result<()> { + let ts_target = U256::from(timestamp); + let provider = Provider::try_from(rpc_url)?; + let last_block_num = provider.get_block_number().await?; + let cast_provider = Cast::new(provider); + + let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1)); + let ts_block_latest = res.0.unwrap(); + let ts_block_1 = res.1.unwrap(); + + let block_num = if ts_block_latest.lt(&ts_target) { + // If the most recent block's timestamp is below the target, return it + last_block_num + } else if ts_block_1.gt(&ts_target) { + // If the target timestamp is below block 1's timestamp, return that + U64::from(1_u64) + } else { + // Otherwise, find the block that is closest to the timestamp + let mut low_block = U64::from(1_u64); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 + let mut high_block = last_block_num; + let mut matching_block: Option = None; + while high_block.gt(&low_block) && matching_block.is_none() { + // Get timestamp of middle block (this approach approach to avoids overflow) + let high_minus_low_over_2 = high_block + .checked_sub(low_block) + .ok_or_else(|| eyre::eyre!("unexpected underflow")) + .unwrap() + .checked_div(U64::from(2_u64)) + .unwrap(); + let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap(); + let ts_mid_block = cast_provider.timestamp(mid_block).await?; + + // Check if we've found a match or should keep searching + if ts_mid_block.eq(&ts_target) { + matching_block = Some(mid_block) + } else if high_block.checked_sub(low_block).unwrap().eq(&U64::from(1_u64)) { + // The target timestamp is in between these blocks. This rounds to the + // highest block if timestamp is equidistant between blocks + let res = join!( + cast_provider.timestamp(high_block), + cast_provider.timestamp(low_block) + ); + let ts_high = res.0.unwrap(); + let ts_low = res.1.unwrap(); + let high_diff = ts_high.checked_sub(ts_target).unwrap(); + let low_diff = ts_target.checked_sub(ts_low).unwrap(); + let is_low = low_diff.lt(&high_diff); + matching_block = if is_low { Some(low_block) } else { Some(high_block) } + } else if ts_mid_block.lt(&ts_target) { + low_block = mid_block; + } else { + high_block = mid_block; + } + } + matching_block.unwrap_or(low_block) + }; + println!("{}", block_num); + + Ok(()) + } +} diff --git a/cli/src/cmd/cast/mod.rs b/cli/src/cmd/cast/mod.rs new file mode 100644 index 0000000000000..3799246cfedee --- /dev/null +++ b/cli/src/cmd/cast/mod.rs @@ -0,0 +1,8 @@ +//! Subcommands for cast +//! +//! All subcommands should respect the `foundry_config::Config`. +//! If a subcommand accepts values that are supported by the `Config`, then the subcommand should +//! implement `figment::Provider` which allows the subcommand to override the config's defaults, see +//! [`foundry_config::Config`]. + +pub mod find_block; diff --git a/cli/src/cmd/bind.rs b/cli/src/cmd/forge/bind.rs similarity index 99% rename from cli/src/cmd/bind.rs rename to cli/src/cmd/forge/bind.rs index 73cf6783d2d3b..4956d46025d3a 100644 --- a/cli/src/cmd/bind.rs +++ b/cli/src/cmd/forge/bind.rs @@ -1,4 +1,4 @@ -use crate::cmd::Cmd; +use crate::cmd::utils::Cmd; use clap::{Parser, ValueHint}; use ethers::contract::MultiAbigen; diff --git a/cli/src/cmd/build.rs b/cli/src/cmd/forge/build.rs similarity index 98% rename from cli/src/cmd/build.rs rename to cli/src/cmd/forge/build.rs index bf6d5155c7789..e47b58745a369 100644 --- a/cli/src/cmd/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use crate::{cmd::Cmd, opts::forge::CompilerArgs}; -use crate::cmd::watch::WatchArgs; +use crate::cmd::forge::watch::WatchArgs; use clap::{Parser, ValueHint}; use ethers::solc::remappings::Remapping; use foundry_config::{ @@ -191,7 +191,7 @@ impl Cmd for BuildArgs { type Output = ProjectCompileOutput; fn run(self) -> eyre::Result { let project = self.project()?; - super::compile(&project, self.names, self.sizes) + crate::cmd::utils::compile(&project, self.names, self.sizes) } } @@ -214,7 +214,7 @@ impl BuildArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { - use crate::cmd::watch; + use crate::cmd::forge::watch; let init = watch::init()?; let mut runtime = watch::runtime(&self.watch)?; diff --git a/cli/src/cmd/config.rs b/cli/src/cmd/forge/config.rs similarity index 96% rename from cli/src/cmd/config.rs rename to cli/src/cmd/forge/config.rs index 060c333765202..05ef67e5755ee 100644 --- a/cli/src/cmd/config.rs +++ b/cli/src/cmd/forge/config.rs @@ -1,7 +1,7 @@ //! config command use crate::{ - cmd::{build::BuildArgs, Cmd}, + cmd::{forge::build::BuildArgs, utils::Cmd}, opts::evm::EvmArgs, }; use clap::Parser; diff --git a/cli/src/cmd/create.rs b/cli/src/cmd/forge/create.rs similarity index 96% rename from cli/src/cmd/create.rs rename to cli/src/cmd/forge/create.rs index a4bfa51a24635..fd4c9e1147f19 100644 --- a/cli/src/cmd/create.rs +++ b/cli/src/cmd/forge/create.rs @@ -1,7 +1,7 @@ //! Create command use crate::{ - cmd::{build::BuildArgs, Cmd}, + cmd::{forge::build::BuildArgs, Cmd}, opts::{EthereumOpts, WalletType}, utils::parse_u256, }; @@ -70,10 +70,11 @@ impl Cmd for CreateArgs { fn run(self) -> Result { // Find Project & Compile let project = self.opts.project()?; - let compiled = super::compile(&project, self.opts.names, self.opts.sizes)?; + let compiled = crate::cmd::utils::compile(&project, self.opts.names, self.opts.sizes)?; // Get ABI and BIN - let (abi, bin, _) = super::read_artifact(&project, compiled, self.contract.clone())?; + let (abi, bin, _) = + crate::cmd::utils::read_artifact(&project, compiled, self.contract.clone())?; let bin = match bin.object { BytecodeObject::Bytecode(_) => bin.object, diff --git a/cli/src/cmd/flatten.rs b/cli/src/cmd/forge/flatten.rs similarity index 98% rename from cli/src/cmd/flatten.rs rename to cli/src/cmd/forge/flatten.rs index b0dae09561a40..c56668f70a523 100644 --- a/cli/src/cmd/flatten.rs +++ b/cli/src/cmd/forge/flatten.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use ethers::solc::remappings::Remapping; -use crate::cmd::{build::BuildArgs, Cmd}; +use crate::cmd::{forge::build::BuildArgs, Cmd}; use clap::{Parser, ValueHint}; use foundry_config::Config; diff --git a/cli/src/cmd/fmt.rs b/cli/src/cmd/forge/fmt.rs similarity index 100% rename from cli/src/cmd/fmt.rs rename to cli/src/cmd/forge/fmt.rs diff --git a/cli/src/cmd/init.rs b/cli/src/cmd/forge/init.rs similarity index 95% rename from cli/src/cmd/init.rs rename to cli/src/cmd/forge/init.rs index 02c6e7c65abb6..4e8aabdd3f3d4 100644 --- a/cli/src/cmd/init.rs +++ b/cli/src/cmd/forge/init.rs @@ -1,14 +1,14 @@ //! init command use crate::{ - cmd::{install::install, Cmd}, + cmd::{forge::install::install, Cmd}, opts::forge::Dependency, utils::p_println, }; use clap::{Parser, ValueHint}; use foundry_config::Config; -use crate::cmd::{install::DependencyInstallOpts, remappings}; +use crate::cmd::forge::{install::DependencyInstallOpts, remappings}; use ansi_term::Colour; use ethers::solc::remappings::Remapping; use std::{ @@ -102,10 +102,13 @@ impl Cmd for InitArgs { // write the contract file let contract_path = src.join("Contract.sol"); - std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.sol"))?; + std::fs::write(contract_path, include_str!("../../../../assets/ContractTemplate.sol"))?; // write the tests let contract_path = test.join("Contract.t.sol"); - std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.t.sol"))?; + std::fs::write( + contract_path, + include_str!("../../../../assets/ContractTemplate.t.sol"), + )?; let dest = root.join(Config::FILE_NAME); if !dest.exists() { @@ -159,7 +162,7 @@ fn init_git_repo(root: &Path, no_commit: bool) -> eyre::Result<()> { if !is_git.success() { let gitignore_path = root.join(".gitignore"); - std::fs::write(gitignore_path, include_str!("../../../assets/.gitignoreTemplate"))?; + std::fs::write(gitignore_path, include_str!("../../../../assets/.gitignoreTemplate"))?; Command::new("git") .arg("init") diff --git a/cli/src/cmd/inspect.rs b/cli/src/cmd/forge/inspect.rs similarity index 98% rename from cli/src/cmd/inspect.rs rename to cli/src/cmd/forge/inspect.rs index aa9c80905880f..3a6c01db28e90 100644 --- a/cli/src/cmd/inspect.rs +++ b/cli/src/cmd/forge/inspect.rs @@ -2,7 +2,7 @@ use std::{fmt, str::FromStr}; use crate::{ cmd::{ - build::{self, BuildArgs}, + forge::build::{self, BuildArgs}, Cmd, }, opts::forge::CompilerArgs, @@ -156,7 +156,7 @@ impl Cmd for InspectArgs { // Build the project let project = modified_build_args.project()?; - let outcome = super::suppress_compile(&project)?; + let outcome = super::super::suppress_compile(&project)?; // Find the artifact let found_artifact = outcome.find(&contract); diff --git a/cli/src/cmd/install.rs b/cli/src/cmd/forge/install.rs similarity index 100% rename from cli/src/cmd/install.rs rename to cli/src/cmd/forge/install.rs diff --git a/cli/src/cmd/forge/mod.rs b/cli/src/cmd/forge/mod.rs new file mode 100644 index 0000000000000..6f0cf893d46b7 --- /dev/null +++ b/cli/src/cmd/forge/mod.rs @@ -0,0 +1,55 @@ +//! Subcommands for forge +//! +//! All subcommands should respect the `foundry_config::Config`. +//! If a subcommand accepts values that are supported by the `Config`, then the subcommand should +//! implement `figment::Provider` which allows the subcommand to override the config's defaults, see +//! [`foundry_config::Config`]. +//! +//! See [`BuildArgs`] for a reference implementation. +//! And [`RunArgs`] for how to merge `Providers`. +//! +//! # Example +//! +//! create a `clap` subcommand into a `figment::Provider` and integrate it in the +//! `foundry_config::Config`: +//! +//! ```rust +//! use crate::{cmd::build::BuildArgs, opts::evm::EvmArgs}; +//! use clap::Parser; +//! use foundry_config::{figment::Figment, *}; +//! +//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` +//! #[derive(Debug, Clone, Parser)] +//! pub struct MyArgs { +//! #[clap(flatten)] +//! evm_opts: EvmArgs, +//! #[clap(flatten)] +//! opts: BuildArgs, +//! } +//! +//! // add `Figment` and `Config` converters +//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); +//! let args = MyArgs::parse_from(["build"]); +//! +//! let figment: Figment = From::from(&args); +//! let evm_opts = figment.extract::().unwrap(); +//! +//! let config: Config = From::from(&args); +//! ``` + +pub mod bind; +pub mod build; +pub mod config; +pub mod create; +pub mod flatten; +pub mod fmt; +pub mod init; +pub mod inspect; +pub mod install; +pub mod remappings; +pub mod run; +pub mod snapshot; +pub mod test; +pub mod tree; +pub mod verify; +pub mod watch; diff --git a/cli/src/cmd/remappings.rs b/cli/src/cmd/forge/remappings.rs similarity index 100% rename from cli/src/cmd/remappings.rs rename to cli/src/cmd/forge/remappings.rs diff --git a/cli/src/cmd/run.rs b/cli/src/cmd/forge/run.rs similarity index 99% rename from cli/src/cmd/run.rs rename to cli/src/cmd/forge/run.rs index ca96d9630dccc..d608bf3ad9966 100644 --- a/cli/src/cmd/run.rs +++ b/cli/src/cmd/forge/run.rs @@ -1,5 +1,5 @@ use crate::{ - cmd::{build::BuildArgs, compile_files, Cmd}, + cmd::{compile_files, forge::build::BuildArgs, Cmd}, opts::evm::EvmArgs, }; use ansi_term::Colour; diff --git a/cli/src/cmd/snapshot.rs b/cli/src/cmd/forge/snapshot.rs similarity index 99% rename from cli/src/cmd/snapshot.rs rename to cli/src/cmd/forge/snapshot.rs index de09ae3bc4006..bafa2f4af8be4 100644 --- a/cli/src/cmd/snapshot.rs +++ b/cli/src/cmd/forge/snapshot.rs @@ -1,8 +1,10 @@ //! Snapshot command use crate::cmd::{ - test, - test::{Test, TestOutcome}, + forge::{ + test, + test::{Test, TestOutcome}, + }, Cmd, }; use ansi_term::Colour; diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/forge/test.rs similarity index 99% rename from cli/src/cmd/test.rs rename to cli/src/cmd/forge/test.rs index d52177dcf6805..33a9eca65c079 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/forge/test.rs @@ -1,6 +1,9 @@ //! Test command use crate::{ - cmd::{build::BuildArgs, run::RunArgs, Cmd}, + cmd::{ + forge::{build::BuildArgs, run::RunArgs}, + Cmd, + }, opts::evm::EvmArgs, utils, }; @@ -174,7 +177,7 @@ impl Cmd for TestArgs { // Set up the project let project = config.project()?; - let output = super::compile(&project, false, false)?; + let output = super::super::compile(&project, false, false)?; // Determine print verbosity and executor verbosity let verbosity = evm_opts.verbosity; diff --git a/cli/src/cmd/tree.rs b/cli/src/cmd/forge/tree.rs similarity index 95% rename from cli/src/cmd/tree.rs rename to cli/src/cmd/forge/tree.rs index 49a269250c953..2e7b4b540c308 100644 --- a/cli/src/cmd/tree.rs +++ b/cli/src/cmd/forge/tree.rs @@ -1,6 +1,6 @@ //! tree command -use crate::cmd::{build::BuildArgs, Cmd}; +use crate::cmd::{forge::build::BuildArgs, Cmd}; use clap::Parser; use ethers::solc::Graph; use foundry_config::Config; diff --git a/cli/src/cmd/verify.rs b/cli/src/cmd/forge/verify.rs similarity index 98% rename from cli/src/cmd/verify.rs rename to cli/src/cmd/forge/verify.rs index 8636a124da195..dc2c6d199c492 100644 --- a/cli/src/cmd/verify.rs +++ b/cli/src/cmd/forge/verify.rs @@ -1,7 +1,7 @@ //! Verify contract source on etherscan use crate::{ - cmd::{build::BuildArgs, flatten::CoreFlattenArgs}, + cmd::forge::{build::BuildArgs, flatten::CoreFlattenArgs}, opts::forge::ContractInfo, }; use clap::Parser; diff --git a/cli/src/cmd/watch.rs b/cli/src/cmd/forge/watch.rs similarity index 99% rename from cli/src/cmd/watch.rs rename to cli/src/cmd/forge/watch.rs index fa31c26fcd47d..03a0aff910f5a 100644 --- a/cli/src/cmd/watch.rs +++ b/cli/src/cmd/forge/watch.rs @@ -1,7 +1,7 @@ //! Watch mode support use crate::{ - cmd::{build::BuildArgs, test::TestArgs}, + cmd::forge::{build::BuildArgs, test::TestArgs}, utils::{self, FoundryPathExt}, }; use clap::Parser; diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index 312c90f6290e4..174119915d644 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -1,260 +1,13 @@ -//! Subcommands for forge +//! Subcommands //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should //! implement `figment::Provider` which allows the subcommand to override the config's defaults, see //! [`foundry_config::Config`]. -//! -//! See [`BuildArgs`] for a reference implementation. -//! And [`RunArgs`] for how to merge `Providers`. -//! -//! # Example -//! -//! create a `clap` subcommand into a `figment::Provider` and integrate it in the -//! `foundry_config::Config`: -//! -//! ```rust -//! use crate::{cmd::build::BuildArgs, opts::evm::EvmArgs}; -//! use clap::Parser; -//! use foundry_config::{figment::Figment, *}; -//! -//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` -//! #[derive(Debug, Clone, Parser)] -//! pub struct MyArgs { -//! #[clap(flatten)] -//! evm_opts: EvmArgs, -//! #[clap(flatten)] -//! opts: BuildArgs, -//! } -//! -//! // add `Figment` and `Config` converters -//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); -//! let args = MyArgs::parse_from(["build"]); -//! -//! let figment: Figment = From::from(&args); -//! let evm_opts = figment.extract::().unwrap(); -//! -//! let config: Config = From::from(&args); -//! ``` - -pub mod bind; -pub mod build; -pub mod config; -pub mod create; -pub mod flatten; -pub mod fmt; -pub mod init; -pub mod inspect; -pub mod install; -pub mod remappings; -pub mod run; -pub mod snapshot; -pub mod test; -pub mod tree; -pub mod verify; -pub mod watch; - -use crate::{opts::forge::ContractInfo, term}; -use ethers::{ - abi::Abi, - prelude::{ - artifacts::{CompactBytecode, CompactDeployedBytecode}, - report::NoReporter, - }, - solc::cache::SolFilesCache, -}; -use std::{collections::BTreeMap, path::PathBuf}; - -/// Common trait for all cli commands -pub trait Cmd: clap::Parser + Sized { - type Output; - fn run(self) -> eyre::Result; -} - -use ethers::solc::{artifacts::CompactContractBytecode, Artifact, Project, ProjectCompileOutput}; - -use foundry_utils::to_table; - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -pub fn compile( - project: &Project, - print_names: bool, - print_sizes: bool, -) -> eyre::Result { - if !project.paths.sources.exists() { - eyre::bail!( - r#"no contracts to compile, contracts folder "{}" does not exist. -Check the configured workspace settings: -{} -If you are in a subdirectory in a Git repository, try adding `--root .`"#, - project.paths.sources.display(), - project.paths - ); - } - - let output = term::with_spinner_reporter(|| project.compile())?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } else if output.is_unchanged() { - println!("No files changed, compilation skipped"); - } else { - // print the compiler output / warnings - println!("{}", output); - - // print any sizes or names - if print_names { - let compiled_contracts = output.compiled_contracts_by_compiler_version(); - for (version, contracts) in compiled_contracts.into_iter() { - println!( - " compiler version: {}.{}.{}", - version.major, version.minor, version.patch - ); - for (name, _) in contracts { - println!(" - {}", name); - } - } - } - if print_sizes { - // add extra newline if names were already printed - if print_names { - println!(); - } - let compiled_contracts = output.compiled_contracts_by_compiler_version(); - let mut sizes = BTreeMap::new(); - for (_, contracts) in compiled_contracts.into_iter() { - for (name, contract) in contracts { - let size = contract - .get_bytecode_bytes() - .map(|bytes| bytes.0.len()) - .unwrap_or_default(); - sizes.insert(name, size); - } - } - let json = serde_json::to_value(&sizes)?; - println!("name size (bytes)"); - println!("-----------------------------"); - println!("{}", to_table(json)); - } - } - - Ok(output) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// Doesn't print anything to stdout, thus is "suppressed". -pub fn suppress_compile(project: &Project) -> eyre::Result { - if !project.paths.sources.exists() { - eyre::bail!( - r#"no contracts to compile, contracts folder "{}" does not exist. -Check the configured workspace settings: -{} -If you are in a subdirectory in a Git repository, try adding `--root .`"#, - project.paths.sources.display(), - project.paths - ); - } - - let output = ethers::solc::report::with_scoped( - ðers::solc::report::Report::new(NoReporter::default()), - || project.compile(), - )?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - - Ok(output) -} - -/// Compile a set of files not necessarily included in the `project`'s source dir -pub fn compile_files(project: &Project, files: Vec) -> eyre::Result { - let output = term::with_spinner_reporter(|| project.compile_files(files))?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - println!("{}", output); - Ok(output) -} - -/// Given a project and its compiled artifacts, proceeds to return the ABI, Bytecode and -/// Runtime Bytecode of the given contract. -pub fn read_artifact( - project: &Project, - compiled: ProjectCompileOutput, - contract: ContractInfo, -) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - Ok(match contract.path { - Some(path) => get_artifact_from_path(project, path, contract.name)?, - None => get_artifact_from_name(contract, compiled)?, - }) -} - -/// Helper function for finding a contract by ContractName -// TODO: Is there a better / more ergonomic way to get the artifacts given a project and a -// contract name? -fn get_artifact_from_name( - contract: ContractInfo, - compiled: ProjectCompileOutput, -) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - let mut has_found_contract = false; - let mut contract_artifact = None; - - for (artifact_id, artifact) in compiled.into_artifacts() { - if artifact_id.name == contract.name { - if has_found_contract { - eyre::bail!("contract with duplicate name. pass path") - } - has_found_contract = true; - contract_artifact = Some(artifact); - } - } - - Ok(match contract_artifact { - Some(artifact) => ( - artifact - .abi - .map(Into::into) - .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract.name)))?, - artifact.bytecode.ok_or_else(|| { - eyre::Error::msg(format!("bytecode not found for {}", contract.name)) - })?, - artifact.deployed_bytecode.ok_or_else(|| { - eyre::Error::msg(format!("bytecode not found for {}", contract.name)) - })?, - ), - None => { - eyre::bail!("could not find artifact") - } - }) -} - -/// Find using src/ContractSource.sol:ContractName -fn get_artifact_from_path( - project: &Project, - contract_path: String, - contract_name: String, -) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - // Get sources from the requested location - let abs_path = dunce::canonicalize(PathBuf::from(contract_path))?; - - let cache = SolFilesCache::read_joined(&project.paths)?; - // Read the artifact from disk - let artifact: CompactContractBytecode = cache.read_artifact(abs_path, &contract_name)?; +pub mod cast; +pub mod forge; - Ok(( - artifact - .abi - .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract_name)))?, - artifact - .bytecode - .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, - artifact - .deployed_bytecode - .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, - )) -} +// Re-export our shared utilities +mod utils; +pub use utils::*; diff --git a/cli/src/cmd/utils.rs b/cli/src/cmd/utils.rs new file mode 100644 index 0000000000000..ad5e81cc50ff5 --- /dev/null +++ b/cli/src/cmd/utils.rs @@ -0,0 +1,204 @@ +use crate::{opts::forge::ContractInfo, term}; +use ethers::{ + abi::Abi, + prelude::{ + artifacts::{CompactBytecode, CompactDeployedBytecode}, + report::NoReporter, + }, + solc::cache::SolFilesCache, +}; +use std::{collections::BTreeMap, path::PathBuf}; + +/// Common trait for all cli commands +pub trait Cmd: clap::Parser + Sized { + type Output; + fn run(self) -> eyre::Result; +} + +use ethers::solc::{artifacts::CompactContractBytecode, Artifact, Project, ProjectCompileOutput}; + +use foundry_utils::to_table; + +/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether +/// compilation was successful or if there was a cache hit. +pub fn compile( + project: &Project, + print_names: bool, + print_sizes: bool, +) -> eyre::Result { + if !project.paths.sources.exists() { + eyre::bail!( + r#"no contracts to compile, contracts folder "{}" does not exist. +Check the configured workspace settings: +{} +If you are in a subdirectory in a Git repository, try adding `--root .`"#, + project.paths.sources.display(), + project.paths + ); + } + + let output = term::with_spinner_reporter(|| project.compile())?; + + if output.has_compiler_errors() { + eyre::bail!(output.to_string()) + } else if output.is_unchanged() { + println!("No files changed, compilation skipped"); + } else { + // print the compiler output / warnings + println!("{}", output); + + // print any sizes or names + if print_names { + let compiled_contracts = output.compiled_contracts_by_compiler_version(); + for (version, contracts) in compiled_contracts.into_iter() { + println!( + " compiler version: {}.{}.{}", + version.major, version.minor, version.patch + ); + for (name, _) in contracts { + println!(" - {}", name); + } + } + } + if print_sizes { + // add extra newline if names were already printed + if print_names { + println!(); + } + let compiled_contracts = output.compiled_contracts_by_compiler_version(); + let mut sizes = BTreeMap::new(); + for (_, contracts) in compiled_contracts.into_iter() { + for (name, contract) in contracts { + let size = contract + .get_bytecode_bytes() + .map(|bytes| bytes.0.len()) + .unwrap_or_default(); + sizes.insert(name, size); + } + } + let json = serde_json::to_value(&sizes)?; + println!("name size (bytes)"); + println!("-----------------------------"); + println!("{}", to_table(json)); + } + } + + Ok(output) +} + +/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether +/// compilation was successful or if there was a cache hit. +/// Doesn't print anything to stdout, thus is "suppressed". +pub fn suppress_compile(project: &Project) -> eyre::Result { + if !project.paths.sources.exists() { + eyre::bail!( + r#"no contracts to compile, contracts folder "{}" does not exist. +Check the configured workspace settings: +{} +If you are in a subdirectory in a Git repository, try adding `--root .`"#, + project.paths.sources.display(), + project.paths + ); + } + + let output = ethers::solc::report::with_scoped( + ðers::solc::report::Report::new(NoReporter::default()), + || project.compile(), + )?; + + if output.has_compiler_errors() { + eyre::bail!(output.to_string()) + } + + Ok(output) +} + +/// Compile a set of files not necessarily included in the `project`'s source dir +pub fn compile_files(project: &Project, files: Vec) -> eyre::Result { + let output = term::with_spinner_reporter(|| project.compile_files(files))?; + + if output.has_compiler_errors() { + eyre::bail!(output.to_string()) + } + println!("{}", output); + Ok(output) +} + +/// Given a project and its compiled artifacts, proceeds to return the ABI, Bytecode and +/// Runtime Bytecode of the given contract. +pub fn read_artifact( + project: &Project, + compiled: ProjectCompileOutput, + contract: ContractInfo, +) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { + Ok(match contract.path { + Some(path) => get_artifact_from_path(project, path, contract.name)?, + None => get_artifact_from_name(contract, compiled)?, + }) +} + +/// Helper function for finding a contract by ContractName +// TODO: Is there a better / more ergonomic way to get the artifacts given a project and a +// contract name? +fn get_artifact_from_name( + contract: ContractInfo, + compiled: ProjectCompileOutput, +) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { + let mut has_found_contract = false; + let mut contract_artifact = None; + + for (artifact_id, artifact) in compiled.into_artifacts() { + if artifact_id.name == contract.name { + if has_found_contract { + eyre::bail!("contract with duplicate name. pass path") + } + has_found_contract = true; + contract_artifact = Some(artifact); + } + } + + Ok(match contract_artifact { + Some(artifact) => ( + artifact + .abi + .map(Into::into) + .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract.name)))?, + artifact.bytecode.ok_or_else(|| { + eyre::Error::msg(format!("bytecode not found for {}", contract.name)) + })?, + artifact.deployed_bytecode.ok_or_else(|| { + eyre::Error::msg(format!("bytecode not found for {}", contract.name)) + })?, + ), + None => { + eyre::bail!("could not find artifact") + } + }) +} + +/// Find using src/ContractSource.sol:ContractName +fn get_artifact_from_path( + project: &Project, + contract_path: String, + contract_name: String, +) -> eyre::Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { + // Get sources from the requested location + let abs_path = dunce::canonicalize(PathBuf::from(contract_path))?; + + let cache = SolFilesCache::read_joined(&project.paths)?; + + // Read the artifact from disk + let artifact: CompactContractBytecode = cache.read_artifact(abs_path, &contract_name)?; + + Ok(( + artifact + .abi + .ok_or_else(|| eyre::Error::msg(format!("abi not found for {}", contract_name)))?, + artifact + .bytecode + .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, + artifact + .deployed_bytecode + .ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", contract_name)))?, + )) +} diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 1b2aeb251b96f..2ed1080e9959f 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -3,7 +3,7 @@ mod opts; mod term; mod utils; -use crate::cmd::{watch, Cmd}; +use crate::cmd::{forge::watch, Cmd}; use ethers::solc::{Project, ProjectPathsConfig}; use opts::forge::{Dependency, Opts, Subcommands}; @@ -31,7 +31,7 @@ fn main() -> eyre::Result<()> { } Subcommands::Build(cmd) => { if cmd.is_watch() { - utils::block_on(crate::cmd::watch::watch_build(cmd))?; + utils::block_on(crate::cmd::forge::watch::watch_build(cmd))?; } else { cmd.run()?; } @@ -40,10 +40,10 @@ fn main() -> eyre::Result<()> { cmd.run()?; } Subcommands::VerifyContract(args) => { - utils::block_on(cmd::verify::run_verify(&args))?; + utils::block_on(cmd::forge::verify::run_verify(&args))?; } Subcommands::VerifyCheck(args) => { - utils::block_on(cmd::verify::run_verify_check(&args))?; + utils::block_on(cmd::forge::verify::run_verify_check(&args))?; } Subcommands::Create(cmd) => { cmd.run()?; diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 12c3bb744aa6c..e61805889b9b5 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -7,7 +7,7 @@ use ethers::{ }; use super::{ClapChain, EthereumOpts, Wallet}; -use crate::utils::parse_u256; +use crate::{cmd::cast::find_block::FindBlockArgs, utils::parse_u256}; #[derive(Debug, Subcommand)] #[clap(about = "Perform Ethereum RPC calls from the comfort of your command line.")] @@ -506,12 +506,7 @@ pub enum Subcommands { name = "find-block", about = "Prints the block number closes to the provided timestamp" )] - FindBlock { - #[clap(help = "The UNIX timestamp to search for (in seconds)")] - timestamp: u64, - #[clap(long, env = "ETH_RPC_URL")] - rpc_url: String, - }, + FindBlock(FindBlockArgs), #[clap(about = "Generate shell completions script")] Completions { #[clap(arg_enum)] diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index be8a667beea7d..50849e8083006 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -3,7 +3,7 @@ use clap::{Parser, Subcommand, ValueHint}; use ethers::solc::{artifacts::output_selection::ContractOutputSelection, EvmVersion}; use std::{path::PathBuf, str::FromStr}; -use crate::cmd::{ +use crate::cmd::forge::{ bind::BindArgs, build::BuildArgs, config,