Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cast storage): respect --json for layout #9332

Merged
merged 15 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions crates/cast/bin/cmd/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use foundry_common::{
abi::find_source,
compile::{etherscan_project, ProjectCompiler},
ens::NameOrAddress,
shell,
};
use foundry_compilers::{
artifacts::{ConfigurableContractArtifact, StorageLayout},
Expand All @@ -31,6 +32,7 @@ use foundry_config::{
impl_figment_convert_cast, Config,
};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

/// The minimum Solc version for outputting storage layouts.
Expand All @@ -45,7 +47,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<B256>,

Expand Down Expand Up @@ -109,19 +111,22 @@ impl StorageArgs {
if project.paths.has_input_files() {
// Find in artifacts and pretty print
add_storage_layout_output(&mut project);
let out = ProjectCompiler::new().compile(&project)?;
let out = ProjectCompiler::new().quiet(shell::is_json()).compile(&project)?;
let artifact = out.artifacts().find(|(_, artifact)| {
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,
!shell::is_json(),
)
.await;
}
}

// Not a forge project or artifact not found
// Get code from Etherscan
sh_warn!("No matching artifacts found, fetching source code from Etherscan...")?;

if !self.etherscan.has_key() {
eyre::bail!("You must provide an Etherscan API key if you're fetching a remote contract's storage.");
}
Expand Down Expand Up @@ -180,7 +185,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, !shell::is_json()).await
}
}

Expand Down Expand Up @@ -215,6 +220,14 @@ impl StorageValue {
}
}

/// Represents the storage layout of a contract and its values.
#[derive(Clone, Debug, Serialize, Deserialize)]
struct StorageReport {
#[serde(flatten)]
layout: StorageLayout,
values: Vec<B256>,
}

async fn fetch_and_print_storage<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
provider: P,
address: Address,
Expand Down Expand Up @@ -255,7 +268,22 @@ async fn fetch_storage_slots<P: Provider<T, AnyNetwork>, T: Transport + Clone>(

fn print_storage(layout: StorageLayout, values: Vec<StorageValue>, pretty: bool) -> Result<()> {
if !pretty {
sh_println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?)?;
let values: Vec<_> = layout
.storage
.iter()
.zip(&values)
.map(|(slot, storage_value)| {
let storage_type = layout.types.get(&slot.storage_type);
storage_value.value(
slot.offset,
storage_type.and_then(|t| t.number_of_bytes.parse::<usize>().ok()),
)
})
.collect();
sh_println!(
"{}",
serde_json::to_string_pretty(&serde_json::to_value(StorageReport { layout, values })?)?
)?;
return Ok(())
}

Expand Down
33 changes: 33 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,23 @@ casttest!(storage_layout_simple, |_prj, cmd| {
"#]]);
});

// <https://github.com/foundry-rs/foundry/pull/9332>
casttest!(storage_layout_simple_json, |_prj, cmd| {
cmd.args([
"storage",
"--rpc-url",
next_rpc_endpoint(NamedChain::Mainnet).as_str(),
"--block",
"21034138",
"--etherscan-api-key",
next_mainnet_etherscan_api_key().as_str(),
"0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2",
"--json",
])
.assert_success()
.stdout_eq(file!["../fixtures/storage_layout_simple.json": Json]);
});

// <https://github.com/foundry-rs/foundry/issues/6319>
casttest!(storage_layout_complex, |_prj, cmd| {
cmd.args([
Expand Down Expand Up @@ -1164,6 +1181,22 @@ casttest!(storage_layout_complex, |_prj, cmd| {
"#]]);
});

casttest!(storage_layout_complex_json, |_prj, cmd| {
cmd.args([
"storage",
"--rpc-url",
next_rpc_endpoint(NamedChain::Mainnet).as_str(),
"--block",
"21034138",
"--etherscan-api-key",
next_mainnet_etherscan_api_key().as_str(),
"0xBA12222222228d8Ba445958a75a0704d566BF2C8",
"--json",
])
.assert_success()
.stdout_eq(file!["../fixtures/storage_layout_complex.json": Json]);
});

casttest!(balance, |_prj, cmd| {
let rpc = next_http_rpc_endpoint();
let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7";
Expand Down
Loading
Loading