5 Tips & Tricks for DeFi Developers from the UniswapV2 Contracts Review
Tags: blockchain, solidity, uniswap, smartcontract
Published: 2022-01-22 15:44:29

**Uniswap** announced their V2 launch on Ethereum mainnet on 18 May 2020, followed by the V3 launch on 5 May 2021. With a total value locked (TVL)of USD 7.4 billion (as of 21 Jan 2022), Uniswap V2 accounted for slightly more than half of the total Uniswap TVL (USD 3.6 billion), with an average daily trading volume of around USD 500 million. Clearly, Uniswap V2 remains a highly popular decentralised exchange (DEX) among DeFi users. 

Having recently reviewed the Uniswap V2 contracts, I found the contracts full of gems which are worth sharing with fellow DeFi smart contract developers. 

##Brief Overview of Contracts
Uniswap V2 contracts are organised into **V2 Core** and **V2 Periphery**. V2 Core consists of the essential V2 smart contracts, including _UniswapV2Pair_, _UniswapV2Factory_ and _UniswapV2ERC20_. V2 Periphery consists of a set of helper contracts or functions which include _UniswapV2Migrator_, _UniswapV2Router_ and a number of libraries.

###UniswapV2 Core

- _UniswapV2Pair_ - responsible for the maintenance of liquidity pool and token swapping functionality. 
- _UniswapV2Factory_ -  responsible for the creation of token pair liquidity pools and the setting of liquidity provider fee. 
- _UniswapV2ERC20_ -  implements the ERC20 token interface but with an additional feature, this is something which I will explore a bit more later. 

###UniswapV2 Periphery

- _UniswapV2Router01_ and 02 - acts as a wrapper for V2Pair, handling liquidity addition and removal operations and token swapping functionalities. 
- _UniswapV2Migrator_ - provides liquidity migration service from Uniswap V1 to V2.

##Review of Source Code
Below are some of the interesting tips and tricks implemented in UniswapV2.
###1. ERC-20-Permit - a gas-efficient version of ERC-20

{% gist https://gist.github.com/francisldn/85a3ae683e0ab55de5b74df8811a434c %}

With a standard ERC-20 token, a tokenholder needs to execute ``approve`` transaction prior to executing ``transferFrom``. The ``approve`` function must be performed by an Externally-Owned Wallet (EOA) and this is meant to protect user's wallet from being drained by a malicious contract. But this also leads to degradation of user experience, as it means that a user would need to execute 2 transactions: ``approve`` and ``transferFrom`` to transfer a token, which is not gas-efficient. So can we implement a more gas-efficient version of ERC20 token while retain the security for user's funds?

**Solution**: With the addition of ``permit`` function, Uniswap implements offline signature for the approval of token transfer, which is gas-less. The signature can then be used for on-chain execution of ``transferFrom`` function (or any other functions). The ``permit`` function is also used in the V2 Router contract for user to withdraw liquidity from a liquidity pool (eg.``removeLiquidityWithPermit`` function). 

To prevent replay attack using the same signature multiple times (given it is done off-chain), a valid signature would consist of a few safeguards such as a clear definition of scope for the signature as defined in ``DOMAIN_SEPARATOR`` and ``PERMIT_TYPEHASH`` with non-reusable nonce which increments with every signature. The signature uses EIP-712 standard for signing and the ERC-20 implementation of permit extension follows EIP-2612 standard.

Smart contract developers who are interested in implementing or learning more about ERC-20-Permit may refer to the resources below:
* [Openzeppelin ERC-20-Permit (in draft status)](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/draft-ERC20Permit.sol)
* [A Long Way To Go: On Gasless Tokens and ERC20-Permit](https://soliditydeveloper.com/erc20-permit)
* [EIP-712](https://eips.ethereum.org/EIPS/eip-712) and [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612)
* https://forum.openzeppelin.com/t/why-is-erc20permit-better-than-approve-transferfrom/7478

###2. CREATE2 - creation of pair addresses
{% gist https://gist.github.com/francisldn/792b8e8bd966f4ebbadbcbd4d72f9f67 %}

There are 2 ways to create a new contract on EVM-compatible chain - ``CREATE`` and ``CREATE2``. In the UniswapV2Pair contract, ``CREATE2`` opcode (in assembly) is used to create a token pair liquidity pool address, which is a change from the V1 contract (where ``CREATE`` is used). 

``CREATE`` opcode is used to compute deployed contract address like so:
```
keccak256(senderAddress, nonce)
```
In Solidity, one can also create a new contract through ``new`` keyword, as below. Instantiating a new contract in this way uses the ``CREATE`` opcode, and contract address cannot be pre-determined in a deterministic manner (due to the use of nonce).
```
pair = new UniswapV2Pair()
```

``CREATE2`` opcode computes pre-determined contract address like so:
```
keccak256(0xFF, senderAddress, salt, bytecode)
```
* ``0xFF`` is a constant to prevent collision with the CREATE function
* senderAddress - sender's own address
* salt - an arbitrary value provided by the sender
* bytecode - contract's bytecode to be deployed

The advantage of ``CREATE2`` vs ``CREATE`` is that with ``CREATE2``, contract address can be pre-computed off-chain (therefore gas-less) and one can then send ETH to the pre-computed contract address even before the contract is deployed on-chain. One such use case is that a developer may distribute a pre-computed contract address to a user in advance without having to deploy the contract, and the contract can then be initialised when a user starts sending funds to it, thus allowing for gas fee saving.

To learn more about deploying contract using ``CREATE2``, below are some resources for your reference:
* https://github.com/miguelmota/solidity-create2-example 
* [Openzeppelin CREATE2 CLI ](https://docs.openzeppelin.com/cli/2.8/deploying-with-create2)
* https://ethereum.stackexchange.com/questions/92491/instantiating-a-smart-contract-in-uniswap-using-assembly-and-create2
* [EIP-1014: Skinny CREATE2](https://eips.ethereum.org/EIPS/eip-1014)

###3. Time-Weighted Average Price update
{% gist https://gist.github.com/francisldn/03da077f87cf0b68e4185f328e16d1b9 %}

In the UniswapV2Pair contract, token prices are updated based on time-weighted average prices (TWAP) mechanism. The way it works is that each transacted price is weighted by the time that has passed since the last block in which it was updated and the historical prices are then accumulated. This increases the difficulty and reduces the incentive to manipulate prices on Uniswap, especially for tokens with high trading volume. This is an improvement from Uniswap V1. The diagram below gives a good overview of the TWAP price mechanism (courtesy of Uniswap).
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqndvzjlzg91ro05t6zs.png) 

Given the improvement in price update mechanism in V2, which is more resistant to price manipulation, Uniswap V2 can therefore serve as an on-chain price oracle for other DApps. Uniswap has actually created the [_UniswapV2OracleLibrary_](https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2OracleLibrary.sol) exactly for such purpose.

**Implications**
As the token prices are not updated based on the actual reserve balance in the liquidity pool, there will exist discrepancies between the token balance calculated based on TWAP mechanism and the actual reserve balance. There are also tokens with deflationary or inflationary implementations which could lead to discrepancies in token balances. Tokens with balances greater than 2<sup>112</sup> will also lead to overflow of the storage slots for reserves, which could lead to failed trades. For all the cases mentioned above (not exhaustive), users can withdraw excess token balances or reserves through ``skim`` and ``sync`` functions.

###4. Fixed Point Binary Library (UQ112x112.sol)
```
library UQ112x112 {
    uint224 constant Q112 = 2**112;

    // encode a uint112 as a UQ112x112
    function encode(uint112 y) internal pure returns (uint224 z) {
        z = uint224(y) * Q112; // never overflows
    }

    // divide a UQ112x112 by a uint112, returning a UQ112x112
    function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
        z = x / uint224(y);
    }
}
```
**Problem**: Solidity lacks support for non-integer numeric types (such as fractions)

**Solution**: To overcome the problem above, Uniswap V2 uses the above library to handle fixed point binary numbers for updating prices. With fixed point binary numbers, there is a trade-off between range and precision, the higher the range of numbers, the lower the precision. In the case of Uniswap, the fixed point binary numbers are implemented as uint224, where the first 112 bits represent integers and the last 112 bits represent decimals. This means the numbers would have a range of [0, 2<sup>112</sup>-1] and a precision of <sup>1</sup>&#8260;<sub>112</sub>.

Readers who would like to learn more about fixed point binary numbers may refer to the excellent explanation in the video below:
* [Fixed Point Binary Fractions](https://www.youtube.com/watch?v=QFlbvSeBkwY)

###5. Lock
```
uint private unlocked = 1;

    modifier lock() {
        require(unlocked == 1, 'UniswapV2: LOCKED');
        unlocked = 0;
        _;
        unlocked = 1;
    }
```
Uniswap uses the ``lock`` modifier as a measure to prevent reentrancy attack. The most high profile reentrancy attack occurred in 2016 where the attacker drained 3.6 million ether from The DAO. 

The ``lock`` modifier prevents reentrancy attack by setting the initial value of ``unlocked`` state variable to 1. Once a function with ``lock`` modifier is executed, the ``unlocked`` state variable value will be set to 0. To reenter the contract, the ``unlocked`` state variable is required to be == 1, thus preventing reentrancy. The ``unlocked`` state variable value will return to 1 upon successful execution of the function or failed execution (reversion to original state). Uniswap applies the ``lock`` modifier to all the _UniswapV2Pair_ functions that perform external calls, such as ``mint``, ``burn``, ``swap`` and ``skim``.

Developers may also refer to the Openzeppelin contract suite for a [similar implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) of reentrancy guard.

Further resources:
* [Protect Your Solidity Smart Contracts From Reentrancy Attacks](https://medium.com/coinmonks/protect-your-solidity-smart-contracts-from-reentrancy-attacks-9972c3af7c21)
* [Openzeppelin ReentrancyGuard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)


_If you like the content, please hit the like button so that it can reach more people._