
Date: September 30, 2026
Chain: BNB Chain
Loss: 183,482.88 USDT, the pair's entire USDT side, with 59,914.12 USDT kept by the attacker
Class: tax token that burned and synced the pair's reserves in the middle of a sell
On September 30, 2026, a single contract-creation transaction in BNB Chain block 124921242 emptied the USDT side of the SKYDAO/USDT PancakeSwap pair. The contract's constructor ran the whole attack, and by the time it finished, the pair had lost all 183,482.88 USDT it started with and the attacker's wallet held 59,914.12 USDT.
SKYDAO is a token that charges a 35% tax when it's sold into its pair. On a sell, the token let a pool controller burn the pair's entire SKYDAO balance and lock in the pair's books before the seller's tokens had arrived, so the pair briefly believed it held almost no SKYDAO while it was actually holding a great deal of it.
According to the DeFiHackLabs proof of concept, which checks its root cause against SKYDAO's verified source and the on-chain trace, the token treats any transfer whose recipient is the pair as a sell. A sell is taxed at 35%, and that tax goes to a pool controller contract, which then acts on the pair. The controller is unverified, so its behavior comes from the transaction trace: it sells the tax, burns SKYDAO from the pair, and calls the pair's sync() function.
A PancakeSwap V2 pair keeps a stored record of its reserves, separate from its actual token balances, and prices every swap off those stored reserves. sync() overwrites the stored reserves with whatever the actual balances are at that moment. Used at the right time, it keeps the books honest. Used in the middle of a transfer, it writes down a number that's about to stop being true.
SKYDAO's sell path ran in this order. The token moved the 35% tax to the controller and handed the controller the gross sell amount. The controller sold its tax, then called SKYDAO's controlburn(), which moved the pair's entire SKYDAO balance to the dead address, and called sync(). The pair's stored SKYDAO reserve was now close to zero. Only after all of that did SKYDAO credit the seller's net 65% to the pair.
That left the pair holding a large real SKYDAO balance against a stored reserve near zero. To a constant-product AMM, a gap like that looks like a huge deposit of SKYDAO that's owed a matching amount of USDT, and anyone who calls swap() directly on the pair can claim it.
The idea a builder should keep from this is that a token's transfer hook must never update an AMM's books while the transfer it's part of is still half done. It's like a bank teller who counts the drawer, writes the total in the ledger, and only then puts the customer's deposit in, so the next person can withdraw the difference as though it were theirs.
controlburn() itself was restricted so only the controller could call it, and the PoC notes that this gate protected nothing, because the public sell path is what made the controller call it. Nothing the attacker touched was privileged.
The attack contract was created by the attacker's wallet, 0x5C92...6EBc, and did everything in its constructor. Following the PoC's reconstruction, it flash-borrowed 2,656,932.603895 USDT from Moolah, a lending protocol on BNB Chain, in a fee-free loan that used exactly what Moolah's pool held. It spent 435,367.43 USDT buying SKYDAO through the PancakeSwap router, paying the 35% buy tax along the way.
It then transferred its whole SKYDAO balance to the pair, which triggered the sell path: the controller sold its tax, burned the pair's SKYDAO, synced the near-zero reserve, and SKYDAO credited the net 65% afterward. With the pair's books out of step with its balance, the contract called pair.swap() directly and pulled out 495,281.56 USDT. It repaid the flash loan and sent the 59,914.12 USDT surplus to the attacker's wallet.
The PoC reconciles the pair's books to the unit. The pair took in 435,367.43 USDT from the buy and 92,676.57 USDT from a controller refill, and paid out 216,245.33 USDT on the controller's tax sale and 495,281.56 USDT in the final drain, for a net loss of 183,482.88 USDT, exactly what it held before the attack.
Olympix reads a contract the way an attacker does, as states, transitions and entry points, and takes in the context it runs in, here a fee-on-transfer token wired to a PancakeSwap V2 pair, to infer the invariants the system depends on but never writes down. The invariant in one sentence is that the pair's stored reserves must match its real balances whenever a swap can be priced, so no transfer hook may sync the pair before the transfer that triggered it has settled.
The engine maps every path through SKYDAO's transfer function and uses signals to decide which ones to attack. The signal here is a transfer hook that, on the sell branch, hands control to an outside contract that burns from the pair and calls sync() before the seller's net amount is credited, an external call that mutates the pair's reserve state in the middle of a transfer. Olympix's static analysis surfaces that ordering in the token's own code.
Where static analysis points at the ordering, BugPoCer shows what it costs. Counterexample-Guided Invariant Refutation starts from the guarantee the pair as a whole depends on, that stored reserves match real balances whenever a swap can be priced, and works outward to the token calls that could leave the two apart. For SKYDAO that means sending sells of different sizes into the pair, letting the controller's burn and sync run inside each one, and then checking the pair's books against its balance. A single sell is enough, since the burn and sync inside it leave stored SKYDAO near zero just before the net 65% lands, and a direct swap() turns that gap into the pair's USDT. That counterexample is the concrete sequence (buy, sell into the pair, swap directly against the stale reserve), the minimal conditions that trigger it, and a generated Forge test that replays it, which is the September 30 transaction produced in development instead of on BNB Chain.
The engine runs during development, at every merge and before release, which matters for tokens like this one, where the pair's safety depends on how the token, an unverified controller and the AMM interact, and each piece can look reasonable on its own.
SKYDAO's loss came down to the order of three steps in one transfer function, with the controller's burn and sync running before the sell was credited to the pair. Any team shipping a token with hooks that touch its own pair can test that order directly instead of trusting it. To see Olympix run your token's transfer paths against the AMM it lives in, book a technical walkthrough.
Protocol: SKYDAO (fee-on-transfer token, SKYDAO/USDT PancakeSwap V2 pair)
Chain: BNB Chain
Date: September 30, 2026, 14:03:26 UTC (block timestamp)
Loss: 183,482.88 USDT from the pair; 59,914.12 USDT net to the attacker
Attacker EOA: 0x5C92...6EBc
SKYDAO token: 0x7eBa...a70c
SKYDAO/USDT pair: 0x096e...c2c8
Pool controller (unverified): 0xEe5f...730c
Moolah flash pool: 0x8F73...5D8C
Exploit tx (contract creation): 0x8e30...40eb
Block: 124921242
Flash loan: 2,656,932.603895 USDT from Moolah
DeFiHackLabs PR #1286: SKYDAO burn-from-pair and premature sync PoC
DeFiHackLabs: SKYDAO_exp.sol (addresses, trace reconciliation)