Skip to main content

forge_lint/sol/med/
incorrect_erc721_interface.rs

1use 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
15/// ERC721 (and ERC165) functions as `(name, parameter types, return types)`.
16const 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}