forge_lint/sol/gas/
keccak.rs

1use super::AsmKeccak256;
2use crate::{
3    linter::{LateLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::{self as ast, Span},
8    interface::kw,
9    sema::hir::{self},
10};
11
12declare_forge_lint!(
13    ASM_KECCAK256,
14    Severity::Gas,
15    "asm-keccak256",
16    "use of inefficient hashing mechanism; consider using inline assembly"
17);
18
19impl<'hir> LateLintPass<'hir> for AsmKeccak256 {
20    fn check_stmt(
21        &mut self,
22        ctx: &LintContext,
23        hir: &'hir hir::Hir<'hir>,
24        stmt: &'hir hir::Stmt<'hir>,
25    ) {
26        let check_expr_and_emit_lint =
27            |expr: &'hir hir::Expr<'hir>, assign: Option<ast::Ident>, is_return: bool| {
28                if let Some(hash_arg) = extract_keccak256_arg(expr) {
29                    self.emit_lint(
30                        ctx,
31                        hir,
32                        stmt.span,
33                        expr,
34                        hash_arg,
35                        AsmContext { _assign: assign, _is_return: is_return },
36                    );
37                }
38            };
39
40        match stmt.kind {
41            hir::StmtKind::DeclSingle(var_id) => {
42                let var = hir.variable(var_id);
43                if let Some(init) = var.initializer {
44                    // Constants should be optimized by the compiler, so no gas savings apply.
45                    if !matches!(var.mutability, Some(hir::VarMut::Constant)) {
46                        check_expr_and_emit_lint(init, var.name, false);
47                    }
48                }
49            }
50            // Expressions that don't (directly) assign to a variable
51            hir::StmtKind::Expr(expr)
52            | hir::StmtKind::Emit(expr)
53            | hir::StmtKind::Revert(expr)
54            | hir::StmtKind::DeclMulti(_, expr)
55            | hir::StmtKind::If(expr, ..) => check_expr_and_emit_lint(expr, None, false),
56            hir::StmtKind::Return(Some(expr)) => check_expr_and_emit_lint(expr, None, true),
57            _ => (),
58        }
59    }
60}
61
62impl AsmKeccak256 {
63    /// Emits lints (when possible with fix suggestions) for inefficient `keccak256` calls.
64    fn emit_lint(
65        &self,
66        ctx: &LintContext,
67        _hir: &hir::Hir<'_>,
68        _stmt_span: Span,
69        call: &hir::Expr<'_>,
70        _hash: &hir::Expr<'_>,
71        _asm_ctx: AsmContext,
72    ) {
73        ctx.emit(&ASM_KECCAK256, call.span);
74    }
75}
76
77/// If the expression is a call to `keccak256` with one argument, returns that argument.
78fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Expr<'hir>> {
79    let hir::ExprKind::Call(
80        callee,
81        hir::CallArgs { kind: hir::CallArgsKind::Unnamed(args), .. },
82        ..,
83    ) = &expr.kind
84    else {
85        return None;
86    };
87
88    let is_keccak = if let hir::ExprKind::Ident([hir::Res::Builtin(builtin)]) = callee.kind {
89        matches!(builtin.name(), kw::Keccak256)
90    } else {
91        return None;
92    };
93
94    if is_keccak && args.len() == 1 { Some(&args[0]) } else { None }
95}
96
97// -- HELPER FUNCTIONS AND STRUCTS ----------------------------------------------------------------
98
99#[derive(Debug, Clone, Copy)]
100struct AsmContext {
101    _assign: Option<ast::Ident>,
102    _is_return: bool,
103}