foundry_cheatcodes_spec/
lib.rs1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use serde::{Deserialize, Serialize};
7use std::{borrow::Cow, fmt};
8
9mod cheatcode;
10pub use cheatcode::{Cheatcode, CheatcodeDef, Group, Safety, Status};
11
12mod function;
13pub use function::{Function, Mutability, Visibility};
14
15mod items;
16pub use items::{Enum, EnumVariant, Error, Event, Struct, StructField};
17
18mod symbolic;
19pub use symbolic::SymbolicVm;
20
21mod vm;
22pub use vm::Vm;
23
24#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28#[serde(rename_all = "camelCase")]
29pub struct Cheatcodes<'a> {
30 #[serde(borrow)]
32 pub errors: Cow<'a, [Error<'a>]>,
33 #[serde(borrow)]
35 pub events: Cow<'a, [Event<'a>]>,
36 #[serde(borrow)]
38 pub enums: Cow<'a, [Enum<'a>]>,
39 #[serde(borrow)]
41 pub structs: Cow<'a, [Struct<'a>]>,
42 #[serde(borrow)]
44 pub cheatcodes: Cow<'a, [Cheatcode<'a>]>,
45}
46
47impl fmt::Display for Cheatcodes<'_> {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 for error in self.errors.iter() {
50 writeln!(f, "{error}")?;
51 }
52 for event in self.events.iter() {
53 writeln!(f, "{event}")?;
54 }
55 for enumm in self.enums.iter() {
56 writeln!(f, "{enumm}")?;
57 }
58 for strukt in self.structs.iter() {
59 writeln!(f, "{strukt}")?;
60 }
61 for cheatcode in self.cheatcodes.iter() {
62 writeln!(f, "{}", cheatcode.func)?;
63 }
64 Ok(())
65 }
66}
67
68impl Default for Cheatcodes<'static> {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl Cheatcodes<'static> {
75 pub fn new() -> Self {
77 Self {
78 structs: Cow::Owned(vec![
81 Vm::Log::STRUCT.clone(),
82 Vm::Rpc::STRUCT.clone(),
83 Vm::EthGetLogs::STRUCT.clone(),
84 Vm::DirEntry::STRUCT.clone(),
85 Vm::FsMetadata::STRUCT.clone(),
86 Vm::Wallet::STRUCT.clone(),
87 Vm::FfiResult::STRUCT.clone(),
88 Vm::ChainInfo::STRUCT.clone(),
89 Vm::Chain::STRUCT.clone(),
90 Vm::AccountAccess::STRUCT.clone(),
91 Vm::StorageAccess::STRUCT.clone(),
92 Vm::Gas::STRUCT.clone(),
93 Vm::DebugStep::STRUCT.clone(),
94 Vm::BroadcastTxSummary::STRUCT.clone(),
95 Vm::SignedDelegation::STRUCT.clone(),
96 Vm::PotentialRevert::STRUCT.clone(),
97 Vm::AccessListItem::STRUCT.clone(),
98 ]),
99 enums: Cow::Owned(vec![
100 Vm::CallerMode::ENUM.clone(),
101 Vm::AccountAccessKind::ENUM.clone(),
102 Vm::ForgeContext::ENUM.clone(),
103 Vm::BroadcastTxType::ENUM.clone(),
104 ]),
105 errors: Vm::VM_ERRORS.iter().copied().cloned().collect(),
106 events: Cow::Borrowed(&[]),
107 cheatcodes: Vm::CHEATCODES.iter().copied().cloned().collect(),
109 }
110 }
111}
112
113#[cfg(test)]
114#[expect(clippy::disallowed_macros)]
115mod tests {
116 use super::*;
117 use std::{fs, path::Path};
118
119 const JSON_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.json");
120 #[cfg(feature = "schema")]
121 const SCHEMA_PATH: &str =
122 concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.schema.json");
123 const IFACE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/utils/Vm.sol");
124
125 fn json_cheatcodes() -> String {
127 serde_json::to_string_pretty(&Cheatcodes::new()).unwrap()
128 }
129
130 #[cfg(feature = "schema")]
132 fn json_schema() -> String {
133 serde_json::to_string_pretty(&schemars::schema_for!(Cheatcodes<'_>)).unwrap()
134 }
135
136 fn sol_iface() -> String {
137 let mut cheats = Cheatcodes::new();
138 cheats.errors = Default::default(); let cheats = cheats.to_string().trim().replace('\n', "\n ");
140 format!(
141 "\
142// Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually.
143// This interface is just for internal testing purposes. Use `forge-std` instead.
144
145// SPDX-License-Identifier: MIT OR Apache-2.0
146pragma solidity >=0.6.2 <0.9.0;
147pragma experimental ABIEncoderV2;
148
149interface Vm {{
150 {cheats}
151}}
152"
153 )
154 }
155
156 #[test]
157 fn spec_up_to_date() {
158 ensure_file_contents(Path::new(JSON_PATH), &json_cheatcodes());
159 }
160
161 #[test]
162 #[cfg(feature = "schema")]
163 fn schema_up_to_date() {
164 ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema());
165 }
166
167 #[test]
168 fn iface_up_to_date() {
169 ensure_file_contents(Path::new(IFACE_PATH), &sol_iface());
170 }
171
172 fn ensure_file_contents(file: &Path, contents: &str) {
175 if let Ok(old_contents) = fs::read_to_string(file)
176 && normalize_newlines(&old_contents) == normalize_newlines(contents)
177 {
178 return;
180 }
181
182 eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display());
183 if std::env::var("CI").is_ok() {
184 eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n");
185 }
186 if let Some(parent) = file.parent() {
187 let _ = fs::create_dir_all(parent);
188 }
189 fs::write(file, contents).unwrap();
190 panic!("some file was not up to date and has been updated, simply re-run the tests");
191 }
192
193 fn normalize_newlines(s: &str) -> String {
194 s.replace("\r\n", "\n")
195 }
196}