Skip to main content

forge_lint/sol/low/
block_timestamp.rs

1use super::BlockTimestamp;
2use crate::{
3    linter::{EarlyLintPass, LintContext},
4    sol::{Severity, SolLint},
5};
6use solar::ast::{BinOp, BinOpKind, Expr, ExprKind};
7
8declare_forge_lint!(
9    BLOCK_TIMESTAMP,
10    Severity::Low,
11    "block-timestamp",
12    "usage of `block.timestamp` in a comparison may be manipulated by validators"
13);
14
15impl<'ast> EarlyLintPass<'ast> for BlockTimestamp {
16    fn check_expr(&mut self, ctx: &LintContext, expr: &'ast Expr<'ast>) {
17        if let ExprKind::Binary(lhs, BinOp { kind, .. }, rhs) = &expr.kind
18            && is_cmp(*kind)
19            && (contains_block_timestamp(lhs) || contains_block_timestamp(rhs))
20        {
21            ctx.emit(&BLOCK_TIMESTAMP, expr.span);
22        }
23    }
24}
25
26const fn is_cmp(kind: BinOpKind) -> bool {
27    matches!(
28        kind,
29        BinOpKind::Lt
30            | BinOpKind::Le
31            | BinOpKind::Gt
32            | BinOpKind::Ge
33            | BinOpKind::Eq
34            | BinOpKind::Ne
35    )
36}
37
38/// Returns `true` if `expr` is `block.timestamp`.
39fn is_block_timestamp(expr: &Expr<'_>) -> bool {
40    matches!(
41        &expr.kind,
42        ExprKind::Member(base, member)
43            if member.as_str() == "timestamp"
44            && matches!(&base.kind, ExprKind::Ident(ident) if ident.as_str() == "block")
45    )
46}
47
48/// Recursively checks if an expression tree contains `block.timestamp`.
49fn contains_block_timestamp(expr: &Expr<'_>) -> bool {
50    if is_block_timestamp(expr) {
51        return true;
52    }
53    match &expr.kind {
54        ExprKind::Unary(_, inner) => contains_block_timestamp(inner),
55        ExprKind::Binary(lhs, _, rhs) => {
56            contains_block_timestamp(lhs) || contains_block_timestamp(rhs)
57        }
58        ExprKind::Tuple(elems) => elems.iter().any(|e| {
59            if let solar::interface::SpannedOption::Some(inner) = e.as_ref() {
60                contains_block_timestamp(inner)
61            } else {
62                false
63            }
64        }),
65        ExprKind::Call(callee, args) => {
66            contains_block_timestamp(callee) || args.exprs().any(|e| contains_block_timestamp(e))
67        }
68        _ => false,
69    }
70}