From abc27185275205a43a1fa944b97fb6c16df3fac6 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:19:31 +0000 Subject: [PATCH 1/5] feat(cast storage): allow ugly printing of layout Prior to this change, `cast storage $ADDRESS --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY` always provided a prettified output. This change adds a `--pretty` flag to `cast storage` which defaults to `true` thus retaining backwards compatibility. Passing `--pretty=false` to `cast storage` results in the json output of the storage layout being produced instead. --- crates/cast/bin/cmd/storage.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 9fca4172e345..4da6d2b415e8 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -63,6 +63,10 @@ pub struct StorageArgs { #[command(flatten)] build: CoreBuildArgs, + + /// Pretty print the layout, if a slot is not provided. Defaults to true. + #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] + pub pretty: bool, } impl_figment_convert_cast!(StorageArgs); @@ -85,7 +89,7 @@ impl StorageArgs { pub async fn run(self) -> Result<()> { let config = Config::from(&self); - let Self { address, slot, block, build, .. } = self; + let Self { address, slot, block, build, pretty, .. } = self; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; @@ -114,7 +118,7 @@ impl StorageArgs { artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) }); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage(provider, address, block, artifact, true).await; + return fetch_and_print_storage(provider, address, block, artifact, pretty).await; } } @@ -180,7 +184,7 @@ impl StorageArgs { // Clear temp directory root.close()?; - fetch_and_print_storage(provider, address, block, artifact, true).await + fetch_and_print_storage(provider, address, block, artifact, pretty).await } } @@ -256,7 +260,7 @@ async fn fetch_storage_slots, T: Transport + Clone>( fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { if !pretty { sh_println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?)?; - return Ok(()) + return Ok(()); } let mut table = Table::new(); From 87312ea38bd5c69a983a62c32ecfb68a08eded0b Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:32:56 +0000 Subject: [PATCH 2/5] fix: remove default value from help text The default value is accessible via `cast storage --help` --- crates/cast/bin/cmd/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 4da6d2b415e8..72747122d4a3 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -64,7 +64,7 @@ pub struct StorageArgs { #[command(flatten)] build: CoreBuildArgs, - /// Pretty print the layout, if a slot is not provided. Defaults to true. + /// Pretty print the layout, if a slot is not provided. #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] pub pretty: bool, } From 89c895fe36bd67337da1d20b4931d2d23b5a1423 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:09:07 +0000 Subject: [PATCH 3/5] fix(cast storage): provide output json path --- crates/cast/bin/cmd/storage.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 72747122d4a3..bfdb4a596400 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -31,6 +31,7 @@ use foundry_config::{ impl_figment_convert_cast, Config, }; use semver::Version; +use std::path::PathBuf; use std::str::FromStr; /// The minimum Solc version for outputting storage layouts. @@ -45,7 +46,7 @@ pub struct StorageArgs { #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, - /// The storage slot number. + /// The storage slot number. If not provided, it gets the full storage layout. #[arg(value_parser = parse_slot)] slot: Option, @@ -64,9 +65,11 @@ pub struct StorageArgs { #[command(flatten)] build: CoreBuildArgs, - /// Pretty print the layout, if a slot is not provided. - #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] - pub pretty: bool, + /// The path of the file in which the storage layout is saved. Only works if a slot + /// is not provided. The formatted (pretty) storage layout is shown even if this value + /// is provided. + #[arg(long, default_value = "", value_name = "output_json")] + pub output_json: String, } impl_figment_convert_cast!(StorageArgs); @@ -89,7 +92,7 @@ impl StorageArgs { pub async fn run(self) -> Result<()> { let config = Config::from(&self); - let Self { address, slot, block, build, pretty, .. } = self; + let Self { address, slot, block, build, output_json, .. } = self; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; @@ -118,7 +121,15 @@ impl StorageArgs { artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) }); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage(provider, address, block, artifact, pretty).await; + return fetch_and_print_storage( + provider, + address, + block, + artifact, + true, + output_json, + ) + .await; } } @@ -184,7 +195,7 @@ impl StorageArgs { // Clear temp directory root.close()?; - fetch_and_print_storage(provider, address, block, artifact, pretty).await + fetch_and_print_storage(provider, address, block, artifact, true, output_json).await } } @@ -225,6 +236,7 @@ async fn fetch_and_print_storage, T: Transport + Clon block: Option, artifact: &ConfigurableContractArtifact, pretty: bool, + output_json: String, ) -> Result<()> { if is_storage_layout_empty(&artifact.storage_layout) { sh_warn!("Storage layout is empty.")?; @@ -232,6 +244,10 @@ async fn fetch_and_print_storage, T: Transport + Clon } else { let layout = artifact.storage_layout.as_ref().unwrap().clone(); let values = fetch_storage_slots(provider, address, block, &layout).await?; + let output_path = PathBuf::from(output_json); + if !output_path.as_os_str().is_empty() { + foundry_common::fs::write_json_file(&output_path, &layout)?; + } print_storage(layout, values, pretty) } } From 3e2f637408ed74af78c280d838fa91ad329cd65b Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:22:54 +0000 Subject: [PATCH 4/5] test(cast): add storage_layout_simple_json test --- crates/cast/tests/cli/main.rs | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index bec345b1710c..a4ec24c166c6 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1134,6 +1134,57 @@ casttest!(storage_layout_simple, |_prj, cmd| { "#]]); }); +// +casttest!(storage_layout_simple_json, |_prj, cmd| { + cmd.args([ + "--json", + "storage", + "--rpc-url", + next_rpc_endpoint(NamedChain::Mainnet).as_str(), + "--block", + "21034138", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + ]) + .assert_success() + .stdout_eq(str![[r#" +{ + "storage": [ + { + "astId": 7, + "contract": "contracts/Create2Deployer.sol:Create2Deployer", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 122, + "contract": "contracts/Create2Deployer.sol:Create2Deployer", + "label": "_paused", + "offset": 20, + "slot": "0", + "type": "t_bool" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + } + } +} + +"#]]); +}); + // casttest!(storage_layout_complex, |_prj, cmd| { cmd.args([ From bcc1665b81c2e41ace8ea44a9dee51856d9237d4 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:26:06 +0000 Subject: [PATCH 5/5] fix(cast storage): use `--json` flag to ugly print --- crates/cast/bin/cmd/storage.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index bfdb4a596400..3a1d43f0684d 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -17,6 +17,7 @@ use foundry_common::{ abi::find_source, compile::{etherscan_project, ProjectCompiler}, ens::NameOrAddress, + shell, }; use foundry_compilers::{ artifacts::{ConfigurableContractArtifact, StorageLayout}, @@ -31,7 +32,6 @@ use foundry_config::{ impl_figment_convert_cast, Config, }; use semver::Version; -use std::path::PathBuf; use std::str::FromStr; /// The minimum Solc version for outputting storage layouts. @@ -64,12 +64,6 @@ pub struct StorageArgs { #[command(flatten)] build: CoreBuildArgs, - - /// The path of the file in which the storage layout is saved. Only works if a slot - /// is not provided. The formatted (pretty) storage layout is shown even if this value - /// is provided. - #[arg(long, default_value = "", value_name = "output_json")] - pub output_json: String, } impl_figment_convert_cast!(StorageArgs); @@ -92,7 +86,7 @@ impl StorageArgs { pub async fn run(self) -> Result<()> { let config = Config::from(&self); - let Self { address, slot, block, build, output_json, .. } = self; + let Self { address, slot, block, build, .. } = self; let provider = utils::get_provider(&config)?; let address = address.resolve(&provider).await?; @@ -126,8 +120,7 @@ impl StorageArgs { address, block, artifact, - true, - output_json, + !shell::is_json(), ) .await; } @@ -195,7 +188,7 @@ impl StorageArgs { // Clear temp directory root.close()?; - fetch_and_print_storage(provider, address, block, artifact, true, output_json).await + fetch_and_print_storage(provider, address, block, artifact, !shell::is_json()).await } } @@ -236,7 +229,6 @@ async fn fetch_and_print_storage, T: Transport + Clon block: Option, artifact: &ConfigurableContractArtifact, pretty: bool, - output_json: String, ) -> Result<()> { if is_storage_layout_empty(&artifact.storage_layout) { sh_warn!("Storage layout is empty.")?; @@ -244,10 +236,6 @@ async fn fetch_and_print_storage, T: Transport + Clon } else { let layout = artifact.storage_layout.as_ref().unwrap().clone(); let values = fetch_storage_slots(provider, address, block, &layout).await?; - let output_path = PathBuf::from(output_json); - if !output_path.as_os_str().is_empty() { - foundry_common::fs::write_json_file(&output_path, &layout)?; - } print_storage(layout, values, pretty) } } @@ -276,7 +264,7 @@ async fn fetch_storage_slots, T: Transport + Clone>( fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { if !pretty { sh_println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?)?; - return Ok(()); + return Ok(()) } let mut table = Table::new();