ANDY (ETH) 中文站
ANDY (ETH) 中文站
Vitreus中文网
Uniswap V1 白皮书
Uniswap V1 白皮书 原文地址:https://hackmd.io/@HaydenAdams/HJ9jLsfTz#🦄-Uniswap-Whitepaper 介绍 Uniswap 是一个基于以太坊的自动化代币交换的协议。它的设计目标是易用性、gas 高效性、抗审查性和零手续费抽成。它对交易者非常有用,并且作为其他需要保证链上流动性的智能合约的组成部分,功能特别好。 大多数交易所都会维护一个订单簿,并促进买家和卖家之间的匹配。 Uniswap 智能合约持有各种代币的流动性储备,交易直接针对这些储备执行。价格是使用恒定乘积 (x*y=k) 做市商机制自动设定的,这使总体储备保持相对平衡。储备金集中在流动性提供者网络之间,流动性提供者向系统提供代币,以换取一定比例的交易费用。 Uniswap 的一个重要特征是利用工厂/注册合约,为每个 ERC20 代币部署单独的交换合约。这些交易合约同时持有 ETH 和关联的 ERC20 代币构成的准备金。这可以实现两个基于相关供应的交易对之间的交易。交易合约通过注册表串联在一起,从而可以使用 ETH 作为媒介,实现 ERC20 代币之间的互相交易。 本文档概述了 Uniswap 的核心机制和技术细节。为了可读性,一些代码被简化。省略了溢出检查和最低购买量等安全功能。完整的源代码可在 GitHub 上获取。   协议网站:uniswap.io 文档:docs.uniswap.io 代码:github.com/Uniswap 形式化模型:https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf) Gas 基准测试 Uniswap 由于其简约的设计而非常高效。对于 ETH 到 ERC20 的交易,它使用的 Gas 几乎比 Bancor 少 10 倍。 它可以比 0x 更高效地执行 ERC20 到 ERC20 的交易,并且与 EtherDelta 和 IDEX 等链上订单簿交易所相比,可以显著减少 Gas 费用。 Exchange 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 no 538,000 113,000 no no *wrapped ETH             在 Uniswap 上,直接 ERC20 代币转账成本为 36,000 Gas - 比 ETH 到 ERC20 交易便宜约 20%。 创建交易合约 uniswap_factory.vy 是一个智能合约,既充当 Uniswap 交易所的工厂又充当注册表。公共函数 createExchange() 允许以太坊用户为任何还没有交换合约的 ERC20 部署交换合约。 exchangeTemplate: public(address) token_to_exchange: address[address] exchange_to_token: address[address] @public def __init__(template: address): self.exchangeTemplate = template @public def createExchange(token: address) -> address: assert self.token_to_exchange[token] == ZERO_ADDRESS new_exchange: address = create_with_code_of(self.exchangeTemplate) self.token_to_exchange[token] = new_exchange self.exchange_to_token[new_exchange] = token return new_exchange 所有代币及其相关交换合约的记录都存储在工厂中。对于所有代币或交换合约地址,函数 getExchange() 和 getToken() 可用于查找另一个。  @public @constant def getExchange(token: address) -> address: return self.token_to_exchange[token] @public @constant def getToken(exchange: address) -> address: return self.exchange_to_token[exchange] 除了强制执行每个代币一次交易的限制之外,工厂合约在启动交换合约时不会对代币进行任何检查。用户和前端应该只与他们信任的代币交易所进行交互。 ETH ⇄ ERC20 Trades Each exchange contract (uniswap_exchange.vy) is associated with a single ERC20 token and holds a liquidity pool of both ETH and that token. The exchange rate between ETH and an ERC20 is based on the relative sizes of their liquidity pools within the contract. This is done by maintaining the relationship eth_pool * token_pool = invariant. This invariant is held constant during trades and only changes when liquidity is added or removed from the market. A simplified version of ethToTokenSwap(), the function for converting ETH to ERC20 tokens, is shown below: 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) When ETH is sent to the function eth_pool increases. In order to maintain the relationship eth_pool * token_pool = invariant, token pool is decreased by a proporitonal amount. The amount by which token_pool is decreased is the amount of tokens purchased. This change in reserve ratio shifts the ETH to ERC20 exchange rate, incentivizing trades in the opposite direction. Exchanging tokens for ETH is done with the function tokenToEthSwap(): @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) This increases token_pool and decreases eth_pool, shifting the price in the opposite direction. An example ETH to OMG purchase is shown below. Example: ETH → OMG 10 ETH and 500 OMG (ERC20) are deposited into a smart contract by liquidity providers. An invariant is automatically set such that ETH_pool * OMG_pool = invariant. ETH_pool = 10 OMG_pool = 500 invariant = 10 * 500 = 5000 An OMG buyer sends 1 ETH to the contract. A 0.25% fee is taken out for the liquidity providers, and the remaining 0.9975 ETH is added to ETH_pool. Next, the invariant is divided by the new amount of ETH in the liquidity pool to determine the new size of OMG_pool. The remaining OMG is sent to the buyer. Buyer sends: 1 ETH Fee = 1 ETH / 500 = 0.0025 ETH ETH_pool = 10 + 1 - 0.0025 = 10.9975 OMG_pool = 5000/10.9975 = 454.65 Buyer receieves: 500 - 454.65 = 45.35 OMG The fee is now added back into the liquidity pool, which acts as a payout to liquidity providers that is collected when liquidity is removed from the market. Since the fee is added after price calculation, the invariant increases slightly with every trade, making the system profitable for liquidity providers. In fact, what the invariant really represents is ETH_pool * OMG_pool at the end of the previous trade. ETH_pool = 10.9975 + 0.0025 = 11 OMG_pool = 454.65 new invariant = 11 * 454.65 = 5,001.15 In this case the buyer received a rate of 45.35 OMG/ETH. However the price has shifted. If another buyer makes a trade in the same direction, they will get a slightly worse rate of OMG/ETH. However, if a buyer makes a trade in the opposite direction they will get a slightly better ETH/OMG rate. 1 ETH in44.5 OMG outRate = 45.35 OMG/ETH Purchases that are large relative to the total size of the liquidity pools will cause price slippage. In an active market, aribitrage will ensure that the price will not shift too far from that of other exchanges. ERC20 ⇄ ERC20 Trades Since ETH is used as a common pair for all ERC20 tokens, it can be used as an intermediary for direct ERC20 to ERC20 swaps. For example, it is possible to convert from OMG to ETH on one exchange and then from ETH to KNC on another within a single transaction. To convert from OMG to KNC (for example), a buyer calls the function tokenToTokenSwap() on the OMG exchange contract: contract Factory(): def getExchange(token_addr: address) -> address: constant contract Exchange(): def ethToTokenTransfer(recipent: address) -> bool: modifying factory: Factory @public def tokenToTokenSwap(token_addr: address, tokens_sold: uint256): exchange: address = self.factory.getExchange(token_addr) fee: uint256 = tokens_sold / 500 invariant: uint256 = self.eth_pool * self.token_pool new_token_pool: uint256 = self.token_pool + tokens_sold new_eth_pool: uint256 = 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 Exchange(exchange).ethToTokenTransfer(msg.sender, value=eth_out) where token_addr is the address of KNC token and tokens_sold is the amount of OMG being sold. This function first checks the factory to retreive the KNC exchange address. Next, the exchange converts the input OMG to ETH. However instead of returning the purchased ETH to the buyer, the function instead calls the payable function ethToTokenTransfer() on the KNC exchange: @public @payable def ethToTokenTransfer(recipent: address): 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.invariant = new_eth_pool * new_token_pool self.token.transfer(recipent, tokens_out) ethToTokenTransfer() receives the ETH and buyer address, verifies that the call is made from an exchange in the registry, converts the ETH to KNC, and forwards the KNC to the original buyer. ethToTokenTransfer() functions indentically to ethToTokenSwap() but has the additional input parameter recipient: address. This is used to forward purchased tokens to the original buyer instead of msg.sender, which in this case would be the OMG exchange. Swaps vs Transfers The functions ethToTokenSwap(), tokenToEthSwap() , and tokenToTokenSwap() return purchased tokens to the buyers address. The functions ethToTokenTransfer(), tokenToEthTransfer(), and tokenToTokenTransfer() allow buyers to make a trade and then immediately transfer purchased tokens to a recipient address. Providing Liquidity Adding Liquidity Adding liquidity requires depositing an equivalent value of ETH and ERC20 tokens into the ERC20 token’s associated exchange contract. The first liquidity provider to join a pool sets the initial exchange rate by depositing what they believe to be an equivalent value of ETH and ERC20 tokens. If this ratio is off, arbitrage traders will bring the prices to equilibrium at the expense of the initial liquidity provider. All future liquidity providers deposit ETH and ERC20’s using the exchange rate at the moment of their deposit. If the exchange rate is bad there is a profitable arbitrage opportunity that will correct the price. Liquidity Tokens Liquidity tokens are minted to track the relative proportion of total reserves that each liquidity provider has contributed. They are highly divisible and can be burned at any time to return a proporitonal share of the markets liquidity to the provider. Liquidity providers call the addLiquidity() function to deposit into the reserves and mint new liquidity tokens: @public @payable def addLiquidity(): token_amount: uint256 = msg.value * token_pool / eth_pool liquidity_minted: uint256 = msg.value * total_liquidity / eth_pool eth_added: uint256 = msg.value shares_minted: uint256 = (eth_added * self.total_shares) / self.eth_pool tokens_added: uint256 = (shares_minted * self.token_pool) / self.total_shares) self.shares[msg.sender] = self.shares[msg.sender] + shares_minted self.total_shares = self.total_shares + shares_minted self.eth_pool = self.eth_pool + eth_added self.token_pool = self.token_pool + tokens_added self.token.transferFrom(msg.sender, self, tokens_added) The number of liquidity tokens minted is determined by the amount of ETH sent to the function. It can be calulcated using the equation:   Depositing ETH into reserves requires depositing an equivalent value of ERC20 tokens as well. This is calculated with the equation:   Removing Liquidity Providers can burn their liquidity tokens at any time to withdraw their proportional share of ETH and ERC20 tokens from the pools.   ETH and ERC20 tokens are withdrawn at the current exchange rate (reserve ratio), not the ratio of their originial investment. This means some value can be lost from market fluctuations and arbitrage. Fees taken during trades are added to total liquidity pools without minting new liquidity tokens. Because of this, ethWithdrawn and tokensWithdrawn include a proportional share of all fees collected since the liquidity was first added. Liquidity Tokens Uniswap liquidity tokens represent a liquidity providers contribution to an ETH-ERC20 pair. They are ERC20 tokens themselves and include a full implementation of EIP-20. This allows liquidity providers to sell their liquidity tokens or transfer them between accounts without removing liquidity from the pools. Liquidity tokens are specific to a single ETH⇄ERC20 exchange. There is no single, unifying ERC20 token for this project. Fee Structure ETH to ERC20 trades 0.3% fee paid in ETH ERC20 to ETH trades 0.3% fee paid in ERC20 tokens ERC20 to ERC20 trades 0.3% fee paid in ERC20 tokens for ERC20 to ETH swap on input exchange 0.3% fee paid in ETH for ETH to ERC20 swap on output exchange Effectively 0.5991% fee on input ERC20 There is a 0.3% fee for swapping between ETH and ERC20 tokens. This fee is split by liquidity providers proportional to their contribution to liquidity reserves. Since ERC20 to ERC20 trades include both an ERC20 to ETH swap and an ETH to ERC20 swap, the fee is paid on both exchanges. There are no platform fees. Swapping fees are immediately deposited into liquidity reserves. Since total reserves are increased without adding any additional share tokens, this increases that value of all share tokens equally. This functions as a payout to liquidity providers that can be collected by burning shares. Since fees are added to liquidity pools, the invariant increases at the end of every trade. Within a single transaction, the invariant represents eth_pool * token_pool at the end of the previous transaction. Custom Pools ERC20 to Exchange The additional functions tokenToExchangeSwap() and tokenToExchangeTransfer() add to Uniswap’s flexibility. These functions convert ERC20 tokens to ETH and attempts an ethToTokenTransfer() at a user input address. This allows ERC20 to ERC20 trades against custom Uniswap exchanges that do not come from the same factory, as long as they implement the proper interface. Custom exchanges can have different curves, managers, private liquidity pools, FOMO-based ponzi schemes, or anything else you can think of. Opt-in Upgrades Upgrading censorship resistant, decentralized smart contracts is hard. Hopefully Uniswap 1.0 is perfect but it probably is not. If an improved Uniswap 2.0 design is created, a new factory contract can be deployed. Liquidity providers can choose to move to the new system or stay in the old one. The tokenToExchange functions enable trades with exchanges launched from different factories. This can be used for backwards compatibility. ERC20 to ERC20 trades will be possible within versions using both tokenToToken and tokenToExchange functions. However, across versions only tokenToExchange will work. All upgrades are opt-in and backwards compatible.  Frontrunning Uniswap can be frontrun to some extent. This is bounded by user set minimum/maximum values and transaction deadlines.

友情链接:

Powered by ANDY (ETH) 中文站 @2013-2022 RSS地图 HTML地图

Copyright Powered by站群系统 © 2013-2024