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 _gcx: solar::sema::Gcx<'hir>,
20 hir: &'hir hir::Hir<'hir>,
21 contract: &'hir hir::Contract<'hir>,
22 ) {
23 let is_erc20 = contract.linearized_bases.iter().any(|base_id| {
25 let name = hir.contract(*base_id).name.as_str();
26 name == "ERC20" || name == "IERC20"
27 });
28
29 if !is_erc20 {
30 return;
31 }
32
33 let is_erc721 = contract.linearized_bases.iter().any(|base_id| {
36 let name = hir.contract(*base_id).name.as_str();
37 name == "ERC721" || name == "IERC721"
38 });
39
40 if is_erc721 {
41 return;
42 }
43
44 for item_id in contract.items {
46 let Some(fid) = item_id.as_function() else { continue };
47 let func = hir.function(fid);
48
49 if !func.kind.is_function() {
50 continue;
51 }
52
53 let Some(name) = func.name else { continue };
54
55 if has_incorrect_erc20_signature(hir, name.as_str(), func.parameters, func.returns) {
56 ctx.emit(&INCORRECT_ERC20_INTERFACE, func.span);
57 }
58 }
59 }
60}
61
62fn has_incorrect_erc20_signature(
67 hir: &hir::Hir<'_>,
68 name: &str,
69 parameters: &[hir::VariableId],
70 returns: &[hir::VariableId],
71) -> bool {
72 let sig_match = |vars: &[hir::VariableId], expected: &[&str]| -> bool {
73 vars.len() == expected.len()
74 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(hir, id, ty))
75 };
76 let params_match = sig_match;
77 let returns_match = sig_match;
78
79 match name {
80 "transfer" if params_match(parameters, &["address", "uint256"]) => {
82 !returns_match(returns, &["bool"])
83 }
84 "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
86 !returns_match(returns, &["bool"])
87 }
88 "approve" if params_match(parameters, &["address", "uint256"]) => {
90 !returns_match(returns, &["bool"])
91 }
92 "allowance" if params_match(parameters, &["address", "address"]) => {
94 !returns_match(returns, &["uint256"])
95 }
96 "balanceOf" if params_match(parameters, &["address"]) => {
98 !returns_match(returns, &["uint256"])
99 }
100 "totalSupply" if params_match(parameters, &[]) => !returns_match(returns, &["uint256"]),
102 _ => false,
103 }
104}