foundry_config/providers/
ext.rs1use crate::{Config, utils};
2use figment::{
3 Error, Figment, Metadata, Profile, Provider,
4 providers::{Env, Format, Toml},
5 value::{Dict, Map, Value},
6};
7use foundry_compilers::ProjectPathsConfig;
8use heck::ToSnakeCase;
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() { cache.clone() } else { self.read() }
113 }
114}
115
116pub(crate) struct ForcedSnakeCaseData<P>(pub(crate) P);
119
120impl<P: Provider> Provider for ForcedSnakeCaseData<P> {
121 fn metadata(&self) -> Metadata {
122 self.0.metadata()
123 }
124
125 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
126 let mut map = Map::new();
127 for (profile, dict) in self.0.data()? {
128 if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) {
129 map.insert(profile, dict);
131 continue;
132 }
133 map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect());
134 }
135 Ok(map)
136 }
137}
138
139pub(crate) struct BackwardsCompatTomlProvider<P>(pub(crate) P);
141
142impl<P: Provider> Provider for BackwardsCompatTomlProvider<P> {
143 fn metadata(&self) -> Metadata {
144 self.0.metadata()
145 }
146
147 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
148 let mut map = Map::new();
149 let solc_env = std::env::var("FOUNDRY_SOLC_VERSION")
150 .or_else(|_| std::env::var("DAPP_SOLC_VERSION"))
151 .map(Value::from)
152 .ok();
153 for (profile, mut dict) in self.0.data()? {
154 if let Some(v) = solc_env.clone() {
155 dict.insert("solc".to_string(), v);
157 } else if let Some(v) = dict.remove("solc_version") {
158 if !dict.contains_key("solc") {
160 dict.insert("solc".to_string(), v);
161 }
162 }
163
164 if let Some(v) = dict.remove("odyssey") {
165 dict.insert("odyssey".to_string(), v);
166 }
167 map.insert(profile, dict);
168 }
169 Ok(map)
170 }
171}
172
173pub(crate) struct DappHardhatDirProvider<'a>(pub(crate) &'a Path);
175
176impl Provider for DappHardhatDirProvider<'_> {
177 fn metadata(&self) -> Metadata {
178 Metadata::named("Dapp Hardhat dir compat")
179 }
180
181 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
182 let mut dict = Dict::new();
183 dict.insert(
184 "src".to_string(),
185 ProjectPathsConfig::find_source_dir(self.0)
186 .file_name()
187 .unwrap()
188 .to_string_lossy()
189 .to_string()
190 .into(),
191 );
192 dict.insert(
193 "out".to_string(),
194 ProjectPathsConfig::find_artifacts_dir(self.0)
195 .file_name()
196 .unwrap()
197 .to_string_lossy()
198 .to_string()
199 .into(),
200 );
201
202 let mut libs = vec![];
207 let node_modules = self.0.join("node_modules");
208 let lib = self.0.join("lib");
209 if node_modules.exists() {
210 if lib.exists() {
211 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
212 }
213 libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string());
214 } else {
215 libs.push(lib.file_name().unwrap().to_string_lossy().to_string());
216 }
217
218 dict.insert("libs".to_string(), libs.into());
219
220 Ok(Map::from([(Config::selected_profile(), dict)]))
221 }
222}
223
224pub(crate) struct DappEnvCompatProvider;
226
227impl Provider for DappEnvCompatProvider {
228 fn metadata(&self) -> Metadata {
229 Metadata::named("Dapp env compat")
230 }
231
232 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
233 use serde::de::Error as _;
234 use std::env;
235
236 let mut dict = Dict::new();
237 if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
238 dict.insert(
239 "block_number".to_string(),
240 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
241 );
242 }
243 if let Ok(val) = env::var("DAPP_TEST_ADDRESS") {
244 dict.insert("sender".to_string(), val.into());
245 }
246 if let Ok(val) = env::var("DAPP_FORK_BLOCK") {
247 dict.insert(
248 "fork_block_number".to_string(),
249 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
250 );
251 } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") {
252 dict.insert(
253 "fork_block_number".to_string(),
254 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
255 );
256 }
257 if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") {
258 dict.insert(
259 "block_timestamp".to_string(),
260 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
261 );
262 }
263 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") {
264 dict.insert(
265 "optimizer_runs".to_string(),
266 val.parse::<u64>().map_err(figment::Error::custom)?.into(),
267 );
268 }
269 if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") {
270 let val = val.parse::<u8>().map_err(figment::Error::custom)?;
272 if val > 1 {
273 return Err(
274 format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into()
275 );
276 }
277 dict.insert("optimizer".to_string(), (val == 1).into());
278 }
279
280 if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) {
282 dict.insert("libraries".to_string(), utils::to_array_value(&val)?);
283 }
284
285 let mut fuzz_dict = Dict::new();
286 if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") {
287 fuzz_dict.insert(
288 "runs".to_string(),
289 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
290 );
291 }
292 dict.insert("fuzz".to_string(), fuzz_dict.into());
293
294 let mut invariant_dict = Dict::new();
295 if let Ok(val) = env::var("DAPP_TEST_DEPTH") {
296 invariant_dict.insert(
297 "depth".to_string(),
298 val.parse::<u32>().map_err(figment::Error::custom)?.into(),
299 );
300 }
301 dict.insert("invariant".to_string(), invariant_dict.into());
302
303 Ok(Map::from([(Config::selected_profile(), dict)]))
304 }
305}
306
307pub(crate) struct RenameProfileProvider<P> {
323 provider: P,
324 from: Profile,
325 to: Profile,
326}
327
328impl<P> RenameProfileProvider<P> {
329 pub(crate) fn new(provider: P, from: impl Into<Profile>, to: impl Into<Profile>) -> Self {
330 Self { provider, from: from.into(), to: to.into() }
331 }
332}
333
334impl<P: Provider> Provider for RenameProfileProvider<P> {
335 fn metadata(&self) -> Metadata {
336 self.provider.metadata()
337 }
338 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
339 let mut data = self.provider.data()?;
340 if let Some(data) = data.remove(&self.from) {
341 return Ok(Map::from([(self.to.clone(), data)]));
342 }
343 Ok(Default::default())
344 }
345 fn profile(&self) -> Option<Profile> {
346 Some(self.to.clone())
347 }
348}
349
350struct UnwrapProfileProvider<P> {
366 provider: P,
367 wrapping_key: Profile,
368 profile: Profile,
369}
370
371impl<P> UnwrapProfileProvider<P> {
372 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
373 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
374 }
375}
376
377impl<P: Provider> Provider for UnwrapProfileProvider<P> {
378 fn metadata(&self) -> Metadata {
379 self.provider.metadata()
380 }
381 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
382 self.provider.data().and_then(|mut data| {
383 if let Some(profiles) = data.remove(&self.wrapping_key) {
384 for (profile_str, profile_val) in profiles {
385 let profile = Profile::new(&profile_str);
386 if profile != self.profile {
387 continue;
388 }
389 match profile_val {
390 Value::Dict(_, dict) => return Ok(profile.collect(dict)),
391 bad_val => {
392 let mut err = Error::from(figment::error::Kind::InvalidType(
393 bad_val.to_actual(),
394 "dict".into(),
395 ));
396 err.metadata = Some(self.provider.metadata());
397 err.profile = Some(self.profile.clone());
398 return Err(err);
399 }
400 }
401 }
402 }
403 Ok(Default::default())
404 })
405 }
406 fn profile(&self) -> Option<Profile> {
407 Some(self.profile.clone())
408 }
409}
410
411pub(crate) struct WrapProfileProvider<P> {
427 provider: P,
428 wrapping_key: Profile,
429 profile: Profile,
430}
431
432impl<P> WrapProfileProvider<P> {
433 pub fn new(provider: P, wrapping_key: impl Into<Profile>, profile: impl Into<Profile>) -> Self {
434 Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() }
435 }
436}
437
438impl<P: Provider> Provider for WrapProfileProvider<P> {
439 fn metadata(&self) -> Metadata {
440 self.provider.metadata()
441 }
442 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
443 if let Some(inner) = self.provider.data()?.remove(&self.profile) {
444 let value = Value::from(inner);
445 let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect();
446 Ok(self.wrapping_key.collect(dict))
447 } else {
448 Ok(Default::default())
449 }
450 }
451 fn profile(&self) -> Option<Profile> {
452 Some(self.profile.clone())
453 }
454}
455
456pub(crate) struct OptionalStrictProfileProvider<P> {
479 provider: P,
480 profiles: Vec<Profile>,
481}
482
483impl<P> OptionalStrictProfileProvider<P> {
484 pub const PROFILE_PROFILE: Profile = Profile::const_new("profile");
485
486 pub fn new(provider: P, profiles: impl IntoIterator<Item = impl Into<Profile>>) -> Self {
487 Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() }
488 }
489}
490
491impl<P: Provider> Provider for OptionalStrictProfileProvider<P> {
492 fn metadata(&self) -> Metadata {
493 self.provider.metadata()
494 }
495 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
496 let mut figment = Figment::from(&self.provider);
497 for profile in &self.profiles {
498 figment = figment.merge(UnwrapProfileProvider::new(
499 &self.provider,
500 Self::PROFILE_PROFILE,
501 profile.clone(),
502 ));
503 }
504 figment.data().map_err(|err| {
505 if let Err(root_err) = self.provider.data() {
510 return root_err;
511 }
512 err
513 })
514 }
515 fn profile(&self) -> Option<Profile> {
516 self.profiles.last().cloned()
517 }
518}
519
520pub struct FallbackProfileProvider<P> {
523 provider: P,
524 profile: Profile,
525 fallback: Profile,
526}
527
528impl<P> FallbackProfileProvider<P> {
529 pub fn new(provider: P, profile: impl Into<Profile>, fallback: impl Into<Profile>) -> Self {
531 Self { provider, profile: profile.into(), fallback: fallback.into() }
532 }
533}
534
535impl<P: Provider> Provider for FallbackProfileProvider<P> {
536 fn metadata(&self) -> Metadata {
537 self.provider.metadata()
538 }
539
540 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
541 let data = self.provider.data()?;
542 if let Some(fallback) = data.get(&self.fallback) {
543 let mut inner = data.get(&self.profile).cloned().unwrap_or_default();
544 for (k, v) in fallback {
545 if !inner.contains_key(k) {
546 inner.insert(k.to_owned(), v.clone());
547 }
548 }
549 Ok(self.profile.collect(inner))
550 } else {
551 Ok(data)
552 }
553 }
554
555 fn profile(&self) -> Option<Profile> {
556 Some(self.profile.clone())
557 }
558}