Skip to main content

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