From 4bada66b73d1b9faac17628e84adcf0b700be2cd Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 27 Nov 2024 11:26:19 +0100 Subject: [PATCH 1/5] add --broadcast flag to forge create, default to dry run --- crates/forge/bin/cmd/create.rs | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 71823416d713..3106e0395079 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -61,6 +61,10 @@ pub struct CreateArgs { )] constructor_args_path: Option, + /// Broadcast the transaction. + #[arg(long)] + pub broadcast: bool, + /// Verify contract after creation. #[arg(long)] verify: bool, @@ -155,10 +159,17 @@ impl CreateArgs { } else { provider.get_chain_id().await? }; + + let contract_name = self.contract.name.clone(); + + // Whether to broadcast the transaction or not + let dry_run = !self.broadcast; + if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( + &contract_name, abi, bin, params, @@ -167,6 +178,7 @@ impl CreateArgs { sender, config.transaction_timeout, id, + dry_run, ) .await } else { @@ -177,6 +189,7 @@ impl CreateArgs { .wallet(EthereumWallet::new(signer)) .on_provider(provider); self.deploy( + &contract_name, abi, bin, params, @@ -185,6 +198,7 @@ impl CreateArgs { deployer, config.transaction_timeout, id, + dry_run, ) .await } @@ -252,6 +266,7 @@ impl CreateArgs { #[allow(clippy::too_many_arguments)] async fn deploy, T: Transport + Clone>( self, + contract_name: &str, abi: JsonAbi, bin: BytecodeObject, args: Vec, @@ -260,6 +275,7 @@ impl CreateArgs { deployer_address: Address, timeout: u64, id: ArtifactId, + dry_run: bool, ) -> Result<()> { let bin = bin.into_bytes().unwrap_or_else(|| { panic!("no bytecode found in bin object for {}", self.contract.name) @@ -339,6 +355,30 @@ impl CreateArgs { self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; } + if dry_run { + if !shell::is_json() { + sh_warn!("Dry run enabled, not broadcasting transaction\n")?; + + sh_println!("Contract: {contract_name}")?; + sh_println!( + "Transaction: {}", + serde_json::to_string_pretty(&deployer.tx.clone())? + )?; + sh_println!("ABI: {}\n", serde_json::to_string_pretty(&abi)?)?; + + sh_warn!("To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more.")?; + } else { + let output = json!({ + "contract": contract_name, + "transaction": &deployer.tx, + "abi":&abi + }); + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; + } + + return Ok(()); + } + // Deploy the actual contract let (deployed_contract, receipt) = deployer.send_with_receipt().await?; @@ -349,7 +389,7 @@ impl CreateArgs { "deployedTo": address.to_string(), "transactionHash": receipt.transaction_hash }); - sh_println!("{output}")?; + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; } else { sh_println!("Deployer: {deployer_address}")?; sh_println!("Deployed to: {address}")?; From 4dbaac1c96867265f44949f9214dff4980858fc6 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 27 Nov 2024 11:37:28 +0100 Subject: [PATCH 2/5] nits --- crates/forge/bin/cmd/create.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 3106e0395079..6c2fbb0cfecc 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -160,8 +160,6 @@ impl CreateArgs { provider.get_chain_id().await? }; - let contract_name = self.contract.name.clone(); - // Whether to broadcast the transaction or not let dry_run = !self.broadcast; @@ -169,7 +167,6 @@ impl CreateArgs { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); self.deploy( - &contract_name, abi, bin, params, @@ -189,7 +186,6 @@ impl CreateArgs { .wallet(EthereumWallet::new(signer)) .on_provider(provider); self.deploy( - &contract_name, abi, bin, params, @@ -266,7 +262,6 @@ impl CreateArgs { #[allow(clippy::too_many_arguments)] async fn deploy, T: Transport + Clone>( self, - contract_name: &str, abi: JsonAbi, bin: BytecodeObject, args: Vec, @@ -359,7 +354,7 @@ impl CreateArgs { if !shell::is_json() { sh_warn!("Dry run enabled, not broadcasting transaction\n")?; - sh_println!("Contract: {contract_name}")?; + sh_println!("Contract: {}", self.contract.name)?; sh_println!( "Transaction: {}", serde_json::to_string_pretty(&deployer.tx.clone())? @@ -369,7 +364,7 @@ impl CreateArgs { sh_warn!("To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more.")?; } else { let output = json!({ - "contract": contract_name, + "contract": self.contract.name, "transaction": &deployer.tx, "abi":&abi }); From 2d1ac1a798dfe31179c8a6eb23e77ffb112b9595 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 27 Nov 2024 11:44:00 +0100 Subject: [PATCH 3/5] fix tests --- crates/forge/tests/cli/create.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index ebf8c81dbcc1..8a1c6cc9b5b4 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -152,6 +152,7 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", ]); cmd.assert().stdout_eq(str![[r#" @@ -193,6 +194,7 @@ forgetest_async!(can_create_using_unlocked, |prj, cmd| { "--from", format!("{dev:?}").as_str(), "--unlocked", + "--broadcast", ]); cmd.assert().stdout_eq(str![[r#" @@ -248,6 +250,7 @@ contract ConstructorContract { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", "--constructor-args", "My Constructor", ]) @@ -335,6 +338,7 @@ contract UniswapV2Swap { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", ]) .assert_success() .stdout_eq(str![[r#" From b2e987dcc5df48240c9058fe30404d0db22a8df9 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 27 Nov 2024 11:51:56 +0100 Subject: [PATCH 4/5] add dry run tests incl --json --- crates/forge/tests/cli/create.rs | 120 +++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 8a1c6cc9b5b4..7cdafcb1a9b6 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -145,6 +145,7 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; prj.write_config(config); + // Dry-run without the `--broadcast` flag cmd.forge_fuse().args([ "create", format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), @@ -152,23 +153,130 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { rpc.as_str(), "--private-key", pk.as_str(), - "--broadcast", ]); + // Dry-run cmd.assert().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -[TX_HASH] +Contract: Counter +Transaction: { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "gas": "0x17575", + "input": "0x6080604052348015600e575f5ffd5b5060c380601a5f395ff3fe6080604052348015600e575f5ffd5b5060043610603a575f3560e01c80633fb5c1cb14603e5780638381f58a14604f578063d09de08a146068575b5f5ffd5b604d6049366004607d565b5f55565b005b60565f5481565b60405190815260200160405180910390f35b604d5f805490806076836093565b9190505550565b5f60208284031215608c575f5ffd5b5035919050565b5f6001820160af57634e487b7160e01b5f52601160045260245ffd5b506001019056fea164736f6c634300081c000a", + "nonce": "0x0", + "chainId": "0x7a69" +} +ABI: [ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setNumber", + "inputs": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] + "#]]); + // Dry-run with `--json` flag + cmd.arg("--json").assert().stdout_eq(str![[r#" +{ + "contract": "Counter", + "transaction": { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "gas": "0x17575", + "input": "0x6080604052348015600e575f5ffd5b5060c380601a5f395ff3fe6080604052348015600e575f5ffd5b5060043610603a575f3560e01c80633fb5c1cb14603e5780638381f58a14604f578063d09de08a146068575b5f5ffd5b604d6049366004607d565b5f55565b005b60565f5481565b60405190815260200160405180910390f35b604d5f805490806076836093565b9190505550565b5f60208284031215608c575f5ffd5b5035919050565b5f6001820160af57634e487b7160e01b5f52601160045260245ffd5b506001019056fea164736f6c634300081c000a", + "nonce": "0x0", + "chainId": "0x7a69" + }, + "abi": [ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setNumber", + "inputs": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } + ] +} + +"#]]); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--broadcast", + ]); + cmd.assert().stdout_eq(str![[r#" No files changed, compilation skipped Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 [TX_HASH] "#]]); @@ -206,6 +314,7 @@ Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 [TX_HASH] "#]]); + cmd.assert().stdout_eq(str![[r#" No files changed, compilation skipped Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 @@ -288,6 +397,7 @@ contract TupleArrayConstructorContract { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", "--constructor-args", "[(1,2), (2,3), (3,4)]", ]) From 444e034863ba25c41e9d4bc787db97835b52b34b Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 27 Nov 2024 12:00:46 +0100 Subject: [PATCH 5/5] minor fixes, failing test due to minor bytecode difference --- crates/forge/tests/cli/create.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 7cdafcb1a9b6..6a78f83231ef 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -9,7 +9,9 @@ use anvil::{spawn, NodeConfig}; use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; use foundry_config::Config; use foundry_test_utils::{ - forgetest, forgetest_async, str, + forgetest, forgetest_async, + snapbox::IntoData, + str, util::{OutputExt, TestCommand, TestProject}, }; use std::str::FromStr; @@ -167,7 +169,7 @@ Transaction: { "maxFeePerGas": "0x77359401", "maxPriorityFeePerGas": "0x1", "gas": "0x17575", - "input": "0x6080604052348015600e575f5ffd5b5060c380601a5f395ff3fe6080604052348015600e575f5ffd5b5060043610603a575f3560e01c80633fb5c1cb14603e5780638381f58a14604f578063d09de08a146068575b5f5ffd5b604d6049366004607d565b5f55565b005b60565f5481565b60405190815260200160405180910390f35b604d5f805490806076836093565b9190505550565b5f60208284031215608c575f5ffd5b5035919050565b5f6001820160af57634e487b7160e01b5f52601160045260245ffd5b506001019056fea164736f6c634300081c000a", + "input": "[..]", "nonce": "0x0", "chainId": "0x7a69" } @@ -211,7 +213,8 @@ ABI: [ "#]]); // Dry-run with `--json` flag - cmd.arg("--json").assert().stdout_eq(str![[r#" + cmd.arg("--json").assert().stdout_eq( + str![[r#" { "contract": "Counter", "transaction": { @@ -220,7 +223,7 @@ ABI: [ "maxFeePerGas": "0x77359401", "maxPriorityFeePerGas": "0x1", "gas": "0x17575", - "input": "0x6080604052348015600e575f5ffd5b5060c380601a5f395ff3fe6080604052348015600e575f5ffd5b5060043610603a575f3560e01c80633fb5c1cb14603e5780638381f58a14604f578063d09de08a146068575b5f5ffd5b604d6049366004607d565b5f55565b005b60565f5481565b60405190815260200160405180910390f35b604d5f805490806076836093565b9190505550565b5f60208284031215608c575f5ffd5b5035919050565b5f6001820160af57634e487b7160e01b5f52601160045260245ffd5b506001019056fea164736f6c634300081c000a", + "input": "[..]", "nonce": "0x0", "chainId": "0x7a69" }, @@ -261,7 +264,9 @@ ABI: [ ] } -"#]]); +"#]] + .is_json(), + ); cmd.forge_fuse().args([ "create",