forge_lint/sol/med/
incorrect_erc721_interface.rs1use super::IncorrectERC721Interface;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, analysis::is_elementary},
5};
6use solar::sema::{Gcx, hir};
7
8declare_forge_lint!(
9 INCORRECT_ERC721_INTERFACE,
10 Severity::Med,
11 "incorrect-erc721-interface",
12 "incorrect ERC721 function interface"
13);
14
15const ERC721_FUNCTIONS: &[(&str, &[&str], &[&str])] = &[
17 ("balanceOf", &["address"], &["uint256"]),
18 ("ownerOf", &["uint256"], &["address"]),
19 ("safeTransferFrom", &["address", "address", "uint256", "bytes"], &[]),
20 ("safeTransferFrom", &["address", "address", "uint256"], &[]),
21 ("transferFrom", &["address", "address", "uint256"], &[]),
22 ("approve", &["address", "uint256"], &[]),
23 ("setApprovalForAll", &["address", "bool"], &[]),
24 ("getApproved", &["uint256"], &["address"]),
25 ("isApprovedForAll", &["address", "address"], &["bool"]),
26 ("supportsInterface", &["bytes4"], &["bool"]),
27];
28
29impl<'gcx> LateLintPass<'gcx> for IncorrectERC721Interface {
30 fn check_contract(
31 &mut self,
32 ctx: &LintContext,
33 gcx: Gcx<'gcx>,
34 contract: &'gcx hir::Contract<'gcx>,
35 ) {
36 if !contract
37 .linearized_bases
38 .iter()
39 .any(|base| matches!(gcx.hir.contract(*base).name.as_str(), "ERC721" | "IERC721"))
40 {
41 return;
42 }
43 let matches = |vars: &[hir::VariableId], expected: &[&str]| {
44 vars.len() == expected.len()
45 && vars.iter().zip(expected).all(|(&id, &ty)| is_elementary(&gcx.hir, id, ty))
46 };
47 let functions = contract.items.iter().filter_map(|id| id.as_function());
48 for func in functions.map(|id| gcx.hir.function(id)) {
49 let Some(name) = func.name.filter(|_| func.kind.is_function()) else { continue };
50 if ERC721_FUNCTIONS.iter().any(|(n, params, returns)| {
51 *n == name.as_str()
52 && matches(func.parameters, params)
53 && !matches(func.returns, returns)
54 }) {
55 ctx.emit(&INCORRECT_ERC721_INTERFACE, func.span);
56 }
57 }
58 }
59}