This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0xef34f4fdf03e403e3c94e96539354fb4fe0b79a5ec927eacc63bc04108dbf4200x05641e33fd15baf819729df55500b07b82eb8e89Ethereum0xb292678438245ec863f9fea64affcea887144240EthereumAn unprivileged Ethereum EOA (0x25869347f7993c50410a9b9b9c48f37d79e12a36) deployed a helper contract and, in a single flashLoan-backed transaction, exploited PumpToken’s public removeLiquidityWhenKIncreases() function to drain value from the PumpToken/WETH UniswapV2 liquidity pool, ending with a net ETH profit. The attack borrowed 30,000 WETH from the Balancer Vault, manipulated the PumpToken/WETH reserves via UniswapV2, invoked the vulnerable function to shrink the pair’s PumpToken balance, removed liquidity, swapped PumpToken back to WETH, repaid the flashLoan, and finally converted the residual WETH to ETH for adversary-controlled accounts.
The root cause is a protocol-level bug in PumpToken. The function removeLiquidityWhenKIncreases() is an unauthenticated, publicly callable entrypoint that burns PumpToken from the PumpToken/WETH pair based solely on a reserve-derived product K, without compensating LPs or adjusting LP shares. An attacker who temporarily increases K (for example via flashLoan-powered liquidity provision and trades) can invoke this function to reduce the pair’s PumpToken balance and subsequently extract WETH from the LP when removing liquidity and swapping, leaving LPs with reduced WETH backing.
0x05641e33fd15baf819729df55500b07b82eb8e89) is a custom ERC20 with a bonding-curve mechanism and a special function removeLiquidityWhenKIncreases() that reads a UniswapV2 pair’s reserves and adjusts the pair’s PumpToken balance when the product K increases beyond a threshold.0xb292678438245Ec863F9FEa64AFfcEA887144240 holds PumpToken and WETH9 () liquidity. UniswapV2Router02 at is used for adding/removing liquidity and swapping.0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc20x7a250d5630B4cF539739dF2C5dAcb4c659F2488D0xBA12222222228d8Ba445958a75a0704d566BF2C8) provides a flashLoan interface that allows arbitrary callers to borrow and repay assets such as WETH within a single transaction, enabling large temporary reserve changes without upfront capital.K and prices. Protocol code that reacts to K without robust invariants or access control is exploitable, especially when it directly modifies pool token balances.From an ACT perspective:
0xef34f4fdf03e403e3c94e96539354fb4fe0b79a5ec927eacc63bc04108dbf420 in block 21529888, including the PumpToken/WETH pair, PumpToken, WETH9, Balancer Vault, and adversary accounts. This state is reconstructed from:
{
"chainid": 1,
"txhash": "0xef34f4fdf03e403e3c94e96539354fb4fe0b79a5ec927eacc63bc04108dbf420",
"native_balance_deltas": [
{
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"before_wei": "2964982195304915405537834",
"after_wei": "2964969854947838121232628",
"delta_wei": "-12340357077284305206"
},
{
"address": "0x25869347f7993c50410a9b9b9c48f37d79e12a36",
"before_wei": "2876766120831698440",
"after_wei": "15186961778116003646",
"delta_wei": "12310195657284305206"
},
{
"address": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
"before_wei": "6342501223369044852",
"after_wei": "6362348586610710214",
"delta_wei": "19847363241665362"
}
],
"erc20_balance_deltas": [
{
"token": "0x05641e33fd15baf819729df55500b07b82eb8e89",
"holder": "0xb292678438245ec863f9fea64affcea887144240",
"before": "6099572126618875527765833",
"after": "6086464626951459453849811",
"delta": "-13107499667416073916022",
"contract_name": "PumpToken"
}
]
}
Caption: Native and ERC20 balance diffs for the exploit transaction, showing WETH9 losing 12.340357077284305206 ETH-equivalent while the adversary EOA and 0x4838…5f97 gain 12.310195657284305206 ETH and 0.019847363241665362 ETH respectively, and the PumpToken/WETH pair losing 13,107,499,667,416,073,916,022 PumpToken units.
PumpToken’s removeLiquidityWhenKIncreases() function is deployed as a publicly callable, unauthenticated function that reduces PumpToken._balances[uniswapV2Pair] whenever the PumpToken/WETH pair’s product of reserves K = tokenReserve * wethReserve exceeds 105% of a hard-coded INITIAL_UNISWAP_K. This effectively burns PumpToken from the pair without adjusting LP token shares, allowing an attacker who temporarily inflates K to withdraw disproportionately more WETH when removing liquidity.
The PumpToken contract encodes a target initial AMM configuration via constants such as TOKENS_IN_LP_AFTER_FILL, ETH_TO_FILL, INITIAL_UNISWAP_K, and REAL_LP_INITIAL_SUPPLY. It sets up the PumpToken/WETH pair and tracks K using ABDK fixed-point math. The vulnerable logic is:
function removeLiquidityWhenKIncreases() public {
(uint256 tokenReserve, uint256 wethReserve) = getReservesSorted();
uint256 currentK = tokenReserve * wethReserve;
if (currentK > (105 * INITIAL_UNISWAP_K / 100)) {
IUniswapV2Pair pair = IUniswapV2Pair(uniswapV2Pair);
_balances[uniswapV2Pair] -= tokenReserve * (currentK - INITIAL_UNISWAP_K) / currentK;
pair.sync();
}
}
Caption: PumpToken’s vulnerable removeLiquidityWhenKIncreases() function from the verified source, showing an unauthenticated reduction of the pair’s PumpToken balance based solely on currentK relative to INITIAL_UNISWAP_K.
Key properties of this function:
onlyOwner or other access control, so any address can call it.tokenReserve and wethReserve to compute currentK = tokenReserve * wethReserve.currentK exceeds 1.05 * INITIAL_UNISWAP_K, it decreases _balances[uniswapV2Pair] by tokenReserve * (currentK - INITIAL_UNISWAP_K) / currentK and then calls pair.sync(), which updates on-chain reserves to match the ERC20 balances.Because currentK can be increased arbitrarily using flashLoan-sourced WETH to add liquidity and trade against the pair, an attacker can:
K by adding PumpToken/WETH liquidity and performing trades.removeLiquidityWhenKIncreases() to burn PumpToken from the pair based on the artificially high K.On-chain diffs and traces from the exploit tx show exactly this behavior: PumpToken’s balance for the pair address decreases by 13,107,499,667,416,073,916,022 units, and the pair’s WETH reserves later drop dramatically as liquidity is removed and swapped.
0x05641e33fd15baf819729df55500b07b82eb8e89, function removeLiquidityWhenKIncreases().0xb292678438245Ec863F9FEa64AFfcEA887144240, whose PumpToken balance is directly modified by PumpToken::removeLiquidityWhenKIncreases().INITIAL_UNISWAP_K and K-based adjustments can be safely exposed via a public function without access control or protections against flashLoan-driven reserve manipulation.The exploit is enabled by the following concrete conditions:
K and later withdrawing WETH is economically meaningful.removeLiquidityWhenKIncreases() is deployed as a public function with no access control or rate limiting, callable by arbitrary addresses.K beyond 105% of INITIAL_UNISWAP_K.PumpToken._balances[uniswapV2Pair] is reduced, allowing subsequent remove-liquidity and swap operations to extract WETH disproportionate to the attacker’s net contribution.K, in the design of protocol-level reactions.The adversary executes a single exploit transaction that:
K.removeLiquidityWhenKIncreases() function to burn PumpToken from the pair.This entire flow occurs within tx 0xef34f4fdf03e403e3c94e96539354fb4fe0b79a5ec927eacc63bc04108dbf420 on Ethereum mainnet (block 21529888).
Adversary EOA (primary)
0x25869347f7993c50410a9b9b9c48f37d79e12a36Helper contract
0x55877Cf2F24286DBA2aCB64311beca39728Fbd10PumpToken::removeLiquidityWhenKIncreases(), liquidity removal, swaps, and profit forwarding. Its source was not verified on-chain but was decompiled for analysis.Secondary profit recipient
0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97Candidate victims:
0xb292678438245Ec863F9FEa64AFfcEA887144240 (LPs collectively lose WETH backing).0x05641e33fd15baf819729df55500b07b82eb8e89, whose tokenomics are distorted by the unauthorized burning of PumpToken from the LP.Adversary contract deployment and flashLoan
0xef34f4fdf03e403e3c94e96539354fb4fe0b79a5ec927eacc63bc04108dbf420 (block 21529888, Ethereum mainnet).flashloan.0x2586…2a36 deploys the helper contract 0x5587…Bd10 and, via the Balancer Vault, obtains a 30,000 WETH flashLoan to the helper. This seeds the helper with temporary WETH liquidity used to manipulate the PumpToken/WETH UniswapV2 pair and drive K above the 105% threshold.debug_trace_callTracer.json) showing the Balancer flashLoan call to the helper.Reserve manipulation and vulnerable function call
0xef34…f420 (block 21529888).mint (liquidity provision).tokenReserve ≈ 1.744e22 PumpTokenwethReserve ≈ 2.306e22 WETHSync events.currentK beyond 1.05 * INITIAL_UNISWAP_K.PumpToken::removeLiquidityWhenKIncreases(), which reads the reserves, computes currentK, and reduces PumpToken._balances[uniswapV2Pair] by 13,107,499,667,416,073,916,022 units, followed by pair.sync(), syncing the pair to the lower PumpToken balance.│ │ │ ├─ [14325] PumpToken::removeLiquidityWhenKIncreases()
│ │ │ │ ├─ [14162] PumpToken::removeLiquidityWhenKIncreases() [delegatecall]
│ │ │ │ │ ├─ [504] UniswapV2Pair::getReserves() [staticcall]
│ │ │ │ │ │ └─ ← [Return] 17442327956960784747337 [1.744e22], 23068964517278041866222 [2.306e22], 1735737755
│ │ │ │ │ ├─ [7803] UniswapV2Pair::sync()
│ │ │ │ │ │ ├─ emit Sync(reserve0: 4334828289544710831315 [4.334e21], reserve1: 23068964517278041866222 [2.306e22])
│ │ │ │ │ │ ├─ storage changes:
│ │ │ │ │ │ │ @ 0x3c3f86b3bfb9...: 0x...0003b18ced1f7110790b49 → 0x...0000eafdd3c26fb2a0bcd3
Caption: Extract from the exploit transaction’s verbose trace (cast run and callTracer), showing the helper contract calling PumpToken::removeLiquidityWhenKIncreases(), which in turn reads Uniswap reserves and calls sync(), resulting in a large decrease in the pair’s PumpToken balance.
0xef34…f420 (block 21529888).transfer (LP burn and swaps).0x4838…5f97, as evidenced by native_balance_deltas.These lifecycle stages are fully contained within a single adversary-crafted transaction that is feasible for any unprivileged EOA: the helper deployment is a standard type-2 contract creation, all called contracts expose the necessary public entrypoints, and no special permissions are required beyond sufficient gas and tip.
WETH
0x2586…2a36.0x4838…5f97.PumpToken
removeLiquidityWhenKIncreases() during the exploit tx, redistributing value away from LPs.The adversary’s net ETH-denominated profit is strictly positive even after fees. Using the tx’s gas limit (0x18bedf) and gas price (0x4a817c800), the maximum possible gas fee is approximately 0.03243454 ETH, comfortably below the 12.310195657284305206 ETH gain shown for the attacker EOA.
PumpToken::removeLiquidityWhenKIncreases() burning PumpToken from the pair.K are distorted during the attack window, and the pool is left in a degraded state with significantly lower WETH backing relative to PumpToken.K-reactive function is unsafe without rigorous consideration of flashLoan-driven adversarial actions.This incident presents a clear ACT (Automated Counter-Transaction) opportunity:
0xef34…f420 in block 21529888, given pre-state (\u03c3_B) that already includes all relevant contracts and balances.metadata.json (providing pre- and post-tx balances and gas parameters).balance_diff.json (native and ERC20 diffs).debug_trace_prestate_diff.json (storage-level changes for PumpToken, the PumpToken/WETH pair, WETH9, and related contracts).oracle_name = "N/A"), as the exploit predicate is purely financial.The analysis shows that the adversary’s ETH-denominated value increases from 2.87676612083169844 ETH to 15.186961778116003646 ETH, a delta of 12.310195657284305206 ETH, and that even under a conservative gas cost bound (≈0.03243454 ETH), the net profit remains positive.
[1] Seed tx metadata for 0xef34f4fdf03e403e3c94e96539354fb4fe0b79a5ec927eacc63bc04108dbf420
[2] PumpToken source (removeLiquidityWhenKIncreases)
forge clone for 0x05641e33fd15baf819729df55500b07b82eb8e89.[3] Exploit tx call trace (callTracer)
debug_traceTransaction with callTracer, providing a structured call tree including Balancer flashLoan, UniswapV2Router02 interactions, removeLiquidityWhenKIncreases(), and liquidity removal/swaps.[4] Exploit tx storage-level prestate diff
debug_traceTransaction with prestateTracer in diffMode, capturing storage changes for PumpToken, the PumpToken/WETH pair, WETH9, and related contracts across the exploit tx.[5] Exploit tx balance diff (native and ERC20)