foundry_config/providers/
warnings.rs1use crate::{Config, DEPRECATIONS, Warning};
2use figment::{
3 Error, Figment, Metadata, Profile, Provider,
4 value::{Dict, Map, Value},
5};
6use heck::ToSnakeCase;
7use std::collections::{BTreeMap, BTreeSet};
8
9const COMPILATION_RESTRICTIONS_KEYS: &[&str] = &[
11 "paths",
12 "version",
13 "via_ir",
14 "bytecode_hash",
15 "min_optimizer_runs",
16 "optimizer_runs",
17 "max_optimizer_runs",
18 "min_evm_version",
19 "evm_version",
20 "max_evm_version",
21];
22
23const SETTINGS_OVERRIDES_KEYS: &[&str] =
25 &["name", "via_ir", "evm_version", "optimizer", "optimizer_runs", "bytecode_hash"];
26
27const VYPER_KEYS: &[&str] = &[
31 "optimize",
32 "opt_level",
33 "optLevel",
34 "path",
35 "experimental_codegen",
36 "venom_experimental",
37 "debug",
38 "enable_decimals",
39 "venom",
40];
41
42const DOC_KEYS: &[&str] =
47 &["out", "title", "book", "homepage", "repository", "commit", "path", "ignore"];
48
49const SYMBOLIC_KEYS: &[&str] = &[
53 "enabled",
54 "seed_corpus",
55 "use_fuzz_corpus",
56 "corpus_seed_limit",
57 "use_fuzz_frontiers",
58 "frontier_limit",
59 "frontier_ids",
60 "frontier_pcs",
61 "frontier_selectors",
62 "solver",
63 "solver_command",
64 "solver_portfolio",
65 "timeout",
66 "loop",
67 "depth",
68 "width",
69 "max_depth",
70 "max_paths",
71 "invariant_depth",
72 "exploration_order",
73 "max_solver_queries",
74 "default_dynamic_length",
75 "max_dynamic_length",
76 "array_lengths",
77 "dynamic_lengths",
78 "default_array_lengths",
79 "default_bytes_lengths",
80 "max_calldata_bytes",
81 "symbolic_call_targets",
82 "dump_smt",
83 "storage_layout",
84];
85
86const TRACING_KEYS: &[&str] = &[
90 "verbosity",
91 "labels",
92 "disable_labels",
93 "compact_labels",
94 "trace_depth",
95 "decode_internal",
96 "external_identification_timeout",
97];
98
99const RESERVED_KEYS: &[&str] = &["extends"];
101
102const BACKWARD_COMPATIBLE_KEYS: &[&str] = &[
107 "solc_version",
108 "tempo",
109 "optimism",
110 #[cfg(feature = "monad")]
111 "monad",
112];
113
114const LABELS_KEY: &str = "labels";
115const TRACING_LABELS_KEY: &str = "tracing.labels";
116
117pub struct WarningsProvider<P> {
119 provider: P,
120 profile: Profile,
121 old_warnings: Result<Vec<Warning>, Error>,
122}
123
124impl<P: Provider> WarningsProvider<P> {
125 const WARNINGS_KEY: &'static str = "__warnings";
126
127 pub fn new(
129 provider: P,
130 profile: impl Into<Profile>,
131 old_warnings: Result<Vec<Warning>, Error>,
132 ) -> Self {
133 Self { provider, profile: profile.into(), old_warnings }
134 }
135
136 pub fn for_figment(provider: P, figment: &Figment) -> Self {
138 let old_warnings = {
139 let warnings_res = figment.extract_inner(Self::WARNINGS_KEY);
140 if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) {
141 Ok(vec![])
142 } else {
143 warnings_res
144 }
145 };
146 Self::new(provider, figment.profile().clone(), old_warnings)
147 }
148
149 pub fn collect_warnings(&self) -> Result<Vec<Warning>, Error> {
151 let data = self.provider.data().unwrap_or_default();
152
153 let mut out = self.old_warnings.clone()?;
154
155 out.extend(data.keys().filter(|k| !Config::is_standalone_section(k.as_str())).map(
157 |unknown_section| {
158 let source = self.provider.metadata().source.map(|s| s.to_string());
159 Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
160 },
161 ));
162
163 let deprecated_key_warning = |key| {
165 DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
166 (key == *deprecated_key).then(|| Warning::DeprecatedKey {
167 old: deprecated_key.to_string(),
168 new: new_value.to_string(),
169 })
170 })
171 };
172 let profiles = data
173 .iter()
174 .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
175 .map(|(_, dict)| dict);
176
177 let deprecated_profile_keys = profiles
178 .clone()
179 .flat_map(|dict| {
180 dict.keys().chain(dict.values().filter_map(Value::as_dict).flat_map(BTreeMap::keys))
181 })
182 .collect::<BTreeSet<_>>();
183 out.extend(deprecated_profile_keys.into_iter().filter_map(deprecated_key_warning));
184 self.collect_deprecated_label_warnings(&data, profiles.clone(), &mut out);
185
186 if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data()
188 && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE)
189 {
190 let allowed_keys: BTreeSet<String> = default_dict.keys().cloned().collect();
191 for profile_map in profiles.clone() {
192 for (profile, value) in profile_map {
193 let Some(profile_dict) = value.as_dict() else {
194 continue;
195 };
196
197 let source = self
198 .provider
199 .metadata()
200 .source
201 .map(|s| s.to_string())
202 .unwrap_or(Config::FILE_NAME.to_string());
203 for key in profile_dict.keys() {
204 let is_not_deprecated = !Self::is_deprecated_profile_key(key);
205 let is_not_allowed = !allowed_keys.contains(key)
206 && !allowed_keys.contains(&key.to_snake_case());
207 let is_not_reserved =
208 !RESERVED_KEYS.contains(&key.as_str()) && key != Self::WARNINGS_KEY;
209 let is_not_backward_compatible =
210 !BACKWARD_COMPATIBLE_KEYS.contains(&key.as_str());
211
212 if is_not_deprecated
213 && is_not_allowed
214 && is_not_reserved
215 && is_not_backward_compatible
216 {
217 out.push(Warning::UnknownKey {
218 key: key.clone(),
219 profile: profile.clone(),
220 source: source.clone(),
221 });
222 }
223 }
224
225 self.collect_nested_section_warnings(
227 profile_dict,
228 default_dict,
229 &source,
230 &mut out,
231 );
232 }
233 }
234
235 self.collect_standalone_section_warnings(&data, default_dict, &mut out);
237 }
238
239 Ok(out)
240 }
241
242 fn collect_standalone_section_warnings(
244 &self,
245 data: &Map<Profile, Dict>,
246 default_dict: &Dict,
247 out: &mut Vec<Warning>,
248 ) {
249 let source = self
250 .provider
251 .metadata()
252 .source
253 .map(|s| s.to_string())
254 .unwrap_or(Config::FILE_NAME.to_string());
255
256 for section_name in Config::STANDALONE_SECTIONS {
257 let section_profile = Profile::new(section_name);
259 let Some(section_dict) = data.get(§ion_profile) else {
260 continue;
261 };
262
263 let allowed_keys: BTreeSet<String> = if *section_name == "vyper" {
267 VYPER_KEYS.iter().map(|s| s.to_string()).collect()
268 } else if *section_name == "doc" {
269 DOC_KEYS.iter().map(|s| s.to_string()).collect()
270 } else if *section_name == "tracing" {
271 TRACING_KEYS.iter().map(|s| s.to_string()).collect()
272 } else if *section_name == "symbolic" {
273 SYMBOLIC_KEYS.iter().map(|s| s.to_string()).collect()
274 } else {
275 let Some(default_section_value) = default_dict.get(*section_name) else {
276 continue;
277 };
278 let Some(default_section_dict) = default_section_value.as_dict() else {
279 continue;
280 };
281 default_section_dict.keys().cloned().collect()
282 };
283
284 for key in section_dict.keys() {
285 let is_not_allowed =
286 !allowed_keys.contains(key) && !allowed_keys.contains(&key.to_snake_case());
287 if is_not_allowed {
288 out.push(Warning::UnknownSectionKey {
289 key: key.clone(),
290 section: section_name.to_string(),
291 source: source.clone(),
292 });
293 }
294 }
295 }
296 }
297
298 fn collect_nested_section_warnings(
301 &self,
302 profile_dict: &Dict,
303 default_dict: &Dict,
304 source: &str,
305 out: &mut Vec<Warning>,
306 ) {
307 for (key, value) in profile_dict {
309 let Some(nested_dict) = value.as_dict() else {
310 if let Some(arr) = value.as_array() {
312 let allowed_keys = Self::get_array_item_allowed_keys(key);
314
315 if allowed_keys.is_empty() {
316 continue;
317 }
318
319 for item in arr {
320 let Some(item_dict) = item.as_dict() else {
321 continue;
322 };
323 for item_key in item_dict.keys() {
324 let is_not_allowed = !allowed_keys.contains(item_key)
325 && !allowed_keys.contains(&item_key.to_snake_case());
326 if is_not_allowed {
327 out.push(Warning::UnknownSectionKey {
328 key: item_key.clone(),
329 section: key.clone(),
330 source: source.to_string(),
331 });
332 }
333 }
334 }
335 }
336 continue;
337 };
338
339 let allowed_keys: BTreeSet<String> = if key == "vyper" {
343 VYPER_KEYS.iter().map(|s| s.to_string()).collect()
344 } else if key == "doc" {
345 DOC_KEYS.iter().map(|s| s.to_string()).collect()
346 } else if key == "symbolic" {
347 SYMBOLIC_KEYS.iter().map(|s| s.to_string()).collect()
348 } else if key == "tracing" {
349 TRACING_KEYS.iter().map(|s| s.to_string()).collect()
350 } else {
351 let Some(default_value) = default_dict.get(key) else {
352 continue;
353 };
354 let Some(default_nested_dict) = default_value.as_dict() else {
355 continue;
356 };
357 default_nested_dict.keys().cloned().collect()
358 };
359
360 for nested_key in nested_dict.keys() {
361 let is_not_allowed = !allowed_keys.contains(nested_key)
362 && !allowed_keys.contains(&nested_key.to_snake_case());
363 if is_not_allowed {
364 out.push(Warning::UnknownSectionKey {
365 key: nested_key.clone(),
366 section: key.clone(),
367 source: source.to_string(),
368 });
369 }
370 }
371 }
372 }
373
374 fn get_array_item_allowed_keys(section_name: &str) -> BTreeSet<String> {
376 match section_name {
377 "compilation_restrictions" => {
378 COMPILATION_RESTRICTIONS_KEYS.iter().map(|s| s.to_string()).collect()
379 }
380 "additional_compiler_profiles" => {
381 SETTINGS_OVERRIDES_KEYS.iter().map(|s| s.to_string()).collect()
382 }
383 _ => BTreeSet::new(),
384 }
385 }
386
387 fn collect_deprecated_label_warnings<'a>(
388 &self,
389 data: &Map<Profile, Dict>,
390 profiles: impl Iterator<Item = &'a Dict>,
391 out: &mut Vec<Warning>,
392 ) {
393 if data.contains_key(&Profile::new(LABELS_KEY)) {
394 out.push(Self::deprecated_label_warning("[labels]", "[tracing.labels]"));
395 }
396
397 if profiles
398 .flat_map(BTreeMap::values)
399 .filter_map(Value::as_dict)
400 .any(|dict| dict.contains_key(LABELS_KEY))
401 {
402 out.push(Self::deprecated_label_warning(LABELS_KEY, TRACING_LABELS_KEY));
403 }
404
405 if let Some(dict) = data.get(&self.profile)
406 && dict.contains_key(LABELS_KEY)
407 {
408 out.push(Self::deprecated_label_warning(LABELS_KEY, TRACING_LABELS_KEY));
409 }
410 }
411
412 fn deprecated_label_warning(old: &str, new: &str) -> Warning {
413 Warning::DeprecatedKey { old: old.to_string(), new: new.to_string() }
414 }
415
416 fn is_deprecated_profile_key(key: &str) -> bool {
417 key == LABELS_KEY || DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key)
418 }
419}
420
421impl<P: Provider> Provider for WarningsProvider<P> {
422 fn metadata(&self) -> Metadata {
423 if let Some(source) = self.provider.metadata().source {
424 Metadata::from("Warnings", source)
425 } else {
426 Metadata::named("Warnings")
427 }
428 }
429
430 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
431 let warnings = self.collect_warnings()?;
432 Ok(Map::from([(
433 self.profile.clone(),
434 Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
435 )]))
436 }
437
438 fn profile(&self) -> Option<Profile> {
439 Some(self.profile.clone())
440 }
441}