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
33impl Cheatcode for existsCall {
34 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
35 let Self { path } = self;
36 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
37 Ok(path.exists().abi_encode())
38 }
39}
40
41impl Cheatcode for fsMetadataCall {
42 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
43 let Self { path } = self;
44 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
45
46 let metadata = path.metadata()?;
47
48 let [modified, accessed, created] =
50 [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| {
51 time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
52 });
53
54 Ok(FsMetadata {
55 isDir: metadata.is_dir(),
56 isSymlink: metadata.is_symlink(),
57 length: U256::from(metadata.len()),
58 readOnly: metadata.permissions().readonly(),
59 modified: U256::from(modified),
60 accessed: U256::from(accessed),
61 created: U256::from(created),
62 }
63 .abi_encode())
64 }
65}
66
67impl Cheatcode for isDirCall {
68 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
69 let Self { path } = self;
70 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
71 Ok(path.is_dir().abi_encode())
72 }
73}
74
75impl Cheatcode for isFileCall {
76 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
77 let Self { path } = self;
78 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
79 Ok(path.is_file().abi_encode())
80 }
81}
82
83impl Cheatcode for projectRootCall {
84 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
85 let Self {} = self;
86 Ok(state.config.root.display().to_string().abi_encode())
87 }
88}
89
90impl Cheatcode for currentFilePathCall {
91 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
92 let Self {} = self;
93 let artifact = state
94 .config
95 .running_artifact
96 .as_ref()
97 .ok_or_else(|| fmt_err!("no running contract found"))?;
98 let relative = artifact.source.strip_prefix(&state.config.root).unwrap_or(&artifact.source);
99 Ok(relative.display().to_string().abi_encode())
100 }
101}
102
103impl Cheatcode for unixTimeCall {
104 fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
105 let Self {} = self;
106 let difference = SystemTime::now()
107 .duration_since(UNIX_EPOCH)
108 .map_err(|e| fmt_err!("failed getting Unix timestamp: {e}"))?;
109 Ok(difference.as_millis().abi_encode())
110 }
111}
112
113impl Cheatcode for closeFileCall {
114 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
115 let Self { path } = self;
116 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
117
118 state.test_context.opened_read_files.remove(&path);
119
120 Ok(Default::default())
121 }
122}
123
124impl Cheatcode for copyFileCall {
125 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
126 let Self { from, to } = self;
127 let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?;
128 let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?;
129 state.config.ensure_not_foundry_toml(&to)?;
130
131 let n = fs::copy(from, to)?;
132 Ok(n.abi_encode())
133 }
134}
135
136impl Cheatcode for createDirCall {
137 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
138 let Self { path, recursive } = self;
139 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
140 if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?;
141 Ok(Default::default())
142 }
143}
144
145impl Cheatcode for readDir_0Call {
146 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
147 let Self { path } = self;
148 read_dir(state, path.as_ref(), 1, false)
149 }
150}
151
152impl Cheatcode for readDir_1Call {
153 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
154 let Self { path, maxDepth } = self;
155 read_dir(state, path.as_ref(), *maxDepth, false)
156 }
157}
158
159impl Cheatcode for readDir_2Call {
160 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
161 let Self { path, maxDepth, followLinks } = self;
162 read_dir(state, path.as_ref(), *maxDepth, *followLinks)
163 }
164}
165
166impl Cheatcode for readFileCall {
167 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
168 let Self { path } = self;
169 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
170 Ok(fs::locked_read_to_string(path)?.abi_encode())
171 }
172}
173
174impl Cheatcode for readFileBinaryCall {
175 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
176 let Self { path } = self;
177 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
178 Ok(fs::locked_read(path)?.abi_encode())
179 }
180}
181
182impl Cheatcode for readLineCall {
183 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
184 let Self { path } = self;
185 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
186
187 let reader = match state.test_context.opened_read_files.entry(path.clone()) {
189 Entry::Occupied(entry) => entry.into_mut(),
190 Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)),
191 };
192
193 let mut line: String = String::new();
194 reader.read_line(&mut line)?;
195
196 if line.ends_with('\n') {
198 line.pop();
199 if line.ends_with('\r') {
200 line.pop();
201 }
202 }
203
204 Ok(line.abi_encode())
205 }
206}
207
208impl Cheatcode for readLinkCall {
209 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
210 let Self { linkPath: path } = self;
211 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
212 let target = fs::read_link(path)?;
213 Ok(target.display().to_string().abi_encode())
214 }
215}
216
217impl Cheatcode for removeDirCall {
218 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
219 let Self { path, recursive } = self;
220 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
221 if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?;
222 Ok(Default::default())
223 }
224}
225
226impl Cheatcode for removeFileCall {
227 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
228 let Self { path } = self;
229 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
230 state.config.ensure_not_foundry_toml(&path)?;
231
232 state.test_context.opened_read_files.remove(&path);
234
235 if state.fs_commit {
236 fs::remove_file(&path)?;
237 }
238
239 Ok(Default::default())
240 }
241}
242
243impl Cheatcode for writeFileCall {
244 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
245 let Self { path, data } = self;
246 write_file(state, path.as_ref(), data.as_bytes())
247 }
248}
249
250impl Cheatcode for writeFileBinaryCall {
251 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
252 let Self { path, data } = self;
253 write_file(state, path.as_ref(), data)
254 }
255}
256
257impl Cheatcode for writeLineCall {
258 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
259 let Self { path, data: line } = self;
260 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
261 state.config.ensure_not_foundry_toml(&path)?;
262
263 if state.fs_commit {
264 fs::locked_write_line(path, line)?;
265 }
266
267 Ok(Default::default())
268 }
269}
270
271impl Cheatcode for getArtifactPathByCodeCall {
272 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
273 let Self { code } = self;
274 let (artifact_id, _) = state
275 .config
276 .available_artifacts
277 .as_ref()
278 .and_then(|artifacts| artifacts.find_by_creation_code(code))
279 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
280
281 Ok(artifact_id.path.to_string_lossy().abi_encode())
282 }
283}
284
285impl Cheatcode for getArtifactPathByDeployedCodeCall {
286 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
287 let Self { deployedCode } = self;
288 let (artifact_id, _) = state
289 .config
290 .available_artifacts
291 .as_ref()
292 .and_then(|artifacts| artifacts.find_by_deployed_code(deployedCode))
293 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
294
295 Ok(artifact_id.path.to_string_lossy().abi_encode())
296 }
297}
298
299impl Cheatcode for getCodeCall {
300 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
301 let Self { artifactPath: path } = self;
302 Ok(get_artifact_code(state, path, false)?.abi_encode())
303 }
304}
305
306impl Cheatcode for getDeployedCodeCall {
307 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
308 let Self { artifactPath: path } = self;
309 Ok(get_artifact_code(state, path, true)?.abi_encode())
310 }
311}
312
313impl Cheatcode for deployCode_0Call {
314 fn apply_full<FEN: FoundryEvmNetwork>(
315 &self,
316 ccx: &mut CheatsCtxt<'_, '_, FEN>,
317 executor: &mut dyn CheatcodesExecutor<FEN>,
318 ) -> Result {
319 let Self { artifactPath: path } = self;
320 deploy_code(ccx, executor, path, None, None, None)
321 }
322}
323
324impl Cheatcode for deployCode_1Call {
325 fn apply_full<FEN: FoundryEvmNetwork>(
326 &self,
327 ccx: &mut CheatsCtxt<'_, '_, FEN>,
328 executor: &mut dyn CheatcodesExecutor<FEN>,
329 ) -> Result {
330 let Self { artifactPath: path, constructorArgs: args } = self;
331 deploy_code(ccx, executor, path, Some(args), None, None)
332 }
333}
334
335impl Cheatcode for deployCode_2Call {
336 fn apply_full<FEN: FoundryEvmNetwork>(
337 &self,
338 ccx: &mut CheatsCtxt<'_, '_, FEN>,
339 executor: &mut dyn CheatcodesExecutor<FEN>,
340 ) -> Result {
341 let Self { artifactPath: path, value } = self;
342 deploy_code(ccx, executor, path, None, Some(*value), None)
343 }
344}
345
346impl Cheatcode for deployCode_3Call {
347 fn apply_full<FEN: FoundryEvmNetwork>(
348 &self,
349 ccx: &mut CheatsCtxt<'_, '_, FEN>,
350 executor: &mut dyn CheatcodesExecutor<FEN>,
351 ) -> Result {
352 let Self { artifactPath: path, constructorArgs: args, value } = self;
353 deploy_code(ccx, executor, path, Some(args), Some(*value), None)
354 }
355}
356
357impl Cheatcode for deployCode_4Call {
358 fn apply_full<FEN: FoundryEvmNetwork>(
359 &self,
360 ccx: &mut CheatsCtxt<'_, '_, FEN>,
361 executor: &mut dyn CheatcodesExecutor<FEN>,
362 ) -> Result {
363 let Self { artifactPath: path, salt } = self;
364 deploy_code(ccx, executor, path, None, None, Some((*salt).into()))
365 }
366}
367
368impl Cheatcode for deployCode_5Call {
369 fn apply_full<FEN: FoundryEvmNetwork>(
370 &self,
371 ccx: &mut CheatsCtxt<'_, '_, FEN>,
372 executor: &mut dyn CheatcodesExecutor<FEN>,
373 ) -> Result {
374 let Self { artifactPath: path, constructorArgs: args, salt } = self;
375 deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into()))
376 }
377}
378
379impl Cheatcode for deployCode_6Call {
380 fn apply_full<FEN: FoundryEvmNetwork>(
381 &self,
382 ccx: &mut CheatsCtxt<'_, '_, FEN>,
383 executor: &mut dyn CheatcodesExecutor<FEN>,
384 ) -> Result {
385 let Self { artifactPath: path, value, salt } = self;
386 deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into()))
387 }
388}
389
390impl Cheatcode for deployCode_7Call {
391 fn apply_full<FEN: FoundryEvmNetwork>(
392 &self,
393 ccx: &mut CheatsCtxt<'_, '_, FEN>,
394 executor: &mut dyn CheatcodesExecutor<FEN>,
395 ) -> Result {
396 let Self { artifactPath: path, constructorArgs: args, value, salt } = self;
397 deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into()))
398 }
399}
400
401fn deploy_code<FEN: FoundryEvmNetwork>(
404 ccx: &mut CheatsCtxt<'_, '_, FEN>,
405 executor: &mut dyn CheatcodesExecutor<FEN>,
406 path: &str,
407 constructor_args: Option<&Bytes>,
408 value: Option<U256>,
409 salt: Option<U256>,
410) -> Result {
411 let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
412
413 if let Some(broadcast) = &mut ccx.state.broadcast {
415 broadcast.deploy_from_code = true;
416 }
417
418 if let Some(args) = constructor_args {
419 bytecode.extend_from_slice(args);
420 }
421
422 let scheme =
423 if let Some(salt) = salt { CreateScheme::Create2 { salt } } else { CreateScheme::Create };
424
425 let caller =
427 ccx.state.get_prank(ccx.ecx.journal().depth()).map_or(ccx.caller, |prank| prank.new_caller);
428
429 let outcome = exec_create(
430 executor,
431 CreateInputs::new(
432 caller,
433 scheme,
434 value.unwrap_or(U256::ZERO),
435 bytecode.into(),
436 ccx.gas_limit,
437 0,
438 ),
439 ccx,
440 )?;
441
442 if !outcome.result.result.is_ok() {
443 return Err(crate::Error::from(outcome.result.output));
444 }
445
446 let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?;
447
448 Ok(address.abi_encode())
449}
450
451fn get_artifact_code<FEN: FoundryEvmNetwork>(
466 state: &Cheatcodes<FEN>,
467 path: &str,
468 deployed: bool,
469) -> Result<Bytes> {
470 let path = if path.ends_with(".json") {
471 PathBuf::from(path)
472 } else {
473 let mut parts = path.split(':');
474
475 let mut file = None;
476 let mut contract_name = None;
477 let mut version = None;
478
479 let path_or_name = parts.next().unwrap();
480 if path_or_name.contains('.') {
481 file = Some(PathBuf::from(path_or_name));
482 if let Some(name_or_version) = parts.next() {
483 if name_or_version.contains('.') {
484 version = Some(name_or_version);
485 } else {
486 contract_name = Some(name_or_version);
487 version = parts.next();
488 }
489 }
490 } else {
491 contract_name = Some(path_or_name);
492 version = parts.next();
493 }
494
495 let version = if let Some(version) = version {
496 Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?)
497 } else {
498 None
499 };
500
501 if let Some(artifacts) = &state.config.available_artifacts {
503 let filtered = artifacts
504 .iter()
505 .filter(|(id, _)| {
506 let id_name = id.name.split('.').next().unwrap();
508
509 if let Some(path) = &file
510 && !id.source.ends_with(path)
511 {
512 return false;
513 }
514 if let Some(name) = contract_name
515 && id_name != name
516 {
517 return false;
518 }
519 if let Some(ref version) = version
520 && (id.version.minor != version.minor
521 || id.version.major != version.major
522 || id.version.patch != version.patch)
523 {
524 return false;
525 }
526 true
527 })
528 .collect::<Vec<_>>();
529
530 let artifact = match &filtered[..] {
531 [] => None,
532 [artifact] => Some(Ok(*artifact)),
533 filtered => {
534 let mut filtered = filtered.to_vec();
535 Some(
537 state
538 .config
539 .running_artifact
540 .as_ref()
541 .and_then(|running| {
542 filtered.retain(|(id, _)| id.version == running.version);
544
545 if filtered.len() == 1 {
547 return Some(filtered[0]);
548 }
549
550 filtered.retain(|(id, _)| id.profile == running.profile);
552
553 (filtered.len() == 1).then(|| filtered[0])
554 })
555 .ok_or_else(|| fmt_err!("multiple matching artifacts found")),
556 )
557 }
558 };
559
560 if let Some(artifact) = artifact {
561 let artifact = artifact?;
562 let maybe_bytecode = if deployed {
563 artifact.1.deployed_bytecode().cloned()
564 } else {
565 artifact.1.bytecode().cloned()
566 };
567
568 return maybe_bytecode.ok_or_else(|| {
569 fmt_err!("no bytecode for contract; is it abstract or unlinked?")
570 });
571 }
572 }
573
574 let path_in_artifacts = match (file.map(|f| f.to_string_lossy().to_string()), contract_name)
576 {
577 (Some(file), Some(contract_name)) => {
578 PathBuf::from(format!("{file}/{contract_name}.json"))
579 }
580 (None, Some(contract_name)) => {
581 PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
582 }
583 (Some(file), None) => {
584 let name = file.replace(".sol", "");
585 PathBuf::from(format!("{file}/{name}.json"))
586 }
587 _ => bail!("invalid artifact path"),
588 };
589
590 state.config.paths.artifacts.join(path_in_artifacts)
591 };
592
593 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
594 let data = fs::read_to_string(path).map_err(|e| {
595 if state.config.available_artifacts.is_some() {
596 fmt_err!("no matching artifact found")
597 } else {
598 e.into()
599 }
600 })?;
601 let artifact = serde_json::from_str::<ContractObject>(&data)?;
602 let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode };
603 maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"))
604}
605
606impl Cheatcode for ffiCall {
607 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
608 let Self { commandInput: input } = self;
609
610 let output = ffi(state, input)?;
611
612 if output.exitCode != 0 {
614 return Err(fmt_err!(
616 "ffi command {:?} exited with code {}. stderr: {}",
617 input,
618 output.exitCode,
619 String::from_utf8_lossy(&output.stderr)
620 ));
621 }
622
623 if !output.stderr.is_empty() {
625 let stderr = String::from_utf8_lossy(&output.stderr);
626 warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr");
627 }
628
629 Ok(output.stdout.abi_encode())
631 }
632}
633
634impl Cheatcode for tryFfiCall {
635 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
636 let Self { commandInput: input } = self;
637 ffi(state, input).map(|res| res.abi_encode())
638 }
639}
640
641impl Cheatcode for promptCall {
642 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
643 let Self { promptText: text } = self;
644 prompt(state, text, prompt_input).map(|res| res.abi_encode())
645 }
646}
647
648impl Cheatcode for promptSecretCall {
649 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
650 let Self { promptText: text } = self;
651 prompt(state, text, prompt_password).map(|res| res.abi_encode())
652 }
653}
654
655impl Cheatcode for promptSecretUintCall {
656 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
657 let Self { promptText: text } = self;
658 parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256))
659 }
660}
661
662impl Cheatcode for promptAddressCall {
663 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
664 let Self { promptText: text } = self;
665 parse(&prompt(state, text, prompt_input)?, &DynSolType::Address)
666 }
667}
668
669impl Cheatcode for promptUintCall {
670 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
671 let Self { promptText: text } = self;
672 parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256))
673 }
674}
675
676pub(super) fn write_file<FEN: FoundryEvmNetwork>(
677 state: &Cheatcodes<FEN>,
678 path: &Path,
679 contents: &[u8],
680) -> Result {
681 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
682 state.config.ensure_not_foundry_toml(&path)?;
684
685 if state.fs_commit {
686 fs::locked_write(path, contents)?;
687 }
688
689 Ok(Default::default())
690}
691
692fn read_dir<FEN: FoundryEvmNetwork>(
693 state: &Cheatcodes<FEN>,
694 path: &Path,
695 max_depth: u64,
696 follow_links: bool,
697) -> Result {
698 let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
699 let paths: Vec<DirEntry> = WalkDir::new(root)
700 .min_depth(1)
701 .max_depth(max_depth.try_into().unwrap_or(usize::MAX))
702 .follow_links(follow_links)
703 .contents_first(false)
704 .same_file_system(true)
705 .sort_by_file_name()
706 .into_iter()
707 .map(|entry| match entry {
708 Ok(entry) => DirEntry {
709 errorMessage: String::new(),
710 path: entry.path().display().to_string(),
711 depth: entry.depth() as u64,
712 isDir: entry.file_type().is_dir(),
713 isSymlink: entry.path_is_symlink(),
714 },
715 Err(e) => DirEntry {
716 errorMessage: e.to_string(),
717 path: e.path().map(|p| p.display().to_string()).unwrap_or_default(),
718 depth: e.depth() as u64,
719 isDir: false,
720 isSymlink: false,
721 },
722 })
723 .collect();
724 Ok(paths.abi_encode())
725}
726
727fn ffi<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>, input: &[String]) -> Result<FfiResult> {
728 ensure!(
729 state.config.ffi,
730 "FFI is disabled; add the `--ffi` flag to allow tests to call external commands"
731 );
732 ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command");
733 let mut cmd = Command::new(&input[0]);
734 cmd.args(&input[1..]);
735
736 debug!(target: "cheatcodes", ?cmd, "invoking ffi");
737
738 let output = cmd
739 .current_dir(&state.config.root)
740 .output()
741 .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?;
742
743 let trimmed_stdout = String::from_utf8(output.stdout)?;
746 let trimmed_stdout = trimmed_stdout.trim();
747 let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) {
748 hex
749 } else {
750 trimmed_stdout.as_bytes().to_vec()
751 };
752 Ok(FfiResult {
753 exitCode: output.status.code().unwrap_or(69),
754 stdout: encoded_stdout.into(),
755 stderr: output.stderr.into(),
756 })
757}
758
759fn prompt_input(prompt_text: &str) -> Result<String, dialoguer::Error> {
760 Input::new().allow_empty(true).with_prompt(prompt_text).interact_text()
761}
762
763fn prompt_password(prompt_text: &str) -> Result<String, dialoguer::Error> {
764 Password::new().with_prompt(prompt_text).interact()
765}
766
767fn prompt<FEN: FoundryEvmNetwork>(
768 state: &Cheatcodes<FEN>,
769 prompt_text: &str,
770 input: fn(&str) -> Result<String, dialoguer::Error>,
771) -> Result<String> {
772 let text_clone = prompt_text.to_string();
773 let timeout = state.config.prompt_timeout;
774 let (tx, rx) = mpsc::channel();
775
776 thread::spawn(move || {
777 let _ = tx.send(input(&text_clone));
778 });
779
780 match rx.recv_timeout(timeout) {
781 Ok(res) => res.map_err(|err| {
782 let _ = sh_println!();
783 err.to_string().into()
784 }),
785 Err(_) => {
786 let _ = sh_eprintln!();
787 Err("Prompt timed out".into())
788 }
789 }
790}
791
792impl Cheatcode for getBroadcastCall {
793 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
794 let Self { contractName, chainId, txType } = self;
795
796 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
797 contractName,
798 *chainId,
799 &state.config.broadcast,
800 vec![map_broadcast_tx_type(*txType)],
801 )?;
802
803 Ok(latest_broadcast.abi_encode())
804 }
805}
806
807impl Cheatcode for getBroadcasts_0Call {
808 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
809 let Self { contractName, chainId, txType } = self;
810
811 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
812 .with_tx_type(map_broadcast_tx_type(*txType));
813
814 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
815
816 let summaries = broadcasts
817 .into_iter()
818 .flat_map(|broadcast| {
819 let results = reader.into_tx_receipts(broadcast);
820 parse_broadcast_results(results)
821 })
822 .collect::<Vec<_>>();
823
824 Ok(summaries.abi_encode())
825 }
826}
827
828impl Cheatcode for getBroadcasts_1Call {
829 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
830 let Self { contractName, chainId } = self;
831
832 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?;
833
834 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
835
836 let summaries = broadcasts
837 .into_iter()
838 .flat_map(|broadcast| {
839 let results = reader.into_tx_receipts(broadcast);
840 parse_broadcast_results(results)
841 })
842 .collect::<Vec<_>>();
843
844 Ok(summaries.abi_encode())
845 }
846}
847
848impl Cheatcode for getDeployment_0Call {
849 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
850 let Self { contractName } = self;
851 let chain_id = ccx.ecx.cfg().chain_id();
852
853 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
854 contractName,
855 chain_id,
856 &ccx.state.config.broadcast,
857 vec![CallKind::Create, CallKind::Create2],
858 )?;
859
860 Ok(latest_broadcast.contractAddress.abi_encode())
861 }
862}
863
864impl Cheatcode for getDeployment_1Call {
865 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
866 let Self { contractName, chainId } = self;
867
868 let latest_broadcast = latest_broadcast::<<FEN as FoundryEvmNetwork>::Network>(
869 contractName,
870 *chainId,
871 &state.config.broadcast,
872 vec![CallKind::Create, CallKind::Create2],
873 )?;
874
875 Ok(latest_broadcast.contractAddress.abi_encode())
876 }
877}
878
879impl Cheatcode for getDeploymentsCall {
880 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
881 let Self { contractName, chainId } = self;
882
883 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
884 .with_tx_type(CallKind::Create)
885 .with_tx_type(CallKind::Create2);
886
887 let broadcasts = reader.read::<<FEN as FoundryEvmNetwork>::Network>()?;
888
889 let summaries = broadcasts
890 .into_iter()
891 .flat_map(|broadcast| {
892 let results = reader.into_tx_receipts(broadcast);
893 parse_broadcast_results(results)
894 })
895 .collect::<Vec<_>>();
896
897 let deployed_addresses =
898 summaries.into_iter().map(|summary| summary.contractAddress).collect::<Vec<_>>();
899
900 Ok(deployed_addresses.abi_encode())
901 }
902}
903
904fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind {
905 match tx_type {
906 BroadcastTxType::Call => CallKind::Call,
907 BroadcastTxType::Create => CallKind::Create,
908 BroadcastTxType::Create2 => CallKind::Create2,
909 _ => unreachable!("invalid tx type"),
910 }
911}
912
913fn parse_broadcast_results<N: Network>(
914 results: Vec<(TransactionWithMetadata<N>, N::ReceiptResponse)>,
915) -> Vec<BroadcastTxSummary> {
916 results
917 .into_iter()
918 .map(|(tx, receipt)| BroadcastTxSummary {
919 txHash: receipt.transaction_hash(),
920 blockNumber: receipt.block_number().unwrap_or_default(),
921 txType: match tx.call_kind {
922 CallKind::Call => BroadcastTxType::Call,
923 CallKind::Create => BroadcastTxType::Create,
924 CallKind::Create2 => BroadcastTxType::Create2,
925 _ => unreachable!("invalid tx type"),
926 },
927 contractAddress: tx.contract_address.unwrap_or_default(),
928 success: receipt.status(),
929 })
930 .collect()
931}
932
933fn latest_broadcast<N: Network>(
934 contract_name: &String,
935 chain_id: u64,
936 broadcast_path: &Path,
937 filters: Vec<CallKind>,
938) -> Result<BroadcastTxSummary>
939where
940 N::TxEnvelope: for<'d> serde::Deserialize<'d>,
941{
942 let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?;
943
944 for filter in filters {
945 reader = reader.with_tx_type(filter);
946 }
947
948 let broadcast = reader.read_latest::<N>()?;
949
950 let results = reader.into_tx_receipts(broadcast);
951
952 let summaries = parse_broadcast_results(results);
953
954 summaries
955 .first()
956 .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}"))
957 .cloned()
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963 use crate::CheatsConfig;
964 use alloy_primitives::{address, b256};
965 use foundry_evm_core::evm::TempoEvmNetwork;
966 use std::{env, fs as stdfs, sync::Arc};
967
968 fn cheats() -> Cheatcodes {
969 let config = CheatsConfig {
970 ffi: true,
971 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
972 ..Default::default()
973 };
974 Cheatcodes::new(Arc::new(config))
975 }
976
977 #[test]
978 fn test_ffi_hex() {
979 let msg = b"gm";
980 let cheats = cheats();
981 let args = ["echo".to_string(), hex::encode(msg)];
982 let output = ffi(&cheats, &args).unwrap();
983 assert_eq!(output.stdout, Bytes::from(msg));
984 }
985
986 #[test]
987 fn test_ffi_string() {
988 let msg = "gm";
989 let cheats = cheats();
990 let args = ["echo".to_string(), msg.to_string()];
991 let output = ffi(&cheats, &args).unwrap();
992 assert_eq!(output.stdout, Bytes::from(msg.as_bytes()));
993 }
994
995 #[test]
996 fn test_ffi_fails_on_error_code() {
997 let mut cheats = cheats();
998
999 #[cfg(unix)]
1001 let args = vec!["false".to_string()];
1002 #[cfg(windows)]
1003 let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()];
1004
1005 let result = Cheatcode::apply(&ffiCall { commandInput: args }, &mut cheats);
1006
1007 assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded");
1009
1010 let err_msg = result.unwrap_err().to_string();
1012 assert!(
1013 err_msg.contains("exited with code 1"),
1014 "Error message did not contain exit code: {err_msg}"
1015 );
1016 }
1017
1018 #[test]
1019 fn test_artifact_parsing() {
1020 let s = include_str!("../../evm/test-data/solc-obj.json");
1021 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1022 assert!(artifact.bytecode.is_some());
1023
1024 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1025 assert!(artifact.deployed_bytecode.is_some());
1026 }
1027
1028 #[test]
1029 fn test_alloy_json_abi_rejects_unlinked_bytecode() {
1030 let artifact_json = r#"{
1031 "abi": [],
1032 "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868"
1033 }"#;
1034
1035 let result: Result<ContractObject, _> = serde_json::from_str(artifact_json);
1036 assert!(result.is_err(), "should reject unlinked bytecode with placeholders");
1037 let err = result.unwrap_err().to_string();
1038 assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder"));
1039 }
1040
1041 fn unique_temp_dir(prefix: &str) -> PathBuf {
1042 env::temp_dir().join(format!(
1043 "foundry-cheatcodes-{prefix}-{}",
1044 SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
1045 ))
1046 }
1047
1048 #[test]
1049 fn test_latest_broadcast_reads_tempo_sequences() {
1050 let root = unique_temp_dir("tempo-broadcast");
1051 let broadcast_path = root.join("broadcast");
1052 let sequence_dir = broadcast_path.join("Counter.s.sol").join("31337");
1053 stdfs::create_dir_all(&sequence_dir).unwrap();
1054
1055 let tx_hash = "0x04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f";
1056 let block_hash = "0x860f788b251ece768e63b0d3906d156f652d843848b71c7fe81faacd49139d66";
1057 let from = "0xa70ab0448e66cd77995bfbba5c5b64b41a85f3fd";
1058 let contract_address = "0x20c0000000000000000000000000000000000000";
1059 let zero_bloom = format!("0x{}", "0".repeat(512));
1060
1061 let sequence = serde_json::json!({
1062 "transactions": [{
1063 "hash": tx_hash,
1064 "transactionType": "CREATE",
1065 "contractName": "Counter",
1066 "contractAddress": contract_address,
1067 "function": serde_json::Value::Null,
1068 "arguments": serde_json::Value::Null,
1069 "transaction": {
1070 "type": "0x76",
1071 "from": from,
1072 "to": serde_json::Value::Null,
1073 "data": "0x",
1074 "value": "0x0",
1075 "gas": "0x5208",
1076 "nonce": "0x0",
1077 "accessList": [],
1078 "calls": [],
1079 "nonceKey": "0x0",
1080 "feePayerSignature": serde_json::Value::Null,
1081 "validBefore": serde_json::Value::Null,
1082 "validAfter": serde_json::Value::Null,
1083 "keyAuthorization": serde_json::Value::Null,
1084 "aaAuthorizationList": []
1085 },
1086 "additionalContracts": [],
1087 "isFixedGasLimit": false
1088 }],
1089 "receipts": [{
1090 "type": "0x76",
1091 "status": "0x1",
1092 "cumulativeGasUsed": "0x5208",
1093 "logs": [],
1094 "logsBloom": zero_bloom,
1095 "transactionHash": tx_hash,
1096 "transactionIndex": "0x0",
1097 "blockHash": block_hash,
1098 "blockNumber": "0x7",
1099 "gasUsed": "0x5208",
1100 "effectiveGasPrice": "0x1",
1101 "from": from,
1102 "to": serde_json::Value::Null,
1103 "contractAddress": contract_address,
1104 "feePayer": from
1105 }],
1106 "libraries": [],
1107 "pending": [],
1108 "returns": {},
1109 "timestamp": 1,
1110 "chain": 31337,
1111 "commit": serde_json::Value::Null
1112 });
1113
1114 fs::write_json_file(&sequence_dir.join("run-1.json"), &sequence).unwrap();
1115
1116 let latest = latest_broadcast::<<TempoEvmNetwork as FoundryEvmNetwork>::Network>(
1117 &"Counter".to_owned(),
1118 31337,
1119 &broadcast_path,
1120 vec![CallKind::Create],
1121 )
1122 .unwrap();
1123
1124 assert_eq!(
1125 latest.txHash,
1126 b256!("04548a0ea27e2cccc1479af3c2ff02da4d4d3ea46af8e8d7edaa49f6ea27073f")
1127 );
1128 assert_eq!(latest.blockNumber, 7);
1129 assert!(matches!(latest.txType, BroadcastTxType::Create));
1130 assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000"));
1131 assert!(latest.success);
1132
1133 stdfs::remove_dir_all(root).unwrap();
1134 }
1135}