October 20, 2026
|
Exploit Postmortem
Cover image for "LULA's $578K Loss and How Olympix Would Have Prevented It"

LULA's $578K Loss and How Olympix Would Have Prevented It

Date: July 28, 2026

Chain: BNB Chain

Loss: about $578K in USDT

Class: a token function that rewrote its own liquidity pool's reserves without a swap

Twelve days before LULA's liquidity pool emptied, an attacker started deploying helper contracts and pointing them at the token's reward system. According to CertiK, the helpers spent those twelve days accumulating referral and team rewards in LULA's Rental contract, building up claims that looked like ordinary participation in the protocol.

On the day of the attack, those claims became a lever. The attacker borrowed close to $200M in USDT through flash loans, pushed it into LULA's PancakeSwap V2 pool, and then cashed in the reward claims one after another, each one triggering a token function called recycle() that pulled LULA straight out of the pool and told the pool to accept its new, smaller balance. When the pool held almost no LULA at all, a final small swap took out its USDT, and the attacker walked away with about $578K after repaying the loans. It worked because LULA's token contract could change a pool's balance directly, without ever going through the pool's own trading logic.

A Deflationary Token That Paid Rewards From the Pool

LULA is a BEP-20 token on BNB Chain built around a rental and team-reward system. Eligible addresses accrue pending rewards in the Rental contract and claim them through a reward function, and that claim calls the token's recycle() function to source the LULA that gets paid out. The design pulled reward tokens out of circulation instead of minting new ones, which is the deflationary part.

The circulation it pulled from was the PancakeSwap V2 LULA/USDT pair. recycle() could only be called by the Rental contract, and each call could take at most one-third of the pair's LULA balance. Both limits were real, and both looked like the kind of guardrails a reviewer would want to see on a function that touches a pool.

How recycle() Moved a Pool Without Trading With It

An automated market maker like PancakeSwap V2 prices a token from the two balances it holds. When you trade, the pair's own swap logic moves tokens in and out and keeps its recorded reserves in step with them, so the price only moves as far as real trades push it. A separate function, sync(), exists so the pair can catch up if tokens arrive or leave some other way: it simply sets the recorded reserves to whatever the pair currently holds.

recycle() used an internal function called _basicTransfer() to move LULA out of the pair. According to CertiK, _basicTransfer() only adjusted balances and emitted a Transfer event, skipping the checks a standard transfer applies. Once the tokens were gone, recycle() called sync(), and the pair adopted the reduced LULA balance as its new reserve. No swap happened and no fee was paid, but the pool's price moved as if someone had bought a third of its LULA for free.

A pool's reserves are the shared ledger every trader prices against, and only trades should be able to rewrite them. A token contract that can shrink one side of its own pool and then make the pool agree has, in effect, a private lever on the price, and the one-third cap only limits how hard each pull can be. Nothing in the code is syntactically wrong, which is why the flaw lives in the relationship between _basicTransfer(), sync() and the pair rather than in any one line.

Two Hundred Million Dollars to Move a Few Thousand Tokens

BlockSec's breakdown lays the transaction out in four steps. The attacker first gathered about 197.05M USDT from several flash-loan and borrowing sources, including Moolah/Lista, Aave V3, Venus, PancakeSwap V3, a PancakeSwap Vault, the Uniswap V4 PoolManager and Uniswap V3. CertiK and SlowMist Hacked describe the flash loan as about $237M. On-chain, the attack contract sent 197,050,771 USDT into the pair in a single transfer, which lines up with BlockSec's figure.

Next, a large USDT-to-LULA swap through the PancakeSwap V2 router cut the pair's LULA reserve from about 8M to 24,022 LULA and raised its USDT side to about 197.64M. Then several EIP-7702 wallets, which are ordinary accounts that have been given contract code to run, called claimTeamReward() on the Rental contract. Each claim triggered LULA.recycle(), each recycle() pulled LULA out of the pair and re-synced it, and by the end the pair's recorded LULA reserve stood at 0.004.

With almost no LULA left on one side and about 197.64M USDT on the other, the pool would sell its entire USDT balance for a handful of tokens. The attacker swapped about 4,749 LULA in and took about 197.64M USDT out, repaid every flash loan in the same transaction, and sent 578,295.91 USDT to its own wallet.

The flash loan only amplified the bug. It made the USDT side of the pool large enough that a near-empty LULA side was worth draining, while the twelve days of reward accumulation supplied the claims that pulled the trigger on recycle(). Without a token function able to rewrite the pair's reserves, neither piece would have done anything.

The whole sequence ran in one transaction at 16:08:29 UTC on July 28, sent from the attacker's wallet 0x2677...a4ed to its attack contract 0x5e50...a816. TenArmor flagged the incident on X at 01:33 UTC on July 29, and BlockSec Phalcon and CertiK followed within about two hours with breakdowns.

How Olympix Tests the Balance Behind a Pool's Price

Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the context around the code to infer the rules a system depends on without ever stating them. For LULA, the state that mattered most didn't live in the token at all. It was the reserve recorded by the PancakeSwap pair the token feeds, and every token function that could change that number was an entry point worth mapping. The engine narrows the token's paths with signals, patterns in the code that mark a path as dangerous, and here the signal is a token function that moves balance out of an AMM pair it doesn't own and then calls sync() on that pair.

BugPoCer picks up from that pair's reserve. Running Counterexample-Guided Invariant Refutation, it starts from the system as a whole and pins down the rule the pool depends on: nothing the token contract does should lower the pair's recorded LULA reserve unless a real swap went through the pair. It then executes paths through the Rental contract and into recycle() until one lowers that reserve without a swap, and the first reward claim that reaches recycle() does exactly that.

What comes back is a counterexample: a reward claim through the Rental contract, a recycle() call that moves LULA out of the pair with _basicTransfer(), a sync() that locks in the smaller reserve, and a swap that prices against it, along with the minimal conditions that trigger it and a generated Forge test that replays the sequence. Scaled up with borrowed USDT and repeated claims, that test is the LULA drain, caught in development instead of on BNB Chain.

Olympix has flagged this class of reserve desynchronization before in other codebases. Because the engine runs automatically in development, at every merge and before release, a token team gets a standing view of every path in its contract that can touch the pool it trades in.

Guardrails Around the Wrong Thing

recycle() had an access check and a size cap, and both did exactly what they said. Neither one asked whether the token should be able to rewrite the pool's reserves at all, and that's the question any token that interacts with its own liquidity needs to settle before launch. If your contracts move tokens in or out of an AMM pair outside a swap, book a technical walkthrough.

On-Chain References

Protocol: LULA

Chain: BNB Chain

Date: July 28, 2026, 16:08:29 UTC

Loss: 578,295.91 USDT sent to the attacker's wallet in the exploit tx

Attacker wallet: 0x2677...a4ed

Attack contract: 0x5e50...a816

LULA token: 0xf5d7...2e9a

PancakeSwap V2 USDT/LULA pair: 0xf0b3...6a96

Reward contract (took the recycled LULA from the pair and paid each claim): 0x377a...7f1a

Exploit tx: 0xa219...1d7c

Block: 112655390

Sources

BlockSec: ~$88M Lost, COLDCARD and LULA Exploits

CertiK Alert: LULA exploit alert

TenArmor Alert: LULA exploit alert

More from Olympix:

No items found.

Ready to Shift Security Assurance In-House? Talk to Our Security Experts Today.