foundry_config/
utils.rs
1use crate::Config;
4use alloy_primitives::U256;
5use figment::value::Value;
6use foundry_compilers::artifacts::{
7 remappings::{Remapping, RemappingError},
8 EvmVersion,
9};
10use revm_primitives::SpecId;
11use serde::{de::Error, Deserialize, Deserializer};
12use std::{
13 io,
14 path::{Path, PathBuf},
15 str::FromStr,
16};
17
18pub fn load_config() -> eyre::Result<Config> {
22 load_config_with_root(None)
23}
24
25pub fn load_config_with_root(root: Option<&Path>) -> eyre::Result<Config> {
27 let root = match root {
28 Some(root) => root,
29 None => &find_project_root(None)?,
30 };
31 Ok(Config::load_with_root(root)?.sanitized())
32}
33
34pub fn find_git_root(relative_to: &Path) -> io::Result<Option<PathBuf>> {
36 let root =
37 if relative_to.is_absolute() { relative_to } else { &dunce::canonicalize(relative_to)? };
38 Ok(root.ancestors().find(|p| p.join(".git").is_dir()).map(Path::to_path_buf))
39}
40
41pub fn find_project_root(cwd: Option<&Path>) -> io::Result<PathBuf> {
63 let cwd = match cwd {
64 Some(path) => path,
65 None => &std::env::current_dir()?,
66 };
67 let boundary = find_git_root(cwd)?;
68 let found = cwd
69 .ancestors()
70 .take_while(|p| if let Some(boundary) = &boundary { p.starts_with(boundary) } else { true })
72 .find(|p| p.join(Config::FILE_NAME).is_file())
73 .map(Path::to_path_buf);
74 Ok(found.or(boundary).unwrap_or_else(|| cwd.to_path_buf()))
75}
76
77pub fn remappings_from_newline(
92 remappings: &str,
93) -> impl Iterator<Item = Result<Remapping, RemappingError>> + '_ {
94 remappings.lines().map(|x| x.trim()).filter(|x| !x.is_empty()).map(Remapping::from_str)
95}
96
97pub fn remappings_from_env_var(env_var: &str) -> Option<Result<Vec<Remapping>, RemappingError>> {
102 let val = std::env::var(env_var).ok()?;
103 Some(remappings_from_newline(&val).collect())
104}
105
106pub fn to_array_value(val: &str) -> Result<Value, figment::Error> {
110 let value: Value = match Value::from(val) {
111 Value::String(_, val) => val
112 .trim_start_matches('[')
113 .trim_end_matches(']')
114 .split(',')
115 .map(|s| s.to_string())
116 .collect::<Vec<_>>()
117 .into(),
118 Value::Empty(_, _) => Vec::<Value>::new().into(),
119 val @ Value::Array(_, _) => val,
120 _ => return Err(format!("Invalid value `{val}`, expected an array").into()),
121 };
122 Ok(value)
123}
124
125pub fn foundry_toml_dirs(root: impl AsRef<Path>) -> Vec<PathBuf> {
147 walkdir::WalkDir::new(root)
148 .max_depth(1)
149 .into_iter()
150 .filter_map(Result::ok)
151 .filter(|e| e.file_type().is_dir())
152 .filter_map(|e| dunce::canonicalize(e.path()).ok())
153 .filter(|p| p.join(Config::FILE_NAME).exists())
154 .collect()
155}
156
157pub(crate) fn get_dir_remapping(dir: impl AsRef<Path>) -> Option<Remapping> {
159 let dir = dir.as_ref();
160 if let Some(dir_name) = dir.file_name().and_then(|s| s.to_str()).filter(|s| !s.is_empty()) {
161 let mut r = Remapping {
162 context: None,
163 name: format!("{dir_name}/"),
164 path: format!("{}", dir.display()),
165 };
166 if !r.path.ends_with('/') {
167 r.path.push('/')
168 }
169 Some(r)
170 } else {
171 None
172 }
173}
174
175pub(crate) fn deserialize_stringified_percent<'de, D>(deserializer: D) -> Result<u32, D::Error>
177where
178 D: Deserializer<'de>,
179{
180 let num: U256 = Numeric::deserialize(deserializer)?.into();
181 let num: u64 = num.try_into().map_err(serde::de::Error::custom)?;
182 if num <= 100 {
183 num.try_into().map_err(serde::de::Error::custom)
184 } else {
185 Err(serde::de::Error::custom("percent must be lte 100"))
186 }
187}
188
189pub(crate) fn deserialize_u64_or_max<'de, D>(deserializer: D) -> Result<u64, D::Error>
191where
192 D: Deserializer<'de>,
193{
194 #[derive(Deserialize)]
195 #[serde(untagged)]
196 enum Val {
197 Number(u64),
198 String(String),
199 }
200
201 match Val::deserialize(deserializer)? {
202 Val::Number(num) => Ok(num),
203 Val::String(s) if s.eq_ignore_ascii_case("max") => Ok(u64::MAX),
204 Val::String(s) => s.parse::<u64>().map_err(D::Error::custom),
205 }
206}
207
208pub(crate) fn deserialize_usize_or_max<'de, D>(deserializer: D) -> Result<usize, D::Error>
210where
211 D: Deserializer<'de>,
212{
213 deserialize_u64_or_max(deserializer)?.try_into().map_err(D::Error::custom)
214}
215
216#[derive(Clone, Copy, Deserialize)]
218#[serde(untagged)]
219pub enum Numeric {
220 U256(U256),
222 Num(u64),
224}
225
226impl From<Numeric> for U256 {
227 fn from(n: Numeric) -> Self {
228 match n {
229 Numeric::U256(n) => n,
230 Numeric::Num(n) => Self::from(n),
231 }
232 }
233}
234
235impl FromStr for Numeric {
236 type Err = String;
237
238 fn from_str(s: &str) -> Result<Self, Self::Err> {
239 if s.starts_with("0x") {
240 U256::from_str_radix(s, 16).map(Numeric::U256).map_err(|err| err.to_string())
241 } else {
242 U256::from_str(s).map(Numeric::U256).map_err(|err| err.to_string())
243 }
244 }
245}
246
247#[inline]
249pub fn evm_spec_id(evm_version: EvmVersion, odyssey: bool) -> SpecId {
250 if odyssey {
251 return SpecId::OSAKA;
252 }
253 match evm_version {
254 EvmVersion::Homestead => SpecId::HOMESTEAD,
255 EvmVersion::TangerineWhistle => SpecId::TANGERINE,
256 EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON,
257 EvmVersion::Byzantium => SpecId::BYZANTIUM,
258 EvmVersion::Constantinople => SpecId::CONSTANTINOPLE,
259 EvmVersion::Petersburg => SpecId::PETERSBURG,
260 EvmVersion::Istanbul => SpecId::ISTANBUL,
261 EvmVersion::Berlin => SpecId::BERLIN,
262 EvmVersion::London => SpecId::LONDON,
263 EvmVersion::Paris => SpecId::MERGE,
264 EvmVersion::Shanghai => SpecId::SHANGHAI,
265 EvmVersion::Cancun => SpecId::CANCUN,
266 EvmVersion::Prague => SpecId::PRAGUE,
267 EvmVersion::Osaka => SpecId::OSAKA,
268 }
269}