1use super::string::parse;
4use crate::{
5 Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*, inspector::exec_create,
6};
7use alloy_dyn_abi::DynSolType;
8use alloy_json_abi::ContractObject;
9use alloy_network::{Network, ReceiptResponse};
10use alloy_primitives::{Bytes, U256, hex, map::Entry};
11use alloy_sol_types::SolValue;
12use dialoguer::{Input, Password};
13use forge_script_sequence::{BroadcastReader, TransactionWithMetadata};
14use foundry_common::fs;
15use foundry_config::fs_permissions::FsAccessKind;
16use foundry_evm_core::evm::FoundryEvmNetwork;
17use revm::{
18 context::{Cfg, ContextTr, CreateScheme, JournalTr},
19 interpreter::CreateInputs,
20};
21use revm_inspectors::tracing::types::CallKind;
22use semver::Version;
23use std::{
24 io::{BufRead, BufReader},
25 path::{Path, PathBuf},
26 process::Command,
27 sync::mpsc,
28 thread,
29 time::{SystemTime, UNIX_EPOCH},
30};
31use walkdir::WalkDir;
32
33#[derive(Debug, Default, PartialEq, Eq)]
35struct ParsedArtifactPath<'a> {
36 file: Option<PathBuf>,
37 contract_name: Option<&'a str>,
38 version: Option<Version>,
39 profile: Option<&'a str>,
40}
41
42fn parse_artifact_path(path: &str) -> std::result::Result<ParsedArtifactPath<'_>, String> {
55 let mut parts = path.split(':');
56
57 let mut file = None;
58 let mut contract_name = None;
59 let mut version = None;
60 let mut profile = None;
61
62 let path_or_name = parts.next().unwrap();
63 if path_or_name.contains('.') {
64 file = Some(PathBuf::from(path_or_name));
65 if let Some(name_or_version_or_profile) = parts.next() {
66 if name_or_version_or_profile.contains('.')
67 || Version::parse(name_or_version_or_profile).is_ok()
68 {
69 version = Some(name_or_version_or_profile);
70 } else {
71 contract_name = Some(name_or_version_or_profile);
72 if let Some(version_or_profile) = parts.next() {
73 if version_or_profile.contains('.')
74 || Version::parse(version_or_profile).is_ok()
75 {
76 version = Some(version_or_profile);
77 } else {
78 profile = Some(version_or_profile);
79 }
80 }
81 }
82 }
83 } else {
84 contract_name = Some(path_or_name);
85 if let Some(version_or_profile) = parts.next() {
86 if version_or_profile.contains('.') || Version::parse(version_or_profile).is_ok() {
87 version = Some(version_or_profile);
88 } else {
89 profile = Some(version_or_profile);
90 }
91 }
92 }
93
94 let version = if let Some(version) = version {
95 Some(Version::parse(version).map_err(|e| format!("failed parsing version: {e}"))?)
96 } else {
97 None
98 };
99
100 Ok(ParsedArtifactPath { file, contract_name, version, profile })
101}
102
103impl Cheatcode for existsCall {
104 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
105 let Self { path } = self;
106 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
107 Ok(path.exists().abi_encode())
108 }
109}
110
111impl Cheatcode for fsMetadataCall {
112 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
113 let Self { path } = self;
114 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
115
116 let metadata = path.metadata()?;
117
118 let [modified, accessed, created] =
120 [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| {
121 time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
122 });
123
124 Ok(FsMetadata {
125 isDir: metadata.is_dir(),
126 isSymlink: metadata.is_symlink(),
127 length: U256::from(metadata.len()),
128 readOnly: metadata.permissions().readonly(),
129 modified: U256::from(modified),
130 accessed: U256::from(accessed),
131 created: U256::from(created),
132 }
133 .abi_encode())
134 }
135}
136
137impl Cheatcode for isDirCall {
138 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
139 let Self { path } = self;
140 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
141 Ok(path.is_dir().abi_encode())
142 }
143}
144
145impl Cheatcode for isFileCall {
146 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
147 let Self { path } = self;
148 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
149 Ok(path.is_file().abi_encode())
150 }
151}
152
153impl Cheatcode for projectRootCall {
154 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
155 let Self {} = self;
156 Ok(state.config.root.display().to_string().abi_encode())
157 }
158}
159
160impl Cheatcode for currentFilePathCall {
161 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
162 let Self {} = self;
163 let artifact = state
164 .config
165 .running_artifact
166 .as_ref()
167 .ok_or_else(|| fmt_err!("no running contract found"))?;
168 let relative = artifact.source.strip_prefix(&state.config.root).unwrap_or(&artifact.source);
169 Ok(relative.display().to_string().abi_encode())
170 }
171}
172
173impl Cheatcode for unixTimeCall {
174 fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
175 let Self {} = self;
176 let difference = SystemTime::now()
177 .duration_since(UNIX_EPOCH)
178 .map_err(|e| fmt_err!("failed getting Unix timestamp: {e}"))?;
179 Ok(difference.as_millis().abi_encode())
180 }
181}
182
183impl Cheatcode for closeFileCall {
184 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
185 let Self { path } = self;
186 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
187
188 state.test_context.opened_read_files.remove(&path);
189
190 Ok(Default::default())
191 }
192}
193
194impl Cheatcode for copyFileCall {
195 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
196 let Self { from, to } = self;
197 let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?;
198 let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?;
199 state.config.ensure_not_foundry_toml(&to)?;
200
201 let n = fs::copy(from, to)?;
202 Ok(n.abi_encode())
203 }
204}
205
206impl Cheatcode for createDirCall {
207 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
208 let Self { path, recursive } = self;
209 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
210 if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?;
211 Ok(Default::default())
212 }
213}
214
215impl Cheatcode for readDir_0Call {
216 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
217 let Self { path } = self;
218 read_dir(state, path.as_ref(), 1, false)
219 }
220}
221
222impl Cheatcode for readDir_1Call {
223 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
224 let Self { path, maxDepth } = self;
225 read_dir(state, path.as_ref(), *maxDepth, false)
226 }
227}
228
229impl Cheatcode for readDir_2Call {
230 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
231 let Self { path, maxDepth, followLinks } = self;
232 read_dir(state, path.as_ref(), *maxDepth, *followLinks)
233 }
234}
235
236impl Cheatcode for readFileCall {
237 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
238 let Self { path } = self;
239 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
240 Ok(fs::locked_read_to_string(path)?.abi_encode())
241 }
242}
243
244impl Cheatcode for readFileBinaryCall {
245 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
246 let Self { path } = self;
247 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
248 Ok(fs::locked_read(path)?.abi_encode())
249 }
250}
251
252impl Cheatcode for readLineCall {
253 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
254 let Self { path } = self;
255 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
256
257 let reader = match state.test_context.opened_read_files.entry(path.clone()) {
259 Entry::Occupied(entry) => entry.into_mut(),
260 Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)),
261 };
262
263 let mut line: String = String::new();
264 reader.read_line(&mut line)?;
265
266 if line.ends_with('\n') {
268 line.pop();
269 if line.ends_with('\r') {
270 line.pop();
271 }
272 }
273
274 Ok(line.abi_encode())
275 }
276}
277
278impl Cheatcode for readLinkCall {
279 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
280 let Self { linkPath: path } = self;
281 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
282 let target = fs::read_link(path)?;
283 Ok(target.display().to_string().abi_encode())
284 }
285}
286
287impl Cheatcode for removeDirCall {
288 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
289 let Self { path, recursive } = self;
290 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
291 if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?;
292 Ok(Default::default())
293 }
294}
295
296impl Cheatcode for removeFileCall {
297 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
298 let Self { path } = self;
299 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
300 state.config.ensure_not_foundry_toml(&path)?;
301
302 state.test_context.opened_read_files.remove(&path);
304
305 if state.fs_commit {
306 fs::remove_file(&path)?;
307 }
308
309 Ok(Default::default())
310 }
311}
312
313impl Cheatcode for writeFileCall {
314 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
315 let Self { path, data } = self;
316 write_file(state, path.as_ref(), data.as_bytes())
317 }
318}
319
320impl Cheatcode for writeFileBinaryCall {
321 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
322 let Self { path, data } = self;
323 write_file(state, path.as_ref(), data)
324 }
325}
326
327impl Cheatcode for writeLineCall {
328 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
329 let Self { path, data: line } = self;
330 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
331 state.config.ensure_not_foundry_toml(&path)?;
332
333 if state.fs_commit {
334 fs::locked_write_line(path, line)?;
335 }
336
337 Ok(Default::default())
338 }
339}
340
341impl Cheatcode for getArtifactPathByCodeCall {
342 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
343 let Self { code } = self;
344 let (artifact_id, _) = state
345 .config
346 .available_artifacts
347 .as_ref()
348 .and_then(|artifacts| artifacts.find_by_creation_code(code))
349 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
350
351 Ok(artifact_id.path.to_string_lossy().abi_encode())
352 }
353}
354
355impl Cheatcode for getArtifactPathByDeployedCodeCall {
356 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
357 let Self { deployedCode } = self;
358 let (artifact_id, _) = state
359 .config
360 .available_artifacts
361 .as_ref()
362 .and_then(|artifacts| artifacts.find_by_deployed_code(deployedCode))
363 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
364
365 Ok(artifact_id.path.to_string_lossy().abi_encode())
366 }
367}
368
369impl Cheatcode for getCodeCall {
370 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
371 let Self { artifactPath: path } = self;
372 Ok(get_artifact_code(state, path, false)?.abi_encode())
373 }
374}
375
376impl Cheatcode for getDeployedCodeCall {
377 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
378 let Self { artifactPath: path } = self;
379 Ok(get_artifact_code(state, path, true)?.abi_encode())
380 }
381}
382
383impl Cheatcode for deployCode_0Call {
384 fn apply_full<FEN: FoundryEvmNetwork>(
385 &self,
386 ccx: &mut CheatsCtxt<'_, '_, FEN>,
387 executor: &mut dyn CheatcodesExecutor<FEN>,
388 ) -> Result {
389 let Self { artifactPath: path } = self;
390 deploy_code(ccx, executor, path, None, None, None)
391 }
392}
393
394impl Cheatcode for deployCode_1Call {
395 fn apply_full<FEN: FoundryEvmNetwork>(
396 &self,
397 ccx: &mut CheatsCtxt<'_, '_, FEN>,
398 executor: &mut dyn CheatcodesExecutor<FEN>,
399 ) -> Result {
400 let Self { artifactPath: path, constructorArgs: args } = self;
401 deploy_code(ccx, executor, path, Some(args), None, None)
402 }
403}
404
405impl Cheatcode for deployCode_2Call {
406 fn apply_full<FEN: FoundryEvmNetwork>(
407 &self,
408 ccx: &mut CheatsCtxt<'_, '_, FEN>,
409 executor: &mut dyn CheatcodesExecutor<FEN>,
410 ) -> Result {
411 let Self { artifactPath: path, value } = self;
412 deploy_code(ccx, executor, path, None, Some(*value), None)
413 }
414}
415
416impl Cheatcode for deployCode_3Call {
417 fn apply_full<FEN: FoundryEvmNetwork>(
418 &self,
419 ccx: &mut CheatsCtxt<'_, '_, FEN>,
420 executor: &mut dyn CheatcodesExecutor<FEN>,
421 ) -> Result {
422 let Self { artifactPath: path, constructorArgs: args, value } = self;
423 deploy_code(ccx, executor, path, Some(args), Some(*value), None)
424 }
425}
426
427impl Cheatcode for deployCode_4Call {
428 fn apply_full<FEN: FoundryEvmNetwork>(
429 &self,
430 ccx: &mut CheatsCtxt<'_, '_, FEN>,
431 executor: &mut dyn CheatcodesExecutor<FEN>,
432 ) -> Result {
433 let Self { artifactPath: path, salt } = self;
434 deploy_code(ccx, executor, path, None, None, Some((*salt).into()))
435 }
436}
437
438impl Cheatcode for deployCode_5Call {
439 fn apply_full<FEN: FoundryEvmNetwork>(
440 &self,
441 ccx: &mut CheatsCtxt<'_, '_, FEN>,
442 executor: &mut dyn CheatcodesExecutor<FEN>,
443 ) -> Result {
444 let Self { artifactPath: path, constructorArgs: args, salt } = self;
445 deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into()))
446 }
447}
448
449impl Cheatcode for deployCode_6Call {
450 fn apply_full<FEN: FoundryEvmNetwork>(
451 &self,
452 ccx: &mut CheatsCtxt<'_, '_, FEN>,
453 executor: &mut dyn CheatcodesExecutor<FEN>,
454 ) -> Result {
455 let Self { artifactPath: path, value, salt } = self;
456 deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into()))
457 }
458}
459
460impl Cheatcode for deployCode_7Call {
461 fn apply_full<FEN: FoundryEvmNetwork>(
462 &self,
463 ccx: &mut CheatsCtxt<'_, '_, FEN>,
464 executor: &mut dyn CheatcodesExecutor<FEN>,
465 ) -> Result {
466 let Self { artifactPath: path, constructorArgs: args, value, salt } = self;
467 deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into()))
468 }
469}
470
471fn deploy_code<FEN: FoundryEvmNetwork>(
474 ccx: &mut CheatsCtxt<'_, '_, FEN>,
475 executor: &mut dyn CheatcodesExecutor<FEN>,
476 path: &str,
477 constructor_args: Option<&Bytes>,
478 value: Option<U256>,
479 salt: Option<U256>,
480) -> Result {
481 let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
482
483 if let Some(broadcast) = &mut ccx.state.broadcast {
485 broadcast.deploy_from_code = true;
486 }
487
488 if let Some(args) = constructor_args {
489 bytecode.extend_from_slice(args);
490 }
491
492 let scheme =
493 if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create };
494
495 let caller =
497 ccx.state.get_prank(ccx.ecx.journal().depth()).map_or(ccx.caller, |prank| prank.new_caller);
498
499 let outcome = exec_create(
500 executor,
501 CreateInputs::new(
502 caller,
503 scheme,
504 value.unwrap_or(U256::ZERO),
505 bytecode.into(),
506 ccx.gas_limit,
507 0,
508 ),
509 ccx,
510 )?;
511
512 if !outcome.result.result.is_ok() {
513 return Err(crate::Error::from(outcome.result.output));
514 }
515
516 let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?;
517
518 Ok(address.abi_encode())
519}
520
521fn get_artifact_code<FEN: FoundryEvmNetwork>(
539 state: &Cheatcodes<FEN>,
540 path: &str,
541 deployed: bool,
542) -> Result<Bytes> {
543 let path = if path.ends_with(".json") {
544 PathBuf::from(path)
545 } else {
546 let parsed = parse_artifact_path(path)
547 .map_err(|e| fmt_err!("failed to parse artifact path: {e}"))?;
548 let ParsedArtifactPath { file, contract_name, version, profile } = parsed;
549
550 if let Some(artifacts) = &state.config.available_artifacts {
552 let ambiguous_file_profile =
553 file.is_some() && version.is_none() && profile.is_none() && contract_name.is_some();
554 let filter_artifacts = |treat_ambiguous_as_profile: bool| -> Vec<_> {
555 artifacts
556 .iter()
557 .filter(|(id, _)| {
558 let id_name = id.name.split('.').next().unwrap();
560
561 if let Some(path) = &file
562 && !id.source.ends_with(path)
563 {
564 return false;
565 }
566 if let Some(ref version) = version
567 && (id.version.minor != version.minor
568 || id.version.major != version.major
569 || id.version.patch != version.patch)
570 {
571 return false;
572 }
573 if let Some(profile) = profile
574 && id.profile != profile
575 {
576 return false;
577 }
578 if let Some(name) = contract_name {
579 if treat_ambiguous_as_profile && ambiguous_file_profile {
580 return id.profile == name;
581 }
582
583 return id_name == name;
584 }
585
586 true
587 })
588 .collect()
589 };
590
591 let mut filtered = filter_artifacts(false);
592 if filtered.is_empty() && ambiguous_file_profile {
593 filtered = filter_artifacts(true);
594 }
595
596 let artifact = match &filtered[..] {
597 [] => None,
598 [artifact] => Some(Ok(*artifact)),
599 filtered => {
600 let mut filtered = filtered.to_vec();
601 Some(
603 state
604 .config
605 .running_artifact
606 .as_ref()
607 .and_then(|running| {
608 if version.is_none() {
610 filtered.retain(|(id, _)| id.version == running.version);
611
612 if filtered.len() == 1 {
614 return Some(filtered[0]);
615 }
616 }
617
618 if profile.is_none() {
620 filtered.retain(|(id, _)| id.profile == running.profile);
621
622 return (filtered.len() == 1).then(|| filtered[0]);
623 }
624
625 None
626 })
627 .ok_or_else(|| fmt_err!("multiple matching artifacts found")),
628 )
629 }
630 };
631
632 if let Some(artifact) = artifact {
633 let artifact = artifact?;
634 let maybe_bytecode = if deployed {
635 artifact.1.deployed_bytecode().cloned()
636 } else {
637 artifact.1.bytecode().cloned()
638 };
639
640 return maybe_bytecode.ok_or_else(|| {
641 fmt_err!("no bytecode for contract; is it abstract or unlinked?")
642 });
643 }
644 }
645
646 let path_in_artifacts = match (file.map(|f| f.to_string_lossy().to_string()), contract_name)
648 {
649 (Some(file), Some(contract_name)) => {
650 PathBuf::from(format!("{file}/{contract_name}.json"))
651 }
652 (None, Some(contract_name)) => {
653 PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
654 }
655 (Some(file), None) => {
656 let name = file.replace(".sol", "");
657 PathBuf::from(format!("{file}/{name}.json"))
658 }
659 _ => bail!("invalid artifact path"),
660 };
661
662 state.config.paths.artifacts.join(path_in_artifacts)
663 };
664
665 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
666 let data = fs::read_to_string(path).map_err(|e| {
667 if state.config.available_artifacts.is_some() {
668 fmt_err!("no matching artifact found")
669 } else {
670 e.into()
671 }
672 })?;
673 let artifact = serde_json::from_str::<ContractObject>(&data)?;
674 let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode };
675 maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"))
676}
677
678impl Cheatcode for ffiCall {
679 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
680 let Self { commandInput: input } = self;
681
682 let output = ffi(state, input)?;
683
684 if output.exitCode != 0 {
686 return Err(fmt_err!(
688 "ffi command {:?} exited with code {}. stderr: {}",
689 input,
690 output.exitCode,
691 String::from_utf8_lossy(&output.stderr)
692 ));
693 }
694
695 if !output.stderr.is_empty() {
697 let stderr = String::from_utf8_lossy(&output.stderr);
698 warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr");
699 }
700
701 Ok(output.stdout.abi_encode())
703 }
704}
705
706impl Cheatcode for tryFfiCall {
707 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
708 let Self { commandInput: input } = self;
709 ffi(state, input).map(|res| res.abi_encode())
710 }
711}
712
713impl Cheatcode for promptCall {
714 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
715 let Self { promptText: text } = self;
716 prompt(state, text, prompt_input).map(|res| res.abi_encode())
717 }
718}
719
720impl Cheatcode for promptSecretCall {
721 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
722 let Self { promptText: text } = self;
723 prompt(state, text, prompt_password).map(|res| res.abi_encode())
724 }
725}
726
727impl Cheatcode for promptSecretUintCall {
728 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
729 let Self { promptText: text } = self;
730 parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256))
731 }
732}
733
734impl Cheatcode for promptAddressCall {
735 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
736 let Self { promptText: text } = self;
737 parse(&prompt(state, text, prompt_input)?, &DynSolType::Address)
738 }
739}
740
741impl Cheatcode for promptUintCall {
742 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
743 let Self { promptText: text } = self;
744 parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256))
745 }
746}
747
748pub(super) fn write_file<FEN: FoundryEvmNetwork>(
749 state: &Cheatcodes<FEN>,
750 path: &Path,
751 contents: &[u8],
752) -> Result {
753 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
754 state.config.ensure_not_foundry_toml(&path)?;
756
757 if state.fs_commit {
758 fs::locked_write(path, contents)?;
759 }
760
761 Ok(Default::default())
762}
763
764fn read_dir<FEN: FoundryEvmNetwork>(
765 state: &Cheatcodes<FEN>,
766 path: &Path,
767 max_depth: u64,
768 follow_links: bool,
769) -> Result {
770 let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
771 let paths: Vec<DirEntry> = WalkDir::new(root)
772 .min_depth(1)
773 .max_depth(max_depth.try_into().unwrap_or(usize::MAX))
774 .follow_links(follow_links)
775 .contents_first(false)
776 .same_file_system(true)
777 .sort_by_file_name()
778 .into_iter()
779 .map(|entry| match entry {
780 Ok(entry) => DirEntry {
781 errorMessage: String::new(),
782 path: entry.path().display().to_string(),
783 depth: entry.depth() as u64,
784 isDir: entry.file_type().is_dir(),
785 isSymlink: entry.path_is_symlink(),
786 },
787 Err(e) => DirEntry {
788 errorMessage: e.to_string(),
789 path: e.path().map(|p| p.display().to_string()).unwrap_or_default(),
790 depth: e.depth() as u64,
791 isDir: false,
792 isSymlink: false,
793 },
794 })
795 .collect();
796 Ok(paths.abi_encode())
797}
798
799fn ffi<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>, input: &[String]) -> Result<FfiResult> {
800 ensure!(
801 state.config.ffi,
802 "FFI is disabled; add the `--ffi` flag to allow tests to call external commands"
803 );
804 ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command");
805 let mut cmd = Command::new(&input[0]);
806 cmd.args(&input[1..]);
807
808 debug!(target: "cheatcodes", ?cmd, "invoking ffi");
809
810 let output = cmd
811 .current_dir(&state.config.root)
812 .output()
813 .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?;
814
815 let trimmed_stdout = String::from_utf8(output.stdout)?;
818 let trimmed_stdout = trimmed_stdout.trim();
819 let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) {
820 hex
821 } else {
822 trimmed_stdout.as_bytes().to_vec()
823 };
824 Ok(FfiResult {
825 exitCode: output.status.code().unwrap_or(69),
826 stdout: encoded_stdout.into(),
827 stderr: output.stderr.into(),
828 })
829}
830
831fn prompt_input(prompt_text: &str) -> Result<String, dialoguer::Error> {
832 Input::new().allow_empty(true).with_prompt(prompt_text).interact_text()
833}
834
835fn prompt_password(prompt_text: &str) -> Result<String, dialoguer::Error> {
836 Password::new().with_prompt(prompt_text).interact()
837}
838
839fn prompt<FEN: FoundryEvmNetwork>(
840 state: &Cheatcodes<FEN>,
841 prompt_text: &str,
842 input: fn(&str) -> Result<String, dialoguer::Error>,
843) -> Result<String> {
844 let text_clone = prompt_text.to_string();
845 let timeout = state.config.prompt_timeout;
846 let (tx, rx) = mpsc::channel();
847
848 thread::spawn(move || {
849 let _ = tx.send(input(&text_clone));
850 });
851
852 match rx.recv_timeout(timeout) {
853 Ok(res) => res.map_err(|err| {
854 let _ = sh_println!();
855 err.to_string().into()
856 }),
857 Err(_) => {
858 let _ = sh_eprintln!();
859 Err("Prompt timed out".into())
860 }
861 }
862}
863
864impl Cheatcode for getBroadcastCall {
865 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
866 let Self { contractName, chainId, txType } = self;
867
868 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
869 contractName,
870 *chainId,
871 &state.config.broadcast,
872 vec![map_broadcast_tx_type(*txType)],
873 )?;
874
875 Ok(latest_broadcast.abi_encode())
876 }
877}
878
879impl Cheatcode for getBroadcasts_0Call {
880 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
881 let Self { contractName, chainId, txType } = self;
882
883 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
884 .with_tx_type(map_broadcast_tx_type(*txType));
885
886 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
887
888 let summaries = broadcasts
889 .into_iter()
890 .flat_map(|broadcast| {
891 let results = reader.into_tx_receipts(broadcast);
892 parse_broadcast_results(results)
893 })
894 .collect::<Vec<_>>();
895
896 Ok(summaries.abi_encode())
897 }
898}
899
900impl Cheatcode for getBroadcasts_1Call {
901 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
902 let Self { contractName, chainId } = self;
903
904 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?;
905
906 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
907
908 let summaries = broadcasts
909 .into_iter()
910 .flat_map(|broadcast| {
911 let results = reader.into_tx_receipts(broadcast);
912 parse_broadcast_results(results)
913 })
914 .collect::<Vec<_>>();
915
916 Ok(summaries.abi_encode())
917 }
918}
919
920impl Cheatcode for getDeployment_0Call {
921 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
922 let Self { contractName } = self;
923 let chain_id = ccx.ecx.cfg().chain_id();
924
925 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
926 contractName,
927 chain_id,
928 &ccx.state.config.broadcast,
929 vec![CallKind::Create, CallKind::Create2],
930 )?;
931
932 Ok(latest_broadcast.contractAddress.abi_encode())
933 }
934}
935
936impl Cheatcode for getDeployment_1Call {
937 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
938 let Self { contractName, chainId } = self;
939
940 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
941 contractName,
942 *chainId,
943 &state.config.broadcast,
944 vec![CallKind::Create, CallKind::Create2],
945 )?;
946
947 Ok(latest_broadcast.contractAddress.abi_encode())
948 }
949}
950
951impl Cheatcode for getDeploymentsCall {
952 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
953 let Self { contractName, chainId } = self;
954
955 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
956 .with_tx_type(CallKind::Create)
957 .with_tx_type(CallKind::Create2);
958
959 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
960
961 let summaries = broadcasts
962 .into_iter()
963 .flat_map(|broadcast| {
964 let results = reader.into_tx_receipts(broadcast);
965 parse_broadcast_results(results)
966 })
967 .collect::<Vec<_>>();
968
969 let deployed_addresses =
970 summaries.into_iter().map(|summary| summary.contractAddress).collect::<Vec<_>>();
971
972 Ok(deployed_addresses.abi_encode())
973 }
974}
975
976fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind {
977 match tx_type {
978 BroadcastTxType::Call => CallKind::Call,
979 BroadcastTxType::Create => CallKind::Create,
980 BroadcastTxType::Create2 => CallKind::Create2,
981 _ => unreachable!("invalid tx type"),
982 }
983}
984
985fn parse_broadcast_results<N: Network>(
986 results: Vec<(TransactionWithMetadata<N>, N::ReceiptResponse)>,
987) -> Vec<BroadcastTxSummary> {
988 results
989 .into_iter()
990 .map(|(tx, receipt)| BroadcastTxSummary {
991 txHash: receipt.transaction_hash(),
992 blockNumber: receipt.block_number().unwrap_or_default(),
993 txType: match tx.call_kind {
994 CallKind::Call => BroadcastTxType::Call,
995 CallKind::Create => BroadcastTxType::Create,
996 CallKind::Create2 => BroadcastTxType::Create2,
997 _ => unreachable!("invalid tx type"),
998 },
999 contractAddress: tx.contract_address.unwrap_or_default(),
1000 success: receipt.status(),
1001 })
1002 .collect()
1003}
1004
1005fn latest_broadcast<N: Network>(
1006 contract_name: &String,
1007 chain_id: u64,
1008 broadcast_path: &Path,
1009 filters: Vec<CallKind>,
1010) -> Result<BroadcastTxSummary>
1011where
1012 N::TxEnvelope: for<'d> serde::Deserialize<'d>,
1013{
1014 let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?;
1015
1016 for filter in filters {
1017 reader = reader.with_tx_type(filter);
1018 }
1019
1020 let broadcast = reader.read_latest::<N>()?;
1021
1022 let results = reader.into_tx_receipts(broadcast);
1023
1024 let summaries = parse_broadcast_results(results);
1025
1026 summaries
1027 .first()
1028 .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}"))
1029 .cloned()
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use crate::CheatsConfig;
1036 use alloy_primitives::{address, b256};
1037 use foundry_common::ContractsByArtifact;
1038 use foundry_compilers::{
1039 ArtifactId,
1040 artifacts::{BytecodeObject, CompactBytecode, CompactContractBytecode},
1041 };
1042 use foundry_evm_core::evm::TempoEvmNetwork;
1043 use std::{env, fs as stdfs, sync::Arc};
1044
1045 fn cheats() -> Cheatcodes {
1046 let config = CheatsConfig {
1047 ffi: true,
1048 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
1049 ..Default::default()
1050 };
1051 Cheatcodes::new(Arc::new(config))
1052 }
1053
1054 #[test]
1055 fn test_ffi_hex() {
1056 let msg = b"gm";
1057 let cheats = cheats();
1058 let args = ["echo".to_string(), hex::encode(msg)];
1059 let output = ffi(&cheats, &args).unwrap();
1060 assert_eq!(output.stdout, Bytes::from(msg));
1061 }
1062
1063 #[test]
1064 fn test_ffi_string() {
1065 let msg = "gm";
1066 let cheats = cheats();
1067 let args = ["echo".to_string(), msg.to_string()];
1068 let output = ffi(&cheats, &args).unwrap();
1069 assert_eq!(output.stdout, Bytes::from(msg.as_bytes()));
1070 }
1071
1072 #[test]
1073 fn test_ffi_fails_on_error_code() {
1074 let mut cheats = cheats();
1075
1076 #[cfg(unix)]
1078 let args = vec!["false".to_string()];
1079 #[cfg(windows)]
1080 let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()];
1081
1082 let result = Cheatcode::apply(&ffiCall { commandInput: args }, &mut cheats);
1083
1084 assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded");
1086
1087 let err_msg = result.unwrap_err().to_string();
1089 assert!(
1090 err_msg.contains("exited with code 1"),
1091 "Error message did not contain exit code: {err_msg}"
1092 );
1093 }
1094
1095 #[test]
1096 fn test_artifact_parsing() {
1097 let s = include_str!("../../evm/test-data/solc-obj.json");
1098 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1099 assert!(artifact.bytecode.is_some());
1100
1101 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1102 assert!(artifact.deployed_bytecode.is_some());
1103 }
1104
1105 #[test]
1106 fn test_alloy_json_abi_rejects_unlinked_bytecode() {
1107 let artifact_json = r#"{
1108 "abi": [],
1109 "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868"
1110 }"#;
1111
1112 let result: Result<ContractObject, _> = serde_json::from_str(artifact_json);
1113 assert!(result.is_err(), "should reject unlinked bytecode with placeholders");
1114 let err = result.unwrap_err().to_string();
1115 assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder"));
1116 }
1117
1118 #[test]
1119 fn test_parse_artifact_path_file_only() {
1120 let parsed = super::parse_artifact_path("path/to/Contract.sol").unwrap();
1121 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1122 assert_eq!(parsed.contract_name, None);
1123 assert_eq!(parsed.version, None);
1124 assert_eq!(parsed.profile, None);
1125 }
1126
1127 #[test]
1128 fn test_parse_artifact_path_file_and_contract() {
1129 let parsed = super::parse_artifact_path("path/to/Contract.sol:MyContract").unwrap();
1130 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1131 assert_eq!(parsed.contract_name, Some("MyContract"));
1132 assert_eq!(parsed.version, None);
1133 assert_eq!(parsed.profile, None);
1134 }
1135
1136 #[test]
1137 fn test_parse_artifact_path_file_contract_version() {
1138 let parsed = super::parse_artifact_path("path/to/Contract.sol:MyContract:0.8.23").unwrap();
1139 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1140 assert_eq!(parsed.contract_name, Some("MyContract"));
1141 assert_eq!(parsed.version, Some(semver::Version::new(0, 8, 23)));
1142 assert_eq!(parsed.profile, None);
1143 }
1144
1145 #[test]
1146 fn test_parse_artifact_path_file_contract_profile() {
1147 let parsed =
1148 super::parse_artifact_path("path/to/Contract.sol:MyContract:optimized").unwrap();
1149 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1150 assert_eq!(parsed.contract_name, Some("MyContract"));
1151 assert_eq!(parsed.version, None);
1152 assert_eq!(parsed.profile, Some("optimized"));
1153 }
1154
1155 #[test]
1156 fn test_parse_artifact_path_file_and_version() {
1157 let parsed = super::parse_artifact_path("path/to/Contract.sol:0.8.18").unwrap();
1158 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1159 assert_eq!(parsed.contract_name, None);
1160 assert_eq!(parsed.version, Some(semver::Version::new(0, 8, 18)));
1161 assert_eq!(parsed.profile, None);
1162 }
1163
1164 #[test]
1165 fn test_parse_artifact_path_file_and_profile() {
1166 let parsed = super::parse_artifact_path("Contract.sol:paris").unwrap();
1169 assert_eq!(parsed.file, Some(PathBuf::from("Contract.sol")));
1170 assert_eq!(parsed.contract_name, Some("paris"));
1171 assert_eq!(parsed.version, None);
1172 assert_eq!(parsed.profile, None);
1173 }
1174
1175 fn test_artifact(
1176 source: &str,
1177 name: &str,
1178 profile: &str,
1179 bytecode: Bytes,
1180 ) -> (ArtifactId, CompactContractBytecode) {
1181 (
1182 ArtifactId {
1183 path: PathBuf::from(format!("{source}/{name}.json")),
1184 name: name.to_owned(),
1185 source: PathBuf::from(source),
1186 version: Version::new(0, 8, 30),
1187 build_id: String::new(),
1188 profile: profile.to_owned(),
1189 },
1190 CompactContractBytecode {
1191 abi: Some(Default::default()),
1192 bytecode: Some(CompactBytecode {
1193 object: BytecodeObject::Bytecode(bytecode),
1194 source_map: None,
1195 link_references: Default::default(),
1196 }),
1197 deployed_bytecode: None,
1198 },
1199 )
1200 }
1201
1202 #[test]
1203 fn test_get_artifact_code_resolves_file_profile_ambiguity() {
1204 let default_bytecode = Bytes::from_static(&[0x60, 0x01]);
1205 let paris_bytecode = Bytes::from_static(&[0x60, 0x02]);
1206 let source = "src/GetCodeProfile.t.sol";
1207 let artifacts = ContractsByArtifact::new([
1208 test_artifact(source, "GetCodeProfile", "default", default_bytecode),
1209 test_artifact(source, "GetCodeProfile", "paris", paris_bytecode.clone()),
1210 ]);
1211 let config = CheatsConfig {
1212 available_artifacts: Some(artifacts),
1213 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
1214 ..Default::default()
1215 };
1216 let cheats: Cheatcodes = Cheatcodes::new(Arc::new(config));
1217
1218 let bytecode =
1219 super::get_artifact_code(&cheats, "src/GetCodeProfile.t.sol:paris", false).unwrap();
1220
1221 assert_eq!(bytecode, paris_bytecode);
1222 }
1223
1224 #[test]
1225 fn test_get_artifact_code_prefers_contract_name_over_file_profile_ambiguity() {
1226 let profile_bytecode = Bytes::from_static(&[0x60, 0x02]);
1227 let contract_bytecode = Bytes::from_static(&[0x60, 0x03]);
1228 let source = "src/GetCodeProfile.t.sol";
1229 let artifacts = ContractsByArtifact::new([
1230 test_artifact(source, "GetCodeProfile", "paris", profile_bytecode),
1231 test_artifact(source, "paris", "default", contract_bytecode.clone()),
1232 ]);
1233 let config = CheatsConfig {
1234 available_artifacts: Some(artifacts),
1235 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
1236 ..Default::default()
1237 };
1238 let cheats: Cheatcodes = Cheatcodes::new(Arc::new(config));
1239
1240 let bytecode =
1241 super::get_artifact_code(&cheats, "src/GetCodeProfile.t.sol:paris", false).unwrap();
1242
1243 assert_eq!(bytecode, contract_bytecode);
1244 }
1245
1246 #[test]
1247 fn test_parse_artifact_path_contract_only() {
1248 let parsed = super::parse_artifact_path("MyContract").unwrap();
1249 assert_eq!(parsed.file, None);
1250 assert_eq!(parsed.contract_name, Some("MyContract"));
1251 assert_eq!(parsed.version, None);
1252 assert_eq!(parsed.profile, None);
1253 }
1254
1255 #[test]
1256 fn test_parse_artifact_path_contract_and_version() {
1257 let parsed = super::parse_artifact_path("MyContract:0.8.23").unwrap();
1258 assert_eq!(parsed.file, None);
1259 assert_eq!(parsed.contract_name, Some("MyContract"));
1260 assert_eq!(parsed.version, Some(semver::Version::new(0, 8, 23)));
1261 assert_eq!(parsed.profile, None);
1262 }
1263
1264 #[test]
1265 fn test_parse_artifact_path_contract_and_profile() {
1266 let parsed = super::parse_artifact_path("MyContract:optimized").unwrap();
1267 assert_eq!(parsed.file, None);
1268 assert_eq!(parsed.contract_name, Some("MyContract"));
1269 assert_eq!(parsed.version, None);
1270 assert_eq!(parsed.profile, Some("optimized"));
1271 }
1272
1273 #[test]
1274 fn test_parse_artifact_path_profile_names() {
1275 for profile in ["v1", "v2", "paris", "optimized", "default", "prod", "dev"] {
1277 let path = format!("MyContract:{profile}");
1278 let parsed = super::parse_artifact_path(&path).unwrap();
1279 assert_eq!(parsed.contract_name, Some("MyContract"));
1280 assert_eq!(parsed.profile, Some(profile));
1281 assert_eq!(parsed.version, None);
1282 }
1283 }
1284
1285 #[test]
1286 fn test_parse_artifact_path_invalid_version() {
1287 let parsed = super::parse_artifact_path("MyContract:invalid").unwrap();
1289 assert_eq!(parsed.contract_name, Some("MyContract"));
1290 assert_eq!(parsed.profile, Some("invalid"));
1291 assert_eq!(parsed.version, None);
1292 }
1293
1294 fn unique_temp_dir(prefix: &str) -> PathBuf {
1295 env::temp_dir().join(format!(
1296 "foundry-cheatcodes-{prefix}-{}",
1297 SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
1298 ))
1299 }
1300
1301 #[test]
1302 fn test_latest_broadcast_reads_tempo_sequences() {
1303 let root = unique_temp_dir("tempo-broadcast");
1304 let broadcast_path = root.join("broadcast");
1305 let sequence_dir = broadcast_path.join("Counter.s.sol").join("31337");
1306 stdfs::create_dir_all(&sequence_dir).unwrap();
1307
1308 let tx_hash = "0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f";
1309 let block_hash = "0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66";
1310 let from = "0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd";
1311 let contract_address = "0x20c0000000000000000000000000000000000000";
1312 let zero_bloom = format!("0x{}", "0".repeat(512));
1313
1314 let sequence = serde_json::json!({
1315 "transactions": [{
1316 "hash": tx_hash,
1317 "transactionType": "CREATE",
1318 "contractName": "Counter",
1319 "contractAddress": contract_address,
1320 "function": serde_json::Value::Null,
1321 "arguments": serde_json::Value::Null,
1322 "transaction": {
1323 "type": "0x76",
1324 "from": from,
1325 "to": serde_json::Value::Null,
1326 "data": "0x",
1327 "value": "0x0",
1328 "gas": "0x5208",
1329 "nonce": "0x0",
1330 "accessList": [],
1331 "calls": [],
1332 "nonceKey": "0x0",
1333 "feePayerSignature": serde_json::Value::Null,
1334 "validBefore": serde_json::Value::Null,
1335 "validAfter": serde_json::Value::Null,
1336 "keyAuthorization": serde_json::Value::Null,
1337 "aaAuthorizationList": []
1338 },
1339 "additionalContracts": [],
1340 "isFixedGasLimit": false
1341 }],
1342 "receipts": [{
1343 "type": "0x76",
1344 "status": "0x1",
1345 "cumulativeGasUsed": "0x5208",
1346 "logs": [],
1347 "logsBloom": zero_bloom,
1348 "transactionHash": tx_hash,
1349 "transactionIndex": "0x0",
1350 "blockHash": block_hash,
1351 "blockNumber": "0x7",
1352 "gasUsed": "0x5208",
1353 "effectiveGasPrice": "0x1",
1354 "from": from,
1355 "to": serde_json::Value::Null,
1356 "contractAddress": contract_address,
1357 "feePayer": from
1358 }],
1359 "libraries": [],
1360 "pending": [],
1361 "returns": {},
1362 "timestamp": 1,
1363 "chain": 31337,
1364 "commit": serde_json::Value::Null
1365 });
1366
1367 fs::write_json_file(&sequence_dir.join("run-1.json"), &sequence).unwrap();
1368
1369 let latest = latest_broadcast::<<TempoEvmNetwork as FoundryEvmNetwork>::Network>(
1370 &"Counter".to_owned(),
1371 31337,
1372 &broadcast_path,
1373 vec![CallKind::Create],
1374 )
1375 .unwrap();
1376
1377 assert_eq!(
1378 latest.txHash,
1379 b256!("04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f")
1380 );
1381 assert_eq!(latest.blockNumber, 7);
1382 assert!(matches!(latest.txType, BroadcastTxType::Create));
1383 assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000"));
1384 assert!(latest.success);
1385
1386 stdfs::remove_dir_all(root).unwrap();
1387 }
1388}