April 9, 2022

Uniswap 2.0

0) Actors:

  • Traders
    • Exchange one asset for another asset
  • Liquidity providrs
    • Willingly accept trades against their portfolio in exchange for a fee.
  • Arbitrageurs
    • Maintain the price of assets within that portfolio in accordance with the market price in exchange for a profit.
A screenshot of this blog with two code blocks stacked vertically. They look identical except that the bottom has a larger font size.

source: https://docs.uniswap.org/contracts/v2/concepts/core-concepts/pools

A screenshot of this blog with two code blocks stacked vertically. They look identical except that the bottom has a larger font size.

source: https://docs.uniswap.org/contracts/v2/concepts/core-concepts/pools]

1) AMMs

  • Automated Liquidity Management
  • Automated middlemen between: (pool-resources-between-traders-providers) a) Liquidity providers b) Traders

2) Constant product market maker

The term “constant function” refers to the fact that any trade must change the reserves in such a way that the product of those reserves remains unchanged (i.e. equal to a constant).


X x Y = k

Where x is ether reserve, y is token reserve (or vice versa), and k is a constant. Uniswap requires that k remains the same no matter how much of reserves of x or y there are.
When you trade ether for tokens you deposit your ethers into the contract and get some amount of tokens in return. Uniswap ensures that after each trade k remains the same

A screenshot of this blog with two code blocks stacked vertically. They look identical except that the bottom has a larger font size.

3) Looking back to V1 liquidity Management

To trade only ether-token swaps

Q. What do we need?

a) assets aka token contract

b) medium of exchange aka exchange contract I) register recognizable token II) provide liquidity c) Other key terms I) pricing function II) token_reserve

  • how much token the exchange has?

4) AMM in action

4.1) Token contract

// contracts/Token.sol
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Token is ERC20 {
constructor(
string memory name,
string memory symbol,
uint256 initialSupply
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
}

4.2) Exchange contract

// contracts/Token.sol
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Token is ERC20 {
constructor(
string memory name,
string memory symbol,
uint256 initialSupply
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
}

4.3) Pricing function

Δy = (x + Δx) / (y * Δx)

Py = x / y

4.3e Modified Pricing function

  • Δx is the amount of ethers or tokens we’re trading for Δy or viceversa
(x + Δx)(y - Δy) = xy

Δy = (x + Δx) / (y * Δx)

Δx = (y * Δy) / (x + Δy)

4.3.1) GetAmount

function getAmount(
uint256 inputAmount,
uint256 inputReserve,
uint256 outputReserve
) private pure returns (uint256) {
require(inputReserve > 0 && outputReserve > 0, "invalid reserves");

return (inputAmount * outputReserve) / (inputReserve + inputAmount);
}

4.3.2) GetAmountXOrY

function getTokenAmount(uint256 _ethSold) public view returns (uint256) {
require(_ethSold > 0, "ethSold is too small");

uint256 tokenReserve = getReserve();

return getAmount(_ethSold, address(this).balance, tokenReserve);
}

function getEthAmount(uint256 _tokenSold) public view returns (uint256) {
require(_tokenSold > 0, "tokenSold is too small");

uint256 tokenReserve = getReserve();

return getAmount(_tokenSold, tokenReserve, address(this).balance);
}

5) Liquidity tokens


function getAmount(
uint256 inputAmount,
uint256 inputReserve,
uint256 outputReserve
) private pure returns (uint256) {
require(inputReserve > 0 && outputReserve > 0, "invalid reserves");

uint256 inputAmountWithFee = inputAmount * 99;
uint256 numerator = inputAmountWithFee * outputReserve;
uint256 denominator = (inputReserve * 100) + inputAmountWithFee;

return numerator / denominator;
}

function addLiquidity(uint256 _tokenAmount)
public
payable
returns (uint256)
{
if (getReserve() == 0) {
...

uint256 liquidity = address(this).balance;
_mint(msg.sender, liquidity);

return liquidity;

} else {
uint256 liquidity = (totalSupply() * msg.value) / ethReserve;
_mint(msg.sender, liquidity);

return liquidity;
}

function removeLiquidity(uint256 _amount) public returns (uint256, uint256) {
require(_amount > 0, "invalid amount");

uint256 ethAmount = (address(this).balance * _amount) / totalSupply();
uint256 tokenAmount = (getReserve() * _amount) / totalSupply();

_burn(msg.sender, _amount);
payable(msg.sender).transfer(ethAmount);
IERC20(tokenAddress).transfer(msg.sender, tokenAmount);

return (ethAmount, tokenAmount);
}

6) Swapping Eth To Token


function ethToTokenSwap(uint256 _minTokens) public payable {
uint256 tokenReserve = getReserve();
uint256 tokensBought = getAmount(
msg.value,
address(this).balance - msg.value,
tokenReserve
);

require(tokensBought >= _minTokens, "insufficient output amount");

IERC20(tokenAddress).transfer(msg.sender, tokensBought);
}

7) Swapping Swapping Token To Eth


function tokenToEthSwap(uint256 _tokensSold, uint256 _minEth) public {
uint256 tokenReserve = getReserve();
uint256 ethBought = getAmount(
_tokensSold,
tokenReserve,
address(this).balance
);

require(ethBought >= _minEth, "insufficient output amount");

IERC20(tokenAddress).transferFrom(msg.sender, address(this), _tokensSold);
payable(msg.sender).transfer(ethBought);
}

8) Factory creating exchanges

pragma solidity ^0.8.0;

import "./Exchange.sol";

contract Factory {
mapping(address => address) public tokenToExchange;

function createExchange(address _tokenAddress) public returns (address) {
require(_tokenAddress != address(0), "invalid token address");
require(
tokenToExchange[_tokenAddress] == address(0),
"exchange already exists"
);

Exchange exchange = new Exchange(_tokenAddress);
tokenToExchange[_tokenAddress] = address(exchange);

return address(exchange);
}

function getExchange(address _tokenAddress) public view returns (address) {
return tokenToExchange[_tokenAddress];
}
}

9) Now more into V2. (Will add stuffs as I get time to work on it...)

How to locate a pool, given two token addresses

// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}

Q. Why this way?
=> This is very gas efficient since there is no storage access.

Shoutouts:

  1. https://jeiwan.net/posts/programming-defi-uniswap-2/
  2. https://hackmd.io/@HaydenAdams/HJ9jLsfTz
  3. https://medium.com/bollinger-investment-group/constant-function-market-makers-defis-zero-to-one-innovation-968f77022159
  4. https://github.com/Jeiwan/zuniswap/tree/part_2
  5. https://betterprogramming.pub/uniswap-v2-in-depth-98075c826254

Thanks for reading! If you want to see future content, you can follow me on Twitter or get connected over at LinkedIn.


Support My Content

If you find my content helpful, consider supporting a humanitarian cause (building homes for elderly people in rural Terai region of Nepal) that I am planning with your donation:

Ethereum (ETH)

0xb0b1B20062DA9Dd7BaA4D5b088DF49dbe4b46fF2

Thank you for your support!