EP 01. Defi 合約鑒賞之 Uniswap(WIP)

前些日子和 Roger Wu 討論,要不要寫一本 Defi 合約鑒賞的 Booklet,但是我們似乎都要面臨和 Donald E. Knuth. 同樣的問題,寫的速度還沒有行業的發展快。於是我們決定採取「分而治之」之策略,為了不讓進度落後更多,不妨讓我先找一個軟柿子下手。顯然所有 Defi 項目中最軟的,就要數 Uniswap 了。

剛好 Uniswap 的創始人 Hayden Adams 參加了 最近的一期 Epicenter,在這集 Podcast 中 Hayden Adams 討論了許多幕後的故事,有興趣的朋友可以去回顧。

參考資料

做市機制 Market Maker Mechanism

Uniswap 最為突出的特性就是她的做市機制(Market Maker Mechanism)足夠的簡單。與 Bancor 和 P3D 類似,Uniswap 中也沒有傳統交易所中的 Order Book,直接在合約中用市價交易,通過合約中的儲備金來提供流動性。

所不同的是,Uniswap 的做市機制更加靈活和普適,不需要像 Bancor 一樣設計所謂的 SmartToken 和固定儲備金率這樣 多餘的設計。並且在 Uniswap 的合約中有機制去鼓勵用戶增加兩個方向儲備金來提供更多的流動性,因而不會因為市場的增長,而讓合約輕易的遭遇 Landslide,使得穩定性成為 Token 進一步發展的瓶頸。

在 Token 的的交易過程中,所維持的不變式(Invariant)也僅僅是 x*y = k

雖然 Uniswap 最近才增長起來,但實際上 x*y = k 是一個相對古老的 idea,早在 16 年 10 月,Vitalik 就在 Reddit 里提出建議,探討用這種方法建立一個去中心化交易所的可能性。隨後在 18 年的 5 月,Vitalik 又回到這個問題,並且介紹了一種防止 Front Running Attacks 的方法。

因為以太坊的交易從發出到確認中間會有延遲,所以事實上你可以搶先發送 Gas 費用更高的轉賬來利用這些半空中的交易信息截胡,從而進行無風險套利,當時包括 Bancor、Cryptokitty 甚至 Crypto Country 都曾經遭受過此類攻擊。 我在 Google 的神同事 Ivan Bogatyy 當時曾經在 Mountainview 的 Google Cryptocurrency Monthly Meetup 做過一期分享,具體可以參閱 這篇文章

其中 x 與 y 分別表示錨定的兩種 Token,在 Uniswap 里是 ETH 和另一種 ERC20,而 k 為一個定值,k 在交易的過程中不發生變化。

我們來看一下 Uniswap 的 Interface,我們看到 Buy/Sell 在 Uniswap 里這兩個操作都叫做 Swap(所以叫 Uniswap)。不失一般性,不妨我們選 ETH/DAI 交易對舉例。

我們把以太坊區塊鏈視作一個狀態機,設
xy 分別是 transcation 執行前合約中儲備的 ETH 和 DAI。
x'y' 分別是 transcation 執行結束之後合約中新的的 ETH 和 DAI。
\Delta x 為下面的 swap() 方法中傳入的 ETH。
\Delta y 是 swap() 方法兌換出的 DAI。

賣 ETH 的過程就是已知其他參數,求 Delta y,那麼顯然本次交易的價格就是 \frac{\Delta y}{\Delta x}

x*y = k invariant 指的就是:

    \[x \times y = k = x' \times y' = (x + \Delta x) \times (y + \Delta y)\]

方程解得

    \[Delta y = \frac{k}{(x + \Delta x)} - y\]

代碼如下:

eth_pool: uint256         
token_pool: uint256       
token: address(ERC20) 

@public
@payable
def ethToTokenSwap():
    fee: uint256 = msg.value / 500 
    invariant: uint256 = self.eth_pool * self.token_pool
    new_eth_pool: uint256 = self.eth_pool + msg.value
    new_token_pool: uint256 = invariant / (new_eth_pool - fee)
    tokens_out: uint256 = self.token_pool - new_token_pool
    self.eth_pool = new_eth_pool
    self.token_pool = new_token_pool
    self.token.transfer(msg.sender, tokens_out

買 ETH 的過程類似,
代碼如下:

@public
def tokenToEthSwap(tokens_in: uint256):
    fee: uint256 = tokens_in / 500
    invariant: uint256 = self.eth_pool * self.token_pool
    new_token_pool: uint256 = self.token_pool + tokens_in
    new_eth_pool: uint256 = self.invariant / (new_token_pool - fee)
    eth_out: uint256 = self.eth_pool - new_eth_pool
    self.eth_pool = new_eth_pool
    self.token_pool = new_token_pool
    self.token.transferFrom(msg.sender, self, tokens_out)
    send(msg.sender, eth_out)

流動性通證 Liquidity Token

流動性是交易所的靈魂。Bancor 最為人詬病的一點就是他的流動性不夠靈活,在合約創建之後,就沒有任何機制可以調節了。而 Uniswap 鼓勵用戶自行給合約提供流動性,並使用 Liquidity Token 進行激勵,頗有一種「吃百家飯,穿百家衣」的感覺。事實上 Uniswap 甚至都沒有給自己設置任何收取手續費的後門(zero rent extraction),Uniswap 盈利的唯一方式就是自己也去提供流動性。

增加流動性(addLiquidity)的方法十分簡單,用戶通過向合約同時抵押一部分 ETH 和 DAI 來提供流動性,而這一次維護的不變式是,流動性改變的前後,價格不變。

尾聲 Epilogue

(TBD)