1use super::string::parse;
4use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*};
5use alloy_dyn_abi::DynSolType;
6use alloy_json_abi::ContractObject;
7use alloy_network::AnyTransactionReceipt;
8use alloy_primitives::{hex, map::Entry, Bytes, U256};
9use alloy_provider::network::ReceiptResponse;
10use alloy_sol_types::SolValue;
11use dialoguer::{Input, Password};
12use forge_script_sequence::{BroadcastReader, TransactionWithMetadata};
13use foundry_common::fs;
14use foundry_config::fs_permissions::FsAccessKind;
15use revm::interpreter::CreateInputs;
16use revm_inspectors::tracing::types::CallKind;
17use semver::Version;
18use std::{
19 io::{BufRead, BufReader, Write},
20 path::{Path, PathBuf},
21 process::Command,
22 sync::mpsc,
23 thread,
24 time::{SystemTime, UNIX_EPOCH},
25};
26use walkdir::WalkDir;
27
28impl Cheatcode for existsCall {
29 fn apply(&self, state: &mut Cheatcodes) -> Result {
30 let Self { path } = self;
31 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
32 Ok(path.exists().abi_encode())
33 }
34}
35
36impl Cheatcode for fsMetadataCall {
37 fn apply(&self, state: &mut Cheatcodes) -> Result {
38 let Self { path } = self;
39 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
40
41 let metadata = path.metadata()?;
42
43 let [modified, accessed, created] =
45 [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| {
46 time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
47 });
48
49 Ok(FsMetadata {
50 isDir: metadata.is_dir(),
51 isSymlink: metadata.is_symlink(),
52 length: U256::from(metadata.len()),
53 readOnly: metadata.permissions().readonly(),
54 modified: U256::from(modified),
55 accessed: U256::from(accessed),
56 created: U256::from(created),
57 }
58 .abi_encode())
59 }
60}
61
62impl Cheatcode for isDirCall {
63 fn apply(&self, state: &mut Cheatcodes) -> Result {
64 let Self { path } = self;
65 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
66 Ok(path.is_dir().abi_encode())
67 }
68}
69
70impl Cheatcode for isFileCall {
71 fn apply(&self, state: &mut Cheatcodes) -> Result {
72 let Self { path } = self;
73 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
74 Ok(path.is_file().abi_encode())
75 }
76}
77
78impl Cheatcode for projectRootCall {
79 fn apply(&self, state: &mut Cheatcodes) -> Result {
80 let Self {} = self;
81 Ok(state.config.root.display().to_string().abi_encode())
82 }
83}
84
85impl Cheatcode for unixTimeCall {
86 fn apply(&self, _state: &mut Cheatcodes) -> Result {
87 let Self {} = self;
88 let difference = SystemTime::now()
89 .duration_since(UNIX_EPOCH)
90 .map_err(|e| fmt_err!("failed getting Unix timestamp: {e}"))?;
91 Ok(difference.as_millis().abi_encode())
92 }
93}
94
95impl Cheatcode for closeFileCall {
96 fn apply(&self, state: &mut Cheatcodes) -> Result {
97 let Self { path } = self;
98 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
99
100 state.context.opened_read_files.remove(&path);
101
102 Ok(Default::default())
103 }
104}
105
106impl Cheatcode for copyFileCall {
107 fn apply(&self, state: &mut Cheatcodes) -> Result {
108 let Self { from, to } = self;
109 let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?;
110 let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?;
111 state.config.ensure_not_foundry_toml(&to)?;
112
113 let n = fs::copy(from, to)?;
114 Ok(n.abi_encode())
115 }
116}
117
118impl Cheatcode for createDirCall {
119 fn apply(&self, state: &mut Cheatcodes) -> Result {
120 let Self { path, recursive } = self;
121 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
122 if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?;
123 Ok(Default::default())
124 }
125}
126
127impl Cheatcode for readDir_0Call {
128 fn apply(&self, state: &mut Cheatcodes) -> Result {
129 let Self { path } = self;
130 read_dir(state, path.as_ref(), 1, false)
131 }
132}
133
134impl Cheatcode for readDir_1Call {
135 fn apply(&self, state: &mut Cheatcodes) -> Result {
136 let Self { path, maxDepth } = self;
137 read_dir(state, path.as_ref(), *maxDepth, false)
138 }
139}
140
141impl Cheatcode for readDir_2Call {
142 fn apply(&self, state: &mut Cheatcodes) -> Result {
143 let Self { path, maxDepth, followLinks } = self;
144 read_dir(state, path.as_ref(), *maxDepth, *followLinks)
145 }
146}
147
148impl Cheatcode for readFileCall {
149 fn apply(&self, state: &mut Cheatcodes) -> Result {
150 let Self { path } = self;
151 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
152 Ok(fs::read_to_string(path)?.abi_encode())
153 }
154}
155
156impl Cheatcode for readFileBinaryCall {
157 fn apply(&self, state: &mut Cheatcodes) -> Result {
158 let Self { path } = self;
159 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
160 Ok(fs::read(path)?.abi_encode())
161 }
162}
163
164impl Cheatcode for readLineCall {
165 fn apply(&self, state: &mut Cheatcodes) -> Result {
166 let Self { path } = self;
167 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
168
169 let reader = match state.context.opened_read_files.entry(path.clone()) {
171 Entry::Occupied(entry) => entry.into_mut(),
172 Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)),
173 };
174
175 let mut line: String = String::new();
176 reader.read_line(&mut line)?;
177
178 if line.ends_with('\n') {
180 line.pop();
181 if line.ends_with('\r') {
182 line.pop();
183 }
184 }
185
186 Ok(line.abi_encode())
187 }
188}
189
190impl Cheatcode for readLinkCall {
191 fn apply(&self, state: &mut Cheatcodes) -> Result {
192 let Self { linkPath: path } = self;
193 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
194 let target = fs::read_link(path)?;
195 Ok(target.display().to_string().abi_encode())
196 }
197}
198
199impl Cheatcode for removeDirCall {
200 fn apply(&self, state: &mut Cheatcodes) -> Result {
201 let Self { path, recursive } = self;
202 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
203 if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?;
204 Ok(Default::default())
205 }
206}
207
208impl Cheatcode for removeFileCall {
209 fn apply(&self, state: &mut Cheatcodes) -> Result {
210 let Self { path } = self;
211 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
212 state.config.ensure_not_foundry_toml(&path)?;
213
214 state.context.opened_read_files.remove(&path);
216
217 if state.fs_commit {
218 fs::remove_file(&path)?;
219 }
220
221 Ok(Default::default())
222 }
223}
224
225impl Cheatcode for writeFileCall {
226 fn apply(&self, state: &mut Cheatcodes) -> Result {
227 let Self { path, data } = self;
228 write_file(state, path.as_ref(), data.as_bytes())
229 }
230}
231
232impl Cheatcode for writeFileBinaryCall {
233 fn apply(&self, state: &mut Cheatcodes) -> Result {
234 let Self { path, data } = self;
235 write_file(state, path.as_ref(), data)
236 }
237}
238
239impl Cheatcode for writeLineCall {
240 fn apply(&self, state: &mut Cheatcodes) -> Result {
241 let Self { path, data: line } = self;
242 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
243 state.config.ensure_not_foundry_toml(&path)?;
244
245 if state.fs_commit {
246 let mut file = std::fs::OpenOptions::new().append(true).create(true).open(path)?;
247
248 writeln!(file, "{line}")?;
249 }
250
251 Ok(Default::default())
252 }
253}
254
255impl Cheatcode for getArtifactPathByCodeCall {
256 fn apply(&self, state: &mut Cheatcodes) -> Result {
257 let Self { code } = self;
258 let (artifact_id, _) = state
259 .config
260 .available_artifacts
261 .as_ref()
262 .and_then(|artifacts| artifacts.find_by_creation_code(code))
263 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
264
265 Ok(artifact_id.path.to_string_lossy().abi_encode())
266 }
267}
268
269impl Cheatcode for getArtifactPathByDeployedCodeCall {
270 fn apply(&self, state: &mut Cheatcodes) -> Result {
271 let Self { deployedCode } = self;
272 let (artifact_id, _) = state
273 .config
274 .available_artifacts
275 .as_ref()
276 .and_then(|artifacts| artifacts.find_by_deployed_code(deployedCode))
277 .ok_or_else(|| fmt_err!("no matching artifact found"))?;
278
279 Ok(artifact_id.path.to_string_lossy().abi_encode())
280 }
281}
282
283impl Cheatcode for getCodeCall {
284 fn apply(&self, state: &mut Cheatcodes) -> Result {
285 let Self { artifactPath: path } = self;
286 Ok(get_artifact_code(state, path, false)?.abi_encode())
287 }
288}
289
290impl Cheatcode for getDeployedCodeCall {
291 fn apply(&self, state: &mut Cheatcodes) -> Result {
292 let Self { artifactPath: path } = self;
293 Ok(get_artifact_code(state, path, true)?.abi_encode())
294 }
295}
296
297impl Cheatcode for deployCode_0Call {
298 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
299 let Self { artifactPath: path } = self;
300 deploy_code(ccx, executor, path, None, None, None)
301 }
302}
303
304impl Cheatcode for deployCode_1Call {
305 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
306 let Self { artifactPath: path, constructorArgs: args } = self;
307 deploy_code(ccx, executor, path, Some(args), None, None)
308 }
309}
310
311impl Cheatcode for deployCode_2Call {
312 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
313 let Self { artifactPath: path, value } = self;
314 deploy_code(ccx, executor, path, None, Some(*value), None)
315 }
316}
317
318impl Cheatcode for deployCode_3Call {
319 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
320 let Self { artifactPath: path, constructorArgs: args, value } = self;
321 deploy_code(ccx, executor, path, Some(args), Some(*value), None)
322 }
323}
324
325impl Cheatcode for deployCode_4Call {
326 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
327 let Self { artifactPath: path, salt } = self;
328 deploy_code(ccx, executor, path, None, None, Some((*salt).into()))
329 }
330}
331
332impl Cheatcode for deployCode_5Call {
333 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
334 let Self { artifactPath: path, constructorArgs: args, salt } = self;
335 deploy_code(ccx, executor, path, Some(args), None, Some((*salt).into()))
336 }
337}
338
339impl Cheatcode for deployCode_6Call {
340 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
341 let Self { artifactPath: path, value, salt } = self;
342 deploy_code(ccx, executor, path, None, Some(*value), Some((*salt).into()))
343 }
344}
345
346impl Cheatcode for deployCode_7Call {
347 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
348 let Self { artifactPath: path, constructorArgs: args, value, salt } = self;
349 deploy_code(ccx, executor, path, Some(args), Some(*value), Some((*salt).into()))
350 }
351}
352
353fn deploy_code(
356 ccx: &mut CheatsCtxt,
357 executor: &mut dyn CheatcodesExecutor,
358 path: &str,
359 constructor_args: Option<&Bytes>,
360 value: Option<U256>,
361 salt: Option<U256>,
362) -> Result {
363 let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
364 if let Some(args) = constructor_args {
365 bytecode.extend_from_slice(args);
366 }
367
368 let scheme = if let Some(salt) = salt {
369 revm::primitives::CreateScheme::Create2 { salt }
370 } else {
371 revm::primitives::CreateScheme::Create
372 };
373
374 let outcome = executor.exec_create(
375 CreateInputs {
376 caller: ccx.caller,
377 scheme,
378 value: value.unwrap_or(U256::ZERO),
379 init_code: bytecode.into(),
380 gas_limit: ccx.gas_limit,
381 },
382 ccx,
383 )?;
384
385 if !outcome.result.result.is_ok() {
386 return Err(crate::Error::from(outcome.result.output))
387 }
388
389 let address = outcome.address.ok_or_else(|| fmt_err!("contract creation failed"))?;
390
391 Ok(address.abi_encode())
392}
393
394fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result<Bytes> {
405 let path = if path.ends_with(".json") {
406 PathBuf::from(path)
407 } else {
408 let mut parts = path.split(':');
409
410 let mut file = None;
411 let mut contract_name = None;
412 let mut version = None;
413
414 let path_or_name = parts.next().unwrap();
415 if path_or_name.contains('.') {
416 file = Some(PathBuf::from(path_or_name));
417 if let Some(name_or_version) = parts.next() {
418 if name_or_version.contains('.') {
419 version = Some(name_or_version);
420 } else {
421 contract_name = Some(name_or_version);
422 version = parts.next();
423 }
424 }
425 } else {
426 contract_name = Some(path_or_name);
427 version = parts.next();
428 }
429
430 let version = if let Some(version) = version {
431 Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?)
432 } else {
433 None
434 };
435
436 if let Some(artifacts) = &state.config.available_artifacts {
438 let filtered = artifacts
439 .iter()
440 .filter(|(id, _)| {
441 let id_name = id.name.split('.').next().unwrap();
443
444 if let Some(path) = &file {
445 if !id.source.ends_with(path) {
446 return false;
447 }
448 }
449 if let Some(name) = contract_name {
450 if id_name != name {
451 return false;
452 }
453 }
454 if let Some(ref version) = version {
455 if id.version.minor != version.minor ||
456 id.version.major != version.major ||
457 id.version.patch != version.patch
458 {
459 return false;
460 }
461 }
462 true
463 })
464 .collect::<Vec<_>>();
465
466 let artifact = match &filtered[..] {
467 [] => Err(fmt_err!("no matching artifact found")),
468 [artifact] => Ok(*artifact),
469 filtered => {
470 let mut filtered = filtered.to_vec();
471 state
473 .config
474 .running_artifact
475 .as_ref()
476 .and_then(|running| {
477 filtered.retain(|(id, _)| id.version == running.version);
479
480 if filtered.len() == 1 {
482 return Some(filtered[0])
483 }
484
485 filtered.retain(|(id, _)| id.profile == running.profile);
487
488 if filtered.len() == 1 {
489 Some(filtered[0])
490 } else {
491 None
492 }
493 })
494 .ok_or_else(|| fmt_err!("multiple matching artifacts found"))
495 }
496 }?;
497
498 let maybe_bytecode = if deployed {
499 artifact.1.deployed_bytecode().cloned()
500 } else {
501 artifact.1.bytecode().cloned()
502 };
503
504 return maybe_bytecode
505 .ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"));
506 } else {
507 let path_in_artifacts =
508 match (file.map(|f| f.to_string_lossy().to_string()), contract_name) {
509 (Some(file), Some(contract_name)) => {
510 PathBuf::from(format!("{file}/{contract_name}.json"))
511 }
512 (None, Some(contract_name)) => {
513 PathBuf::from(format!("{contract_name}.sol/{contract_name}.json"))
514 }
515 (Some(file), None) => {
516 let name = file.replace(".sol", "");
517 PathBuf::from(format!("{file}/{name}.json"))
518 }
519 _ => bail!("invalid artifact path"),
520 };
521
522 state.config.paths.artifacts.join(path_in_artifacts)
523 }
524 };
525
526 let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
527 let data = fs::read_to_string(path)?;
528 let artifact = serde_json::from_str::<ContractObject>(&data)?;
529 let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode };
530 maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?"))
531}
532
533impl Cheatcode for ffiCall {
534 fn apply(&self, state: &mut Cheatcodes) -> Result {
535 let Self { commandInput: input } = self;
536
537 let output = ffi(state, input)?;
538 if !output.stderr.is_empty() {
540 let stderr = String::from_utf8_lossy(&output.stderr);
541 error!(target: "cheatcodes", ?input, ?stderr, "non-empty stderr");
542 }
543 Ok(output.stdout.abi_encode())
545 }
546}
547
548impl Cheatcode for tryFfiCall {
549 fn apply(&self, state: &mut Cheatcodes) -> Result {
550 let Self { commandInput: input } = self;
551 ffi(state, input).map(|res| res.abi_encode())
552 }
553}
554
555impl Cheatcode for promptCall {
556 fn apply(&self, state: &mut Cheatcodes) -> Result {
557 let Self { promptText: text } = self;
558 prompt(state, text, prompt_input).map(|res| res.abi_encode())
559 }
560}
561
562impl Cheatcode for promptSecretCall {
563 fn apply(&self, state: &mut Cheatcodes) -> Result {
564 let Self { promptText: text } = self;
565 prompt(state, text, prompt_password).map(|res| res.abi_encode())
566 }
567}
568
569impl Cheatcode for promptSecretUintCall {
570 fn apply(&self, state: &mut Cheatcodes) -> Result {
571 let Self { promptText: text } = self;
572 parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256))
573 }
574}
575
576impl Cheatcode for promptAddressCall {
577 fn apply(&self, state: &mut Cheatcodes) -> Result {
578 let Self { promptText: text } = self;
579 parse(&prompt(state, text, prompt_input)?, &DynSolType::Address)
580 }
581}
582
583impl Cheatcode for promptUintCall {
584 fn apply(&self, state: &mut Cheatcodes) -> Result {
585 let Self { promptText: text } = self;
586 parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256))
587 }
588}
589
590pub(super) fn write_file(state: &Cheatcodes, path: &Path, contents: &[u8]) -> Result {
591 let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?;
592 state.config.ensure_not_foundry_toml(&path)?;
594
595 if state.fs_commit {
596 fs::write(path, contents)?;
597 }
598
599 Ok(Default::default())
600}
601
602fn read_dir(state: &Cheatcodes, path: &Path, max_depth: u64, follow_links: bool) -> Result {
603 let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?;
604 let paths: Vec<DirEntry> = WalkDir::new(root)
605 .min_depth(1)
606 .max_depth(max_depth.try_into().unwrap_or(usize::MAX))
607 .follow_links(follow_links)
608 .contents_first(false)
609 .same_file_system(true)
610 .sort_by_file_name()
611 .into_iter()
612 .map(|entry| match entry {
613 Ok(entry) => DirEntry {
614 errorMessage: String::new(),
615 path: entry.path().display().to_string(),
616 depth: entry.depth() as u64,
617 isDir: entry.file_type().is_dir(),
618 isSymlink: entry.path_is_symlink(),
619 },
620 Err(e) => DirEntry {
621 errorMessage: e.to_string(),
622 path: e.path().map(|p| p.display().to_string()).unwrap_or_default(),
623 depth: e.depth() as u64,
624 isDir: false,
625 isSymlink: false,
626 },
627 })
628 .collect();
629 Ok(paths.abi_encode())
630}
631
632fn ffi(state: &Cheatcodes, input: &[String]) -> Result<FfiResult> {
633 ensure!(
634 state.config.ffi,
635 "FFI is disabled; add the `--ffi` flag to allow tests to call external commands"
636 );
637 ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command");
638 let mut cmd = Command::new(&input[0]);
639 cmd.args(&input[1..]);
640
641 debug!(target: "cheatcodes", ?cmd, "invoking ffi");
642
643 let output = cmd
644 .current_dir(&state.config.root)
645 .output()
646 .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?;
647
648 let trimmed_stdout = String::from_utf8(output.stdout)?;
651 let trimmed_stdout = trimmed_stdout.trim();
652 let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) {
653 hex
654 } else {
655 trimmed_stdout.as_bytes().to_vec()
656 };
657 Ok(FfiResult {
658 exitCode: output.status.code().unwrap_or(69),
659 stdout: encoded_stdout.into(),
660 stderr: output.stderr.into(),
661 })
662}
663
664fn prompt_input(prompt_text: &str) -> Result<String, dialoguer::Error> {
665 Input::new().allow_empty(true).with_prompt(prompt_text).interact_text()
666}
667
668fn prompt_password(prompt_text: &str) -> Result<String, dialoguer::Error> {
669 Password::new().with_prompt(prompt_text).interact()
670}
671
672fn prompt(
673 state: &Cheatcodes,
674 prompt_text: &str,
675 input: fn(&str) -> Result<String, dialoguer::Error>,
676) -> Result<String> {
677 let text_clone = prompt_text.to_string();
678 let timeout = state.config.prompt_timeout;
679 let (tx, rx) = mpsc::channel();
680
681 thread::spawn(move || {
682 let _ = tx.send(input(&text_clone));
683 });
684
685 match rx.recv_timeout(timeout) {
686 Ok(res) => res.map_err(|err| {
687 let _ = sh_println!();
688 err.to_string().into()
689 }),
690 Err(_) => {
691 let _ = sh_eprintln!();
692 Err("Prompt timed out".into())
693 }
694 }
695}
696
697impl Cheatcode for getBroadcastCall {
698 fn apply(&self, state: &mut Cheatcodes) -> Result {
699 let Self { contractName, chainId, txType } = self;
700
701 let latest_broadcast = latest_broadcast(
702 contractName,
703 *chainId,
704 &state.config.broadcast,
705 vec![map_broadcast_tx_type(*txType)],
706 )?;
707
708 Ok(latest_broadcast.abi_encode())
709 }
710}
711
712impl Cheatcode for getBroadcasts_0Call {
713 fn apply(&self, state: &mut Cheatcodes) -> Result {
714 let Self { contractName, chainId, txType } = self;
715
716 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
717 .with_tx_type(map_broadcast_tx_type(*txType));
718
719 let broadcasts = reader.read()?;
720
721 let summaries = broadcasts
722 .into_iter()
723 .flat_map(|broadcast| {
724 let results = reader.into_tx_receipts(broadcast);
725 parse_broadcast_results(results)
726 })
727 .collect::<Vec<_>>();
728
729 Ok(summaries.abi_encode())
730 }
731}
732
733impl Cheatcode for getBroadcasts_1Call {
734 fn apply(&self, state: &mut Cheatcodes) -> Result {
735 let Self { contractName, chainId } = self;
736
737 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?;
738
739 let broadcasts = reader.read()?;
740
741 let summaries = broadcasts
742 .into_iter()
743 .flat_map(|broadcast| {
744 let results = reader.into_tx_receipts(broadcast);
745 parse_broadcast_results(results)
746 })
747 .collect::<Vec<_>>();
748
749 Ok(summaries.abi_encode())
750 }
751}
752
753impl Cheatcode for getDeployment_0Call {
754 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
755 let Self { contractName } = self;
756 let chain_id = ccx.ecx.env.cfg.chain_id;
757
758 let latest_broadcast = latest_broadcast(
759 contractName,
760 chain_id,
761 &ccx.state.config.broadcast,
762 vec![CallKind::Create, CallKind::Create2],
763 )?;
764
765 Ok(latest_broadcast.contractAddress.abi_encode())
766 }
767}
768
769impl Cheatcode for getDeployment_1Call {
770 fn apply(&self, state: &mut Cheatcodes) -> Result {
771 let Self { contractName, chainId } = self;
772
773 let latest_broadcast = latest_broadcast(
774 contractName,
775 *chainId,
776 &state.config.broadcast,
777 vec![CallKind::Create, CallKind::Create2],
778 )?;
779
780 Ok(latest_broadcast.contractAddress.abi_encode())
781 }
782}
783
784impl Cheatcode for getDeploymentsCall {
785 fn apply(&self, state: &mut Cheatcodes) -> Result {
786 let Self { contractName, chainId } = self;
787
788 let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?
789 .with_tx_type(CallKind::Create)
790 .with_tx_type(CallKind::Create2);
791
792 let broadcasts = reader.read()?;
793
794 let summaries = broadcasts
795 .into_iter()
796 .flat_map(|broadcast| {
797 let results = reader.into_tx_receipts(broadcast);
798 parse_broadcast_results(results)
799 })
800 .collect::<Vec<_>>();
801
802 let deployed_addresses =
803 summaries.into_iter().map(|summary| summary.contractAddress).collect::<Vec<_>>();
804
805 Ok(deployed_addresses.abi_encode())
806 }
807}
808
809fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind {
810 match tx_type {
811 BroadcastTxType::Call => CallKind::Call,
812 BroadcastTxType::Create => CallKind::Create,
813 BroadcastTxType::Create2 => CallKind::Create2,
814 _ => unreachable!("invalid tx type"),
815 }
816}
817
818fn parse_broadcast_results(
819 results: Vec<(TransactionWithMetadata, AnyTransactionReceipt)>,
820) -> Vec<BroadcastTxSummary> {
821 results
822 .into_iter()
823 .map(|(tx, receipt)| BroadcastTxSummary {
824 txHash: receipt.transaction_hash,
825 blockNumber: receipt.block_number.unwrap_or_default(),
826 txType: match tx.opcode {
827 CallKind::Call => BroadcastTxType::Call,
828 CallKind::Create => BroadcastTxType::Create,
829 CallKind::Create2 => BroadcastTxType::Create2,
830 _ => unreachable!("invalid tx type"),
831 },
832 contractAddress: tx.contract_address.unwrap_or_default(),
833 success: receipt.status(),
834 })
835 .collect()
836}
837
838fn latest_broadcast(
839 contract_name: &String,
840 chain_id: u64,
841 broadcast_path: &Path,
842 filters: Vec<CallKind>,
843) -> Result<BroadcastTxSummary> {
844 let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?;
845
846 for filter in filters {
847 reader = reader.with_tx_type(filter);
848 }
849
850 let broadcast = reader.read_latest()?;
851
852 let results = reader.into_tx_receipts(broadcast);
853
854 let summaries = parse_broadcast_results(results);
855
856 summaries
857 .first()
858 .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}"))
859 .cloned()
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865 use crate::CheatsConfig;
866 use std::sync::Arc;
867
868 fn cheats() -> Cheatcodes {
869 let config = CheatsConfig {
870 ffi: true,
871 root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")),
872 ..Default::default()
873 };
874 Cheatcodes::new(Arc::new(config))
875 }
876
877 #[test]
878 fn test_ffi_hex() {
879 let msg = b"gm";
880 let cheats = cheats();
881 let args = ["echo".to_string(), hex::encode(msg)];
882 let output = ffi(&cheats, &args).unwrap();
883 assert_eq!(output.stdout, Bytes::from(msg));
884 }
885
886 #[test]
887 fn test_ffi_string() {
888 let msg = "gm";
889 let cheats = cheats();
890 let args = ["echo".to_string(), msg.to_string()];
891 let output = ffi(&cheats, &args).unwrap();
892 assert_eq!(output.stdout, Bytes::from(msg.as_bytes()));
893 }
894
895 #[test]
896 fn test_artifact_parsing() {
897 let s = include_str!("../../evm/test-data/solc-obj.json");
898 let artifact: ContractObject = serde_json::from_str(s).unwrap();
899 assert!(artifact.bytecode.is_some());
900
901 let artifact: ContractObject = serde_json::from_str(s).unwrap();
902 assert!(artifact.deployed_bytecode.is_some());
903 }
904}