library contract
在 Solidity 中,库是一种无状态合约(即它没有可变状态),它实现了一组可被其他合约使用的函数--这是库的主要目的。与合约不同,库没有状态:它们的函数通过 DELEGATECALL 在调用者的状态下执行。但与合约一样,库也必须部署后才能使用。幸运的是,Forge 支持自动链接库(我们不需要在测试中部署库),这让我们的工作变得更轻松。
library ZuniswapV2Library {
error InsufficientAmount();
error InsufficientLiquidity();
function getReserves(
address factoryAddress,
address tokenA,
address tokenB
) public returns (uint256 reserveA, uint256 reserveB) {
(address token0, address token1) = _sortTokens(tokenA, tokenB);
(uint256 reserve0, uint256 reserve1, ) = IZuniswapV2Pair(
pairFor(factoryAddress, token0, token1)
).getReserves();
(reserveA, reserveB) = tokenA == token0
? (reserve0, reserve1)
: (reserve1, reserve0);
}
...
这是一个高级函数,它可以获取任意配对的reserve。
函数的第一步是token地址排序--当我们想通过token地址查找pair地址时,总是要这样做。这就是我们下一步要做的事情:有了factory地址和排序过的token地址,我们就能获得pair地址--我们接下来会看看 pairFor 函数。
请注意,在返回之前,我们已经对储备进行了排序:我们希望按照token地址指定的顺序返回它们!
现在,让我们来看看 pairFor 函数:
function pairFor(
address factoryAddress,
address tokenA,
address tokenB
) internal pure returns (address pairAddress) {
该函数用于通过factory地址和token地址查找pair地址。最直接的方法是从factory合约中获取配对地址,例如
ZuniswapV2Factory(factoryAddress).pairs(address(token0), address(token1))
但这样做会产生外部调用,从而使函数的运行成本增加。
Uniswap 使用的是更先进的方法,我们可以从 CREATE2 操作码的确定性地址生成中获的启发。
(address token0, address token1) = sortTokens(tokenA, tokenB);
pairAddress = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factoryAddress,
keccak256(abi.encodePacked(token0, token1)),
keccak256(type(ZuniswapV2Pair).creationCode)
)
)
)
)
);
这段代码生成地址的方式与 CREATE2 相同。
1、第一步是对token地址进行排序。还记得 createPair 函数吗?我们使用排序过的令牌地址作为盐。
2、接下来,我们构建一个字节序列,其中包括:
0xff - 第一个字节有助于避免与 CREATE 操作码发生碰撞。(更多详情请参见 EIP-1014)。
factoryAddress - 用于部署配对的工厂。
salt - 已排序和散列的token地址。
一对合约字节码的哈希值 - 我们对 creationCode 进行哈希处理以获得该值。
3、然后,对字节序列进行散列(keccak256)并转换为地址(字节->uint256->uint160->地址)。
整个过程在 EIP-1014 中定义,并在 CREATE2 操作码中实现。我们要做的就是在 Solidity 中重新实现地址生成!
最后,我们来到引用函数。
function quote(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256 amountOut) {
if (amountIn == 0) revert InsufficientAmount();
if (reserveIn == 0 || reserveOut == 0) revert InsufficientLiquidity();
return (amountIn * reserveOut) / reserveIn;
}
如前所述,该函数根据输入量和配对储备计算输出量。这样,我们就可以知道用特定数量的token A 换取多少token B。在交换中,使用的是基于恒积公式的公式。