Uniswap Part Ⅰ | 创建交易对

创建交易对

1
2
3
4
5
title:CreatePool
User->NonfungiblePositionManager:createAndInitializePoolIfNecessary(token0,token1,fee,√p)
NonfungiblePositionManager->UniswapV3Factory:createPool
UniswapV3Factory->UniswapV3Pool:deploy
NonfungiblePositionManager->UniswapV3Pool:initialize

全局变量

NonfungiblePositionManager中有一些全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// @dev IDs of pools assigned by this contract
//_poolIds记录所有交易池的地址和编号的对应关系
mapping(address => uint80) private _poolIds;

/// @dev Pool keys by pool ID, to save on SSTOREs for position data
//_poolIdToPoolKey记录交易池编号和PoolKey的对应关系。PoolKey中包含了token0,token1,fee
mapping(uint80 => PoolAddress.PoolKey) private _poolIdToPoolKey;

/// @dev The token ID position data
mapping(uint256 => Position) private _positions;

/// @dev The ID of the next token that will be minted. Skips 0
//position的编号
uint176 private _nextId = 1;
/// @dev The ID of the next pool that is used for the first time. Skips 0
//每一个Pool的唯一编号
uint80 private _nextPoolId = 1;

/// @dev The address of the token descriptor contract, which handles generating token URIs for position tokens
address private immutable _tokenDescriptor;

还有一个构造函数

1
2
3
4
5
6
7
constructor(
address _factory, //UniswapV3Factory的地址
address _WETH9, //ETH智能合约的地址
address _tokenDescriptor_ //ERC721描述信息的接口地址
) ERC721Permit('Uniswap V3 Positions NFT-V1', 'UNI-V3-POS', '1') PeripheryImmutableState(_factory, _WETH9) {
_tokenDescriptor = _tokenDescriptor_;
}

createAndInitializePoolIfNecessary

NonfungiblePositionManager合约继承了抽象合约PoolInitializer

1
2
3
4
5
6
7
8
9
10
11
12
contract NonfungiblePositionManager is
INonfungiblePositionManager,
Multicall,
ERC721Permit,
PeripheryImmutableState,
PoolInitializer,
LiquidityManagement,
PeripheryValidation,
SelfPermit
{
...
}

createAndInitializePoolIfNecessary方法写在抽象合约PoolInitializer中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// @title Creates and initializes V3 Pools
abstract contract PoolInitializer is IPoolInitializer, PeripheryImmutableState {
/// @inheritdoc IPoolInitializer
function createAndInitializePoolIfNecessary(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external payable override returns (address pool) {
require(token0 < token1);
//查看交易对是否已经创建
pool = IUniswapV3Factory(factory).getPool(token0, token1, fee);

if (pool == address(0)) {
pool = IUniswapV3Factory(factory).createPool(token0, token1, fee);
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
} else {
(uint160 sqrtPriceX96Existing, , , , , , ) = IUniswapV3Pool(pool).slot0();
if (sqrtPriceX96Existing == 0) {
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
}
}
}
}

getPool

IUniswapV3Factory中的getPool函数

1
2
3
4
5
6
7
8
9
10
11
/// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
/// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
/// @param tokenA The contract address of either token0 or token1
/// @param tokenB The contract address of the other token
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @return pool The pool address
function getPool(
address tokenA,
address tokenB,
uint24 fee
) external view returns (address pool);

在UniswapV3Factory中的getPool

1
2
3
4
5
contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall {
...
mapping(address => mapping(address => mapping(uint24 => address))) public override getPool;
...
}

createPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// @inheritdoc IUniswapV3Factory
function createPool(
address tokenA,
address tokenB,
uint24 fee //期望的费率
) external override noDelegateCall returns (address pool) {
//检查是否是同一token
require(tokenA != tokenB);
//将TokenA与TokenB根据地址进行升序排列
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
//检查token0是否为空地址
require(token0 != address(0));
//根据费率检索TickSpace并检查TickSpace是否为0
int24 tickSpacing = feeAmountTickSpacing[fee];
require(tickSpacing != 0);
//检查当前新建的池子是否已经存在
require(getPool[token0][token1][fee] == address(0));
//核心是调用 deploy 函数完成交易对的创建
pool = deploy(address(this), token0, token1, fee, tickSpacing);
getPool[token0][token1][fee] = pool;
// populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses
//提供了反向映射,减少后期检索时比较地址的成本
getPool[token1][token0][fee] = pool;
emit PoolCreated(token0, token1, fee, tickSpacing, pool);
}

deploy

位于合约UniswapV3PoolDeployer中,被UniswapV3Factory继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then
/// clearing it after deploying the pool.
/// @param factory The contract address of the Uniswap V3 factory
/// @param token0 The first token of the pool by address sort order
/// @param token1 The second token of the pool by address sort order
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @param tickSpacing The spacing between usable ticks
function deploy(
address factory,
address token0,
address token1,
uint24 fee,
int24 tickSpacing
) internal returns (address pool) {
parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing});
pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());
delete parameters;
}

CREATE2和CREATE

CREATE指令创建的合约地址是通通过交易发起者(sender)的地址以及交易序号(nonce)来计算确定的。sender 和 nonce 进行 RLP 编码,然后用 Keccak-256 进行 hash 计算(伪码):

1
keccak256(rlp([sender, nonce]))

而 CREATE2 指令则主要是根据创建合约的初始化代码(init_code)及盐(slat) 生成(伪码),让生成的合约地址更具有可控性:

一般而言init_code==bytecode,就是编译生成的字节码,借此让地址变成了对合约代码的验证

CREATE2 的另一个值得注意的(有用的)是,由于其对计算合约地址的参数多了一点控制, 如果一个合约自毁了,那么新合约未来可以再次部署到这个地址上。但是,如果已经有非自毁合约部署到这个地址上了,那么 CREATE2 不能在这个地址上再次部署一个合约。

1
keccak256(0xff + sender + salt + keccak256(init_code))

函数deploy中使用CREATE2来创建合约

1
pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());

优点

  • 可以在链下计算出已经创建的交易池的地址

  • 其他合约不必通过 UniswapV3Factory 中的接口来查询交易池的地址,可以节省 gas

  • 合约地址不会因为reorg (区块重组、分叉) 而改变

  • 如果一个合约自毁了,那么新合约未来可以再次部署到这个地址上

  • 在未部署前可以提前获取合约地址

可以依据bytecode计算合约地址,例如在library PoolAddress中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;

/// @notice The identifying key of the pool
struct PoolKey {
address token0;
address token1;
uint24 fee;
}

...

/// @notice Deterministically computes the pool address given the factory and PoolKey
/// @param factory The Uniswap V3 factory contract address
/// @param key The PoolKey
/// @return pool The contract address of the V3 pool
function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) {
require(key.token0 < key.token1);
pool = address(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(key.token0, key.token1, key.fee)),
POOL_INIT_CODE_HASH
)
)
)
);
}
}

新交易对合约的构造函数中会反向查询 UniswapV3Factory 中的 parameters 值来进行初始变量的赋值:

1
2
3
4
5
6
7
8
9
10
11
contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall {
...
constructor() {
int24 _tickSpacing;
(factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();
tickSpacing = _tickSpacing;

maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
}
...
}

为什么不直接使用参数传递来对新合约的状态变量赋值呢。这是因为 CREATE2 会将合约的 initcodesalt 一起用来计算创建出的合约地址。而 initcode 是包含 contructor code 和其参数的,如果合约的 constructor 函数包含了参数,那么其 initcode 将因为其传入参数不同而不同。在 off-chain 计算合约地址时,也需要通过这些参数来查询对应的 initcode。为了让合约地址的计算更简单,这里的 constructor 不包含参数(这样合约的 initcode 将时唯一的),而是使用动态 call 的方式来获取其创建参数。

initialize

在合约UniswapV3Pool中,对创建的交易对合约进行初始化。所有交易池的参数和状态用一个数据结构Slot0来记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    struct Slot0 {
// the current price
uint160 sqrtPriceX96;
// the current tick
int24 tick;
// the most-recently updated index of the observations array
uint16 observationIndex;
// the current maximum number of observations that are being stored
uint16 observationCardinality;
// the next maximum number of observations to store, triggered in observations.write
uint16 observationCardinalityNext;
// the current protocol fee as a percentage of the swap fee taken on withdrawal
// represented as an integer denominator (1/x)%
uint8 feeProtocol;
// whether the pool is locked
bool unlocked;
}
/// @inheritdoc IUniswapV3PoolState
Slot0 public override slot0;

/// @inheritdoc IUniswapV3PoolActions
/// @dev not locked because it initializes unlocked
//这里的sqrtPriceX96为sqrt(amountToken1/amountToken0)Q64.96精度的定点数值
function initialize(uint160 sqrtPriceX96) external override {
//检查池子价格是否未初始化
require(slot0.sqrtPriceX96 == 0, 'AI');

//计算最大的tick
int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);

//获取cardinality(基数)与cardinalityNext(下一个基数)的数值
(uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp());

//对slot0进行初始化操作
slot0 = Slot0({
sqrtPriceX96: sqrtPriceX96,
tick: tick,
observationIndex: 0,
observationCardinality: cardinality,
observationCardinalityNext: cardinalityNext,
feeProtocol: 0,
unlocked: true
});

emit Initialize(sqrtPriceX96, tick);
}

初始化主要是设置了交易池的初始价格(注意,此时池子中还没有流动性),以及费率,tick 等相关变量的初始化。


Uniswap Part Ⅰ | 创建交易对
http://sissice.github.io/2022/08/20/uniswap-v3-1/
作者
Sissice
发布于
2022年8月20日
许可协议