技術 約10分で読めます

Uniswapの定数積AMMをスワップ価格と集中流動性から読む

いけさん目次

UniswapはEthereum上で動く分散型取引所(DEX)で、ERC-20トークン同士をその場で交換できるサービスだ。
中央集権の取引所と違って注文板(オーダーブック)を持たず、価格は事前に登録された数式から自動で決まる。
こうした方式は「自動マーケットメーカー(AMM, Automated Market Maker)」と呼ばれ、Uniswapはその代表例として2018年から動いている。

AMMの中心にあるのが「流動性プール」だ。
誰でもETHやUSDCなどの2種類のトークンをペアでプールに預け、その対価として取引手数料の分配を受け取る。
スワップしたいユーザーは、このプールに片方のトークンを入れて、もう片方を引き出す。
プール内の2つの残高をどう動かしてよいかを決めるルールが、Uniswap V2で採用された定数積モデル x * y = k だ。

x * y = k は外部市場の価格を引いてくる式ではない。
プールに残っている2種類のトークン量から、次の取引でどこまで価格が動くかを決める式だ。
この記事では、この計算式がスワップ価格やインパーマネントロスにどう反映されるのか、そしてUniswap V3以降の集中流動性へどう繋がるのかを、数値例を交えて読み解く。

kは取引の前後で曲線を保つための値

2トークンのプールで、片方の残高を x、もう片方を y とする。
Uniswapの説明では、スワップはこの積を保つように動く。

x * y = k

たとえば ETH と USDC のプールで、ユーザーがUSDCを入れてETHを受け取るとする。
プール内のUSDCは増え、ETHは減る。
そのままだと積が崩れるので、ETHをどれだけ出せるかは次の形で決まる。

(x - Δx) * (y + Δy) = x * y

ここで Δy がユーザーの入力、Δx が受け取る出力になる。
式を解くと、入力を増やしても出力は直線的には増えない。

Δx = x * Δy / (y + Δy)

小さい取引なら、プールの比率 y / x に近い価格で約定する。
大きい取引になるほど、分母の y + Δy が効いて受け取れる Δx が伸びにくくなる。
これがAMMで見る価格影響だ。

オーダーブックでは、各価格帯に並んだ注文を最良気配から順に約定させていく。
定数積AMMには板がなく、かわりに曲線がある。
現在価格に近い場所はなだらかで、大きく買うほど曲線の奥へ入っていく。

同じプールで取引サイズを変えて計算する

具体的な数字を入れて、取引サイズで価格がどう変わるかを見る。
ETH/USDCプールに x = 100 ETHy = 200,000 USDC が入っているとする。
k = 20,000,000、スポット価格は y / x = 2,000 USDC/ETH

小さい取引として、1,000 USDCをETHに替える。

Δx = 100 * 1,000 / (200,000 + 1,000)
   ≈ 0.4975 ETH
P_eff = 1,000 / 0.4975 ≈ 2,010 USDC/ETH

スポット価格との差は約0.5%、これくらいなら誤差の範囲だ。
取引後のプールは 99.5025 ETH / 201,000 USDC、新しいスポット価格は約2,020 USDC/ETH。

同じプールで100,000 USDCを一気に投入するとどうなるか。

Δx = 100 * 100,000 / (200,000 + 100,000)
   ≈ 33.33 ETH
P_eff = 100,000 / 33.33 ≈ 3,000 USDC/ETH

スポット価格2,000に対して有効価格は3,000、50%もの価格悪化になる。
取引後のプールは 66.67 ETH / 300,000 USDC、スポット価格は4,500 USDC/ETHまで跳ね上がる。
同じプール、同じ式、同じkでも、入力サイズが流動性に対して大きいかどうかで結果が変わる。

これがDEXの「price impact」表示の中身だ。
プール深度に対して取引が小さいうちは曲線がほぼ直線に見えるが、深度の数十%を一度に動かすと曲線の急な領域まで進む。

x+y=kでは何が壊れるか

定数積は「曲線の形が選ばれた一つの式」にすぎない。
スワップ後に何らかの不変量を保つだけなら、和や平方和でも理屈は通るはずだ。
それでも x * y = k が採用されたのは、他の式では破綻する性質がいくつもあるからだ。

定数和 x + y = k を考えてみる。
スポット価格は常に1で固定され、いくら取引しても価格が動かない。
ステーブルコイン同士の1:1ペアでは便利そうに見えるが、片側を買い続けるとプールのそのトークンがちょうどゼロになる。
枯渇したら以後そのトークンは引き出せず、プールが機能停止する。

定数積は枯渇しない。
x * y = k では x が小さくなるほど y が指数的に大きくなる必要があるので、片側を完全に空にするには無限の入力がいる。
そのかわり価格は0から無限大までの全域を連続的に動く。
価格レンジが事前に決まっているオーダーブックと違って、AMMは想定外の価格でも何らかの値で取引を成立させる。

実際、Curveが採用するStableSwapは定数和と定数積の中間を取る式になっている。
ペアが1:1付近にいる間は定数和に寄せて低スリッページにし、価格が乖離したら定数積に寄せて枯渇を防ぐ。
「定数積が正解」というより、「想定する価格分布に合った曲線を選ぶ」のほうが正確な見方だ。

手数料が入るとkは減らない方向へ動く

説明用の x * y = k では手数料を無視しがちだが、実装ではここを混ぜると理解がずれる。
Uniswapの公式ドキュメントでも、k は取引後に一定または増加する値として扱われる。

手数料がある場合、ユーザーが入れた全額が出力計算に使われるわけではない。
たとえば0.3%手数料なら、入力の99.7%だけを有効入力として計算し、残りはプール側に残る。
そのため、理想化した曲線では積が同じでも、実際のプール残高で見た積は少し増える。

ここを間違えると、シミュレーションで「計算上は通るはずの取引」が本番でずれる。
ルーターや集約サービスを作る側は、単に x * y = k を解くだけでなく、手数料を引いた入力、丸め、トークンの小数桁、プールの現在残高を同じ順序で扱う必要がある。

価格影響は取引サイズをプール深度で割ると見える

定数積AMMのスポット価格は、現在の残高比で近似できる。
ただしユーザーが実際に払うのは、取引の始点から終点まで曲線を移動した平均価格になる。

数式上、有効価格は次の形で出る。

P_eff = Δy / Δx

スポット価格だけを見ると、流動性の薄いプールでも一瞬はそれっぽい価格が出る。
しかし大きめの注文を入れると、曲線上を一気に移動して約定価格が悪化する。
DEX画面で見る「price impact」は、この差分をユーザー向けに出したものだ。

表示価格だけを信じるのではなく、入力数量を入れた後の amountOut、最小受取数量、取引前後のプール残高を見る。
低流動性トークンで価格が飛ぶのは、相場観より先にこの曲線の問題として出る。

インパーマネントロスは外部価格への追随で発生する

AMMは外部市場の価格を知らない。
それでも価格が寄っていくのは、裁定取引が入るからだ。

外部市場でETHが上がったとき、AMM内のETHが相対的に安くなる。
裁定取引者はAMMからETHを買い、USDCを入れる。
その結果、プール内のETHは減り、USDCは増え、残高比が外部価格に近づく。

流動性提供者から見ると、この過程でポートフォリオが片側に寄る。
単にETHとUSDCを持っていた場合と比べると、AMMに預けたほうの価値が低くなる場面がある。
これがインパーマネントロスと呼ばれる差分だ。

「一時的」と言っても、価格が戻らないまま引き出せば損益は確定する。
手数料収入がその差分を上回るかどうかで、流動性提供の結果が変わる。

定数積プールでは、預け入れ時に対する価格比 r を使って閉形式で書ける。

IL(r) = 2 * sqrt(r) / (1 + r) - 1

具体的な値を入れると規模感が掴める。

価格比 rIL
1.0倍0%
1.25倍-0.6%
2倍-5.7%
4倍-20.0%
5倍-25.5%

倍率が大きいほど差分は大きくなるが、2倍程度の値動きでも約-5.7%になる。
この差分を取引手数料収入で埋められるかが、LPとして黒字になるかどうかの分かれ目だ。

数学的に見ると、定数積プールに預けた資産の価値は √r に比例して動く。
価格が上がるほどプールはその上がっているトークンを自動的に売る方向に動くので、HODLよりリターンが伸びにくい。
この形は、オプション市場でいう「ショートガンマ」と同じ性質になる。
言い換えれば、LPは無自覚にボラティリティを売っている。
ボラティリティが想定より大きい相場では手数料収入がILに追いつかず、小さければILも小さいかわりに手数料収入も少ない、というトレードオフだ。

実データでもこの傾向は出ていて、Topaze Blueが2021年に出したUniswap V3 LPの分析では、過半数のポジションが同じ資産をHODLしただけの場合に対して負けていた。
集中流動性は手数料効率を上げるが、レンジを狭くするほど範囲外に出る確率も上がる。
範囲外では手数料が発生せず、片側資産だけになって待たされる。
「インパーマネントロス」という名前は平均回帰を前提にしているが、価格が戻らないまま引き抜けばそのまま確定損失だ。

集中流動性は曲線を狭い価格帯へ置き直す

Uniswap V3以降で変わったのは、定数積の考え方を捨てたことではない。
集中流動性のドキュメントでは、V3とV4でもこの式はLPが選んだ価格範囲の中で適用されると説明されている。

V2では、流動性は0から無限大までの価格帯に広く置かれる。
ステーブルコイン同士のように価格がほぼ1対1で動くペアでも、使われない遠い価格帯に資本が眠る。
同ドキュメントは、V2のDAI/USDCペアでは0.99から1.01ドルの範囲に使われる資本が全体の約0.50%にすぎない例を挙げている。

V3の集中流動性では、LPが価格範囲を選ぶ。
たとえば0.99から1.01の間だけに資本を置けば、その範囲では厚い流動性として振る舞う。
範囲外に価格が出ると、そのポジションは片方の資産だけになり、手数料も発生しなくなる。

安定したペアなら狭い範囲に置くことで約定品質と手数料効率が上がる。
一方で、価格が範囲外に出やすいペアでは、ポジション管理が必要になる。
V2の「置いておく」流動性と、V3以降の「価格帯を選ぶ」流動性は、同じAMMでも運用の性質が違う。

JIT流動性は集中流動性の隙間に入り込む

集中流動性が生んだ副作用の一つがJIT (Just-In-Time) 流動性だ。
MEV (Maximal Extractable Value) サーチャーは公開メモリプールで大口スワップのトランザクションを見つけ、同じブロックの中で次のように動く。

  1. 対象スワップの直前に、現在価格を含む極めて狭いレンジへ深い流動性を追加する
  2. 対象スワップが実行され、大口分の手数料が新しく追加した流動性ポジションへ大きく配分される
  3. 同じブロックの直後で流動性を引き抜く

ポジションが板に乗っていたのは1ブロックだけなので、価格変動の影響はほぼなく、ILも実質発生しない。
受け取るのは大口スワップから配分された手数料のうち、自分のシェアに応じた額だ。

リテールLP側から見ると、ずっと流動性を置き続けていた自分の手数料収入が、その大口取引の瞬間だけJIT側に奪われる。
継続提供のリスク(IL、機会費用、ガス代)は変わらないのに、リターンの一番大きい部分だけが抜かれる構図になる。

JITは外形的にはAMMの仕組みを正しく使っているだけなので、コントラクト側で禁止するのは難しい。
Uniswap V4のhooksでは、プール作成時にスワップ前後・流動性追加削除前後にカスタムロジックを差し込めるので、同一ブロック内の追加・即引き抜きに追加手数料を課すような対策を理論上は実装できる。
一方で、JITが入るとスワップ側のスリッページは瞬間的に小さくなる。
LP収益を削るか、ユーザー側のスワップコストを下げるか、立場で利害が割れる。

amountOutMinimumは曲線外の悪化を止める

AMMの計算は、トランザクションを送った瞬間のプール状態だけで終わらない。
ブロックに入るまでに別の取引が先に実行されると、残高が変わり、同じ入力でも受け取れる数量が下がる。

そこでスワップには最小受取数量を置く。
Uniswap系の実装やルーターでは、期待出力に対してスリッページ許容幅を引いた値を amountOutMinimum のような名前で渡す。
実際の出力がそれを下回れば、取引は失敗する。

amountOutMinimum = expectedAmountOut * (1 - slippageTolerance)

この値を緩くしすぎると、前後の取引に挟まれて悪い価格で約定しやすくなる。
典型的なのがサンドイッチ攻撃で、メモリプールで自分のスワップを観測したMEVサーチャーが、直前に同じトークンを買って価格を吊り上げ、直後に売り抜けてくる手口だ。
スリッページ許容幅を5%や10%といった広い値で設定していると、その範囲のすべてが攻撃者の取り分になる。
厳しすぎる値だと普通の価格変動でも失敗するので、現在価格・板の薄さ・ガス代の優先度に合わせてユーザーやアプリ側が決める必要がある。
AMMの数式は「いまのプールならいくら出るか」を返すだけで、「どこまで悪化したら止めるか」は別レイヤーで決める設計になっている。