恒定乘积自动做市商算法
一、公式推演
1、交换
△y = (△x * y ) / △x + x
2、添加流动性
S = √(△x * △y)
T = √(x * y)
x / y = (x + △x) / (y + △y) => △x / △y = x / y
时段 | 流动性 |
---|---|
L0 添加之前的lq | T |
L1 添加之后的lq | T+S |
L0/L1 = T/(T+S)
S = (△x / x) * T = (△y / y) * T
3、移除流通性
△x = x * (S / T)
△y = y * (S / T)
二、智能合约编写
1、合约结构设计
- 在开始编写之前,需要明确定义合约的功能和结构。这个合约是一个自动市场做市商(AMM),主要包括交换、提供流动性和移除流动性等功能。
2、导入依赖:
- 确保导入了所需的依赖文件,如
IERC20.sol
,它定义了 ERC20 标准接口。
3、合约定义:
- 定义合约名称为
CPAMM
,采用 Solidity 的版本^0.8.13
。 - 定义两种代币
token0
和token1
,这些代币通过IERC20
接口定义。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./IERC20.sol";
contract CPAMM {
// 定义代币0和代币1的储备量 reserve0 和 reserve1。
IERC20 public immutable token0;
IERC20 public immutable token1;
// 定义合约的总供应量 totalSupply 和用户余额的映射 balanceOf。
uint public totalSupply;
mapping(address => uint) public balanceOf;
}
4、构造函数:
- 编写构造函数
constructor
,在合约初始化时传入两种代币的地址,并将其存储在状态变量中。
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
5、内部函数定义:
- 定义一些内部函数,如
_mint
、_burn
、_sqrt
、_update
和_min
,用于增发代币、销毁代币、计算平方根、更新代币储备量和计算两个数中的最小值等功能。
// 私有函数,用于增发代币给指定地址
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 _sqrt(uint y) internal 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 _update(uint _reserve0, uint _reserve1) private {
reserve0 = _reserve0;
reserve1 = _reserve1;
}
// 返回两个数中较小的数
function _min(uint _x, uint _y) private pure returns (uint) {
return _x > _y ? _y : _x;
}
6、交换功能(swap
函数):
- 编写交换函数,用户通过提供其中一种代币来交换获得另一种代币。在函数中,需要进行一些检查,如检查输入金额是否有效、检查输入代币是否合法等。
- 计算输出代币的数量,并将输出代币转给用户。
- 更新代币储备量。
function swap(address _tokenIn, uint _amountIn) external returns (uint amountOut) {
// 检查输入金额是否有效
require(_amountIn > 0, "Invalid Amount");
// 检查输入代币是否合法
require(_tokenIn == address(token0) || _tokenIn == address(token1), "Invalid token");
// 确定输入代币和输出代币
bool isToken0 = _tokenIn == address(token0);
(IERC20 tokenIn, IERC20 tokenOut) =
isToken0 ? (token0, token1) : (token1, token0);
// 确定输入代币和输出代币的储备量
(uint reserveIn, uint reserveOut) =
isToken0 ? (reserve0, reserve1) : (reserve1, reserve0);
// 将输入代币转入合约
tokenIn.transferFrom(msg.sender, address(this), _amountIn);
// 计算输出代币数量(△y = (△x * y ) / △x + x)
amountOut = (_amountIn * reserveOut) / (reserveIn + _amountIn);
// 将输出代币转给用户
tokenOut.transfer(msg.sender, amountOut);
// 更新代币储备量
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
7、添加流动性功能(addLiquidity
函数):
- 编写添加流动性函数,用户可以将两种代币同时提供给合约以增加流动性。需要检查提供的金额是否有效,并将两种代币转入合约。
- 计算用户获得的份额,并增发份额给用户。
- 更新代币储备量。
function addLiquidity(uint _amount0, uint _amount1) external returns (uint shares) {
// 检查提供的金额是否有效
require(_amount0 > 0 && _amount1 > 0, "Invalid amount");
// 将代币0和代币1转入合约
token0.transferFrom(msg.sender, address(this), _amount0);
token1.transferFrom(msg.sender, address(this), _amount1);
// 如果储备量不为零,则检查提供的代币比例是否匹配
if (reserve0 > 0 || reserve1 > 0) {
require(_amount0 * reserve1 == _amount1 * reserve0, "dy/dx != y/x");
}
// 计算用户获得的份额
if (totalSupply == 0) {
shares = _sqrt(_amount0 * _amount1);
} else {
// 取小的S,S = (△x / x) * T = (△y / y) * T
shares = _min(
(_amount0 * totalSupply) / reserve0,
(_amount1 * totalSupply) / reserve1
);
}
// 检查份额是否有效
require(shares > 0, "share is zero");
// 增发份额给用户
_mint(msg.sender, shares);
// 更新代币储备量
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
8、移除流动性功能(removeLiquidity
函数):
- 编写移除流动性函数,用户可以根据持有的份额从合约中提取相应的代币。计算用户提取的代币数量,并将相应的代币转回给用户。
- 更新代币储备量。
function removeLiquidity(uint _shares) external returns (uint amount0, uint amount1) {
// 检查提供的份额是否有效
require(_shares > 0, "Invalid shares");
// 计算提取的代币数量
amount0 = (_shares * reserve0) / totalSupply;
amount1 = (_shares * reserve1) / totalSupply;
// 销毁用户的份额
_burn(msg.sender, _shares);
// 将相应的代币转回给用户
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
// 更新代币储备量
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}