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, FixedBytes, U256, hex, map::Entry};
11use alloy_sol_types::SolValue;
12use dialoguer::{Input, Password};
13use forge_script_sequence::{BroadcastReader, TransactionWithMetadata};
14use foundry_common::{contracts::ContractData, 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 getSelectorsCall {
384 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
385 let Self { artifactPath: path } = self;
386 let selectors: Vec<FixedBytes<4>> = match get_artifact_source(state, path)? {
387 ArtifactSource::InMemory(data) => data.abi.functions().map(|f| f.selector()).collect(),
388 ArtifactSource::Disk(path) => {
389 let data = read_artifact_file(state, &path)?;
390 let json: serde_json::Value = serde_json::from_str(&data)?;
393 let abi =
394 json.get("abi").ok_or_else(|| fmt_err!("no `abi` field in artifact JSON"))?;
395 let abi: alloy_json_abi::JsonAbi =
396 serde_json::from_value(abi.clone()).map_err(|e| fmt_err!("{e}"))?;
397 abi.functions().map(|f| f.selector()).collect()
398 }
399 };
400 Ok(selectors.abi_encode())
401 }
402}
403
404impl Cheatcode for deployCode_0Call {
405 fn apply_full<FEN: FoundryEvmNetwork>(
406 &self,
407 ccx: &mut CheatsCtxt<'_, '_, FEN>,
408 executor: &mut dyn CheatcodesExecutor<FEN>,
409 ) -> Result {
410 let Self { artifactPath: path } = self;
411 deploy_code(ccx, executor, path, None, None, None)
412 }
413}
414
415impl Cheatcode for deployCode_1Call {
416 fn apply_full<FEN: FoundryEvmNetwork>(
417 &self,
418 ccx: &mut CheatsCtxt<'_, '_, FEN>,
419 executor: &mut dyn CheatcodesExecutor<FEN>,
420 ) -> Result {
421 let Self { artifactPath: path, constructorArgs: args } = self;
422 deploy_code(ccx, executor, path, Some(args), None, None)
423 }
424}
425
426impl Cheatcode for deployCode_2Call {
427 fn apply_full<FEN: FoundryEvmNetwork>(
428 &self,
429 ccx: &mut CheatsCtxt<'_, '_, FEN>,
430 executor: &mut dyn CheatcodesExecutor<FEN>,
431 ) -> Result {
432 let Self { artifactPath: path, value } = self;
433 deploy_code(ccx, executor, path, None, Some(*value), None)
434 }
435}
436
437impl Cheatcode for deployCode_3Call {
438 fn apply_full<FEN: FoundryEvmNetwork>(
439 &self,
440 ccx: &mut CheatsCtxt<'_, '_, FEN>,
441 executor: &mut dyn CheatcodesExecutor<FEN>,
442 ) -> Result {
443 let Self { artifactPath: path, constructorArgs: args, value } = self;
444 deploy_code(ccx, executor, path, Some(args), Some(*value), None)
445 }
446}
447
448impl Cheatcode for deployCode_4Call {
449 fn apply_full<FEN: FoundryEvmNetwork>(
450 &self,
451 ccx: &mut CheatsCtxt<'_, '_, FEN>,
452 executor: &mut dyn CheatcodesExecutor<FEN>,
453 ) -> Result {
454 let Self { artifactPath: path, salt } = self;
455 deploy_code(ccx, executor, path, None, None, Some((*salt).into()))
456 }
457}
458
459impl Cheatcode for deployCode_5Call {
460 fn apply_full<FEN: FoundryEvmNetwork>(
461 &self,
462 ccx: &mut CheatsCtxt<'_, '_, FEN>,
463 executor: &mut dyn CheatcodesExecutor<FEN>,
464 ) -> Result {
465 let Self { artifactPath: path, constructorArgs: args, salt } = self;
466 deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into()))
467 }
468}
469
470impl Cheatcode for deployCode_6Call {
471 fn apply_full<FEN: FoundryEvmNetwork>(
472 &self,
473 ccx: &mut CheatsCtxt<'_, '_, FEN>,
474 executor: &mut dyn CheatcodesExecutor<FEN>,
475 ) -> Result {
476 let Self { artifactPath: path, value, salt } = self;
477 deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into()))
478 }
479}
480
481impl Cheatcode for deployCode_7Call {
482 fn apply_full<FEN: FoundryEvmNetwork>(
483 &self,
484 ccx: &mut CheatsCtxt<'_, '_, FEN>,
485 executor: &mut dyn CheatcodesExecutor<FEN>,
486 ) -> Result {
487 let Self { artifactPath: path, constructorArgs: args, value, salt } = self;
488 deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into()))
489 }
490}
491
492fn deploy_code<FEN: FoundryEvmNetwork>(
495 ccx: &mut CheatsCtxt<'_, '_, FEN>,
496 executor: &mut dyn CheatcodesExecutor<FEN>,
497 path: &str,
498 constructor_args: Option<&Bytes>,
499 value: Option<U256>,
500 salt: Option<U256>,
501) -> Result {
502 let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
503
504 if let Some(broadcast) = &mut ccx.state.broadcast {
506 broadcast.deploy_from_code = true;
507 }
508
509 if let Some(args) = constructor_args {
510 bytecode.extend_from_slice(args);
511 }
512
513 let scheme =
514 if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create };
515
516 let caller =
518 ccx.state.get_prank(ccx.ecx.journal().depth()).map_or(ccx.caller, |prank| prank.new_caller);
519
520 let outcome = exec_create(
521 executor,
522 CreateInputs::new(
523 caller,
524 scheme,
525 value.unwrap_or(U256::ZERO),
526 bytecode.into(),
527 ccx.gas_limit,
528 0,
529 ),
530 ccx,
531 )?;
532
533 if !outcome.result.result.is_ok() {
534 return Err(crate::Error::from(outcome.result.output));
535 }
536
537 let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?;
538
539 Ok(address.abi_encode())
540}
541
542enum ArtifactSource<'a> {
544 InMemory(&'a ContractData),
546 Disk(PathBuf),
548}
549
550fn get_artifact_source<'a, FEN: FoundryEvmNetwork>(
564 state: &'a Cheatcodes<FEN>,
565 path: &str,
566) -> Result<ArtifactSource<'a>> {
567 if path.ends_with(".json") {
568 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
569 return Ok(ArtifactSource::Disk(path));
570 }
571
572 let parsed =
573 parse_artifact_path(path).map_err(|e| fmt_err!("failed to parse artifact path: {e}"))?;
574 let ParsedArtifactPath { file, contract_name, version, profile } = parsed;
575
576 if let Some(artifacts) = &state.config.available_artifacts {
578 let ambiguous_file_profile =
579 file.is_some() && version.is_none() && profile.is_none() && contract_name.is_some();
580 let filter_artifacts = |treat_ambiguous_as_profile: bool| -> Vec<_> {
581 artifacts
582 .iter()
583 .filter(|(id, _)| {
584 let id_name = id.name.split('.').next().unwrap();
586
587 if let Some(path) = &file
588 && !id.source.ends_with(path)
589 {
590 return false;
591 }
592 if let Some(ref version) = version
593 && (id.version.minor != version.minor
594 || id.version.major != version.major
595 || id.version.patch != version.patch)
596 {
597 return false;
598 }
599 if let Some(profile) = profile
600 && id.profile != profile
601 {
602 return false;
603 }
604 if let Some(name) = contract_name {
605 if treat_ambiguous_as_profile && ambiguous_file_profile {
606 return id.profile == name;
607 }
608
609 return id_name == name;
610 }
611
612 true
613 })
614 .collect()
615 };
616
617 let mut filtered = filter_artifacts(false);
618 if filtered.is_empty() && ambiguous_file_profile {
619 filtered = filter_artifacts(true);
620 }
621
622 let artifact = match &filtered[..] {
623 [] => None,
624 [artifact] => Some(Ok(*artifact)),
625 filtered => {
626 let mut filtered = filtered.to_vec();
627 Some(
629 state
630 .config
631 .running_artifact
632 .as_ref()
633 .and_then(|running| {
634 if version.is_none() {
636 filtered.retain(|(id, _)| id.version == running.version);
637
638 if filtered.len() == 1 {
640 return Some(filtered[0]);
641 }
642 }
643
644 if profile.is_none() {
646 filtered.retain(|(id, _)| id.profile == running.profile);
647
648 return (filtered.len() == 1).then(|| filtered[0]);
649 }
650
651 None
652 })
653 .ok_or_else(|| fmt_err!("multiple matching artifacts found")),
654 )
655 }
656 };
657
658 if let Some(artifact) = artifact {
659 return Ok(ArtifactSource::InMemory(artifact?.1));
660 }
661 }
662
663 let path_in_artifacts = match (file.map(|f| f.to_string_lossy().to_string()), contract_name) {
665 (Some(file), Some(contract_name)) => PathBuf::from(format!("{file}/{contract_name}.json")),
666 (None, Some(contract_name)) => {
667 PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
668 }
669 (Some(file), None) => {
670 let name = file.replace(".sol", "");
671 PathBuf::from(format!("{file}/{name}.json"))
672 }
673 _ => bail!("invalid artifact path"),
674 };
675
676 let path = state.config.paths.artifacts.join(path_in_artifacts);
677 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
678 Ok(ArtifactSource::Disk(path))
679}
680
681fn read_artifact_file<FEN: FoundryEvmNetwork>(
684 state: &Cheatcodes<FEN>,
685 path: &Path,
686) -> Result<String> {
687 fs::read_to_string(path).map_err(|e| {
688 if state.config.available_artifacts.is_some() {
689 fmt_err!("no matching artifact found")
690 } else {
691 e.into()
692 }
693 })
694}
695
696fn get_artifact_code<FEN: FoundryEvmNetwork>(
704 state: &Cheatcodes<FEN>,
705 path: &str,
706 deployed: bool,
707) -> Result<Bytes> {
708 let maybe_bytecode = match get_artifact_source(state, path)? {
709 ArtifactSource::InMemory(data) => {
710 if deployed { data.deployed_bytecode() } else { data.bytecode() }.cloned()
711 }
712 ArtifactSource::Disk(path) => {
713 let data = read_artifact_file(state, &path)?;
714 let artifact = serde_json::from_str::<ContractObject>(&data)?;
715 if deployed { artifact.deployed_bytecode } else { artifact.bytecode }
716 }
717 };
718 maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"))
719}
720
721impl Cheatcode for ffiCall {
722 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
723 let Self { commandInput: input } = self;
724
725 let output = ffi(state, input)?;
726
727 if output.exitCode != 0 {
729 return Err(fmt_err!(
731 "ffi command {:?} exited with code {}. stderr: {}",
732 input,
733 output.exitCode,
734 String::from_utf8_lossy(&output.stderr)
735 ));
736 }
737
738 if !output.stderr.is_empty() {
740 let stderr = String::from_utf8_lossy(&output.stderr);
741 warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr");
742 }
743
744 Ok(output.stdout.abi_encode())
746 }
747}
748
749impl Cheatcode for tryFfiCall {
750 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
751 let Self { commandInput: input } = self;
752 ffi(state, input).map(|res| res.abi_encode())
753 }
754}
755
756impl Cheatcode for promptCall {
757 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
758 let Self { promptText: text } = self;
759 prompt(state, text, prompt_input).map(|res| res.abi_encode())
760 }
761}
762
763impl Cheatcode for promptSecretCall {
764 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
765 let Self { promptText: text } = self;
766 prompt(state, text, prompt_password).map(|res| res.abi_encode())
767 }
768}
769
770impl Cheatcode for promptSecretUintCall {
771 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
772 let Self { promptText: text } = self;
773 parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256))
774 }
775}
776
777impl Cheatcode for promptAddressCall {
778 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
779 let Self { promptText: text } = self;
780 parse(&prompt(state, text, prompt_input)?, &DynSolType::Address)
781 }
782}
783
784impl Cheatcode for promptUintCall {
785 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
786 let Self { promptText: text } = self;
787 parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256))
788 }
789}
790
791pub(super) fn write_file<FEN: FoundryEvmNetwork>(
792 state: &Cheatcodes<FEN>,
793 path: &Path,
794 contents: &[u8],
795) -> Result {
796 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
797 state.config.ensure_not_foundry_toml(&path)?;
799
800 if state.fs_commit {
801 fs::locked_write(path, contents)?;
802 }
803
804 Ok(Default::default())
805}
806
807fn read_dir<FEN: FoundryEvmNetwork>(
808 state: &Cheatcodes<FEN>,
809 path: &Path,
810 max_depth: u64,
811 follow_links: bool,
812) -> Result {
813 let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
814 let paths: Vec<DirEntry> = WalkDir::new(root)
815 .min_depth(1)
816 .max_depth(max_depth.try_into().unwrap_or(usize::MAX))
817 .follow_links(follow_links)
818 .contents_first(false)
819 .same_file_system(true)
820 .sort_by_file_name()
821 .into_iter()
822 .map(|entry| match entry {
823 Ok(entry) => DirEntry {
824 errorMessage: String::new(),
825 path: entry.path().display().to_string(),
826 depth: entry.depth() as u64,
827 isDir: entry.file_type().is_dir(),
828 isSymlink: entry.path_is_symlink(),
829 },
830 Err(e) => DirEntry {
831 errorMessage: e.to_string(),
832 path: e.path().map(|p| p.display().to_string()).unwrap_or_default(),
833 depth: e.depth() as u64,
834 isDir: false,
835 isSymlink: false,
836 },
837 })
838 .collect();
839 Ok(paths.abi_encode())
840}
841
842fn ffi<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>, input: &[String]) -> Result<FfiResult> {
843 ensure!(
844 state.config.ffi,
845 "FFI is disabled; add the `--ffi` flag to allow tests to call external commands"
846 );
847 ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command");
848 let mut cmd = Command::new(&input[0]);
849 cmd.args(&input[1..]);
850
851 debug!(target: "cheatcodes", ?cmd, "invoking ffi");
852
853 let output = cmd
854 .current_dir(&state.config.root)
855 .output()
856 .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?;
857
858 let trimmed_stdout = String::from_utf8(output.stdout)?;
861 let trimmed_stdout = trimmed_stdout.trim();
862 let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) {
863 hex
864 } else {
865 trimmed_stdout.as_bytes().to_vec()
866 };
867 Ok(FfiResult {
868 exitCode: output.status.code().unwrap_or(69),
869 stdout: encoded_stdout.into(),
870 stderr: output.stderr.into(),
871 })
872}
873
874fn prompt_input(prompt_text: &str) -> Result<String, dialoguer::Error> {
875 Input::new().allow_empty(true).with_prompt(prompt_text).interact_text()
876}
877
878fn prompt_password(prompt_text: &str) -> Result<String, dialoguer::Error> {
879 Password::new().with_prompt(prompt_text).interact()
880}
881
882fn prompt<FEN: FoundryEvmNetwork>(
883 state: &Cheatcodes<FEN>,
884 prompt_text: &str,
885 input: fn(&str) -> Result<String, dialoguer::Error>,
886) -> Result<String> {
887 let text_clone = prompt_text.to_string();
888 let timeout = state.config.prompt_timeout;
889 let (tx, rx) = mpsc::channel();
890
891 thread::spawn(move || {
892 let _ = tx.send(input(&text_clone));
893 });
894
895 match rx.recv_timeout(timeout) {
896 Ok(res) => res.map_err(|err| {
897 let _ = sh_println!();
898 err.to_string().into()
899 }),
900 Err(_) => {
901 let _ = sh_eprintln!();
902 Err("Prompt timed out".into())
903 }
904 }
905}
906
907impl Cheatcode for getBroadcastCall {
908 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
909 let Self { contractName, chainId, txType } = self;
910
911 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
912 contractName,
913 *chainId,
914 &state.config.broadcast,
915 vec![map_broadcast_tx_type(*txType)],
916 )?;
917
918 Ok(latest_broadcast.abi_encode())
919 }
920}
921
922impl Cheatcode for getBroadcasts_0Call {
923 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
924 let Self { contractName, chainId, txType } = self;
925
926 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
927 .with_tx_type(map_broadcast_tx_type(*txType));
928
929 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
930
931 let summaries = broadcasts
932 .into_iter()
933 .flat_map(|broadcast| {
934 let results = reader.into_tx_receipts(broadcast);
935 parse_broadcast_results(results)
936 })
937 .collect::<Vec<_>>();
938
939 Ok(summaries.abi_encode())
940 }
941}
942
943impl Cheatcode for getBroadcasts_1Call {
944 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
945 let Self { contractName, chainId } = self;
946
947 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?;
948
949 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
950
951 let summaries = broadcasts
952 .into_iter()
953 .flat_map(|broadcast| {
954 let results = reader.into_tx_receipts(broadcast);
955 parse_broadcast_results(results)
956 })
957 .collect::<Vec<_>>();
958
959 Ok(summaries.abi_encode())
960 }
961}
962
963impl Cheatcode for getDeployment_0Call {
964 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
965 let Self { contractName } = self;
966 let chain_id = ccx.ecx.cfg().chain_id();
967
968 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
969 contractName,
970 chain_id,
971 &ccx.state.config.broadcast,
972 vec![CallKind::Create, CallKind::Create2],
973 )?;
974
975 Ok(latest_broadcast.contractAddress.abi_encode())
976 }
977}
978
979impl Cheatcode for getDeployment_1Call {
980 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
981 let Self { contractName, chainId } = self;
982
983 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
984 contractName,
985 *chainId,
986 &state.config.broadcast,
987 vec![CallKind::Create, CallKind::Create2],
988 )?;
989
990 Ok(latest_broadcast.contractAddress.abi_encode())
991 }
992}
993
994impl Cheatcode for getDeploymentsCall {
995 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
996 let Self { contractName, chainId } = self;
997
998 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
999 .with_tx_type(CallKind::Create)
1000 .with_tx_type(CallKind::Create2);
1001
1002 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
1003
1004 let summaries = broadcasts
1005 .into_iter()
1006 .flat_map(|broadcast| {
1007 let results = reader.into_tx_receipts(broadcast);
1008 parse_broadcast_results(results)
1009 })
1010 .collect::<Vec<_>>();
1011
1012 let deployed_addresses =
1013 summaries.into_iter().map(|summary| summary.contractAddress).collect::<Vec<_>>();
1014
1015 Ok(deployed_addresses.abi_encode())
1016 }
1017}
1018
1019fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind {
1020 match tx_type {
1021 BroadcastTxType::Call => CallKind::Call,
1022 BroadcastTxType::Create => CallKind::Create,
1023 BroadcastTxType::Create2 => CallKind::Create2,
1024 _ => unreachable!("invalid tx type"),
1025 }
1026}
1027
1028fn parse_broadcast_results<N: Network>(
1029 results: Vec<(TransactionWithMetadata<N>, N::ReceiptResponse)>,
1030) -> Vec<BroadcastTxSummary> {
1031 results
1032 .into_iter()
1033 .map(|(tx, receipt)| BroadcastTxSummary {
1034 txHash: receipt.transaction_hash(),
1035 blockNumber: receipt.block_number().unwrap_or_default(),
1036 txType: match tx.call_kind {
1037 CallKind::Call => BroadcastTxType::Call,
1038 CallKind::Create => BroadcastTxType::Create,
1039 CallKind::Create2 => BroadcastTxType::Create2,
1040 _ => unreachable!("invalid tx type"),
1041 },
1042 contractAddress: tx.contract_address.unwrap_or_default(),
1043 success: receipt.status(),
1044 })
1045 .collect()
1046}
1047
1048fn latest_broadcast<N: Network>(
1049 contract_name: &String,
1050 chain_id: u64,
1051 broadcast_path: &Path,
1052 filters: Vec<CallKind>,
1053) -> Result<BroadcastTxSummary>
1054where
1055 N::TxEnvelope: for<'d> serde::Deserialize<'d>,
1056{
1057 let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?;
1058
1059 for filter in filters {
1060 reader = reader.with_tx_type(filter);
1061 }
1062
1063 let broadcast = reader.read_latest::<N>()?;
1064
1065 let results = reader.into_tx_receipts(broadcast);
1066
1067 let summaries = parse_broadcast_results(results);
1068
1069 summaries
1070 .first()
1071 .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}"))
1072 .cloned()
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077 use super::*;
1078 use crate::CheatsConfig;
1079 use alloy_primitives::{address, b256};
1080 use foundry_common::ContractsByArtifact;
1081 use foundry_compilers::{
1082 ArtifactId,
1083 artifacts::{BytecodeObject, CompactBytecode, CompactContractBytecode},
1084 };
1085 use foundry_evm_core::evm::TempoEvmNetwork;
1086 use std::{env, fs as stdfs, sync::Arc};
1087
1088 fn cheats() -> Cheatcodes {
1089 let config = CheatsConfig {
1090 ffi: true,
1091 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
1092 ..Default::default()
1093 };
1094 Cheatcodes::new(Arc::new(config))
1095 }
1096
1097 #[test]
1098 fn test_ffi_hex() {
1099 let msg = b"gm";
1100 let cheats = cheats();
1101 let args = ["echo".to_string(), hex::encode(msg)];
1102 let output = ffi(&cheats, &args).unwrap();
1103 assert_eq!(output.stdout, Bytes::from(msg));
1104 }
1105
1106 #[test]
1107 fn test_ffi_string() {
1108 let msg = "gm";
1109 let cheats = cheats();
1110 let args = ["echo".to_string(), msg.to_string()];
1111 let output = ffi(&cheats, &args).unwrap();
1112 assert_eq!(output.stdout, Bytes::from(msg.as_bytes()));
1113 }
1114
1115 #[test]
1116 fn test_ffi_fails_on_error_code() {
1117 let mut cheats = cheats();
1118
1119 #[cfg(unix)]
1121 let args = vec!["false".to_string()];
1122 #[cfg(windows)]
1123 let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()];
1124
1125 let result = Cheatcode::apply(&ffiCall { commandInput: args }, &mut cheats);
1126
1127 assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded");
1129
1130 let err_msg = result.unwrap_err().to_string();
1132 assert!(
1133 err_msg.contains("exited with code 1"),
1134 "Error message did not contain exit code: {err_msg}"
1135 );
1136 }
1137
1138 #[test]
1139 fn test_artifact_parsing() {
1140 let s = include_str!("../../evm/test-data/solc-obj.json");
1141 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1142 assert!(artifact.bytecode.is_some());
1143
1144 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1145 assert!(artifact.deployed_bytecode.is_some());
1146 }
1147
1148 #[test]
1149 fn test_alloy_json_abi_rejects_unlinked_bytecode() {
1150 let artifact_json = r#"{
1151 "abi": [],
1152 "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868"
1153 }"#;
1154
1155 let result: Result<ContractObject, _> = serde_json::from_str(artifact_json);
1156 assert!(result.is_err(), "should reject unlinked bytecode with placeholders");
1157 let err = result.unwrap_err().to_string();
1158 assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder"));
1159 }
1160
1161 #[test]
1162 fn test_parse_artifact_path_file_only() {
1163 let parsed = super::parse_artifact_path("path/to/Contract.sol").unwrap();
1164 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1165 assert_eq!(parsed.contract_name, None);
1166 assert_eq!(parsed.version, None);
1167 assert_eq!(parsed.profile, None);
1168 }
1169
1170 #[test]
1171 fn test_parse_artifact_path_file_and_contract() {
1172 let parsed = super::parse_artifact_path("path/to/Contract.sol:MyContract").unwrap();
1173 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1174 assert_eq!(parsed.contract_name, Some("MyContract"));
1175 assert_eq!(parsed.version, None);
1176 assert_eq!(parsed.profile, None);
1177 }
1178
1179 #[test]
1180 fn test_parse_artifact_path_file_contract_version() {
1181 let parsed = super::parse_artifact_path("path/to/Contract.sol:MyContract:0.8.23").unwrap();
1182 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1183 assert_eq!(parsed.contract_name, Some("MyContract"));
1184 assert_eq!(parsed.version, Some(semver::Version::new(0, 8, 23)));
1185 assert_eq!(parsed.profile, None);
1186 }
1187
1188 #[test]
1189 fn test_parse_artifact_path_file_contract_profile() {
1190 let parsed =
1191 super::parse_artifact_path("path/to/Contract.sol:MyContract:optimized").unwrap();
1192 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1193 assert_eq!(parsed.contract_name, Some("MyContract"));
1194 assert_eq!(parsed.version, None);
1195 assert_eq!(parsed.profile, Some("optimized"));
1196 }
1197
1198 #[test]
1199 fn test_parse_artifact_path_file_and_version() {
1200 let parsed = super::parse_artifact_path("path/to/Contract.sol:0.8.18").unwrap();
1201 assert_eq!(parsed.file, Some(PathBuf::from("path/to/Contract.sol")));
1202 assert_eq!(parsed.contract_name, None);
1203 assert_eq!(parsed.version, Some(semver::Version::new(0, 8, 18)));
1204 assert_eq!(parsed.profile, None);
1205 }
1206
1207 #[test]
1208 fn test_parse_artifact_path_file_and_profile() {
1209 let parsed = super::parse_artifact_path("Contract.sol:paris").unwrap();
1212 assert_eq!(parsed.file, Some(PathBuf::from("Contract.sol")));
1213 assert_eq!(parsed.contract_name, Some("paris"));
1214 assert_eq!(parsed.version, None);
1215 assert_eq!(parsed.profile, None);
1216 }
1217
1218 fn test_artifact(
1219 source: &str,
1220 name: &str,
1221 profile: &str,
1222 bytecode: Bytes,
1223 ) -> (ArtifactId, CompactContractBytecode) {
1224 (
1225 ArtifactId {
1226 path: PathBuf::from(format!("{source}/{name}.json")),
1227 name: name.to_owned(),
1228 source: PathBuf::from(source),
1229 version: Version::new(0, 8, 30),
1230 build_id: String::new(),
1231 profile: profile.to_owned(),
1232 },
1233 CompactContractBytecode {
1234 abi: Some(Default::default()),
1235 bytecode: Some(CompactBytecode {
1236 object: BytecodeObject::Bytecode(bytecode),
1237 source_map: None,
1238 link_references: Default::default(),
1239 }),
1240 deployed_bytecode: None,
1241 },
1242 )
1243 }
1244
1245 #[test]
1246 fn test_get_artifact_code_resolves_file_profile_ambiguity() {
1247 let default_bytecode = Bytes::from_static(&[0x60, 0x01]);
1248 let paris_bytecode = Bytes::from_static(&[0x60, 0x02]);
1249 let source = "src/GetCodeProfile.t.sol";
1250 let artifacts = ContractsByArtifact::new([
1251 test_artifact(source, "GetCodeProfile", "default", default_bytecode),
1252 test_artifact(source, "GetCodeProfile", "paris", paris_bytecode.clone()),
1253 ]);
1254 let config = CheatsConfig {
1255 available_artifacts: Some(artifacts),
1256 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
1257 ..Default::default()
1258 };
1259 let cheats: Cheatcodes = Cheatcodes::new(Arc::new(config));
1260
1261 let bytecode =
1262 super::get_artifact_code(&cheats, "src/GetCodeProfile.t.sol:paris", false).unwrap();
1263
1264 assert_eq!(bytecode, paris_bytecode);
1265 }
1266
1267 #[test]
1268 fn test_get_artifact_code_prefers_contract_name_over_file_profile_ambiguity() {
1269 let profile_bytecode = Bytes::from_static(&[0x60, 0x02]);
1270 let contract_bytecode = Bytes::from_static(&[0x60, 0x03]);
1271 let source = "src/GetCodeProfile.t.sol";
1272 let artifacts = ContractsByArtifact::new([
1273 test_artifact(source, "GetCodeProfile", "paris", profile_bytecode),
1274 test_artifact(source, "paris", "default", contract_bytecode.clone()),
1275 ]);
1276 let config = CheatsConfig {
1277 available_artifacts: Some(artifacts),
1278 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
1279 ..Default::default()
1280 };
1281 let cheats: Cheatcodes = Cheatcodes::new(Arc::new(config));
1282
1283 let bytecode =
1284 super::get_artifact_code(&cheats, "src/GetCodeProfile.t.sol:paris", false).unwrap();
1285
1286 assert_eq!(bytecode, contract_bytecode);
1287 }
1288
1289 #[test]
1290 fn test_parse_artifact_path_contract_only() {
1291 let parsed = super::parse_artifact_path("MyContract").unwrap();
1292 assert_eq!(parsed.file, None);
1293 assert_eq!(parsed.contract_name, Some("MyContract"));
1294 assert_eq!(parsed.version, None);
1295 assert_eq!(parsed.profile, None);
1296 }
1297
1298 #[test]
1299 fn test_parse_artifact_path_contract_and_version() {
1300 let parsed = super::parse_artifact_path("MyContract:0.8.23").unwrap();
1301 assert_eq!(parsed.file, None);
1302 assert_eq!(parsed.contract_name, Some("MyContract"));
1303 assert_eq!(parsed.version, Some(semver::Version::new(0, 8, 23)));
1304 assert_eq!(parsed.profile, None);
1305 }
1306
1307 #[test]
1308 fn test_parse_artifact_path_contract_and_profile() {
1309 let parsed = super::parse_artifact_path("MyContract:optimized").unwrap();
1310 assert_eq!(parsed.file, None);
1311 assert_eq!(parsed.contract_name, Some("MyContract"));
1312 assert_eq!(parsed.version, None);
1313 assert_eq!(parsed.profile, Some("optimized"));
1314 }
1315
1316 #[test]
1317 fn test_parse_artifact_path_profile_names() {
1318 for profile in ["v1", "v2", "paris", "optimized", "default", "prod", "dev"] {
1320 let path = format!("MyContract:{profile}");
1321 let parsed = super::parse_artifact_path(&path).unwrap();
1322 assert_eq!(parsed.contract_name, Some("MyContract"));
1323 assert_eq!(parsed.profile, Some(profile));
1324 assert_eq!(parsed.version, None);
1325 }
1326 }
1327
1328 #[test]
1329 fn test_parse_artifact_path_invalid_version() {
1330 let parsed = super::parse_artifact_path("MyContract:invalid").unwrap();
1332 assert_eq!(parsed.contract_name, Some("MyContract"));
1333 assert_eq!(parsed.profile, Some("invalid"));
1334 assert_eq!(parsed.version, None);
1335 }
1336
1337 fn unique_temp_dir(prefix: &str) -> PathBuf {
1338 env::temp_dir().join(format!(
1339 "foundry-cheatcodes-{prefix}-{}",
1340 SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
1341 ))
1342 }
1343
1344 #[test]
1345 fn test_latest_broadcast_reads_tempo_sequences() {
1346 let root = unique_temp_dir("tempo-broadcast");
1347 let broadcast_path = root.join("broadcast");
1348 let sequence_dir = broadcast_path.join("Counter.s.sol").join("31337");
1349 stdfs::create_dir_all(&sequence_dir).unwrap();
1350
1351 let tx_hash = "0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f";
1352 let block_hash = "0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66";
1353 let from = "0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd";
1354 let contract_address = "0x20c0000000000000000000000000000000000000";
1355 let zero_bloom = format!("0x{}", "0".repeat(512));
1356
1357 let sequence = serde_json::json!({
1358 "transactions": [{
1359 "hash": tx_hash,
1360 "transactionType": "CREATE",
1361 "contractName": "Counter",
1362 "contractAddress": contract_address,
1363 "function": serde_json::Value::Null,
1364 "arguments": serde_json::Value::Null,
1365 "transaction": {
1366 "type": "0x76",
1367 "from": from,
1368 "to": serde_json::Value::Null,
1369 "data": "0x",
1370 "value": "0x0",
1371 "gas": "0x5208",
1372 "nonce": "0x0",
1373 "accessList": [],
1374 "calls": [],
1375 "nonceKey": "0x0",
1376 "feePayerSignature": serde_json::Value::Null,
1377 "validBefore": serde_json::Value::Null,
1378 "validAfter": serde_json::Value::Null,
1379 "keyAuthorization": serde_json::Value::Null,
1380 "aaAuthorizationList": []
1381 },
1382 "additionalContracts": [],
1383 "isFixedGasLimit": false
1384 }],
1385 "receipts": [{
1386 "type": "0x76",
1387 "status": "0x1",
1388 "cumulativeGasUsed": "0x5208",
1389 "logs": [],
1390 "logsBloom": zero_bloom,
1391 "transactionHash": tx_hash,
1392 "transactionIndex": "0x0",
1393 "blockHash": block_hash,
1394 "blockNumber": "0x7",
1395 "gasUsed": "0x5208",
1396 "effectiveGasPrice": "0x1",
1397 "from": from,
1398 "to": serde_json::Value::Null,
1399 "contractAddress": contract_address,
1400 "feePayer": from
1401 }],
1402 "libraries": [],
1403 "pending": [],
1404 "returns": {},
1405 "timestamp": 1,
1406 "chain": 31337,
1407 "commit": serde_json::Value::Null
1408 });
1409
1410 fs::write_json_file(&sequence_dir.join("run-1.json"), &sequence).unwrap();
1411
1412 let latest = latest_broadcast::<<TempoEvmNetwork as FoundryEvmNetwork>::Network>(
1413 &"Counter".to_owned(),
1414 31337,
1415 &broadcast_path,
1416 vec![CallKind::Create],
1417 )
1418 .unwrap();
1419
1420 assert_eq!(
1421 latest.txHash,
1422 b256!("04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f")
1423 );
1424 assert_eq!(latest.blockNumber, 7);
1425 assert!(matches!(latest.txType, BroadcastTxType::Create));
1426 assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000"));
1427 assert!(latest.success);
1428
1429 stdfs::remove_dir_all(root).unwrap();
1430 }
1431}