前言作为AMM的开创者,Uniswap在defi领域有着举足轻重的地位,稳居top1,并且除了稳定币兑换池,Uniswap的交易量可以说是遥遥领先于其它DEX。
作为AMM的开创者,Uniswap在defi领域有着举足轻重的地位,稳居top1,并且除了稳定币兑换池,Uniswap的交易量可以说是遥遥领先于其它DEX。所以让我们来看看它具体是如何实现的。之所以分析V2,是因为V1是用vyper写的。
Uniswap V2主要分为core与periphery两个模块。
Uniswap-v2-periphery--------Uniswap-v2-core
|
UniswapV2Migrator.sol | UniswapV2ERC20.sol
|
UniswapV2Router01.sol | UniswapV2Factory.sol
|
UniswapV2Router01.sol | UniswapV2Pair.sol
|
我们先介绍几个主要合约的功能:
uniswap-v2-core UniswapV2Factory:工厂合约,用于创建Pair合约
UniswapV2Pair:负责核心逻辑,如swap/mint/burn,价格预言机等功能,其本身是一个ERC20合约,继承UniswapV2ERC20(Factory只允许创建唯一的交易对)
UniswapV2ERC20:这是一个扩展的ERC20实现,用于实现LPToken
uniswap-v2-periphery
UniswapV2Router02:最新版的路由合约,相比UniswapV2Router01增加了对FeeOnTransfer代币的支持;实现Uniswap v2最常用的接口,比如添加/移除流动性,使用代币A交换代币B,使用ETH交换代币等,用来提升用户的体验。
UniswapV1Router01:旧版本Router实现,与Router02类似,但不支持FeeOnTransferTokens,目前已不使用
UniswapV2Migrator:用于迁移流动性
在工厂合约中最重要的是createPair方法
function createPair(address tokenA, address tokenB) external returns (address pair) {
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
具体实现是先判断tokenA与tokenB是否相同,然后排序(防止生成A-B与B-A这样的相同的pair),再用create2生成合约,最后记录合约地址。
标准的ERC20实现,唯一不同的是实现了EIP-2612以支持转账的离线授权
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合约主要实现了四个方法:mint,burn,swap,skim。
用于添加流动性。
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));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply;
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
首先通过getReserves()获取两种代币的缓存余额(防止攻击者操控价格预言机,计算协议手续费),然后计算用户转入的token数amount0与amount1,再计算是否需要收取协议手续费,然后计算流动性(如果是第一次添加,则要锁定MINIMUM_LIQUIDITY的LP)。最后铸造LpToken,更新储备。
用于移除流动性
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)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
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');
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
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);
}
基本与mint函数类似,就不赘述了。
用于两种代币的交换
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
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);
}
首先经历一系列检查后,swap主要通过amountOut的大小来判断用户需要的代币,这样是为了兼容闪电贷功能。
由于在swap方法最后会检查余额(扣掉手续费后)符合k恒等式约束,因此合约可以先将用户希望获得的代币转出,;如果使用闪电贷,即用户之前并没有向合约转入用于交易的代币,则需要在自定义的uniswapV2Call方法中将借出的代币归还。
用于清理合约中多余的Token。无原因是多余的代币都会导致池子里的储备量和实际余额不一致,从而影响价格和流动性。
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));
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!