価格とは何ですか#
Uniswap はオンチェーンの分散型取引所であり、価値の発見機能を担っており、ユーザーや他のオンチェーンのスマートコントラクトは Uniswap を通じてトークンの価格を取得することができます。Uniswap はオンチェーンのオラクルの機能を果たしています。
現在のトレードプールに 1 Ether と 2000 USDC がある場合、Ether の価格は 2000/1=2000 USDC となります。逆に、USDC の価格はその逆です。したがって、価格は比率です。スマートコントラクトは小数をサポートしていないため、Uniswap のコードでは価格を保存するために新しいデータ型が拡張されています。新しいデータ型については後で詳しく説明します。
TWAP 価格メカニズム#
ただし、価格を瞬時のトークンの数量比率だけで計算するのは安全ではありません。価格オラクルの操作リスクが存在します。Uniswap はフラッシュローン機能を提供しているため、フラッシュローン取引の瞬間にトレードペア内のトークン残高が激しく変動します。UniswapV2 では、この問題を解決するために TWAP(Time Weighted Average Price)という時間加重平均価格のオラクルメカニズムが採用されています。
具体的な仕組みは以下の通りです:
- 過去 24 時間で、資産の価格が最初の 20 時間で 20 ドル、最後の 4 時間で 10 ドルだったとします。その場合、TWAP=($2020+$104)/24 = 18.33 ドルとなります。
- 過去 24 時間で、資産の価格が最初の 1 時間で 10 ドル、最後の 23 時間で 15 ドルだったとします。その場合、TWAP=($101+$1523)/24 = 14.79 ドルとなります。
TWAP の計算式は以下の通りです。ここで、T は時間帯を表し、P は対応する時間帯の価格を表します。
UniswapV2 のコントラクトでは、分子部分のみが記録されます。つまり、各時間帯の価格を単価に掛けた合計が記録されます。分母部分はユーザーが自分で管理する必要があります。トレードペアには 2 つのトークンがあるため、2 つの値が記録されます。
通常、特定の時間帯のトークンの価格に関心を持つだけで十分です。これは TWAP の過去の価格の計算式です:
計算の基準となる時間を T4 から始める場合、実際の計算式は以下のようになります:
前述のように、コントラクト内には分子の合計値を追跡する変数があります。token0 の追跡計算価格変数 price0Cumulativelast を例に取ります:
この変数は、過去のすべての時間帯の合計を記録しています。したがって、T4 から始まる部分だけを取得すれば、T4 から最新の時間帯までの token0 の計算価格となります。
私たちは最近のウィンドウの持続時間も管理しており、T4+T5+T6 となります。
したがって、この期間の TWAP 価格は次のように計算できます:(price0Cumulativelast-UpToTime3)/(T4+T5+T6)
UniswapV2 の実装#
具体的には、Uniswap の実装では、各トレードペアごとに price0Cumulativelast と price1Cumulativelast という 2 つの変数を管理し、前述の_update
メソッドで合計を計算しています。具体的なコードは以下の通りです:
- 現在のブロックのタイムスタンプ
blockTimestamp
を取得します。 blockTimestampLast
からの経過時間timeElapsed
を計算します。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 オラクルは依然としてオフチェーンのタイマートリガーに依存しており、メンテナンスコストと中央集権化の問題があります。