找回密码
 立即注册
首页 业界区 安全 solidity学习之AMM

solidity学习之AMM

左丘平莹 5 天前
什么是AMM

AMM即自动做市商(Automated Market Maker,简称 AMM),以创建流动性池的形式支持资产的去中心化交易,无需传统的对手盘订单匹配,允许用户随时进行交易并且成交。
实现逻辑

AMM中最流行的模型是恒定乘积自动做市商(CPAMM),即两种交易标的的乘积是一个固定的k值:k = x*y。
假设当前市场上有10瓶可乐和10美元,那么常数k就是100,可乐的价格为1元每瓶。如果有人拿出10美元交换可乐,此时的计算逻辑为先增加市场中美元的数量为20,那么可乐的数量就是100/20=5,因此这10美元交换到的可乐数为10-5=5,可乐现价为20/5=4美元,而交换得到的可乐均价为10/5=2美元。
可以看到CPAMM省去了中间价格的计算,而是直接以最终的数量变化来决定单次交易可兑换出的数量。并且随着数量变少,标的的价格会不断上升,能够有效地表现稀缺性的变化。
流动性池
在AMM中流动性池是人为添加的,用户通过向流动性池中发送代币来提高流动性,流动性越高的池子在相同的交易量下价格波动就会越小,为了鼓励用户添加流动性,流动性池会向LP(LIquidity Provider)发放奖励。
添加完流动性后会得到一种特殊的代币,即LP token,用来标识用户在流动性池中的权益,凭借LP token能够分享流动性池产生的手续费。用LP token取回一开始添加的流动性资产时,会受到池子本身流动性变化的影响,最终取出的两种资产的比例可能会发生变化。
具体实现

AMM合约本身也是一个ERC20合约,其中的代币就是LP token,此外定义了两种token作为交易的两端,合约支持接收和转出这两种token。
  1. contract simpleSwap is ERC20 {
  2.     IERC20 public token0;
  3.     IERC20 public token1;
  4.     uint256 public reserveToken0;
  5.     uint256 public reserveToken1;
  6.     constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") {
  7.         token0 = _token0;
  8.         token1 = _token1;
  9.     }
  10.    
  11.     event Mint(address indexed sender, uint amount0, uint amount1);
  12.           event Burn(address indexed sender, uint amount0, uint amount1);
  13.     event Swap (
  14.         address indexed sender,
  15.         uint amountIn,
  16.         address tokenIn,
  17.         uint amountOut,
  18.         address tokenOut
  19.     );
  20. }
复制代码
首先实现两个方法用来改变合约的流动性池,添加和取回流动性。
添加流动性时,LP的计算公式:

  • 如果流动性为0,那么LP = $\sqrt{\Delta x* \Delta y}$
  • 否则LP = $L*min(\frac {\Delta X} {x},\frac {\Delta y}{y})$
而取出流动性的时候, $\Delta x= \frac{\Delta L}{L}x$, $\Delta y = \frac {\Delta L}{L}y$,即根据当前LP占总LP的比例,取出对应比例的x代币和y代币。
  1. function addLiquidity(uint amountToken0, uint amountToken1) public returns (uint liquidity){
  2.     token0.transferFrom(msg.sender, address(this), amountToken0);
  3.     token1.transferFrom(msg.sender, address(this), amountToken1);
  4.     uint _totalSupply = totalSupply();
  5.     if (_totalSupply == 0) {
  6.         liquidity = sqrt(amountToken0*amountToken1);
  7.     } else {
  8.         liquidity = min((amountToken0*_totalSupply)/reserveToken0, (amountToken1*_totalSupply)/reserveToken1);
  9.     }
  10.     require(liquidity>0,"insufficient liquidity minted");
  11.     reserveToken0 = token0.balanceOf(address(this));
  12.     reserveToken1 = token1.balanceOf(address(this));
  13.     _mint(msg.sender, liquidity);
  14.     emit Mint(msg.sender, amountToken0, amountToken1);
  15. }
  16. function removeLiquidity(uint liquidityAmount) public returns(uint amount0, uint amount1) {
  17.     amount0 = (liquidityAmount * reserveToken0 / totalSupply());
  18.     amount1 = (liquidityAmount * reserveToken1 / totalSupply());
  19.     require(amount0>0 && amount1>0, "INSUFFICIENT_LIQUIDITY_BURNED");
  20.     _burn(msg.sender, liquidityAmount);
  21.     token0.transfer(msg.sender, amount0);
  22.     token1.transfer(msg.sender, amount1);
  23.     reserveToken0 = token0.balanceOf(address(this));
  24.     reserveToken1 = token1.balanceOf(address(this));
  25.     emit Burn(msg.sender, amount0, amount1);
  26. }
复制代码
接下来要实现swap方法,即用流动性池的一种token去交换另一种token,此时不改变LP token的数量,但是改变流动性池的比例分配。
在swap中,因为$k=x*y$,那么交易后有$k=(x+\Delta x)(y+\Delta y)$,因为交易前后k的值不改变,那么得到$\Delta y = -\frac{\Delta x *y}{x+\Delta x}$。
根据公式实现一个用来快速计算swap返回token数量的方法,既可以在swap中调用,也可以让用户在交易执行前预先知道能够获得的token数量。
  1.   function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
  2.       require(amountIn>0, "INSUFFICIENT AMOUNT");
  3.       require(reserveIn >0 && reserveOut>0,"INSUFFICIENT LIQUIDITY");
  4.       amountOut = amountIn * reserveOut / (reserveIn + amountIn);
  5.   }
复制代码
最终swap功能实现如下,支持两种token的相互转换,合约得到tokenIn,转出tokenOut给调用方。
  1.   function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns(uint amountOut, IERC20 tokenOut) {
  2.       require(amountIn>0, "INSUFFICIENT AMOUNT");
  3.       require(tokenIn==token0||tokenIn==token1, "INVALID TOKEN");
  4.       if(tokenIn==token0) {
  5.           amountOut = getAmountOut(amountIn, reserveToken0, reserveToken1);
  6.           tokenOut = token1;
  7.       } else {
  8.           amountOut = getAmountOut(amountIn, reserveToken1, reserveToken0);
  9.           tokenOut = token0;
  10.       }
  11.       require(amountOut>=amountOutMin, "INSUFFICIENT OUTPUT AMOUNT");
  12.       tokenIn.transferFrom(msg.sender, address(this), amountIn);
  13.       tokenOut.transfer(msg.sender, amountOut);
  14.       reserveToken0 = token0.balanceOf(address(this));
  15.       reserveToken1 = token1.balanceOf(address(this));
  16.       emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut));
  17.   }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册