1. ハックの概要
- ハッカーはSui上のCetus DEXを狙い、約2億6,000万ドル(約3,600億円)相当を不正に引き出しました。盗まれた資金はUSDCにスワップされ、そのうち約6,000万ドル分がEthereumへブリッジされ、約21,938 ETHを取得しています(CryptoRank, CryptoSlate)。
2. 脆弱性のポイント
- 問題箇所は
clmm_math.move
のget_delta_a
関数内:
let (numberator, overflowing) = math_u256::checked_shlw(
full_math_u128::full_mul(liquidity, sqrt_price_diff)
);
if (overflowing) { abort EMULTIPLICATION_OVERFLOW };
- ここで期待される「128ビット×128ビットの乗算結果を正しく256ビットで得る」処理が、実際には 乗算時のオーバーフローを検出 できず、結果が切り捨てられてしまう可能性があります。つまり、「かけ算」のチェックが抜けているわけです。
- 本来は liquidity × sqrt_price_diff(128ビット×128ビット)の結果を「256ビットで正しく表せているか」を確かめる必要があります。ところがこのコードでは、その「かけ算」自体に異常が起きていても検出せずに先へ進んでしまいます。
- その後のシフト(
checked_shlw
)でしかオーバーフローをチェックしないため、すでに壊れた値がそのまま使われ、後続の価格計算(分子)が小さく誤算出されます。
例えば「ビット幅を縮小したミニチュア版」でイメージしてみましょう。
例題:8ビット型×8ビット型 → 16ビット型を期待
-
本来の計算
-
liquidity = 200
(8ビットで表せる最大は255) -
sqrt_price_diff = 200
-
期待する掛け算結果:
$$
200 \times 200 = 40,000
$$これは16ビット(最大 65,535)までは余裕で表現できます。
-
-
もし “full_mul” が壊れていて下位8ビットだけ返すと…
- 本来の 40,000(0x9C40) を 8ビットに切り捨てると、0x9C40 の下位8ビットは 0x40(=64)になります。
- つまり、本来「40,000」のはずが「64」になってしまう。
-
シフト(ずらし)でのオーバーフローチェック
- つづけて
checked_shlw
(ビットをずらす処理)で「オーバーフローしていないか」を見るとき、すでに値は壊れているので問題なしと判定される。 - 例:左に1ビットシフトしても 64→128(0x80) なので「溢れていない」と見なされる。
- つづけて
-
その後の分母・除算
-
たとえば分母(価格計算に使う値)を
100 × 100 = 10,000
とすると、-
本来 の結果:
$$
\frac{40,000}{10,000} = 4
$$ -
誤った 計算:
$$
\frac{64 ;(\text{切り捨て後})}{10,000} \approx 0.0064
$$(整数型なので「0」になるか、
round_up=true
なら「1」になる)
-
-
何が起きる?
- 本来は4トークン必要なはず の操作で、誤計算により 0〜1トークン 分しか徴収されない。
- これを利用すると、プールから「ほんのわずかなコスト」で大量のトークンを取り出せてしまい、結果として大規模な資金抜き取り(ハック)が可能になります。
まとめ
- かけ算の段階で本来必要な大きな値が「切り捨てられて」しまい、そのあとにオーバーフローチェックをしても気づかれない。
- そのまま不正な(小さすぎる)必要量でトークンを引き出せてしまうのが、このバグの本質です。
3. 悪用手順
- 攻撃者は非常に大きな
liquidity
とsqrt_price_diff
を組み合わせ、128ビット乗算を意図的にオーバーフローさせる。 - 本来はここでabortすべきところ、乗算時点のオーバーフロー検出がないため、結果が切り捨てられたまま次に進む。
- その後の
get_delta_a
が返す ΔA(プールから必要なトークン量)が実際よりも極端に小さく計算され、swap
やremoveLiquidity
呼び出しで 極小のコストで大量の資産を引き出せる 状態に。 - プールが一気に枯渇し、ハッカーは多額の資金を瞬時に盗み取ることに成功。
まとめ
-
get_delta_a
の乗算オーバーフロー検出が「シフト時のみ」だったため、壊れた値で価格計算が行われ、プールの価格影響や必要トークン量が異常に低く見積もられた - これを利用して、ハッカーはごくわずかな資金で巨額の資金を引き出し、約2億6,000万ドルを盗み取りました。