Uniswap最主要的公式 xy=k
而計算發行LP token會需要將k開根號以維持線性關係
當每次有人添加流動性時都會有手續費,因此多出的手續費資金將會填入流動性中
k1=原本投入的流動性 k2=收取手續費後的添加的流動性
S1=原本發給流動性提供者的LP token數量
Sm=增發的LP token數量
目標:求Sm
當項目方全拿的話,會按比例增發token且全部都項目方拿走
Z為比例 = 100% = 1
因此可以得到公式:
帶入值可以得出Sm
當流動性提供者全拿的話,不會增發token,因此每一token可以換得的資金變多
Z為比例 = 0% = 0
因此Sm = 0
當項目方拿走部分的話,會按比例增發token且全部都項目方拿走
Z為比例 = Z%
因此可以得到公式:
可看成LP token增發比例 等於 流動性增加比例乘上%數 (乘上%數讓流動性增加比例下降了,類似於打折)
將%數帶入Z可以得到Sm,即增發的token
V2中項目方拿走的是%數是1/6,因此帶入1/6
兌換函式中添加
//扣除手續費
_newAmountIn = (_amountIn * 997) / 1000;
//計算原始換出量
_amountOut = (_newAmountIn * reserveOut) / (_newAmountIn + reserveIn);
//兌換
function swap (address _tokenIn, address _tokenOut, uint _amountIn)
external
returns (uint _amountOut){
require(_tokenIn != address(0) && _tokenOut != address(0),"Address can not be 0x00");
require(_tokenIn != _tokenOut,"Address can not be the same");
//LPtoken池子地址
address LPtokenAddress = checkLPtokenAddress[_tokenIn][_tokenOut];
//返回數量
uint reserveIn = reserve[LPtokenAddress][_tokenIn];
uint reserveOut = reserve[LPtokenAddress][_tokenOut];
//原始流動性
uint k = reserveIn * reserveOut;
//扣除手續費
uint _newAmountIn = (_amountIn * 997) / 1000;
//計算換出量
_amountOut = (_newAmountIn * reserveOut) / (_newAmountIn + reserveIn);
//轉移ERC20
ERC20 token0 = ERC20(_tokenIn);
ERC20 token1 = ERC20(_tokenOut);
token0.transferFrom(msg.sender, address(this), _amountIn);
token1.transfer(msg.sender, _amountOut);
//池內數量調整
reserve[LPtokenAddress][_tokenIn] += _amountIn;
reserve[LPtokenAddress][_tokenOut] -= _amountOut;
//執行增發代幣
caculateFee(LPtokenAddress, _tokenIn, _tokenOut, k);
emit Swap(_tokenIn, _tokenOut, _amountIn, _amountOut);
}
由於Solidity沒有浮點數,當開根號時有小數點會被無條件捨去,因此在添加流動性公式時先將token數量都先放大,都乘上1e8,在兌換函式中最後調用了計算增發代幣的函式
//計算手續費並紀錄
function caculateFee (address LPtokenAddress, address _tokenIn, address _tokenOut, uint k) private returns(uint){
//返回數量
uint reserve0 = reserve[LPtokenAddress][_tokenIn];
uint reserve1 = reserve[LPtokenAddress][_tokenOut];
//新的流動性
uint newK = reserve0 * reserve1;
//增發數量
ERC20 LP = ERC20(LPtokenAddress);
uint totalSupply = LP.totalSupply();
uint sm = ((_sqrt(newK) - _sqrt(k)) * totalSupply) / ((5 * _sqrt(newK)) + _sqrt(k));
//紀錄獎勵代幣
checkLPtokenOwner[LPtokenAddress] += sm;
return sm;
}
這部分得出的數字先用mapping記錄下來,LPtoken小數部分為八位數,因此一顆為1e8單位。所以會另外寫一個發行的函式。
//執行增發獎勵
function mintLPtokenForOwner (address LPtokenAddress) public onlyOwner(){
//取得增發獎勵
uint sm = checkLPtokenOwner[LPtokenAddress];
//鑄造獎勵代幣
LPtoken lpTokenInstance = LPtoken(LPtokenAddress);
lpTokenInstance.mint(owner,sm);
//減去發行數量
checkLPtokenOwner[LPtokenAddress] -= sm;
emit MintLPtokenForOwner(sm);
}
此函式要加上onlyOwner。
完整版合約:
https://github.com/AaaronJasper/Smart-contract-practice/blob/main/contract/AMM.sol
有更新的再話新增新的episode
更:從10月17日開始,對一組特定代幣的交易(換幣)收取0.15%的固定interface fee(界面費用),即使用錢堆收費,使用協議則不改變。