UN Burn-Skim Exploit
Exploit Transactions
0xff5515268d53df41d407036f547b206e288b226989da496fda367bfeb31c5b8bVictim Addresses
0x1afa48b74ba7ac0c3c5a2c8b7e24eb71d440846fBSC0x5f739a4ade4341d4aee049e679095bccbe904ee1BSCLoss Breakdown
Similar Incidents
CS Pair Balance Burn Drain
42%GPT Public LP-Burn Exploit
41%APE2 Pair Burn Exploit
41%T3913 Pair-Skim Referral Drain
41%SafeMoon LP Burn Drain
41%BIGFI Burn Bug Drains PancakeSwap
40%Root Cause Analysis
UN Burn-Skim Exploit
1. Incident Overview TL;DR
On BNB Chain transaction 0xff5515268d53df41d407036f547b206e288b226989da496fda367bfeb31c5b8b in block 28864174, an unprivileged adversary used a public DODO flash loan to buy UN, repeatedly manipulated the UN/USDT pair reserve accounting, then exited back to USDT at an inflated price. The attacker repaid the flash loan inside the same transaction and realized 26558.707032043241953536 USDT of profit.
The root cause is in the UN token itself. Any public transfer to swapPair enters a pre-settlement hook that burns pair-held UN and immediately calls sync() before the sell transfer finishes. Because the later transfer lands after the reserve sync, the attacker can call skim(address) on the pair to recover the post-sync excess and repeat the loop until the pair's UN reserve is heavily depressed.
2. Key Background
UN is the token at 0x1afa48b74ba7ac0c3c5a2c8b7e24eb71d440846f. Its configured swapPair is the UN/USDT pair at 0x5f739a4ade4341d4aee049e679095bccbe904ee1. The pair is not verified on Etherscan, but the saved source lookup shows it is unverified rather than unknown, and the execution trace directly proves that it exposes the public sync() and skim(address) entrypoints used in the exploit.
The exploit relies on standard Uniswap V2 semantics. sync() updates stored reserves to current balances, while skim(address) transfers any balance above stored reserves to an arbitrary recipient. Under normal AMM accounting, arbitrary traders should not be able to change the pair's reserves except through value-conserving swap or LP flows.
The attacker also used DODO pool 0xfeafe253802b77456b4627f8c2306a9cebb5d681 as a public flash-loan source. No privileged access, private calldata, or attacker-specific infrastructure was required beyond deploying a helper contract and composing public calls in one transaction.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability class is an application-level attack caused by unsafe reserve mutation inside the token contract. UN places a public hook on every sell into swapPair, and that hook runs before the sell-side transfer accounting is complete. Inside the hook, UN burns a caller-controlled amount of pair inventory and calls sync(), which resets the pair's stored reserve to the reduced UN balance. When control returns to the sell path, UN transfers roughly 90.5% of the caller's amount into the pair, but that balance is now surplus relative to the just-synced reserves. Because the pair still exposes public skim(address), the attacker can immediately reclaim the surplus. Repeating this pattern lets the attacker pay taxes while driving down the pair's synced UN reserve. Once the reserve is sufficiently low, the attacker can sell the remaining UN into a badly distorted pool price and extract USDT.
The violated invariant is straightforward: a public transfer into the pair must not let an arbitrary seller destroy pair inventory, resync the reserve baseline, and reclaim the later transfer as excess. The precise breakpoint is UN's _transfer() function calling _swapBurn(amount) when to == swapPair, and _swapBurn(amount) executing a pair burn plus sync() before the sell settles.
4. Detailed Root Cause Analysis
The verified UN source shows the bug directly:
function _transfer(address from, address to, uint256 amount) internal override {
...
if (swapPair != address(0) && to == swapPair && !_inSwapAndLiquify) {
_swapBurn(amount);
}
...
} else if (to == swapPair) {
uint256 every = amount.div(1000);
...
super._transfer(from, to, amount - every * 95);
}
}
function _swapBurn(uint amount) private lockTheSwap {
if (balanceOf(address(this)) > 0)
super._transfer(address(this), swapPair, balanceOf(address(this)));
if (totalCirculation() > minTotalSupply() + (amount * 40) / 100) {
super._burn(swapPair, (amount * 40) / 100);
}
ISwapPair(swapPair).sync();
}
This code establishes the full exploit path:
- Any seller controls
amountby callingUN.transfer(pair, amount). - Before the sell-side tax branch transfers the seller's tokens into the pair,
_swapBurn(amount)burns40%ofamountfrom the pair's existing UN inventory. _swapBurn(amount)immediately callsISwapPair(swapPair).sync(), so the pair stores the post-burn UN balance as its new reserve.- Control returns to the sell branch, which later transfers
amount - every * 95into the pair. - Because the reserve was already synced, the fresh transfer sits above the stored reserve and is recoverable through
skim(address).
The on-chain trace shows this sequence repeating in the live exploit. The transaction begins with a DODO flash loan and the initial UN purchase:
0xFeAFe253802b77456B4627F8c2306a9CeBb5d681::flashLoan(..., 29100000000000001048576, ...)
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::swap(91391982773176450879376, 0, attacker, 0x)
The reserve-manipulation loop is then visible several times:
UN::transfer(pair, 84994543979054099317825)
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::sync()
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::skim(attacker)
...
UN::transfer(pair, 71535657939970882690921)
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::sync()
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::skim(attacker)
...
UN::transfer(pair, 30103993252588246708450)
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::sync()
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::skim(attacker)
After these loops collapse the pair's synced UN reserve, the attacker performs the final manipulated exit:
0x5F739a4AdE4341D4AEe049E679095BcCbe904Ee1::swap(
0,
55658707032043243002112,
attacker,
0x
)
BEP20USDT::transfer(DODO_pool, 29100000000000001048576)
BEP20USDT::transfer(sender, 26558707032043241953536)
The balance diff confirms the outcome quantitatively. The pair lost 26558707032043241953536 raw USDT units and the seed EOA gained exactly the same amount, while also paying 582331659769200000 wei of gas:
{
"pair_usdt_delta": "-26558707032043241953536",
"attacker_usdt_delta": "26558707032043241953536",
"attacker_gas_delta_wei": "-582331659769200000"
}
The exploit is therefore deterministic and ACT-feasible from public state alone:
swapPairis publicly configured.- The DODO flash loan is public.
- The pair's public
sync()andskim(address)behavior is proven by the seed trace. - No privileged role, stolen key, or attacker-only artifact is needed.
5. Adversary Flow Analysis
The adversary cluster consists of:
- EOA
0xf84efa8a9f7e68855cf17eaac9c2f97a9d131366, which submitted the transaction and received the final profit. - Helper contract
0x98e241bd3be918e0d927af81b430be00d86b04f9, which executed the flash-loan callback logic on-chain.
The execution flow is:
- The helper borrows
29100USDT from DODO pool0xfeafe253802b77456b4627f8c2306a9cebb5d681. - It transfers the borrowed USDT into the UN/USDT pair and swaps into
91391982773176450879376UN. - It repeatedly calls
UN.transfer(pair, amount)followed bypair.skim(attacker):- the transfer triggers
_swapBurn(amount), _swapBurnburns pair-held UN and callssync(),- the sell branch then transfers fresh UN into the pair,
skim(attacker)recovers the post-sync excess.
- the transfer triggers
- After enough loops, the pair's stored UN reserve is far below its pre-state level.
- The helper performs a final UN to USDT swap against the manipulated reserve ratio and receives
55658707032043243002112raw USDT units. - It repays the DODO flash loan principal.
- It transfers the remaining
26558707032043241953536raw USDT units to the seed EOA.
This is a single-transaction ACT realization. The helper contract only packages public actions; it does not add any privileged capability beyond orchestration.
6. Impact & Losses
The direct measurable loss is the USDT drained from the UN/USDT pair:
- Token:
USDT - Raw on-chain amount:
26558707032043241953536 - Decimal:
18 - Human-readable amount:
26558.707032043241953536
The pool also suffered severe reserve distortion on the UN side. The balance diff shows the pair's UN balance dropping from 186912581853851065964787 to 53382130893876527157390, a reduction of 133530450959974538807397 raw UN units. The transaction therefore caused both immediate LP loss in USDT terms and a large reserve collapse that enabled the inflated exit price.
7. References
- Seed exploit transaction:
0xff5515268d53df41d407036f547b206e288b226989da496fda367bfeb31c5b8b - Seed transaction metadata for block and sender information.
- Seed execution trace showing the flash loan, repeated
transfer -> sync -> skimloops, final swap, repayment, and profit transfer. - Seed balance diff showing pair depletion and attacker profit.
- Verified UN source at
0x1afa48b74ba7ac0c3c5a2c8b7e24eb71d440846f. - Pair verification lookup for
0x5f739a4ade4341d4aee049e679095bccbe904ee1, showing the pair is unverified rather than unknown.