Como criar um produto constante AMM

porMatheusem21/07/2022

Nesse artigo iremos aprender a como criar um produto constante AMM através de contrato inteligente.

Produto Constante AMM

Produto constante AMM XY = K

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract CPAMM {
IERC20 public immutable token0;
IERC20 public immutable token1;
uint public reserve0;
uint public reserve1;
uint public totalSupply;
mapping(address => uint) public balanceOf;
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function _mint(address _to, uint _amount) private {
balanceOf[_to] += _amount;
totalSupply += _amount;
}
function _burn(address _from, uint _amount) private {
balanceOf[_from] -= _amount;
totalSupply -= _amount;
}
function _update(uint _reserve0, uint _reserve1) private {
reserve0 = _reserve0;
reserve1 = _reserve1;
}
function swap(address _tokenIn, uint _amountIn) external returns (uint amountOut) {
require(
_tokenIn == address(token0) || _tokenIn == address(token1),
"token inválido"
);
require(_amountIn > 0, "quantia = 0");
bool isToken0 = _tokenIn == address(token0);
(IERC20 tokenIn, IERC20 tokenOut, uint reserveIn, uint reserveOut) = isToken0
? (token0, token1, reserve0, reserve1)
: (token1, token0, reserve1, reserve0);
tokenIn.transferFrom(msg.sender, address(this), _amountIn);
/*
Quanto dy para dx?
xy = k
(x + dx)(y - dy) = k
y - dy = k / (x + dx)
y - k / (x + dx) = dy
y - xy / (x + dx) = dy
(yx + ydx - xy) / (x + dx) = dy
ydx / (x + dx) = dy
*/
// 0.3% gas fee
uint amountInWithFee = (_amountIn * 997) / 1000;
amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
tokenOut.transfer(msg.sender, amountOut);
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
function addLiquidity(uint _amount0, uint _amount1) external returns (uint shares) {
token0.transferFrom(msg.sender, address(this), _amount0);
token1.transferFrom(msg.sender, address(this), _amount1);
/*
Quanto dx, dy adicionar?
xy = k
(x + dx)(y + dy) = k'
Sem alteração de preço, antes e depois de adicionar liquidez
x / y = (x + dx) / (y + dy)
x(y + dy) = y(x + dx)
x * dy = y * dx
x / y = dx / dy
dy = y / x * dx
*/
if (reserve0 > 0 || reserve1 > 0) {
require(reserve0 * _amount1 == reserve1 * _amount0, "x / y != dx / dy");
}
/*
Quantas ações para cunhar?
f(x, y) = valor da liquidez
Nós vamos definir f(x, y) = sqrt(xy)
L0 = f(x, y)
L1 = f(x + dx, y + dy)
T = ações totais
s = ações para cunhar
Total de ações deve aumentar proporcionalmente ao aumento da liquidez
L1 / L0 = (T + s) / T
L1 * T = L0 * (T + s)
(L1 - L0) * T / L0 = s
*/
/*
Reivindicar (Claim)
(L1 - L0) / L0 = dx / x = dy / y
Proof
--- Equação 1 ---
(L1 - L0) / L0 = (sqrt((x + dx)(y + dy)) - sqrt(xy)) / sqrt(xy)
dx / dy = x / y então substitua dy = dx * y / x
--- Equação 2 ---
Equação 1 = (sqrt(xy + 2ydx + dx^2 * y / x) - sqrt(xy)) / sqrt(xy)
Multiplique por sqrt(x) / sqrt(x)
Equação 2 = (sqrt(x^2y + 2xydx + dx^2 * y) - sqrt(x^2y)) / sqrt(x^2y)
= (sqrt(y)(sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(y)sqrt(x^2))
sqrt(y) em cima e embaixo se cancela
--- Equação 3 ---
Equação 2 = (sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(x^2)
= (sqrt((x + dx)^2) - sqrt(x^2)) / sqrt(x^2)
= ((x + dx) - x) / x
= dx / x
Desde dx / dy = x / y,
dx / x = dy / y
Finalmente
(L1 - L0) / L0 = dx / x = dy / y
*/
if (totalSupply == 0) {
shares = _sqrt(_amount0 * _amount1);
} else {
shares = _min(
(_amount0 * totalSupply) / reserve0,
(_amount1 * totalSupply) / reserve1
);
}
require(shares > 0, "shares = 0");
_mint(msg.sender, shares);
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
function removeLiquidity(uint _shares)
external
returns (uint amount0, uint amount1)
{
/*
Reivindicar (Claim)
dx, dy = quantidade de liquidez para remover
dx = s / T * x
dy = s / T * y
Prova
Vamos encontrar dx, dy tal que
v / L = s / T
where
v = f(dx, dy) = sqrt(dxdy)
L = liquidez total = sqrt(xy)
s = ações
T = oferta total
--- Equação 1 ---
v = s / T * L
sqrt(dxdy) = s / T * sqrt(xy)
A quantidade de liquidez a ser removida não deve alterar o preço, portanto
dx / dy = x / y
substituir dy = dx * y / x
sqrt(dxdy) = sqrt(dx * dx * y / x) = dx * sqrt(y / x)
Divida os dois lados da Equação 1 com sqrt(y / x)
dx = s / T * sqrt(xy) / sqrt(y / x)
= s / T * sqrt(x^2) = s / T * x
Da mesma maneira
dy = s / T * y
*/
// bal0 >= reserve0
// bal1 >= reserve1
uint bal0 = token0.balanceOf(address(this));
uint bal1 = token1.balanceOf(address(this));
amount0 = (_shares * bal0) / totalSupply;
amount1 = (_shares * bal1) / totalSupply;
require(amount0 > 0 && amount1 > 0, "amount0 ou amount1 = 0");
_burn(msg.sender, _shares);
_update(bal0 - amount0, bal1 - amount1);
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
}
function _sqrt(uint y) private pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function _min(uint x, uint y) private pure returns (uint) {
return x <= y ? x : y;
}
}
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint amount);
event Approval(address indexed owner, address indexed spender, uint amount);
}

Testar no Remix