Uniswap v1 のしくみメモ

## 背景

DeFi での取引の損益計算するときに, Uniswap の仕組みを知り, より正確な損益計算したいと思いました. LP トークンの量の計算とか, slippage とか.

とくに Uniswap は最古? の DeFi サービスであり, 現在各種 DeFi サービスの基盤としても使われています(いまは v2 が利用されています).

Uniswap = Unified Swap の略でしょうかね?(アイコンはユニコーンですが) 


## v1

https://hackmd.io/@HaydenAdams/HJ9jLsfTz?type=view

https://github.com/Uniswap/uniswap-v1

vyper(python)で書かれていていいですね.

https://github.com/Uniswap/uniswap-v1/blob/master/contracts/uniswap_exchange.vy

swap 自体のコントラクトのコードはコメントなどいれて 500 行くらいと, そんなに多くありません. 実ロジックとしては ~150 行くらいでしょうか.

v1 whitepaper にあるように, コントラクト自体は minimalistic なデザインとなっています.

コアとなるのは,

* ETH <-> ERC20 を手数料を考慮して交換(swap)
* 流動性ペアの提供, 開放(addLiquidity, removeLiquidity)

だけです.

UI などコントラクト以外のところは別途で提供されています.

### 新しいペアの作成

https://hackmd.io/@HaydenAdams/HJ9jLsfTz#Creating-Exchanges

`uniswap_factory.vy` に実装されています.

ペアにはそれぞれのトークンを保存するくらいのミニマムな感じになっています.
ガス代を払えば誰でも作れるようになっています. 

### 流動性の提供　

https://github.com/Uniswap/uniswap-v1/blob/master/contracts/uniswap_exchange.vy#L48

最初に流動性を提供(プールを作る)するかどうかでロジックが変わります.

### 流動性の開放

https://github.com/Uniswap/uniswap-v1/blob/c10c08d81d6114f694baa8bd32f555a40f6264da/contracts/uniswap_exchange.vy#L83

開放時では, LP token を減らし, LP のシェアに応じた ETH, ERC20 をユーザーの wallet address に戻す, で処理しています.

### swap 時の fee

https://hackmd.io/@HaydenAdams/HJ9jLsfTz#ETH-%E2%87%84-ERC20-Trades


ERC20 <-> ETH で各方向で 0.3% の手数料がかかります.
(取引量 + 0.3% ではなくて, 取引量 - 0.3% で計算しているのでちょっとやっかいです)

手数料分は, LP token を増やすことなく(流動性ペアを追加することなく), pool のトークンに追加されていきます. 
実際には, ETH -> ERC20 の場合, ETH の残高に fee ぶんを減らしたものを反映することで対応しています(したがって計算がちょっとややこしい. 得られる ERC20 も fee を引いた ETH に対して計算される).

これにより, LP token の価値が swap を経るごとに高まっていき, LP token を開放したときに金利(手数料収入)がついて返ってくる感じになります.
(したがって時価で損益計算するときは, ちょっとやっかいである)


ERC20 <-> ERC20 を swap する場合, (ERC20 -> ETH) -> (ETH -> ERC20) となりそれぞれに 0.3% の手数料がかかります.
(実効は上の fee ぶん引いたので交換するためか 0.5991 % になるもよう)


### swap 時の価格の決定

Pool 内のトークン量の割合と, 手数料(0.3%)を考慮して決めています.　e.g.

https://github.com/Uniswap/uniswap-v1/blob/master/contracts/uniswap_exchange.vy#L106

```py
# @dev Pricing function for converting between ETH and Tokens.
# @param input_amount Amount of ETH or Tokens being sold.
# @param input_reserve Amount of ETH or Tokens (input type) in exchange reserves.
# @param output_reserve Amount of ETH or Tokens (output type) in exchange reserves.
# @return Amount of ETH or Tokens bought.
@private
@constant
def getInputPrice(input_amount: uint256, input_reserve: uint256, output_reserve: uint256) -> uint256:
    assert input_reserve > 0 and output_reserve > 0
    input_amount_with_fee: uint256 = input_amount * 997
    numerator: uint256 = input_amount_with_fee * output_reserve
    denominator: uint256 = (input_reserve * 1000) + input_amount_with_fee
    return numerator / denominator
```


## front running

HFT(high frequency tracing)の世界で有名な(?)フロントランニングが可能とあります.

あまり詳しい説明はありませんが, arXiv あたりの論文をあさると DeFi で front running する手法の論文があるのでそれを詳しく読むとなにかわかるかもです!
(マイナーがマイニング手数料多めにはらって, 価格を釣り上げ/吊り下げするとか mempool にあるブロックのキューの順序を入れ替えるとかっぽい)

## v2(おまけ)

Uniswapのソースを見た時のつらつらとした感想
https://qiita.com/Akira-Taniguchi/items/71767d2351da5e565667

ありがとうございます.

### プールシェア(LP token)の量

最初の LP の量の計算がことなります

プールに流動性を提供したとき, 割り当てられる LP トークン量 s_minted は, 提供するトークンを x とすると,

```
s_minted = (x_deposited / x_starting) * s_starting
```

で計算されます.
`(y_deposite / y_depsoted)` の比率は `x_deposited / x_starting` とほぼ同じはずなので一方のトークンで計算しているのですかね.

プールをつくるとき(一番最初にプールに流動性を提供するとき)は `x_starting` と `s_starting` はゼロになるので, 初回は計算方法が変わります.

### 初期プールシェア

v2 では, sqrt をとって初期のプールシェア(LP share)を計算します. 

```
s_minted = sqrt(x_deposited, y_deposited)
```

whitepaper では, これは LP の価値がどの時点でも, 初期のトークン間の価値の比率とは非依存になるようにするのを狙っているとあります.

1 XYZ = 100 USDC のとき,

200 USDC と 2 XYZ を deposit すると 20 LP tokens(shares) が得られます. 
(pool の総価値は 400 USDC なので, 1 LP token あたり 20 USDC になります)

1 XYZ = 400 USDC のとき,

800 USDC と 2 XYZ を deposit すると 40 LP tokens が得られます. 
(1 LP あたり 40 USDC になります)

いろいろ流動性が提供されたり取り除かれたりなどで時間がたつと大体初期の割合に非依存になるのかな...? :thinking: 

## 気になる点

どんどん accumulate していくと計算誤差がでそうな気がするがどうでしょうか.

## TODO

* [ ] v2 のソースコードを調べる