diff --git a/cli/src/cmd/forge/debug.rs b/cli/src/cmd/forge/debug.rs index 9d2b61988786..97ea984728db 100644 --- a/cli/src/cmd/forge/debug.rs +++ b/cli/src/cmd/forge/debug.rs @@ -51,6 +51,8 @@ impl DebugArgs { sig: self.sig, legacy: false, broadcast: false, + skip_simulation: false, + gas_estimate_multiplier: 130, opts: BuildArgs { args: self.opts, names: false, diff --git a/cli/src/cmd/forge/script/broadcast.rs b/cli/src/cmd/forge/script/broadcast.rs index 7db88ea51c19..1985037c2abe 100644 --- a/cli/src/cmd/forge/script/broadcast.rs +++ b/cli/src/cmd/forge/script/broadcast.rs @@ -207,15 +207,30 @@ impl ScriptArgs { ) -> eyre::Result<()> { if let Some(txs) = result.transactions { if script_config.evm_opts.fork_url.is_some() { - let gas_filled_txs = self - .execute_transactions(txs, &mut script_config, decoder, &verify.known_contracts) - .await - .map_err(|_| { - eyre::eyre!( - "One or more transactions failed when simulating the - on-chain version. Check the trace by re-running with `-vvv`" + let mut gas_filled_txs; + if self.skip_simulation { + println!("\nSKIPPING ON CHAIN SIMULATION."); + gas_filled_txs = VecDeque::new(); + for tx in txs.into_iter() { + gas_filled_txs + .push_back(TransactionWithMetadata::from_typed_transaction(tx)?); + } + } else { + gas_filled_txs = self + .execute_transactions( + txs, + &mut script_config, + decoder, + &verify.known_contracts, ) - })?; + .await + .map_err(|_| { + eyre::eyre!( + "One or more transactions failed when simulating the + on-chain version. Check the trace by re-running with `-vvv`" + ) + })?; + } let fork_url = self.evm_opts.fork_url.as_ref().unwrap().clone(); @@ -272,8 +287,10 @@ impl ScriptArgs { let typed_tx = tx.typed_tx_mut(); - if has_different_gas_calc(chain) { - typed_tx.set_gas(provider.estimate_gas(typed_tx).await?); + if has_different_gas_calc(chain) || self.skip_simulation { + typed_tx.set_gas( + provider.estimate_gas(typed_tx).await? * self.gas_estimate_multiplier / 100, + ); } total_gas += *typed_tx.gas().expect("gas is set"); diff --git a/cli/src/cmd/forge/script/executor.rs b/cli/src/cmd/forge/script/executor.rs index c30746f7cf67..1eb0cbcdc045 100644 --- a/cli/src/cmd/forge/script/executor.rs +++ b/cli/src/cmd/forge/script/executor.rs @@ -122,9 +122,8 @@ impl ScriptArgs { runner.executor.env_mut().block.number += U256::one(); } - // We inflate the gas used by the transaction by x1.3 since the estimation - // might be off - tx.gas = Some(U256::from(result.gas * 13 / 10)); + // We inflate the gas used by the user specified percentage + tx.gas = Some(U256::from(result.gas * self.gas_estimate_multiplier / 100)); if !result.success { failed = true; diff --git a/cli/src/cmd/forge/script/mod.rs b/cli/src/cmd/forge/script/mod.rs index 011b20967796..b70f72f21bf7 100644 --- a/cli/src/cmd/forge/script/mod.rs +++ b/cli/src/cmd/forge/script/mod.rs @@ -83,6 +83,18 @@ pub struct ScriptArgs { #[clap(long, help = "Broadcasts the transactions.")] pub broadcast: bool, + #[clap(long, help = "Skips on-chain simulation")] + pub skip_simulation: bool, + + #[clap( + long, + short, + default_value = "130", + value_name = "GAS_ESTIMATE_MULTIPLIER", + help = "Relative percentage to multiply gas estimates by" + )] + pub gas_estimate_multiplier: u64, + #[clap(flatten, next_help_heading = "BUILD OPTIONS")] pub opts: BuildArgs, diff --git a/cli/src/cmd/forge/script/sequence.rs b/cli/src/cmd/forge/script/sequence.rs index 1d2b6c57bb17..0873598325a8 100644 --- a/cli/src/cmd/forge/script/sequence.rs +++ b/cli/src/cmd/forge/script/sequence.rs @@ -265,6 +265,11 @@ fn default_vec_of_strings() -> Option> { } impl TransactionWithMetadata { + pub fn from_typed_transaction(transaction: TypedTransaction) -> eyre::Result { + let metadata = Self { transaction, ..Default::default() }; + Ok(metadata) + } + pub fn new( transaction: TypedTransaction, result: &ScriptResult, diff --git a/cli/tests/it/script.rs b/cli/tests/it/script.rs index 7e179c424716..3605b236202e 100644 --- a/cli/tests/it/script.rs +++ b/cli/tests/it/script.rs @@ -6,6 +6,7 @@ use foundry_cli_test_utils::{ util::{TestCommand, TestProject}, ScriptOutcome, ScriptTester, }; +use foundry_utils::rpc; use regex::Regex; use std::{env, path::PathBuf, str::FromStr}; @@ -211,6 +212,79 @@ result: uint256 255 )); }); +forgetest_async!( + can_broadcast_script_skipping_simulation, + |prj: TestProject, mut cmd: TestCommand| async move { + foundry_cli_test_utils::util::initialize(prj.root()); + // This example script would fail in on-chain simulation + let script = prj + .inner() + .add_source( + "Foo", + r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; +import "forge-std/Script.sol"; + +contract HashChecker { + bytes32 public lastHash; + function update() public { + bytes32 newHash = blockhash(block.number - 1); + require(newHash != lastHash, "Hash didn't change"); + lastHash = newHash; + } +} +contract Demo is Script { + function run() external returns (uint256 result, uint8) { + vm.startBroadcast(); + HashChecker hashChecker = new HashChecker(); + uint numUpdates = 8; + vm.roll(block.number - numUpdates); + for(uint i = 0; i < numUpdates; i++) { + vm.roll(block.number + 1); + hashChecker.update(); + } + } +}"#, + ) + .unwrap(); + + let node_config = NodeConfig::test() + .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())) + .silent(); + + let (_api, handle) = spawn(node_config).await; + let target_contract = script.display().to_string() + ":Demo"; + let private_key = + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); + cmd.set_current_dir(prj.root()); + + cmd.args([ + "script", + &target_contract, + "--root", + prj.root().to_str().unwrap(), + "--fork-url", + &handle.http_endpoint(), + "-vvvvv", + "--broadcast", + "--slow", + "--skip-simulation", + "--gas-estimate-multiplier", + "200", + "--private-key", + &private_key, + ]); + + let output = cmd.stdout_lossy(); + + println!("{}", output.to_string()); + + assert!(output.contains("SKIPPING ON CHAIN SIMULATION")); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); + } +); + forgetest_async!(can_deploy_script_without_lib, |prj: TestProject, cmd: TestCommand| async move { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());