Uniswap Smart Contract Breakdown

Explaining its functionality by grouping lines of code

Lakshay Maini
Coinmonks
Published in
10 min readFeb 25, 2022

--

You probably know the constant product formula (x*y=k) that powers Uniswap. But how does the Uniswap smart contract really work under the hood?

In this article, we are going to understand how Uniswap is implemented by breaking down its smart contract. We are going to examine a couple of hundred lines of Solidity code that generate $1.28 billion in revenue daily.

Spoiler alert: you will see a very efficient, elegant, and secure Solidity code ahead.

Here is the outline of this article:

  • How Uniswap works at a high level
  • How Uniswap code is organized
  • Uniswap functionalities
  • Core contracts: Pair (hard)
  • Core contracts: Factory (easy)
  • Periphery contract: Router (easy)
  • Fully annotated code

How Uniswap works at a high level

The whole purpose of Uniswap is to allow you to swap one ERC20 token for another. For example, you need Dogecoin but you only have Shiba coin. Uniswap allows you to sell your Dogecoin and get Shiba in return. This is all done in an automatic and decentralized fashion. Uniswap is just a decentralized exchange.

Exchanges can be implemented in two ways.

  1. Order book model: Buyers and sellers file orders. And the centralized system matches the buy orders to the sell orders. This is how the traditional stock exchange works.
  2. Automated market makers (AMM): There is no centralized matchmaker. There are people who provide both tokens (Dogecoin and Shiba). They are called liquidity providers. These liquidity providers create a pool of Dogecoin and Shiba tokens. Now traders can come and deposit Dogecoin and get Shiba in return. This is done automatically, without a centralized entity. Traders pay a small percentage fee for the trade which goes to liquidity providers for their services.

Uniswap uses the AMM technique. How does it determine the exchange rate in a pool? i.e. how many Shiba tokens is 1 Dogecoin worth? This is determined by the constant product formula (Dogecoin amount)*(Shiba amount)=k. During trades, this product must remain constant.

From “Uniswap — a Unique Exchange

Don’t worry if this is not completely clear. We will learn more about this formula and market dynamics as we go along in the rest of the article.

A note about Uniswap versions

Uniswap has 3 versions.

  • We’ll be working with v2.
  • v1 is too simple and does not have all the modern features.
  • v3 is essentially v2 but improved and optimized — its code is way more complicated than v2.

How Uniswap code is organized

Uniswap has 4 smart contracts in total. They are divided into core and periphery.

  1. Core is for storing the funds (the tokens) and exposing functions for swapping tokens, adding funds, getting rewards, etc.
  2. Periphery is for interacting with the core.

Core consists of the following smart contracts:

  1. Pair — a smart contract that implements the functionality for swapping, minting, burning of tokens. This contract is created for every exchange pair like Dogecoin ↔ Shiba.
  2. Factory — creates and keeps track of all Pair contracts
  3. ERC20 — for keeping track of ownership of pool. Think of the pool as a property. When liquidity providers provide funds to the pool, they get “pool ownership tokens” in return. These ownership tokens earn rewards (by traders paying a small percentage for each trade). When liquidity providers want their funds back, they just submit the ownership tokens back and get their funds + the rewards that were accumulated. The ERC20 contract keeps track of the ownership tokens.

Periphery consists of just one smart contract:

  1. Router is for interacting with the core. Provides functions such as swapExactETHForTokens, swapETHForExactTokens, etc.

Uniswap functionality

We talked about the 4 smart contracts that Uniswap has and how they are organized. But what’s the main functionality that these contracts implement? The main functionality is the following:

  1. Managing the funds (how tokens such as Dogecoin and Shiba are managed in the pool)
  2. Functions for liquidity providers — deposit more funds and withdraw the funds along with the rewards
  3. Functions for traders — swapping
  4. Managing pool ownership tokens
  5. Protocol fee — Uniswap v2 introduced a switchable protocol fee. This protocol fee goes to the Uniswap team for their efforts in maintaining Uniswap. At the moment, this protocol fee is turned off but it can be turned on in the future. When it’s on, the traders will still pay the same fee for trading but 1/6 of this fee will now go to the Uniswap team and the rest 5/6 will go to the liquidity providers as the reward for providing their funds.

In addition to the main functionality described above, Uniswap has another one that is not core to Uniswap but it’s a useful helper for other contracts in the Ethereum ecosystem:

  1. Price oracle — Uniswap tracks prices of tokens relative to each other and can be used as a price oracle for other smart contracts in the Ethereum ecosystem. Due to arbitrage (which we will learn about later in the article), Uniswap prices tend to closely follow the real market prices of tokens. So the Uniswap price oracle is a pretty good approximation of the real market prices.

Core contracts: Pair (hard)

Let’s now dig into the actual Solidity code of the Uniswap smart contracts. We will start with the Pair contract. This is the most complex of the 4 smart contracts. The rest will get easier.

The Pair contract implements the exchange between a pair of tokens such as Dogecoin and Shiba. The full code of the Pair smart contract can be found on Github under v2-core/contracts/UniswapV2Pair.sol

Let’s break it down line-by-line.

First, the import statements:

Next, the contract declaration:

  • The contract name is UniswapV2Pair
  • It implements the IUniswapV2Pair interface, which is just an interface for this contract (can be found here). It also extends the UniswapV2ERC20 contract. Why? For managing the pool ownership tokens. We will learn more about it later.
  • SafeMath is a library for dealing with overflow/underflow. UQ112x112 is a library for supporting floating numbers. Solidity does not support floats by default. This library represents floats using 224 bits. The First 112 bits are for the whole number, and the last 112 bits are for the fractional part.

Next, we will group the code by the functionality that it implements.

Managing the funds

A Uniswap Pair is an exchange between a pair of tokens such as Dogecoin and Shiba. These tokens are represented as token0 and token1 in the contract. They are the addresses of the ERC20 smart contracts that implement them.

reserve variables store how much of the token we have in this Pair.

You might wonder, where is the actual token stored? This is done in the ERC20 contract of the token itself. It’s not done in the Pair contract. The Pair contract just keeps track of the reserves. From the ERC20’s perspective, the Pair contract is just a regular user that can transfer and receive tokens, it has its own balance, etc.

This is how funds are managed across 3 smart contracts

The Pair contract calls ERC20’s functions such as balanceOf (with owner=Pair contract’s address) and transfer to manage the tokens (see my ERC20 Smart Contract Breakdown if you’re confused). Here is an example of how ERC20's transfer the function is used in the Pair contract.

The _update the function below is called whenever there are new funds deposited or withdrawn by the liquidity providers or tokens are swapped by the traders.

A few things happening in this function:

  • balance0 and balance1 are the balances of tokens in the ERC20. They are the return value of ERC20’s balanceOf function.
  • _reserve0 and _reserve1 are Uniswap’s previously known balances (last time balanceOf was checked).
  • All we do in this function is check for overflow (line 74), update price oracle (this will be explained in a later section), update reserves, and update a Sync event.

What’s the difference between the arguments _reserve0, _reserve1 and the stored variables reserve0, reserve1 (shown below)? They are essentially the same. The callers of the _update function already have read the reserve variables from storage and just pass them as arguments to the _update function. This is just a way to save on gas. Reading from storage is more expensive than reading from memory

You will notice this again and again: Uniswap absolutely loves efficiency and gas savings. They squeeze out every single performance point they can from Solidity. _reserve0 and _reserve1 are one example of it.

Minting and Burning

Now onto the next functionality — minting and burning. Minting is when a liquidity provider adds funds to the pool and as a result, new pool ownership tokens are minted (created out of thin air) for the liquidity provider. Burning is the opposite — liquidity provider withdraws funds (and the accumulated rewards) and his pool ownership tokens are burned (destroyed).

Let’s take a look at the mint function.

  • Immediately you might notice the gas savings again: reserve0, reserve1, and totalSupply are transferred from storage to memory (lines 111 and 118) so that it’s cheaper to read these values.
  • We read the balances of our contract (the Pair contract) on lines 112 and 113 and then calculate the amount of each token that was deposited.
  • The pink part of the code is for the optional protocol fee. We will examine it later.
  • totalSupply indicates the total supply of the pool ownership tokens and is a stored variable in the UniswapV2ERC20 contract (see my breakdown of it here). The Pair contract extends UniswapV2ERC20 which is why it has access to the totalSupply variable.
  • If totalSupply is 0, it means that this pool is brand new and we need to lock in MINIMIUM_LIQUIDITY the amount of pool ownership tokens to avoid division by zero in the liquidity calculations. The way it’s locked in is by sending it to the address zero. (no one knows the private key that will lead to the address zero so by sending funds to the address zero, you essentially lock the funds forever).
  • liquidity variable is the amount of new pool ownership tokens that need to be minted to the liquidity provider. The liquidity provider gets a proportional amount of pool ownership tokens depending on how many new funds he provides (line 123)
  • We finally mint new pool ownership tokens to the to address (line 126). to is the address of the liquidity provider (this will be provided by the Periphery contract called the Router which calls the mint function)

The way adding funds works is: they are just deposited to the ERC20 contracts (by calling transfer(from: liquidity provider’s address, to: Pair contract’s address, amount) for each token). Then the Pair contract will read the balances (lines 112 and 113) and compare them to the last known balances (lines 114 and 115). This is how the Pair contract can deduce the amounts deposited.

The burn function is just the mirror image of the mint function:

  • We again see gas savings on lines 135, 136, 137 and 143
  • balance0 and balance1 are total balances of the tokens in this pool. liquidity is the amount of pool ownership tokens that the liquidity provider (who wishes to cash out) has. Why do access the liquidity as the balance of address(this)? Because the liquidity was transferred to the Pair contract by the Periphery contract before calling the burn function.
  • We calculate the amounts of tokens to withdraw to the liquidity provider proportionally to how much liquidity (pool ownership tokens) he has (lines 144 and 145)
  • We then burn his liquidity and transfer the tokens to him.
  • Rewards to the liquidity provider are automatically withdrawn along with his funds. The math makes sure that rewards are accumulated properly and that you get more than you deposited.

That’s it for Part 1! I broke down this article into 2 parts because it was getting too long. I hope this was helpful. Let me know in the comments if you have any questions.

In Part 2 we will cover:

  • the rest of the Pair contract: swapping, pool ownership tokens, protocol fee, and price oracle.
  • Also the Factory, ERC20, and Router contracts of Uniswap

You can also check out my other articles on my Medium profile.

Want to Connect? Follow me on Twitter.

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read

--

--

Lakshay Maini
Coinmonks

Full-stack Blockchain Developer | Driven to Deliver Excellence