Uniswap-v2 Contract Walk-Through
Introduction
Uniswap v2 can create an exchange market between any two ERC-20 tokens. In this article we will go over the source code for the contracts that implement this protocol and see why they are written this way.
What Does Uniswap Do?
Basically, there are two types of users: liquidity providers and traders.
The liquidity providers provide the pool with the two tokens that can be exchanged (we'll call them Token0 and Token1). In return, they receive a third token that represents partial ownership of the pool called a liquidity token.
Traders send one type of token to the pool and receive the other (for example, send Token0 and receive Token1) out of the pool provided by the liquidity providers. The exchange rate is determined by the relative number of Token0s and Token1s that the pool has. In addition, the pool takes a small percent as a reward for the liquidity pool.
When liquidity providers want their assets back they can burn the pool tokens and receive back their tokens, including their share of the rewards.
Click here for a fuller description.
Why v2? Why not v3?
Uniswap v3 is an upgrade that is much more complicated than the v2. It is easier to first learn v2 and then go to v3.
Core Contracts vs Periphery Contracts
Uniswap v2 is divided into two components, a core and a periphery. This division allows the core contracts, which hold the assets and therefore have to be secure, to be simpler and easier to audit. All the extra functionality required by traders can then be provided by periphery contracts.
Data and Control Flows
This is the flow of data and control that happens when you perform the three main actions of Uniswap:
- Swap between different tokens
- Add liquidity to the market and get rewarded with pair exchange ERC-20 liquidity tokens
- Burn ERC-20 liquidity tokens and get back the ERC-20 tokens that the pair exchange allows traders to exchange
Swap
This is most common flow, used by traders:
Caller
- Provide the periphery account with an allowance in the amount to be swapped.
- Call one of the periphery contract's many swap functions (which one depends on whether ETH is involved or not, whether the trader specifies the amount of tokens to deposit or the amount of tokens to get back, etc).
Every swap function accepts a
path, an array of exchanges to go through.
In the periphery contract (UniswapV2Router02.sol)
- Identify the amounts that need to be traded on each exchange along the path.
- Iterates over the path. For every exchange along the way it sends the input token and then calls the exchange's
swapfunction. In most cases the destination address for the tokens is the next pair exchange in the path. In the final exchange it is the address provided by the trader.
In the core contract (UniswapV2Pair.sol)
- Verify that the core contract is not being cheated and can maintain sufficient liquidity after the swap.
- See how many extra tokens we have in addition to the known reserves. That amount is the number of input tokens we received to exchange.
- Send the output tokens to the destination.
- Call
_updateto update the reserve amounts
Back in the periphery contract (UniswapV2Router02.sol)
- Perform any necessary cleanup (for example, burn WETH tokens to get back ETH to send the trader)
Add Liquidity
Caller
- Provide the periphery account with an allowance in the amounts to be added to the liquidity pool.
- Call one of the periphery contract's
addLiquidityfunctions.
In the periphery contract (UniswapV2Router02.sol)
- Create a new pair exchange if necessary
- If there is an existing pair exchange, calculate the amount of tokens to add. This is supposed to be identical value for both tokens, so the same ratio of new tokens to existing tokens.
- Check if the amounts are acceptable (callers can specify a minimum amount below which they'd rather not add liquidity)
- Call the core contract.
In the core contract (UniswapV2Pair.sol)
- Mint liquidity tokens and send them to the caller
- Call
_updateto update the reserve amounts
Remove Liquidity
Caller
- Provide the periphery account with an allowance of liquidity tokens to be burned in exchange for the underlying tokens.
- Call one of the periphery contract's
removeLiquidityfunctions.
In the periphery contract (UniswapV2Router02.sol)
- Send the liquidity tokens to the pair exchange
In the core contract (UniswapV2Pair.sol)
- Send the destination address the underlying tokens in proportion to the burned tokens. For example if there are 1000 A tokens in the pool, 500 B tokens, and 90 liquidity tokens, and we receive 9 tokens to burn, we're burning 10% of the liquidity tokens so we send back the user 100 A tokens and 50 B tokens.
- Burn the liquidity tokens
- Call
_updateto update the reserve amounts
The Core Contracts
These are the secure contracts which hold the liquidity.
UniswapV2Pair.sol
This contract implements the actual pool that exchanges tokens. It is the core Uniswap functionality.
1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Pair.sol';4import './UniswapV2ERC20.sol';5import './libraries/Math.sol';6import './libraries/UQ112x112.sol';7import './interfaces/IERC20.sol';8import './interfaces/IUniswapV2Factory.sol';9import './interfaces/IUniswapV2Callee.sol';10Toon alleKopiëren
These are all the interfaces that the contract needs to know about, either because the contract implements them (IUniswapV2Pair and UniswapV2ERC20) or because it calls contracts that implement them.
1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {2Kopiëren
This contract inherits from UniswapV2ERC20, which provides the ERC-20 functions for the liquidity tokens.
1 using SafeMath for uint;2Kopiëren
The SafeMath library is used to avoid overflows and underflows. This is important because otherwise we might end up with a situation where a value should be -1, but is instead 2^256-1.
1 using UQ112x112 for uint224;2Kopiëren
A lot of calculations in the pool contract require fractions. However, fractions are not supported by the EVM.
The solution that Uniswap found is to use 224 bit values, with 112 bits for the integer part, and 112 bits for the fraction. So 1.0 is represented as 2^112, 1.5 is represented as 2^112 + 2^111, etc.
More details about this library are available later in the document.
Variables
1 uint public constant MINIMUM_LIQUIDITY = 10**3;2Kopiëren
To avoid cases of division by zero, there is a minimum number of liquidity tokens that always exist (but are owned by account zero). That number is MINIMUM_LIQUIDITY, a thousand.
1 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));2Kopiëren
This is the ABI selector for the ERC-20 transfer function. It is used to transfer ERC-20 tokens in the two token accounts.
1 address public factory;2