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. 合約架構總覽
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 流程:
- 從交易所合約扣除
amount抵押品 - 鑄造
amount個 YES 代幣 - 鑄造
amount個 NO 代幣 - 代幣存入交易所合約供後續分配
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 流程:
- 從交易所合約扣除
amount個 YES 代幣 - 從交易所合約扣除
amount個 NO 代幣 - 銷毀這些代幣
- 釋放
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 | 描述 | Complement | Condition ID |
|---|---|---|---|
| 12345 | YES (民主黨勝) | 12346 | 0xabc…123 |
| 12346 | NO (民主黨敗) | 12345 | 0xabc…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;
}
取消機制:
- 用戶呼叫
incrementNonce() - nonces[user] 變為 N+1
- 所有 nonce = N 的訂單自動失效
- 新訂單必須使用 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
總結
架構優勢
-
Mixin 模式
- 高度模組化,關注點分離
- 易於測試和維護
- 可獨立升級各模組
-
多簽名支援
- EOA、Proxy、Gnosis Safe、EIP1271
- 覆蓋各種錢包類型
- CREATE2 地址推導確保安全性
-
CTF 整合
- 符合 Gnosis 標準
- MINT/MERGE 機制優化資金效率
- 二元結果完整代幣化
-
靈活費用模型
- 價格敏感費用計算
- 接近 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 整合值得深入學習。