Double Entry Point
Exploit a legacy contract interaction pattern involving multiple contracts and a shared dependency (CryptoVault) to drain funds.
Vulnerable Code
Analyze the Solidity code below to find the vulnerability.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Assume Forta is an external monitoring contract/service interface
interface IForta {
    function setDetectionBot(address detectionBotAddress) external;
    function notify(address user, bytes calldata msgData) external;
    function raiseAlert(address user) external;
}
// Assume CryptoVault holds the underlying tokens
contract CryptoVault {
    address public underlying; // The actual ERC20 token (e.g., DET)
    constructor(address _underlying) { underlying = _underlying; }
    // Simplified: Assume deposit/withdraw logic exists
    function transfer(address to, uint amount) external {
        IERC20(underlying).transfer(to, amount);
    }
     function sweepToken(address token) external {
        // Allows transferring *any* token held by the vault
        require(token != underlying, "Cannot sweep underlying");
        IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this)));
    }
}
// Legacy contract interacting with the vault
contract LegacyToken is IERC20 {
    CryptoVault public vault;
    // Standard ERC20 functions omitted for brevity...
    // Interactions with vault happen internally
    constructor(address _vault) { vault = CryptoVault(_vault); }
}
// The main vulnerable contract
contract DoubleEntryPoint is IERC20 { // Implements ERC20, likely wraps DET
    address public player;
    CryptoVault public vault;
    address public delegatedToken; // Address of LegacyToken
    IForta public forta;
    constructor(address _vault, address _legacyToken, address _forta, address _player) {
        vault = CryptoVault(_vault);
        delegatedToken = _legacyToken;
        forta = IForta(_forta);
        player = _player;
    }
    // Delegate transfer logic (simplified)
    function transfer(address recipient, uint256 amount) public override returns (bool) {
        // Delegate actual transfer to CryptoVault
        vault.transfer(recipient, amount);
        return true;
    }
    // Other standard ERC20 functions (balanceOf, etc.) assumed to exist,
    // likely querying the vault or internal state.
    // Function to allow sweeping tokens *other than underlying*
    function sweepVaultToken(address token) external {
        // This seems redundant if CryptoVault.sweepToken exists? Maybe restricted access?
        vault.sweepToken(token);
    }
    // Function potentially used by Forta bot
    function delegateTransfer(address recipient, uint256 value, address origSender) public {
        // This seems designed to be called by a trusted bot?
        // Requires check that msg.sender is the registered Forta bot.
        // Transfer underlying tokens.
         require(msg.sender == address(forta), "Not Forta"); // Simplified check
         vault.transfer(recipient, value);
    }
}
// --- BOT Contract ---
// Deployed by player, registered with Forta
contract DetectionBot {
    IForta forta;
    address vaultAddress;
    address doubleEntryPointAddress;
    constructor(address _forta, address _vault, address _det) {
        forta = IForta(_forta);
        vaultAddress = _vault;
        doubleEntryPointAddress = _det; // Address of DoubleEntryPoint contract
    }
    function handleTransaction(address user, bytes calldata msgData) external {
        // Logic to detect malicious activity
        // If delegateTransfer is called originating from DoubleEntryPoint contract, raise alert
        (address target, ) = abi.decode(msgData[4:], (address, uint256)); // Decode target of internal call
        bytes4 selector = bytes4(msgData[:4]);
        // If DET token (DoubleEntryPoint) calls delegateTransfer... suspicious!
        // The goal is to alert IF DoubleEntryPoint contract *originates* a call to its own 'delegateTransfer'
        // This check is likely flawed. How can we trigger this?
        if (selector == DoubleEntryPoint.delegateTransfer.selector && target == doubleEntryPointAddress) {
             forta.raiseAlert(user); // Alert the user (player)
        }
    }
}
Submit Explanation
Explain the vulnerability and how to exploit it.
Hints (5)
Just a little peak
Hint 1
Hint 2
Hint 3
Hint 4
Hint 5
Explanation
Discomfort = Learning