Uniswap v4 Permissioned Pools解析:把合规控制嵌入 AMM 交易路径

현재 언어 번역이 없어 원문을 표시합니다.
Uniswap v4 把 AMM 从固定池子合约推进到可扩展的池子执行框架。v4 通过单例 PoolManager 管理所有池子,并允许每个池子挂载一个 hook,在初始化、swap、增减流动性等生命周期节点插入自定义逻辑。

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: MIT
pragma 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: MIT
pragma 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 项目提供更标准的链上流动性基础设施,获得更多流动性,但项目团队需满足更复杂的权限治理和更高的合约审计要求

공유하기:

작성자: Beosin

이 글은 PANews 입주 칼럼니스트의 관점으로, PANews의 입장을 대표하지 않으며 법적 책임을 지지 않습니다.

글 및 관점은 투자 조언을 구성하지 않습니다

이미지 출처: Beosin. 권리 침해가 있을 경우 저자에게 삭제를 요청해 주세요.

PANews 공식 계정을 팔로우하고 함께 상승장과 하락장을 헤쳐나가세요
PANews APP
방어형 AI 네트워크 보안 모델 코르마, 6000만 달러 시드 투자 유치
PANews 속보