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::{Ethereum, 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 ),
438 ccx,
439 )?;
440
441 if !outcome.result.result.is_ok() {
442 return Err(crate::Error::from(outcome.result.output));
443 }
444
445 let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?;
446
447 Ok(address.abi_encode())
448}
449
450fn get_artifact_code<FEN: FoundryEvmNetwork>(
465 state: &Cheatcodes<FEN>,
466 path: &str,
467 deployed: bool,
468) -> Result<Bytes> {
469 let path = if path.ends_with(".json") {
470 PathBuf::from(path)
471 } else {
472 let mut parts = path.split(':');
473
474 let mut file = None;
475 let mut contract_name = None;
476 let mut version = None;
477
478 let path_or_name = parts.next().unwrap();
479 if path_or_name.contains('.') {
480 file = Some(PathBuf::from(path_or_name));
481 if let Some(name_or_version) = parts.next() {
482 if name_or_version.contains('.') {
483 version = Some(name_or_version);
484 } else {
485 contract_name = Some(name_or_version);
486 version = parts.next();
487 }
488 }
489 } else {
490 contract_name = Some(path_or_name);
491 version = parts.next();
492 }
493
494 let version = if let Some(version) = version {
495 Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?)
496 } else {
497 None
498 };
499
500 if let Some(artifacts) = &state.config.available_artifacts {
502 let filtered = artifacts
503 .iter()
504 .filter(|(id, _)| {
505 let id_name = id.name.split('.').next().unwrap();
507
508 if let Some(path) = &file
509 && !id.source.ends_with(path)
510 {
511 return false;
512 }
513 if let Some(name) = contract_name
514 && id_name != name
515 {
516 return false;
517 }
518 if let Some(ref version) = version
519 && (id.version.minor != version.minor
520 || id.version.major != version.major
521 || id.version.patch != version.patch)
522 {
523 return false;
524 }
525 true
526 })
527 .collect::<Vec<_>>();
528
529 let artifact = match &filtered[..] {
530 [] => None,
531 [artifact] => Some(Ok(*artifact)),
532 filtered => {
533 let mut filtered = filtered.to_vec();
534 Some(
536 state
537 .config
538 .running_artifact
539 .as_ref()
540 .and_then(|running| {
541 filtered.retain(|(id, _)| id.version == running.version);
543
544 if filtered.len() == 1 {
546 return Some(filtered[0]);
547 }
548
549 filtered.retain(|(id, _)| id.profile == running.profile);
551
552 (filtered.len() == 1).then(|| filtered[0])
553 })
554 .ok_or_else(|| fmt_err!("multiple matching artifacts found")),
555 )
556 }
557 };
558
559 if let Some(artifact) = artifact {
560 let artifact = artifact?;
561 let maybe_bytecode = if deployed {
562 artifact.1.deployed_bytecode().cloned()
563 } else {
564 artifact.1.bytecode().cloned()
565 };
566
567 return maybe_bytecode.ok_or_else(|| {
568 fmt_err!("no bytecode for contract; is it abstract or unlinked?")
569 });
570 }
571 }
572
573 let path_in_artifacts = match (file.map(|f| f.to_string_lossy().to_string()), contract_name)
575 {
576 (Some(file), Some(contract_name)) => {
577 PathBuf::from(format!("{file}/{contract_name}.json"))
578 }
579 (None, Some(contract_name)) => {
580 PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
581 }
582 (Some(file), None) => {
583 let name = file.replace(".sol", "");
584 PathBuf::from(format!("{file}/{name}.json"))
585 }
586 _ => bail!("invalid artifact path"),
587 };
588
589 state.config.paths.artifacts.join(path_in_artifacts)
590 };
591
592 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
593 let data = fs::read_to_string(path).map_err(|e| {
594 if state.config.available_artifacts.is_some() {
595 fmt_err!("no matching artifact found")
596 } else {
597 e.into()
598 }
599 })?;
600 let artifact = serde_json::from_str::<ContractObject>(&data)?;
601 let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode };
602 maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"))
603}
604
605impl Cheatcode for ffiCall {
606 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
607 let Self { commandInput: input } = self;
608
609 let output = ffi(state, input)?;
610
611 if output.exitCode != 0 {
613 return Err(fmt_err!(
615 "ffi command {:?} exited with code {}. stderr: {}",
616 input,
617 output.exitCode,
618 String::from_utf8_lossy(&output.stderr)
619 ));
620 }
621
622 if !output.stderr.is_empty() {
624 let stderr = String::from_utf8_lossy(&output.stderr);
625 warn!(target: "cheatcodes", ?input, ?stderr, "ffi command wrote to stderr");
626 }
627
628 Ok(output.stdout.abi_encode())
630 }
631}
632
633impl Cheatcode for tryFfiCall {
634 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
635 let Self { commandInput: input } = self;
636 ffi(state, input).map(|res| res.abi_encode())
637 }
638}
639
640impl Cheatcode for promptCall {
641 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
642 let Self { promptText: text } = self;
643 prompt(state, text, prompt_input).map(|res| res.abi_encode())
644 }
645}
646
647impl Cheatcode for promptSecretCall {
648 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
649 let Self { promptText: text } = self;
650 prompt(state, text, prompt_password).map(|res| res.abi_encode())
651 }
652}
653
654impl Cheatcode for promptSecretUintCall {
655 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
656 let Self { promptText: text } = self;
657 parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256))
658 }
659}
660
661impl Cheatcode for promptAddressCall {
662 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
663 let Self { promptText: text } = self;
664 parse(&prompt(state, text, prompt_input)?, &DynSolType::Address)
665 }
666}
667
668impl Cheatcode for promptUintCall {
669 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
670 let Self { promptText: text } = self;
671 parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256))
672 }
673}
674
675pub(super) fn write_file<FEN: FoundryEvmNetwork>(
676 state: &Cheatcodes<FEN>,
677 path: &Path,
678 contents: &[u8],
679) -> Result {
680 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
681 state.config.ensure_not_foundry_toml(&path)?;
683
684 if state.fs_commit {
685 fs::locked_write(path, contents)?;
686 }
687
688 Ok(Default::default())
689}
690
691fn read_dir<FEN: FoundryEvmNetwork>(
692 state: &Cheatcodes<FEN>,
693 path: &Path,
694 max_depth: u64,
695 follow_links: bool,
696) -> Result {
697 let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
698 let paths: Vec<DirEntry> = WalkDir::new(root)
699 .min_depth(1)
700 .max_depth(max_depth.try_into().unwrap_or(usize::MAX))
701 .follow_links(follow_links)
702 .contents_first(false)
703 .same_file_system(true)
704 .sort_by_file_name()
705 .into_iter()
706 .map(|entry| match entry {
707 Ok(entry) => DirEntry {
708 errorMessage: String::new(),
709 path: entry.path().display().to_string(),
710 depth: entry.depth() as u64,
711 isDir: entry.file_type().is_dir(),
712 isSymlink: entry.path_is_symlink(),
713 },
714 Err(e) => DirEntry {
715 errorMessage: e.to_string(),
716 path: e.path().map(|p| p.display().to_string()).unwrap_or_default(),
717 depth: e.depth() as u64,
718 isDir: false,
719 isSymlink: false,
720 },
721 })
722 .collect();
723 Ok(paths.abi_encode())
724}
725
726fn ffi<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>, input: &[String]) -> Result<FfiResult> {
727 ensure!(
728 state.config.ffi,
729 "FFI is disabled; add the `--ffi` flag to allow tests to call external commands"
730 );
731 ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command");
732 let mut cmd = Command::new(&input[0]);
733 cmd.args(&input[1..]);
734
735 debug!(target: "cheatcodes", ?cmd, "invoking ffi");
736
737 let output = cmd
738 .current_dir(&state.config.root)
739 .output()
740 .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?;
741
742 let trimmed_stdout = String::from_utf8(output.stdout)?;
745 let trimmed_stdout = trimmed_stdout.trim();
746 let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) {
747 hex
748 } else {
749 trimmed_stdout.as_bytes().to_vec()
750 };
751 Ok(FfiResult {
752 exitCode: output.status.code().unwrap_or(69),
753 stdout: encoded_stdout.into(),
754 stderr: output.stderr.into(),
755 })
756}
757
758fn prompt_input(prompt_text: &str) -> Result<String, dialoguer::Error> {
759 Input::new().allow_empty(true).with_prompt(prompt_text).interact_text()
760}
761
762fn prompt_password(prompt_text: &str) -> Result<String, dialoguer::Error> {
763 Password::new().with_prompt(prompt_text).interact()
764}
765
766fn prompt<FEN: FoundryEvmNetwork>(
767 state: &Cheatcodes<FEN>,
768 prompt_text: &str,
769 input: fn(&str) -> Result<String, dialoguer::Error>,
770) -> Result<String> {
771 let text_clone = prompt_text.to_string();
772 let timeout = state.config.prompt_timeout;
773 let (tx, rx) = mpsc::channel();
774
775 thread::spawn(move || {
776 let _ = tx.send(input(&text_clone));
777 });
778
779 match rx.recv_timeout(timeout) {
780 Ok(res) => res.map_err(|err| {
781 let _ = sh_println!();
782 err.to_string().into()
783 }),
784 Err(_) => {
785 let _ = sh_eprintln!();
786 Err("Prompt timed out".into())
787 }
788 }
789}
790
791impl Cheatcode for getBroadcastCall {
792 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
793 let Self { contractName, chainId, txType } = self;
794
795 let latest_broadcast = latest_broadcast(
796 contractName,
797 *chainId,
798 &state.config.broadcast,
799 vec![map_broadcast_tx_type(*txType)],
800 )?;
801
802 Ok(latest_broadcast.abi_encode())
803 }
804}
805
806impl Cheatcode for getBroadcasts_0Call {
807 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
808 let Self { contractName, chainId, txType } = self;
809
810 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
811 .with_tx_type(map_broadcast_tx_type(*txType));
812
813 let broadcasts = reader.read::<Ethereum>()?;
814
815 let summaries = broadcasts
816 .into_iter()
817 .flat_map(|broadcast| {
818 let results = reader.into_tx_receipts(broadcast);
819 parse_broadcast_results(results)
820 })
821 .collect::<Vec<_>>();
822
823 Ok(summaries.abi_encode())
824 }
825}
826
827impl Cheatcode for getBroadcasts_1Call {
828 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
829 let Self { contractName, chainId } = self;
830
831 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?;
832
833 let broadcasts = reader.read::<Ethereum>()?;
834
835 let summaries = broadcasts
836 .into_iter()
837 .flat_map(|broadcast| {
838 let results = reader.into_tx_receipts(broadcast);
839 parse_broadcast_results(results)
840 })
841 .collect::<Vec<_>>();
842
843 Ok(summaries.abi_encode())
844 }
845}
846
847impl Cheatcode for getDeployment_0Call {
848 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
849 let Self { contractName } = self;
850 let chain_id = ccx.ecx.cfg().chain_id();
851
852 let latest_broadcast = latest_broadcast(
853 contractName,
854 chain_id,
855 &ccx.state.config.broadcast,
856 vec![CallKind::Create, CallKind::Create2],
857 )?;
858
859 Ok(latest_broadcast.contractAddress.abi_encode())
860 }
861}
862
863impl Cheatcode for getDeployment_1Call {
864 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
865 let Self { contractName, chainId } = self;
866
867 let latest_broadcast = latest_broadcast(
868 contractName,
869 *chainId,
870 &state.config.broadcast,
871 vec![CallKind::Create, CallKind::Create2],
872 )?;
873
874 Ok(latest_broadcast.contractAddress.abi_encode())
875 }
876}
877
878impl Cheatcode for getDeploymentsCall {
879 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
880 let Self { contractName, chainId } = self;
881
882 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
883 .with_tx_type(CallKind::Create)
884 .with_tx_type(CallKind::Create2);
885
886 let broadcasts = reader.read::<Ethereum>()?;
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 let deployed_addresses =
897 summaries.into_iter().map(|summary| summary.contractAddress).collect::<Vec<_>>();
898
899 Ok(deployed_addresses.abi_encode())
900 }
901}
902
903fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind {
904 match tx_type {
905 BroadcastTxType::Call => CallKind::Call,
906 BroadcastTxType::Create => CallKind::Create,
907 BroadcastTxType::Create2 => CallKind::Create2,
908 _ => unreachable!("invalid tx type"),
909 }
910}
911
912fn parse_broadcast_results<N: Network>(
913 results: Vec<(TransactionWithMetadata<N>, N::ReceiptResponse)>,
914) -> Vec<BroadcastTxSummary> {
915 results
916 .into_iter()
917 .map(|(tx, receipt)| BroadcastTxSummary {
918 txHash: receipt.transaction_hash(),
919 blockNumber: receipt.block_number().unwrap_or_default(),
920 txType: match tx.call_kind {
921 CallKind::Call => BroadcastTxType::Call,
922 CallKind::Create => BroadcastTxType::Create,
923 CallKind::Create2 => BroadcastTxType::Create2,
924 _ => unreachable!("invalid tx type"),
925 },
926 contractAddress: tx.contract_address.unwrap_or_default(),
927 success: receipt.status(),
928 })
929 .collect()
930}
931
932fn latest_broadcast(
933 contract_name: &String,
934 chain_id: u64,
935 broadcast_path: &Path,
936 filters: Vec<CallKind>,
937) -> Result<BroadcastTxSummary> {
938 let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?;
939
940 for filter in filters {
941 reader = reader.with_tx_type(filter);
942 }
943
944 let broadcast = reader.read_latest::<Ethereum>()?;
945
946 let results = reader.into_tx_receipts(broadcast);
947
948 let summaries = parse_broadcast_results(results);
949
950 summaries
951 .first()
952 .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}"))
953 .cloned()
954}
955
956#[cfg(test)]
957mod tests {
958 use super::*;
959 use crate::CheatsConfig;
960 use std::sync::Arc;
961
962 fn cheats() -> Cheatcodes {
963 let config = CheatsConfig {
964 ffi: true,
965 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
966 ..Default::default()
967 };
968 Cheatcodes::new(Arc::new(config))
969 }
970
971 #[test]
972 fn test_ffi_hex() {
973 let msg = b"gm";
974 let cheats = cheats();
975 let args = ["echo".to_string(), hex::encode(msg)];
976 let output = ffi(&cheats, &args).unwrap();
977 assert_eq!(output.stdout, Bytes::from(msg));
978 }
979
980 #[test]
981 fn test_ffi_string() {
982 let msg = "gm";
983 let cheats = cheats();
984 let args = ["echo".to_string(), msg.to_string()];
985 let output = ffi(&cheats, &args).unwrap();
986 assert_eq!(output.stdout, Bytes::from(msg.as_bytes()));
987 }
988
989 #[test]
990 fn test_ffi_fails_on_error_code() {
991 let mut cheats = cheats();
992
993 #[cfg(unix)]
995 let args = vec!["false".to_string()];
996 #[cfg(windows)]
997 let args = vec!["cmd".to_string(), "/c".to_string(), "exit 1".to_string()];
998
999 let result = Cheatcode::apply(&ffiCall { commandInput: args }, &mut cheats);
1000
1001 assert!(result.is_err(), "Expected ffi cheatcode to fail, but it succeeded");
1003
1004 let err_msg = result.unwrap_err().to_string();
1006 assert!(
1007 err_msg.contains("exited with code 1"),
1008 "Error message did not contain exit code: {err_msg}"
1009 );
1010 }
1011
1012 #[test]
1013 fn test_artifact_parsing() {
1014 let s = include_str!("../../evm/test-data/solc-obj.json");
1015 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1016 assert!(artifact.bytecode.is_some());
1017
1018 let artifact: ContractObject = serde_json::from_str(s).unwrap();
1019 assert!(artifact.deployed_bytecode.is_some());
1020 }
1021
1022 #[test]
1023 fn test_alloy_json_abi_rejects_unlinked_bytecode() {
1024 let artifact_json = r#"{
1025 "abi": [],
1026 "bytecode": "0x73__$987e73aeca5e61ce83e4cb0814d87beda9$__63baf2f868"
1027 }"#;
1028
1029 let result: Result<ContractObject, _> = serde_json::from_str(artifact_json);
1030 assert!(result.is_err(), "should reject unlinked bytecode with placeholders");
1031 let err = result.unwrap_err().to_string();
1032 assert!(err.contains("expected bytecode, found unlinked bytecode with placeholder"));
1033 }
1034}