1use super::Result;
2use crate::Vm::Rpc;
3use alloy_primitives::{map::AddressHashMap, U256};
4use foundry_common::{fs::normalize_path, ContractsByArtifact};
5use foundry_compilers::{utils::canonicalize, ArtifactId, ProjectPathsConfig};
6use foundry_config::{
7 cache::StorageCachingConfig, fs_permissions::FsAccessKind, Config, FsPermissions,
8 ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl,
9};
10use foundry_evm_core::opts::EvmOpts;
11use std::{
12 path::{Path, PathBuf},
13 time::Duration,
14};
15
16#[derive(Clone, Debug)]
20pub struct CheatsConfig {
21 pub ffi: bool,
23 pub always_use_create_2_factory: bool,
25 pub prompt_timeout: Duration,
27 pub rpc_storage_caching: StorageCachingConfig,
29 pub no_storage_caching: bool,
31 pub rpc_endpoints: ResolvedRpcEndpoints,
33 pub paths: ProjectPathsConfig,
35 pub fs_permissions: FsPermissions,
37 pub root: PathBuf,
39 pub broadcast: PathBuf,
41 pub allowed_paths: Vec<PathBuf>,
43 pub evm_opts: EvmOpts,
45 pub labels: AddressHashMap<String>,
47 pub available_artifacts: Option<ContractsByArtifact>,
51 pub running_artifact: Option<ArtifactId>,
53 pub assertions_revert: bool,
55 pub seed: Option<U256>,
57 pub internal_expect_revert: bool,
59}
60
61impl CheatsConfig {
62 pub fn new(
64 config: &Config,
65 evm_opts: EvmOpts,
66 available_artifacts: Option<ContractsByArtifact>,
67 running_artifact: Option<ArtifactId>,
68 ) -> Self {
69 let mut allowed_paths = vec![config.root.clone()];
70 allowed_paths.extend(config.libs.iter().cloned());
71 allowed_paths.extend(config.allow_paths.iter().cloned());
72
73 let rpc_endpoints = config.rpc_endpoints.clone().resolved();
74 trace!(?rpc_endpoints, "using resolved rpc endpoints");
75
76 let available_artifacts =
78 if config.unchecked_cheatcode_artifacts { None } else { available_artifacts };
79
80 Self {
81 ffi: evm_opts.ffi,
82 always_use_create_2_factory: evm_opts.always_use_create_2_factory,
83 prompt_timeout: Duration::from_secs(config.prompt_timeout),
84 rpc_storage_caching: config.rpc_storage_caching.clone(),
85 no_storage_caching: config.no_storage_caching,
86 rpc_endpoints,
87 paths: config.project_paths(),
88 fs_permissions: config.fs_permissions.clone().joined(config.root.as_ref()),
89 root: config.root.clone(),
90 broadcast: config.root.clone().join(&config.broadcast),
91 allowed_paths,
92 evm_opts,
93 labels: config.labels.clone(),
94 available_artifacts,
95 running_artifact,
96 assertions_revert: config.assertions_revert,
97 seed: config.fuzz.seed,
98 internal_expect_revert: config.allow_internal_expect_revert,
99 }
100 }
101
102 pub fn clone_with(&self, config: &Config, evm_opts: EvmOpts) -> Self {
104 Self::new(config, evm_opts, self.available_artifacts.clone(), self.running_artifact.clone())
105 }
106
107 pub fn normalized_path(&self, path: impl AsRef<Path>) -> PathBuf {
111 let path = self.root.join(path);
112 canonicalize(&path).unwrap_or_else(|_| normalize_path(&path))
113 }
114
115 pub fn is_path_allowed(&self, path: impl AsRef<Path>, kind: FsAccessKind) -> bool {
122 self.is_normalized_path_allowed(&self.normalized_path(path), kind)
123 }
124
125 fn is_normalized_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool {
126 self.fs_permissions.is_path_allowed(path, kind)
127 }
128
129 pub fn ensure_path_allowed(
133 &self,
134 path: impl AsRef<Path>,
135 kind: FsAccessKind,
136 ) -> Result<PathBuf> {
137 let path = path.as_ref();
138 let normalized = self.normalized_path(path);
139 ensure!(
140 self.is_normalized_path_allowed(&normalized, kind),
141 "the path {} is not allowed to be accessed for {kind} operations",
142 normalized.strip_prefix(&self.root).unwrap_or(path).display()
143 );
144 Ok(normalized)
145 }
146
147 pub fn is_foundry_toml(&self, path: impl AsRef<Path>) -> bool {
151 let foundry_toml = self.root.join(Config::FILE_NAME);
156 Path::new(&foundry_toml.to_string_lossy().to_lowercase())
157 .starts_with(Path::new(&path.as_ref().to_string_lossy().to_lowercase()))
158 }
159
160 pub fn ensure_not_foundry_toml(&self, path: impl AsRef<Path>) -> Result<()> {
163 ensure!(!self.is_foundry_toml(path), "access to `foundry.toml` is not allowed");
164 Ok(())
165 }
166
167 pub fn rpc_endpoint(&self, url_or_alias: &str) -> Result<ResolvedRpcEndpoint> {
181 if let Some(endpoint) = self.rpc_endpoints.get(url_or_alias) {
182 Ok(endpoint.clone().try_resolve())
183 } else {
184 if url_or_alias.starts_with("http") ||
186 url_or_alias.starts_with("ws") ||
187 Path::new(url_or_alias).exists()
189 {
190 let url = RpcEndpointUrl::Env(url_or_alias.to_string());
191 Ok(RpcEndpoint::new(url).resolve())
192 } else {
193 Err(fmt_err!("invalid rpc url: {url_or_alias}"))
194 }
195 }
196 }
197 pub fn rpc_urls(&self) -> Result<Vec<Rpc>> {
199 let mut urls = Vec::with_capacity(self.rpc_endpoints.len());
200 for alias in self.rpc_endpoints.keys() {
201 let url = self.rpc_endpoint(alias)?.url()?;
202 urls.push(Rpc { key: alias.clone(), url });
203 }
204 Ok(urls)
205 }
206}
207
208impl Default for CheatsConfig {
209 fn default() -> Self {
210 Self {
211 ffi: false,
212 always_use_create_2_factory: false,
213 prompt_timeout: Duration::from_secs(120),
214 rpc_storage_caching: Default::default(),
215 no_storage_caching: false,
216 rpc_endpoints: Default::default(),
217 paths: ProjectPathsConfig::builder().build_with_root("./"),
218 fs_permissions: Default::default(),
219 root: Default::default(),
220 broadcast: Default::default(),
221 allowed_paths: vec![],
222 evm_opts: Default::default(),
223 labels: Default::default(),
224 available_artifacts: Default::default(),
225 running_artifact: Default::default(),
226 assertions_revert: true,
227 seed: None,
228 internal_expect_revert: false,
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use foundry_config::fs_permissions::PathPermission;
237
238 fn config(root: &str, fs_permissions: FsPermissions) -> CheatsConfig {
239 CheatsConfig::new(
240 &Config { root: root.into(), fs_permissions, ..Default::default() },
241 Default::default(),
242 None,
243 None,
244 )
245 }
246
247 #[test]
248 fn test_allowed_paths() {
249 let root = "/my/project/root/";
250 let config = config(root, FsPermissions::new(vec![PathPermission::read_write("./")]));
251
252 assert!(config.ensure_path_allowed("./t.txt", FsAccessKind::Read).is_ok());
253 assert!(config.ensure_path_allowed("./t.txt", FsAccessKind::Write).is_ok());
254 assert!(config.ensure_path_allowed("../root/t.txt", FsAccessKind::Read).is_ok());
255 assert!(config.ensure_path_allowed("../root/t.txt", FsAccessKind::Write).is_ok());
256 assert!(config.ensure_path_allowed("../../root/t.txt", FsAccessKind::Read).is_err());
257 assert!(config.ensure_path_allowed("../../root/t.txt", FsAccessKind::Write).is_err());
258 }
259
260 #[test]
261 fn test_is_foundry_toml() {
262 let root = "/my/project/root/";
263 let config = config(root, FsPermissions::new(vec![PathPermission::read_write("./")]));
264
265 let f = format!("{root}foundry.toml");
266 assert!(config.is_foundry_toml(f));
267
268 let f = format!("{root}Foundry.toml");
269 assert!(config.is_foundry_toml(f));
270
271 let f = format!("{root}lib/other/foundry.toml");
272 assert!(!config.is_foundry_toml(f));
273 }
274}