前些日子和 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 交易对举例。
我们把以太坊区块链视作一个状态机,设
– 、 分别是 transcation 执行前合约中储备的 ETH 和 DAI。
– 、 分别是 transcation 执行结束之后合约中新的的 ETH 和 DAI。
– 为下面的 swap() 方法中传入的 ETH。
– 是 swap() 方法兑换出的 DAI。
卖 ETH 的过程就是已知其他参数,求 ,那么显然本次交易的价格就是 。
而 x*y = k invariant 指的就是:
方程解得
代码如下:
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)