什么是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。- contract simpleSwap is ERC20 {
- IERC20 public token0;
- IERC20 public token1;
- uint256 public reserveToken0;
- uint256 public reserveToken1;
- constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") {
- token0 = _token0;
- token1 = _token1;
- }
-
- event Mint(address indexed sender, uint amount0, uint amount1);
- event Burn(address indexed sender, uint amount0, uint amount1);
- event Swap (
- address indexed sender,
- uint amountIn,
- address tokenIn,
- uint amountOut,
- address tokenOut
- );
- }
复制代码 首先实现两个方法用来改变合约的流动性池,添加和取回流动性。
添加流动性时,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代币。- function addLiquidity(uint amountToken0, uint amountToken1) public returns (uint liquidity){
- token0.transferFrom(msg.sender, address(this), amountToken0);
- token1.transferFrom(msg.sender, address(this), amountToken1);
- uint _totalSupply = totalSupply();
- if (_totalSupply == 0) {
- liquidity = sqrt(amountToken0*amountToken1);
- } else {
- liquidity = min((amountToken0*_totalSupply)/reserveToken0, (amountToken1*_totalSupply)/reserveToken1);
- }
- require(liquidity>0,"insufficient liquidity minted");
- reserveToken0 = token0.balanceOf(address(this));
- reserveToken1 = token1.balanceOf(address(this));
- _mint(msg.sender, liquidity);
- emit Mint(msg.sender, amountToken0, amountToken1);
- }
- function removeLiquidity(uint liquidityAmount) public returns(uint amount0, uint amount1) {
- amount0 = (liquidityAmount * reserveToken0 / totalSupply());
- amount1 = (liquidityAmount * reserveToken1 / totalSupply());
- require(amount0>0 && amount1>0, "INSUFFICIENT_LIQUIDITY_BURNED");
- _burn(msg.sender, liquidityAmount);
- token0.transfer(msg.sender, amount0);
- token1.transfer(msg.sender, amount1);
- reserveToken0 = token0.balanceOf(address(this));
- reserveToken1 = token1.balanceOf(address(this));
- emit Burn(msg.sender, amount0, amount1);
- }
复制代码 接下来要实现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数量。- function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
- require(amountIn>0, "INSUFFICIENT AMOUNT");
- require(reserveIn >0 && reserveOut>0,"INSUFFICIENT LIQUIDITY");
- amountOut = amountIn * reserveOut / (reserveIn + amountIn);
- }
复制代码 最终swap功能实现如下,支持两种token的相互转换,合约得到tokenIn,转出tokenOut给调用方。- function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns(uint amountOut, IERC20 tokenOut) {
- require(amountIn>0, "INSUFFICIENT AMOUNT");
- require(tokenIn==token0||tokenIn==token1, "INVALID TOKEN");
- if(tokenIn==token0) {
- amountOut = getAmountOut(amountIn, reserveToken0, reserveToken1);
- tokenOut = token1;
- } else {
- amountOut = getAmountOut(amountIn, reserveToken1, reserveToken0);
- tokenOut = token0;
- }
- require(amountOut>=amountOutMin, "INSUFFICIENT OUTPUT AMOUNT");
- tokenIn.transferFrom(msg.sender, address(this), amountIn);
- tokenOut.transfer(msg.sender, amountOut);
- reserveToken0 = token0.balanceOf(address(this));
- reserveToken1 = token1.balanceOf(address(this));
- emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut));
- }
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |