本文详细介绍了Uniswap V4的架构更新和技术创新,包括新的Hook系统、单例模式、闪电结算、费用层灵活性和原生代币支持等方面,阐释了这些更新如何解决V3中的一系列问题,如Gas效率低和设计可定制性差。通过代码示例对比V3与V4的主要区别,使读者更好理解Uniswap V4的潜力和应用。
了解 Uniswap V4 的架构更新和技术创新:新的 hook 系统、单例模式、闪电会计、费用等级灵活性、原生代币支持。
Uniswap V4 引入了一系列更新,构建在 Uniswap V3 集中流动性模型之上,重点是提高效率和灵活性。本文旨在突出 Uniswap V4 中的关键架构变化和技术创新,包括新的 hook 系统、单例模式、闪电会计、费用等级灵活性和原生代币支持。这些更新解决了 V3 的各种限制,例如Gas效率低下和自动做市商(AMM)设计中有限的可定制性。为方便比较,已包含代码片段/链接。
Uniswap V4 中最显著的变化之一是转向 单例架构,所有资产都由一个单一的 PoolManager 合约存储和管理。这代表了 V3 工厂模式 的根本转变,其中每个池都需要自己的 合约部署,这突显了一个安全核心协议的重要性,因为单例可以被看作是攻击者的蜜罐。
单例方法大幅降低了Gas成本,并通过 将所有代币对合并到一个合约中 提高了资本效率。这种合并消除了在每个池之间路由代币交换的需求,从而节省了大量Gas并改善了系统效率。
V4 还引入了其他一些优化,以提升 PoolManager 中的 状态 管理,例如基础 Extsload 功能,通过加载任意存储插槽显著减少合约字节码大小;但这并不以开发者体验为代价,因为 StateLibrary 提供了熟悉的函数名来封装存储插槽的抽象计算。
V4 另一个抽象是使用 ERC-6909 代币 来结算增量(由于交换或其他池交互而导致的代币余额差异)和 PoolManager 中的代币管理。这是对 ERC-1155 的一种Gas优化替代方案,专门针对在单个合约中按 id 管理多个代币而设计。
Uniswap V3 工厂模式:
contract UniswapV3Factory is UniswapV3PoolDeployer {
   mapping(address => mapping(address => mapping(uint24 => address))) public getPool;
   function createPool(
       address tokenA,
       address tokenB,
       uint24 fee
   ) external returns (address pool) {
       pool = deploy(address(this), token0, token1, fee, tickSpacing);
       getPool[token0][token1][fee] = pool;
   }
}
contract UniswapV3PoolDeployer {
   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;
   }
}
Uniswap V4 单例模式:
contract PoolManager {
   mapping(PoolId id => Pool.State) internal _pools;
   function initialize(
       PoolKey memory key,
       uint160 sqrtPriceX96
   ) external noDelegateCall returns (int24 tick) {
       ...
       PoolId id = key.toId();
       tick = _pools[id].initialize(sqrtPriceX96, lpFee);
       ...
   }
}
library Pool {
   function initialize(
       State storage self,
       uint160 sqrtPriceX96,
       uint24 lpFee
   ) internal returns (int24 tick) {
       if (self.slot0.sqrtPriceX96() != 0) PoolAlreadyInitialized.selector.revertWith();
       tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);
       // 初始的 protocolFee 为 0,因此不需要设置
       self.slot0 = Slot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setLpFee(lpFee);
   }
}
在单例模式的效率提升基础上,Uniswap V4 引入了一种“闪电会计”系统,这也有助于在流动性池之间建立路由时显著提高Gas效率。与 V3 中使用的 直接代币转账 不同,V4 系统在单个交易的整个生命周期内 跟踪余额增量 并确保所有欠款在结束时 结算。确保这一逻辑的正确性至关重要;否则,协议及其基础资产的安全性可能会受到威胁。
如果存在未结算的正增量,用户必须故意调用 clear() 函数进行结算,否则执行将回退。这作为一种保护机制,防止无意中留下未结算的增量,防止意外状态更改。
这一设计使得可以将多个操作链入单个交易中,减少不必要的代币转账,大幅提高复杂操作的Gas效率。在 Uniswap V4 中,闪电会计凭借用于各种操作的汇编级瞬态存储得以实现,包括:
这些优化组合在一起,通过最小化代币转账和内部状态更新上的存储操作来减少Gas成本。
Uniswap V3 代币管理:
contract UniswapV3Pool {
   function swap(
       address recipient,
       bool zeroForOne,
       int256 amountSpecified,
       uint160 sqrtPriceLimitX96,
       bytes calldata data
   ) external override noDelegateCall returns (int256 amount0, int256 amount1) {
       ...
       // 进行转移并收取费用
       if (zeroForOne) {
           if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1));
           uint256 balance0Before = balance0();
           IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
           require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA');
       } else {
           if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0));
           uint256 balance1Before = balance1();
           IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
           require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA');
       }
   }
}
Uniswap V4 闪电会计:
contract PoolManager {
   function _settle(address recipient) internal returns (uint256 paid) {
       Currency currency = CurrencyReserves.getSyncedCurrency();
       // 如果之前未同步,或者同步货币槽已重置,预计结算原生货币
       if (currency.isAddressZero()) {
           paid = msg.value;
       } else {
           if (msg.value > 0) NonzeroNativeValue.selector.revertWith();
           // 储备保证被设置,因为货币和储备总是一起设置
           uint256 reservesBefore = CurrencyReserves.getSyncedReserves();
           uint256 reservesNow = currency.balanceOfSelf();
           paid = reservesNow - reservesBefore;
           CurrencyReserves.resetCurrency();
       }
       _accountDelta(currency, paid.toInt128(), recipient);
   }
   function _accountDelta(Currency currency, int128 delta, address target) internal {
       if (delta == 0) return;
       (int256 previous, int256 next) = currency.applyDelta(target, delta);
       if (next == 0) {
           NonzeroDeltaCount.decrement();
       } else if (previous == 0) {
           NonzeroDeltaCount.increment();
       }
   }
}
对 V4 协议架构的另一个显著且备受期待的变化是引入 hooks。允许集成者在特定预设点“Hook”执行,解锁了巨大的创新潜力,并解决了一个关键设计目标,即无需完全的协议分叉就能修改 AMM 机制(这一努力往往伴随安全漏洞)。
Uniswap V4 实现了在 8 种 hook 类型 中的 14 个独特权限。Hook 权限通过地址挖掘独特处理——hooks 必须部署到特定地址以确定其权限。这种方法防止未进行新合约部署的权限变更,并通过在 hook 合约地址中编码权限来优化Gas使用,消除了对外部权限检查的需求。
要了解 hooks 启用的自定义会计,可以考虑 V4 交换支持的 正值和负值 作为 SwapParams.amountSpecified,这意味着某些 hook 权限( beforeSwap、afterSwap、beforeSwapReturnDelta、afterSwapReturnDelta)可以返回增量以修改交换金额。这可以用于实现自定义交易曲线逻辑(例如稳定交换、动态费用、TWAMM 等),并替换传统的 V3 集中流动性模型,创建全新的 AMM 实现。
虽然 hooks 可以在集中流动性上修改交换,但调用者指定的金额保持不变(除非流动性被消耗,正如在 V3 和 V4 中一样),如果满足滑点要求则交换将成功。类似的但更严格的行为也可以在对 Pool::modifyLiquidity 的调用中实现。
除了定制外,hooks 的实现也是更广泛的 Uniswap V4 安全模型的一部分,通过 解锁回调系统,这是其安全架构的关键部分。任何可能应用增量的函数都可以在解锁时调用,并在再次锁定之前验证增量。这使得在调用 _accountDelta() 之前,任何破坏性假设对安全性尤为重要:
contract PoolManager {
   modifier onlyWhenUnlocked() {
       if (!Lock.isUnlocked()) ManagerLocked.selector.revertWith();
       _;
   }
   function unlock(bytes calldata data) external override returns (bytes memory result) {
       if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith();
       Lock.unlock();
       // 调用者在此回调中完成所有操作,包括通过结算调用支付所欠费用
       result = IUnlockCallback(msg.sender).unlockCallback(data);
       if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith();
       Lock.lock();
   }
}
为了实现额外的灵活性,Uniswap V4 移除了 V3 的 费用等级限制(0.05%、0.30%、1.00%)。这允许池设置 任何费用值,开启新的自定义费用结构的可能性,使市场能够找到其最佳费用水平,甚至支持 动态 LP 费用 在启用时。
凭借灵活费用和在 PoolId 结构 中集成 hooks,Uniswap V4 可以支持由相同货币组成的无限数量池。单例架构缓解了一般的代币分化,因为所有代币都存储在一个合约中,而市场力量将解决流动性分化,当流动性提供者聚集在一组通常被认可的池中。结果是,可以通过指定不同的费用和/或 hook 配置使任何货币对具有无限数量的池。因此,V4 不再支持池的链上枚举,所以初始化事件的索引变得更加重要。
Uniswap V3 固定费用:
contract UniswapV3Factory {
   mapping(uint24 => int24) public override feeAmountTickSpacing;
   constructor() {
       ...
       feeAmountTickSpacing[500] = 10;
       feeAmountTickSpacing[3000] = 60;
       feeAmountTickSpacing[10000] = 200;
   }
   function createPool(
       address tokenA,
       address tokenB,
       uint24 fee
   ) external override noDelegateCall returns (address pool) {
       ...
       int24 tickSpacing = feeAmountTickSpacing[fee];
       require(tickSpacing != 0);
       require(getPool[token0][token1][fee] == address(0));
       ...
   }
}
Uniswap V4 灵活费用:
contract PoolManager {
   function initialize(
       PoolKey memory key,
       uint160 sqrtPriceX96
   ) external noDelegateCall returns (int24 tick) {
       ...
       if (!key.hooks.isValidHookAddress(key.fee)) Hooks.HookAddressNotValid.selector.revertWith(address(key.hooks));
       uint24 lpFee = key.fee.getInitialLPFee();
       ...
   }
   function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external {
       if (!key.fee.isDynamicFee() || msg.sender != address(key.hooks)) {
           UnauthorizedDynamicLPFeeUpdate.selector.revertWith();
       }
       newDynamicLPFee.validate();
       PoolId id = key.toId();
       _pools[id].setLPFee(newDynamicLPFee);
   }
}
与 Uniswap V3 不支持原生代币不同,Uniswap V4 由于广泛使用自定义类型,因此直接支持原生代币。
除了 Currency 表示外,该表示通过一个共同的 转移 API 统一了 ERC-20 和原生代币处理。这些自定义类型包括:
这里的一个好处是通过内存打包 Slot0 结构来提高Gas效率。此外,关于 Currency 中的 返回数据大小检查 的一个有趣实现细节源于需要容纳一些 小众 Curve v1 代币,这些代币返回的是 4096 字节而非严格的 32 字节。
注意,某些代币如 CELO 拥有原生和 ERC-20 表示,这可能使得通过自定义 Currency 类型进行代币处理变得困难。对于这样的实现需要格外小心,以避免引入任何安全漏洞,正如 Open Zeppelin 审计报告中记录的。
Uniswap V4 引入了一种优化的位置管理系统,利用核心闪电会计系统。
外围的 PositionManager 合约经过重新设计,以支持类似于 UniversalRouter 和 V4Router 合约的命令样式接口。虽然与 V3 的 NonFungiblePositionManager 非常不同,但该结构允许批量操作以 经典操作和解决增量功能,因此支持 复杂的多步骤操作 在单个交易中。
contract PositionManager is BaseActionsRouter {
   function modifyLiquidities(bytes calldata unlockData, uint256 deadline)
       external
       payable
       isNotLocked
       checkDeadline(deadline)
   {
       _executeActions(unlockData);
   }
}
contract BaseActionsRouter {
   function _executeActions(bytes calldata unlockData) internal {
       poolManager.unlock(unlockData);
   }
   function _handleAction(uint256 action, bytes calldata params) internal virtual;
}
与 V3 不同,Uniswap V4 位置通过使用 盐参数 唯一标识,使得每个流动性位置独特。之前,在同一流动性范围内的两个存款者 共享池状态 作为Gas优化,但 V4 使它们隔离,以便在定制和跟踪上更好。在 V4 中,仅 存储代币id和底层信息,然后可以直接用于查询 PoolManager。
该系统使得用户能够在调用增加头寸的函数时指定最大金额,在调用减少头寸的函数时指定最小金额。因闪电会计自动管理费用,因此在 扣除费用的本金金额 上执行滑点验证。
Uniswap V4 外围合约引入新的通知者/订阅者模型,以支持无须 NFT 转移的质押操作。该设计可以视为流动性头寸的 hooks,允许 订阅者 注册更新,并监视头寸状态,包括流动性金额和代币转账的任何修改。
对用户的操作成本降至最低,同时保持其 LP 头寸的完全所有权。Gas限制验证 的实现确保在取消订阅时通知给定的订阅者,try/catch 块降低了恶意订阅者通过回退使其用户遭受困扰的风险。因此,取消订阅总是成功的。
Uniswap V4 的交换现在通过基础 Dispatcher 合约集成到 UniversalRouter 中。这通过允许在 Uniswap V2、V3 和 V4 池之间进行复杂的优化交易路径增强了路由功能。该功能通过访问多种池类型和路径,为用户提供无缝的交易体验。
UniversalRouter 还通过 V3ToV4Migrator 合约支持从 V3 到 V4 的流动性迁移。通过嵌套操作,流动性提供者可以无缝转移其流动性头寸,利用闪电会计以确保增量解决而无不必要的代币转账。
这两个特性都由 PositionManager 中的两个入口点中的第二个启用,跳过在 PoolManager 上的解锁调用,使 UniversalRouter 能够在 hooks 中管理头寸。
V4 外围合约还允许用户结算在预先可能未知的交换金额的增量。这对费用转移代币或复杂的路由交易特别有用。该功能通过 Actions 常量 实现,处理灵活的交换金额,并根据增量解决路由代币。
这种灵活性方便了更稳健的交易执行,并允许在外围进行更好的定制。请注意,ERC-6909 代币在当前情况下不直接支持 V4Router,但可以通过 Actions 常量实现,预计未来将添加此功能。
Uniswap V4 代表了 AMM 架构演进的下一阶段。通过闪电会计和模块化 hook 系统等创新,Uniswap V4 解决了 V3 中存在的许多限制,同时在经过验证的集中流动性模型上进行了迭代。
这些架构变化也为更可定制的流动性模型奠定了基础。这使得 Uniswap V4 不仅仅是一个 DEX,而是构建下一代去中心化金融应用的高度多功能框架,允许开发者安全地进行实验、构建和创新。
这是一个令人兴奋的设计空间,尤其是对自定义 hooks 和由此衍生的 hook 审核的安全性关注既具有挑战性又极其重要。要了解更多关于 Uniswap V4 的信息并深入其安全考虑,建议阅读 白皮书、核心协议审计、外围合约审计 和 hook 权限的已知效果。
- 原文链接: cyfrin.io/blog/uniswap-v...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!