导入包
定义UniswapV2Pair合约
定义一个UniswapV2Pair合约,实现了IUniswapV2Pair与UniswapV2ERC20接口
`contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {}`
Pair管理资金
- Uniswap Pair是一对代币之间的交换,例如Dogecoin和shiba。这些代币在合约中表示为
token0
和token1
。它们是实现ERC20智能合约的地址
- reserve0和reserve1变量存储这两个币的数量
Pair图解
- Pair合约本身不存储实际的代币,只是作为跟踪储备。从ERC20的角度来看,Pair合约只是说一个普通用户,Pair合约只是一个普通用户,可以转账和接收代币,它有自己的余额等
Pair合约调用ERC20函数
- 将ERC20的
transfer
函数调用打包成bytes4,供底层call调用,用这个方式调用比较节省gas
- 在
_safeTransfer
函数中进行调用,第一个参数是布尔值,成功调用为true, 第二个参数是返回来的数据 - 接着判断调用成功 && (返回字节数据长度为0 || abi解码参数 ),都不满足条件,则抛出交易失败的提示
流动性提供者存储、提取新资金和交换代币
balance0
、balance1
是 ERC20中的代币余额。它们是ERC20balanceOf
函数返回的余额- reserve0、reserve1 如刚刚所提的代币数量,随着调用更新
require(balance0 <=uint112(-1) && balance1 <=uint112(-1)..);
检查浮点数余额是否溢出
节省Gas的操作
Uniswap Pair 有一处很有趣的设计写法,会大量的减少gas的支出,那就是关于reserve0和reserve1状态变量的读取
整份合约关于这两个变量的读取的函数分别有_update
_mintFee
mint
burn
swap
sync
巧妙减少gas
- 通过
getReserves
函数从状态存储转移到内存存储,上面的函数读取到这两个值会更便宜
代币铸造和销毁
- 铸造
- 流动性提供者向池中添加资金,因此,为流动性提供者铸造(凭空创建)新的池所有权代币
- 销毁
- 流动性提供者提取资金(以及累积的奖励),他的池所有权代币被销毁(销毁)
实现代币铸造
balance0
和balance1
通过IERC20接口传入合约地址,调用当前余额- totalSupply表示池所有权令牌的总供应量,并且是
UniswapV2ERC20
合约中的一个存储变量 - 黄色119行代码,如果总供应量为0,则表示该池是全新的,我们需要锁定
MINIMIUM_LIQUIDITY
池所有权令牌的数量,以避免在流动性计算中被零除。它被锁定的方式是将其发送到地址零。(没有人知道会导致地址 0 的私钥,因此通过将资金发送到地址 0,您实际上永远锁定了资金) - 黄色123行代码, liquidity 变量是需要铸造给流动性提供者的新池所有权代币的数量。流动性提供者根据他提供的新资金数量获得一定比例的池所有权代币
- 黄色126行代码,我们最终将新的池所有权令牌铸造到to地址。to 是流动性提供者的地址(这将由调用该 mint 函数的称为Router的外围合约提供)
- 添加金额的方式是存入ERC20合约,调用transfer(from: liquidity provider, to: Pair contract, amount) 然后读取余额(112,113行)
实现代币销毁
- 第 135、136、137 和 143 行进行了gas优化
balance0
和balance1
是该池中的代币的总余额- liquidity 是流动性提供者(希望兑现)拥有的池所有权代币数量,burn 因为在调用函数之前,流动性已经被 Periphery 合约转移到了 Pair 合约中
- 黄色144、145行, 计算要提取给流动性提供者的代币数量与他拥有多少流动性(池所有权代币),然后烧掉他的流动性并将代币转移给他
- 对流动性提供者的奖励与他的资金一起自动提取。数学可以确保奖励得到适当的累积,并且你得到的比你存的多
Pair交换代币
- 第170、171行上,将代币转移到交易者上,后续有断言检查是否收到对应的代币,如果没有收到,断言将失败,然后恢复整个合约
- 如果有请求,第172行向接收器通知呼叫
- 第176、177行上,检查收到了多少代币,如果该池中的代币的总余额小于(两个币的数量 - 接收代币数量),则赋值0,并进行抛错
- 第180、181行上,从余额中减去交易费用(0.3%),并在182行检查 k值 (x * y = k) 是否降低, k值永远不会减少,否则Uni会从交换中丢失
- 最后通过新的余额来更新已知的存储,并发出事件