用工厂合约来部署pool
它作为池子合约的中心化注册点。在工厂中,你可以找到所有已部署的池子,对应的 token 和地址。
它简化了池子合约的部署流程。EVM允许在智能合约中部署智能合约——工厂合约使用这个性质来让池子合约的部署变得十分简单。
它让池子合约的地址可预测,并且能够在注册池子之前就计算出这个地址。这让池子更容易被发现
智能合约部署有两个Opcode:CREATE和CREATE2
CREATE
使用部署者账户的 nonce
来产生新的合约地址(伪代码如下):
KECCAK256(deployer.address, deployer.nonce)
nonce
是一个账户特定的交易计数器。在产生合约地址过程中使用 nonce
会使得在其他合约或者链下app中计算合约地址变得非常困难,主要是因为想要找到对应的 nonce,我们需要扫描账户的交易历史。
CREATE2
使用一个特殊的*盐值(salt)*来产生合约地址。这是一个由开发者选择的任意序列,能够使得地址产生更加确定性(并降低碰撞概率):
KECCAK256(deployer.address, salt, contractCodeHash)
工厂合约用的就是CREATE2
,能够提前计算出地址
keccak256(abi.encodePacked(token0, token1, tickSpacing))
pool = address(
new UniswapV3Pool{
salt: keccak256(abi.encodePacked(tokenX, tokenY, tickSpacing))
}()
); //有salt参数的是CREATE2,没有参数的是CREATE
用控制反转存储和读取参数
// src/UniswapV3Factory.sol 存入参数
parameters = PoolParameters({
factory: address(this),
token0: tokenX,
token1: tokenY,
tickSpacing: tickSpacing
});
// src/IuniswapV3PoolDeployer.sol 读取参数
interface IUniswapV3PoolDeployer {
struct PoolParameters {
address factory;
address token0;
address token1;
uint24 tickSpacing;
}
function parameters()
external
returns (
address factory,
address token0,
address token1,
uint24 tickSpacing
);
}
// src/UniswapV3Pool.sol 通过接口读取参数
constructor() {
(factory, token0, token1, tickSpacing) = IUniswapV3PoolDeployer(
msg.sender
).parameters();
}
CREATE2
创建pool函数:
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(
abi.encodePacked(token0, token1, tickSpacing)
),
keccak256(type(UniswapV3Pool).creationCode)
)
)
)
)
);
首先,我们计算盐值(abi.encodePacked(token0, token1, tickSpacing)
)并求哈希;
接下来,我们获取池子合约的代码(type(UniswapV3Pool).creationCode
)并求哈希;
然后,我们构建这样一个字节序列:0xff
,工厂合约地址,哈希后的盐值,哈希后的池子合约代码(EIP-1014用0xff来区分CREATE
和CREATE2
)
最后求这个序列的哈希并转换成地址。
将pool地址存入pools池中:
pools[tokenX][tokenY][tickSpacing] = pool;
pools[tokenY][tokenX][tickSpacing] = pool;
只有流动性提供在价格区间才能收取手续费。需要判断两个点:
价格区间是否被激活,当价格上穿过tick上限和价格下穿tick下限。
价格区间的累积交易费用,比如区间内某个价格成交了会有一笔费用,如果去按个计算非常浪费gas,采用总费用减去价格区间之外的累积费用。
那么总费用是什么?价格区间之外的费用又如何计算?
这个费用是在用户swap的时候产生,是从用户提交token的金额里面减去一部分。这部分减去的金额会存储在池子里面,也就是说池子里面因为交易费用而增加一部分tokenx和tokeny,每单位的流动性累积的总费用由feeGrowthGlobal0X128
和 feeGrowthGlobal1X128
两个状态变量表示,即总的费用/流动性。在计算真正的手续费时,需要使用 LP 数相乘来得出实际手续费数额,又因为 LP 数在不同价格可能时不同的(因为流动性深度不同),所以在计算手续费时只能针对 position
进行计算(同一个 position
内 LP 总量不变)。
这里有两个方面:swap时产生手续费,移除LP时获得手续费。所以用户swap时添加的手续费加入池子算出feeGrowthGlobal0X128
和 feeGrowthGlobal1X128
,在LP获取手续费时用单位费用*这个区间的LP。这个LP是
当价格穿过价格区间的tick时,记录区间外产生的费用。
例如这张图,当从下往上穿过lower tick 时记录当前的费用,当价格从上往下穿过时,记录当前的费用。
提供流动性时,需要初始化 tick 对应的 fo 值
发生交易时,需要更新 fg
当交易过程中,当前价格穿过某一个 tick 时,需要更新此 tick 上的 fo 值
当流动性发生变动时,更新此 position
中手续费的总和