forge_lint/sol/med/
tx_origin.rs1use 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}