# Ch 2 Uniswap [toc] ## Uniswap 简介 本章介绍去中心化交易所 Uniswap,Uniswap 是一个运行在以太坊上的自动化做市商协议,主要用于 Ethereum 上的代币交换,它使用自动化做市(Automated Market Maker, a.k.a. AMM)而不是订单簿(Order Book)。任何人都可以在 ETH 和任何 ERC20 代币之间快速交换,任何人也都可以通过提供流动性赚取手续费(每笔交易的 0.3%)并且在任何时候取回这些流动性,手续费的分润是实时的,但在价格变化较大的情况下,流动性提供者会遭受 "无常损失"(Impermanent Loss)。当价格恢复到提供流动性时的水平时,损失就会减少。如果交易量足够大,赚取的费用可能会抵消这一损失。任何人也都可以通过提供初始流动性池而创建一个新的交易对。 Uniswap 最鲜明的特征就是在交易的前后维持代币数量的乘积不变(Constant Product Market Maker),每个用户都可以为合约提供流动性并获得交易手续费作为回报。令人惊讶的是 Uniswap 迄今为止依然没有自己的原生治理通证而只有流动性代币,但这通常认为这也是 Uniswap 去中心化和极简主义的一个表现。Uniswap 会不会在 V3 中引入治理代币,将会如何引入成为每一个关心 DeFi 发展的人都在思考的问题。 本章中的代码来自 V2 版本。 ### 里程碑 - [2018-11 初版 Uniswap 在以太坊开发者大会 Devcon4 期间上线](https://uniswap.org/docs/v1/),这是一个使用类 Python 语法的合约语言 Vyper 实现的版本 - [2019-11 Uniswap 一周年](https://medium.com/uniswap/uniswap-birthday-blog-v0-7a91f3f6a1ba) - [2020-05 上线 V2](https://uniswap.org/blog/launch-uniswap-v2/),这是一个用 Solidity 实现的版本,开始实现支持原生 ERC20-ERC20 交易对等诸多新功能 - [2020-08 完成 A 轮融资](https://uniswap.org/blog/uniswap-raise/) ### 增长 ![](https://i.imgur.com/4V8TVt9.png) - [DeFiPulse](https://defipulse.com/uniswap) ## 去中心化交易所的必要性與瓶頸 作為去中心化資產的加密貨幣,長期以來卻依靠著中心化的交易所提供的交易服務,作為一個悖論一直以來制約著加密貨幣的發展。自中心化交易所誕生之日開始,因各種事故所造成的丟幣事件就屢見不鮮[^1][^2]. ![](https://i.imgur.com/34FaYYN.jpg) > Mt.Gox 事件开启了比特幣长达近三年之久的黑暗时代 | 時間 | 交易所 | 被駭金額 | | -------- | -------- | -------- | | | Mt. Gox | $700MM | | | Coincheck | $530MM | | | Bitfinex | $120MM | | | Binanace | $27MM | 除了被駭的風險之外,去中心化交易所因為沒有引入額外的控制,從而無需進行 KYC 對用戶和資產進行區別對待,還可以提供相應的 API 對交易所的流動性進行編程,並且使得交易長尾和臨時的資產(cTokens, liquidity tokens, NFTs, etcs)成為可能。 雖然不時有新的中心化交易所的設計出現,但是因為速度、延遲、交易手續費等所造成用戶體驗方面的缺陷,使得這些去中心化交易所的交易規模都無法同便捷、高效的中心化交易所競爭,人們一直在等待著一個真正可以適合大規模應用的去中心化交易所方案。 [^1]: MtGox [^2]: 中心化交易所被駭總結。 [^3]: [🦄 Uniswap Birthday Blog — V0](https://medium.com/uniswap/uniswap-birthday-blog-v0-7a91f3f6a1ba) ## 定价曲线(Bounding Curve) 我們根據是否有具有訂單簿,通常可將去中心化交易所簡單分成有訂單和無訂單(AMM 既自動化做市商)兩種主要形式,Uniswap 屬於後者。簡單說來,Uniswap 通過 [工廠合約](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Factory.sol#L23),為每個交易對創建一個合約,合約中託管兩種資產 A 和 B,設她們的數量分別為 x 和 y,任何時刻,合約認為 x 數量的 A 資產和 y 數量的 B 資產的價值是相等的,並且在每次交易的前後,維持合約中 x 和 y 的乘積不變(Invariant)。 ![](https://i.imgur.com/H9entj5.png) 合約的初始流動性則有創建者給定,在給定了初始流動性的同時,它們的當前時刻的價格也就確定了。和 Bancor 所不同的是,任何用戶都可以隨時增加,和移除之前已增加的流動性,從而反映真實的市場交易需求。增加流動性的用戶將獲得對應的流動性通證,並作為日後分享合約交易手續費,以及贖回所提供的流動性資產的憑證。增加和删除流动性的过程中,维持合约中 x 和 y 的比值不变,即增减流动性操作不影响当前通证的价格。 Uniswap 由于其极简主义的设计,使得其相较其他去中心化交易所,非常节约交易手续费。对于 ETH 到 ERC20 的交易,它比 Bancor 的手续费低近 10 倍。 | 交易所 | Uniswap | EtherDelta | Bancor | Radar Relay (0x) | IDEX | Airswap | | -------- | -------- | -------- | -------- | -------- | -------- |---- | | ETH to ERC20 | 46,000 |108,000 | 440,000 | 113,000 | 143,000 | 90,000 | | ERC20 to ETH | 60,000 | 93,000 | 403,000 | 113,000 | 143,000 |120,000* | | ERC20 to ERC20 | 88,000 | - | 538,000 | 113,000 | - |- | Uniswap V2 中将协议分成了核心合约 [Uniswap Core](https://github.com/Uniswap/uniswap-v2-core) 和外围的辅助合约 [Uniswap Periphery](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol)。而其中所有的兑换行为,最后都被规约到了核心合约的 swap 函数,此之谓「Uniswap」。 ```solidity=158 // this low-level function should be called from a contract which performs important safety checks function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); } _update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } ``` 我们发现在 [UniswapV2Pair.sol](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Pair.sol) 中,并没有进行相应的数值运算,而仅仅是在第 182 行检查了不等约束。这是一种有效的合约优化技巧,可以将较为耗时具体的数值操作转移到链下进行而同时保证链上合约运行的准确无误。 ## 流动性提供者(Liquidity Provider) Uniswap 的流动性通证是一种支持增发和销毁的 ERC20 通证,发行方为每个交易对合约,当用户执行 `add_liquidity` 操作时,最后将会由核心合约执行 `Mint` 函数根据该笔操作所提供的流动性与当前合约整体流动性的比值,计算出需要增发的流动性通证。 假设当前已发行 t 单位流动性通证,合约中 A、B 两种资产的储备金数值分别为 x,y。新加入的储备金分别为 x',y',那么需满足: `x/y = x'/d'` 新发行的流动性通证 t',那么有 `x'/x = y'/y = t'/t`。 根据此关系可以计算出新发行的流动性通证的数值(第 123 行)。 ```solidity=109 // this low-level function should be called from a contract which performs important safety checks function mint(address to) external lock returns (uint liquidity) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); uint amount0 = balance0.sub(_reserve0); uint amount1 = balance1.sub(_reserve1); bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); } require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity); _update(balance0, balance1, _reserve0, _reserve1); if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date emit Mint(msg.sender, amount0, amount1); } ``` 手续费则是直接增加的 liqudity pool 中,利用某种类似懒标记(Lazy Tag)的方式,因而每次交易时,k 的值实际有所增加,在销毁流动性通证的时候兑付,详情可参考文档中关于 [手续费的段落](https://uniswap.org/docs/v2/advanced-topics/fees)。 https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol ## 链上价格预言机(Onchain Oracle) ## 无常损失 ## 本章注记 最早的關於去中心化交易所的嘗試可追溯到比特幣時代的 Hash Time Locked Contracts,例如 Bisq 和 LocalBitcoins,這些模式昂貴,緩慢,不靈活但是久經檢驗。另一些嘗試是 Ring Exchanges,例如 ShapeShift。另一些人開始嘗試構建一條新的區塊鏈來處理交易,求中最具有代表性的是比特股(BitShares),但是這些定制化的區塊鏈因為缺少原生的交易資產,在跨鏈技術還沒有成熟的時代,需要引入額外的信任來構建可交易的資產。 緊接著出現的是 OasisDex 和 EtherDelta,前者是 MakerDAO 團隊為 MKR 和 DAI 建立的鏈上託管的交易所,在很長時間裡 OasisDex 都是 Ethereum 上流量最高的去中心化交易所之一。EtherDelta 則是劃時代的去中心化交易所設計方式,她通過 Off-chain Relay,On-chain Settlement 的方式,做出了絕佳的 trade-off,真正將中心化交易所帶向普及。 ![](https://i.imgur.com/J2rYqu0.png) 從完全鏈上模式的去中心化交易所中,又衍生出兩個分支,她們分別是有訂單薄的(代表產品 Kyber)和沒有訂單簿的 Bancor。Bancor 最早引入了 Bounding Curve 的概念,並提出無需訂單簿的交易機制更加能夠適應長尾代幣的需求[4],但是她的 Bounding Curve 設計較為複雜,且有一個保證金率的參數,使得合約在一經設定之後,流動性就是固定的。後來一些人們發現,Bounding Curve 不必依賴 Bancor 中的複雜曲線,例如註明的 P3D 遊戲,使用最簡單的一次函數來作為 Bounding Curve。而 Uniswap 中使用反比例函數(a.k.a. x*y=k 或者 Constant Product Market Maker Model)作為 Bounding Curve,所有的普通用戶都可以為交易所提供流動性,並創建流動性代幣。 很多後續的產品也在 Uniswap 的 Bounding Curve 基礎上進行改造,例如 Banlencer 和 Curve。 [^3]: [Bancor Protocol Continuous Liquidity for Cryptographic Tokens through their Smart Contracts](https://storage.googleapis.com/website-bancor/2018/04/01ba8253-bancor_protocol_whitepaper_en.pdf) - [解析 DeFi 項目《Uniswap》,吳冠融 Roger Wu](https://medium.com/taipei-ethereum-meetup/defi-uniswap-1-e36db975e4ae) - [A Brief History Of Decentralized Exchange, Tom Schmidt](https://github.com/carboclan/pm/issues/69) - [What explains the rise of AMMs?](https://medium.com/dragonfly-research/what-explains-the-rise-of-amms-7d008af1c399) - [When is Uniswap a good oracle?](https://medium.com/gauntlet-networks/why-is-uniswap-a-good-oracle-22d84e5b0b6c) ## 习题 1. [R] P3D 的线性价格曲线 2. [R] 可分润代币模型 3. [SR] Bancor 的价格公式 4. [SR] Rex 的设计