一、公式推演


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
  • 定义两种代币 token0token1,这些代币通过 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)));
}