forge_lint/sol/med/
incorrect_erc20_interface.rs1use super::IncorrectERC20Interface;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, analysis::interface::is_elementary},
5};
6use solar::sema::hir;
7
8declare_forge_lint!(
9 INCORRECT_ERC20_INTERFACE,
10 Severity::Med,
11 "incorrect-erc20-interface",
12 "incorrect ERC20 function interface"
13);
14
15impl<'hir> LateLintPass<'hir> for IncorrectERC20Interface {
16 fn check_contract(
17 &mut self,
18 ctx: &LintContext,
19 hir: &'hir hir::Hir<'hir>,
20 contract: &'hir hir::Contract<'hir>,
21 ) {
22 let is_erc20 = contract.linearized_bases.iter().any(|base_id| {
24 let name = hir.contract(*base_id).name.as_str();
25 name == "ERC20" || name == "IERC20"
26 });
27
28 if !is_erc20 {
29 return;
30 }
31
32 let is_erc721 = contract.linearized_bases.iter().any(|base_id| {
35 let name = hir.contract(*base_id).name.as_str();
36 name == "ERC721" || name == "IERC721"
37 });
38
39 if is_erc721 {
40 return;
41 }
42
43 for item_id in contract.items {
45 let Some(fid) = item_id.as_function() else { continue };
46 let func = hir.function(fid);
47
48 if !func.kind.is_function() {
49 continue;
50 }
51
52 let Some(name) = func.name else { continue };
53
54 if has_incorrect_erc20_signature(hir, name.as_str(), func.parameters, func.returns) {
55 ctx.emit(&INCORRECT_ERC20_INTERFACE, func.span);
56 }
57 }
58 }
59}
60
61fn has_incorrect_erc20_signature(
66 hir: &hir::Hir<'_>,
67 name: &str,
68 parameters: &[hir::VariableId],
69 returns: &[hir::VariableId],
70) -> bool {
71 let sig_match = |vars: &[hir::VariableId], expected: &[&str]| -> bool {
72 vars.len() == expected.len()
73 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(hir, id, ty))
74 };
75 let params_match = sig_match;
76 let returns_match = sig_match;
77
78 match name {
79 "transfer" if params_match(parameters, &["address", "uint256"]) => {
81 !returns_match(returns, &["bool"])
82 }
83 "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
85 !returns_match(returns, &["bool"])
86 }
87 "approve" if params_match(parameters, &["address", "uint256"]) => {
89 !returns_match(returns, &["bool"])
90 }
91 "allowance" if params_match(parameters, &["address", "address"]) => {
93 !returns_match(returns, &["uint256"])
94 }
95 "balanceOf" if params_match(parameters, &["address"]) => {
97 !returns_match(returns, &["uint256"])
98 }
99 "totalSupply" if params_match(parameters, &[]) => !returns_match(returns, &["uint256"]),
101 _ => false,
102 }
103}