forge_lint/sol/med/
incorrect_erc721_interface.rs1use super::IncorrectERC721Interface;
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_ERC721_INTERFACE,
10 Severity::Med,
11 "incorrect-erc721-interface",
12 "incorrect ERC721 function interface"
13);
14
15impl<'hir> LateLintPass<'hir> for IncorrectERC721Interface {
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_erc721 = contract.linearized_bases.iter().any(|base_id| {
24 let name = hir.contract(*base_id).name.as_str();
25 name == "ERC721" || name == "IERC721"
26 });
27
28 if !is_erc721 {
29 return;
30 }
31
32 for item_id in contract.items {
34 let Some(fid) = item_id.as_function() else { continue };
35 let func = hir.function(fid);
36
37 if !func.kind.is_function() {
38 continue;
39 }
40
41 let Some(name) = func.name else { continue };
42
43 if has_incorrect_erc721_signature(hir, name.as_str(), func.parameters, func.returns) {
44 ctx.emit(&INCORRECT_ERC721_INTERFACE, func.span);
45 }
46 }
47 }
48}
49
50fn has_incorrect_erc721_signature(
55 hir: &hir::Hir<'_>,
56 name: &str,
57 parameters: &[hir::VariableId],
58 returns: &[hir::VariableId],
59) -> bool {
60 let sig_match = |vars: &[hir::VariableId], expected: &[&str]| -> bool {
61 vars.len() == expected.len()
62 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(hir, id, ty))
63 };
64 let params_match = sig_match;
65 let returns_match = sig_match;
66
67 match name {
68 "balanceOf" if params_match(parameters, &["address"]) => {
70 !returns_match(returns, &["uint256"])
71 }
72 "ownerOf" if params_match(parameters, &["uint256"]) => {
74 !returns_match(returns, &["address"])
75 }
76 "safeTransferFrom"
78 if params_match(parameters, &["address", "address", "uint256", "bytes"]) =>
79 {
80 !returns_match(returns, &[])
81 }
82 "safeTransferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
84 !returns_match(returns, &[])
85 }
86 "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
88 !returns_match(returns, &[])
89 }
90 "approve" if params_match(parameters, &["address", "uint256"]) => {
92 !returns_match(returns, &[])
93 }
94 "setApprovalForAll" if params_match(parameters, &["address", "bool"]) => {
96 !returns_match(returns, &[])
97 }
98 "getApproved" if params_match(parameters, &["uint256"]) => {
100 !returns_match(returns, &["address"])
101 }
102 "isApprovedForAll" if params_match(parameters, &["address", "address"]) => {
104 !returns_match(returns, &["bool"])
105 }
106 "supportsInterface" if params_match(parameters, &["bytes4"]) => {
108 !returns_match(returns, &["bool"])
109 }
110 _ => false,
111 }
112}