NEWS 新闻NEWS 新闻

Uniswap v3:去中心化交易的革命性升级

引言

去中心化金融(DeFi)的快速发展,让 Uniswap 作为领先的去中心化交易所,不断寻求创新。Uniswap v3 的推出,标志着该协议在流动性管理、交易效率和安全性方面迈出了重大步伐。本文将深入探讨 Uniswap v3 的核心机制,并分析其功能设计,包括集中流动性、多重费率、代币兑换及闪电贷等关键功能,并为审计人员提供相关的审计要点。

架构简析

Uniswap v3 协议主要由四个模块组成:

  • PositionManager: 用户进行流动性操作的主要接口,用户可以通过它创建代币池、提供/移除流动性,并使用 ERC721 作为流动性提供者 (LP) 的凭证。
  • SwapRouter: 用户进行代币交换的入口,用户可以通过该模块完成代币的交换操作。
  • Pool: 负责实现代币交易、流动性管理、收取交易手续费,以及 Oracle 数据的管理功能。其中,Tick 机制将价格范围划分为多个精细的刻度。
  • Factory: 用于创建和管理 Pool 合约。

流程梳理

创建代币对

用户可以通过 createAndInitializePoolIfNecessary 函数来完成。用户需传入代币对的 token0、token1、手续费 (fee) 以及初始价格 ()。系统会检查该代币对是否已存在,如果尚未创建,则调用 createPool,并使用 CREATE2 指令进行交易对的部署。最后,通过 initialize 函数完成价格、手续费、tick、预言机等相关参数的初始化。

提供流动性

用户可以通过 mint 函数创建新的流动性头寸并生成对应的 NFT,或通过 increaseLiquidity 函数为现有的 NFT 流动性头寸增加流动性。系统会检查交易是否在规定的时间范围内执行,然后调用 addLiquidity 函数完成具体操作。该函数首先计算出池子的地址和流动性的大小,接着调用 _updatePosition 更新用户的 Position,修改 lower、upper tick 以及累计的手续费总额。随后,系统通过 _modifyPosition 添加流动性,确保 tick 满足上下限条件,返回计算出的 token0 和 token1 数量 (int256),并将其发送到池中。最后,系统根据用户的 tokenId 更新对应的 Position 信息。

移除流动性

用户可以通过 decreaseLiquidity 函数来移除流动性。系统会检查 LP 凭证的权限以及交易的时间有效性。在确保池子拥有足够流动性的前提下,调用 burn 函数来移除流动性。随后,系统会核实实际移除的代币数量是否满足用户设定的最小限度要求,并相应地更新用户的 Position 信息。

Swap

用户可以通过 exactInput 函数指定支付的 token 数量以及期望获得的最小 token 数量,或通过 exactOutput 函数指定支付的最大 token 数量并设定期望获得的 token 数量。系统首先解析路径 (path),然后依次调用 exactInputInternalexactOutputInternal 函数完成每一步的 swap 操作。在 swap 函数中,系统首先锁定 unlocked 状态,防止其他交易干扰状态变量的更新。进入循环后,系统通过 tick 找到下一个交易价格,并调用 computeSwapStep 函数计算每一步的交换,直到 tokenIn 或 tokenOut 达到用户预期。同时,系统会更新手续费、流动性、tick 以及价格的相关值。如果 tick 发生变化,还需要更新 Oracle 数据。完成这些操作后,系统将 tokenOut 支付给用户,用户再通过回调函数 uniswapV3SwapCallback 支付 tokenIn,这种机制可以被视为一种闪电交换 (flash swap)。随后,系统会检查合约余额是否匹配,并在确认无误后解锁unlocked 状态。当路径中的所有 swap 操作都完成,且交易符合用户的预期时,交易即成功结束。

Flash

用户可以通过 flash 函数来进行闪电贷操作。系统会计算借贷的手续费,然后将用户所需的 token 发送到指定的借贷地址。接下来,系统回调用户实现的 uniswapV3FlashCallback 函数,用户在此函数中完成还款操作。系统会在回调后检查合约余额的变化,确保其与用户借贷的数量相符,同时更新相应的手续费。除了 flash 函数,用户也可以通过 swap 操作实现类似的闪电贷功能,即在交易过程中先借入再偿还 token。

审计要点

  1. 检查 swap 操作后是否有调用 refundETH

exactInput 函数中,用户需要指定支付的 token 数量和预期获得的最小 token 数量。在调用 uniswapV3SwapCallback 之前,系统会重新计算 amount0 和 amount1,以确保用户可以精确地发送 token。然而,当使用 ETH 进行交换时,用户需要随交易一起发送 ETH。即便在交易过程中未使用完所有的 ETH,函数不会自动退回多余部分。exactInput 函数仅返回 amountOut,因此交易者无法直接得知此次交换实际消耗了多少 ETH。此外,任何人都可以调用 refundETH 函数,从合约中提取未使用的 ETH。因此,建议检查 swap 操作后是否调用 refundETH 以防止用户未使用的 ETH 遗留在协议中,或使用 MultiCall 函数在一次操作中完成多个函数的调用。

solidity
function refundETH() external payable override {
if (address(this).balance \u003e 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance);
}

  1. 检查是否实现 TWAP来获取预言机价格

当将 Uniswap 作为价格来源时,外部协议直接访问 Slot0 获取 sqrtPriceX96 可能存在价格操纵的风险。攻击者能通过 swap 等方式操纵流动性池的状态,从而在执行交易时获得有利的价格。为了降低这种风险,建议开发者进一步实现时间加权平均价格 (TWAP) 来获取价格,因为 TWAP 能有效减少短期内价格的剧烈波动影响,使操纵价格的难度增加。

solidity
function observe( Observation[65535] storage self, uint32 time, uint32[] memory secondsAgos, int24 tick, uint16 index, uint128 liquidity, uint16 cardinality ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) {
require(cardinality \u003e 0, 'I');
tickCumulatives = new int56[](secondsAgos.length);
secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length);
uint32 secondsAgo;
uint32 index0;
int56 tickCumulative;
uint160 secondsPerLiquidityCumulativeX128;
for (uint256 i = 0; i \u003c secondsAgos.length; i++) {
secondsAgo = secondsAgos[i];
index0 = (index + self.cardinality - uint16(secondsAgo)) % self.cardinality;
tickCumulative = self.observations[index0].tickCumulative;
secondsPerLiquidityCumulativeX128 = self.observations[index0].secondsPerLiquidityCumulativeX128;
tickCumulatives[i] = tickCumulative;
secondsPerLiquidityCumulativeX128s[i] = secondsPerLiquidityCumulativeX128;
}
}

结论

Uniswap v3 的推出,不仅是去中心化交易所的一次重大升级,更代表着 DeFi 领域在流动性管理、交易效率和安全性方面取得的突破。集中流动性、多重费率、闪电贷等功能,为用户提供了更灵活、更便捷的交易体验,也为开发者提供了更强大的工具。然而,随着协议的复杂性增加,安全审计的重要性也日益凸显。本文提出的审计要点,旨在帮助审计人员更有效地识别潜在的安全风险,确保 Uniswap v3 的安全性和稳定性。

参考文献


>>> Read more <<<

Views: 0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注