Uniswap v4 Permissioned Pools 解析:把合规控制嵌入 AMM 交易路径
2026-08-1108:01
Beosin
2026-08-11 08:01
Beosin
2026-08-11 08:01
收藏文章
订阅专栏


Uniswap v4 把 AMM 从固定池子合约推进到可扩展的池子执行框架。v4 通过单例 PoolManager 管理所有池子,并允许每个池子挂载一个 hook,在初始化、swap、增减流动性等生命周期节点插入自定义逻辑。

Permissioned Pools 正是基于 hook 能力构建的。Uniswap Labs 在 7 月 23 日联合 Securitize 和 Superstate 发布了 Permissioned Pools,旨在让受监管资产、RWA(如股票代币、基金代币)等资产可以在 AMM 上交易,同时把白名单和权限检查放到链上执行,而不是只依赖前端或链下合规判断。

一、Permissioned Pools 机制解析

传统 AMM Pool 默认是 permissionless 的,任何地址只要持有代币、授权代币,就可以进行交易或提供流动性。这一设计难以满足 RWA 资产的合规要求,而如果直接把 permissioned token 放入 Uniswap v4 AMM Pool,未获得授权的地址可能通过内部 claim 或组合路径绕过原 token 白名单的流动性入口。为此,Uniswap 的 Permissioned Pools 采用了 adapter + hook 的设计:池子里交易的不是原始 permissioned token,而是由 PermissionsAdapter 管理的虚拟表示。


1.1 Permissioned Pools 架构

Permissioned Pools 主要由以下 4 个核心组件:

(1) PermissionsAdapter

核心包装合约。它持有底层 permissioned token,并铸造池子内部使用的 virtual token。用户通常不直接管理 virtual token,路由器和 position manager 会在交互中自动处理包装与解包(unwrap)。

(2) IAllowlistChecker

发行方自行实现的白名单检查合约。它返回某个地址对某个 token 的权限,例如是否允许 swap、是否允许提供流动性。


(3) PermissionedHooks

挂在 v4 pool 上的 hook。它在 beforeInitialize、beforeSwap、beforeAddLiquidity 等阶段执行检查:池子是否使用已验证 adapter、swapper 是否有 SWAP_ALLOWED、LP 是否有 LIQUIDITY_ALLOWED。


(4) PermissionedPositionManager / Universal Router

负责用户实际交互。permissioned swap routing 需要通过 Universal Router,而 LP position NFT 也不可转让,以避免已授权地址把 LP 仓位转给未授权地址。这一限制能够对获得做市收益或间接持有敞口的场景进行监管。


1.2 交易流程如何被限制

以 swap 为例,路径基本为:

(1) 用户持有底层 permissioned token;

(2) 用户通过 Universal Router 发起 swap;

(3) Router 检查并把底层 token 转入 PermissionsAdapter;

(4) Adapter 创建池子内部使用的 virtual token;

(5) v4 PoolManager 执行 swap;

(6) PermissionedHooks.beforeSwap 检查用户是否有 SWAP_ALLOWED;

(7) swap 完成后,virtual token 被转换回底层 token。


二、代码案例分析

2.1 Allowlist Checker

下面是一个简化版 allowlist checker,实际生产环境应使用多签、更细致的角色权限管理、可审计的 KYC provider 更新流程。

 

// SPDX-License-Identifier: MITpragma solidity ^0.8.24; import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {    IAllowlistChecker} from "@uniswap/v4-periphery/src/hooks/permissionedPools/interfaces/IAllowlistChecker.sol"; import {    PermissionFlag,    PermissionFlags} from "@uniswap/v4-periphery/src/hooks/permissionedPools/libraries/PermissionFlags.sol"; contract IssuerAllowlistChecker is IAllowlistChecker, ERC165, Ownable {    mapping(address account => PermissionFlag permission) private _permissions;     constructor(address initialOwner) Ownable(initialOwner) {}     function checkAllowlist(        address account,        address /* tokenAddress */    ) external view returns (PermissionFlag) {        return _permissions[account];    }     function setSwapAllowed(address account, bool allowed) external onlyOwner {        PermissionFlag current = _permissions[account];         if (allowed) {            _permissions[account] = current | PermissionFlags.SWAP_ALLOWED;        } else {            _permissions[account] = current & ~PermissionFlags.SWAP_ALLOWED;        }    }     function setLiquidityAllowed(address account, bool allowed) external onlyOwner {        PermissionFlag current = _permissions[account];         if (allowed) {            _permissions[account] = current | PermissionFlags.LIQUIDITY_ALLOWED;        } else {            _permissions[account] = current & ~PermissionFlags.LIQUIDITY_ALLOWED;        }    }     function setFullPermission(address account) external onlyOwner {        _permissions[account] =            PermissionFlags.SWAP_ALLOWED |            PermissionFlags.LIQUIDITY_ALLOWED;    }     function clearPermission(address account) external onlyOwner {        delete _permissions[account];    }     function supportsInterface(        bytes4 interfaceId    ) public view override(ERC165, IERC165) returns (bool) {        return            interfaceId == type(IAllowlistChecker).interfaceId ||            super.supportsInterface(interfaceId);    }}

该合约只负责检查某地址对某类资产有什么权限,真正的执行操作发生在 adapter 和 hook 中。

2.2 部署 adapter 并创建 pool

// SPDX-License-Identifier: MITpragma solidity ^0.8.24;import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";import {    IAllowlistChecker} from "@uniswap/v4-periphery/src/hooks/permissionedPools/interfaces/IAllowlistChecker.sol"; interface IPermissionsAdapterFactory {    function createPermissionsAdapter(        IERC20 token,        address owner,        IAllowlistChecker checker    ) external returns (address adapter);     function verifyPermissionsAdapter(address adapter) external;} interface IPermissionsAdapter {    function depositForVerification(uint256 amount) external;    function updateAllowedWrapper(address wrapper, bool allowed) external;    function updateSwappingEnabled(bool enabled) external;} interface IPermissionedPositionManager {    function setAllowedHook(        Currency currency,        IHooks hook,        bool allowed    ) external;} contract PermissionedPoolOnboardingExample {    function onboard(        address permissionedToken,        address issuerAdmin,        address checker,        address factory,        address permissionedPositionManager,        address universalRouter,        address v4Quoter,        address mixedRouteQuoterV2,        address permissionedHooks,        address pairedAsset    ) external returns (PoolKey memory key) {        address adapter = IPermissionsAdapterFactory(factory)            .createPermissionsAdapter(                IERC20(permissionedToken),                issuerAdmin,                IAllowlistChecker(checker)            );        IPermissionsAdapter(adapter).depositForVerification(1);        IPermissionsAdapterFactory(factory).verifyPermissionsAdapter(adapter);        IPermissionsAdapter(adapter).updateAllowedWrapper(permissionedPositionManager, true);        IPermissionsAdapter(adapter).updateAllowedWrapper(universalRouter, true);        IPermissionsAdapter(adapter).updateAllowedWrapper(v4Quoter, true);        IPermissionsAdapter(adapter).updateAllowedWrapper(mixedRouteQuoterV2, true);        IPermissionedPositionManager(permissionedPositionManager).setAllowedHook(            Currency.wrap(adapter),            IHooks(permissionedHooks),            true        );         key = PoolKey({            currency0: Currency.wrap(adapter),            currency1: Currency.wrap(pairedAsset),            fee: 3000,            tickSpacing: 60,            hooks: IHooks(permissionedHooks)        });         IPermissionsAdapter(adapter).updateSwappingEnabled(true);    }}


Permissioned Pools 将合规控制嵌入链上执行路径,但需要注意其中的潜在安全点:PermissionsAdapter 的 owner 地址可以更换 allowlist checker、可以暂停或开启 swap、批准或撤销 wrapper,allowlist checker 的逻辑实现,资产发行方管理的用户资格变化。


结语

Uniswap v4 Permissioned Pools 的意义在于它让 permissioned assets 可以进入 AMM 流动性池。v4 的 hook 架构提供了插入点,PermissionsAdapter 解决了底层 token 不能直接进入共享 PoolManager 的问题,PermissionedHooks 则把 swap 和 LP 的资格检查变成链上约束。这一设计将为 RWA 项目提供更标准的链上流动性基础设施,获得更多流动性,但项目团队需满足更复杂的权限治理和更高的合约审计要求。

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

专栏文章
查看更多
数据请求中

推荐专栏

数据请求中

一起「遇见」未来

DOWNLOAD FORESIGHT NEWS APP

Download QR Code