diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index e13eed21c29f8..4538512f93c19 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -338,17 +338,16 @@ pub async fn run_command(args: CastArgs) -> Result<()> { Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await? )? } - CastSubcommand::Block { block, full, field, raw, rpc } => { + CastSubcommand::Block { block, full, fields, raw, rpc } => { let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - // Can use either --raw or specify raw as a field - let raw = raw || field.as_ref().is_some_and(|f| f == "raw"); + let raw = raw || fields.contains(&"raw".into()); sh_println!( "{}", Cast::new(provider) - .block(block.unwrap_or(BlockId::Number(Latest)), full, field, raw) + .block(block.unwrap_or(BlockId::Number(Latest)), full, fields, raw) .await? )? } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 5e8fdff2ef88c..ad8066b93a302 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -339,7 +339,7 @@ impl> Cast

{ /// let provider = /// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let block = cast.block(5, true, None, false).await?; + /// let block = cast.block(5, true, vec![], false).await?; /// println!("{}", block); /// # Ok(()) /// # } @@ -348,14 +348,11 @@ impl> Cast

{ &self, block: B, full: bool, - field: Option, + fields: Vec, raw: bool, ) -> Result { let block = block.into(); - if let Some(ref field) = field - && field == "transactions" - && !full - { + if fields.contains(&"transactions".into()) && !full { eyre::bail!("use --full to view transactions") } @@ -369,9 +366,17 @@ impl> Cast

{ Ok(if raw { let header: Header = block.into_inner().header.inner.try_into_header()?; format!("0x{}", hex::encode(alloy_rlp::encode(&header))) - } else if let Some(ref field) = field { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) + } else if !fields.is_empty() { + let mut result = String::new(); + for field in fields { + result.push_str( + &get_pretty_block_attr(&block, &field) + .unwrap_or_else(|| format!("{field} is not a valid block field")), + ); + + result.push('\n'); + } + result.trim_end().to_string() } else if shell::is_json() { serde_json::to_value(&block).unwrap().to_string() } else { @@ -385,7 +390,7 @@ impl> Cast

{ block.into(), false, // Select only select field - Some(field), + vec![field], false, ) .await? @@ -414,14 +419,15 @@ impl> Cast

{ 0, false, // Select only block hash - Some(String::from("hash")), + vec![String::from("hash")], false, ) .await?; Ok(match &genesis_hash[..] { "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { - match &(Self::block(self, 1920000, false, Some("hash".to_string()), false).await?)[..] + match &(Self::block(self, 1920000, false, vec![String::from("hash")], false) + .await?)[..] { "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { "etclive" @@ -464,7 +470,7 @@ impl> Cast

{ "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { - match &(Self::block(self, 1, false, Some(String::from("hash")), false).await?)[..] { + match &(Self::block(self, 1, false, vec![String::from("hash")], false).await?)[..] { "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { "avalanche-fuji" } diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index 95c14f3a0e0b3..b52d38c4c73c8 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -9,12 +9,11 @@ use crate::cmd::{ use alloy_ens::NameOrAddress; use alloy_primitives::{Address, B256, Selector, U256}; use alloy_rpc_types::BlockId; -use clap::{Parser, Subcommand, ValueHint}; +use clap::{ArgAction, Parser, Subcommand, ValueHint}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts}; use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; use std::{path::PathBuf, str::FromStr}; - /// A Swiss Army knife for interacting with Ethereum applications from the command line. #[derive(Parser)] #[command( @@ -379,11 +378,11 @@ pub enum CastSubcommand { block: Option, /// If specified, only get the given field of the block. - #[arg(long, short)] - field: Option, + #[arg(short, long = "field", aliases = ["fields"], num_args = 0.., action = ArgAction::Append, value_delimiter = ',')] + fields: Vec, /// Print the raw RLP encoded block header. - #[arg(long, conflicts_with = "field")] + #[arg(long, conflicts_with = "fields")] raw: bool, #[arg(long, env = "CAST_FULL_BLOCK")] diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 5d768911b27a8..51c9b1502d364 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -142,9 +142,17 @@ transactions: [ "#]]); // - cmd.cast_fuse().args(["block", "15007840", "-f", "hash", "--rpc-url", eth_rpc_url.as_str()]); + cmd.cast_fuse().args([ + "block", + "15007840", + "-f", + "hash,timestamp", + "--rpc-url", + eth_rpc_url.as_str(), + ]); cmd.assert_success().stdout_eq(str![[r#" 0x950091817a57e22b6c1f3b951a15f52d41ac89b299cc8f9c89bb6d185f80c415 +1655904485 "#]]); }); @@ -1312,6 +1320,7 @@ casttest!(to_base, |_prj, cmd| { }); // tests that revert reason is only present if transaction has reverted. + casttest!(receipt_revert_reason, |_prj, cmd| { let rpc = next_http_archive_rpc_url(); @@ -1323,27 +1332,25 @@ casttest!(receipt_revert_reason, |_prj, cmd| { rpc.as_str(), ]) .assert_success() - .stdout_eq(str![[r#" - + .stdout_eq(format!(r#" blockHash 0x2cfe65be49863676b6dbc04d58176a14f39b123f1e2f4fea0383a2d82c2c50d0 blockNumber 16239315 -contractAddress +contractAddress {} cumulativeGasUsed 10743428 effectiveGasPrice 10539984136 from 0x199D5ED7F45F4eE35960cF22EAde2076e95B253F gasUsed 21000 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -root +root {} status 1 (success) transactionHash 0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e transactionIndex 116 type 0 -blobGasPrice -blobGasUsed +blobGasPrice {} +blobGasUsed {} to 0x91da5bf3F8Eb72724E6f50Ec6C3D199C6355c59c - -"#]]); +"#,"", "", "", "")); let rpc = next_http_archive_rpc_url(); @@ -1356,30 +1363,27 @@ to 0x91da5bf3F8Eb72724E6f50Ec6C3D199C6355c59c rpc.as_str(), ]) .assert_success() - .stdout_eq(str![[r#" - + .stdout_eq(format!(r#" blockHash 0x883f974b17ca7b28cb970798d1c80f4d4bb427473dc6d39b2a7fe24edc02902d blockNumber 14839405 -contractAddress +contractAddress {} cumulativeGasUsed 20273649 effectiveGasPrice 21491736378 from 0x3cF412d970474804623bb4e3a42dE13F9bCa5436 gasUsed 24952 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -root +root {} status 0 (failed) transactionHash 0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c transactionIndex 173 type 2 -blobGasPrice -blobGasUsed +blobGasPrice {} +blobGasUsed {} to 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 revertReason [..]Transaction too old, data: "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000135472616e73616374696f6e20746f6f206f6c6400000000000000000000000000" - -"#]]); +"#,"","","","")); }); - // tests that the revert reason is loaded using the correct `from` address. casttest!(revert_reason_from, |_prj, cmd| { let rpc = next_rpc_endpoint(NamedChain::Sepolia); @@ -1391,28 +1395,26 @@ casttest!(revert_reason_from, |_prj, cmd| { rpc.as_str(), ]) .assert_success() - .stdout_eq(str![[r#" - + .stdout_eq(format!(r#" blockHash 0x32663d7730c9ea8e1de6d99854483e25fcc05bb56c91c0cc82f9f04944fbffc1 blockNumber 7823353 -contractAddress +contractAddress {} cumulativeGasUsed 7500797 effectiveGasPrice 14296851013 from 0x3583fF95f96b356d716881C871aF7Eb55ea34a93 gasUsed 25815 logs [] logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -root +root {} status 0 (failed) transactionHash 0x10ee70cf9f5ced5c515e8d53bfab5ea9f5c72cd61b25fba455c8355ee286c4e4 transactionIndex 96 type 0 -blobGasPrice -blobGasUsed +blobGasPrice {} +blobGasUsed {} to 0x91b5d4111a4C038153b24e31F75ccdC47123595d revertReason Counter is too large, data: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014436f756e74657220697320746f6f206c61726765000000000000000000000000" - -"#]]); +"#, "", "", "", "")); }); // tests that `cast --parse-bytes32-address` command is working correctly.