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 _gcx: solar::sema::Gcx<'hir>,
20 hir: &'hir hir::Hir<'hir>,
21 contract: &'hir hir::Contract<'hir>,
22 ) {
23 let is_erc721 = contract.linearized_bases.iter().any(|base_id| {
25 let name = hir.contract(*base_id).name.as_str();
26 name == "ERC721" || name == "IERC721"
27 });
28
29 if !is_erc721 {
30 return;
31 }
32
33 for item_id in contract.items {
35 let Some(fid) = item_id.as_function() else { continue };
36 let func = hir.function(fid);
37
38 if !func.kind.is_function() {
39 continue;
40 }
41
42 let Some(name) = func.name else { continue };
43
44 if has_incorrect_erc721_signature(hir, name.as_str(), func.parameters, func.returns) {
45 ctx.emit(&INCORRECT_ERC721_INTERFACE, func.span);
46 }
47 }
48 }
49}
50
51fn has_incorrect_erc721_signature(
56 hir: &hir::Hir<'_>,
57 name: &str,
58 parameters: &[hir::VariableId],
59 returns: &[hir::VariableId],
60) -> bool {
61 let sig_match = |vars: &[hir::VariableId], expected: &[&str]| -> bool {
62 vars.len() == expected.len()
63 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(hir, id, ty))
64 };
65 let params_match = sig_match;
66 let returns_match = sig_match;
67
68 match name {
69 "balanceOf" if params_match(parameters, &["address"]) => {
71 !returns_match(returns, &["uint256"])
72 }
73 "ownerOf" if params_match(parameters, &["uint256"]) => {
75 !returns_match(returns, &["address"])
76 }
77 "safeTransferFrom"
79 if params_match(parameters, &["address", "address", "uint256", "bytes"]) =>
80 {
81 !returns_match(returns, &[])
82 }
83 "safeTransferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
85 !returns_match(returns, &[])
86 }
87 "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
89 !returns_match(returns, &[])
90 }
91 "approve" if params_match(parameters, &["address", "uint256"]) => {
93 !returns_match(returns, &[])
94 }
95 "setApprovalForAll" if params_match(parameters, &["address", "bool"]) => {
97 !returns_match(returns, &[])
98 }
99 "getApproved" if params_match(parameters, &["uint256"]) => {
101 !returns_match(returns, &["address"])
102 }
103 "isApprovedForAll" if params_match(parameters, &["address", "address"]) => {
105 !returns_match(returns, &["bool"])
106 }
107 "supportsInterface" if params_match(parameters, &["bytes4"]) => {
109 !returns_match(returns, &["bool"])
110 }
111 _ => false,
112 }
113}