Cetus Hack — Post-Mortem of a $223M Heist
$223 million was stolen in what might be one of the simplest hacks the crypto space has seen.
All the attacker needed to do was come knocking at the door with a high liquidity position, and they were handed the entire Cetus treasury.
While Cetus labeled the attack a “sophisticated smart contract exploit,” in truth, the exploit was incredibly simple both in technique and execution.
It earned the attacker the title of the second-largest exploit of the year, and the ninth-largest in crypto history.
Here’s how they did it.
A Flash Loan, a High Liquidity Position and a Left Shift Do The Trick
The attacker kicked off the hack by borrowing 10 million haSUI tokens through a flash swap.
They opened multiple massive liquidity positions during the attack, all within insanely narrow tick ranges — one example being [300000, 300200], as spotted by Dedaub.
Cetus is a type of AMM that uses concentrated liquidity, meaning liquidity providers pick specific price ranges (ticks) where their funds are active. Unlike traditional AMMs that spread liquidity evenly across all prices, Cetus lets you focus your liquidity on tight price windows.
Because of that, the tick range really matters.
Narrow ticks mean liquidity is squeezed into a tiny price range, which makes the liquidity parameter in the calculations that follow much bigger — keep that in mind.
The first step taken by a protocol after you open a position is calculating how much token A you need for the liquidity amount you want to provide.
And this is where things went sideways.
Because of the huge liquidity provided through the flash loan combined with that super narrow tick range — a wider range would’ve kept numbers smaller and safer — the new liquidity position basically broke Cetus’s math engine.
Here’s how.
The get_delta_a function within clmm_math.move calculates the amount of token A necessary, which is commonly used in AMMs to compute token amounts from liquidity.
The first calculation : liquidity × delta_sqrt_price produced a very large number, which is the raw product.
In AMMs like Cetus or Uniswap v3, prices and liquidity are often represented as fixed-point numbers instead of simple integers or floats.
A fixed-point number stores decimals by representing them as large integers shifted by some number of bits.
Multiplying liquidity and price differences would lose decimal precision, so an intermediate calculation is needed, called left shift.
This left shift (usually by 64 bits or some other amount) scales up the number, effectively adding extra decimal places in integer form so that when you later divide by another large number, you keep accuracy without losing fractional parts.
Shifting left by 64 bits is equivalent to multiplying by 2⁶⁴(<< 64), to retain fixed-point precision.
Left shift is calculation is then: shifted = raw product <<64
That’s where everything derails.
The Cetus AMM runs on a blockchain environment called the Move virtual machine, which uses fixed-size integers — specifically 256-bit integers — to store numbers, which can represent values from 0 up to 2²⁵⁶ — 1 (about 1.16 × 10⁷⁷).
If numbers go beyond this threshold, they are too big to be stored in the fixed amount of memory (bits) that the system has reserved for them.
Operations like addition, multiplication, subtraction, and division abort the program if they overflow or underflow, preventing errors.
However, left shift operations (<<) are special — they do not abort on overflow.
Instead, if the shifted value exceeds the 256-bit storage limit, the excess bits (Most Significant Bits (MSB) or overflow) are silently dropped (truncated), which can cause incorrect values without triggering errors.
This phenomenom is classically named MSB truncation.
Due to the extremely large raw product created during the hack, when the left shift took place, it produced a number that went beyond the maximum 256-bit number.
As the figure didn’t fit, it wrapped around modulo 2²⁵⁶.
Modulo 2²⁵⁶ means the system keeps only the lowest 256 bits, cutting off the highest bits as if they don’t exist.
This causes values larger than 2²⁵⁶ to become much smaller in storage.
In the Cetus case, the stored value became very close to zero due to this wraparound — which we will see in a minute.
Normally, as the overflow issue is collectively known, they are safeguards to stop MSB truncation, but in the Cetus hack, the safeguard failed them.
Exploiting The Overflow Check Vulnerability
The true root cause of the issue was a flawed overflow check inside the checked_shlw function, which was supposed to ensure the number was small enough to be safely left-shifted by 64 bits without overflowing.
According to Cetus, the contract uses a helper function from an open-source math library called integer-mate. This function was meant to verify that the input value wouldn’t cause an overflow when left-shifted.
Oh well — the “helper function” didn’t help much. In fact, it did the opposite.
Here’s where it broke down: Shifting a number left by 64 bits is like multiplying it by 2⁶⁴ as we have just seen. So, to keep the result within the 256-bit limit, the input must be less than 2¹⁹².
That’s the real safety threshold.
But instead of directly checking whether the value was less than 2¹⁹², the contract tried a masking trick — a quick way to detect if any of the higher bits beyond 2¹⁹² were set (meaning those bits have a value of 1, or are “turned on”) — using mask = 0xffffffffffffffff << 192 to check for overflow.
However, this mask did not work as intended.
Because 0xffffffffffffffff is only 64 bits wide, shifting it left by 192 bits inside a 256-bit space pushes all those “on” bits out of the allowed memory area, turning the mask into zero (no bits set).
Let’s simplify this.
0xffffffffffffffff in hexadecimal is a 64-bit number with all 64 bits set to 1 (that’s 64 ones in binary).
Shifting left by some number of bits means moving all the bits in the number to the left by that many positions, and filling in the empty positions on the right with zeros.
If you have: 0001 and shift left by 2 bits, you get: 0100.
What does it mean to shift a 64-bit number left by 192 bits?
If the number is stored in only 64 bits of memory, and you try to shift it left by 192 bits, you are trying to move all its bits far beyond the size of its memory.
Because the memory only has space for 64 bits, pushing the bits left by 192 positions means all the bits get pushed outside of the 64-bit space.
So the number becomes: 00000000…000 (all zeros) or simply zero.
Because the calculation results in zero, the mask detects nothing — it’s essentially all zeros after shifting.
As a result, the mask is effectively nonexistent, and the code using it is unable to catch any bits or detect any overflow risk.
TL;DR: There was no overflow safeguard.
If the mask is zero (meaning all bits are 0), then when you apply it to any number, the result will always be zero.
That means no bits get detected as set, regardless of how big the number is.
So, since the mask ended up as zero after shifting, it was completely ineffective at detecting whether any of the high bits (above 2¹⁹²) were set.
So the final calculation to determine the token A necessary was triggered, rather than stopped.
Due to truncation, the numerator of the calculation became very small (close to 0).
numerator = checked_shlw(liquidity * sqrt_price_diff)
When divided by the denominator, the quotient is also close to 0, and the result ends up being:
liquidity_token_required ≈ 1
This means that the contract mistakenly assumed only one token was required from the attacker in exchange for minting a massive liquidity position worth
Source: Cetus
According to Dedaub, the numeric values involved in the attack “are precisely calculated — the attacker utilized some existing functions in the contract to compute these, notably get_liquidity_from_a.”
The attacker exploited this by carefully crafting inputs (using internal helper functions) that pushed the intermediate values just over the unsafe threshold, triggering the silent overflow and minting disproportionate liquidity for minimal token input.
It was an exploit that was both outstandingly simple and executed with surgical precision.
The attacker repeated the same process multiple times, crafting slightly different values to avoid suspicion and maximize profit before anyone noticed. They repaid their flash swap and then walked out the door with $223 million.
Almost.
Overflow Check Vulnerability — A Long Standing Issue for Cetus?
Dedaub reviewed the multiple audits Cetus underwent and discovered that ‘an eerily similar overflow vulnerability’ had been flagged back in early 2023 during an audit of Cetus’s codebase on Aptos.
For reasons unknown, when the protocol was later deployed on Sui, it appears little effort was made to assess whether that same vulnerability might still apply.
The team may have felt secure relying on what they assumed was a standard masking technique used by others, and didn’t consider re-evaluating the underlying calculation logic — especially at such a critical point.
Apparently, OtterSec, MoveBit, and, more recently, Zellic audited the code but didn’t catch the issue. Cetus’s post-mortem also suggests that they overlooked it.
As for Dedaub, the root cause may be as simple as the library handling numerical calculations being out of scope for the audits.
Unlike Aptos, the Sui blockchain natively supports 256-bit operations, which could have contributed to the oversight.
The Aftermath & Decentralization Critics
The crypto community has been divided in the immediate aftermath of the attack. After the theft, the attacker swapped $60 million into ETH. But before they could secure the rest of their bounty, Sui validators — coordinated by the Sui Foundation — voted to refuse transactions signed by the attacker’s addresses.
Once the vote crossed the 33% stake threshold, the remaining $163 million from the exploit was effectively frozen.
A few days ago, the Sui community approved the unfreezing of the tokens locked during the Cetus hack, in hopes of a swift repayment of user losses.
It’s abundantly clear that the Cetus community applauded the asset freezing with all their being.
Meanwhile, it drew abundant criticism from much of the broader crypto space.
The move was painted as anti-decentralization. Freezing those assets was seen by some as a betrayal of one of DeFi’s core values: censorship-resistance.
On the decentralization front, at least, it’s somewhat questionable — because it wasn’t a centralized power of three deciding alone to freeze the assets, but a consortium of validators that reached consensus by surpassing the 33% stake threshold.
To what degree those validators feel free to act independently, especially while being prompted to vote by the Sui Foundation, well, that’s a question only they can answer.
As for the censorship criticism, the act speaks for itself: you can’t get more censorious on a blockchain than by freezing assets.
The debate, as always, rages on — should we, or should we not, bend the core values of blockchain when malicious actors exploit them?
For purists, “code is law,” and decentralization and censorship-resistance are absolute, no matter the circumstances.
For others, it’s acceptable to bend or even crack those principles if it means safeguarding people’s funds.
Meanwhile, the attacker remains at large, and Cetus’s promise of a 10% cut of the $60 million has yet to convince them to come out of the shadows and put on the white hat.
About us
Nefture is a Web3 real-time security and risk prevention platform that detects on-chain vulnerabilities and protects digital assets, protocols and asset managers from significant losses or threats.Nefture core services includes Real-Time Transaction Security and a Threat Monitoring Platform that provides accurate exploits detections and fully customized alerts covering hundreds of risk types with a clear expertise in DeFi.Today, Nefture proudly collaborates with leading projects and asset managers, providing them with unparalleled security solutions.Book a demo 🤝
Cetus Hack — Post-Mortem of a $223M Heist was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.