foundry_config/providers/
ext.rs
1use crate::{utils, Config};
2use figment::{
3 providers::{Env, Format, Toml},
4 value::{Dict, Map, Value},
5 Error, Figment, Metadata, Profile, Provider,
6};
7use foundry_compilers::ProjectPathsConfig;
8use inflector::Inflector;
9use std::path::{Path, PathBuf};
10
11pub(crate) trait ProviderExt: Provider + Sized {
12 fn rename(
13 self,
14 from: impl Into<Profile>,
15 to: impl Into<Profile>,
16 ) -> RenameProfileProvider<Self> {
17 RenameProfileProvider::new(self, from, to)
18 }
19
20 fn wrap(
21 self,
22 wrapping_key: impl Into<Profile>,
23 profile: impl Into<Profile>,
24 ) -> WrapProfileProvider<Self> {
25 WrapProfileProvider::new(self, wrapping_key, profile)
26 }
27
28 fn strict_select(
29 self,
30 profiles: impl IntoIterator<Item = impl Into<Profile>>,
31 ) -> OptionalStrictProfileProvider<Self> {
32 OptionalStrictProfileProvider::new(self, profiles)
33 }
34
35 fn fallback(
36 self,
37 profile: impl Into<Profile>,
38 fallback: impl Into<Profile>,
39 ) -> FallbackProfileProvider<Self> {
40 FallbackProfileProvider::new(self, profile, fallback)
41 }
42}
43
44impl<P: Provider> ProviderExt for P {}
45
46pub(crate) struct TomlFileProvider {
49 pub env_var: Option<&'static str>,
50 pub default: PathBuf,
51 pub cache: Option<Result<Map<Profile, Dict>, Error>>,
52}
53
54impl TomlFileProvider {
55 pub(crate) fn new(env_var: Option<&'static str>, default: impl Into<PathBuf>) -> Self {
56 Self { env_var, default: default.into(), cache: None }
57 }
58
59 fn env_val(&self) -> Option<String> {
60 self.env_var.and_then(Env::var)
61 }
62
63 fn file(&self) -> PathBuf {
64 self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone())
65 }
66
67 fn is_missing(&self) -> bool {
68 if let Some(file) = self.env_val() {
69 let path = Path::new(&file);
70 if !path.exists() {
71 return true;
72 }
73 }
74 false
75 }
76
77 pub(crate) fn cached(mut self) -> Self {
78 self.cache = Some(self.read());
79 self
80 }
81
82 fn read(&self) -> Result<Map<Profile, Dict>, Error> {
83 use serde::de::Error as _;
84 if let Some(file) = self.env_val() {
85 let path = Path::new(&file);
86 if !path.exists() {
87 return Err(Error::custom(format!(
88 "Config file `{}` set in env var `{}` does not exist",
89 file,
90 self.env_var.unwrap()
91 )));
92 }
93 Toml::file(file)
94 } else {
95 Toml::file(&self.default)
96 }
97 .nested()
98 .data()
99 }
100}
101
102impl Provider for TomlFileProvider {
103 fn metadata(&self) -> Metadata {
104 if self.is_missing() {
105 Metadata::named("TOML file provider")
106 } else {
107 Toml::file(self.file()).nested().metadata()
108 }
109 }
110
111 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
112 if let Some(cache) = self.cache.as_ref() {
113 cache.clone()
114 } else {
115 self.read()
116 }
117 }
118}
119
120pub(crate) struct ForcedSnakeCaseData<P>(pub(crate) P);
123
124impl<P: Provider> Provider for ForcedSnakeCaseData<P> {
125 fn metadata(&self) -> Metadata {
126 self.0.metadata()
127 }
128
129 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
130 let mut map = Map::new();
131 for (profile, dict) in self.0.data()? {
132 if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
133 map.insert(profile, dict);
135 continue;
136 }
137 map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect());
138 }
139 Ok(map)
140 }
141}
142
143pub(crate) struct BackwardsCompatTomlProvider<P>(pub(crate) P);
145
146impl<P: Provider> Provider for BackwardsCompatTomlProvider<P> {
147 fn metadata(&self) -> Metadata {
148 self.0.metadata()
149 }
150
151 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
152 let mut map = Map::new();
153 let solc_env = std::env::var("FOUNDRY_SOLC_VERSION")
154 .or_else(|_| std::env::var("DAPP_SOLC_VERSION"))
155 .map(Value::from)
156 .ok();
157 for (profile, mut dict) in self.0.data()? {
158 if let Some(v) = solc_env.clone() {
159 dict.insert("solc".to_string(), v);
161 } else if let Some(v) = dict.remove("solc_version") {
162 if !dict.contains_key("solc") {
164 dict.insert("solc".to_string(), v);
165 }
166 }
167
168 if let Some(v) = dict.remove("odyssey") {
169 dict.insert("odyssey".to_string(), v);
170 }
171 map.insert(profile, dict);
172 }
173 Ok(map)
174 }
175}
176
177pub(crate) struct DappHardhatDirProvider<'a>(pub(crate) &'a Path);
179
180impl Provider for DappHardhatDirProvider<'_> {
181 fn metadata(&self) -> Metadata {
182 Metadata::named("Dapp Hardhat dir compat")
183 }
184
185 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
186 let mut dict = Dict::new();
187 dict.insert(
188 "src".to_string(),
189 ProjectPathsConfig::find_source_dir(self.0)
190 .file_name()
191 .unwrap()
192 .to_string_lossy()
193 .to_string()
194 .into(),
195 );
196 dict.insert(
197 "out".to_string(),
198 ProjectPathsConfig::find_artifacts_dir(self.0)
199 .file_name()
200 .unwrap()
201 .to_string_lossy()
202 .to_string()
203 .into(),
204 );
205
206 let mut libs = vec![];
211 let node_modules = self.0.join("node_modules");
212 let lib = self.0.join("lib");
213 if node_modules.exists() {
214 if lib.exists() {
215 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
216 }
217 libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string());
218 } else {
219 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
220 }
221
222 dict.insert("libs".to_string(), libs.into());
223
224 Ok(Map::from([(Config::selected_profile(), dict)]))
225 }
226}
227
228pub(crate) struct DappEnvCompatProvider;
230
231impl Provider for DappEnvCompatProvider {
232 fn metadata(&self) -> Metadata {
233 Metadata::named("Dapp env compat")
234 }
235
236 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
237 use serde::de::Error as _;
238 use std::env;
239
240 let mut dict = Dict::new();
241 if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
242 dict.insert(
243 "block_number".to_string(),
244 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
245 );
246 }
247 if let Ok(val) = env::var("DAPP_TEST_ADDRESS") {
248 dict.insert("sender".to_string(), val.into());
249 }
250 if let Ok(val) = env::var("DAPP_FORK_BLOCK") {
251 dict.insert(
252 "fork_block_number".to_string(),
253 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
254 );
255 } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
256 dict.insert(
257 "fork_block_number".to_string(),
258 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
259 );
260 }
261 if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") {
262 dict.insert(
263 "block_timestamp".to_string(),
264 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
265 );
266 }
267 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") {
268 dict.insert(
269 "optimizer_runs".to_string(),
270 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
271 );
272 }
273 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") {
274 let val = val.parse::<u8>().map_err(figment::Error::custom)?;
276 if val > 1 {
277 return Err(
278 format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into()
279 );
280 }
281 dict.insert("optimizer".to_string(), (val == 1).into());
282 }
283
284 if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) {
286 dict.insert("libraries".to_string(), utils::to_array_value(&val)?);
287 }
288
289 let mut fuzz_dict = Dict::new();
290 if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") {
291 fuzz_dict.insert(
292 "runs".to_string(),
293 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
294 );
295 }
296 dict.insert("fuzz".to_string(), fuzz_dict.into());
297
298 let mut invariant_dict = Dict::new();
299 if let Ok(val) = env::var("DAPP_TEST_DEPTH") {
300 invariant_dict.insert(
301 "depth".to_string(),
302 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
303 );
304 }
305 dict.insert("invariant".to_string(), invariant_dict.into());
306
307 Ok(Map::from([(Config::selected_profile(), dict)]))
308 }
309}
310
311pub(crate) struct RenameProfileProvider<P> {
327 provider: P,
328 from: Profile,
329 to: Profile,
330}
331
332impl<P> RenameProfileProvider<P> {
333 pub(crate) fn new(provider: P, from: impl Into<Profile>, to: impl Into<Profile>) -> Self {
334 Self { provider, from: from.into(), to: to.into() }
335 }
336}
337
338impl<P: Provider> Provider for RenameProfileProvider<P> {
339 fn metadata(&self) -> Metadata {
340 self.provider.metadata()
341 }
342 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
343 let mut data = self.provider.data()?;
344 if let Some(data) = data.remove(&self.from) {
345 return Ok(Map::from([(self.to.clone(), data)]));
346 }
347 Ok(Default::default())
348 }
349 fn profile(&self) -> Option<Profile> {
350 Some(self.to.clone())
351 }
352}
353
354struct UnwrapProfileProvider<P> {
370 provider: P,
371 wrapping_key: Profile,
372 profile: Profile,
373}
374
375impl<P> UnwrapProfileProvider<P> {
376 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
377 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
378 }
379}
380
381impl<P: Provider> Provider for UnwrapProfileProvider<P> {
382 fn metadata(&self) -> Metadata {
383 self.provider.metadata()
384 }
385 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
386 self.provider.data().and_then(|mut data| {
387 if let Some(profiles) = data.remove(&self.wrapping_key) {
388 for (profile_str, profile_val) in profiles {
389 let profile = Profile::new(&profile_str);
390 if profile != self.profile {
391 continue;
392 }
393 match profile_val {
394 Value::Dict(_, dict) => return Ok(profile.collect(dict)),
395 bad_val => {
396 let mut err = Error::from(figment::error::Kind::InvalidType(
397 bad_val.to_actual(),
398 "dict".into(),
399 ));
400 err.metadata = Some(self.provider.metadata());
401 err.profile = Some(self.profile.clone());
402 return Err(err);
403 }
404 }
405 }
406 }
407 Ok(Default::default())
408 })
409 }
410 fn profile(&self) -> Option<Profile> {
411 Some(self.profile.clone())
412 }
413}
414
415pub(crate) struct WrapProfileProvider<P> {
431 provider: P,
432 wrapping_key: Profile,
433 profile: Profile,
434}
435
436impl<P> WrapProfileProvider<P> {
437 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
438 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
439 }
440}
441
442impl<P: Provider> Provider for WrapProfileProvider<P> {
443 fn metadata(&self) -> Metadata {
444 self.provider.metadata()
445 }
446 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
447 if let Some(inner) = self.provider.data()?.remove(&self.profile) {
448 let value = Value::from(inner);
449 let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect();
450 Ok(self.wrapping_key.collect(dict))
451 } else {
452 Ok(Default::default())
453 }
454 }
455 fn profile(&self) -> Option<Profile> {
456 Some(self.profile.clone())
457 }
458}
459
460pub(crate) struct OptionalStrictProfileProvider<P> {
483 provider: P,
484 profiles: Vec<Profile>,
485}
486
487impl<P> OptionalStrictProfileProvider<P> {
488 pub const PROFILE_PROFILE: Profile = Profile::const_new("profile");
489
490 pub fn new(provider: P, profiles: impl IntoIterator<Item = impl Into<Profile>>) -> Self {
491 Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() }
492 }
493}
494
495impl<P: Provider> Provider for OptionalStrictProfileProvider<P> {
496 fn metadata(&self) -> Metadata {
497 self.provider.metadata()
498 }
499 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
500 let mut figment = Figment::from(&self.provider);
501 for profile in &self.profiles {
502 figment = figment.merge(UnwrapProfileProvider::new(
503 &self.provider,
504 Self::PROFILE_PROFILE,
505 profile.clone(),
506 ));
507 }
508 figment.data().map_err(|err| {
509 if let Err(root_err) = self.provider.data() {
514 return root_err;
515 }
516 err
517 })
518 }
519 fn profile(&self) -> Option<Profile> {
520 self.profiles.last().cloned()
521 }
522}
523
524pub struct FallbackProfileProvider<P> {
527 provider: P,
528 profile: Profile,
529 fallback: Profile,
530}
531
532impl<P> FallbackProfileProvider<P> {
533 pub fn new(provider: P, profile: impl Into<Profile>, fallback: impl Into<Profile>) -> Self {
535 Self { provider, profile: profile.into(), fallback: fallback.into() }
536 }
537}
538
539impl<P: Provider> Provider for FallbackProfileProvider<P> {
540 fn metadata(&self) -> Metadata {
541 self.provider.metadata()
542 }
543
544 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
545 let data = self.provider.data()?;
546 if let Some(fallback) = data.get(&self.fallback) {
547 let mut inner = data.get(&self.profile).cloned().unwrap_or_default();
548 for (k, v) in fallback {
549 if !inner.contains_key(k) {
550 inner.insert(k.to_owned(), v.clone());
551 }
552 }
553 Ok(self.profile.collect(inner))
554 } else {
555 Ok(data)
556 }
557 }
558
559 fn profile(&self) -> Option<Profile> {
560 Some(self.profile.clone())
561 }
562}