全面比较:Uniswap V4与V3:变更与创新

  • cyfrin
  • 发布于 2024-12-31 17:31
  • 阅读 41

本文详细介绍了Uniswap V4的架构更新和技术创新,包括新的Hook系统、单例模式、闪电结算、费用层灵活性和原生代币支持等方面,阐释了这些更新如何解决V3中的一系列问题,如Gas效率低和设计可定制性差。通过代码示例对比V3与V4的主要区别,使读者更好理解Uniswap V4的潜力和应用。

Uniswap V4 与 V3:架构变化和技术创新及代码示例

了解 Uniswap V4 的架构更新和技术创新:新的 hook 系统、单例模式、闪电会计、费用等级灵活性、原生代币支持。

Uniswap V4 引入了一系列更新,构建在 Uniswap V3 集中流动性模型之上,重点是提高效率和灵活性。本文旨在突出 Uniswap V4 中的关键架构变化和技术创新,包括新的 hook 系统、单例模式、闪电会计、费用等级灵活性和原生代币支持。这些更新解决了 V3 的各种限制,例如Gas效率低下和自动做市商(AMM)设计中有限的可定制性。为方便比较,已包含代码片段/链接。

Uniswap V4 核心架构变化

单例模式 & ERC-6909

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();
       }
   }
}

Hooks 和自定义会计

对 V4 协议架构的另一个显著且备受期待的变化是引入 hooks。允许集成者在特定预设点“Hook”执行,解锁了巨大的创新潜力,并解决了一个关键设计目标,即无需完全的协议分叉就能修改 AMM 机制(这一努力往往伴随安全漏洞)。

Uniswap V4 实现了在 8 种 hook 类型 中的 14 个独特权限。Hook 权限通过地址挖掘独特处理——hooks 必须部署到特定地址以确定其权限。这种方法防止未进行新合约部署的权限变更,并通过在 hook 合约地址中编码权限来优化Gas使用,消除了对外部权限检查的需求。

要了解 hooks 启用的自定义会计,可以考虑 V4 交换支持的 正值和负值 作为 SwapParams.amountSpecified,这意味着某些 hook 权限( beforeSwapafterSwapbeforeSwapReturnDeltaafterSwapReturnDelta)可以返回增量以修改交换金额。这可以用于实现自定义交易曲线逻辑(例如稳定交换、动态费用、TWAMM 等),并替换传统的 V3 集中流动性模型,创建全新的 AMM 实现。

虽然 hooks 可以在集中流动性上修改交换,但调用者指定的金额保持不变(除非流动性被消耗,正如在 V3V4 中一样),如果满足滑点要求则交换将成功。类似的但更严格的行为也可以在对 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 合约经过重新设计,以支持类似于 UniversalRouterV4Router 合约的命令样式接口。虽然与 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 块降低了恶意订阅者通过回退使其用户遭受困扰的风险。因此,取消订阅总是成功的。

UniversalRouter 集成

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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
cyfrin
cyfrin
Securing the blockchain and its users. Industry-leading smart contract audits, tools, and education.