Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changelog/fix-tempo-script-setup-fees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
forge: patch
---

Prevented synthetic Tempo script execution from charging TIP-20 gas fees.
35 changes: 35 additions & 0 deletions crates/forge/tests/cli/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use anvil::{NodeConfig, spawn};
use axum::{Router, body::Bytes as BodyBytes};
use forge_script_sequence::ScriptSequence;
use foundry_compilers::artifacts::EvmVersion;
use foundry_evm::constants::CALLER;
use foundry_test_utils::{
ScriptOutcome, ScriptTester,
rpc::{self, next_http_archive_rpc_url},
Expand Down Expand Up @@ -4805,6 +4806,40 @@ forgetest!(can_execute_script_command_with_tempo, |prj, cmd| {
.assert_success();
});

forgetest_async!(tempo_script_runs_with_zero_fee_token_balance, |prj, cmd| {
foundry_test_utils::util::initialize(prj.root());
let script = prj.add_script(
"TempoScript.s.sol",
r#"
import "forge-std/Script.sol";

contract TempoScript is Script {
function run() external {}
}
"#,
);
prj.update_config(|config| {
config.gas_limit = u64::MAX.into();
config.gas_price = Some(600_000_000);
});

let (api, handle) = spawn(NodeConfig::test_tempo()).await;
let fee_token = address!("0x20c0000000000000000000000000000000000000");
// Synthetic script execution should not require the caller to fund protocol fees.
api.anvil_deal_tip20(CALLER, fee_token, U256::ZERO).await.unwrap();
cmd.arg("script").arg(script).args([
"--rpc-url",
&handle.http_endpoint(),
"--network",
"tempo",
"--gas-price",
"600000000",
"--tempo.fee-token",
"0x20c0000000000000000000000000000000000000",
]);
cmd.assert_success();
Comment thread
mablr marked this conversation as resolved.
});

forgetest_async!(tempo_aa_script_broadcast_deploys_with_fee_token, |prj, cmd| {
foundry_test_utils::util::initialize(prj.root());
let script = prj.add_script(
Expand Down
58 changes: 57 additions & 1 deletion crates/script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,17 @@ impl<FEN: FoundryEvmNetwork> ScriptConfig<FEN> {
target: ArtifactId,
restricted: bool,
) -> Result<ScriptRunner<FEN>> {
self._get_runner(Some((known_contracts, script_wallets, target)), debug, restricted).await
let mut runner = self
._get_runner(Some((known_contracts, script_wallets, target)), debug, restricted)
.await?;

// Script execution is synthetic. Keep the Tempo transaction context, but do not charge
// protocol fees for deploying or calling the local script contract.
if self.evm_opts.networks.is_tempo() {
runner.executor.evm_env_mut().cfg_env.disable_fee_charge = true;
}

Ok(runner)
}

async fn _get_runner(
Expand Down Expand Up @@ -1147,6 +1157,7 @@ mod tests {
CallKind, CallTrace, CallTraceArena, CallTraceNode, SparsedTraceArena, TraceKind,
},
};
use semver::Version;
use std::{fs, num::NonZeroU64, sync::LazyLock};
use tempfile::tempdir;
use tokio::sync::{Mutex, MutexGuard};
Expand Down Expand Up @@ -1936,6 +1947,51 @@ mod tests {
assert_eq!(script.hardfork, Some(FoundryHardfork::Monad(MonadHardfork::MonadNine)));
}

#[tokio::test(flavor = "multi_thread")]
async fn tempo_runner_fee_charge_matches_execution_context() {
let (_api, handle) = spawn(NodeConfig::test_tempo()).await;
let networks = NetworkConfigs::with_tempo();
let evm_opts = EvmOpts {
fork_url: Some(handle.http_endpoint()),
sender: handle.dev_accounts().next().unwrap(),
networks,
..Default::default()
};
let config = Config { networks, ..Default::default() };
let mut script = ScriptConfig::<TempoEvmNetwork>::new(
config,
evm_opts,
false,
TempoOpts::default(),
None,
)
.await
.unwrap();

let rpc_runner = script._get_runner(None, false, false).await.unwrap();
assert!(!rpc_runner.executor.evm_env().cfg_env.disable_fee_charge);

let target = ArtifactId {
path: PathBuf::from("Script.json"),
name: "Script".to_string(),
source: PathBuf::from("Script.sol"),
version: Version::new(0, 8, 30),
build_id: String::new(),
profile: "default".to_string(),
};
let synthetic_runner = script
.get_runner_with_cheatcodes(
ContractsByArtifact::default(),
Wallets::new(Default::default(), None),
false,
target,
false,
)
.await
.unwrap();
assert!(synthetic_runner.executor.evm_env().cfg_env.disable_fee_charge);
}

#[test]
fn can_parse_shared_tempo_opts() {
let args = ScriptArgs::parse_from([
Expand Down
Loading