在外围合约 v3-periphery\contracts\lens 目录下有两个很重要的用于流动池相关数据查询的合约(目录名称lens意思是lenses,透镜,很应景), 分别是:
TickLens.sol, 此工具合约主要用于查询给定的流动池的所有流动性仓位的流动性大小, 这些信息将用于填充Uniswap链下前端信息网站上展示的流动性深度图
Quoter.sol, 此工具合约主要用于模拟真实交易, 获取实际交易输入输出的token数量, 用户在真实交易前, 链下前端需要预先计算出用户输入token能够预期兑换的输出token数, 但是这个计算工作只有链上的流动池合约自己能做到, 而且流动池合约中的swap函数都是会更改合约状态的external函数, 需要消耗gas费, 那么就需要把这个操作当作view/pure函数来使用, 本合约为了实现这个目的而存在. 备注: 新版本此合约已经升级成最新的QuoterV2版本, 新版本合约可以查询更多信息, 比如gas评估等
TickLens合约
参考代码如下:
contract TickLens is ITickLens {
/// @inheritdoc ITickLens
function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex)
public
view
override
returns (PopulatedTick[] memory populatedTicks)
{
// fetch bitmap
uint256 bitmap = IUniswapV3Pool(pool).tickBitmap(tickBitmapIndex); #池内的tick位图
// calculate the number of populated ticks
uint256 numberOfPopulatedTicks;
for (uint256 i = 0; i < 256; i++) {
if (bitmap & (1 << i) > 0) numberOfPopulatedTicks++;
}
// fetch populated tick data
int24 tickSpacing = IUniswapV3Pool(pool).tickSpacing();
populatedTicks = new PopulatedTick[](numberOfPopulatedTicks);
for (uint256 i = 0; i < 256; i++) {
if (bitmap & (1 << i) > 0) {
int24 populatedTick = ((int24(tickBitmapIndex) << 8) + int24(i)) * tickSpacing;
(uint128 liquidityGross, int128 liquidityNet, , , , , , ) = IUniswapV3Pool(pool).ticks(populatedTick); #查询池内流动性仓位相关tick中包含的元数据信息
populatedTicks[--numberOfPopulatedTicks] = PopulatedTick({
tick: populatedTick, #价格边界
liquidityNet: liquidityNet, #净流动性
liquidityGross: liquidityGross #流动性和
});
}
}
}
}
Quoter合约
参考代码如下:
contract Quoter is IQuoter, IUniswapV3SwapCallback, PeripheryImmutableState {
...
/// @inheritdoc IUniswapV3SwapCallback
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes memory path
) external view override {
require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
(address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);
(bool isExactInput, uint256 amountToPay, uint256 amountReceived) =
amount0Delta > 0
? (tokenIn < tokenOut, uint256(amount0Delta), uint256(-amount1Delta))
: (tokenOut < tokenIn, uint256(amount1Delta), uint256(-amount0Delta));
if (isExactInput) {
assembly {
let ptr := mload(0x40)
mstore(ptr, amountReceived) #保存计算结果
revert(ptr, 32) #还原交易
}
} else {
// if the cache has been populated, ensure that the full output amount has been received
if (amountOutCached != 0) require(amountReceived == amountOutCached);
assembly {
let ptr := mload(0x40)
mstore(ptr, amountToPay) #保存计算结果
revert(ptr, 32) #还原交易
}
}
}
/// @dev Parses a revert reason that should contain the numeric quote
function parseRevertReason(bytes memory reason) private pure returns (uint256) {
if (reason.length != 32) {
if (reason.length < 68) revert('Unexpected error');
assembly {
reason := add(reason, 0x04)
}
revert(abi.decode(reason, (string)));
}
return abi.decode(reason, (uint256)); #捕获计算结果
}
/// @inheritdoc IQuoter
function quoteExactInputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint160 sqrtPriceLimitX96
) public override returns (uint256 amountOut) {
bool zeroForOne = tokenIn < tokenOut;
try #进行异常捕获
getPool(tokenIn, tokenOut, fee).swap( #模拟交易,内部会回调uniswapV3SwapCallback
address(this), // address(0) might cause issues with some tokens
zeroForOne,
amountIn.toInt256(),
sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
: sqrtPriceLimitX96,
abi.encodePacked(tokenIn, fee, tokenOut)
)
{} catch (bytes memory reason) { #异常捕获
return parseRevertReason(reason); #将计算结果返回给链下前端用户
}
}
...
}
如代码所示, 基本流程:
1.quoteExactInputSingle中调用swap模拟交易
2.在回调函数uniswapV3SwapCallback中保存计算结果, 并且还原交易
3.在quoteExactInputSingle函数的catch代码块中捕获异常, 调用parseRevertReason函数
4.parseRevertReason函数中解析计算结果