solidity學習之AMM
什么是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));
}

浙公網安備 33010602011771號