Skip to content

Commit 7e5635d

Browse files
MaxMustermann2zerosnacks
authored andcommitted
fix(cast storage): respect --json for layout (foundry-rs#9332)
* 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. * fix: remove default value from help text The default value is accessible via `cast storage --help` * fix(cast storage): provide output json path * test(cast): add storage_layout_simple_json test * fix(cast storage): use `--json` flag to ugly print * fix(cast storage): include values in json mode * fix(cast-storage): quiet compilation in all cases * chore: cargo clippy * use fixtures, assert JSON * only quiet if JSON mode, avoid unnecessary warning (if you pass an API key you already expect to fetch remote, very likely default) --------- Co-authored-by: zerosnacks <[email protected]> Co-authored-by: zerosnacks <[email protected]>
1 parent b963ec2 commit 7e5635d

File tree

4 files changed

+503
-9
lines changed

4 files changed

+503
-9
lines changed

crates/cast/bin/cmd/storage.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use foundry_common::{
1717
abi::find_source,
1818
compile::{etherscan_project, ProjectCompiler},
1919
ens::NameOrAddress,
20+
shell,
2021
};
2122
use foundry_compilers::{
2223
artifacts::{ConfigurableContractArtifact, StorageLayout},
@@ -31,6 +32,7 @@ use foundry_config::{
3132
impl_figment_convert_cast, Config,
3233
};
3334
use semver::Version;
35+
use serde::{Deserialize, Serialize};
3436
use std::str::FromStr;
3537

3638
/// The minimum Solc version for outputting storage layouts.
@@ -45,7 +47,7 @@ pub struct StorageArgs {
4547
#[arg(value_parser = NameOrAddress::from_str)]
4648
address: NameOrAddress,
4749

48-
/// The storage slot number.
50+
/// The storage slot number. If not provided, it gets the full storage layout.
4951
#[arg(value_parser = parse_slot)]
5052
slot: Option<B256>,
5153

@@ -109,19 +111,22 @@ impl StorageArgs {
109111
if project.paths.has_input_files() {
110112
// Find in artifacts and pretty print
111113
add_storage_layout_output(&mut project);
112-
let out = ProjectCompiler::new().compile(&project)?;
114+
let out = ProjectCompiler::new().quiet(shell::is_json()).compile(&project)?;
113115
let artifact = out.artifacts().find(|(_, artifact)| {
114116
artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code)
115117
});
116118
if let Some((_, artifact)) = artifact {
117-
return fetch_and_print_storage(provider, address, block, artifact, true).await;
119+
return fetch_and_print_storage(
120+
provider,
121+
address,
122+
block,
123+
artifact,
124+
!shell::is_json(),
125+
)
126+
.await;
118127
}
119128
}
120129

121-
// Not a forge project or artifact not found
122-
// Get code from Etherscan
123-
sh_warn!("No matching artifacts found, fetching source code from Etherscan...")?;
124-
125130
if !self.etherscan.has_key() {
126131
eyre::bail!("You must provide an Etherscan API key if you're fetching a remote contract's storage.");
127132
}
@@ -180,7 +185,7 @@ impl StorageArgs {
180185
// Clear temp directory
181186
root.close()?;
182187

183-
fetch_and_print_storage(provider, address, block, artifact, true).await
188+
fetch_and_print_storage(provider, address, block, artifact, !shell::is_json()).await
184189
}
185190
}
186191

@@ -215,6 +220,14 @@ impl StorageValue {
215220
}
216221
}
217222

223+
/// Represents the storage layout of a contract and its values.
224+
#[derive(Clone, Debug, Serialize, Deserialize)]
225+
struct StorageReport {
226+
#[serde(flatten)]
227+
layout: StorageLayout,
228+
values: Vec<B256>,
229+
}
230+
218231
async fn fetch_and_print_storage<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
219232
provider: P,
220233
address: Address,
@@ -255,7 +268,22 @@ async fn fetch_storage_slots<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
255268

256269
fn print_storage(layout: StorageLayout, values: Vec<StorageValue>, pretty: bool) -> Result<()> {
257270
if !pretty {
258-
sh_println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?)?;
271+
let values: Vec<_> = layout
272+
.storage
273+
.iter()
274+
.zip(&values)
275+
.map(|(slot, storage_value)| {
276+
let storage_type = layout.types.get(&slot.storage_type);
277+
storage_value.value(
278+
slot.offset,
279+
storage_type.and_then(|t| t.number_of_bytes.parse::<usize>().ok()),
280+
)
281+
})
282+
.collect();
283+
sh_println!(
284+
"{}",
285+
serde_json::to_string_pretty(&serde_json::to_value(StorageReport { layout, values })?)?
286+
)?;
259287
return Ok(())
260288
}
261289

crates/cast/tests/cli/main.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,23 @@ casttest!(storage_layout_simple, |_prj, cmd| {
11311131
"#]]);
11321132
});
11331133

1134+
// <https://github.com/foundry-rs/foundry/pull/9332>
1135+
casttest!(storage_layout_simple_json, |_prj, cmd| {
1136+
cmd.args([
1137+
"storage",
1138+
"--rpc-url",
1139+
next_rpc_endpoint(NamedChain::Mainnet).as_str(),
1140+
"--block",
1141+
"21034138",
1142+
"--etherscan-api-key",
1143+
next_mainnet_etherscan_api_key().as_str(),
1144+
"0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2",
1145+
"--json",
1146+
])
1147+
.assert_success()
1148+
.stdout_eq(file!["../fixtures/storage_layout_simple.json": Json]);
1149+
});
1150+
11341151
// <https://github.com/foundry-rs/foundry/issues/6319>
11351152
casttest!(storage_layout_complex, |_prj, cmd| {
11361153
cmd.args([
@@ -1164,6 +1181,22 @@ casttest!(storage_layout_complex, |_prj, cmd| {
11641181
"#]]);
11651182
});
11661183

1184+
casttest!(storage_layout_complex_json, |_prj, cmd| {
1185+
cmd.args([
1186+
"storage",
1187+
"--rpc-url",
1188+
next_rpc_endpoint(NamedChain::Mainnet).as_str(),
1189+
"--block",
1190+
"21034138",
1191+
"--etherscan-api-key",
1192+
next_mainnet_etherscan_api_key().as_str(),
1193+
"0xBA12222222228d8Ba445958a75a0704d566BF2C8",
1194+
"--json",
1195+
])
1196+
.assert_success()
1197+
.stdout_eq(file!["../fixtures/storage_layout_complex.json": Json]);
1198+
});
1199+
11671200
casttest!(balance, |_prj, cmd| {
11681201
let rpc = next_http_rpc_endpoint();
11691202
let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7";

0 commit comments

Comments
 (0)