Skip to main content

forge_lint/sol/med/
incorrect_erc20_interface.rs

1use 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        // Check if the contract is a possible ERC20 by name or inheritance.
24        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        // If this contract implements a function from ERC721, we can assume it is an ERC721 token.
34        // These tokens offer functions which are similar to ERC20, but are not compatible.
35        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        // Check each function in the contract for incorrect ERC20 signatures.
45        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
62/// Checks if a function signature does not match the expected ERC20 specification.
63///
64/// Returns `true` if the function name and parameter types match an ERC20 function but the return
65/// types are incorrect.
66fn 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        // function transfer(address,uint256) external returns (bool)
81        "transfer" if params_match(parameters, &["address", "uint256"]) => {
82            !returns_match(returns, &["bool"])
83        }
84        // function transferFrom(address,address,uint256) external returns (bool)
85        "transferFrom" if params_match(parameters, &["address", "address", "uint256"]) => {
86            !returns_match(returns, &["bool"])
87        }
88        // function approve(address,uint256) external returns (bool)
89        "approve" if params_match(parameters, &["address", "uint256"]) => {
90            !returns_match(returns, &["bool"])
91        }
92        // function allowance(address,address) external view returns (uint256)
93        "allowance" if params_match(parameters, &["address", "address"]) => {
94            !returns_match(returns, &["uint256"])
95        }
96        // function balanceOf(address) external view returns (uint256)
97        "balanceOf" if params_match(parameters, &["address"]) => {
98            !returns_match(returns, &["uint256"])
99        }
100        // function totalSupply() external view returns (uint256)
101        "totalSupply" if params_match(parameters, &[]) => !returns_match(returns, &["uint256"]),
102        _ => false,
103    }
104}