forge_lint/sol/low/
solmate_safe_transfer_lib.rs1use super::SolmateSafeTransferLib;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, analysis::source_in_package},
5};
6use solar::sema::{
7 Gcx,
8 hir::{Expr, ExprKind, FunctionId},
9};
10
11declare_forge_lint!(
12 SOLMATE_SAFE_TRANSFER_LIB,
13 Severity::Low,
14 "solmate-safe-transfer-lib",
15 "the `SafeTransferLib` from Solmate does not check that the token has code, so a transfer to a token-less address succeeds silently"
16);
17
18impl<'gcx> LateLintPass<'gcx> for SolmateSafeTransferLib {
19 fn check_expr(&mut self, ctx: &LintContext, gcx: Gcx<'gcx>, expr: &'gcx Expr<'gcx>) {
20 if matches!(expr.kind, ExprKind::Ident(..) | ExprKind::Member(..))
24 && let Some(function_id) = gcx.resolved_function(expr)
25 && is_unchecked_token_op(gcx, function_id)
26 {
27 ctx.emit(&SOLMATE_SAFE_TRANSFER_LIB, expr.span);
28 }
29 }
30}
31
32fn is_unchecked_token_op(gcx: Gcx<'_>, function_id: FunctionId) -> bool {
41 let function = gcx.hir.function(function_id);
42 let (Some(name), Some(contract_id)) = (function.name, function.contract) else { return false };
43 let contract = gcx.hir.contract(contract_id);
44 matches!(name.as_str(), "safeTransfer" | "safeTransferFrom" | "safeApprove")
45 && contract.kind.is_library()
46 && contract.name.as_str() == "SafeTransferLib"
47 && source_in_package(&gcx.hir, function.source, &["solmate"])
48}