Appearance
L3-6: Prediction Market(预测市场)
1. 问题
预测市场允许参与者对未来事件(如"2024 年大选谁会赢")进行交易,价格反映市场对事件发生概率的集体判断。链上实现面临三个核心挑战:(1) 如何创建代表"是"和"否"结果的金融工具;(2) 如何确保市场创建者和参与者无法操纵最终结果;(3) 获胜方的奖金分配如何确保公平。
与 Polymarket 等大型预测市场不同,这个挑战构建的是简化版本——固定价格购买 YES/NO 份额对,结果由任何人(而非去中心化预言机)提交。这引入了信任假设,但保留了预测市场核心的二元结果 + 份额赎回机制。
2. 原因
Polymarket 的成功(月交易量数十亿美元)验证了预测市场在 Web3 的产品-市场契合度。区块链提供无需许可的市场创建、透明的定价和无需信任的结算。在传统预测市场中,法律和地域限制是主要障碍——Polymarket 通过加密货币规避了部分限制。
对于开发者而言,预测市场是理解"条件代币"(conditional tokens)概念的优秀切入点。条件代币框架(CTF)将预测市场中的每个可能结果封装为可交易的 ERC-1155 代币。这个简化的实现聚焦于最核心的二元市场机制:购买份额、结算市场、赎回奖金。
3. 方案
PredictionMarket.sol 实现了基于 ETH 的二元预测市场。
创建市场:createMarket() 接受问题描述和持续时间。市场结构包含:question、endTime、resolved(是否已结算)、isYes(结果)、totalYesShares/totalNoShares(总份额量)。
购买份额:buyShares() 允许用户同时购买 YES 和 NO 份额。定价模型简化:每个份额对(1 YES + 1 NO)= SHARE_PRICE = 0.01 ETH。用户支付 (yesAmount + noAmount) * SHARE_PRICE ETH,多付的部分退还。这本质上是一个"购买整个市场敞口"的模型——用户持有两个方向的份额,可以在市场上卖出自己不想要的方向。
结算市场:resolveMarket() 由任何人调用(在测试/教育环境中)。设置 isYes 标志位和 resolved = true。在生产环境中,结算应由去中心化预言机(如 UMA's Optimistic Oracle 或 Chainlink Functions)执行。
赎回奖金:redeem() 允许持有获胜份额的用户按比例提取奖金。总奖池 = 所有份额的总购买成本 (totalYes + totalNo) * SHARE_PRICE。如果 YES 获胜,持有 YES 份额的用户获得:(myYesShares * totalPool) / totalYesShares。
参考源文件:src/level3/PredictionMarket.sol
4. 遭遇的陷阱
- 结算者可信度:任何人都可以调用
resolveMarket(),在缺乏权限控制的情况下,恶意行为者可以提交错误的结果 - 份额对定价的流动性问题:当前模型强制用户同时购买 YES 和 NO 份额,无法单独购买某一个方向,限制了价格发现
- 赎回后的份额状态:
redeem()在发放奖金后不销毁份额,也不减少totalYesShares/totalNoShares,可能导致后续赎回计算的混乱 - 奖金池与合约余额脱节:奖池计算基于
totalShares * SHARE_PRICE,但合约实际余额可能因其他错误或直接转账而不一致 - 手续费缺失:市场创建者没有获得任何经济激励(如交易手续费),缺乏可持续性
5. 陷阱的原因
结算可信度是去中心化预测市场的核心问题——"预言机问题"。在 PredictionMarket.sol 中,resolveMarket() 没有权限控制,任何人都可以调用。这是因为在教育设计中省略了权限管理(测试驱动开发中,任何人都应该能够结算以方便测试)。但在生产中,这会导致严重的操纵风险。
更深层的问题在于:很多事件的真实结果是主观的或需要链下验证的。例如"比特币在 2025 年涨停到 $200K"——何时算作涨停?依据什么价格数据源?这些都需要预言机来桥接。UMA 的 Optimistic Oracle 通过经济激励(挑战机制)来解决这个问题——结算者需要质押代币,如果结果被成功挑战,质押将被罚没。
份额对定价模型也值得讨论。当前强制同时购买 YES+NO 的模型被称为"二元 CLOB 的简化版"。在 Polymarket 的实际实现中,使用的是 CLOB(中央限价订单簿)+ CTF(条件代币框架),允许用户单独买卖 YES 或 NO 份额。价格通过订单簿的买卖盘口自然发现。YES 份额的价格在 0 到 1 美元之间浮动,反映市场对事件发生概率的判断。
6. 如何解决陷阱
预言机集成:在生产环境中,使用 UMA Optimistic Oracle 或 Chainlink Functions 来结算市场:
solidity
function resolveMarket(uint256 _marketId) external {
// 从 UMA 预言机获取结果
bool outcome = umaOracle.getOutcome(market.question);
// 或使用 Chainlink Functions 请求链下数据
// ... 后续逻辑
}
单独份额购买:修改 buyShares() 为独立的 buyYes() 和 buyNo():
solidity
function buyYes(uint256 _marketId, uint256 _amount) external payable {
uint256 cost = _amount * currentYesPrice; // 需要 AMM 定价模型
positions[_marketId][msg.sender].yesShares += _amount;
market.totalYesShares += _amount;
}
价格的发现可以通过 AMM 的 x*y=k 曲线,或 CLOB 的订单簿匹配。
经济激励:为市场创建者添加手续费分成:
solidity
uint256 public constant CREATOR_FEE_BPS = 100; // 1%
// 在 buyShares 中扣除手续费并转给 marketCreator
余额一致性:在 redeem() 中,从合约的实际余额扣除,而非使用公式计算的 totalPool:
solidity
uint256 payout = (winningShares * address(this).balance) / totalWinningShares;
7. 技术要点
| 技术点 | 说明 |
|---|---|
| 二元市场 | YES/NO 两个互斥结果 |
| 份额对定价 | 1 YES + 1 NO = 0.01 ETH(简化模型) |
| 赎回算法 | 按比例分配:(myShares * totalPool) / winningShares |
| 状态机 | 创建 → 交易中 → 结算 → 赎回 |
| 防重入 | buyShares 和 redeem 使用 nonReentrant |
| 多付退款 | 超额支付自动退还,改善 UX |
| 预言机依赖 | 结算结果需要外部数据输入 |