1use super::{ScriptConfig, ScriptResult};
2use crate::build::ScriptPredeployLibraries;
3use alloy_eips::eip7702::SignedAuthorization;
4use alloy_evm::{
5 FromRecoveredTx,
6 revm::context::{Block, Transaction},
7};
8use alloy_network::TransactionBuilder;
9use alloy_primitives::{Address, Bytes, TxKind, U256, map::AddressHashMap};
10use eyre::Result;
11use foundry_cheatcodes::BroadcastableTransaction;
12use foundry_common::{LIBRARY_DEPLOYER, TransactionMaybeSigned};
13use foundry_config::Config;
14use foundry_evm::{
15 constants::CALLER,
16 core::{
17 FoundryBlock, FoundryTransaction,
18 evm::{EvmEnvFor, FoundryEvmNetwork, TransactionRequestFor, TxEnvFor},
19 },
20 executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult},
21 opts::EvmOpts,
22 revm::interpreter::{InstructionResult, return_ok},
23 traces::{TraceKind, Traces},
24};
25use std::collections::VecDeque;
26
27#[derive(Debug)]
29pub struct ScriptRunner<FEN: FoundryEvmNetwork> {
30 pub executor: Executor<FEN>,
31 pub evm_opts: EvmOpts,
32 collect_debug_bytecodes: bool,
33}
34
35impl<FEN: FoundryEvmNetwork> ScriptRunner<FEN> {
36 pub const fn new(executor: Executor<FEN>, evm_opts: EvmOpts) -> Self {
37 Self { executor, evm_opts, collect_debug_bytecodes: false }
38 }
39
40 pub const fn with_debug_bytecodes(mut self, collect_debug_bytecodes: bool) -> Self {
41 self.collect_debug_bytecodes = collect_debug_bytecodes;
42 self
43 }
44
45 fn maybe_debug_bytecodes(
46 &self,
47 debug_bytecodes: AddressHashMap<Bytes>,
48 ) -> AddressHashMap<Bytes> {
49 if self.collect_debug_bytecodes { debug_bytecodes } else { Default::default() }
50 }
51
52 fn extend_debug_bytecodes(
53 &self,
54 target: &mut AddressHashMap<Bytes>,
55 debug_bytecodes: AddressHashMap<Bytes>,
56 ) {
57 if self.collect_debug_bytecodes {
58 target.extend(debug_bytecodes);
59 }
60 }
61
62 fn deploy_local_libraries(
63 &mut self,
64 libraries: &[foundry_linking::LinkedLibrary],
65 debug_bytecodes: &mut AddressHashMap<Bytes>,
66 ) -> Result<()> {
67 if libraries.is_empty() {
68 return Ok(());
69 }
70 let balance = self.executor.get_balance(LIBRARY_DEPLOYER)?;
71 let nonce = self.executor.get_nonce(LIBRARY_DEPLOYER)?;
72 self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?;
73 self.executor.set_nonce(LIBRARY_DEPLOYER, 0)?;
74 for library in libraries {
75 let DeployResult { address, raw } = self
76 .executor
77 .deploy(LIBRARY_DEPLOYER, library.bytecode.clone(), U256::ZERO, None)
78 .map_err(|err| eyre::eyre!("couldn't deploy local library: {err}"))?;
79 eyre::ensure!(
80 library.address == address,
81 "local library deployed at an unexpected address"
82 );
83 self.extend_debug_bytecodes(debug_bytecodes, raw.debug_bytecodes);
84 }
85 self.executor.set_balance(LIBRARY_DEPLOYER, balance)?;
86 self.executor.set_nonce(LIBRARY_DEPLOYER, nonce)?;
87 Ok(())
88 }
89
90 pub fn setup(
92 &mut self,
93 libraries: &ScriptPredeployLibraries,
94 code: Bytes,
95 setup: bool,
96 script_config: &ScriptConfig<FEN>,
97 is_broadcast: bool,
98 ) -> Result<(Address, ScriptResult<FEN::Network>)> {
99 trace!(target: "script", "executing setUP()");
100
101 if !is_broadcast {
102 if self.evm_opts.sender == Config::DEFAULT_SENDER {
103 self.executor.set_balance(self.evm_opts.sender, U256::MAX)?;
105 }
106
107 if script_config.evm_opts.fork_url.is_none()
108 && !script_config.evm_opts.networks.is_tempo()
109 {
110 self.executor.deploy_create2_deployer()?;
111 }
112 }
113
114 let sender_nonce = script_config.sender_nonce;
115 self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?;
116
117 self.executor.set_balance(CALLER, U256::MAX)?;
119
120 let mut library_transactions = VecDeque::new();
121 let mut traces = Traces::default();
122 let mut debug_bytecodes: AddressHashMap<Bytes> = Default::default();
123
124 match libraries {
126 ScriptPredeployLibraries::Default { onchain, local } => {
127 self.deploy_local_libraries(local, &mut debug_bytecodes)?;
128 for library in onchain {
129 let code = &library.bytecode;
130 let RawCallResult {
131 traces: deploy_traces,
132 debug_bytecodes: deploy_debug_bytecodes,
133 ..
134 } = self
135 .executor
136 .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None)
137 .map_err(|err| eyre::eyre!("couldn't deploy library: {err}"))?
138 .raw;
139
140 self.extend_debug_bytecodes(&mut debug_bytecodes, deploy_debug_bytecodes);
141
142 if let Some(deploy_traces) = deploy_traces {
143 traces.push((TraceKind::Deployment, deploy_traces));
144 }
145
146 let mut tx_req = TransactionRequestFor::<FEN>::default()
147 .with_from(self.evm_opts.sender)
148 .with_input(code.clone())
149 .with_nonce(sender_nonce + library_transactions.len() as u64);
150
151 script_config.tempo.apply::<FEN::Network>(&mut tx_req, None);
152
153 library_transactions.push_back(BroadcastableTransaction {
154 rpc: self.evm_opts.fork_url.clone(),
155 transaction: TransactionMaybeSigned::new(tx_req),
156 contract_address: None,
157 })
158 }
159 }
160 ScriptPredeployLibraries::Create2 { onchain, salt, local } => {
161 self.deploy_local_libraries(local, &mut debug_bytecodes)?;
162 let create2_deployer = self.executor.create2_deployer();
163 for library in onchain {
164 let address =
165 create2_deployer.create2_from_code(salt, library.bytecode.as_ref());
166 if !self.executor.is_empty_code(address)? {
168 continue;
169 }
170 let calldata = [salt.as_ref(), library.bytecode.as_ref()].concat();
171 let RawCallResult {
172 traces: deploy_traces,
173 debug_bytecodes: deploy_debug_bytecodes,
174 ..
175 } = self
176 .executor
177 .transact_raw(
178 self.evm_opts.sender,
179 create2_deployer,
180 calldata.clone().into(),
181 U256::ZERO,
182 )
183 .map_err(|err| eyre::eyre!("couldn't deploy library: {err}"))?;
184
185 self.extend_debug_bytecodes(&mut debug_bytecodes, deploy_debug_bytecodes);
186
187 if let Some(deploy_traces) = deploy_traces {
188 traces.push((TraceKind::Deployment, deploy_traces));
189 }
190
191 let mut tx_req = TransactionRequestFor::<FEN>::default()
192 .with_from(self.evm_opts.sender)
193 .with_input(calldata)
194 .with_nonce(sender_nonce + library_transactions.len() as u64)
195 .with_to(create2_deployer);
196
197 script_config.tempo.apply::<FEN::Network>(&mut tx_req, None);
198
199 library_transactions.push_back(BroadcastableTransaction {
200 rpc: self.evm_opts.fork_url.clone(),
201 transaction: TransactionMaybeSigned::new(tx_req),
202 contract_address: None,
203 });
204 }
205
206 self.executor.set_nonce(
209 self.evm_opts.sender,
210 sender_nonce + library_transactions.len() as u64,
211 )?;
212 }
213 };
214
215 let address = CALLER.create(self.executor.get_nonce(CALLER)?);
216
217 self.executor.set_balance(address, self.evm_opts.initial_balance)?;
220
221 let prev_sender_nonce = self.executor.get_nonce(self.evm_opts.sender)?;
228 if self.evm_opts.sender == CALLER {
229 self.executor.set_nonce(self.evm_opts.sender, u64::MAX / 2)?;
230 }
231
232 let DeployResult {
234 address,
235 raw:
236 RawCallResult {
237 mut logs,
238 traces: constructor_traces,
239 debug_bytecodes: constructor_debug_bytecodes,
240 ..
241 },
242 } = self
243 .executor
244 .deploy(CALLER, code, U256::ZERO, None)
245 .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?;
246
247 if self.evm_opts.sender == CALLER {
248 self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?;
249 }
250
251 if script_config.config.script_execution_protection {
253 self.executor.set_script_execution(address);
254 }
255
256 traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));
257 self.extend_debug_bytecodes(&mut debug_bytecodes, constructor_debug_bytecodes);
258
259 let (success, returned, exit_reason, gas_used, labeled_addresses, transactions) = if setup {
261 let result = match self.executor.setup(Some(self.evm_opts.sender), address, None) {
262 Ok(result) => result,
263 Err(EvmError::Execution(err)) => err.raw,
264 Err(err) => return Err(err.into()),
265 };
266 let RawCallResult {
267 reverted,
268 result,
269 exit_reason,
270 traces: setup_traces,
271 labels,
272 logs: setup_logs,
273 gas_used,
274 debug_bytecodes: setup_debug_bytecodes,
275 transactions: setup_transactions,
276 ..
277 } = result;
278 traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
279 logs.extend_from_slice(&setup_logs);
280 self.extend_debug_bytecodes(&mut debug_bytecodes, setup_debug_bytecodes);
281 if let Some(txs) = setup_transactions {
282 library_transactions.extend(txs);
283 }
284 (!reverted, result, exit_reason, gas_used, labels, Some(library_transactions))
285 } else {
286 self.executor.backend_mut().set_test_contract(address);
287 (true, Bytes::new(), None, 0, Default::default(), Some(library_transactions))
288 };
289
290 Ok((
291 address,
292 ScriptResult {
293 returned,
294 exit_reason,
295 success,
296 gas_used,
297 labeled_addresses,
298 debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
299 transactions,
300 logs,
301 traces,
302 address: None,
303 ..Default::default()
304 },
305 ))
306 }
307
308 pub fn script(
310 &mut self,
311 address: Address,
312 calldata: Bytes,
313 ) -> Result<ScriptResult<FEN::Network>> {
314 self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false)
315 }
316
317 pub fn simulate(
319 &mut self,
320 transaction: &TransactionMaybeSigned<FEN::Network>,
321 is_fixed_gas_limit: bool,
322 ) -> Result<ScriptResult<FEN::Network>> {
323 let Some(blob_hashes) = transaction.blob_versioned_hashes() else {
324 return self.simulate_with_tx_env(transaction, is_fixed_gas_limit);
325 };
326
327 let original_tx_env = self.executor.tx_env().clone();
330 let blob_gasprice = self.executor.evm_env().block_env.blob_gasprice().unwrap_or_default();
331 let tx_env = self.executor.tx_env_mut();
332 tx_env.set_blob_hashes(blob_hashes.to_vec());
333 tx_env.set_max_fee_per_blob_gas(blob_gasprice);
334 tx_env.set_tx_type(3);
335 let result = self.simulate_with_tx_env(transaction, is_fixed_gas_limit);
336 *self.executor.tx_env_mut() = original_tx_env;
337 result
338 }
339
340 fn simulate_with_tx_env(
341 &mut self,
342 transaction: &TransactionMaybeSigned<FEN::Network>,
343 is_fixed_gas_limit: bool,
344 ) -> Result<ScriptResult<FEN::Network>> {
345 if is_fixed_gas_limit {
346 let (env, tx) = self.prepare_fixed_gas_env(transaction);
347 return if tx.is_single_create() {
348 let result = self.executor.deploy_with_env(env, tx, None);
349 self.deployment_result(result)
350 } else {
351 let result = self.executor.transact_with_env(env, tx)?;
352 let gas_used = result.gas_used;
353 Ok(self.call_result(result, gas_used))
354 };
355 }
356
357 let from = transaction.from().expect("transaction sender missing");
358 if let Some(to) = transaction.to() {
359 self.call(
360 from,
361 to,
362 transaction.input().cloned().unwrap_or_default(),
363 transaction.value().unwrap_or_default(),
364 transaction.authorization_list(),
365 true,
366 )
367 } else {
368 let res = self.executor.deploy(
369 from,
370 transaction.input().cloned().expect("No data for create transaction"),
371 transaction.value().unwrap_or_default(),
372 None,
373 );
374 self.deployment_result(res)
375 }
376 }
377
378 pub(crate) fn deployment_result(
379 &self,
380 res: Result<DeployResult<FEN>, EvmError<FEN>>,
381 ) -> Result<ScriptResult<FEN::Network>> {
382 let (address, RawCallResult { gas_used, logs, traces, debug_bytecodes, exit_reason, .. }) =
383 match res {
384 Ok(DeployResult { address, raw }) => (address, raw),
385 Err(EvmError::Execution(err)) => {
386 let ExecutionErr { raw, reason } = *err;
387 sh_err!("Failed with `{reason}`:\n")?;
388 (Address::ZERO, raw)
389 }
390 Err(e) => {
391 eyre::bail!("Failed deploying contract: {e:?}");
392 }
393 };
394
395 Ok(ScriptResult {
396 returned: Bytes::new(),
397 success: !address.is_zero(),
398 gas_used,
399 logs,
400 debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
401 traces: traces.map(|traces| vec![(TraceKind::Execution, traces)]).unwrap_or_default(),
403 exit_reason,
404 address: Some(address),
405 ..Default::default()
406 })
407 }
408
409 fn call(
416 &mut self,
417 from: Address,
418 to: Address,
419 calldata: Bytes,
420 value: U256,
421 authorization_list: Option<Vec<SignedAuthorization>>,
422 commit: bool,
423 ) -> Result<ScriptResult<FEN::Network>> {
424 let mut res = if let Some(authorization_list) = &authorization_list {
425 self.executor.call_raw_with_authorization(
426 from,
427 to,
428 calldata.clone(),
429 value,
430 authorization_list.clone(),
431 )?
432 } else {
433 self.executor.call_raw(from, to, calldata.clone(), value)?
434 };
435 let mut gas_used = res.gas_used;
436
437 if commit {
443 gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
444 res = if let Some(authorization_list) = authorization_list {
445 self.executor.transact_raw_with_authorization(
446 from,
447 to,
448 calldata,
449 value,
450 authorization_list,
451 )?
452 } else {
453 self.executor.transact_raw(from, to, calldata, value)?
454 }
455 }
456
457 Ok(self.call_result(res, gas_used))
458 }
459
460 pub(crate) fn call_result(
461 &self,
462 res: RawCallResult<FEN>,
463 gas_used: u64,
464 ) -> ScriptResult<FEN::Network> {
465 let RawCallResult {
466 result,
467 reverted,
468 logs,
469 traces,
470 labels,
471 transactions,
472 debug_bytecodes,
473 exit_reason,
474 cheatcodes,
475 ..
476 } = res;
477 let breakpoints = cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
478
479 ScriptResult {
480 returned: result,
481 success: !reverted,
482 gas_used,
483 logs,
484 debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
485 traces: traces
486 .map(|traces| {
487 vec![(TraceKind::Execution, traces)]
490 })
491 .unwrap_or_default(),
492 labeled_addresses: labels,
493 transactions,
494 exit_reason,
495 address: None,
496 breakpoints,
497 }
498 }
499
500 fn search_optimal_gas_usage(
507 &mut self,
508 res: &RawCallResult<FEN>,
509 from: Address,
510 to: Address,
511 calldata: &Bytes,
512 value: U256,
513 ) -> Result<u64> {
514 let mut gas_used = res.gas_used;
515 if matches!(res.exit_reason, Some(return_ok!())) {
516 let init_gas_limit = self.executor.tx_env().gas_limit();
518
519 let mut search = GasSearch::new(gas_used);
520 while let Some(limit) = search.next_limit() {
521 self.executor.tx_env_mut().set_gas_limit(limit);
522 let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
523 search.record(limit, needs_more_gas(res.exit_reason));
524 }
525 gas_used = search.gas_used();
526 self.executor.tx_env_mut().set_gas_limit(init_gas_limit);
528 }
529 Ok(gas_used)
530 }
531
532 pub(crate) fn prepare_fixed_gas_env(
534 &self,
535 transaction: &TransactionMaybeSigned<FEN::Network>,
536 ) -> (EvmEnvFor<FEN>, TxEnvFor<FEN>) {
537 let from = transaction.from().expect("transaction sender missing");
538 let kind = transaction.to().map_or(TxKind::Create, TxKind::Call);
539 let (mut env, mut tx) = self.executor.prepare_call_env(
540 from,
541 kind,
542 transaction.input().cloned().unwrap_or_default(),
543 transaction.value().unwrap_or_default(),
544 );
545 env.block_env.set_gas_limit(self.executor.evm_env().block_env.gas_limit());
546 match transaction {
547 TransactionMaybeSigned::Signed { tx: signed, .. } => {
548 tx = TxEnvFor::<FEN>::from_recovered_tx(signed, from);
550 }
551 TransactionMaybeSigned::Unsigned(request) => {
552 let access_list = request.access_list().cloned().unwrap_or_default();
553 if !access_list.is_empty() && tx.tx_type() == 0 {
554 tx.set_tx_type(1);
555 }
556 tx.set_access_list(access_list);
557 if let Some(authorization_list) = transaction.authorization_list() {
558 tx.set_signed_authorization(authorization_list);
559 tx.set_tx_type(4);
560 }
561 }
562 }
563 tx.set_gas_price(0);
565 tx.set_gas_priority_fee(None);
566 tx.set_chain_id(Some(env.cfg_env.chain_id));
567 tx.set_gas_limit(transaction.gas().expect("fixed gas limit missing") as u64);
568 (env, tx)
569 }
570}
571
572pub(crate) struct GasSearch {
574 gas_used: u64,
575 highest: u64,
576 lowest: u64,
577 last_highest: u64,
578 done: bool,
579}
580
581impl GasSearch {
582 pub(crate) const fn new(gas_used: u64) -> Self {
583 Self {
584 gas_used,
585 highest: gas_used * 3,
586 lowest: gas_used,
587 last_highest: gas_used * 3,
588 done: false,
589 }
590 }
591
592 pub(crate) const fn next_limit(&self) -> Option<u64> {
593 if !self.done && self.highest - self.lowest > 1 {
594 Some((self.highest + self.lowest) / 2)
595 } else {
596 None
597 }
598 }
599
600 pub(crate) const fn record(&mut self, limit: u64, needs_more_gas: bool) {
603 if needs_more_gas {
604 self.lowest = limit;
605 } else {
606 self.highest = limit;
607 if (self.last_highest - self.highest) * 10 / self.last_highest < 1 {
609 self.gas_used = self.highest;
610 self.done = true;
611 } else {
612 self.last_highest = self.highest;
613 }
614 }
615 }
616
617 pub(crate) const fn gas_used(&self) -> u64 {
618 self.gas_used
619 }
620}
621
622pub(crate) const fn needs_more_gas(exit_reason: Option<InstructionResult>) -> bool {
624 matches!(
625 exit_reason,
626 Some(
627 InstructionResult::Revert | InstructionResult::OutOfGas | InstructionResult::OutOfFunds
628 )
629 )
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635 use alloy_primitives::bytes;
636 use alloy_signer::SignerSync;
637 use alloy_signer_local::PrivateKeySigner;
638 use foundry_evm::{
639 backend::Backend, core::evm::TempoEvmNetwork, executors::ExecutorBuilder, revm::DatabaseRef,
640 };
641 use foundry_evm_networks::NetworkConfigs;
642 use tempo_alloy::primitives::{TempoTransaction, TempoTxEnvelope, transaction::Call};
643
644 #[test]
645 fn successful_probes_keep_existing_ten_percent_stop() {
646 let mut search = GasSearch::new(100);
647 for expected in [200, 150, 125, 112, 106] {
648 assert_eq!(search.next_limit(), Some(expected));
649 search.record(expected, false);
650 }
651 assert_eq!(search.next_limit(), None);
652 assert_eq!(search.gas_used(), 106);
653 }
654
655 #[test]
656 fn unsuccessful_probes_keep_original_estimate() {
657 let mut search = GasSearch::new(100);
658 while let Some(limit) = search.next_limit() {
659 search.record(limit, true);
660 }
661 assert_eq!(search.gas_used(), 100);
662 assert_eq!(GasSearch::new(0).next_limit(), None);
663 }
664
665 #[test]
666 fn only_revert_and_running_out_mean_too_little_gas() {
667 for reason in
668 [InstructionResult::Revert, InstructionResult::OutOfGas, InstructionResult::OutOfFunds]
669 {
670 assert!(needs_more_gas(Some(reason)));
671 }
672 for reason in [
673 Some(InstructionResult::Return),
674 Some(InstructionResult::Stop),
675 Some(InstructionResult::InvalidFEOpcode),
676 None,
677 ] {
678 assert!(!needs_more_gas(reason));
679 }
680 }
681
682 #[test]
683 fn fixed_gas_tempo_create_first_batch() {
684 let signer = PrivateKeySigner::random();
685 let sender = signer.address();
686 let target = sender.create(0);
687 let initcode = bytes!("60075f556008600e5f3960085ff35f546001015f5500");
689 for batch in [false, true] {
690 let mut env = EvmEnvFor::<TempoEvmNetwork>::default();
691 env.cfg_env.chain_id = 31337;
692 env.cfg_env.disable_nonce_check = true;
693 let executor = ExecutorBuilder::<TempoEvmNetwork>::new().gas_limit(30_000_000).build(
694 env,
695 TxEnvFor::<TempoEvmNetwork>::default(),
696 Backend::spawn(None).unwrap(),
697 NetworkConfigs::with_tempo(),
698 );
699 let mut runner = ScriptRunner::new(executor, EvmOpts::default());
700 let mut calls =
701 vec![Call { to: TxKind::Create, value: U256::ZERO, input: initcode.clone() }];
702 if batch {
703 calls.push(Call {
704 to: TxKind::Call(target),
705 value: U256::ZERO,
706 input: Bytes::new(),
707 });
708 }
709 let transaction = TempoTransaction {
710 chain_id: 31337,
711 gas_limit: 2_000_000,
712 calls,
713 ..Default::default()
714 };
715 let signature = signer.sign_hash_sync(&transaction.signature_hash()).unwrap();
716 let transaction = TransactionMaybeSigned::Signed {
717 tx: TempoTxEnvelope::AA(transaction.into_signed(signature.into())),
718 from: sender,
719 };
720 let result = runner.simulate(&transaction, true).unwrap();
721 assert!(result.success);
722 assert_eq!(
723 runner.executor.backend().storage_ref(target, U256::ZERO).unwrap(),
724 U256::from(if batch { 8 } else { 7 }),
725 );
726 assert!(!runner.executor.is_empty_code(target).unwrap());
727 assert_eq!(transaction.gas(), Some(2_000_000));
728 assert_eq!(runner.executor.gas_limit(), 30_000_000);
729 }
730 }
731}