Spartan Protocol Current-Balance Liquidity-Share Inflation Drain (2021-05-01)
- Name: Spartan Protocol current-balance liquidity-share inflation drain
- Date: 2021-05-01
- Protocol: Spartan Protocol
- Chain: BNB Chain
- Category: ambient-balance liquidity-share inflation / LP-burn accounting failure / AMM pool-accounting exploit
- Loss: about
$30.5Mfrom the SPARTA/WBNB pool - Summary: Spartan Protocol’s
removeLiquidity()path did not redeem LP tokens against a synced reserve snapshot. Instead,calcLiquidityShare()read the pool’s current token balances, which meant anyone could inflate the balances seen by LP-burn math simply by transferring assets into the pool before burning. The attacker used a large PancakeSwap flash swap to buy SPARTA, mintedSPT1-WBNBLP tokens, then sent additionalSPARTAandWBNBdirectly into the pool outside the normal liquidity-add path. When those previously minted LP tokens were burned, Spartan treated the attacker-donated balances as if they were already backing the old LP position and paid out far more underlying assets than the original deposit justified. The exploit got even worse because Spartan’s cached reserve accounting stayed stale enough that the same amplification assets could then be added as fresh liquidity and withdrawn again. Repeating that cycle drained most of the pool’s WBNB. - Trigger / entry point:
- Spartan exposed public
addLiquidity()andremoveLiquidity()flows on its Uniswap-v2-like pools, with math delegated to the upgradeableUTILScontract. - The vulnerable share-redemption logic lived in
calcLiquidityShare(), which consulted live pool balances instead of the cached pooled amounts that Spartan itself tracked. - The main public exploit transaction was
0xb64ae25b0d836c25d115a9368319902c972a0215bd108ae17b1b9617dfb93af8on BNB Chain.
- Spartan exposed public
- Root cause:
- Spartan’s liquidity-burn path trusted the pool’s current ERC-20 balances as the amount that existing LP tokens were entitled to redeem.
- That made unsolicited direct transfers into the pool economically meaningful, even though they had not been committed through the protocol’s own liquidity-accounting path.
- Spartan also failed to synchronize those live balances back into its cached reserve variables before later liquidity-accounting steps, so the same direct-transfer balances could be reinterpreted as fresh user input on the next
addLiquidity()call. - The exploitable gap was therefore not just
burn reads the wrong number; it was a two-step accounting failure where ambient balances first inflated LP redemption value and were then counted again as new liquidity.
- Exploit path:
- The attacker used a PancakeSwap flash swap to borrow
100,000 WBNB, owing100,260 WBNBby the end of the transaction. - To avoid Spartan’s liquidity-sensitive fee schedule, the attacker bought SPARTA in several smaller batches, then added roughly
2,536,613.2061 SPARTAplus11,853.3327 WBNBas liquidity and minted933,350.9599 SPT1-WBNBLP tokens. - The attacker then bought more SPARTA in additional batches, accumulating about
2,639,121.9774 SPARTA, and transferred that SPARTA plus roughly21,632.1474 WBNBdirectly into the pool without using the normal liquidity-add accounting path. - Next, the attacker burned the earlier
933,350.9599 SPT1-WBNB. BecausecalcLiquidityShare()looked at the pool’s now-inflated live balances, Spartan paid out about2,538,199.1531 SPARTAand20,694.0594 WBNB. - That redemption was already profitable in WBNB terms relative to the original step-2 deposit, but Spartan’s stale reserve bookkeeping created a second payout path.
- The attacker re-added the amplification assets as liquidity, minting about
1,414,010.1599more LP tokens, then immediately burned them to withdraw about2,643,882.0741 SPARTAand21,555.6973 WBNB. - The cycle was repeated multiple times across the attack sequence, draining roughly 90% of the WBNB from the victim pool before the flash swap was repaid.
- The attacker used a PancakeSwap flash swap to borrow
- What the attacker needed to be true:
- Spartan had to leave direct token transfers economically visible to LP-redemption math instead of isolating redeemable reserves from ambient balances.
removeLiquidity()had to price burned LP tokens from current balances rather than from a synchronized reserve snapshot.addLiquidity()had to keep treating the difference between current balances and stale cached reserves as fresh input.- The attacker needed enough flash liquidity to make the balance inflation large relative to the pool’s existing size.
- Spartan’s fee schedule had to be batchable rather than absolute, so the attacker could split swap setup across multiple smaller trades.
- Technical anatomy worth remembering:
- This was not an oracle-manipulation exploit. The pool’s own liquidity-share accounting was the false oracle.
- The decisive mistake was letting
someone sent tokens herebecome equivalent tothese tokens now back the LP shares being burned right now. - The direct transfers were not pure donations to the pool; because reserve synchronization lagged, the attacker could cash them out once through an old LP position and then again by reintroducing them through
addLiquidity(). - The incident is a sharp AMM cousin to broader ambient-balance attribution bugs: live balances were real, but Spartan assigned them to the wrong economic claim. Put more compactly, balances stayed real but reserve-level provenance did not.
- Representative transactions and addresses:
- Main exploit transaction:
0xb64ae25b0d836c25d115a9368319902c972a0215bd108ae17b1b9617dfb93af8 - Publicly tracked attacker collection wallet:
0x3b6e77722e2bbe97c1cfa337b42c0939aeb83671 - Initial emergency Utils patch:
0x130859c0419f155d33a3b1cc4d8f5c3c815178717d56cdb685d8e9d45ebc78a0 - Buggy rollback / re-exposure transaction:
0x004447a82421d9b05fd75eca5239efdc6913d96a01fafb49a1d8ee099ec913a0 - Final patch deployment:
0xf70a35bf0de33746503002a93714397de2fa4c01c922e9793b2e036e066fca8e
- Main exploit transaction:
- Response / mitigation:
- Spartan deployed an emergency
UTILSpatch on May 3, 2021 after outside researchers reproduced the still-live exploit path against residual pools. - That first patch prevented the demonstrated drain path but also blocked ordinary liquidity removals, so it was not a durable fix.
- A later contract update briefly reverted to the buggy version, re-exposing remaining funds before the final patch was deployed roughly 40 minutes later.
- Post-incident response also required accurate pre-exploit snapshots because compensation and recovery depended on reconstructing pool ownership before the drain.
- Spartan deployed an emergency
Why this incident matters
- It is a clean example of LP-share redemption becoming exploitable because a protocol priced burn claims from ambient balances rather than from reserve state it had explicitly recognized.
- It broadens the exploit corpus beyond lending-vault and reward-accounting provenance bugs into AMM liquidity math: the same
balance is up, therefore this claim owns itmistake can live inside pool-share issuance and redemption. - It is also a reminder that emergency patching can itself become part of the incident record: Spartan briefly moved from
live exploittotemporary fixback tore-exposedbefore finally landing a stable repair. - The sharper preserved healthy-side boundary is that
tokens are sitting in the pool right nowshould not have been treated asthese tokens already belong to the LP shares being burned right now. Spartan needed a stronger reserve-adoption boundary thancurrent balance happens to be larger.
Pattern tags:
- Ambient-balance liquidity-share inflation: live pool balances were treated as already-recognized LP backing.
- Reserve-adoption failure: unsolicited balances became redeemable before Spartan’s own reserve ledger had actually absorbed them.
- Double-counting sequence: the same amplification balances first improved an old LP burn payout and then reappeared as fresh liquidity input because cached reserves stayed stale.
- Design companions: balance-delta-provenance-failure, unexpected-balance-quarantine-and-explicit-recognition-discipline, reserve-backed-claim-truth-security-object, and sync-harvest-and-cache-write-authority-discipline.
Why the exploit worked
- Spartan’s
removeLiquidity()path priced LP burns from current ERC-20 balances instead of from a protocol-owned reserve snapshot, so direct transfers could immediately inflate what old LP shares appeared to own. - The protocol never forced a clean
ambient inventory -> recognized reserveadoption step before that burn math ran. - Cached reserve bookkeeping then lagged enough that the same attacker-supplied balances could be reinterpreted again as new liquidity on the next
addLiquidity()call. - The pool was large enough, and flash liquidity deep enough, that misattributed balances could be converted into real WBNB before operators or patches shut the path down.
Detection and audit pivots
- Search AMM mint / burn paths for any place where LP entitlement is derived from raw
balanceOf(pool)rather than from synchronized reserve variables or an explicitly journaled reserve object. - Ask what exact event is allowed to move balances from
ambient inventoryintorecognized pool backing. If the answer is effectivelyany token transfer before burn, the pool is too trusting. - Model the full two-step path, not only the first over-withdrawal: can attacker-added balances improve one burn payout and then later reappear as fresh deposit input because reserve caches never cleanly adopted them?
- Re-test pool accounting under direct transfers, fee-on-transfer assets, manual sync helpers, and stale-cache conditions; provenance failures in AMMs often hide at the seam between
live balanceandrecognized reserves. - Treat emergency patches and rollback procedures as part of the security boundary. If a hotfix can briefly restore the old accounting path, remaining pool inventory is still economically live.
Failure-prevention checklist
- Burn LP tokens against one canonical recognized-reserve object, not against raw token balances that any outsider can inflate with direct transfers.
- Keep direct transfers economically inert until one explicit reserve-adoption step decides what they mean.
Tokens arrived at the poolshould not by itself improve LP redemption value. - Make mint, burn, quote, and reserve-sync paths consume the same reserve ledger. The same balance should not help an old LP burn first and then reappear as fresh
addLiquidity()input later. - Reconcile reserve caches atomically around liquidity removal. If a burn path updates only decremented cached reserves while raw balances remain higher, the protocol is already publishing two incompatible pool states.
- Add invariants that replay the exact hostile sequence Spartan exposed: mint LP, direct-transfer both assets, burn, re-add the same balances, and try to burn again. The second monetization should fail.
- Treat
sync, cache writers, and accounting helpers as solvency code. If those helpers can be skipped, delayed, or rolled back, they belong inside the same review scope as swap and LP math. - Keep emergency patch rollout narrow and monotonic. Hotfix deployment, rollback permissions, and upgrade sequencing should not allow a buggy accounting path to come back online over still-live pool inventory.
- Stress-test low-liquidity and fee-bypass behavior together. If the attacker can cheaply batch into the setup state, a provenance bug that looks marginal in one swap may still become treasury-scale over several loops.
Audit prompts
- What exact state do LP tokens redeem against here: current ERC-20 balances, cached reserves, or another journaled reserve object?
- Can anyone improve a burn payout merely by transferring assets into the pool before
removeLiquidity()runs, even if those assets never passed through the official liquidity-add path? - After a burn, could the same attacker-supplied balances still appear as fresh
actualInputon the next liquidity-add call because cached reserves were only decremented, not fully resynchronized? - Which function is the sole authority allowed to adopt ambient pool balances into recognized reserves, and can mint / burn logic bypass that authority by reading raw balances directly?
- If the protocol supports manual
sync-like helpers, fee-on-transfer assets, or upgradeable math modules, do all of those paths preserve one reserve story under direct-transfer stress? - Under what conditions can batching around fee logic make it materially cheaper to reach the dangerous state, and was that economic setup tested alongside the accounting invariant?
- If operators deploy an emergency patch, what exact permissions or upgrade rails could accidentally restore the buggy math while meaningful inventory is still on the line?
- If a reviewer watched only transfer logs, what evidence would distinguish
pool received a donationfrompool recognized new redeemable LP backing?
Lessons that should already be standard
- AMM balances are not self-authenticating reserves.
The pool holds more tokens nowwas never the same thing asexisting LP shares earned more backing. - LP-burn math can become its own false oracle. A venue does not need an external price feed bug to print withdrawable value if its internal reserve provenance is wrong.
- Reserve adoption is a first-class control surface. Direct transfers, cache writes, sync helpers, and burn pricing all have to agree on when ambient inventory becomes canonical pool backing.
- Double-counting is often the real payout multiplier. The first over-withdrawal mattered, but Spartan became devastating because the same balances could then re-enter as fresh liquidity and be monetized again.
- Upgrade and incident-response paths are part of the exploit surface. A temporary patch that can revert to buggy accounting while reserves remain live is not merely an ops footnote.
- Flash loans scaled the exploit, but they did not create it. The root bug was that Spartan credited the right balances to the wrong LP claim before reserve accounting had actually adopted them.
sync lateris not a safety argument when users can already burn, mint, or quote against the unsynced state now.
Internal linkages
- Core recurring pattern: balance-delta-provenance-failure — Spartan is the AMM
ambient balances treated as already-recognized LP backingcousin, where balances stayed real but reserve-level provenance lagged behind what LP-burn math assumed. - Positive-design companion: reserve-backed-claim-truth-security-object — the healthier question is what exact reserve object LP shares were supposed to redeem against, and when unsolicited balances should become part of that object at all.
- Publication / adoption companion: sync-harvest-and-cache-write-authority-discipline — the sharper follow-up is why one balance change became spendable LP-burn truth before Spartan had committed that change through one authoritative reserve-sync path.
- Adjacent comparison: allbridge-core-bnb-chain-busd-usdt-virtual-balance-withdrawal-mismatch-drain-2023-04-01 — another pool-accounting exploit where LP-style withdrawal math corrupted the next state, though Allbridge’s failure lived in virtual-liability retirement rather than ambient-balance inclusion.
- Adjacent comparison: balancer-sta-deflationary-token-gulp-pool-drain-2020-06-28 — both incidents let real token balances and economically recognized reserves drift apart, but Balancer mismeasured what arrived from a fee-on-transfer token while Spartan misattributed unsolicited balances as redeemable LP backing.
Control surface
-
The leverage sat in Spartan’s
UTILSmath, especially the decision to letcalcLiquidityShare()and later liquidity-add accounting consult live pool balances without first proving those balances belonged to the LP position being redeemed. -
The deeper control surface was reserve adoption itself: Spartan lacked one canonical step that said
these balances now belong to the pool's recognized reserve ledger, so ambient inventory could outrun the accounting object LP shares were supposed to reference. -
Spartan did not merely suffer from
flash loans. It let ambient balances masquerade as recognized pool backing, then let the same balances re-enter as fresh liquidity after the first redemption; the balances were real, but the protocol credited them to the wrong LP claim before reserve accounting had actually adopted them. -
Sources:
-
Last reviewed: 2026-07-24 UTC