1/ Finished a preliminary deep dive into the Kyber exploit, and think I now have a pretty good understanding of what happened.
This is easily the most complex and carefully engineered smart contract exploit I've ever seen...
2/ First thing to note is this exploit is specific to Kyber's implementation of concentrated liquidity
There's no reason to believe that other reputable concentrated liquidity dexes, like Ambient or Uniswap, are at risk from this exploit. (Though Kyber forks obviously are)
3/ We'll look at the first pool the attacker drained on Ethereum, ETH/wstETH. Though all of the other pools followed a similar strategy.
The attack on this pool can be found in this transaction:
4/ This particular transaction drained three separate pools, but for now we'll only look at the ETH/wstETH drain.
The process to drain each pool is independent, so we only need to understand one. Each pool exploit is carried out in a flash loan to manipulate price and liquidity
5/ In the wstETH/ETH case, the exploit started with a flash loan of 10 thousand wstETH (worth $23 million)
The next step was to swap 2800 wstETH ($6mn) into the pool to push the price from 1.05 ETH to 0.0000152. Unlike most flash loans, the reason wasn't to manipulate an oracle
6/ The point was to move the pool price to an area of the concentrated liquidity curve, where there was 0 existing liquidity. Since the attack relied on an extremely precise manipulation of Kyber's concentrated liquidity math, this basically created a "fresh canvas".
7/ The exploit mints 3.4 wstETH of liquidity in the price range of 0.0000146 to 0.0000153. Then the exploiter burns 0.56 wstETH of liquidity for some reason (probably to make the subsequent numerical calculations line up perfectly).
8/ The exploit executes two swaps around this price. Now remember there is no other liquidity here. In the absence of a numerical bug, someone doing this would just be trading back and forth with their own liquidity, and all the flows would net out to zero (minus fees)
9/ However what happens is an infinite money glitch.
The first swap is the exploiter selling 1056 wstETH for 0.0157 ETH pushing the price down to 0.0000146 (just barely below his liquidity's price range).
10/ The second swap is in the opposite direction. The exploiter buys 3911 wstETH from the pool for 0.06 ETH pushing the price of the pool back up to 0.00001637 (a bit above the upper edge of his liquidity range.
11/ And that's it. The exploit is complete. The pool is drained
Notice how in the second swap he received more money than he paid in the first (3911 vs. 1052 wstETH). Remember the only liquidity here is the ~3 wstETH he minted at the outset. Where did the extra money come from?
12/ This is where things get really tricky. It took me hours to even figure out what happened.
The first clue came from looking at the resting state of the pool's liquidity at the end of the second swap. Notice anything weird?
13/ In Kyber the value of the resting in-range liquidity is in the `baseL` poolData variable.
Shouldn't this be 0? Remember the second swap ended at a price *outside* the attacker's liquidity range. There should be no liquidity here.
14/ Somehow the exploit was able to make the pool think it had more liquidity than it actually did at these price ranges. And now we understand where the infinite money glitch comes from
If the pool thinks there's more liquidity than there actually is, it overpay for large swaps
15/ The second clue comes from comparing the call trace stack for the first and second swap.
In Kyber when tick boundaries are crossed, the `updateLiquidityAndCrossTick` is invoked. It adjusts the curve's liquidity value based on the LP range positions at that tick
16/ The second swap handles it correctly. It starts by being out of range above the LP position. updateLiquidityAndCrossTick is called once when the price moves in range. Then the swap ends at a price below the LP position. So it's called again as the price moves out of range
17/ However in the first swap updateLiquidityAndCrossTick is *never* called. Remember the curve price started in range, then the swap moved the price until it was just slightly out of range (carefully note "slightly")
It should have been called, but never was on swap 1
18/ And now the pieces are falling into place.
When an LP position moves out of range updateLiquidityAndCrossTick is responsible for removing that liquidity from the curve. When it moves back in range, it adds the liquidity back into the curve.
What would happen if it broke?
19/ If you could get it not to invoke when your LP position moved out of range, liquidity would never be removed from the curve. You've now tricked the pool into thinking it has more liquidity then it does.
20/ But when you move back in range, you make sure it invokes. And the liquidity gets added back in, even though it was never removed the first time. You're double dipping! The pool is double counting the liquidity from the original LP position.
The infinite money glitch
21/ How was the exploiter able to bypass the call to updateLiquidity on swap 1? This is where things get really technical
In concentrated liq AMMs, swaps are calculated as a series of steps. At each step you have to determine if you'll reach a tick boundary or exhaust the swap
22/ Kyber runs this swap step, and it checks to see if the ending price of the step is the same as the next tick price. If it is isn't it assumes the swap exhausted, it didn't reach the tick boundary and `updateLiq` doesn't need to be called.
23/ However note the check is inequality, not directional comparison. If you were somehow able to execute a swap step and get the price to end *outside* the tick boundary, the check would fail and `updateLiquidity` would never be called, even though you crossed a tick boundary
24/ Normally this shouldn't happen because `computeSwapStep` function first calculates an upper limit of the amount that can be swapped before reaching the tick
If that amount is less than remainder of the swap, it confidently predicts the ending price will *not* reach the tick
25/ However in this case something funny happened. calcReachAmount predicted the swap quantity would not reach the tick boundary, yet somehow the ending price ended just slightly *beyond* the tick boundary.
26/ And that's because the "reach quantity" was the upper bound for reaching the tick boundary was calculated as ...22080000, whereas the exploiter set a swap quantity of ...220799999
That shows just how carefully engineered this exploit was. The check failed by <0.00000000001%
27/ This has to do with how Kyber implements the quantity calculation (for the upper limit until a bounds is hit) and the price change. The two use very slightly different arithmetic.
28/ In a very carefully controlled and precisely engineered case, the bounds check will tell you that anything less than X swap qty will keep you inside the tick price.
But the parallel calculation price change calculation will apply X swap qty and wind up outside the tick bound
29/ I remember being endlessly paranoid about this when writing the smart contracts for @ambient_finance
For that reason, our code includes explicit checks on every step that were explicitly inside the tick boundary if the swap quantity exhausts.
@ambient_finance 30/ The good news at least, it would be pretty straightforward to patch the existing Kyber contracts with a similar assertion on the swap step to prevent this exploit in the future.
• • •
Missing some Tweet in this thread? You can try to
force a refresh
This meta-study claims to overturn the long established pattern that moderate drinkers have lower mortality than abstainers. After adjusting for confounders there was "no significant reduction in mortality". Very misleading...
2/ At the bottom of the chart, you see moderate drinkers have a 0.86 RR (i.e. 13% lower mortality). The 95% confidence interval is 0.83-0.88. This is extremely significant
This study then throws in a kitchen sink of confounder variables. Everything from BMI to publication year
3/ If you look at the top of the chart, after all those adjustments are made, moderate drinkers do look not quite as healthy as before. The RR moves about closer to one at 0.93 (i.e. 7% lower mortality)
However even after all that, moderate drinkers *still* look healthier
1/ A thing I keep hearing more and more is that RFQs are always better for swappers. This is wrong
We can all agree that the RFQ provider has a free option. The value of that option comes at the expense of someone. Most people think it comes entirely from the passive LPs. Not so
2/ The simple mechanics behind pre-chain RFQs is that the RFQ provider is given "first look" at the swap intention. The RFQ provider is given the option to fill at a better price than the indicative on-chain AMM price. If the RFQ provider passes the swap is routed to the AMM.
3/ Surely this must be better from the swapper's perspective. The swapper can only get price improvement, and the worse case is just the base case (swap against the AMM), without the RFQ. Right?
Wrong. The key distinction is between indicative AMM price and true arrival price
Recently it’s become fashionable in crypto circles to be critical of that app-layer governance tokens. Builders are encouraged to build public good and carefully think twice whether adding a token is really necessary
This is psyops…
2/ At best the notion is misguided. At worse, it’s proffered by L1 bag holders, cynically trying to retain all value accrual at the chain layer, leaving the app layer out in the cold
Regardless it’s near impossible to build sustainably decentralized apps without a token
3/ Anything beyond the simplest protocol is going to require some level of governance that can’t be reduced to an autonomous algorithm.
For example lending protocols have to constantly and intelligently update collateralization parameters as market conditions shift.
1/ There's a lot of debate and speculation on crypto Twitter about how Alameda managed to lose so much money. But this may give a false impression of ambiguity in other aspects
There's one thing that's unambiguously and indisputably true. SBF and Alameda committed fraud. Period.
2/ For anyone who's been close to the chaos, this will seem so obviously true that it may seem laughable that I even have to make a thread to drive the point home. Not a single credible voice in the industry would tell that this wasn't naked, malicious and criminal fraud
3/ But Sam is engaging in a coordinated attempt to whitewash his crimes by painting a picture. It's a picture that many in the finance industry will quickly pattern match to. A picture of a stereotypical over-leveraged over-confident hedge fund where risk gets out of control
1/ Very rough and speculative sketch of what I increasingly think happened at FTX as more info comes out…
The central question is where did the money go? Yes malfeasance and fraud is necessary, but at one point in the cycle cash actually has to go out the door
2/ At 3AC we knew it went to losses on leveraged long positions. At Lehman it went to bad mortgages. At Enron it went to boondoggle mega projects.
Some FTX money obviously went to seed rounds in bad or illiquid projects. But AFAICT nowhere near enough to explain the hole.
3/ Let’s rewind to 2017/18. Alameda the prop firm is a big fish in a little pond. They’re mediocre traders (there’s a video of SBF bragging about how their quoter latency is down to something like two seconds). But crypto is still a weird asset class that most won’t touch
1/ IMO settling with the Mango exploiter was the correct move. It's very unlikely the exploiter would have been criminally prosecuted, even if they were doxxed.
To understand why, it's important to distinguish "computer fraud" from "securities fraud".
2/ The vast majority of hackers (crypto or otherwise) are prosecuted under the Computer Fraud and Abuse Act. Computer fraud is very easy to prosecute, and US Attorneys are very comfortable bringing cases, having a clear template for prosecution.
3/ But... computer fraud requires some type of "breach" or "unauthorized access" to a computer system
SCOTUS clarified in Van Buren that simply using the authorized part in an unauthorized way is insufficient. You have to explicitly touch a part of the system that is off-limits