Skip to content

Commit

Permalink
feat: add --skip-simulation and --gas-estimate-multiplier flags t…
Browse files Browse the repository at this point in the history
…o `forge script` (#2524)

* Add skip-simulation flag to bypass on-chain simulation

* Add gas-estimate-multiplier cli argument

* chore: nits

Co-authored-by: Oliver Nordbjerg <[email protected]>
  • Loading branch information
ckoopmann and onbjerg authored Aug 3, 2022
1 parent f0a7315 commit b107a6e
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 13 deletions.
2 changes: 2 additions & 0 deletions cli/src/cmd/forge/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 27 additions & 10 deletions cli/src/cmd/forge/script/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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");
Expand Down
5 changes: 2 additions & 3 deletions cli/src/cmd/forge/script/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions cli/src/cmd/forge/script/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
5 changes: 5 additions & 0 deletions cli/src/cmd/forge/script/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ fn default_vec_of_strings() -> Option<Vec<String>> {
}

impl TransactionWithMetadata {
pub fn from_typed_transaction(transaction: TypedTransaction) -> eyre::Result<Self> {
let metadata = Self { transaction, ..Default::default() };
Ok(metadata)
}

pub fn new(
transaction: TypedTransaction,
result: &ScriptResult,
Expand Down
74 changes: 74 additions & 0 deletions cli/tests/it/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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());
Expand Down

0 comments on commit b107a6e

Please sign in to comment.