Calculated from recorded token losses using historical USD prices at the incident time.
0xe30dc75253eecec3377e03c532aa41bae1c26909bc8618f21fb83d4330a010180x10bc28d2810dd462e16facff18f78783e859351bBSC0xf9e3151e813cd6729d52d9a0c3ee69f22cce650aBSCOn BSC block 20969096, transaction 0xe30dc75253eecec3377e03c532aa41bae1c26909bc8618f21fb83d4330a01018 exploited the ShadowFi token and the ShadowFi/WBNB Pancake pair in a single transaction. The adversary bought a small amount of ShadowFi, burned almost the entire ShadowFi balance held by the pair through a public burn function, called sync() so the pair reserve dropped to one token unit, and then sold the acquired ShadowFi back into the manipulated pool. The sequence drained 1078616699290110670619 WBNB from the pair.
The root cause is a direct authorization failure in ShadowFi. Its public burn(address,uint256) function accepts an arbitrary account and forwards that address into _transferFrom without checking caller ownership or allowance, so any external caller can destroy tokens held by the pair.
PancakeSwap V2 pairs price assets from stored reserves. If a token balance inside the pair changes for any reason, sync() updates the recorded reserves to match the current balances. An attacker who can reduce one side of the pair without paying the counter-asset can force a severe price distortion and extract the other reserve.
ShadowFi is a fee-on-transfer token, but the fee logic does not protect balances from unauthorized destruction. The relevant public contracts are the ShadowFi token at 0x10bc28d2810dd462e16facff18f78783e859351b, the ShadowFi/WBNB Pancake pair at 0xf9e3151e813cd6729d52d9a0c3ee69f22cce650a, and WBNB at 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c.
This incident is an ATTACK, not a benign market dislocation. The violated invariant is simple: only a holder or an approved spender should be able to reduce that holder's token balance. ShadowFi breaks that invariant by exposing a public burn entrypoint that debits any supplied address.
The verified source contains the precise breakpoint:
function burn(address account, uint256 _amount) public {
_transferFrom(account, DEAD, _amount);
emit burnTokens(account, _amount);
}
function _transferFrom(address sender, address recipient, uint256 amount) internal returns (bool) {
_balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");
uint256 amountReceived = shouldTakeFee(sender, recipient) ? takeFee(sender, recipient, amount) : amount;
_balances[recipient] = _balances[recipient].add(amountReceived);
emit Transfer(sender, recipient, amountReceived);
return true;
}
Because _transferFrom authenticates neither sender nor caller intent in the burn path, any user can burn the pair's ShadowFi inventory. Once the pair is reduced to one token unit and sync() is called, the pair records an extreme reserve imbalance, making the remaining attacker-held ShadowFi redeem for nearly all WBNB in the pool.
The ACT opportunity exists at pre-state block 20969095, immediately before the adversary transaction is mined. At that point the pair still holds approximately 10354946297404462 ShadowFi and 1078615699417903036883 WBNB, and the public burn function is live. Those conditions are sufficient for an unprivileged actor to reproduce the exploit.
The seed trace shows the full exploit sequence. First, the transient helper wraps 0.001 BNB into WBNB, transfers it to the pair, and buys ShadowFi:
emit Transfer(from: PancakePair, to: 0x6ED2175Bc502f45499d233ea47e1201C1aD537de, value: 9001636470)
emit Swap(sender: 0x6ED2175Bc502f45499d233ea47e1201C1aD537de, amount0In: 0, amount1In: 1000000000000000, amount0Out: 9576209010, amount1Out: 0, to: 0x6ED2175Bc502f45499d233ea47e1201C1aD537de)
Next comes the authorization failure in action:
ShadowFi::burn(PancakePair, 10354936721195451)
emit Transfer(from: PancakePair, to: 0x000000000000000000000000000000000000dEaD, value: 10354936721195451)
The balance diff confirms the resulting state changes:
{
"pair_shadowfi_before": "10354946297404462",
"pair_shadowfi_after": "8461538283",
"pair_shadowfi_delta": "-10354937835866179",
"dead_shadowfi_delta": "10354936721195451",
"token_fee_delta": "1114670728"
}
After the burn, the helper calls sync(). The trace records the manipulated reserve state directly:
emit Sync(reserve0: 1, reserve1: 1078616699417903036883)
That is the economic pivot. The pair now treats one ShadowFi unit as the entire token-side reserve. The helper then transfers back the bought ShadowFi, swaps it against the distorted pool, and the pair pays WBNB directly to the adversary EOA:
WBNB::transfer(0x4daa3135B016Ac37C46ED03423D314CAeA89ff5e, 1078616699290110670619)
emit Swap(sender: 0x6ED2175Bc502f45499d233ea47e1201C1aD537de, amount0In: 8461538282, amount1Out: 1078616699290110670619, to: 0x4daa3135B016Ac37C46ED03423D314CAeA89ff5e)
The mechanism is deterministic and permissionless. It requires only public BSC state, the public ShadowFi burn function, the public Pancake pair, and minimal initial capital for the first buy.
The adversary cluster consists of EOA 0x4daa3135b016ac37c46ed03423d314caea89ff5e and transient helper contract 0x6ed2175bc502f45499d233ea47e1201c1ad537de. The helper performs the exploit steps and routes proceeds back to the EOA.
The on-chain execution flow is:
0.001 BNB into WBNB.burn(pair, pairBalance - 1).sync() so the pair reserves become 1 ShadowFi versus 1078616699417903036883 WBNB.1078616699290110670619 WBNB at the EOA.The final pair WBNB balance is only 127792366264 wei, so the pool is effectively emptied.
The measurable loss is:
WBNB: 1078616699290110670619 raw units with 18 decimalsThe ShadowFi/WBNB pair lost essentially its entire WBNB inventory. Liquidity providers absorb the loss, while the attacker realizes the drained WBNB in one transaction.
0xe30dc75253eecec3377e03c532aa41bae1c26909bc8618f21fb83d4330a010180x10bc28d2810dd462e16facff18f78783e859351b0xf9e3151e813cd6729d52d9a0c3ee69f22cce650a