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", "labels", "disable_labels", "compact_labels", "trace_depth", "decode_internal"];
91
92const RESERVED_KEYS: &[&str] = &["extends"];
94
95const BACKWARD_COMPATIBLE_KEYS: &[&str] = &["solc_version", "tempo", "optimism"];
100
101const LABELS_KEY: &str = "labels";
102const TRACING_LABELS_KEY: &str = "tracing.labels";
103
104pub struct WarningsProvider<P> {
106 provider: P,
107 profile: Profile,
108 old_warnings: Result<Vec<Warning>, Error>,
109}
110
111impl<P: Provider> WarningsProvider<P> {
112 const WARNINGS_KEY: &'static str = "__warnings";
113
114 pub fn new(
116 provider: P,
117 profile: impl Into<Profile>,
118 old_warnings: Result<Vec<Warning>, Error>,
119 ) -> Self {
120 Self { provider, profile: profile.into(), old_warnings }
121 }
122
123 pub fn for_figment(provider: P, figment: &Figment) -> Self {
125 let old_warnings = {
126 let warnings_res = figment.extract_inner(Self::WARNINGS_KEY);
127 if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) {
128 Ok(vec![])
129 } else {
130 warnings_res
131 }
132 };
133 Self::new(provider, figment.profile().clone(), old_warnings)
134 }
135
136 pub fn collect_warnings(&self) -> Result<Vec<Warning>, Error> {
138 let data = self.provider.data().unwrap_or_default();
139
140 let mut out = self.old_warnings.clone()?;
141
142 out.extend(data.keys().filter(|k| !Config::is_standalone_section(k.as_str())).map(
144 |unknown_section| {
145 let source = self.provider.metadata().source.map(|s| s.to_string());
146 Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
147 },
148 ));
149
150 let deprecated_key_warning = |key| {
152 DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
153 (key == *deprecated_key).then(|| Warning::DeprecatedKey {
154 old: deprecated_key.to_string(),
155 new: new_value.to_string(),
156 })
157 })
158 };
159 let profiles = data
160 .iter()
161 .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
162 .map(|(_, dict)| dict);
163
164 let deprecated_profile_keys = profiles
165 .clone()
166 .flat_map(|dict| {
167 dict.keys().chain(dict.values().filter_map(Value::as_dict).flat_map(BTreeMap::keys))
168 })
169 .collect::<BTreeSet<_>>();
170 out.extend(deprecated_profile_keys.into_iter().filter_map(deprecated_key_warning));
171 self.collect_deprecated_label_warnings(&data, profiles.clone(), &mut out);
172
173 if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data()
175 && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE)
176 {
177 let allowed_keys: BTreeSet<String> = default_dict.keys().cloned().collect();
178 for profile_map in profiles.clone() {
179 for (profile, value) in profile_map {
180 let Some(profile_dict) = value.as_dict() else {
181 continue;
182 };
183
184 let source = self
185 .provider
186 .metadata()
187 .source
188 .map(|s| s.to_string())
189 .unwrap_or(Config::FILE_NAME.to_string());
190 for key in profile_dict.keys() {
191 let is_not_deprecated = !Self::is_deprecated_profile_key(key);
192 let is_not_allowed = !allowed_keys.contains(key)
193 && !allowed_keys.contains(&key.to_snake_case());
194 let is_not_reserved =
195 !RESERVED_KEYS.contains(&key.as_str()) && key != Self::WARNINGS_KEY;
196 let is_not_backward_compatible =
197 !BACKWARD_COMPATIBLE_KEYS.contains(&key.as_str());
198
199 if is_not_deprecated
200 && is_not_allowed
201 && is_not_reserved
202 && is_not_backward_compatible
203 {
204 out.push(Warning::UnknownKey {
205 key: key.clone(),
206 profile: profile.clone(),
207 source: source.clone(),
208 });
209 }
210 }
211
212 self.collect_nested_section_warnings(
214 profile_dict,
215 default_dict,
216 &source,
217 &mut out,
218 );
219 }
220 }
221
222 self.collect_standalone_section_warnings(&data, default_dict, &mut out);
224 }
225
226 Ok(out)
227 }
228
229 fn collect_standalone_section_warnings(
231 &self,
232 data: &Map<Profile, Dict>,
233 default_dict: &Dict,
234 out: &mut Vec<Warning>,
235 ) {
236 let source = self
237 .provider
238 .metadata()
239 .source
240 .map(|s| s.to_string())
241 .unwrap_or(Config::FILE_NAME.to_string());
242
243 for section_name in Config::STANDALONE_SECTIONS {
244 let section_profile = Profile::new(section_name);
246 let Some(section_dict) = data.get(§ion_profile) else {
247 continue;
248 };
249
250 let allowed_keys: BTreeSet<String> = if *section_name == "vyper" {
254 VYPER_KEYS.iter().map(|s| s.to_string()).collect()
255 } else if *section_name == "doc" {
256 DOC_KEYS.iter().map(|s| s.to_string()).collect()
257 } else if *section_name == "tracing" {
258 TRACING_KEYS.iter().map(|s| s.to_string()).collect()
259 } else if *section_name == "symbolic" {
260 SYMBOLIC_KEYS.iter().map(|s| s.to_string()).collect()
261 } else {
262 let Some(default_section_value) = default_dict.get(*section_name) else {
263 continue;
264 };
265 let Some(default_section_dict) = default_section_value.as_dict() else {
266 continue;
267 };
268 default_section_dict.keys().cloned().collect()
269 };
270
271 for key in section_dict.keys() {
272 let is_not_allowed =
273 !allowed_keys.contains(key) && !allowed_keys.contains(&key.to_snake_case());
274 if is_not_allowed {
275 out.push(Warning::UnknownSectionKey {
276 key: key.clone(),
277 section: section_name.to_string(),
278 source: source.clone(),
279 });
280 }
281 }
282 }
283 }
284
285 fn collect_nested_section_warnings(
288 &self,
289 profile_dict: &Dict,
290 default_dict: &Dict,
291 source: &str,
292 out: &mut Vec<Warning>,
293 ) {
294 for (key, value) in profile_dict {
296 let Some(nested_dict) = value.as_dict() else {
297 if let Some(arr) = value.as_array() {
299 let allowed_keys = Self::get_array_item_allowed_keys(key);
301
302 if allowed_keys.is_empty() {
303 continue;
304 }
305
306 for item in arr {
307 let Some(item_dict) = item.as_dict() else {
308 continue;
309 };
310 for item_key in item_dict.keys() {
311 let is_not_allowed = !allowed_keys.contains(item_key)
312 && !allowed_keys.contains(&item_key.to_snake_case());
313 if is_not_allowed {
314 out.push(Warning::UnknownSectionKey {
315 key: item_key.clone(),
316 section: key.clone(),
317 source: source.to_string(),
318 });
319 }
320 }
321 }
322 }
323 continue;
324 };
325
326 let allowed_keys: BTreeSet<String> = if key == "vyper" {
330 VYPER_KEYS.iter().map(|s| s.to_string()).collect()
331 } else if key == "doc" {
332 DOC_KEYS.iter().map(|s| s.to_string()).collect()
333 } else if key == "symbolic" {
334 SYMBOLIC_KEYS.iter().map(|s| s.to_string()).collect()
335 } else if key == "tracing" {
336 TRACING_KEYS.iter().map(|s| s.to_string()).collect()
337 } else {
338 let Some(default_value) = default_dict.get(key) else {
339 continue;
340 };
341 let Some(default_nested_dict) = default_value.as_dict() else {
342 continue;
343 };
344 default_nested_dict.keys().cloned().collect()
345 };
346
347 for nested_key in nested_dict.keys() {
348 let is_not_allowed = !allowed_keys.contains(nested_key)
349 && !allowed_keys.contains(&nested_key.to_snake_case());
350 if is_not_allowed {
351 out.push(Warning::UnknownSectionKey {
352 key: nested_key.clone(),
353 section: key.clone(),
354 source: source.to_string(),
355 });
356 }
357 }
358 }
359 }
360
361 fn get_array_item_allowed_keys(section_name: &str) -> BTreeSet<String> {
363 match section_name {
364 "compilation_restrictions" => {
365 COMPILATION_RESTRICTIONS_KEYS.iter().map(|s| s.to_string()).collect()
366 }
367 "additional_compiler_profiles" => {
368 SETTINGS_OVERRIDES_KEYS.iter().map(|s| s.to_string()).collect()
369 }
370 _ => BTreeSet::new(),
371 }
372 }
373
374 fn collect_deprecated_label_warnings<'a>(
375 &self,
376 data: &Map<Profile, Dict>,
377 profiles: impl Iterator<Item = &'a Dict>,
378 out: &mut Vec<Warning>,
379 ) {
380 if data.contains_key(&Profile::new(LABELS_KEY)) {
381 out.push(Self::deprecated_label_warning("[labels]", "[tracing.labels]"));
382 }
383
384 if profiles
385 .flat_map(BTreeMap::values)
386 .filter_map(Value::as_dict)
387 .any(|dict| dict.contains_key(LABELS_KEY))
388 {
389 out.push(Self::deprecated_label_warning(LABELS_KEY, TRACING_LABELS_KEY));
390 }
391
392 if let Some(dict) = data.get(&self.profile)
393 && dict.contains_key(LABELS_KEY)
394 {
395 out.push(Self::deprecated_label_warning(LABELS_KEY, TRACING_LABELS_KEY));
396 }
397 }
398
399 fn deprecated_label_warning(old: &str, new: &str) -> Warning {
400 Warning::DeprecatedKey { old: old.to_string(), new: new.to_string() }
401 }
402
403 fn is_deprecated_profile_key(key: &str) -> bool {
404 key == LABELS_KEY || DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key)
405 }
406}
407
408impl<P: Provider> Provider for WarningsProvider<P> {
409 fn metadata(&self) -> Metadata {
410 if let Some(source) = self.provider.metadata().source {
411 Metadata::from("Warnings", source)
412 } else {
413 Metadata::named("Warnings")
414 }
415 }
416
417 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
418 let warnings = self.collect_warnings()?;
419 Ok(Map::from([(
420 self.profile.clone(),
421 Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
422 )]))
423 }
424
425 fn profile(&self) -> Option<Profile> {
426 Some(self.profile.clone())
427 }
428}