Polymarket CTF Exchange 智能合約深度剖析:條件代幣框架與訂單匹配引擎的工程實現


生成提示詞

請深入分析 Polymarket CTF Exchange 智能合約的完整架構:
1. Clone polymarket-ctf-exchange 源代碼
2. 分析合約繼承層次與 Mixin 模式設計
3. 研究 Gnosis CTF(條件代幣框架)整合方式
4. 解析訂單結構、簽名驗證與匹配邏輯
5. 分析 MINT/MERGE/COMPLEMENTARY 三種結算類型
6. 繪製 Mermaid 流程圖說明訂單生命週期
7. 以繁體中文撰寫工程級深度文章

執行摘要

Polymarket 是領先的去中心化預測市場,其核心 CTF Exchange 智能合約實現了複雜的訂單匹配與結算邏輯。本文將深入剖析其核心技術:

  • Mixin 模式架構:模組化合約組合實現關注點分離
  • 條件代幣框架 (CTF):Gnosis 標準的二元結果代幣化機制
  • EIP712 簽名系統:支援 EOA、Proxy Wallet、Gnosis Safe、EIP1271
  • 三種結算類型:MINT(鑄造)、MERGE(合併)、COMPLEMENTARY(互補交換)
  • 價格交叉邏輯:動態判斷訂單是否可匹配

目錄

  1. 合約架構總覽
  2. 訂單數據結構
  3. 簽名驗證系統
  4. 條件代幣框架整合
  5. 訂單匹配邏輯
  6. 費用計算機制
  7. 代幣註冊與管理
  8. 安全機制
  9. 實際交易流程

1. 合約架構總覽

1.1 Mixin 繼承層次

Polymarket 使用 Mixin 模式將功能分散到多個合約中,實現高度模組化:

classDiagram
    class CTFExchange {
        +fillOrder()
        +fillOrders()
        +matchOrders()
        +cancelOrder()
    }

    class BaseExchange {
        <<ERC1155Holder, ReentrancyGuard>>
    }

    class Auth {
        +admins mapping
        +operators mapping
        +onlyAdmin()
        +onlyOperator()
    }

    class Assets {
        +collateral address
        +ctf address
        +getCollateral()
        +getCtf()
    }

    class Fees {
        +feeRate uint256
        +getMaxFeeRate()
    }

    class Pausable {
        +paused bool
        +pauseTrading()
        +unpauseTrading()
    }

    class AssetOperations {
        +_transfer()
        +_mint()
        +_merge()
    }

    class Hashing {
        +domainSeparator bytes32
        +hashOrder()
    }

    class NonceManager {
        +nonces mapping
        +incrementNonce()
    }

    class Registry {
        +registry mapping
        +_registerToken()
    }

    class Signatures {
        +verifyEOASignature()
        +verifyPolyProxySignature()
        +verifyPolySafeSignature()
        +verifyPoly1271Signature()
    }

    class Trading {
        +orderStatus mapping
        +_fillOrder()
        +_matchOrders()
    }

    CTFExchange --|> BaseExchange
    CTFExchange --|> Auth
    CTFExchange --|> Assets
    CTFExchange --|> Fees
    CTFExchange --|> Pausable
    CTFExchange --|> AssetOperations
    CTFExchange --|> Hashing
    CTFExchange --|> NonceManager
    CTFExchange --|> Registry
    CTFExchange --|> Signatures
    CTFExchange --|> Trading

1.2 目錄結構

src/
├── exchange/
│   ├── CTFExchange.sol          # 主合約入口
│   ├── BaseExchange.sol         # 基礎功能
│   ├── interfaces/              # 介面定義
│   │   ├── IAuth.sol
│   │   ├── IAssets.sol
│   │   ├── IFees.sol
│   │   ├── IHashing.sol
│   │   ├── INonceManager.sol
│   │   ├── IPausable.sol
│   │   ├── IRegistry.sol
│   │   ├── ISignatures.sol
│   │   ├── ITrading.sol
│   │   └── ICTFExchange.sol
│   ├── libraries/               # 工具庫
│   │   ├── OrderStructs.sol     # 訂單結構定義
│   │   ├── CalculatorHelper.sol # 價格與費用計算
│   │   ├── PolyProxyLib.sol     # Proxy 錢包地址推導
│   │   └── PolySafeLib.sol      # Gnosis Safe 地址推導
│   └── mixins/                  # 功能模組
│       ├── Auth.sol
│       ├── Assets.sol
│       ├── Fees.sol
│       ├── Pausable.sol
│       ├── AssetOperations.sol
│       ├── Hashing.sol
│       ├── NonceManager.sol
│       ├── Registry.sol
│       ├── Signatures.sol
│       └── Trading.sol
└── common/                      # 通用元件

1.3 主合約入口

檔案路徑: src/exchange/CTFExchange.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

contract CTFExchange is
    BaseExchange,
    Auth,
    Assets,
    Fees,
    Pausable,
    AssetOperations,
    Hashing("Polymarket CTF Exchange", "1"),
    NonceManager,
    Registry,
    Signatures,
    Trading
{
    constructor(
        address _collateral,     // USDC 地址
        address _ctf,            // 條件代幣合約
        address _proxyFactory,   // Poly Proxy 工廠
        address _safeFactory     // Gnosis Safe 工廠
    )
        Assets(_collateral, _ctf)
        Signatures(_proxyFactory, _safeFactory)
    { }
}

關鍵依賴:

  • Collateral: 通常為 USDC,作為所有預測市場的抵押品
  • CTF: Gnosis 條件代幣合約,符合 ERC1155 標準
  • ProxyFactory: 用於計算 Polymarket Proxy 錢包地址
  • SafeFactory: 用於計算 Gnosis Safe 錢包地址

2. 訂單數據結構

2.1 Order 結構

檔案路徑: src/exchange/libraries/OrderStructs.sol

struct Order {
    uint256 salt;              // 唯一亂數,確保訂單唯一性
    address maker;             // 資金來源地址
    address signer;            // 簽名者地址
    address taker;             // 指定接單者 (address(0) = 公開)
    uint256 tokenId;           // CTF ERC1155 代幣 ID
    uint256 makerAmount;       // maker 願意賣出的數量
    uint256 takerAmount;       // maker 期望收到的數量
    uint256 expiration;        // 過期時間戳
    uint256 nonce;             // 用於批量取消的 nonce
    uint256 feeRateBps;        // 費率 (基點,最高 1000 = 10%)
    Side side;                 // BUY 或 SELL
    SignatureType signatureType; // 簽名類型
    bytes signature;           // 簽名數據
}

2.2 枚舉類型

enum Side {
    BUY,   // 購買結果代幣
    SELL   // 賣出結果代幣
}

enum SignatureType {
    EOA,              // 普通外部帳戶
    POLY_PROXY,       // Polymarket Proxy 錢包
    POLY_GNOSIS_SAFE, // Gnosis Safe 多簽
    POLY_1271         // EIP1271 合約錢包
}

enum MatchType {
    COMPLEMENTARY,  // 買賣互補
    MINT,           // 雙方都買 - 鑄造新代幣
    MERGE           // 雙方都賣 - 合併代幣
}

2.3 訂單狀態追蹤

struct OrderStatus {
    bool isFilledOrCancelled;  // 是否已完成或取消
    uint256 remaining;         // 剩餘可成交數量
}

// Trading mixin 中的狀態映射
mapping(bytes32 => OrderStatus) public orderStatus;

2.4 EIP712 訂單雜湊

bytes32 constant ORDER_TYPEHASH = keccak256(
    "Order(uint256 salt,address maker,address signer,address taker,"
    "uint256 tokenId,uint256 makerAmount,uint256 takerAmount,"
    "uint256 expiration,uint256 nonce,uint256 feeRateBps,"
    "uint8 side,uint8 signatureType)"
);

function hashOrder(Order memory order) public view returns (bytes32) {
    return _hashTypedDataV4(
        keccak256(
            abi.encode(
                ORDER_TYPEHASH,
                order.salt,
                order.maker,
                order.signer,
                order.taker,
                order.tokenId,
                order.makerAmount,
                order.takerAmount,
                order.expiration,
                order.nonce,
                order.feeRateBps,
                order.side,
                order.signatureType
            )
        )
    );
}

3. 簽名驗證系統

3.1 四種簽名類型

Polymarket 支援多種錢包類型,實現廣泛的錢包生態系統兼容:

flowchart TB
    subgraph SignatureTypes["簽名類型"]
        EOA[EOA - 普通錢包]
        Proxy[POLY_PROXY - Poly 代理錢包]
        Safe[POLY_GNOSIS_SAFE - 多簽錢包]
        EIP1271[POLY_1271 - 合約錢包]
    end

    subgraph Verification["驗證流程"]
        CheckType{檢查簽名類型}
        ECDSA[ECDSA 簽名驗證]
        ProxyOwner[驗證 Proxy 所有權]
        SafeOwner[驗證 Safe 所有權]
        ContractSig[EIP1271 合約驗證]
    end

    CheckType -->|EOA| ECDSA
    CheckType -->|POLY_PROXY| ECDSA
    ECDSA -->|成功| ProxyOwner
    CheckType -->|POLY_GNOSIS_SAFE| ECDSA
    ECDSA -->|成功| SafeOwner
    CheckType -->|POLY_1271| ContractSig

3.2 簽名驗證實作

檔案路徑: src/exchange/mixins/Signatures.sol

abstract contract Signatures is ISignatures {
    address public proxyFactory;
    address public safeFactory;

    constructor(address _proxyFactory, address _safeFactory) {
        proxyFactory = _proxyFactory;
        safeFactory = _safeFactory;
    }

    function _validateSignature(
        bytes32 orderHash,
        Order memory order
    ) internal view {
        // 根據簽名類型選擇驗證方法
        if (order.signatureType == SignatureType.EOA) {
            if (!verifyEOASignature(
                order.signer, order.maker, orderHash, order.signature
            )) revert InvalidSignature();
        }
        else if (order.signatureType == SignatureType.POLY_PROXY) {
            if (!verifyPolyProxySignature(
                order.signer, order.maker, orderHash, order.signature
            )) revert InvalidSignature();
        }
        else if (order.signatureType == SignatureType.POLY_GNOSIS_SAFE) {
            if (!verifyPolySafeSignature(
                order.signer, order.maker, orderHash, order.signature
            )) revert InvalidSignature();
        }
        else if (order.signatureType == SignatureType.POLY_1271) {
            if (!verifyPoly1271Signature(
                order.signer, order.maker, orderHash, order.signature
            )) revert InvalidSignature();
        }
        else {
            revert InvalidSignatureType();
        }
    }
}

3.3 EOA 簽名驗證

function verifyEOASignature(
    address signer,
    address maker,
    bytes32 structHash,
    bytes memory signature
) internal pure returns (bool) {
    // 條件 1: signer 必須等於 maker
    // 條件 2: ECDSA 簽名有效
    return (signer == maker) &&
           verifyECDSASignature(signer, structHash, signature);
}

function verifyECDSASignature(
    address signer,
    bytes32 structHash,
    bytes memory signature
) internal pure returns (bool) {
    // 提取 r, s, v
    bytes32 r;
    bytes32 s;
    uint8 v;
    assembly {
        r := mload(add(signature, 0x20))
        s := mload(add(signature, 0x40))
        v := byte(0, mload(add(signature, 0x60)))
    }

    // 恢復簽名者地址
    address recoveredSigner = ecrecover(structHash, v, r, s);
    return recoveredSigner == signer;
}

3.4 Poly Proxy 錢包驗證

檔案路徑: src/exchange/libraries/PolyProxyLib.sol

library PolyProxyLib {
    /// @notice 計算 Proxy 錢包的 CREATE2 地址
    function getProxyWalletAddress(
        address signer,
        address implementation,
        address deployer
    ) internal pure returns (address proxyWallet) {
        // 使用 signer 作為 salt
        bytes32 salt = keccak256(abi.encodePacked(signer));

        // CREATE2 地址推導
        // address = keccak256(0xff ++ deployer ++ salt ++ bytecodeHash)[12:]
        bytes32 bytecodeHash = keccak256(
            getProxyBytecode(implementation)
        );

        proxyWallet = address(uint160(uint256(keccak256(
            abi.encodePacked(
                bytes1(0xff),
                deployer,
                salt,
                bytecodeHash
            )
        ))));
    }
}
function verifyPolyProxySignature(
    address signer,
    address proxyWallet,
    bytes32 structHash,
    bytes memory signature
) internal view returns (bool) {
    // 條件 1: ECDSA 簽名有效
    // 條件 2: 計算出的 proxy 地址等於 maker
    return verifyECDSASignature(signer, structHash, signature) &&
           getPolyProxyWalletAddress(signer) == proxyWallet;
}

3.5 Gnosis Safe 驗證

function verifyPolySafeSignature(
    address signer,
    address safeAddress,
    bytes32 hash,
    bytes memory signature
) internal view returns (bool) {
    return verifyECDSASignature(signer, hash, signature) &&
           getSafeAddress(signer) == safeAddress;
}

3.6 EIP1271 合約錢包驗證

function verifyPoly1271Signature(
    address signer,
    address maker,
    bytes32 hash,
    bytes memory signature
) internal view returns (bool) {
    // 條件 1: signer 等於 maker
    // 條件 2: maker 是合約
    // 條件 3: EIP1271 isValidSignature 返回 true
    return (signer == maker) &&
           maker.code.length > 0 &&
           SignatureCheckerLib.isValidSignatureNow(maker, hash, signature);
}

4. 條件代幣框架整合

4.1 CTF 基本概念

Gnosis 條件代幣框架 (CTF) 實現了二元結果的代幣化:

flowchart LR
    subgraph Collateral["抵押品"]
        USDC[100 USDC]
    end

    subgraph CTF["條件代幣"]
        YES[100 YES 代幣]
        NO[100 NO 代幣]
    end

    subgraph Outcome["結果"]
        Win[勝出方獲得抵押品]
        Lose[失敗方代幣歸零]
    end

    USDC -->|splitPosition| YES
    USDC -->|splitPosition| NO
    YES -->|結算| Win
    NO -->|結算| Lose

    YES -->|mergePositions| USDC
    NO -->|mergePositions| USDC

4.2 資產操作 Mixin

檔案路徑: src/exchange/mixins/AssetOperations.sol

abstract contract AssetOperations is IAssetOperations, Assets {
    bytes32 public constant parentCollectionId = bytes32(0);

    /// @notice 統一的資產轉移
    function _transfer(
        address from,
        address to,
        uint256 id,
        uint256 value
    ) internal virtual override {
        // tokenId = 0 表示抵押品 (ERC20)
        if (id == 0) return _transferCollateral(from, to, value);
        // 其他 tokenId 表示 CTF 代幣 (ERC1155)
        return _transferCTF(from, to, id, value);
    }

    /// @notice 獲取餘額
    function _getBalance(uint256 tokenId)
        internal
        virtual
        override
        returns (uint256)
    {
        if (tokenId == 0) {
            return IERC20(getCollateral()).balanceOf(address(this));
        }
        return IERC1155(getCtf()).balanceOf(address(this), tokenId);
    }
}

4.3 MINT 操作 - 鑄造結果代幣

當兩個 BUY 訂單匹配時,需要從抵押品鑄造新的結果代幣:

/// @notice 從抵押品鑄造結果代幣
function _mint(bytes32 conditionId, uint256 amount) internal override {
    // 分區:[1, 2] 表示兩個互補結果
    uint256[] memory partition = new uint256[](2);
    partition[0] = 1;  // YES
    partition[1] = 2;  // NO

    // 調用 CTF 的 splitPosition
    // 將 amount 抵押品分割為等量的 YES 和 NO 代幣
    IConditionalTokens(getCtf()).splitPosition(
        IERC20(getCollateral()),  // 抵押品
        parentCollectionId,        // 根集合
        conditionId,               // 條件 ID
        partition,                 // 分區
        amount                     // 數量
    );
}

MINT 流程:

  1. 從交易所合約扣除 amount 抵押品
  2. 鑄造 amount 個 YES 代幣
  3. 鑄造 amount 個 NO 代幣
  4. 代幣存入交易所合約供後續分配

4.4 MERGE 操作 - 合併結果代幣

當兩個 SELL 訂單匹配時,需要合併結果代幣回抵押品:

/// @notice 將結果代幣合併為抵押品
function _merge(bytes32 conditionId, uint256 amount) internal override {
    uint256[] memory partition = new uint256[](2);
    partition[0] = 1;
    partition[1] = 2;

    // 調用 CTF 的 mergePositions
    // 銷毀等量的 YES 和 NO 代幣,釋放抵押品
    IConditionalTokens(getCtf()).mergePositions(
        IERC20(getCollateral()),
        parentCollectionId,
        conditionId,
        partition,
        amount
    );
}

MERGE 流程:

  1. 從交易所合約扣除 amount 個 YES 代幣
  2. 從交易所合約扣除 amount 個 NO 代幣
  3. 銷毀這些代幣
  4. 釋放 amount 抵押品到交易所合約

4.5 代幣轉移函數

/// @notice 轉移抵押品 (ERC20)
function _transferCollateral(
    address from,
    address to,
    uint256 value
) internal {
    if (from == address(this)) {
        // 從合約轉出
        SafeTransferLib.safeTransfer(
            ERC20(getCollateral()), to, value
        );
    } else if (to == address(this)) {
        // 轉入合約
        SafeTransferLib.safeTransferFrom(
            ERC20(getCollateral()), from, to, value
        );
    } else {
        // 用戶間轉移
        SafeTransferLib.safeTransferFrom(
            ERC20(getCollateral()), from, to, value
        );
    }
}

/// @notice 轉移 CTF 代幣 (ERC1155)
function _transferCTF(
    address from,
    address to,
    uint256 id,
    uint256 value
) internal {
    IERC1155(getCtf()).safeTransferFrom(from, to, id, value, "");
}

5. 訂單匹配邏輯

5.1 匹配類型判斷

檔案路徑: src/exchange/mixins/Trading.sol

function _deriveMatchType(
    Order memory takerOrder,
    Order memory makerOrder
) internal pure returns (MatchType) {
    if (takerOrder.side == Side.BUY && makerOrder.side == Side.BUY) {
        // 雙方都買 - 需要鑄造新代幣
        return MatchType.MINT;
    }
    if (takerOrder.side == Side.SELL && makerOrder.side == Side.SELL) {
        // 雙方都賣 - 需要合併代幣
        return MatchType.MERGE;
    }
    // 買賣互補 - 直接交換
    return MatchType.COMPLEMENTARY;
}

5.2 訂單成交流程圖

sequenceDiagram
    participant Operator as 操作員
    participant Exchange as CTFExchange
    participant CTF as 條件代幣
    participant Maker as 掛單者
    participant Taker as 接單者

    Operator->>Exchange: fillOrder(order, fillAmount)

    Exchange->>Exchange: _validateTaker()
    Exchange->>Exchange: hashOrder()
    Exchange->>Exchange: _validateOrder()

    alt 驗證失敗
        Exchange-->>Operator: revert
    end

    Exchange->>Exchange: calculateTakingAmount()
    Exchange->>Exchange: calculateFee()
    Exchange->>Exchange: _updateOrderStatus()

    alt BUY 訂單
        Exchange->>Taker: 轉入結果代幣
        Exchange->>Maker: 轉出結果代幣
        Exchange->>Maker: 收取抵押品 (扣除費用)
    else SELL 訂單
        Exchange->>Maker: 轉入結果代幣
        Exchange->>Taker: 收取抵押品 (扣除費用)
    end

    Exchange-->>Operator: emit OrderFilled

5.3 單一訂單成交

function _fillOrder(
    Order memory order,
    uint256 fillAmount,
    address to
) internal {
    uint256 making = fillAmount;

    // 執行訂單檢查並計算 taking 數量
    (uint256 taking, bytes32 orderHash) = _performOrderChecks(order, making);

    // 計算費用
    uint256 fee = CalculatorHelper.calculateFee(
        order.feeRateBps,
        order.side == Side.BUY ? taking : making,
        order.makerAmount,
        order.takerAmount,
        order.side
    );

    // 推導資產 ID
    (uint256 makerAssetId, uint256 takerAssetId) = _deriveAssetIds(order);

    // 從 taker 轉移到 maker (扣除費用)
    _transfer(msg.sender, order.maker, takerAssetId, taking - fee);

    // 從 maker 轉移到 taker
    _transfer(order.maker, to, makerAssetId, making);

    emit OrderFilled(
        orderHash,
        order.maker,
        msg.sender,
        makerAssetId,
        takerAssetId,
        making,
        taking,
        fee
    );
}

5.4 訂單匹配(雙訂單)

function _matchOrders(
    Order memory takerOrder,
    Order[] memory makerOrders,
    uint256 takerFillAmount,
    uint256[] memory makerFillAmounts
) internal {
    uint256 making = takerFillAmount;
    (uint256 taking, bytes32 orderHash) = _performOrderChecks(takerOrder, making);
    (uint256 makerAssetId, uint256 takerAssetId) = _deriveAssetIds(takerOrder);

    // 步驟 1: 拉取 taker 訂單的 maker 資產到交易所
    _transfer(takerOrder.maker, address(this), makerAssetId, making);

    // 步驟 2: 填充所有 maker 訂單
    _fillMakerOrders(takerOrder, makerOrders, makerFillAmounts);

    // 步驟 3: 更新實際收到的 taking 數量(可能有盈餘)
    taking = _updateTakingWithSurplus(taking, takerAssetId);

    // 步驟 4: 計算並收取費用
    uint256 fee = CalculatorHelper.calculateFee(
        takerOrder.feeRateBps,
        takerOrder.side == Side.BUY ? taking : making,
        making,
        taking,
        takerOrder.side
    );

    // 步驟 5: 將收益轉給 taker maker (扣除費用)
    _transfer(address(this), takerOrder.maker, takerAssetId, taking - fee);

    // 步驟 6: 費用轉給操作員
    _chargeFee(address(this), msg.sender, takerAssetId, fee);

    // 步驟 7: 退還未匹配的資產
    uint256 refund = _getBalance(makerAssetId);
    if (refund > 0) {
        _transfer(address(this), takerOrder.maker, makerAssetId, refund);
    }

    emit OrderFilled(orderHash, takerOrder.maker, address(this),
                     makerAssetId, takerAssetId, making, taking, fee);
    emit OrdersMatched(orderHash, takerOrder.maker,
                       makerAssetId, takerAssetId, making, taking);
}

5.5 填充 Maker 訂單

function _fillMakerOrders(
    Order memory takerOrder,
    Order[] memory makerOrders,
    uint256[] memory makerFillAmounts
) internal {
    for (uint256 i = 0; i < makerOrders.length; i++) {
        Order memory makerOrder = makerOrders[i];
        uint256 makerFillAmount = makerFillAmounts[i];

        // 確保訂單針對相同代幣
        if (takerOrder.tokenId != makerOrder.tokenId) revert MismatchedTokenIds();

        // 確保訂單可以交叉
        if (!CalculatorHelper.isCrossing(takerOrder, makerOrder))
            revert OrdersNotCrossing();

        // 推導匹配類型
        MatchType matchType = _deriveMatchType(takerOrder, makerOrder);

        // 根據匹配類型執行 CTF 操作
        if (matchType == MatchType.MINT) {
            _performMintMatch(takerOrder, makerOrder, makerFillAmount);
        } else if (matchType == MatchType.MERGE) {
            _performMergeMatch(takerOrder, makerOrder, makerFillAmount);
        } else {
            _performComplementaryMatch(takerOrder, makerOrder, makerFillAmount);
        }
    }
}

6. 費用計算機制

6.1 價格計算

檔案路徑: src/exchange/libraries/CalculatorHelper.sol

uint256 internal constant ONE = 10 ** 18;
uint256 internal constant BPS_DIVISOR = 10_000;

/// @notice 計算訂單價格
function calculatePrice(Order memory order)
    internal pure returns (uint256)
{
    return _calculatePrice(order.makerAmount, order.takerAmount, order.side);
}

function _calculatePrice(
    uint256 makerAmount,
    uint256 takerAmount,
    Side side
) internal pure returns (uint256) {
    // BUY: 價格 = 每個代幣的抵押品成本 = makerAmount / takerAmount
    if (side == Side.BUY) {
        return takerAmount != 0 ? makerAmount * ONE / takerAmount : 0;
    }
    // SELL: 價格 = 每個代幣的抵押品收入 = takerAmount / makerAmount
    return makerAmount != 0 ? takerAmount * ONE / makerAmount : 0;
}

6.2 費用計算公式

/// @notice 計算交易費用
/// @param feeRateBps 費率(基點)
/// @param outcomeTokens 結果代幣數量
/// @param makerAmount maker 數量
/// @param takerAmount taker 數量
/// @param side 訂單方向
function calculateFee(
    uint256 feeRateBps,
    uint256 outcomeTokens,
    uint256 makerAmount,
    uint256 takerAmount,
    Side side
) internal pure returns (uint256 fee) {
    if (feeRateBps > 0) {
        uint256 price = _calculatePrice(makerAmount, takerAmount, side);

        if (price > 0 && price <= ONE) {
            // 費用基於 min(price, 1-price)
            // 這確保了價格接近 0.5 時費用最高
            uint256 minPrice = price < (ONE - price) ? price : (ONE - price);

            if (side == Side.BUY) {
                // BUY: 費用從代幣收益中扣除
                fee = (feeRateBps * minPrice * outcomeTokens)
                    / (price * BPS_DIVISOR);
            } else {
                // SELL: 費用從抵押品收益中扣除
                fee = feeRateBps * minPrice * outcomeTokens
                    / (BPS_DIVISOR * ONE);
            }
        }
    }
}

6.3 費用計算範例

假設 feeRateBps = 200 (2%),買入 100 個結果代幣,價格 0.6:

price = 0.6
minPrice = min(0.6, 0.4) = 0.4

fee = 200 * 0.4 * 100 / (0.6 * 10000)
    = 8000 / 6000
    = 1.33 抵押品

6.4 訂單交叉判斷

/// @notice 判斷兩個訂單是否可以匹配
function isCrossing(Order memory a, Order memory b)
    internal pure returns (bool)
{
    if (a.takerAmount == 0 || b.takerAmount == 0) return true;
    return _isCrossing(
        calculatePrice(a),
        calculatePrice(b),
        a.side,
        b.side
    );
}

function _isCrossing(
    uint256 priceA,
    uint256 priceB,
    Side sideA,
    Side sideB
) internal pure returns (bool) {
    if (sideA == Side.BUY) {
        if (sideB == Side.BUY) {
            // 兩個 BUY: 價格和 >= 1.0 (可以 MINT)
            return priceA + priceB >= ONE;
        }
        // BUY vs SELL: 買價 >= 賣價
        return priceA >= priceB;
    }
    if (sideB == Side.BUY) {
        // SELL vs BUY: 買價 >= 賣價
        return priceB >= priceA;
    }
    // 兩個 SELL: 價格和 <= 1.0 (可以 MERGE)
    return priceA + priceB <= ONE;
}

7. 代幣註冊與管理

7.1 Registry Mixin

檔案路徑: src/exchange/mixins/Registry.sol

struct OutcomeToken {
    uint256 complement;   // 互補代幣 ID
    bytes32 conditionId;  // 條件 ID
}

abstract contract Registry is IRegistry {
    // 代幣 ID => 代幣資訊
    mapping(uint256 => OutcomeToken) public registry;

    /// @notice 註冊代幣對
    function _registerToken(
        uint256 token0,
        uint256 token1,
        bytes32 conditionId
    ) internal {
        // 驗證代幣 ID
        if (token0 == token1 || token0 == 0 || token1 == 0)
            revert InvalidTokenId();

        // 防止重複註冊
        if (registry[token0].complement != 0 ||
            registry[token1].complement != 0)
            revert AlreadyRegistered();

        // 雙向註冊
        registry[token0] = OutcomeToken({
            complement: token1,
            conditionId: conditionId
        });
        registry[token1] = OutcomeToken({
            complement: token0,
            conditionId: conditionId
        });

        emit TokenRegistered(token0, token1, conditionId);
        emit TokenRegistered(token1, token0, conditionId);
    }

    /// @notice 獲取互補代幣
    function getComplement(uint256 token)
        public view override returns (uint256)
    {
        return registry[token].complement;
    }

    /// @notice 獲取條件 ID
    function getConditionId(uint256 token)
        public view override returns (bytes32)
    {
        return registry[token].conditionId;
    }
}

7.2 代幣對範例

對於一個「2024 年美國總統選舉」市場:

Token ID描述ComplementCondition ID
12345YES (民主黨勝)123460xabc…123
12346NO (民主黨敗)123450xabc…123

8. 安全機制

8.1 重入攻擊防護

// BaseExchange 繼承 ReentrancyGuard
abstract contract BaseExchange is ERC1155Holder, ReentrancyGuard { }

// 所有交易函數使用 nonReentrant
function fillOrder(Order memory order, uint256 fillAmount)
    external
    nonReentrant
    onlyOperator
    notPaused
{
    _fillOrder(order, fillAmount, order.maker);
}

8.2 權限控制

modifier onlyAdmin() {
    if (admins[msg.sender] != 1) revert NotAdmin();
    _;
}

modifier onlyOperator() {
    if (operators[msg.sender] != 1) revert NotOperator();
    _;
}

權限分配:

  • Admin: 管理 operators、暫停交易、註冊代幣
  • Operator: 執行 fillOrder/matchOrders、收取費用

8.3 交易暫停

bool public paused = false;

modifier notPaused() {
    if (paused) revert Paused();
    _;
}

function pauseTrading() external onlyAdmin {
    _pauseTrading();
}

function _pauseTrading() internal {
    paused = true;
    emit TradingPaused(msg.sender);
}

8.4 Nonce 批量取消

// 每個用戶有一個 nonce
mapping(address => uint256) public nonces;

/// @notice 遞增 nonce 取消所有舊訂單
function incrementNonce() external override {
    updateNonce(1);
}

/// @notice 更新 nonce
function updateNonce(uint256 val) public override {
    nonces[msg.sender] += val;
    emit NonceIncremented(msg.sender, nonces[msg.sender]);
}

/// @notice 驗證 nonce
function isValidNonce(address usr, uint256 nonce)
    public view override returns (bool)
{
    return nonces[usr] == nonce;
}

取消機制:

  1. 用戶呼叫 incrementNonce()
  2. nonces[user] 變為 N+1
  3. 所有 nonce = N 的訂單自動失效
  4. 新訂單必須使用 nonce = N+1

8.5 費用上限

// 最高 10% 費率
uint256 internal constant MAX_FEE_RATE_BIPS = 1000;

function _validateOrder(bytes32 orderHash, Order memory order) internal {
    // ... 其他驗證

    // 費率不能超過最大值
    if (order.feeRateBps > getMaxFeeRate()) revert FeeTooHigh();
}

8.6 過期時間驗證

function _validateOrder(bytes32 orderHash, Order memory order) internal {
    // 訂單過期檢查
    if (order.expiration > 0 && order.expiration < block.timestamp)
        revert OrderExpired();

    // ... 其他驗證
}

9. 實際交易流程

9.1 場景一:簡單訂單成交

sequenceDiagram
    participant Alice as Alice (Maker)
    participant Bob as Bob (Operator/Taker)
    participant Exchange as CTFExchange
    participant USDC as USDC
    participant CTF as CTF Tokens

    Note over Alice: 創建 BUY 訂單:100 USDC 買 166 YES

    Alice->>Alice: 簽署訂單 (EIP712)

    Bob->>Exchange: fillOrder(order, 100)
    Exchange->>Exchange: 驗證簽名
    Exchange->>Exchange: 驗證 nonce、過期、費用

    Exchange->>CTF: transferFrom(Bob, Alice, YES_TOKEN, 100)
    Exchange->>USDC: transferFrom(Alice, Bob, 58 USDC)

    Note over Alice: 收到 100 YES 代幣
    Note over Bob: 收到 58 USDC (60 - 2 費用)

9.2 場景二:MINT 匹配

兩個 BUY 訂單匹配,需要鑄造新代幣:

sequenceDiagram
    participant Alice as Alice (BUY YES @ 0.6)
    participant Bob as Bob (BUY NO @ 0.5)
    participant Operator as Operator
    participant Exchange as CTFExchange
    participant CTF as CTF Tokens
    participant USDC as USDC

    Operator->>Exchange: matchOrders(aliceOrder, [bobOrder])

    Exchange->>USDC: transferFrom(Alice, Exchange, 60)
    Exchange->>USDC: transferFrom(Bob, Exchange, 50)

    Exchange->>CTF: splitPosition(100 USDC → 100 YES + 100 NO)

    Exchange->>CTF: transfer(Alice, 100 YES)
    Exchange->>CTF: transfer(Bob, 100 NO)

    Exchange->>USDC: transfer(Alice, 10 退款)

    Note over Alice: 花費 50 USDC,得到 100 YES
    Note over Bob: 花費 50 USDC,得到 100 NO

9.3 場景三:MERGE 匹配

兩個 SELL 訂單匹配,合併代幣為抵押品:

sequenceDiagram
    participant Alice as Alice (SELL YES @ 0.6)
    participant Bob as Bob (SELL NO @ 0.4)
    participant Operator as Operator
    participant Exchange as CTFExchange
    participant CTF as CTF Tokens
    participant USDC as USDC

    Operator->>Exchange: matchOrders(aliceOrder, [bobOrder])

    Exchange->>CTF: transferFrom(Alice, Exchange, 100 YES)
    Exchange->>CTF: transferFrom(Bob, Exchange, 100 NO)

    Exchange->>CTF: mergePositions(100 YES + 100 NO → 100 USDC)

    Exchange->>USDC: transfer(Alice, 60)
    Exchange->>USDC: transfer(Bob, 40)

    Note over Alice: 賣出 100 YES,得到 60 USDC
    Note over Bob: 賣出 100 NO,得到 40 USDC

總結

架構優勢

  1. Mixin 模式

    • 高度模組化,關注點分離
    • 易於測試和維護
    • 可獨立升級各模組
  2. 多簽名支援

    • EOA、Proxy、Gnosis Safe、EIP1271
    • 覆蓋各種錢包類型
    • CREATE2 地址推導確保安全性
  3. CTF 整合

    • 符合 Gnosis 標準
    • MINT/MERGE 機制優化資金效率
    • 二元結果完整代幣化
  4. 靈活費用模型

    • 價格敏感費用計算
    • 接近 0.5 價格時費用最高
    • 最高 10% 費率上限

安全特性

機制防護目標
ReentrancyGuard重入攻擊
onlyOperator非授權交易執行
Nonce 機制批量訂單取消
過期時間過期訂單執行
費用上限過高費用
Pausable緊急停止

關鍵文件路徑

合約入口:
├── src/exchange/CTFExchange.sol

核心邏輯:
├── src/exchange/mixins/Trading.sol
├── src/exchange/mixins/Signatures.sol
├── src/exchange/mixins/AssetOperations.sol

數據結構:
├── src/exchange/libraries/OrderStructs.sol
├── src/exchange/libraries/CalculatorHelper.sol

工具庫:
├── src/exchange/libraries/PolyProxyLib.sol
├── src/exchange/libraries/PolySafeLib.sol

Polymarket CTF Exchange 展示了如何在智能合約中實現複雜的訂單匹配系統,其模組化設計、多錢包支援與 CTF 整合值得深入學習。