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