
Date: September 23, 2026
Chain: BNB Chain
Loss: about 1.6489 BNB, roughly $1.3K
Class: signed swap amounts read with the wrong sign, so the input size decided the payout
On September 23, 2026, a swap that should have returned 0.002089 WBNB paid out 1.651 BNB instead. The contract that paid it was an unverified helper sitting in front of a PancakeSwap V3 pool, and the 1.651 BNB was its entire working balance.
The helper read the two signed numbers a V3 pool reports after every swap and used the wrong one. It took the positive figure, the USDT the pool had received, and treated it as the amount of WBNB to unwrap and hand to the caller, so the size of the payout followed the size of the input instead of what the pool actually sent out.
PancakeSwap V3 pools, like the Uniswap V3 design they follow, report a swap as two signed amounts, one per token, measured from the pool's side. A positive amount is what the pool received and a negative amount is what it sent out. The sign carries the direction, and code that consumes those amounts has to read it.
Helper contracts sit on top of pools to handle the practical parts of a swap, such as wrapping and unwrapping native BNB. This one kept a working inventory of WBNB so it could unwrap and pay out BNB to callers. Because it was never verified on BscScan, its source isn't public, and what's known about its logic comes from the DeFiHackLabs reproduction that traced the attack, which also records the helper's address, the attacker's wallet, the attack contract and the attack transaction.
In a USDT-to-WBNB swap, the pool receives USDT, so the USDT amount is positive, and it sends out WBNB, so the WBNB amount is negative. According to the reproduction, the helper treated the positive USDT amount as WBNB to unwrap and pay out. A swap of about 1.651 USDT, a dollar and change, therefore told the helper to pay out about 1.651 BNB.
The two numbers had nothing to do with each other. They're denominated in different tokens and point in opposite directions, and the only reason they matched in size was that the attacker chose them to. It's like an exchange booth that reads the number on the bills you hand over and gives you that many units of a far more valuable currency back. Code that consumes signed amounts should check both which token an amount belongs to and which direction its sign says it moved before any of it drives a transfer.
Before the attack, the helper held 1.651021225164017256 WBNB. The attacker borrowed 3.651021225164017256 USDT from Moolah, a lending protocol on BNB Chain, at zero fee, using 1.651021225164017256 USDT as the swap input and keeping 2 USDT as a buffer. The input was sized to match the helper's WBNB balance exactly.
The pool returned 0.002089397784051482 WBNB for the swap, and the helper paid 1.651021225164017256 BNB to the attacker contract. The contract then wrapped 0.05 BNB, bought back the borrowed USDT on PancakeSwap V2 for 0.002095724137859296 WBNB, and repaid Moolah, all inside the flash-loan callback. The attacker came out with 1.648925501026157960 BNB before gas and about 1.6489 BNB after, roughly $1,300.
No privileged role was involved, and the original attack ran inside a contract deployment, so setup and exploit happened in a single transaction. ExVul's alert flagged it the same day. The loss stopped at 1.65 BNB only because that was everything the helper held, and the same bug in front of a larger balance would have paid out more.
The helper's source was never published, so what follows applies the engine to the logic the DeFiHackLabs reproduction describes. Olympix reads a contract the way an attacker does, as states, transitions and entry points, and takes in the context around it, here a helper built on a pool whose swap results carry meaning in their sign. The state that matters is the helper's WBNB inventory, and the paths worth mapping are the ones that end in a BNB transfer to the caller. A signal marks a path as dangerous, and Olympix's static analysis surfaces this one: a signed amount returned from an external swap that's used as an unsigned payout size without any check on its sign or on which token it belongs to. Olympix has flagged this class of signed-arithmetic error before in other codebases.
Whether that misread number can actually move money depends on the helper's balance and the pool around it, so BugPoCer treats the helper and the pool as one system. For a contract whose whole job is passing swap output through to a caller, the rule is short: the BNB it pays out for a swap never exceeds the WBNB the pool sent it for that swap. Counterexample-Guided Invariant Refutation has BugPoCer execute swaps through the helper in both directions and at different sizes until one breaks that rule, and a USDT-to-WBNB swap whose payout is sized by the USDT going in breaks it on the first try.
The counterexample is short: a swap input sized to the helper's balance, a payout of 1.651 BNB where the pool delivered 0.002089 WBNB, and the minimal conditions that make it work, along with a generated Forge test that replays it. That's the September 23 attack as the reproduction describes it, found in development before the helper ever held a balance. Olympix runs automatically in development, at every merge and before release, so a helper like this gets checked the first time its payout logic is written.
Small helpers often skip the scrutiny given to core protocols, yet they hold balances and make payouts like any other contract. Every value a pool reports carries a direction, and payout logic needs to read that direction and the token before it moves funds. If your contracts sit between users and AMM pools, book a technical walkthrough.
Protocol: PancakeV3SwapHelper (unverified third-party helper)
Chain: BNB Chain
Date: September 23, 2026
Loss: about 1.6489 BNB, roughly $1,300
Attacker: 0x804D...d101
Attack contract: 0x4349...3A2f
Vulnerable helper contract (unverified): 0x222C...c52D
Flash-loan source (Moolah): 0x8F73...5D8C
Exploit tx: 0x70bc...5104