Uniswap V2 white paper translation

Uniswap V2 core architecture

Hayden Adams Noah Zinsmeister Dan Robinson
[email protected] [email protected] [email protected]
                
    2020 年 3 月

Summary:

    This technical white paper explains some of the design logic behind the Uniswap v2 core contract. It covers the new features of the contract-including any pairing between ERC20, an enhanced price prediction that allows other contracts to estimate the time-weighted average price in a given interval, "lightning swap", which allows traders to receive assets and use them In other places, and then pay in future transactions, and a protocol fee that can be opened in the future. It also restructured the contract to reduce its attack surface. This white paper introduces the "core" contract mechanism of Uniswap v2, including the matching contract for storing liquidity provider funds, and the factory contract for instantiating the matching contract.

Introduction

    Uniswap v1 is a smart contract on-chain system on the Ethereum blockchain, which implements an automatic liquidity protocol based on the "constant product formula" [1]. Each Uniswap v1 trading pair will store a collective reserve of two assets and provide liquidity for these two assets, maintaining the characteristic that the product of reserves cannot be reduced. Traders pay a 0.3% fee in the transaction, which is owned by the liquidity provider. These contracts cannot be upgraded.
    Uniswap v2 is also based on the "constant product market maker" mechanism, which adds some new functions and has some new and very desirable characteristics. Most importantly, it can create any ERC20/ERC20 pair instead of only supporting the pairing between ERC20 and ETH. It also provides a hardened price prediction that accumulates the relative prices of two assets at the beginning of each block. This allows other contracts on Ethereum to estimate the time-weighted average price of two assets in any time interval. Finally, it can realize lightning exchange (lightning loan), users can freely receive assets, and use them elsewhere on the chain, and only pay (or return) these assets at the end of the transaction.
    Although the contract generally cannot be upgraded, there is a private key that can update a variable in the factory contract and open the transaction chain by 0.05%. This fee is initially closed, but can be opened in the future, after which the liquidity provider will earn 0.25% on each transaction instead of 0.3%.
    As mentioned in Section 3, Uniswap v2 also fixes some minor issues of Uniswap v1 and restructures the implementation method to reduce Uniswap’s attack surface and minimize the amount of “core” contracts that hold liquidity provider funds. The logic makes the system easier to upgrade.
    This article introduces the mechanism of core contracts and the factory contracts used to instantiate these contracts. In fact, using Uniswap v2 requires a routing contract to call the matching contract, which calculates the transaction or deposit amount and transfers the funds to the matching contract.

New feature

2.1 ERC20/ERC20 token pair

    Uniswap v1 uses ETH as an intermediary currency. Each currency pair has ETH as one of its assets. This makes routing easier-every transaction between ABC and XYZ has to go through the ETH/ABC pair and the ETH/XYZ pair, and reduces the dispersion of liquidity.
    However, this rule brings huge costs to liquidity providers. All liquidity providers are at risk of ETH and suffer impermanent losses based on changes in the price of other assets relative to ETH. When the two assets ABC and XYZ are related-for example, if they are both USD stablecoins, in Uniswap, the liquidity provider of the ABC/XYZ token pair is usually better than the ABC/ETH or XYZ/ETH token pair , Suffer less impermanence loss.
    Using ETH as a mandatory intermediary currency also brings costs to traders. Traders need to pay twice as much as using ABC/XYZ currency pairs (also known as trading pairs, or token pairs) directly, and they have to suffer twice as much slippage.
    Uniswap v2 allows liquidity providers to create matching contracts for any two ERC-20s.
    The proliferation of transaction pairs between any ERC-20 may make it a little difficult to find the best path to trade a particular transaction pair, but routing can be handled at a higher level (off-chain or through on-chain routers or aggregators).

2.2 Price prediction

    The marginal price (excluding expenses) provided by Uniswap at time t can be calculated by dividing the reserve of asset a by the reserve of asset b.


    Since arbitrageurs will trade with Uniswap when the price is incorrect (with a sufficient amount to cover the cost), as shown by Angeris et al. [2], the price provided by Uniswap tends to follow the relative market of the asset price. This means that it can be used as an approximate price prediction.
    However, Uniswap v1 is not safe as an on-chain price prediction because it is very easy to manipulate. Suppose there are other contracts that use the current ETH-DAI price to settle derivatives. An attacker who wants to manipulate the estimated price can buy ETH from the ETH-DAI pair, trigger the settlement of the derivative contract (making it settle based on an inflated price), and then sell the ETH back to the pair, returning its transaction to the real price. This can even be done in the form of atomic transactions, or miners control the order of transactions within the block.
    Uniswap v2 improves this prediction function by measuring and recording the price of each block before the first transaction (or after the last transaction equivalent to the previous block). This price is more difficult to manipulate than the price in a block. If an attacker submits a transaction in an attempt to manipulate the price at the end of a block, some other arbitrageurs may submit another transaction in the same block, followed by another transaction. Miners (or attackers who use enough fuel to fill the entire block) can manipulate the price at the end of a block, but unless they are also mining in the next block, they may not have a special advantage in arbitrage trading 1.1.
    Specifically, Uniswap v2 accumulates this price by tracking the cumulative sum of prices when someone interacts with the contract at the beginning of each block. According to the block timestamp 1.2, each price will be weighted according to the amount of time since the previous block was updated. This means that the accumulator value at any given time (after being updated) should be the sum of the spot price per second in the history of the contract.


    To estimate the time-weighted average price from time t1 to t2, the external caller can check the value of the point accumulator at t1 and t2, and then check the value of the point accumulator again, subtracting the first value from the second value , Then divide by the number of seconds elapsed. (Note that the contract itself does not store the historical value of this accumulator-the caller must call the contract at the beginning of the period to read and store this value.)

    The user of the oracle can choose when to start and end this period. Choosing a longer period will result in a less up-to-date price, but the cost of manipulating TWAP by an attacker will be higher.

    A complicated question is: Should we use asset B to measure the price of asset A, or use asset A to measure the price of asset B? Although the spot price of A in units of B is always the inverse of the spot price of B in units of A, the average price of asset A in units of B in a specific time period is not equal to asset B in units of A The inverse of the average price is 1.3. For example, if the USD/ETH price of block 1 is 100 and the USD/ETH price of block 2 is 300, then the average price of USD/ETH will be 200 USD/ETH, but the average price of ETH/USD will be 1/150 ETH/USD. Since the contract cannot know which of these two assets the user wants to use as the accounting unit, Uniswap v2 will track the two prices.
    Another complication is that it is possible for someone to send assets to the matching contract without interacting with the contract-thereby changing its balance and marginal price, so as not to trigger an update of the prediction. If the contract simply checks its balance and updates the prediction based on the current price, the attacker can manipulate the prediction by sending assets to the contract immediately before the first call to the contract in a block. If the last transaction was made in a block with a timestamp X seconds ago, the contract will mistakenly multiply it by X before accumulating the new price, even if no one has the opportunity to trade at that price. To prevent this from happening, the core contract caches its reserves after each interaction, and uses the prices derived from the cached reserves to update the predictions instead of the current reserves. In addition to protecting the prophecy from manipulation, this change also enables the contract restructuring described in Section 3.2 below.

2.2.1 Accuracy

    Since Solidity does not provide support for non-integer numeric data types, Uniswap v2 uses a simple binary fixed-point format to encode and process prices. Specifically, the price at a given moment is stored as a UQ112.112 number, which means that a precision of 112 decimal places is specified on both sides of the decimal point, and there is no sign. The range of these numbers is [0,2^112-1]1.4 and the precision is 1/2^112.
    The UQ112.112 format was chosen for a practical reason-because these numbers can be stored in a uint224, which frees up 32 bits of the 256-bit storage slot. It also happens that every reserve stored in uint112 will also leave 32 bits of free space in a (packed) 256-bit storage slot. These free spaces are used in the accumulation process described above. Specifically, the reserve is stored together with the timestamp of the block that has at least one transaction recently, and modified with 2^32 to make it fit to 32 bits. In addition, although the price (stored as a UQ112.112 number) at any given moment is guaranteed to fit 224 bits, the accumulation of that price in an interval is not suitable. The extra 32 bits at the end of the storage slot for the cumulative prices of A/B and B/A are used to store overflow bits caused by repeated addition of prices. This design means that the price prediction only adds three SSTORE operations in the first transaction of each block (currently the cost is about 15000 gas).
    The main disadvantage is that 32 bits are not enough to store a timestamp value that can reasonably never overflow. In fact, the Unix timestamp overflows a uint32 date which is 02/07/2106. In order to ensure that the system continues to operate normally after this date and every multiple of 2^32 −1 seconds thereafter, only Oracles need to check the price at least once every certain period of time (about 136 years). This is because the core accumulation method (and modification timestamp) is actually overflow safe, which means that transactions that cross the overflow interval can be properly accounted for, because Oracles use appropriate (simple) overflow arithmetic to calculate the increase the amount.

2.3 Lightning loan

    In Uniswap v1, when users purchase ABC with XYZ, they need to send XYZ to the contract before they can receive ABC. It is inconvenient if the user needs the ABC they purchased to get the XYZ they paid. For example, the user may use the ABC to buy XYZ in other contracts in order to arbitrage the price difference from Uniswap, or they may sell collateral to repay Uniswap to liquidate Maker or Compound.
    Uniswap v2 adds a new feature that allows users to receive and use the asset before paying for it, as long as they pay in the same atomic transaction. The swap function calls an optional callback contract specified by the user between the transfer of the token requested by the user and the execution of immutability. Once the callback is completed, the contract will check the new balance and confirm that the immutability is met (after adjusting the fee for the payment amount). If the contract does not have enough funds, the entire transaction will be restored.
    Users can also use the same tokens to repay the Uniswap pool instead of completing the exchange. This is actually equivalent to allowing anyone to flash lend any assets stored in the Uniswap pool (same as Uniswap charges 0.30% for transactions) 1.5.

2.4 Agreement fees

    Uniswap v2 includes a 0.05% agreement fee, which can be turned on or off. If enabled, the fee will be sent to the feeTo fee address specified in the factory contract.
    FeeTo is not set initially, and no fee is charged. For the pre-specified feeToSetter address, you can call the setFeeTo function on the Uniswap v2 factory contract to set feeTo to a different value. FeeToSetter can also call setFeeToSetter to change the feeToSetter address itself.
    If the feeTo address is set, the agreement will start to charge 0.05% of the fee, which is deducted from 1/6 of the 0.3% fee earned by the liquidity provider. In other words, traders will continue to pay a 0.3% fee for all transactions: 83.3% of the fee (5/6 from the 0.3% fee) is returned to the LP, and the remaining 16.6% (1/1 from the 0.3% fee) 6) Give feeTo account.
    If the 0.05% fee is charged at the time of the transaction, it will bring additional fuel costs to each transaction. In order to avoid this situation, cumulative fees will only be charged when depositing or withdrawing liquid funds. The contract calculates the cumulative cost, and immediately mints new liquidity tokens (LP Token, named UNI V2) into the corresponding LP and feeTo accounts before any tokens are minted or burned.
    Since the last time the fees were collected, the total amount of fees charged can be calculated by measuring the growth of √K (that is, √(x∙y)) 1.6. This formula gives the cumulative cost between t1 and t2 as a percentage of the liquidity in the pool at t2.


    If the fee is activated before t1, the feeTo address should capture 1/6 of the accumulated fee between t1 and t2. Therefore, we need to mint new liquidity tokens to feeTo address, and its proportion of the pool is ϕ∙f_1,2, where ϕ=1/6.
    In other words, we have to choose s_m to satisfy the following relationship, where s_1 is the total number of outstanding shares at time t_1.


    Substituting 1-√(k_1 )/√(k_2) = f_1,2 into formula (5), we get the following:


    When ϕ=1/6, the following is obtained:


    Assume that the initial depositor puts 100 DAI and 1 ETH into a currency pair and gets 10 shares. After a period of time (without any other depositors participating in the currency pair), they tried to withdraw the currency pair. At this time, the currency pair has 96 DAI and 1.5 ETH. Inserting these values ​​into the above formula, we get the following result.


2.5 Meta transactions for pool shares

    Uniswap v2 natively supports meta-transactions for the pool shares cast, which means that users can transfer their pool shares through signature authorization instead of performing on-chain transactions from their address 1.7. Anyone can submit this signature on behalf of the user by calling the permission function, pay for gas, and possibly perform other operations in the same transaction.

3. Other

3.1 Solidity

    Uniswap v1 is implemented in Vyper, a smart contract language similar to Python. Uniswap v2 is implemented in the more widely used Solidity, because it requires some features that are not yet available in Vyper (such as the ability to interpret the return value of non-standard ERC-20 tokens, and access new opcodes through inline assembly , Such as chainid).

3.2 New contract structure

    A design focus of Uniswap v2 is to minimize the external interface and complexity of the core matching contract, the contract that stores the assets of the liquidity provider. Any errors in the contract can be catastrophic, as millions of dollars in liquidity can be stolen or frozen.
    When evaluating the security of this core contract, the most important question is whether it can protect the liquidity provider’s assets from being stolen or locked. Any function designed to support or protect traders-in addition to the basic function that allows one asset in the pool to be exchanged for another asset, can be handled in a routing contract.
    In fact, even part of the exchange function can be pulled into the routing contract. As mentioned above, Uniswap v2 stores the last recorded balance of each asset (to prevent special manipulative use of the prediction mechanism). The new architecture uses this storage tag to further simplify the Uniswap v1 contract.
    In Uniswap v2, the seller sends the asset to the core contract before calling the exchange function. Then, the contract measures how many assets it has received by comparing the last recorded balance with the current balance. This means that the core contract is agnostic to the way traders transfer assets. Instead of transferFrom(), it can be a meta-transaction, or any other mechanism to authorize the transfer of ERC-20 in the future.

3.2.1 Fee adjustment

    Uniswap v1's transaction fee is to reduce the amount paid to the contract by 0.3% before executing the constant product market maker. The contract implicitly implements the following formula:


    For fast exchange, Uniswap v2 takes into account the possibility that x_in and y_in may both be non-zero (when the user wants to use the same asset to repay the pair of assets, rather than exchange). In order to deal with this situation and at the same time to properly apply the fees, the writing of the contract enforces the following inequality 1.8:


    To simplify the calculation on this chain, we can multiply each side of the inequality by 1,000,000.


3.2.2 sync()和skim()

    In order to prevent custom token implementations from being able to update the balance of the pairing contract, and to handle tokens whose total supply may be greater than 2^112 more elegantly, Uniswap v2 has two guarantee functions: sync() and skim().
    sync() functions as a recovery mechanism when the token asynchronously reduces the balance of the transaction pair. In this case, the transaction will receive a suboptimal interest rate, and if no liquidity provider is willing to correct this situation, the currency pair will be stuck. The existence of sync() is to set the contract's reserve fund to the current balance, and to provide some degree of loose recovery for this situation.
    The function of skim() is as a recovery mechanism. When enough tokens are sent to a currency pair, the reserves of two uint112 storage slots will overflow, otherwise the transaction will fail. If the difference between the current balance and 2^112-1 is greater than 0, the shim() function allows the user to give the difference to the caller.

3.3 Handling non-standard or abnormal tokens

    The ERC-20 standard requires transfer() and transferFrom() to return a Boolean value indicating the success or failure of the call [4]. One or both of these functions are implemented on some tokens-including popular tokens like Tether (USDT) and Binance Coin (BNB), but they have no return value. Uniswap v1 interprets the missing return values ​​of these improperly defined functions as false: it means that the transfer is unsuccessful and the transaction is resumed, causing the attempted transfer to fail.
    Uniswap v2 handles non-standard tokens in a different way. Specifically, if the transfer() call does not return a value of 1.9, Uniswap v2 interprets it as a success rather than a failure. This change should not affect any standard ERC-20 tokens (because transfer() always has a return value in these tokens).
    Uniswap v1 also assumes that calling transfer() and transferFrom() cannot trigger a reentrant call to Uniswap to the contract. This hypothesis has been attacked by certain ERC-20 tokens, including "hooks" that support ERC-777 [5]. In order to fully support such tokens, Uniswap v2 includes a "lock" that directly prevents the re-entry of all public state change functions. This also prevents the re-entry of user-specified callbacks in flash loans, as described in section 2.3.

3.4 Initialize the total amount of liquidity tokens

    When a new liquidity provider deposits tokens into an existing Uniswap trading pair, the number of minted liquidity tokens (LP Token) will be calculated based on the number of existing tokens.


    But what if they are the first depositors? In this case, x_starting is 0, so this formula (12) will not be used.
    Uniswap v1 sets the initial share supply to the amount of ETH deposited (in wei). This is a reasonable value, because if the initial liquidity is deposited at the correct price, then 1 liquidity pool share LP token (like ETH, it is an 18-digit precision token) is worth about 2 ETH.
    However, this means that the value of the liquidity pool share depends on the ratio at which the liquidity is initially deposited, which is quite arbitrary, especially since the ratio cannot be guaranteed to reflect the true price. In addition, Uniswap v2 supports any token pair, so many token pairs will not include ETH at all.
    The improved method is that the total amount of LP Token initially issued by Uniswap v2 is √K (each TokenA/TokenB liquidity pool has its own LP Token, which is independent of each other. If there are 3 liquidity pools, there are corresponding 3 A kind of LP Token, although their symbol names are all called UNI-V2), it is the geometric mean of the currency pair x∙y in the fund pool:


    This formula guarantees the liquidity pool share √K, and the value at any time is basically independent of the proportion of the initial deposit of liquid funds. For example, suppose the price of 1 ABC is currently 100 XYZ. If the initial deposit is 2 ABC and 200 XYZ (the ratio is 1:100), then the depositor will get √(2∙200)=20 shares of LP Token. Now the value of these shares of LP Token should still be 2 ABC and 200 XYZ, plus accumulated expenses.
    If the initial deposit is 2ABC and 800XYZ (the ratio is 1:400), the depositor will get √(2∙800)=40 shares of LP token1.10.
    The above formula ensures that the value of the share of the liquidity pool will never be lower than the geometric mean of the reserves in the pool. However, the value of the liquidity pool share is likely to grow over time, whether by accumulating transaction fees or by “donating” to the liquidity pool. In theory, this may cause the minimum number of shares in the liquidity pool (1e-18 shares in the pool) to be so high in value that small liquidity providers cannot provide any liquidity.
    In order to alleviate this problem, Uniswap v2 will burn 1e-15 (0.000000000000001) LP Tokens (1000 times the minimum pool share) when creating a token pairing, and send them to the zero address instead of sending to minter. This should be a negligible cost of 1.11 for almost all token pairs. But it greatly increases the cost of the aforementioned attacks. In order to increase the value of the liquidity pool share to $100, the attacker needs to donate $100,000 to the pool, which will be permanently locked in as liquidity.

3.5 WETH in packaging

    WETH is an interface used to interact with Ethereum's native ETH token, which is different from the standard interface for ERC-20 token interaction. Therefore, many other protocols on Ethereum do not support ETH, but use standardized "wrapped ETH" tokens [6].
    Uniswap v1 is an exception. Since each pair of Uniswap v1 contains ETH as an asset, it is reasonable to directly process ETH, which saves gas slightly.
    Since Uniswap v2 supports arbitrary ERC-20 pairs, it doesn't make sense to support unpackaged ETH now. Adding such support will double the size of the core code base and potentially decentralize the liquidity between ETH and WETH pairs by 1.12. Before trading on Uniswap v2, native ETH needs to be encapsulated in WETH.

3.6 Deterministic pairing address

    As in Uniswap v1, all Uniswap v2 matching contracts are instantiated by a factory contract. In Uniswap v1, these pairing contracts are created using the CREATE opcode, which means that the address of this type of contract depends on the order in which the token pair was created. Uniswap v2 uses Ethereum's new CREATE2 opcode [8] to generate a pairing contract with a deterministic address. This means that the address of a pairing contract can be calculated off-chain (if it exists) without having to check on-chain status.

3.7 Maximum token balance

    In order to effectively implement the prediction mechanism, Uniswap v2 only supports a reserve balance of up to 2^112-1. This number is enough to support 18 decimal places of tokens, and the total supply exceeds 1 trillion.
    If the reserve balance exceeds 2^112-1, any call to the swap() function will fail (due to the check of the _update() function). In order to recover from this situation, any user can call the skim() function to remove excess assets from the liquidity pool.

    Note [1.1] For a practical example of how to use Uniswap v1 as an oracle to make the contract vulnerable to such attacks, see [3].
    Note [1.2] Since miners have a certain degree of freedom in setting block timestamp parameters, users who use oracles should be aware that these values ​​may not accurately correspond to real-world time.
    Note [1.3] The arithmetic average price of asset A and asset B in a certain period is equal to the reciprocal of the harmonic average price of asset B and asset A in that period. If the contract measures the geometric average price, then the price will be the reciprocal of each other. However, the geometric mean TWAP is not very commonly used and is difficult to calculate on Ethereum.
    Note [1.4] The theoretical upper limit of 2 112-(1/2 112) does not apply in this environment, because the UQ112.112 number in Uniswap is always generated by the ratio of two uint112. The largest such ratio is (2^112-1)/1= 2^112-1.
    Note [1.5] Because Uniswap charges based on the amount invested, the cost relative to the amount of cash withdrawal is actually slightly higher:
1/(1-0.003)-1= 3/997=0.300903%.
    Note [1.6] We can use this invariant, which does not count the liquidity tokens that are minted or burned, because we know that a fee will be charged every time liquidity is deposited or withdrawn.
    Note [1.7] The signed message complies with the EIP-712 standard, which is the same as the standard used for meta-transactions of tokens such as CHAI and DAI.
    Note [1.8] In a certain period, the arithmetic average price of asset A and asset B is equal to the reciprocal of the harmonic average price of asset B and asset A in that period. If the contract measures the geometric average price, then the price will be the reciprocal of each other. However, the geometric mean TWAP is not very commonly used and is difficult to calculate on Ethereum.
    Note [1.9] As mentioned in section 3.2 above, Uniswap v2 core does not use transferFrom().
    Note [1.10] This also reduces the possibility of rounding errors, because the number of bits in the number of shares will approximate the average of the number of bits in the number of assets X in the reserve and the number of bits in the number of assets Y in the reserve:

    Note [1.11] In theory, in some cases, this burning may not be negligible, such as the pairing between high-value zero-decimal tokens. However, these trading pairs are not suitable for Uniswap in any way, because rounding errors will make trading infeasible.
    Note [1.12] As of this writing, one of the most liquid trading pairs on Uniswap v1 is the trading pair between ETH and WETH.

Disclaimer

    This article is for reference only. It does not constitute investment advice or solicitation advice for buying or selling any investment, nor should it be used to evaluate the pros and cons of any investment decision. It should not be used as a basis for accounting, legal or tax advice or investment advice. This article only reflects the current views of the author, does not represent Paradigm or its affiliates, nor does it necessarily reflect the views of Paradigm, its affiliates or individuals related to Paradigm. The opinions reflected in this article are subject to change and will not be updated.

references

[1] Hayden Adams. 2018. url:https://hackmd.io/@477aQ9OrQTCbVR3fq1Qzxg/HJ9jLsfTz?type=view.
[2] Guillermo Angeris et al. An analysis of Uniswap markets. 2019. arXiv: 1911.03380 [q-fin.TR].
[3] samczsun. Taking undercollateralized loans for fun and for profit. Sept. 2019. url:
https://samczsun.com/taking-undercollateralized-loans-for-fun-and-for-profit/
[4] Fabian Vogelsteller and Vitalik Buterin. Nov. 2015. url:
https://eips.ethereum.org/EIPS/eip-20
[5] Jordi Baylina Jacques Dafflon and Thomas Shababi. EIP 777: ERC777 Token Standard. Nov. 2017.url: https://eips.ethereum.org/EIPS/eip-777
[6] Radar. WTF is WETH? url: https://weth.io/
[7] Uniswap.info. Wrapped Ether (WETH). url:
https://uniswap.info/token/0xc02aaa39b223fe8d0a0e5c4f27
[8] Vitalik Buterin. EIP 1014: Skinny CREATE2. Apr. 2018. url:
https://eips.ethereum.org/EIPS/eip-1014

Guess you like

Origin blog.csdn.net/sanqima/article/details/109582738