By the end of this lesson you can

  • →Name each market maker generation and the capability it added
  • →Identify the assumption each generation depends on, which is its failure surface
  • →Explain why a pricing function reading a wrong balance quotes a wrong price
  • →Check whether a specific token violates a specific pool's assumptions

Junior · enrolled learners

This lesson opens with Balancer and the deflationary token, 28 June 2020.

What happened
Balancer's weighted pools generalized constant product pricing to several assets at arbitrary weights, and priced trades from the balances the pool contract recorded. On 28 June 2020 an attacker took a $23M flash loan of ether from dYdX, converted it to wrapped ether, and swapped back and forth with STA, a token that charged a one percent fee on every transfer, taken from the recipient. Each time STA went into the pool, the pool received one percent less than its accounting credited, and around twenty-four swaps drained the pool's actual STA holding to a single wei. The attacker then called the pool's gulp() function, which resets the recorded balance to the actual one, so the pool now recorded almost no STA and priced it as extremely scarce. Each further swap sent one wei of STA in: the pool credited the wei to its record before collecting it, the transfer fee meant nothing arrived, and gulp() reset the record to one wei again. Against deposits it never received, the pool released wrapped ether priced as though STA were nearly unobtainable, and the same procedure drained its WBTC, SNX and LINK. Losses were about $450,000, and only pools containing STA and STONK, both fee-on-transfer tokens, were affected.
The decision point
The pricing function was correct and the balances it read were not. Every market maker generation buys its advantage with an assumption, and the constant product family assumes that transferring an amount moves that amount. A token class existed for which that was false, so the pool credited deposits it never received and priced from a number that did not describe what it held. Nothing in the formula was wrong; the input to it was.
Recorded loss
$450,000

What you will be able to answer

  • →What does each AMM generation buy its advantage with?
  • →Why did a correct formula produce a wrong price?
  • →Which token classes break constant product accounting?
  • →What is the check before using a pool?

Orientation and Year One are open: anyone can read them without an account. From Year Two onward the lessons are for enrolled learners, because progress through the later years only means anything if it is tracked against a record.

It is free. We do not sell the list and there is nothing to buy at the end of it.

Terms used here

Sources and review

Confidence high·Volatility low·Reviewed 2026-08-06·Owner unassigned

Contested

J302-01 derives constant product pricing and J303 covers liquidity provision including concentrated positions. This lesson covers the generations as venue architecture and the assumption each one carries. Keep the split and do not re-derive the invariant or the impermanent loss formula here.

Accounts of the loss differ with the assets counted and the prices used. The Block put it at about $450,000, itemized as about $452,000 across ether, WBTC, LINK and SNX, while PeckShield's loss analysis values the stolen assets at about $523,616. This lesson uses The Block's figure. The mechanism is not disputed and is the durable part.

The attack transaction was mined at 18:03 UTC on 28 June 2020, per PeckShield and the transaction on Etherscan. Balancer's and 1inch's write-ups and The Block's report are dated 29 June, which is why some accounts give that date.

Early write-ups describe the mechanism with different emphasis. 1inch's account says only that the pool received one percent less STA than expected on each swap. Balancer's post-mortem and PeckShield's analysis add the gulp() call that synced the record down to the real balance, which is what made one wei of STA price as nearly unobtainable. This lesson follows Balancer and PeckShield.