Skip to main content

forge_lint/sol/med/
tx_origin.rs

1use super::TxOrigin;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    ast::{Expr, ExprKind, IndexKind, Stmt, StmtKind},
8    interface::SpannedOption,
9};
10
11declare_forge_lint!(
12    TX_ORIGIN,
13    Severity::Med,
14    "tx-origin",
15    "`tx.origin` should not be used for authorization"
16);
17
18impl<'ast> EarlyLintPass<'ast> for TxOrigin {
19    fn check_stmt(&mut self, ctx: &LintContext, stmt: &'ast Stmt<'ast>) {
20        match &stmt.kind {
21            StmtKind::If(cond, ..) | StmtKind::DoWhile(_, cond) => {
22                emit_if_contains_tx_origin(ctx, cond);
23            }
24            StmtKind::While(cond, _) => {
25                emit_if_contains_tx_origin(ctx, cond);
26            }
27            StmtKind::For { cond: Some(cond), .. } => {
28                emit_if_contains_tx_origin(ctx, cond);
29            }
30            _ => {}
31        }
32    }
33
34    fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
35        if let ExprKind::Call(callee, args) = &expr.kind
36            && is_require_or_assert_call(callee)
37            && let Some(cond) = args.exprs().next()
38        {
39            emit_if_contains_tx_origin(ctx, cond);
40        }
41    }
42}
43
44fn emit_if_contains_tx_origin(ctx: &LintContext, expr: &Expr<'_>) {
45    if contains_tx_origin(expr) {
46        ctx.emit(&TX_ORIGIN, expr.span);
47    }
48}
49
50fn contains_tx_origin(expr: &Expr<'_>) -> bool {
51    if is_tx_origin(expr) {
52        return true;
53    }
54    match &expr.kind {
55        ExprKind::Unary(_, inner) => contains_tx_origin(inner),
56        ExprKind::Binary(lhs, _, rhs) => contains_tx_origin(lhs) || contains_tx_origin(rhs),
57        ExprKind::Index(base, index) => {
58            contains_tx_origin(base)
59                || match index {
60                    IndexKind::Index(Some(index)) => contains_tx_origin(index),
61                    IndexKind::Range(start, end) => {
62                        start.as_ref().is_some_and(|start| contains_tx_origin(start))
63                            || end.as_ref().is_some_and(|end| contains_tx_origin(end))
64                    }
65                    _ => false,
66                }
67        }
68        ExprKind::Tuple(elems) => elems.iter().any(|elem| {
69            if let SpannedOption::Some(inner) = elem.as_ref() {
70                contains_tx_origin(inner)
71            } else {
72                false
73            }
74        }),
75        ExprKind::Call(callee, args) => {
76            contains_tx_origin(callee) || args.exprs().any(contains_tx_origin)
77        }
78        ExprKind::Ternary(cond, then_expr, else_expr) => {
79            contains_tx_origin(cond)
80                || contains_tx_origin(then_expr)
81                || contains_tx_origin(else_expr)
82        }
83        _ => false,
84    }
85}
86
87fn is_tx_origin(expr: &Expr<'_>) -> bool {
88    matches!(
89        &expr.kind,
90        ExprKind::Member(base, member)
91            if member.as_str() == "origin"
92            && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "tx")
93    )
94}
95
96fn is_require_or_assert_call(callee: &Expr<'_>) -> bool {
97    matches!(
98        &callee.kind,
99        ExprKind::Ident(ident) if matches!(ident.as_str(), "require" | "assert")
100    )
101}