Skip to main content

forge_lint/sol/info/
event_fields.rs

1use super::EventFields;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::ast::{ElementaryType, Item, ItemKind, TypeKind};
7
8declare_forge_lint!(
9    EVENT_FIELDS,
10    Severity::Info,
11    "event-fields",
12    "`address` event parameter is not `indexed`"
13);
14
15impl<'ast> EarlyLintPass<'ast> for EventFields {
16    fn check_item(&mut self, ctx: &LintContext, item: &'ast Item<'ast>) {
17        let ItemKind::Event(event) = &item.kind else { return };
18        if event.parameters.iter().any(|p| p.indexed) {
19            return;
20        }
21        // The EVM allows 3 indexed parameters in a non-anonymous event and 4 in an anonymous one.
22        let slots_available = if event.anonymous { 4 } else { 3 };
23        // The offending `address` parameters, rendered as `name (type)` in declaration order.
24        let names: Vec<String> = event
25            .parameters
26            .iter()
27            .enumerate()
28            .filter_map(|(idx, param)| {
29                let TypeKind::Elementary(ElementaryType::Address(payable)) = &param.ty.kind else {
30                    return None;
31                };
32                let name =
33                    param.name.map_or_else(|| format!("parameter #{}", idx + 1), |n| n.to_string());
34                let ty = if *payable { "address payable" } else { "address" };
35                Some(format!("`{name}` (`{ty}`)"))
36            })
37            .take(slots_available)
38            .collect();
39        if !names.is_empty() {
40            let msg = format!(
41                "event has unindexed fields that may benefit from being indexed: {}",
42                names.join(", ")
43            );
44            ctx.emit_with_msg(&EVENT_FIELDS, event.name.span, msg);
45        }
46    }
47}