LeetSwap Base Pair Drain
Exploit Transactions
0xbb837d417b76dd237b4418e1695a50941a69259a1c4dee561ea57d982b9f10ecVictim Addresses
0x94dac4a3ce998143aa119c05460731da80ad90cfBase0xfcd3842f85ed87ba2889b4d35893403796e67ff1BaseLoss Breakdown
Similar Incidents
Base DUCKVADER infinite mint + Uniswap drain
36%CAROL Reward Inflation Drain
35%Veil01ETH forged-proof drain on Base
35%MPRO Staking Proxy unwrapWETH Flash-Loan Exploit (Base)
34%ACP Router/PaymentManager Double-Claim Drain on Base
33%Base USDC drain from malicious transferFrom spender approvals
33%Root Cause Analysis
LeetSwap Base Pair Drain
1. Incident Overview TL;DR
On Base block 2031747, transaction 0xbb837d417b76dd237b4418e1695a50941a69259a1c4dee561ea57d982b9f10ec drained the LeetSwap WETH/axlUSDC pair at 0x94dAC4a3Ce998143aa119c05460731dA80ad90cf. The attacker bought a small axlUSDC position, used a public helper on the pair to move almost all axlUSDC from the pair into the fee vault, called sync() to ratchet reserves down to the manipulated balance, and then swapped axlUSDC back into the pair to extract WETH.
The root cause is a contract bug in LeetSwapV2Pair: _transferFeesSupportingTaxTokens(address,uint256) is externally callable even though it is an invariant-critical fee-moving primitive. Because it transfers pair-held assets to the fees contract without access control and without updating reserves, any unprivileged caller can desynchronize balances from stored reserves and then make the manipulated balances authoritative via sync().
2. Key Background
LeetSwap uses a Uniswap-V2-style pair design with separately stored reserves (reserve0, reserve1) and live ERC-20 balances. Pricing and invariant checks are performed against the stored reserves until _update() or sync() refreshes them.
The attacked pair is the verified LeetSwap WETH/axlUSDC pair at 0x94dAC4a3Ce998143aa119c05460731dA80ad90cf. In the verified source, the pair deploys a dedicated fee vault contract and stores its address in immutable fees.
axlUSDC on Base is the verified token at 0xEB466342C4d449BC9f53A865D5Cb90586f405215. The pair held deep WETH liquidity and large axlUSDC reserves immediately before the exploit, which made reserve manipulation economically meaningful.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability class is an access-control and reserve-accounting failure in the pair contract. The key bug is that the helper below is public, not internal, and it directly transfers pair-held tokens to the fee vault:
function _transferFeesSupportingTaxTokens(address token, uint256 amount)
public
returns (uint256)
{
uint256 balanceBefore = IERC20(token).balanceOf(fees);
_safeTransfer(token, fees, amount);
uint256 balanceAfter = IERC20(token).balanceOf(fees);
return balanceAfter - balanceBefore;
}
The contract's reserve model requires economically available balances and stored reserves to stay aligned. That invariant is broken here because _transferFeesSupportingTaxTokens moves assets out of the pair without calling _update(). The pair therefore keeps stale reserves until somebody later calls sync():
function sync() external lock {
_update(
IERC20Metadata(token0).balanceOf(address(this)),
IERC20Metadata(token1).balanceOf(address(this)),
reserve0,
reserve1
);
}
In effect, the attacker can first shrink the live balance of one side of the pool, then use sync() to overwrite the stored reserves with the manipulated balance, and then trade against a price curve that now vastly overprices the depleted asset. That is exactly what happened here on the axlUSDC side of the pair.
4. Detailed Root Cause Analysis
Immediately before the exploit, the pair held about 120.187521209079354818 WETH and 216584888040 raw axlUSDC units. The attacker-funded exploit contract first wrapped 0.001 ETH into WETH and swapped it into the pair for a starter axlUSDC position.
The crucial breakpoint was the direct call to the public helper:
LeetSwapV2Pair::_transferFeesSupportingTaxTokens(
0xEB466342C4d449BC9f53A865D5Cb90586f405215,
216583081404
)
The collected trace shows the pair transferring 216583081404 raw axlUSDC units to its fee vault 0xE659e3044B4720B4f107b12a45bcd9bc44A4AC02, and returning the same amount. The balance-diff artifact confirms the effect:
{
"token": "0xeb466342c4d449bc9f53a865d5cb90586f405215",
"holder": "0x94dac4a3ce998143aa119c05460731da80ad90cf",
"before": "216584888040",
"after": "1622117",
"delta": "-216583265923"
}
At that point the pair's live axlUSDC balance had been almost fully stripped, but the stored reserves were still stale. The attacker then called sync(). The trace shows the exact reserve update:
emit Sync(reserve0: 120188518209079354818, reserve1: 10000)
This is the invariant failure made authoritative: the pair still held roughly 120.1885 WETH, but reserve1 had been collapsed to only 10000 raw axlUSDC units. With reserves now reflecting the manipulated balance, the pair priced axlUSDC as if it were nearly absent.
The attacker then sold the axlUSDC accumulated from the seed swap back into the pair. The trace and balance diffs show a WETH transfer of 119446585023779038288 wei to the exploit contract, after which the contract unwrapped WETH to ETH and paid the profit recipient. The exploit did not require privileged keys, governance, or non-public data. Any unprivileged actor could have reproduced the same sequence on the public pair contract.
5. Adversary Flow Analysis
The adversary cluster consisted of:
- EOA
0x705f736145bb9d4a4a186f4595907b60815085c3, which sent the exploit transaction. - Exploit contract
0xea8f89f47f3d4293897b4fe8cb69b5c233b9f560, which executed the calls. - Profit-recipient EOA
0x5b030f90db67190373dbf3422436df4c62f60a60, which received the final ETH payout.
The on-chain sequence was:
- The exploit contract received
0.001ETH of seed capital and wrapped it into WETH. - It swapped that WETH into the target pair to acquire a small axlUSDC inventory.
- It called the public pair helper
_transferFeesSupportingTaxTokens(axlUSDC, 216583081404), pushing almost all pair-held axlUSDC into the pair's fee vault. - It called
sync(), causing the pair to emitSync(reserve0=120188518209079354818,reserve1=10000). - It transferred its axlUSDC back into the pair and called
swap(...)to pull out WETH against the manipulated reserves. - It unwrapped WETH to ETH and transferred the proceeds to
0x5b030f90db67190373dbf3422436df4c62f60a60.
This is a single-transaction ACT exploit: all calls were made through public interfaces, and the profit condition was realized immediately in the same transaction.
6. Impact & Losses
The measurable loss was 119446585023779038288 wei of WETH-equivalent value, or 119.446585023779038288 WETH, drained from the LeetSwap WETH/axlUSDC pair.
The balance-diff artifact shows:
{
"token": "0x4200000000000000000000000000000000000006",
"holder": "0x94dac4a3ce998143aa119c05460731da80ad90cf",
"before": "120187521209079354818",
"after": "740936185300316530",
"delta": "-119446585023779038288"
}
The profit recipient 0x5b030f90db67190373dbf3422436df4c62f60a60 ended with 119447582023779038288 wei of native ETH-equivalent balance increase. After subtracting the exploit contract's 0.001 ETH seed capital and the sender EOA's 987419792502374 wei gas cost, the net profit was 119445594603986535914 wei.
7. References
- Base tx
0xbb837d417b76dd237b4418e1695a50941a69259a1c4dee561ea57d982b9f10ecmetadata and trace. - Collected balance diff for the same transaction, showing WETH loss from the pair and ETH gain by the profit recipient.
- Verified LeetSwapV2Pair source for
0x94dAC4a3Ce998143aa119c05460731dA80ad90cf, specifically_transferFeesSupportingTaxTokens,_update, andsync. - Verified axlUSDC source for
0xEB466342C4d449BC9f53A865D5Cb90586f405215.