前言:UniswapV2代码结构Uniswap智能合约代码由两个github项目组成。一个是core,一个是periphery。https://github.com/Uniswap/uniswap-v2-core.githttps://github.com/Uniswap/uniswap-
Uniswap V2代码结构
Uniswap智能合约代码由两个github项目组成。一个是core,一个是periphery。
https://github.com/Uniswap/uniswap-v2-core.git
https://github.com/Uniswap/uniswap-v2-periphery.git
core偏核心逻辑,单个swap的逻辑。periphery偏外围服务,一个个swap的基础上构建服务。单个swap,两种代币形成的交易对,俗称“池子”。每个交易对有一些基本属性:reserve0/reserve1以及total supply。reserve0/reserve1是交易对的两种代币的储存量。total supply是当前流动性代币的总量。每个交易对都对应一个流动性代币(LPT - liquidity provider token)。简单的说,LPT记录了所有流动性提供者的贡献。所有流动性代币的总和就是total supply。Uniswap协议的思想是reserve0*reserve1的乘积不变。
Periphery逻辑
核心逻辑实现在UniswapV2Router02.sol中。称为Router,因为Periphery实现了“路由”,支持各个swap之间的连接。基本上实现了三个功能:1/ add liquidity(增加流动性)2/remove liqudity (抽取流动性) 3/ swap(交换)。
uniswap v2非常重要,必须要非常熟悉该协议!!!
以下便是对uniswap v2 代码的解读。
uniswap v2的代币实际上是 ERC20代币。实现ERC20标准方法。
代码解读如下:
pragma solidity =0.5.16;
import './interfaces/IUniswapV2ERC20.sol';
import './libraries/SafeMath.sol';
contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint; // 将 SafeMath 库合约用于 uint 类型
string public constant name = 'Uniswap V2'; // 代币的名字
string public constant symbol = 'UNI-V2'; // 代币符号
uint8 public constant decimals = 18;
uint public totalSupply; // 发行量
// 存储某地址的代币余额,address => uint的映射
mapping(address => uint) public balanceOf;
// 存储某一地址对另一地址的代币授权量,授权之后可以允许被授权人使用授权人的代币进行转账 `transferFrom`
mapping(address => mapping(address => uint)) public allowance;
//
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
//
mapping(address => uint) public nonces;
// 两个在授权和转账时会被触发的事件
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
constructor() public {
uint chainId;
assembly {
// chainid指令用于获取当前区块链的链ID, 它唯一地标识了当前区块链的网络
chainId := chainid
}
// 初始化 DOMAIN_SEPARATOR 变量
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
/**
1. 铸币操作,因为有库函数的引用,可以直接调用 add,sub等运算
2. totalSupply 发行量时对于整个系统来说,而balance是对于某个账户来说
*/
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value); // 发行量累加 value
balanceOf[to] = balanceOf[to].add(value); // to账户的 余额累加 value
emit Transfer(address(0), to, value); // 触发交易事件
}
/**
1. 销币操作
*/
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value); // from 账户的 余额累减 value
totalSupply = totalSupply.sub(value); // 发行量累减 value
emit Transfer(from, address(0), value); // 触发交易事件
}
/**
1. 授权操作
2. owner一般是调用者,spender则是授权者,value是授权代币量
*/
function _approve(address owner, address spender, uint value) private {
// 记录owner 对spender 的授权量为 value
allowance[owner][spender] = value;
emit Approval(owner, spender, value); // 触发授权事件
}
/**
1. 转账操作
2. 内置函数_transfer(), from账户向to账户转移 value的代币
3. 而在外部函数中,transfer中的from为合约调用者
*/
function _transfer(address from, address to, uint value) private {
// from 账户余额减少value
balanceOf[from] = balanceOf[from].sub(value);
// to 账户余额增加 value
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
/**
1. 外部授权操作
2. 外部授权规定了授权者是调用者,予以:'我'给spender授权
*/
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
/**
1. 外部转账操作
2. 规定了转账者是调用者,予以:'我'给to转账value
*/
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
/**
1. 外部转账操作,msg.sender是代理人
2. 在 pragma=0.5.16的版本中,uint(-1)= 115792089237316195423570985008687907853269984665640564039457584007913129639935
3. 要调用该函数,事先要让 from执行approve函数,给msg.sender授权
4. 最后调用_transfer()函数,执行from 向 to 转账 value的操作
*/
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}
/**
1. 许可操作
2. permit函数的作用是将代币授权给指定的目标地址,
使得目标地址可以代表代币持有人进行交易,而无需进行传统的授权交易。
这种新型的授权方法可以提高代币交易的效率和安全性,
同时也可以减少交易的成本和时间。
3. owner授权者,spender被授权者,value代币数目,deadline:授权的截止时间,必须在此时间之前完成授权
nonce:随机数,用于避免授权被重复使用,v、r、s:用于验证授权的签名参数。
*/
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}
工厂合约,用于创建Pair合约(以及设置协议手续费接收地址)
代码解读如下:
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';
contract UniswapV2Factory is IUniswapV2Factory {
address public feeTo; // 手续费接收地址
address public feeToSetter; // 手续费接收地址的设置者
/**
如果将 getPair设置为public,则编译的时候会在该合约中默认生成set 和get函数
解读:通过两个地址获取到交易对地址
*/
mapping(address => mapping(address => address)) public getPair;
// 数组,存储所有交易对
address[] public allPairs;
// 交易对创建事件
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
// 初始化,手续费接收地址的设置者
constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}
// 获取交易对的对数
function allPairsLength() external view returns (uint) {
return allPairs.length;
}
/**
首先将token0 token1按照顺序排序,确保token0字面地址小于token1。
接着使用assembly + create2创建合约。
assembly可以在Solidity中使用Yul语言直接操作EVM,是较底层的操作方法。
《Uniswap v2 白皮书》中讲到,create2主要用于创建确定性的交易对合约地址,
目的是根据两个代币地址直接计算pair地址,而无需调用链上合约查询。
*/
function createPair(address tokenA, address tokenB) external returns (address pair) {
// 两种代币地址不能相同
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
// 先将token0 token1按照顺序排序,确保token0字面地址小于token1
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
// 确保token0不等于 address(0),则两个地址都不为 address(0)
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
// 确保这两种代币地址的交易对为address(0),即这两个代币尚未创建交易对
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
// 获取 `UniswapV2Pair`的字节码
bytes memory bytecode = type(UniswapV2Pair).creationCode;
// 生成盐salt,salt由这两个地址紧打包再hash获得,是唯一的
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
// 通过creat2计算交易对地址
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
// 将新生成的交易对中的两种代币设置为 token0, token1
IUniswapV2Pair(pair).initialize(token0, token1);
// 记录token0 和 token1生成的交易对
getPair[token0][token1] = pair;
// 反向填充映射
getPair[token1][token0] = pair; // populate mapping in the reverse direction
// 保存该交易对pair
allPairs.push(pair);
// 创建成功,触发交易对生成事件
emit PairCreated(token0, token1, pair, allPairs.length);
}
// 设置手续费接收地址
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}
// 修改手续费接收地址的设置者
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
}
Pair(交易对)合约,定义和交易有关的几个最基础方法,如swap/mint/burn,价格预言机等功能,其本身是一个ERC20合约,继承UniswapV2ERC20
Pair合约主要实现了三个方法:mint(添加流动性)、burn(移除流动性)、swap(兑换)。
代码解读如下:
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Pair.sol';
import './UniswapV2ERC20.sol';
import './libraries/Math.sol';
import './libraries/UQ112x112.sol';
import './interfaces/IERC20.sol';
import './interfaces/IUniswapV2Factory.sol';
import './interfaces/IUniswapV2Callee.sol';
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
// 引用库函数
using SafeMath for uint;
using UQ112x112 for uint224;
// 最低额度的流动性
uint public constant MINIMUM_LIQUIDITY = 10**3;
// tansfer函数的选择器
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
// 工厂?token0和token1交易对中的两种代币
address public factory;
address public token0;
address public token1;
// token0和token1交易对中的两种代币的存储量
uint112 private reserve0; // uses single storage slot, accessible via getReserves
uint112 private reserve1; // uses single storage slot, accessible via getReserves
// 上次更新的时间
uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
// 累加交易价格
uint public price0CumulativeLast;
uint public price1CumulativeLast;
uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
// 锁标志
uint private unlocked = 1;
// 修饰器为了防止异步
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
// 读取交易对中两种代币的余额,以及上一次交易对更新的时间
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
/**
1. 通过call的方式,调用token中的 transfer 函数---兼容性更强
2. 判断调用是否成功,success是否为true,data是否为空
*/
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
// 初始化 工厂地址为调用者
constructor() public {
factory = msg.sender;
}
// called once by the factory at time of deployment
// 初始化交易对中的两种代币地址
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
token1 = _token1;
}
// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
// 记录当前更新时间
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
// 记录过去了多久
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
// 计算出当前的交易价格
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
// 将这两种代币的存储量设置为代币的余额
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
// 更新当前操作时间
blockTimestampLast = blockTimestamp;
// 触发同步事件
emit Sync(reserve0, reserve1);
}
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
// 计算铸币手续费
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
address feeTo = IUniswapV2Factory(factory).feeTo(); // 获取当前factory铸币手续费的接收地址
feeOn = feeTo != address(0); // 检查该factory是否设置了手续费接收地址
uint _kLast = kLast; // gas savings
if (feeOn) { // 如果该factory有手续费接收地址
if (_kLast != 0) {
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
if (liquidity > 0) _mint(feeTo, liquidity); // 给feeTo地址铸币liquidity
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}
// this low-level function should be called from a contract which performs important safety checks
// 铸币操作,添加流动性
function mint(address to) external lock returns (uint liquidity) {
// 读取代币的存储量
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 获取这两种代币的余额
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
// 计算当前合约中两个代币的净增量,并赋值给amount0和amount1变量
// 净增量等于余额减去储备量
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
// 调用_mintFee函数,计算是否需要收取协议手续费,并返回一个布尔值,赋值给feeOn变量
bool feeOn = _mintFee(_reserve0, _reserve1); // 理解为支付完铸币费用
// 记录发行量,难道这里也讲究异步的现象?
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// 如果是首次提供该交易对的流动性,则根据根号xy生成流动性代币,并销毁其中的MINIMUM_LIQUIDITY(即1000wei
if (_totalSupply == 0) {
// 计算流动性代币的数量,等于两个代币净增量乘积的平方根减去最小流动性常量,并赋值给liquidity变量
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
// 调用_mint函数,向零地址铸造最小流动性常量数量的流动性代币(永久锁定)
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
// 计算流动性代币的数量,等于两个代币净增量与储备量比例乘积与总供应量乘积的较小值,并赋值给liquidity变量
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity); // 为to铸币,liquidity
_update(balance0, balance1, _reserve0, _reserve1); // 调用_update函数,更新当前合约中两个代币的储备量为最新的余额
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
// this low-level function should be called from a contract which performs important safety checks
// 销币操作,移除流动性
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
// 参考白皮书,为了节省交易手续费,Uniswap v2只在mint/burn流动性时收取累计的协议手续费。
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// 采用mint中计算 liquidity的方法倒推amount
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
// 销毁本合约的 流动性 liquidity
_burn(address(this), liquidity);
// 调用_token0(_token1)中的 transfer函数
// 通过各自token中实现的transfer将token转移回to
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
//更新合约自身的token0、1余额
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
//更新池子中的储备量和价格积累器
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
// this low-level function should be called from a contract which performs important safety checks
/**
实现两种代币的交换(交易)功能-----闪电贷功能
uint amount0Out:要转换的第一种代币的数量。
uint amount1Out:要转换的第二种代币的数量。
address to:接收转换后代币的目标地址。
bytes data:可选的额外数据,用于向目标地址提供更多信息。
*/
// 接收者 to 必须要实现 `uniswapV2Call`函数,通过此函数输入交换的代币
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 检查输出量是否小于储备量
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
// 检查接收地址是否合法
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
// 如果有额外数据,就调用接收地址的回调函数
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
// 计算输入量
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
// 计算调整后的余额,因为在solidity中没有浮点数 0.3%,这样是为了模拟出 0.3%
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
// force balances to match reserves
/**
用于移除资金池中多余代币的函数。它的作用是将代币池中多余的代币转移到指定的目标地址,以便于在资金池中保持正确的代币比例
*/
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
// force reserves to match balances
// 同步,迫使代币余额与代币储量相匹配,调用_update函数
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}
分析
mint
函数,举例假设有一个流动性池,其中有 10 个 ETH 和 5000 个 USDC,总供应量为 70.71 个流动性代币(这里面包括过去交易留下的Fees)。现在,有人想向这个池中添加 1 个 ETH 和 500 个 USDC,以获得更多的流动性代币。那么,他们将获得多少流动性代币呢?
首先,我们需要计算添加后的两种代币的储备量和余额。储备量等于原来的储备量加上添加的数量,余额等于储备量加上协议手续费(假设为 0.3%)。因此,我们有:
- 储备量:reserve0 = 10 + 1 = 11 ETH,reserve1 = 5000 + 500 = 5500 USDC
- 余额:balance0 = reserve0 (1 + 0.003) = 11.033 ETH,balance1 = reserve1 (1 + 0.003) = 5516.5 USDC
接下来,我们需要计算添加后的总供应量。如果是第一次添加流动性,则使用公式 sqrt(x y) — MINIMUM_LIQUIDITY,否则使用公式 min(x totalSupply / reserve0, y * totalSupply / reserve1)。因为这不是第一次添加流动性,所以我们使用后者。因此,我们有:
- 总供应量:totalSupply = min(balance0 70.71 / reserve0, balance1 70.71 / reserve1) = min(71.41, 71.41) = 71.41
最后,我们需要计算添加者获得的流动性代币的数量。这个数量等于添加后的总供应量减去添加前的总供应量。因此,我们有:
- 流动性代币:liquidity = totalSupply — 70.71 = 71.41–70.71 = 0.7
也就是说,添加者将获得大约 0.7 个流动性代币。
分析
burn
,举例假设有一个池子,它允许交易 ETH 和 DAI,它的地址是
0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11
。这个池子有以下的状态:
- 流动性代币的总供应量是
3,000,000
。- 池子里有
10,000
个 ETH 和4,000,000
个 DAI。- 协议费用是
0.05%
。- 你拥有
30,000
个流动性代币,也就是池子的1%
。现在,你想要退出这个池子,把你的流动性代币销毁,并拿回你的 ETH 和 DAI。你可以调用这个函数,把
to
参数设为你自己的地址。这样,函数会做以下的事情:
- 它会从池子里获取 ETH 和 DAI 的储备量,分别是
_reserve0 = 10,000
和_reserve1 = 4,000,000
。- 它会获取 ETH 和 DAI 的地址,分别是
_token0 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
和_token1 = 0x6B175474E89094C44Da98b954EedeAC495271d0F
。- 它会获取合约自身的 ETH 和 DAI 的余额,分别是
balance0 = 10,000
和balance1 = 4,000,000
。注意,这里假设没有其他人在同一区块内与池子交互,否则余额可能会有变化。- 它会获取合约自身的流动性代币余额,也就是
liquidity = 30,000
。- 它会调用
_mintFee
函数,来分发协议费用给流动性提供者。假设在你加入池子后,没有发生过任何交易,那么协议费用就是零,所以_mintFee
函数不会改变任何东西,并返回feeOn = false
。它会计算你能够拿回的 ETH 和 DAI 的数量,分别是
amount0 = liquidity * balance0 / totalSupply = 30,000 * 10,000 / 3,000,000 = 100
和amount1 = liquidity * balance1 / totalSupply = 30,000 * 4,000,000 / 3,000,000 = 40,000
。这保证了按比例分配。知识点:
abi.decode(data, (bool))
是Solidity中的一种函数调用,用于将字节数组(byte array)解码为布尔值(bool)类型。具体来说,
abi.decode
函数接受两个参数:字节数组和数据类型。在这里,字节数组是要解码的数据,数据类型是要解码成的目标类型,即布尔值。函数调用
abi.decode(data, (bool))
将字节数组解码为一个布尔值。这个布尔值的值取决于字节数组中的数据。如果字节数组中的数据为0,则解码后的布尔值为false,否则为true。知识点:
UQ112x112.encode(_reserve1).uqdiv(_reserve0)
UQ112x112.encode(_reserve1).uqdiv(_reserve0)
是Uniswap V2中的一个计算交易价格的操作,使用了UQ112x112固定点数算法。具体来说,
_reserve0
和_reserve1
是Uniswap V2交易对中两种资产的余额(reserve),UQ112x112.encode()
函数将余额编码为UQ112x112固定点数格式,然后使用了.uqdiv()
函数对两种资产的余额进行了除法操作,计算出当前的交易价格。UQ112x112固定点数算法是一种用于在以太坊合约中进行精确数学计算的算法。它将浮点数转换为整数,并使用固定的小数位数进行计算。在Uniswap V2中,UQ112x112固定点数算法被广泛应用于计算交易价格和资金池分配等。
在这个操作中,
UQ112x112.encode(_reserve1)
将_reserve1
编码为UQ112x112格式,然后.uqdiv(_reserve0)
将编码后的_reserve1
除以_reserve0
。最终的结果是一个UQ112x112格式的数,表示当前的交易价格。
pragma solidity =0.6.6;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import './interfaces/IUniswapV2Router02.sol';
import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';
contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
// 修饰器确保 操作在截止日期之前
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
// 初始化factory 和 WETH地址
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
// 接收ETH
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
// **** ADD LIQUIDITY ****
/** _addLiquidity可以帮助计算最佳汇率。如果是首次添加流动性,则会先创建交易对合约;
否则根据当前池子余额计算应该注入的最佳代币数量。
*/
function _addLiquidity(
address tokenA, // 代币A
address tokenB, // 代币B
uint amountADesired, // 希望存入的代币A数量
uint amountBDesired, // 希望存入的代币B数量
uint amountAMin, // 最少存入的代币A数量
uint amountBMin // 最少存入的代币B数量
) internal virtual returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
// 获取交易对中代币A B的存储量
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
// 如果储备量都为 0,那两个预期支付额就是成交量
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
// 采用控制变量法算出最佳的汇率
/**
如果计算得出的结果值 amountBOptimal 不比 amountBDesired 大,且不会小于 amountBMin,
就可将 amountADesired 和该 amountBOptimal 作为结果值返回。
如果 amountBOptimal 大于 amountBDesired,则根据 amountBDesired 计算得出需要支付多少 tokenA,
得到 amountAOptimal,只要 amountAOptimal 不大于 amountADesired 且不会小于 amountAMin,
就可将 amountAOptimal 和 amountBDesired 作为结果值返回。
*/
// 调用quote函数,换算amountADesired 对应的B代币为多少
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
// 最佳的B代币数量少于希望存入的代币B数量
if (amountBOptimal <= amountBDesired) {
// 这是定 amountADesired,求B的最佳数量
// 而且最佳B代币数量要求 >= amountBMin
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
// 返回希望存入的A,和最佳的B数量
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
// 定amountBDesired,求A的最佳数量
// 调用quote函数,换算amountBDesired 对应的A代币为多少
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
// 返回希望存入的B,和最佳的A数量
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA, // 代币A
address tokenB, // 代币B
uint amountADesired, // 希望存入的代币A数量
uint amountBDesired, // 希望存入的代币B数量
uint amountAMin, // 用户可接受的最小成交代币A数量
uint amountBMin, // 用户可接受的最小成交代币B数量
address to, // 流动性代币接收地址
uint deadline // 该笔交易的有效时间,如果超过该时间还没得到交易处理就直接失效不进行交易了
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
// 调用_addLiquidity函数,返回 代币A和代币B 的最佳汇率
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
// 获取交易对地址
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
// 调用tokenA的`transferFrom`函数,实现msg.sender向交易对pair转入amountA代币
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
// 获取存入代币后uniswapv2中的流动性
liquidity = IUniswapV2Pair(pair).mint(to);
}
/** 添加流动性 */
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
// 调用_addLiquidity函数,返回 TOken和ETH的最佳汇率
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
// 获取交易对地址
address pair = UniswapV2Library.pairFor(factory, token, WETH);
// 调用token的`transferFrom`函数,实现msg.sender向交易对pair转入amountToken代币
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
// 先把amountETH数量的ETH存入本合约
IWETH(WETH).deposit{value: amountETH}();
// 再将amount数量的WETH转入交易对中
assert(IWETH(WETH).transfer(pair, amountETH));
// 获取存入代币后uniswapv2中的流动性
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
// 如果还有剩余的ETH,将退还
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
// 移除流动性
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
// 获取交易对地址
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
// 将流动性代币从用户划转到 pair 合约
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
// 收到的流动性代币占全部代币比例
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
// 如果低于用户设定的最低预期(amountAMin/amountBMin),则回滚交易
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
function removeLiquidityETH(
address token, //待移除流动性的代币地址
uint liquidity, // 要移除的流动性数量
uint amountTokenMin, // 用户愿意接受的最小代币数量,如果实际返回的代币数量小于该值,则函数会抛出异常
uint amountETHMin, // 用户愿意接受的最小ETH数量,如果实际返回的ETH数量小于该值,则函数会抛出异常
address to, // 代币和ETH将被发送到的目标地址
uint deadline // 操作的截止时间,必须在该时间之前完成操作,否则操作将被视为无效
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
// 调用 removeLiquidity,得到退还的 Token 和 ETH
// removeLiquidity中执行了 `burn`但是是将token和ETH转移到了addrss(this),说明还可以执行 safeTransfer 和 withdraw
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
// 向to转账(token)
TransferHelper.safeTransfer(token, to, amountToken);
// 将weth取出来,暂时存在addrss(this)
IWETH(WETH).withdraw(amountETH);
// 将address(this)中的WETH转到指定地址to
TransferHelper.safeTransferETH(to, amountETH);
}
// 其实就是在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}
// 在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
// **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
/**
返回值没有 amountToken;
调用 removeLiquidity 后也没有 amountToken 值返回
进行 safeTransfer 时传值直接读取当前地址的 token 余额。
*/
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
/** 历整个兑换路径,并对路径中每两个配对的 token 调用 pair 合约的兑换函数,实现底层的兑换处理 */
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
// 如下语句是在判断要兑换的是哪种 货币,比如 A 或者 B
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
/** 用 ERC20 兑换 ERC20,但支付的数量是指定的,而兑换回的数量则是未确定的 */
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
// 将支付的代币转到 pair 合约
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
/** 用 ERC20 兑换 ERC20,与上一个函数不同,指定的是兑换回的数量 */
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
/** 指定 ETH 数量兑换 ERC20 */
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
/** 用 ERC20 兑换成指定数量的 ETH */
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
/** 用指定数量的 ERC20 兑换 ETH */
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
/** 用 ETH 兑换指定数量的 ERC20 */
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// refund dust eth, if any
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
// **** SWAP (supporting fee-on-transfer tokens) ****
// requires the initial amount to have already been sent to the first pair
function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // scope to avoid stack too deep errors
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
/** 指定数量的 ERC20 兑换 ERC20,支持转账时扣费 */
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
/** 指定数量的 ETH 兑换 ERC20,支持转账时扣费 */
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
/** 指定数量的 ETH 兑换 ERC20,支持转账时扣费 */
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
// **** LIBRARY FUNCTIONS ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
解读
addLiquidity
中的amountAMin 和 amountBMin该值一般是由前端根据预期值和滑点值计算得出的。比如,预期值 amountADesired 为 1000,设置的滑点为 0.5%,那就可以计算得出可接受的最小值 amountAMin 为 1000 * (1 - 0.5%) = 995。
addLiquidityETH
addLiquidityETH 则支付的其中一个 token 则是 ETH,而不是 ERC20 代币。来看看其代码实现:
可看到,入参不再是两个 token 地址,而只有一个 token 地址,因为另一个是以太坊主币 ETH。预期支付的 ETH 金额也是直接从 msg.value 读取的,所以入参里也不需要 ETH 的 Desired 参数。但是会定义 amountETHMin 表示愿意接受成交的 ETH 最小额。
实现逻辑上,请注意,调用 _addLiquidity 时传入的第二个参数是 WETH。其实,addLiquidityETH 实际上也是将 ETH 转为 WETH 进行处理的。可以看到代码中还有这么一行:
IWETH(WETH).deposit{value: amountETH}();
这就是将用户转入的 ETH 转成了 WETH。
而最后一行代码则会判断,如果一开始支付的 msg.value 大于实际需要支付的金额,多余的部分将返还给用户
代码解读如下:
pragma solidity >=0.5.0;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import "./SafeMath.sol";
library UniswapV2Library {
using SafeMath for uint;
// returns sorted token addresses, used to handle return values from pairs sorted in this order
// 给 tokenA和tokenB排序,按字面值排序
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
// calculates the CREATE2 address for a pair without making any external calls
// 输入工厂地址和两个代币地址,计算这两个代币的交易对地址
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
// 先对这两个代币地址进行排序
(address token0, address token1) = sortTokens(tokenA, tokenB);
// 采用create2的方式计算地址
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
// UniswapV2Pair 合约的 creationCode 的哈希值
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}
// fetches and sorts the reserves for a pair
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
// 先让 tokenA 和 tokenB 从小到大排列
(address token0,) = sortTokens(tokenA, tokenB);
// 根据 `pairFor(factory, tokenA, tokenB)`算出一个新的地址
// .getReserves() 获取 该新地址的 reserve0,reserve1
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
// 如果交换过顺序就交换输出,简单来说及时为了对应输入的形参
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
/**
数量为amountA的代币A,按照合约中两种代币余额比例,换算成另一个代币B。此时不考虑手续费,因为仅是计价单位的换算
【根据给定的两个 token 的储备量和其中一个 token 数量,计算得到另一个 token 等值的数值】
*/
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
/**
该方法计算:输入一定数量(amountIn)代币A,根据池子中代币余额,能得到多少数量(amountOut)代币B。
amountIn指输入的代币A,reserveIn 指代币A的存储量,reserveOut指代币B的存储量
【根据给定的两个 token 的储备量和输入的 token 数量,计算得到输出的 token 数量,该计算会扣减掉 0.3% 的手续费】
*/
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
/**
该方法计算当希望获得一定数量(amountOut)的代币B时,应该输入多少数量(amoutnIn)的代币A。
amountOut指要得到的代币B,reserveIn 指代币A的存储量,reserveOut指代币B的存储量
【根据给定的两个 token 的储备量和输出的 token 数量,计算得到输入的 token 数量,该计算会扣减掉 0.3% 的手续费】
*/
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
// performs chained getAmountOut calculations on any number of pairs
/**
该方法用于计算在使用多个交易对时,输入一定数量(amountIn)的第一种代币,
最终能收到多少数量的最后一种代币(amounts)。amounts数组中的第一个元素表示amountIn,
最后一个元素表示该目标代币对应的数量。该方法实际上是循环调用getAmountIn方法。
【根据兑换路径和输入数量,计算得到兑换路径中每个交易对的输出数量】
*/
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
// 创建一个和path等长的uint数组
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// performs chained getAmountIn calculations on any number of pairs
/**
与getAmountsOut相对,getAmountsIn用于计算当希望收到一定数量(amountOut)的目标代币,
应该分别输入多少数量的中间代币。计算方法也是循环调用getAmountIn。
*/
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
getAmountOut 的实现:
根据 AMM 的原理,恒定乘积公式「x * y = K」,兑换前后 K 值不变。因此,在不考虑交易手续费的情况下,以下公式会成立:
reserveIn * reserveOut = (reserveIn + amountIn) * (reserveOut - amountOut)
将公式右边的表达式展开,并推导下,就变成了:
reserveIn * reserveOut = reserveIn * reserveOut + amountIn * reserveOut - (reserveIn + amountIn) * amountOut -> amountIn * reserveOut = (reserveIn + amountIn) * amountOut -> amountOut = amountIn * reserveOut / (reserveIn + amountIn)
而实际上交易时,还需要扣减千分之三的交易手续费,所以实际上:
amountIn = amountIn * 997 / 1000
代入上面的公式后,最终结果就变成了:
amountOut = (amountIn * 997 / 1000) * reserverOut / (reserveIn + amountIn * 997 / 1000) -> amountOut = amountIn * 997 * reserveOut / 1000 * (reserveIn + amountIn * 997 / 1000) -> amountOut = amountIn * 997 * reserveOut / (reserveIn * 1000 + amountIn * 997)
这即是最后代码实现中的计算公式了。
解读
getAmountsOut
根据兑换路径和输入数量,计算得到兑换路径中每个交易对的输出数量。
举例:
假如一个交易地址数组 path[A,B,C,D] ,其中
pair(A,B)中的tokenA=6,tokenB=9; pair(B,C)中的tokenA=10,tokenB=8; pair(C,D)中的tokenA=19,tokenB=84
则 amounts[0] = IN_tokenA,
pair(A,B) : amount[1] = getAmountOut(IN_tokenA, reserveA, reserveB),
pair(B,C): amount[2] = getAmountOut(IN_tokenA, reserveA, reserveB),
pair(C,D): amount[3] = getAmountOut(IN_tokenA, reserveA, reserveB).
该函数会计算 path 中每一个中间资产和最终资产的数量,比如 path 为 [A,B,C],则会先将 A 兑换成 B,再将 B 兑换成 C。返回值则是一个数组,第一个元素是 A 的数量,即 amountIn,而第二个元素则是兑换到的代币 B 的数量,最后一个元素则是最终要兑换得到的代币 C 的数量。
从代码中还可看到,每一次兑换其实都调用了 getAmountOut 函数,这也意味着每一次中间兑换都会扣减千分之三的交易手续费。那如果兑换两次,实际支付假设为 1000,那最终实际兑换得到的价值只剩下:
1000 * (1 - 0.003) * (1 - 0.003) = 994.009
即实际支付的交易手续费将近千分之六了。兑换路径越长,实际扣减的交易手续费会更多,所以兑换路径一般不宜过长。
getAmountsIn
同理
最后,码字不易,点个赞呗~🤪
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!