価格とは何ですか#
Uniswap は、分散型取引所として、価値の発見機能を担っており、ユーザーや他のスマートコントラクトは Uniswap を通じてトークンの価格を取得することができます。Uniswap は、オンチェーンのオラクルの役割も果たしています。
例えば、現在のトレードプールには 1 Ether と 2000 USDC があるとします。その場合、Ether の価格は 2000/1=2000 USDC となります。逆に、USDC の価格は 1/2000 Ether となります。したがって、価格は比率です。ただし、スマートコントラクトは小数をサポートしていないため、Uniswap のコードでは新しいデータ型を使用して価格を格納する必要があります。新しいデータ型については後ほど詳しく説明します。
TWAP 価格メカニズム#
ただし、瞬時のトークンの数量比率だけを使用して価格を決定するのは安全ではありません。価格オラクルの操作リスクが存在します。Uniswap はフラッシュローン機能を提供しているため、フラッシュローン取引の瞬間にトレードペア内のトークン残高が激しく変動することがあります。UniswapV2 では、この問題を解決するために TWAP(Time Weighted Average Price)という時間加重平均価格のオラクルメカニズムが採用されています。
具体的な仕組みは以下の通りです:
- 過去の 24 時間のうち、最初の 20 時間の価格が 20 ドルであり、最後の 4 時間の価格が 10 ドルであるとします。その場合、TWAP=($20×20 + $10×4)/ 24 = 18.33 ドルとなります。
- 過去の 24 時間のうち、最初の 1 時間の価格が 10 ドルであり、最後の 23 時間の価格が 15 ドルであるとします。その場合、TWAP=($10×1 + $15×23)/ 24 = 14.79 ドルとなります。
つまり、TWAP の計算式は以下のようになります。ここで、T は時間帯を表し、P は対応する時間帯の価格を表します。
UniswapV2 の契約では、分子部分のみが記録されます。つまり、各時間帯の価格に単価を掛けた値の合計が記録されます。分母部分はユーザーが自分で管理する必要があります。トレードペアには 2 つのトークンがあるため、2 つの値が記録されます。
通常、私たちは特定の時間帯のトークン価格に関心を持つだけです。これが TWAP の過去の価格の計算式です:
計算の基準時点を T4 から開始する場合、実際の計算式は以下のようになります:
先ほど述べたように、契約内には分子の合計値を追跡する変数があります。例えば、token0 の追跡計算価格変数 price0Cumulativelast を使用します:
この変数は、過去のすべての時間帯の合計を記録しています。したがって、私たちは T4 からの部分だけを取得し、計算方法も非常に簡単です。T3 の時点で price0Cumulativelast 変数のスナップショットを取得し、最新の T6 の時点で再度取得し、その差分が T4 から最新の時間帯内の token0 の計算価格となります。
私たちはまた、最近のウィンドウの持続時間を自分で管理しています。つまり、T4+T5+T6 です。
したがって、この期間の TWAP 価格を計算することができます:(price0Cumulativelast-UpToTime3)/(T4+T5+T6)
UniswapV2 の実装#
具体的には、Uniswap の実装では、各トレードペアごとに price0Cumulativelast と price1Cumulativelast という 2 つの変数を管理し、先ほど説明した_update
メソッド内で合計を行います。具体的なコードは以下の通りです:
- まず、現在のブロックのタイムスタンプ
blockTimestamp
を取得します。 blockTimestampLast
からの経過時間timeElapsed
を計算するために、blockTimestamp
とblockTimestampLast
を引き算します。timeElapsed
が 0 より大きい場合、つまり次のブロックに入った場合にのみ、価格 × 時間帯を累積します。- ここでの価格計算には
UQ112x112
という特殊なフォーマットが使用されています。小数点を記録するためのものであり、後ほどこの最適化について詳しく説明します。 - 累積が完了した後、
blockTimestampLast
を最新のブロックのタイムスタンプに更新します。
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
...
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
blockTimestampLast = blockTimestamp;
...
}
TWAP の潜在的な問題#
- 時間加重平均価格の計算により、攻撃者の操作コストが増加します。複数のブロックを連続して制御する必要があるため、攻撃コストが非常に高くなります。
- 価格が急激に変動する場合、時間が加重要素として存在するため、オラクルの価格は短期間で価格変動を反映することができず、むしろ古い価格を提供することになります。特に市場が激しく変動する場合、Uniswap の価格と外部市場との間に大きな差が生じる可能性があります。
- TWAP オラクルは依然としてオフチェーンのタイマートリガーに依存しており、メンテナンスコストと中央集権化の問題が存在します。