登链学习日记001-uniswapV2-01
主要合约
UniswapV2Factory工厂合约
function createPair( address tokenA, address tokenB ) external returns (address pair)
用create2创建交易对,并初始化pair中的token
UniswapV2Pair交易对合约
ERC20合约,提供LP代币
function mint(address to) external lock returns (uint liquidity)
给流动性提供者铸造LP function burn( address to ) external lock returns (uint amount0, uint amount1)
销毁LP,拿回代币 function swap( uint amount0Out, uint amount1Out, address to, bytes calldata data ) external
兑换代币
UniswapV2Router01路由合约
前端交互合约
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
)
external
override
ensure(deadline)
returns (uint amountA, uint amountB, uint liquidity)
添加流动性,根据用户提供的代币和数量,计算出实际需要的代币数量和LP,然后调用Pair合约的mint铸造代币
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public override ensure(deadline) returns (uint amountA, uint amountB)
移除流动性,调用Pair合约的burn销毁LP并返回代币
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override ensure(deadline) returns (uint[] memory amounts)
代币兑换,根据代币兑换路径和输入/输出代币,计算出输出/输入代币数量,调用Pair合约的swap进行交换
操作路径
添加流动性
- 调用Router合约的addLiquidity方法
- 计算出需要提供的代币数量,例如 池子里面 a:500 b:1000 amountDesired的比例最好是池子里的比例 现在想加流动性 a:100 b:300 =>最后是 a:100 b:200 1 现在想加流动性 a:100 b:180 =>最后是 a:90 b:180 2
- 把代币转给Pair合约,调用mint
- 由于Pair里面的reserve和balance没同步 amount = balance.sub(_reserve) 计算出刚转入的代币数量,再根据 xy = L^2(池子里还没有流动性) 或者 amounttotalSupply / reserve(按比例)计算出LP并mint给用户,最后更新Pair
交换代币
- 调用合约的swap方法
- 根据代币兑换路径和输入/输出代币数量计算出输出/输入代币数量。以x换y为例,根据(x1+x)(y-y1) = xy 推导出 x1y/(x+x1) = y1 手续费版 x1997y/(x1000+x1997) 计算出可以兑换出来的代币y的数量
- 由于可能是多路径兑换,比如 USDC=>WETH=>DAI ,在第一个池子中先兑换出WETH,再用WETH在第二个池子中兑换出DAI 返回的amounts 数组 中 amounts[0] 为输入的代币数量amounts[amounts.length-1]为想要兑换的代币数量
- 把代币转给第一个池子,再调用Pair合约的swap进行兑换
- 由于不确定我要兑换的代币是池子的token0还是token1,在调用swap(amount0Out, amount1Out, to, new bytes(0) 时传入两个数量,其中一个为0,不为0的是想要兑换的代币
- Pair合约把代币转给用户,此时是先把代币打进来的也不涉及回调,第一次用户先把代币1打给第一个池子,此时to为第二个池子的地址直接把代币2提前打进去,再次调用swap时to为用户地址把代币3转给用户,然后更新Pair
function swap( uint amount0Out, uint amount1Out, address to, bytes calldata data )
当to为合约地址时并且data不为空时可以实现闪电贷,合约先把代币转给to合约,to合约在uniswapV2Call函数中必须还回代币(代币0或者代币1都行),Pair合约会检查当前的代币乘积(去除手续费)不能小于原来的k值,否则回滚交易
移除流动性
- 调用合约的removeLiquidity方法
- 把LP转给Pair合约,调用burn
- 根据liquidity*balance / totalSupply 计算出可以拿到的代币数量转给用户并销毁LP,最后更新Pair
其他知识点
UniswapV2Library里的pairFor
不同的编译器结果不同,在UniswapV2Library中的pariFor拿池子的地址的hash由于是硬编码需要重新计算type(UniswapV2Pair).creationCode
Pair中的变量price0CumulativeLast
每次update都会跟新用于oracle计算价格。记录 Token1 对 Token0 的时间累积价格,用于下个时间段计算新的价格,新的价格 = (price0Cumulative - price0CumulativeLast) / (t - t0)
Pair中的_mintFee函数
UniswapV2Factory中的feeTo一旦设置,表示平台会收取一定手续费。平台收0.3%手续费的1/6 给到这个地址,每次 mint或者burn的时候收费,而不是用户swap的时候,收到的钱又添加到池子里继续拿lp。拿到增发的1/6 除以 (上次的+增发的5/6) = 等比例拿到池子里的lp => (k-klast)/6 / (klast+(k-klast)5/6) = lp/totalSupply => totalSupply(k-klast) / (k*5+klast) 用的这个公式
Pair中的MINIMUM_LIQUIDITY
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);代码中有最小流动性的限制。如果没有这个限制:
- 如果liquidity很小,池子里的代币很少,用户swap很容易导致价格大幅变化,而且流动性很差
- 恶意攻击,别人捐款给池子,然后调用sync这个函数让reserve 和balance同步(reserve很大,totalSupply很小),此时池子的利率不正常,,如果捐款了10个eth 那么添加流动性的人至少要超过10个eth才能拿到lp,amount0.mul(_totalSupply) / _reserve0 由于_reserve0很大,_totalSupply很小,那么amount0必须很大
闪电贷简单示例
- 合约中调用IUniswapV2Pair(pair).swap
- uniswapV2Call中先拿到Pair合约转过来的token
- 调用UniswapV2Library.getAmountsIn计算出要还的token数量(包含手续费)
- token授权给router,调用IUniswapV2Router01(router).swapExactTokensForTokens兑换token
- 如果换出来的token比原来借的多,就还给Pair,否则中断交易
声明
本人初学小白,记录智能合约学习知识,文章内容可信度低,仅供参考,谢谢!
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。