DeFi Protocol by code

DeFi Protocol by code

Share this post

DeFi Protocol by code
DeFi Protocol by code
Uniswap V2 — Complete Guide to understand the DeFi Protocol from its Smart Contracts

Uniswap V2 — Complete Guide to understand the DeFi Protocol from its Smart Contracts

DEEP DIVE TO UNDERSTAND UNISWAP V2 SMART CONTRACTS

Bloqarl's avatar
Bloqarl
Apr 12, 2023
∙ Paid

Share this post

DeFi Protocol by code
DeFi Protocol by code
Uniswap V2 — Complete Guide to understand the DeFi Protocol from its Smart Contracts
Share

DEEP DIVE TO UNDERSTAND UNISWAP V2 SMART CONTRACTS

In order to understand the different components that we are going to go through while analysing the code first it is important to know which are the main concepts and which is their role. So, bare with me because it is going to be worth it.

I have summarised in 5 paragraphs the main important concepts that you need to know exist and that you will understand by the end of this article.


Uniswap is a decentralised exchange protocol. This protocol is a suite of persistent, non-upgradable smart contracts that together create an automated market maker.

The Uniswap ecosystem consists of liquidity providers who contribute to liquidity, traders who swap the tokens and developers who interact with smart contracts to develop new interactions for the tokens.

Each Uniswap smart contract, or pair, manages a liquidity pool made up of reserves of two ERC-20 tokens.

Every liquidity pool rebalances to maintain a 50/50 proportion of cryptocurrency assets, which in turn determines the price of the assets.

Liquidity providers can be anyone who is able to supply equal values of ETH and an ERC-20 token to a Uniswap exchange contract. In return they are given Liquidity Provider Tokens (LP tokens represent the share of the pool owned by a liquidity provider) from the exchange contract which can be used to withdraw their proportion of the liquidity pool at any time.


The main smart contracts in their repository are these:

  • UniswapV2ERC20 — an extended ERC20 implementation that’s used for LP-tokens. It additionally implements EIP-2612 to support off-chain approval of transfers.

  • UniswapV2Factory — similarly to V1, this is a factory contract that creates pair contracts and serves as a registry for them. The registry uses create2 to generate pair addresses–we’ll see how it works in details.

  • UniswapV2Pair — the main contract that’s responsible for the core logic. It’s worth noting that the factory allows to create only unique pairs to not dilute liquidity.

  • UniswapV2Router — the main entry point for the Uniswap UI and other web and decentralized applications working on top of Uniswap.

  • UniswapV2Library — a collection of helper functions that implement important calculations.

In this article we will be mentioning all of these but we will focus mainly on going through UniswapV2Router and UniswapV2Factory code, although UniswapV2Pair and UniswapV2Library are going to be very involved.

UniswapV2Router02.sol

This contract makes it easier to create pairs, add and remove liquidity, calculate prices for all possible swap variations and perform actual swaps. Router works with all pairs deployed via the Factory contract

You will need to create an instance in your contract in order to call addLiquidity, removeLiquidity and swapExactTokensForTokens functions

address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

IUniswapV2Router02 public uniswapV2Router;
uniswapV2Router = IUniswapV2Router02();

Let’s now look into liquidity management:

FUNCTION addLiquidity():

function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
  • tokenA and tokenB: are the tokens we need to get or create the pair we want to add liquidity to.

  • amountADesired and amountBDesired are the amounts we want to deposit into the liquidity pool.

  • amountAMin and amountBMin are the minimal amounts we want to deposit.

  • to address is the address that receives LP-tokens.

  • deadline, would most commonly be the block.timestamp

Inside internal _addLiquidity() it will check if a pair of those two tokens already exists and in case it doesn’t it will create a new one

if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
    IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}

Then it needs to get the existing amount of the tokens or also known as reserveA and reserveB, which we can access through UniswapV2Pair contract

IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves()

Now, the external function addLiquidity, returns (uint amountA, uint amountB, uint liquidity), so how does it calculate it?

After getting the reserves mentioned above through UniswapV2Library, there are a series of checks

If the pair didn’t exist and a new one was newly created amountA and amountB returned will be amountADesired and amountBDesired which were passed as parameters (See above).

Otherwise, it will do this operation

amountBOptimal = amountADesired.mul(reserveB) / reserveA;

if the amountB is smaller or equal to the amountBDesired, then it will return:

(uint amountA, uint amountB) = (amountADesired, amountBOptimal)

Otherwise, it will return

(uint amountA, uint amountB) = (amountAOptimal, amountBDesired)

where amountAOptimal is calculated the same way as amountBOptimal

Then, to calculate the liquidity to return will go through the following:

First of all, it is going to deploy the UniswapV2Pair contract with the address of the existing/newly created pair.

How does it do that? It calculates the CREATE2 address for a pair without making any external call with: (Read more about CREATE2 Opcode)

pair = address(uint(keccak256(abi.encodePacked(
    hex'ff',
    factory,
    keccak256(abi.encodePacked(token0, token1)),
   hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' 
))));

Then, it gets the address of the newly deployed contract, which we will need in order to mint the tokens from this pair of tokens.

When you add liquidity to a pair, the contract mints LP-tokens; when you remove liquidity, LP-tokens get burned.

So, first we get the address using pairFor from UniswapV2Library:

address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

So, that later can mint the ERC20 tokens and calculate the liquidity to return:

liquidity = IUniswapV2Pair(pair).mint(to);

In case you’re wondering why it ends up being an ERC20, inside the mint function it’s storing it like this

uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));

FUNCTION removeLiquidity():

function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB);

Keep reading with a 7-day free trial

Subscribe to DeFi Protocol by code to keep reading this post and get 7 days of free access to the full post archives.

Already a paid subscriber? Sign in
© 2025 Bloqarl
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share