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.
source: https://docs.uniswap.org/contracts/v2/concepts/core-concepts/pools
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
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
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:
- https://jeiwan.net/posts/programming-defi-uniswap-2/
- https://hackmd.io/@HaydenAdams/HJ9jLsfTz
- https://medium.com/bollinger-investment-group/constant-function-market-makers-defis-zero-to-one-innovation-968f77022159
- https://github.com/Jeiwan/zuniswap/tree/part_2
- https://betterprogramming.pub/uniswap-v2-in-depth-98075c826254