BNB Chain
Reserve manipulation

JB Token's $50K Loss and How Olympix Would Have Prevented It

Loss
Loss
Exploit date
June 18, 2026

At 16:15:57 UTC on June 18, 2026, one transaction on BNB Chain sold JB into its own PancakeSwap pool sixteen times in a row, and after every sale the JB token reached into that pool and destroyed part of its JB balance. By the end of block 104980467, the token had burned 76,299,309.66 JB straight out of the JB/USDT pair, and the attacker's wallet held 49,958.06 USDT that the pool's liquidity providers used to own.

Every dollar of borrowed capital in the transaction went back where it came from before it ended. The only lasting change was the one JB's own code made, a burn aimed at the pool rather than at the person selling, followed by a call that told the pool to believe it. DARKNAVY's analysis, published the same day, traced the drain to that burn and put the pool's loss at the 49,958.06 USDT the attacker kept.

A Token That Shrinks Its Own Pool

JB traded against USDT in a PancakeSwap V2 pair, and like every pair of that design, the pool prices a swap from two stored numbers, its recorded JB reserve and its recorded USDT reserve. A swap that takes USDT out has to put enough JB in to keep the product of those two reserves from falling. The pool's sync() function overwrites the recorded reserves with whatever balances it actually holds, which is how a pool catches up after tokens arrive or leave outside a normal swap.

JB sales went through a route contract that pulled the seller's JB, handed slices of it to two other addresses, sold the rest into the pair, and forwarded the USDT back to the seller. After the USDT had already been paid out, the route called a function on the JB token. According to DARKNAVY, that function burned JB from the pair's own balance and then called sync(), so the pool's recorded JB reserve dropped while its USDT reserve stayed exactly where the sale had left it.

DARKNAVY recovered JB's logic from the transaction trace because the token's source isn't verified on-chain, and it labels the recovered code an approximation. Its reading is that the burn function was restricted to the trading route, which meant any seller who used the route set it off.

Why Each Sale Paid Better Than the Last

A burn from the pair works like a price increase that nobody paid for. With fewer JB on the pool's books and the same USDT behind them, every JB still outside the pool can be sold for more USDT than it could a moment earlier. A seller who keeps selling gets the benefit of every burn that came before, and the liquidity providers absorb the difference, because the JB that disappeared from their pool was never bought from them.

In ordinary trading, a small burn per sale nudges the price up a little and looks like the deflationary feature it was probably meant to be. In the hands of someone holding a large borrowed position in JB and willing to sell it back in pieces, the same burn becomes a way to walk the pool's USDT out the door one cycle at a time.

Seventy Million in Borrowed USDT, Sixteen Sales

The attacker's wallet, 0xd99e...b856, called an executor contract at 0xcc21...dee7. Following DARKNAVY's reconstruction and the transfers recorded on-chain, the executor flash-borrowed 417,464.10 WBNB, deposited it in Venus as collateral, and borrowed 70,000,000 USDT against it.

It spent all 70 million USDT buying JB through the route contract, which gave it 119,907,137.39 JB out of the pair. Then it started selling. Each of the sixteen sales sent JB through the route, which passed some of it to a controller contract and another address, sold the remainder into the pair, returned the USDT, and called the token's burn function. Each burn took JB out of the pair and synced its reserves before the next sale arrived. DARKNAVY counts sixteen burns and sixteen post-burn sync() calls in the trace.

When the loop finished, the executor repaid the Venus loan, withdrew its WBNB, repaid the flash loan, and sent the 49,958.06 USDT that remained to the attacker's wallet. SlowMist's hack tracker lists the incident at about $50,000. As of November 6, 2026, no public statement or post-mortem from the JB team had surfaced in our research.

How Olympix Would Have Followed the Burn Into the Pool

Olympix reads a token as an attacker would, as states, transitions and entry points, and it takes in the context the code runs in. JB lives next to a constant-product pair whose prices are its stored reserves, so the state that matters most isn't in the token at all. It's the pair's JB balance and the reserves it records after sync(). Any entry point that can lower that balance without a matching trade is a path worth mapping, and the engine uses signals to narrow from every path, to the feasible ones, to the ones that touch the invariant. A burn whose source is the pair address, followed immediately by sync() on that pair, is about as strong a signal as token code produces.

The invariant takes one sentence: the pool's JB reserve should only fall when someone pays the pool for the JB that leaves. BugPoCer would have run Counterexample-Guided Invariant Refutation from the system outward, treating JB, its route and the pair as one machine, and executed sell sequences until one broke that rule. A single sale through the route already breaks it, since the burn removes pool JB that nobody bought, and repeating the sale shows how quickly the gap compounds into USDT leaving the pool.

That search would have ended with a counterexample a developer can run: buy JB out of the pair, sell it back through the route in pieces, let each burn and sync() shrink the recorded JB reserve, and collect more USDT on every pass. BugPoCer would have returned the sequence with the minimal conditions that trigger it and a generated Forge test that replays it, which is the June 18 drain produced on a developer's machine instead of on BNB Chain. Burns that come out of the pool keep turning up in BNB Chain tokens, and a month later the same pair-burn-and-resync design cost CrowdRingCircle $209K.

Because Olympix runs at every merge and before release, the burn function would have been tested against the pool the day it was written, while changing who pays for the burn was still a one-line decision.

Who Should Pay for a Burn

JB's burn had a reasonable goal and the wrong payer. Supply reductions that come out of the seller's balance cost the seller, while the ones that come out of the pair cost every liquidity provider in it and hand the difference to whoever trades next. A token that touches its own pool's balances is setting that pool's price, and the safest rule is that only the AMM's own swap and liquidity functions ever move its reserves. To see how Olympix tests your token's transfer and burn paths against the pools that price them, and hands back a runnable counterexample when one breaks, book a technical walkthrough.

On-Chain References

Protocol: JB token and its JB/USDT PancakeSwap V2 pair

Chain: BNB Chain

Date: June 18, 2026, 16:15:57 UTC

Loss: 49,958.06 USDT (on-chain, per DARKNAVY)

Exploit tx: 0x54e1...dc25

Block: 104980467

Attacker EOA: 0xd99e...b856

Executor contract: 0xcc21...dee7

JB token (source unverified): 0xcf92...99a7

JB route contract: 0x1b57...e6d7

JB/USDT pair (victim): 0x4393...49f8

Flash-loan source: 0x8f73...5d8c

See what Olympix finds on your own code

Send us a repo that's already been audited, and we'll run Olympix at the same commit and show you what comes back.