Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --skip-simulation and --gas-estimate-multiplier cli options to forge script command #2524

Merged
merged 3 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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