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