Back to Blog

#3: KyberSwap Incident: Masterful Exploitation of Rounding Errors with Exceedingly Subtle Calculations

Code Auditing
February 10, 2024

On November 23, 2023, there were a series of attacks targeting KyberSwap. These attacks resulted in a total loss of over $48 million. The fundamental issue originated from incorrect rounding direction during KyberSwap's reinvestment process. This subsequently led to improper tick calculations and, ultimately, to double counting of liquidity.

We've released an extensive report, "Yet Another Tragedy of Precision Loss: An In-Depth Analysis of the KyberSwap Incident", which delves into the particulars of the event. For a deeper understanding, you are encouraged to consult the full analysis. Below, we provide a succinct introduction to this incident, highlighting it as one of the top ten security incidents of 2023.

Background

KyberSwap is a decentralized automated market maker (CLAMM) platform. To meet the market demand of the concentrated liquidity, KyberSwap Elastic is launched based on Uniswap V3, with several improvements including the reinvestment curve to enable the auto-compounding of the liquidity provision yields.

1. Tick and Square Root Price

Tick in Uniswap V3-like CLAMMs is used to mark the price in a discrete manner so that the LPs can provide liquidity within a fixed range instead of the entire range (hence the term "concentrated").

Liquidity can be put in a range between any two ticks (which need not be adjacent), i.e., a pair of tick indices (a lower tick and an upper tick). Specifically, the price of each tick (at an integer index i) is defined as follows:

In practice, the square root price (denoted as sqrtP or sqrtPrice) is used:

It is also possible to compute the current tick based on the current square root price:

Obviously, while only a single square root price is calculated for a given tick, multiple square root prices may point to the same tick. For a more detailed explanation, please refer to the documents of Uniswap V3 and KyberSwap.

2. Reinvestment Curve

Uniswap V3-based CLAMM suffers from the pool utilization of LP fees and significant gas fees required to reinvest. Hence, KyberSwap adopted reinvestment curve to address the problem.

The key to the reinvestment curve is that the fees collected in each swap is accumulated as additional liquidity in the pool as the reinvestment liquidity within an infinite range. The reinvestment tokens are minted to the LPs and the reinvestment liquidity accumulated is allocated to the LPs accordingly. Besides, the reinvestment liquidity also participates in the swap and price calculation process.

The corresponding code for the calculations introduced above is shown in the computeSwapStep function in following code snippet of the corresponding pool.

It should be noticed that due to the reinvestment liquidity, the liquidity in this function is a sum of two components: baseL for the base liquidity, and reinvestL for the accumulated liquidity for the reinvestment.

3. Swap in KyberSwap

The implementation of the swap function of the KyberSwap's pool discussed earlier can be abstracted as the below diagram:

The crucial logic pertaining to the tick calculation resides within the swapping while loop, as highlighted by the blue rectangle. Specifically, the principal logic involves the computeSwapStep function and the _updateLiquidityAndCrossTick function. The former calculates key states, such as input and output amounts for the given swap and nextSqrtP, while the latter handles cases when a cross-tick occurs.

Traditionally, when the price increases, we refer to this as shifting the tick right/upward; otherwise, we say the tick moves left/downward.

To better understand the vulnerability that will be discussed later, it's essential that we explore the relevant code logic of the computeSwapStep function, as illustrated in the following figure:

Specifically, the calcReachAmount function calculates the input tokens needed for the target price targetSqrtP (lines 50-57). If the usedAmount is greater than specifiedAmount, the tick isn't crossed, and nextSqrtP is calculated from deltaL (i.e., the delta liquidity, lines 59-62). deltaL is determined using the estimateIncrementalLiquidity function, and the final price nextSqrtP is calculated with the calcFinalPrice function (lines 70-79). If less input is needed, nextSqrtP is set to the next tick's price, but this case isn't used in the attack.

The steps outlined above make clear that if the tick is not crossed, the nextSqrtP returned by computeSwapStep should not be larger than the sqrtP of the next tick. However, due to the dependency of the price on the liquidty (base liquidity and delta liquidity) and precision loss, the attackers is able to manipulate the nextSqrtP to be larger while the tick is not crossed.

Vulnerability Analysis

The root cause lies in the flawed tick calculation caused by the incorrect rounding direction within the delta liquidity calculation (i.e., the estimateIncrementalLiquidity function) of the SwapMath contract (which is invoked by the computeSwapStep function). This, in turn, improperly affects the tick calculation later.

Interestingly, upon examining the comment at line 188 (highlighted by the blue rectangle), we find that deltaL is intended to be rounded up in order to round down the nextSqrtP. However, deltaL is mistakenly rounded down due to the use of the mulDivFloor function at line 189. Consequently, nextSqrtP is inaccurately rounded up.

Attack Analysis

The attackers initiated multiple attack transactions, with each transaction draining multiple pools. For the sake of simplicity, the following discussion is based on the first attack within the attack transaction.

The core attack logic consists of the following six steps:

  1. Borrowing 2,000 WETH via flash loan from AAVE.

  2. Swapping 6.850 WETH for 6.371 frxETH in the victim pool 0xfd7b. This step is used to push current tick and currentSqrtP into a location where currently no liquidity is present.

  • currentSqrtP seems to be randomly chosen by the attacker, and the swap stops at this price precisely.
  • Base liquidity (baseL) is zero after this step, but the reinvestment liquidity (reinvestL) is non-zero.
  1. Adding liquidity into the pool and then removing part of the liquidity. This step is used to control the range and total liquidity to a desired amount.
  • The tick range is chosen based on the currentSqrtP.
  • The desired liquidity for the attack could be derived from the tick range, although the corresponding calculation logic requires further exploration.
  1. Swapping 387.170 WETH for 0.06 frxETH in the pool. This step is used to manipulate the current tick so that nextTick == currentTick. Specifically, the swap in step 4 cunningly deceives the pool into believing that the tick 111,310 is not crossed. However, in reality, the currentSqrtP is indeed greater than the sqrtP of tick 111,310.
  1. Swapping 0.06 frxETH for 396.244 WETH in the pool. Note that the swapping direction is opposite compared to the previous step. In this step, liquidity is double counted to make the swap profitable and consequently drain the pool.
  1. Repaying the flash loan, and harvesting 6.364WETH and 1.117 frxETH.

For an in-depth examination, please refer to our comprehensive analysis, which includes more details with calculations and figures.

Summary

The fundamental issue in this incident stems from incorrect rounding during KyberSwap's reinvestment process, leading to inaccurate tick calculations and ultimately to double liquidity counting. This incident highlights the intricate and elusive nature of precision loss problems within DeFi protocols, posing a serious challenge to the entire community.

This 2023 attack stands out for its complexity, featuring exceptionally nuanced calculations and serving as a prime example of the many precision-related security incidents that have significantly tested the community. Moreover, after extensive negotiations with authorities, the attacker issued a provocatively toned message to the public, asserting a demand for total control over the protocol.

Sign up for the latest updates
Bitget's $387.5M Off-Chain Breach: Beyond Keys and Contracts
Security Insights

Bitget's $387.5M Off-Chain Breach: Beyond Keys and Contracts

On September 24, 2026, attackers exploited a vulnerability in a third-party security product, obtained internal credentials, and forged withdrawal commands. The resulting transfers moved approximately $387.5M from some of Bitget's operational wallets across Ethereum, other EVM networks, XRP Ledger, Zcash, and TRON; private keys and cold wallets remained intact. This deep dive summarizes the disclosed incident path and fund flow, examines rapid conversion into native assets and the ecosystem recovery response, proposes a systematic defense-in-depth framework for institutions, and explains how authorized blockchain penetration testing can validate cross-layer assumptions.

~$11.3M Lost: Multicall Router, Nostra | BlockSec Weekly
Security Insights

~$11.3M Lost: Multicall Router, Nostra | BlockSec Weekly

This report, covering 2026/09/14 - 2026/09/20, examines two security incidents with approximately $11.3M in combined losses, on Ethereum and Starknet. In the larger one, a multicall router accepted its own address as a dispatch target, so the nested call reached the Gateway module of a Safe wallet carrying the router's own already-authorized identity instead of the external caller's, and roughly 2,900 `aEthrsETH` was routed out of that wallet into an attacker-created Uniswap v4 pool. On Starknet, Nostra's oracle integration required a minimum of only one aggregated source, so when only two of the three configured price sources reached the aggregation, a manipulated thin-pool quote averaged with a normal quote to value `NSTR` at roughly $49.52, supporting approximately $3.5M of borrowing against overvalued collateral.

~$320M Lost: Liquid Network, Symbiosis Exploits | BlockSec
Security Insights

~$320M Lost: Liquid Network, Symbiosis Exploits | BlockSec

This report, covering 2026/09/07 - 2026/09/13, examines two security incidents that caused approximately $320M in losses, including the Liquid Network exploit of 2026/09/06 that the previous report did not cover. The larger was that Liquid Network exploit, where the rangeproof validation cache in Elements derived its key by hashing four fields — two of them variable in length — concatenated with nothing marking the boundaries between them, so a verdict recorded for one output was returned for another whose proof was never examined, letting the attacker create 4,000 unbacked L-BTC and peg out nearly all of them as bitcoin. On the Bitcoin route of the Symbiosis cross-chain bridge, spanning BNB Smart Chain, Ethereum and Rootstock, off-chain code that reads Bitcoin deposits took the depositor's identity from a field the depositor controls and then subtracted its fee from the deposit without checking whether the fee itself was negative, letting a 330-satoshi deposit mint `46,116,860,184.27388234 syBTC`; the pools it had to be sold through held only 11.26 syBTC, so the loss to liquidity providers and users came to an estimated 9.97 BTC (~$770K).

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit