diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index f010c279e0d0..7078810e4ce9 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -512,8 +512,8 @@ pub enum CastSubcommand { /// /// Similar to `abi-decode --input`, but function selector MUST be prefixed in `calldata` /// string - #[command(visible_aliases = &["--calldata-decode", "cdd"])] - CalldataDecode { + #[command(visible_aliases = &["calldata-decode", "--calldata-decode", "cdd"])] + DecodeCalldata { /// The function signature in the format `()()`. sig: String, @@ -524,19 +524,28 @@ pub enum CastSubcommand { /// Decode ABI-encoded string. /// /// Similar to `calldata-decode --input`, but the function argument is a `string` - #[command(visible_aliases = &["--string-decode", "sd"])] - StringDecode { + #[command(visible_aliases = &["string-decode", "--string-decode", "sd"])] + DecodeString { /// The ABI-encoded string. data: String, }, + /// Decode event data. + #[command(visible_aliases = &["event-decode", "--event-decode", "ed"])] + DecodeEvent { + /// The event signature. + sig: String, + /// The event data to decode. + data: String, + }, + /// Decode ABI-encoded input or output data. /// /// Defaults to decoding output data. To decode input data pass --input. /// /// When passing `--input`, function selector must NOT be prefixed in `calldata` string - #[command(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] - AbiDecode { + #[command(name = "decode-abi", visible_aliases = &["abi-decode", "--abi-decode", "ad"])] + DecodeAbi { /// The function signature in the format `()()`. sig: String, diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index ffce79099c9c..21b1df36d6cd 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate tracing; -use alloy_dyn_abi::DynSolValue; +use alloy_dyn_abi::{DynSolValue, EventExt}; use alloy_primitives::{eip191_hash_message, hex, keccak256, Address, B256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; @@ -189,7 +189,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } // ABI encoding & decoding - CastSubcommand::AbiDecode { sig, calldata, input } => { + CastSubcommand::DecodeAbi { sig, calldata, input } => { let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?; print_tokens(&tokens); } @@ -200,17 +200,22 @@ async fn main_args(args: CastArgs) -> Result<()> { sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)? } } - CastSubcommand::CalldataDecode { sig, calldata } => { + CastSubcommand::DecodeCalldata { sig, calldata } => { let tokens = SimpleCast::calldata_decode(&sig, &calldata, true)?; print_tokens(&tokens); } CastSubcommand::CalldataEncode { sig, args } => { sh_println!("{}", SimpleCast::calldata_encode(sig, &args)?)?; } - CastSubcommand::StringDecode { data } => { + CastSubcommand::DecodeString { data } => { let tokens = SimpleCast::calldata_decode("Any(string)", &data, true)?; print_tokens(&tokens); } + CastSubcommand::DecodeEvent { sig, data } => { + let event = get_event(sig.as_str())?; + let decoded_event = event.decode_log_parts(None, &hex::decode(data)?, false)?; + print_tokens(&decoded_event.body); + } CastSubcommand::Interface(cmd) => cmd.run().await?, CastSubcommand::CreationCode(cmd) => cmd.run().await?, CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 39e821dc91f1..b5b719e39ae8 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -2017,7 +2017,7 @@ impl SimpleCast { pub fn disassemble(code: &[u8]) -> Result { let mut output = String::new(); - for step in decode_instructions(code) { + for step in decode_instructions(code)? { write!(output, "{:08x}: ", step.pc)?; if let Some(op) = step.op { @@ -2290,4 +2290,23 @@ mod tests { r#"["0x2b5df5f0757397573e8ff34a8b987b21680357de1f6c8d10273aa528a851eaca","0x","0x","0x2838ac1d2d2721ba883169179b48480b2ba4f43d70fcf806956746bd9e83f903","0x","0xe46fff283b0ab96a32a7cc375cecc3ed7b6303a43d64e0a12eceb0bc6bd87549","0x","0x1d818c1c414c665a9c9a0e0c0ef1ef87cacb380b8c1f6223cb2a68a4b2d023f5","0x","0x","0x","0x236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff0","0x","0x","0x","0x","0x"]"# ) } + + #[test] + fn disassemble_incomplete_sequence() { + let incomplete = &hex!("60"); // PUSH1 + let disassembled = Cast::disassemble(incomplete); + assert!(disassembled.is_err()); + + let complete = &hex!("6000"); // PUSH1 0x00 + let disassembled = Cast::disassemble(complete); + assert!(disassembled.is_ok()); + + let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes + let disassembled = Cast::disassemble(incomplete); + assert!(disassembled.is_err()); + + let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes + let disassembled = Cast::disassemble(complete); + assert!(disassembled.is_ok()); + } } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 332f0f99f57b..92f23a457435 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1474,6 +1474,14 @@ casttest!(string_decode, |_prj, cmd| { "#]]); }); +casttest!(event_decode, |_prj, cmd| { + cmd.args(["decode-event", "MyEvent(uint256,address)", "0x000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000d0004f"]).assert_success().stdout_eq(str![[r#" +78 +0x0000000000000000000000000000000000D0004F + +"#]]); +}); + casttest!(format_units, |_prj, cmd| { cmd.args(["format-units", "1000000", "6"]).assert_success().stdout_eq(str![[r#" 1 diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index d19c776b9916..d8f8d21df67b 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3111,7 +3111,7 @@ "func": { "id": "attachDelegation", "description": "Designate the next call as an EIP-7702 transaction", - "declaration": "function attachDelegation(SignedDelegation memory signedDelegation) external;", + "declaration": "function attachDelegation(SignedDelegation calldata signedDelegation) external;", "visibility": "external", "mutability": "", "signature": "attachDelegation((uint8,bytes32,bytes32,uint64,address))", @@ -4615,7 +4615,7 @@ "func": { "id": "eth_getLogs", "description": "Gets all the logs according to specified filter.", - "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) external returns (EthGetLogs[] memory logs);", + "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs);", "visibility": "external", "mutability": "", "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", @@ -5355,7 +5355,7 @@ "func": { "id": "getBroadcast", "description": "Returns the most recent broadcast for the given contract on `chainId` matching `txType`.\nFor example:\nThe most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`.\nThe most recent call can be fetched by passing `txType` as `CALL`.", - "declaration": "function getBroadcast(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory);", + "declaration": "function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcast(string,uint64,uint8)", @@ -5375,7 +5375,7 @@ "func": { "id": "getBroadcasts_0", "description": "Returns all broadcasts for the given contract on `chainId` with the specified `txType`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", - "declaration": "function getBroadcasts(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory);", + "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcasts(string,uint64,uint8)", @@ -5395,7 +5395,7 @@ "func": { "id": "getBroadcasts_1", "description": "Returns all broadcasts for the given contract on `chainId`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", - "declaration": "function getBroadcasts(string memory contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory);", + "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcasts(string,uint64)", @@ -5455,7 +5455,7 @@ "func": { "id": "getDeployment_0", "description": "Returns the most recent deployment for the current `chainId`.", - "declaration": "function getDeployment(string memory contractName) external view returns (address deployedAddress);", + "declaration": "function getDeployment(string calldata contractName) external view returns (address deployedAddress);", "visibility": "external", "mutability": "view", "signature": "getDeployment(string)", @@ -5475,7 +5475,7 @@ "func": { "id": "getDeployment_1", "description": "Returns the most recent deployment for the given contract on `chainId`", - "declaration": "function getDeployment(string memory contractName, uint64 chainId) external view returns (address deployedAddress);", + "declaration": "function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress);", "visibility": "external", "mutability": "view", "signature": "getDeployment(string,uint64)", @@ -5495,7 +5495,7 @@ "func": { "id": "getDeployments", "description": "Returns all deployments for the given contract on `chainId`\nSorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber.\nThe most recent deployment is the first element, and the oldest is the last.", - "declaration": "function getDeployments(string memory contractName, uint64 chainId) external view returns (address[] memory deployedAddresses);", + "declaration": "function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses);", "visibility": "external", "mutability": "view", "signature": "getDeployments(string,uint64)", @@ -8621,7 +8621,7 @@ "func": { "id": "serializeJsonType_0", "description": "See `serializeJson`.", - "declaration": "function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json);", + "declaration": "function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json);", "visibility": "external", "mutability": "pure", "signature": "serializeJsonType(string,bytes)", @@ -8641,7 +8641,7 @@ "func": { "id": "serializeJsonType_1", "description": "See `serializeJson`.", - "declaration": "function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json);", + "declaration": "function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeJsonType(string,string,string,bytes)", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index bf0fe9781f8a..6b66d31ddb50 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -808,7 +808,7 @@ interface Vm { /// Gets all the logs according to specified filter. #[cheatcode(group = Evm, safety = Safe)] - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); @@ -1764,27 +1764,27 @@ interface Vm { /// /// The most recent call can be fetched by passing `txType` as `CALL`. #[cheatcode(group = Filesystem)] - function getBroadcast(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); + function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); /// Returns all broadcasts for the given contract on `chainId` with the specified `txType`. /// /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. #[cheatcode(group = Filesystem)] - function getBroadcasts(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); + function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); /// Returns all broadcasts for the given contract on `chainId`. /// /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. #[cheatcode(group = Filesystem)] - function getBroadcasts(string memory contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); /// Returns the most recent deployment for the current `chainId`. #[cheatcode(group = Filesystem)] - function getDeployment(string memory contractName) external view returns (address deployedAddress); + function getDeployment(string calldata contractName) external view returns (address deployedAddress); /// Returns the most recent deployment for the given contract on `chainId` #[cheatcode(group = Filesystem)] - function getDeployment(string memory contractName, uint64 chainId) external view returns (address deployedAddress); + function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); /// Returns all deployments for the given contract on `chainId` /// @@ -1792,7 +1792,7 @@ interface Vm { /// /// The most recent deployment is the first element, and the oldest is the last. #[cheatcode(group = Filesystem)] - function getDeployments(string memory contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); // -------- Foreign Function Interface -------- @@ -2040,7 +2040,7 @@ interface Vm { /// Designate the next call as an EIP-7702 transaction #[cheatcode(group = Scripting)] - function attachDelegation(SignedDelegation memory signedDelegation) external; + function attachDelegation(SignedDelegation calldata signedDelegation) external; /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction #[cheatcode(group = Scripting)] @@ -2298,13 +2298,13 @@ interface Vm { returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] - function serializeJsonType(string calldata typeDescription, bytes memory value) + function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] - function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json); diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index 3eca0800e0cd..c4dfccae1641 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -196,11 +196,6 @@ impl Provider for EvmArgs { #[derive(Clone, Debug, Default, Serialize, Parser)] #[command(next_help_heading = "Executor environment config")] pub struct EnvArgs { - /// The block gas limit. - #[arg(long, value_name = "GAS_LIMIT")] - #[serde(skip_serializing_if = "Option::is_none")] - pub gas_limit: Option, - /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). #[arg(long, value_name = "CODE_SIZE")] @@ -253,7 +248,7 @@ pub struct EnvArgs { pub block_prevrandao: Option, /// The block gas limit. - #[arg(long, value_name = "GAS_LIMIT")] + #[arg(long, visible_alias = "gas-limit", value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub block_gas_limit: Option, diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index 2711f8933543..fcabf2a18b41 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,4 +1,5 @@ use alloy_primitives::map::HashMap; +use eyre::Result; use revm::interpreter::{ opcode::{PUSH0, PUSH1, PUSH32}, OpCode, @@ -100,7 +101,7 @@ pub struct Instruction<'a> { } /// Decodes raw opcode bytes into [`Instruction`]s. -pub fn decode_instructions(code: &[u8]) -> Vec> { +pub fn decode_instructions(code: &[u8]) -> Result>> { let mut pc = 0; let mut steps = Vec::new(); @@ -108,10 +109,14 @@ pub fn decode_instructions(code: &[u8]) -> Vec> { let op = OpCode::new(code[pc]); let immediate_size = op.map(|op| immediate_size(op, &code[pc + 1..])).unwrap_or(0) as usize; + if pc + 1 + immediate_size > code.len() { + eyre::bail!("incomplete sequence of bytecode"); + } + steps.push(Instruction { op, pc, immediate: &code[pc + 1..pc + 1 + immediate_size] }); pc += 1 + immediate_size; } - steps + Ok(steps) } diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 9fc2629e5988..71823416d713 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -1,3 +1,4 @@ +use crate::cmd::install; use alloy_chains::Chain; use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::{Constructor, JsonAbi}; @@ -98,7 +99,14 @@ pub struct CreateArgs { impl CreateArgs { /// Executes the command to create a contract pub async fn run(mut self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let mut config = self.try_load_config_emit_warnings()?; + + // Install missing dependencies. + if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + // need to re-configure here to also catch additional remappings + config = self.load_config(); + } + // Find Project & Compile let project = config.project()?; diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index df2a59bdc4df..d7776eee2e87 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -6,6 +6,7 @@ use anvil::{spawn, NodeConfig}; use forge_script_sequence::ScriptSequence; use foundry_test_utils::{ rpc, + snapbox::IntoData, util::{OTHER_SOLC_VERSION, SOLC_VERSION}, ScriptOutcome, ScriptTester, }; @@ -1821,6 +1822,83 @@ Warning: Script contains a transaction to 0x000000000000000000000000000000000000 "#]]); }); +// Asserts that the script runs with expected non-output using `--quiet` flag +forgetest_async!(adheres_to_quiet_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--broadcast", + "--unlocked", + "--non-interactive", + "--quiet", + ]) + .assert_empty_stdout(); +}); + +// Asserts that the script runs with expected non-output using `--quiet` flag +forgetest_async!(adheres_to_json_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--broadcast", + "--unlocked", + "--non-interactive", + "--json", + ]) + .assert_success() + .stdout_eq(str![[r#" +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"0x6080604052600c805462ff00ff191662010001179055348015601f575f5ffd5b506101568061002d5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063c040622614610038578063f8ccbf4714610054575b5f5ffd5b610040610067565b604051901515815260200160405180910390f35b600c546100409062010000900460ff1681565b5f7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d5f1c6001600160a01b0316637fb5297f6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156100c2575f5ffd5b505af11580156100d4573d5f5f3e3d5ffd5b50506040515f925090508181818181805af19150503d805f8114610113576040519150601f19603f3d011682016040523d82523d5f602084013e610118565b606091505b50909291505056fea264697066735822122060ba6332e526de9b6bc731fb4682b44e42845196324ec33068982984d700cdd964736f6c634300081b0033","output":"0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063c040622614610038578063f8ccbf4714610054575b5f5ffd5b610040610067565b604051901515815260200160405180910390f35b600c546100409062010000900460ff1681565b5f7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d5f1c6001600160a01b0316637fb5297f6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156100c2575f5ffd5b505af11580156100d4573d5f5f3e3d5ffd5b50506040515f925090508181818181805af19150503d805f8114610113576040519150601f19603f3d011682016040523d82523d5f602084013e610118565b606091505b50909291505056fea264697066735822122060ba6332e526de9b6bc731fb4682b44e42845196324ec33068982984d700cdd964736f6c634300081b0033","gas_used":90639,"gas_limit":1073682810,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":3214,"gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":0,"gas_limit":1056940983,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1056940820,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":24278,"labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"chain":31337,"estimated_gas_price":"2.000000001","estimated_total_gas_used":29005,"estimated_amount_required":"0.000058010000029005"} +{"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":21000,"gas_price":1000000001} +{"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} + +"#]].is_jsonlines()); +}); + // https://github.com/foundry-rs/foundry/pull/7742 forgetest_async!(unlocked_no_sender, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); diff --git a/crates/macros/src/cheatcodes.rs b/crates/macros/src/cheatcodes.rs index d9c2d2c91435..4d0f260c294b 100644 --- a/crates/macros/src/cheatcodes.rs +++ b/crates/macros/src/cheatcodes.rs @@ -58,6 +58,17 @@ fn derive_call(name: &Ident, data: &DataStruct, attrs: &[Attribute]) -> Result Self { - let mut template = "{spinner:.green}".to_string(); - write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain)) - .unwrap(); - template.push_str("{msg}"); - - let top_spinner = ProgressBar::new_spinner() - .with_style(ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅")); - let top_spinner = multi.add(top_spinner); - - let txs = multi.insert_after( - &top_spinner, - init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "), - ); - - let receipts = multi.insert_after( - &txs, - init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "), - ); - - top_spinner.enable_steady_tick(Duration::from_millis(100)); - txs.enable_steady_tick(Duration::from_millis(1000)); - receipts.enable_steady_tick(Duration::from_millis(1000)); - - txs.set_position(sequence.receipts.len() as u64); - receipts.set_position(sequence.receipts.len() as u64); - - let mut state = Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi }; + let mut state = if shell::is_quiet() || shell::is_json() { + let top_spinner = ProgressBar::hidden(); + let txs = ProgressBar::hidden(); + let receipts = ProgressBar::hidden(); + + Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi } + } else { + let mut template = "{spinner:.green}".to_string(); + write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain)) + .unwrap(); + template.push_str("{msg}"); + + let top_spinner = ProgressBar::new_spinner().with_style( + ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅"), + ); + let top_spinner = multi.add(top_spinner); + + let txs = multi.insert_after( + &top_spinner, + init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "), + ); + + let receipts = multi.insert_after( + &txs, + init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "), + ); + + top_spinner.enable_steady_tick(Duration::from_millis(100)); + txs.enable_steady_tick(Duration::from_millis(1000)); + receipts.enable_steady_tick(Duration::from_millis(1000)); + + txs.set_position(sequence.receipts.len() as u64); + receipts.set_position(sequence.receipts.len() as u64); + + Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi } + }; for tx_hash in sequence.pending.iter() { state.tx_sent(*tx_hash); @@ -71,16 +80,21 @@ impl SequenceProgressState { pub fn tx_sent(&mut self, tx_hash: B256) { // Avoid showing more than 10 spinners. if self.tx_spinners.len() < 10 { - let spinner = ProgressBar::new_spinner() - .with_style( - ProgressStyle::with_template(" {spinner:.green} {msg}") - .unwrap() - .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), - ) - .with_message(format!("{} {}", "[Pending]".yellow(), tx_hash)); - - let spinner = self.multi.insert_before(&self.txs, spinner); - spinner.enable_steady_tick(Duration::from_millis(100)); + let spinner = if shell::is_quiet() || shell::is_json() { + ProgressBar::hidden() + } else { + let spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template(" {spinner:.green} {msg}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), + ) + .with_message(format!("{} {}", "[Pending]".yellow(), tx_hash)); + + let spinner = self.multi.insert_before(&self.txs, spinner); + spinner.enable_steady_tick(Duration::from_millis(100)); + spinner + }; self.tx_spinners.insert(tx_hash, spinner); } @@ -98,7 +112,10 @@ impl SequenceProgressState { /// Same as finish_tx_spinner but also prints a message to stdout above all other progress bars. pub fn finish_tx_spinner_with_msg(&mut self, tx_hash: B256, msg: &str) -> std::io::Result<()> { self.finish_tx_spinner(tx_hash); - self.multi.println(msg)?; + + if !(shell::is_quiet() || shell::is_json()) { + self.multi.println(msg)?; + } Ok(()) } diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index c11fdd71a92c..f073e38e607b 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -3,7 +3,7 @@ use alloy_primitives::{utils::format_units, TxHash, U256}; use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; use alloy_rpc_types::AnyTransactionReceipt; use eyre::Result; -use foundry_common::provider::RetryProvider; +use foundry_common::{provider::RetryProvider, shell}; use std::time::Duration; /// Convenience enum for internal signalling of transaction status @@ -71,31 +71,51 @@ pub async fn check_tx_status( pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { let gas_used = receipt.gas_used; let gas_price = receipt.effective_gas_price; - format!( - "\n##### {chain}\n{status} Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n\n", - status = if !receipt.inner.inner.inner.receipt.status.coerce_status() { - "❌ [Failed]" - } else { - "✅ [Success]" - }, - tx_hash = receipt.transaction_hash, - caddr = if let Some(addr) = &receipt.contract_address { - format!("\nContract Address: {}", addr.to_checksum(None)) - } else { - String::new() - }, - bn = receipt.block_number.unwrap_or_default(), - gas = if gas_price == 0 { - format!("Gas Used: {gas_used}") - } else { - let paid = format_units(gas_used.saturating_mul(gas_price), 18) - .unwrap_or_else(|_| "N/A".into()); - let gas_price = format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); - format!( - "Paid: {} ETH ({gas_used} gas * {} gwei)", - paid.trim_end_matches('0'), - gas_price.trim_end_matches('0').trim_end_matches('.') - ) - }, - ) + let block_number = receipt.block_number.unwrap_or_default(); + let success = receipt.inner.inner.inner.receipt.status.coerce_status(); + + if shell::is_json() { + let _ = sh_println!( + "{}", + serde_json::json!({ + "chain": chain, + "status": if success { + "success" + } else { + "failed" + }, + "tx_hash": receipt.transaction_hash, + "contract_address": receipt.contract_address.map(|addr| addr.to_string()), + "block_number": block_number, + "gas_used": gas_used, + "gas_price": gas_price, + }) + ); + + String::new() + } else { + format!( + "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n", + status = if success { "✅ [Success]" } else { "❌ [Failed]" }, + tx_hash = receipt.transaction_hash, + contract_address = if let Some(addr) = &receipt.contract_address { + format!("\nContract Address: {}", addr.to_checksum(None)) + } else { + String::new() + }, + gas = if gas_price == 0 { + format!("Gas Used: {gas_used}") + } else { + let paid = format_units(gas_used.saturating_mul(gas_price), 18) + .unwrap_or_else(|_| "N/A".into()); + let gas_price = + format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); + format!( + "Paid: {} ETH ({gas_used} gas * {} gwei)", + paid.trim_end_matches('0'), + gas_price.trim_end_matches('0').trim_end_matches('.') + ) + }, + ) + } } diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 95427225f202..e833073ac00f 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -16,7 +16,7 @@ use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; -use foundry_common::ContractData; +use foundry_common::{shell, ContractData}; use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; @@ -151,7 +151,7 @@ impl PreSimulationState { }) .collect::>(); - if self.script_config.evm_opts.verbosity > 3 { + if !shell::is_json() && self.script_config.evm_opts.verbosity > 3 { sh_println!("==========================")?; sh_println!("Simulated On-chain Traces:\n")?; } @@ -220,9 +220,11 @@ impl PreSimulationState { async fn build_runners(&self) -> Result> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); - let n = rpcs.len(); - let s = if n != 1 { "s" } else { "" }; - sh_println!("\n## Setting up {n} EVM{s}.")?; + if !shell::is_json() { + let n = rpcs.len(); + let s = if n != 1 { "s" } else { "" }; + sh_println!("\n## Setting up {n} EVM{s}.")?; + } let futs = rpcs.into_iter().map(|rpc| async move { let mut script_config = self.script_config.clone(); @@ -348,24 +350,34 @@ impl FilledTransactionsState { provider_info.gas_price()? }; - sh_println!("\n==========================")?; - sh_println!("\nChain {}", provider_info.chain)?; - - sh_println!( - "\nEstimated gas price: {} gwei", - format_units(per_gas, 9) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - .trim_end_matches('.') - )?; - sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!( - "\nEstimated amount required: {} ETH", - format_units(total_gas.saturating_mul(per_gas), 18) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - )?; - sh_println!("\n==========================")?; + let estimated_gas_price_raw = format_units(per_gas, 9) + .unwrap_or_else(|_| "[Could not calculate]".to_string()); + let estimated_gas_price = + estimated_gas_price_raw.trim_end_matches('0').trim_end_matches('.'); + + let estimated_amount_raw = format_units(total_gas.saturating_mul(per_gas), 18) + .unwrap_or_else(|_| "[Could not calculate]".to_string()); + let estimated_amount = estimated_amount_raw.trim_end_matches('0'); + + if !shell::is_json() { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; + sh_println!("\n==========================")?; + } else { + sh_println!( + "{}", + serde_json::json!({ + "chain": provider_info.chain, + "estimated_gas_price": estimated_gas_price, + "estimated_total_gas_used": total_gas, + "estimated_amount_required": estimated_amount, + }) + )?; + } } } diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index ca90129d543b..78f39fe96dcc 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -139,7 +139,6 @@ impl VerificationProvider for EtherscanVerificationProvider { retry: RETRY_CHECK_ON_VERIFY, verifier: args.verifier, }; - // return check_args.run().await return self.check(check_args).await } } else { diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index 9c32ee95f5bf..5a8efe4c6598 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -220,6 +220,17 @@ impl VerifyArgs { let verifier_url = self.verifier.verifier_url.clone(); sh_println!("Start verifying contract `{}` deployed on {chain}", self.address)?; + if let Some(version) = &self.compiler_version { + sh_println!("Compiler version: {version}")?; + } + if let Some(optimizations) = &self.num_of_optimizations { + sh_println!("Optimizations: {optimizations}")? + } + if let Some(args) = &self.constructor_args { + if !args.is_empty() { + sh_println!("Constructor args: {args}")? + } + } self.verifier.verifier.client(&self.etherscan.key())?.verify(self, context).await.map_err(|err| { if let Some(verifier_url) = verifier_url { match Url::parse(&verifier_url) { diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 9d1d3efd1420..f6f66969f99f 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -149,7 +149,7 @@ interface Vm { function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; function assumeNoRevert() external pure; - function attachDelegation(SignedDelegation memory signedDelegation) external; + function attachDelegation(SignedDelegation calldata signedDelegation) external; function blobBaseFee(uint256 newBlobBaseFee) external; function blobhashes(bytes32[] calldata hashes) external; function breakpoint(string calldata char) external pure; @@ -224,7 +224,7 @@ interface Vm { function envUint(string calldata name) external view returns (uint256 value); function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); function etch(address target, bytes calldata newRuntimeBytecode) external; - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) external returns (EthGetLogs[] memory logs); + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); function exists(string calldata path) external view returns (bool result); function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external; @@ -261,14 +261,14 @@ interface Vm { function getBlobhashes() external view returns (bytes32[] memory hashes); function getBlockNumber() external view returns (uint256 height); function getBlockTimestamp() external view returns (uint256 timestamp); - function getBroadcast(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); - function getBroadcasts(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); - function getBroadcasts(string memory contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); + function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); + function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); - function getDeployment(string memory contractName) external view returns (address deployedAddress); - function getDeployment(string memory contractName, uint64 chainId) external view returns (address deployedAddress); - function getDeployments(string memory contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + function getDeployment(string calldata contractName) external view returns (address deployedAddress); + function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); + function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); function getFoundryVersion() external view returns (string memory version); function getLabel(address account) external view returns (string memory currentLabel); function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent); @@ -424,8 +424,8 @@ interface Vm { function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) external returns (string memory json); function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) external returns (string memory json); function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); - function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); - function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json); + function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json); + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json); function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json); function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json); function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json);