4.7 智能合约
一句话总结:Factory 模式批量生产市场,预言机保证结果可信——这是链上核心。
📊 学习进度
- 状态:⬜ 未开始
- 预计时长:6-8 小时
- 已完成:0/3 个模块
- 在整体流程中的位置:预测市场实战·第 4 阶段
📍 本章定位
- 服务方案:方案 3(核心 90%)
- 学习方式:⭐ 必学
- 在流程中的作用:实现预测市场的链上逻辑
- 核心知识点:预言机集成、Factory 模式、Gas 优化
- 预计时长:6-8 小时
- 完成后能做什么:能开发完整的预测市场智能合约
人机分工
| 环节 | 谁做 | 重要度 | 说明 |
|---|---|---|---|
| 合约架构设计 | 🧑 人 | ⭐⭐⭐⭐⭐ | 设计 Factory 模式 |
| 预言机设计 | 🧑 人 | ⭐⭐⭐⭐⭐ | 设计验证机制 |
| 代码实现 | 🤖 AI | ⭐⭐⭐⭐ | AI 生成代码 |
| 代码审核 | 🧑 人 | ⭐⭐⭐⭐⭐ | 必须人审 |
| Gas 优化 | 🤖 AI | ⭐⭐⭐ | AI 建议优化 |
1. Factory 模式架构
1.1 整体架构图
1.2 Factory 产出关系
| Factory | 产出 | 功能 | 关键特性 |
|---|---|---|---|
| EventVoteFactory | eventVotePod | 事件投票 | 投票完成后用户认可才进入下一步 |
| EventFactory | eventPod | 事件创建 | 管理事件元数据 |
| FundingFactory | fundingPod | 资金管理 | 管理市场资金池 |
| OrderBookFactory | orderBookPod | 订单簿 | 收取 0.5% 手续费 |
| FeeVaultFactory | feeVaultPod | 手续费 | 收取 0.25% 管理费 |
| — | AdminFeeVault | 管理费池 | 平台收入 |
1.3 Factory 模式的优势
| 对比维度 | 单一合约 | Factory 模式 |
|---|---|---|
| 隔离性 | 一个出问题全部停 | 互不影响 |
| 扩展性 | 只能做一个 | 可以批量创建 |
| 独立性 | 共享资源 | 各自独立 |
| 收益结构 | 统一分配 | 各自独立 |
| 升级影响 | 全部受影响 | 可独立升级 |
2. 核心合约实现
2.1 EventVoteFactory 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
/// @title EventVoteFactory - 事件投票工厂
/// @notice 批量创建事件投票 Pod
contract EventVoteFactory is Ownable {
address public immutable implementation;
address[] public pods;
event PodCreated(address indexed pod, uint256 indexed podId);
constructor(address _implementation) Ownable(msg.sender) {
implementation = _implementation;
}
/// @notice 创建新的事件投票 Pod
/// @param title 事件标题
/// @param description 事件描述
/// @param endTime 投票截止时间
/// @return pod 新创建的 Pod 地址
function createPod(
string calldata title,
string calldata description,
uint256 endTime
) external onlyOwner returns (address pod) {
// 使用 Clones 库最小代理模式,节省 Gas
pod = Clones.clone(implementation);
// 初始化 Pod
IEventVotePod(pod).initialize(title, description, endTime, msg.sender);
pods.push(pod);
emit PodCreated(pod, pods.length - 1);
}
/// @notice 获取所有 Pod 地址
function getAllPods() external view returns (address[] memory) {
return pods;
}
/// @notice 获取 Pod 数量
function podCount() external view returns (uint256) {
return pods.length;
}
}
interface IEventVotePod {
function initialize(
string calldata title,
string calldata description,
uint256 endTime,
address creator
) external;
}2.2 EventFactory 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
/// @title EventFactory - 事件工厂
/// @notice 管理事件的创建和元数据
contract EventFactory is AccessControl {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
address public immutable eventImplementation;
address[] public events;
struct EventInfo {
string title;
string description;
string category;
string resolutionSource;
uint256 endTime;
address creator;
bool resolved;
bool outcome;
}
event EventCreated(uint256 indexed eventId, address indexed eventAddress, string title);
event EventResolved(uint256 indexed eventId, bool outcome);
constructor(address _eventImplementation) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
eventImplementation = _eventImplementation;
}
/// @notice 创建新事件
/// @param title 事件标题
/// @param description 事件描述
/// @param category 事件分类
/// @param resolutionSource 结果验证来源
/// @param endTime 结算时间
/// @return eventId 事件 ID
function createEvent(
string calldata title,
string calldata description,
string calldata category,
string calldata resolutionSource,
uint256 endTime
) external onlyRole(OPERATOR_ROLE) returns (uint256 eventId) {
require(endTime > block.timestamp, "End time must be in the future");
eventId = events.length;
address eventAddr = Clones.clone(eventImplementation);
IEventPod(eventAddr).initialize(
title, description, category, resolutionSource, endTime, msg.sender
);
events.push(eventAddr);
emit EventCreated(eventId, eventAddr, title);
}
/// @notice 解决事件
/// @param eventId 事件 ID
/// @param outcome 结果(true=YES, false=NO)
function resolveEvent(uint256 eventId, bool outcome)
external
onlyRole(OPERATOR_ROLE)
{
require(eventId < events.length, "Invalid event ID");
IEventPod(events[eventId]).resolve(outcome);
emit EventResolved(eventId, outcome);
}
}
interface IEventPod {
function initialize(
string calldata title,
string calldata description,
string calldata category,
string calldata resolutionSource,
uint256 endTime,
address creator
) external;
function resolve(bool outcome) external;
}2.3 OrderBookFactory 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
/// @title OrderBookFactory - 订单簿工厂
/// @notice 管理订单簿的创建和交易撮合
contract OrderBookFactory is AccessControl {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
address public immutable orderBookImplementation;
address public feeVault;
uint256 public constant FEE_BPS = 50; // 0.5%
struct OrderBook {
address eventAddress;
address tokenYes;
address tokenNo;
uint256 totalVolume;
}
mapping(uint256 => OrderBook) public orderBooks;
event OrderBookCreated(uint256 indexed eventId, address orderBook);
event Trade(
uint256 indexed eventId,
address indexed trader,
bool isBuy,
bool isYes,
uint256 amount,
uint256 price,
uint256 fee
);
constructor(address _orderBookImplementation) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
orderBookImplementation = _orderBookImplementation;
}
/// @notice 为事件创建订单簿
/// @param eventId 事件 ID
/// @param eventAddress 事件合约地址
function createOrderBook(
uint256 eventId,
address eventAddress
) external onlyRole(OPERATOR_ROLE) returns (address) {
require(orderBooks[eventId].eventAddress == address(0), "Already exists");
address ob = Clones.clone(orderBookImplementation);
IOrderBook(ob).initialize(eventAddress, feeVault, FEE_BPS);
orderBooks[eventId] = OrderBook({
eventAddress: eventAddress,
tokenYes: IOrderBook(ob).tokenYes(),
tokenNo: IOrderBook(ob).tokenNo(),
totalVolume: 0
});
emit OrderBookCreated(eventId, ob);
return ob;
}
/// @notice 设置手续费接收地址
function setFeeVault(address _feeVault) external onlyRole(DEFAULT_ADMIN_ROLE) {
feeVault = _feeVault;
}
}
interface IOrderBook {
function initialize(address eventAddr, address feeVault, uint256 feeBps) external;
function tokenYes() external view returns (address);
function tokenNo() external view returns (address);
}3. 预言机与验证系统
3.1 预言机架构
3.2 验证机制
| 步骤 | 说明 | 关键参数 |
|---|---|---|
| 1. 节点质押 | 所有 oracle-node 先进行质押 | 最低质押 1000 USDC |
| 2. 结果提交 | 多个节点提交结果 | 至少 3 个节点 |
| 3. 汇总决策 | oracle-manager 汇总 | 多数一致即通过 |
| 4. 合约验证 | oracle-services-manager + zk-verifier | zk 证明正确性 |
| 5. 奖惩 | 成功奖励,失败罚没质押 | 奖励 = 质押 * 5% |
3.3 Oracle Manager 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/// @title OracleManager - 预言机管理器
/// @notice 管理预言机节点和结果汇总
contract OracleManager is AccessControl, ReentrancyGuard {
bytes32 public constant NODE_ROLE = keccak256("NODE_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
uint256 public constant MIN_NODES = 3;
uint256 public constant DISPUTE_PERIOD = 24 hours;
struct Resolution {
uint256 eventId;
bool outcome;
uint256 submitTime;
uint256 agreeCount;
uint256 disagreeCount;
bool finalized;
mapping(address => bool) hasVoted;
}
// eventId => Resolution
mapping(uint256 => Resolution) public resolutions;
event ResultSubmitted(uint256 indexed eventId, address indexed node, bool outcome);
event ResultFinalized(uint256 indexed eventId, bool outcome);
event NodeSlashed(address indexed node, uint256 amount);
/// @notice 提交事件结果
/// @param eventId 事件 ID
/// @param outcome 预测结果
function submitResult(uint256 eventId, bool outcome)
external
onlyRole(NODE_ROLE)
{
Resolution storage res = resolutions[eventId];
require(!res.hasVoted[msg.sender], "Already voted");
require(!res.finalized, "Already finalized");
res.hasVoted[msg.sender] = true;
if (res.submitTime == 0) {
res.eventId = eventId;
res.outcome = outcome;
res.submitTime = block.timestamp;
res.agreeCount = 1;
} else if (res.outcome == outcome) {
res.agreeCount++;
} else {
res.disagreeCount++;
}
emit ResultSubmitted(eventId, msg.sender, outcome);
// 自动终结条件:同意数超过最小节点数且无争议
if (res.agreeCount >= MIN_NODES && res.disagreeCount == 0) {
_finalize(eventId);
}
}
/// @notice 终结结果(争议期结束后)
/// @param eventId 事件 ID
function finalizeResult(uint256 eventId) external {
Resolution storage res = resolutions[eventId];
require(!res.finalized, "Already finalized");
require(block.timestamp >= res.submitTime + DISPUTE_PERIOD, "Dispute period not ended");
_finalize(eventId);
}
function _finalize(uint256 eventId) internal {
Resolution storage res = resolutions[eventId];
res.finalized = true;
// 奖励同意多数的节点,罚没少数节点
if (res.agreeCount > res.disagreeCount) {
// 多数节点获得奖励
emit ResultFinalized(eventId, res.outcome);
} else {
// 争议过大,需要人工介入
emit ResultFinalized(eventId, res.outcome);
}
}
/// @notice 检查结果是否已终结
function isFinalized(uint256 eventId) external view returns (bool) {
return resolutions[eventId].finalized;
}
/// @notice 获取终结结果
function getOutcome(uint256 eventId) external view returns (bool) {
require(resolutions[eventId].finalized, "Not finalized");
return resolutions[eventId].outcome;
}
}3.4 zk-verifier 集成
zk-verifier 的价值:
- 把结果确认机制做成"可证明"的,而不是停留在口头共识层面
- 防止预言机节点合谋作恶
- 提供密码学级别的安全保障
4. 合约安全模式
4.1 安全清单
| 安全措施 | 说明 | 实现方式 | 优先级 |
|---|---|---|---|
| 重入攻击防护 | 防止递归调用 | ReentrancyGuard | ⭐⭐⭐⭐⭐ |
| 整数溢出 | 防止数值异常 | Solidity 0.8+ 内置 | ⭐⭐⭐⭐⭐ |
| 访问控制 | 限制关键函数 | AccessControl | ⭐⭐⭐⭐⭐ |
| 暂停机制 | 紧急情况暂停 | Pausable | ⭐⭐⭐⭐ |
| 代理升级 | 可升级合约 | TransparentProxy | ⭐⭐⭐⭐ |
| 多签管理 | 关键操作多签 | Gnosis Safe | ⭐⭐⭐⭐⭐ |
4.2 紧急暂停合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
/// @title EmergencyPause - 紧急暂停管理
contract EmergencyPause is Pausable, AccessControl {
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
event EmergencyPaused(address indexed by, string reason);
event EmergencyUnpaused(address indexed by);
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(GUARDIAN_ROLE, msg.sender);
}
/// @notice 紧急暂停
/// @param reason 暂停原因
function emergencyPause(string calldata reason) external onlyRole(GUARDIAN_ROLE) {
_pause();
emit EmergencyPaused(msg.sender, reason);
}
/// @notice 恢复运行
function emergencyUnpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
emit EmergencyUnpaused(msg.sender);
}
}4.3 合约升级模式
// 使用 TransparentProxy 模式实现可升级
// 1. 部署实现合约(逻辑)
// 2. 部署代理合约(存储)
// 3. 通过代理调用实现合约
// 升级流程:
// 1. 部署新的实现合约
// 2. 通过 Timelock 提交升级提案
// 3. 等待延迟期(24-48 小时)
// 4. 多签确认执行升级5. 合约开发流程
5.1 完整开发流程
5.2 AI 辅助合约开发
AI 生成合约的 Prompt 示例:
请帮我生成一个预测市场的订单簿合约,要求:
1. 支持限价单和市价单
2. 使用价格优先、时间优先的撮合逻辑
3. 收取 0.5% 手续费
4. 支持批量结算
5. 使用 ReentrancyGuard 防重入
6. 使用 AccessControl 控制权限
请输出完整的 Solidity 代码,包含详细注释。AI 审计合约的 Prompt 示例:
请审计以下 Solidity 合约,检查以下安全问题:
1. 重入攻击
2. 整数溢出
3. 访问控制
4. 前端运行风险
5. 闪电贷攻击
6. 价格操纵
合约代码:
[paste contract code]
请列出所有发现的问题,按严重程度排序,并提供修复建议。5.3 测试策略
| 测试类型 | 工具 | 覆盖目标 | 说明 |
|---|---|---|---|
| 单元测试 | Hardhat | 90%+ | 每个函数独立测试 |
| 集成测试 | Hardhat | 80%+ | 模块间交互测试 |
| 模糊测试 | Foundry | 关键函数 | 随机输入测试 |
| 安全审计 | Slither | 全部 | 静态分析 |
| 形式化验证 | Certora | 核心逻辑 | 数学证明正确性 |
测试用例示例:
import { expect } from "chai";
import { ethers } from "hardhat";
describe("OrderBook", function () {
it("should create order and match", async function () {
const [buyer, seller] = await ethers.getSigners();
// 创建订单簿
const OrderBook = await ethers.getContractFactory("OrderBook");
const ob = await OrderBook.deploy();
// 买单
await ob.connect(buyer).placeOrder(true, 6000, 100); // YES @ $0.60
// 卖单
await ob.connect(seller).placeOrder(false, 6000, 100); // NO @ $0.60
// 验证撮合
const trades = await ob.getTrades();
expect(trades.length).to.equal(1);
expect(trades[0].price).to.equal(6000);
});
});6. Gas 优化详解
6.1 存储优化
| 优化策略 | 说明 | 节省 |
|---|---|---|
| 使用 mapping 替代 array | 减少存储槽 | 30-50% |
| 打包变量 | 多个小变量放一个 slot | 20-30% |
| 使用 immutable | 构造函数赋值 | 读取免费 |
| 使用 calldata | 不修改的参数 | 读取便宜 |
// 优化前:使用 array
address[] public users; // 每次访问需要计算偏移
// 优化后:使用 mapping
mapping(uint256 => address) public users; // 直接访问
// 变量打包示例
// 优化前:3 个 slot
uint256 a; // slot 0
uint256 b; // slot 1
bool c; // slot 2
// 优化后:2 个 slot
uint256 a; // slot 0
uint128 b; // slot 1 (前半)
bool c; // slot 1 (后半)
uint128 d; // slot 1 (后半)6.2 批处理优化
// 单笔结算:每笔一个交易
function settle(uint256 eventId, address user, uint256 amount) external {
_settle(eventId, user, amount); // ~50,000 Gas
}
// 批量结算:多笔一个交易
function batchSettle(
uint256 eventId,
address[] calldata users,
uint256[] calldata amounts
) external {
for (uint256 i = 0; i < users.length; i++) {
_settle(eventId, users[i], amounts[i]);
}
// 100 笔 ~2,000,000 Gas,平均每笔 20,000 Gas(节省 60%)
}7. 合约安全最佳实践
7.1 常见攻击向量与防御
预测市场合约面临的独特安全挑战:
| 攻击向量 | 攻击方式 | 防御措施 | 严重程度 |
|---|---|---|---|
| 重入攻击 | 回调中重复提取资金 | ReentrancyGuard + CEI 模式 | P0 |
| 前端运行 | 监听 mempool 抢先交易 | 提交-揭示方案 + 私有交易 | P0 |
| 闪电贷 | 借贷操纵价格 | TWAP + 区块延迟结算 | P1 |
| 预言机操纵 | 贿赂节点提交假结果 | 多节点 + zk 验证 + 质押罚没 | P0 |
| 整数溢出 | 数值计算异常 | Solidity 0.8+ + SafeMath | P1 |
| 权限提升 | 未授权操作 | AccessControl + 多签 | P0 |
CEI(Checks-Effects-Interactions)模式:
// 安全的结算函数
function settle(uint256 eventId, address user) external {
// Checks: 验证条件
require(isResolved[eventId], "Not resolved");
require(balances[eventId][user] > 0, "No balance");
require(!settled[eventId][user], "Already settled");
// Effects: 更新状态(在外部调用之前)
uint256 payout = calculatePayout(eventId, user);
settled[eventId][user] = true;
balances[eventId][user] = 0;
// Interactions: 外部调用(最后执行)
(bool success, ) = user.call{value: payout}("");
require(success, "Transfer failed");
}7.2 合约升级安全
使用 TransparentProxy 模式时的安全清单:
// 升级安全检查
contract UpgradeSafety {
// 1. 存储布局兼容性
// 新版本不能改变已有变量的顺序和类型
// 2. 初始化函数保护
bool private _initialized;
function initialize() external {
require(!_initialized, "Already initialized");
_initialized = true;
// 初始化逻辑
}
// 3. 升级权限控制
// 只有 Timelock 才能升级
function upgrade(address newImplementation) external onlyTimelock {
_authorizeUpgrade(newImplementation);
}
}升级前检查清单:
| 检查项 | 说明 | 工具 |
|---|---|---|
| 存储布局 | 新旧版本变量顺序一致 | OpenZeppelin Upgrades Plugin |
| 初始化 | 防止重复初始化 | initializer 修饰符 |
| 函数选择器 | 无冲突 | forge inspect |
| 测试覆盖 | 升级路径测试 | Hardhat + Fork 测试 |
| 审计报告 | 第三方审计 | Slither + 人工审核 |
7.3 Gas 优化进阶
// 优化 1: 使用 calldata 替代 memory
function createEvent(
string calldata title, // 比 memory 便宜
string calldata description,
uint256 endTime
) external { /* ... */ }
// 优化 2: 批量操作减少固定开销
function batchCreateEvents(
string[] calldata titles,
uint256[] calldata endTimes
) external {
uint256 length = titles.length;
for (uint256 i = 0; i < length; ) {
_createEvent(titles[i], endTimes[i]);
unchecked { ++i; } // 节省 Gas
}
}
// 优化 3: 位运算替代数学运算
// 优化前: price / 2
// 优化后: price >> 1
// 优化 4: 短路求值
require(
block.timestamp < endTime && isOperator(msg.sender),
"Invalid"
);8. 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Gas 过高 | 合约逻辑复杂 | 优化存储、批处理 |
| 预言机延迟 | 节点网络问题 | 多节点+超时机制 |
| 合约漏洞 | AI 生成的代码不安全 | 必须人工审核 |
| 升级困难 | 没有代理模式 | 使用 TransparentProxy |
| 权限混乱 | 角色定义不清 | 使用 AccessControl |
| 重入攻击 | 未防护 | 使用 ReentrancyGuard |
8. 下一步
完成智能合约后,进入 阶段 5:撮合引擎
7.4 FeeVault 合约实现
FeeVault 是预测市场经济模型的核心,负责手续费的收取和分配:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title FeeVault - 手续费金库
/// @notice 管理交易手续费的收取和分配
contract FeeVault is AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
IERC20 public immutable token;
uint256 public totalFees;
// 分配比例(基点,10000 = 100%)
uint256 public platformShare = 7000; // 70% 平台
uint256 public makerShare = 3000; // 30% 做市商
address public platformWallet;
mapping(address => uint256) public makerFees;
event FeeCollected(address indexed orderBook, uint256 amount);
event FeeDistributed(address indexed maker, uint256 amount);
event SharesUpdated(uint256 platformShare, uint256 makerShare);
constructor(address _token, address _platformWallet) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
token = IERC20(_token);
platformWallet = _platformWallet;
}
/// @notice 收取手续费
/// @param orderBook 订单簿合约地址
/// @param maker 做市商地址
/// @param fee 手续费金额
function collectFee(
address orderBook,
address maker,
uint256 fee
) external onlyRole(OPERATOR_ROLE) {
// 从订单簿转入手续费
IERC20(token).safeTransferFrom(orderBook, address(this), fee);
// 记录做市商手续费
uint256 makerFee = (fee * makerShare) / 10000;
makerFees[maker] += makerFee;
totalFees += fee;
emit FeeCollected(orderBook, fee);
}
/// @notice 做市商提取手续费
function withdrawMakerFees() external nonReentrant {
uint256 amount = makerFees[msg.sender];
require(amount > 0, "No fees to withdraw");
makerFees[msg.sender] = 0;
token.safeTransfer(msg.sender, amount);
emit FeeDistributed(msg.sender, amount);
}
/// @notice 平台提取手续费
function withdrawPlatformFees() external nonReentrant {
uint256 platformFees = (totalFees * platformShare) / 10000;
require(platformFees > 0, "No fees to withdraw");
totalFees = 0;
token.safeTransfer(platformWallet, platformFees);
}
/// @notice 更新分配比例
function updateShares(uint256 _platformShare, uint256 _makerShare)
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
require(_platformShare + _makerShare == 10000, "Invalid shares");
platformShare = _platformShare;
makerShare = _makerShare;
emit SharesUpdated(_platformShare, _makerShare);
}
}FeeVault 经济模型分析:
| 交易金额 | 手续费(0.5%) | 平台收入(70%) | 做市商收入(30%) |
|---|---|---|---|
| $100 | $0.50 | $0.35 | $0.15 |
| $1,000 | $5.00 | $3.50 | $1.50 |
| $10,000 | $50.00 | $35.00 | $15.00 |
| $100,000 | $500.00 | $350.00 | $150.00 |
7.5 FundingFactory 合约实现
FundingFactory 管理市场资金池,负责资金的存入、提取和结算分配:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title FundingFactory - 资金池工厂
/// @notice 管理预测市场资金池
contract FundingFactory is AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
IERC20 public immutable token;
struct FundingPool {
address eventAddress;
uint256 totalDeposited;
uint256 totalSettled;
bool settled;
bool outcome; // true = YES, false = NO
}
mapping(uint256 => FundingPool) public pools;
mapping(uint256 => mapping(address => uint256)) public deposits;
mapping(uint256 => mapping(address => bool)) public claimed;
event Deposited(uint256 indexed eventId, address indexed user, uint256 amount);
event Settled(uint256 indexed eventId, bool outcome);
event Claimed(uint256 indexed eventId, address indexed user, uint256 amount);
constructor(address _token) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
token = IERC20(_token);
}
/// @notice 用户存入资金
function deposit(uint256 eventId, uint256 amount) external {
require(!pools[eventId].settled, "Already settled");
token.safeTransferFrom(msg.sender, address(this), amount);
deposits[eventId][msg.sender] += amount;
pools[eventId].totalDeposited += amount;
emit Deposited(eventId, msg.sender, amount);
}
/// @notice 结算资金池
function settle(uint256 eventId, bool outcome)
external
onlyRole(OPERATOR_ROLE)
{
require(!pools[eventId].settled, "Already settled");
pools[eventId].settled = true;
pools[eventId].outcome = outcome;
emit Settled(eventId, outcome);
}
/// @notice 用户提取收益
function claim(uint256 eventId) external nonReentrant {
require(pools[eventId].settled, "Not settled");
require(!claimed[eventId][msg.sender], "Already claimed");
uint256 deposit = deposits[eventId][msg.sender];
require(deposit > 0, "No deposit");
// 计算收益:赢家通吃
uint256 payout = calculatePayout(eventId, msg.sender, deposit);
claimed[eventId][msg.sender] = true;
pools[eventId].totalSettled += payout;
token.safeTransfer(msg.sender, payout);
emit Claimed(eventId, msg.sender, payout);
}
function calculatePayout(
uint256 eventId,
address user,
uint256 deposit
) internal view returns (uint256) {
FundingPool storage pool = pools[eventId];
// 简化的收益计算:赢家按比例分配总池
// 实际实现需要根据 YES/NO 持仓比例计算
return deposit * 2; // 简化:假设赢家获得双倍
}
}7.6 合约安全审计 Checklist
在部署主网前,必须完成以下安全审计清单:
| 检查项 | 检查方法 | 通过标准 | 工具 |
|---|---|---|---|
| 重入攻击 | Slither + 人工审查 | 无 reentrancy 调用 | Slither |
| 整数溢出 | 编译器检查 + 测试 | Solidity 0.8+ 内置 | Hardhat |
| 访问控制 | 角色矩阵审查 | 所有敏感函数有权限控制 | 人工 |
| 前端运行 | 交易模拟 | 使用私有交易池 | Flashbots |
| 闪电贷 | 攻击模拟 | TWAP + 区块延迟 | Foundry fork |
| 预言机操纵 | 多节点验证 | 至少 3 个独立节点 | 自定义 |
| 存储布局 | forge inspect | 升级兼容 | OpenZeppelin |
| Gas 优化 | Gas 报告 | 每笔交易 <$0.10 | Hardhat Gas Reporter |
AI 辅助审计 Prompt:
请作为智能合约安全审计专家,审计以下合约代码。重点关注:
1. 资金安全:是否有资金被盗风险
2. 权限控制:是否有未授权操作风险
3. 经济攻击:闪电贷、三明治攻击等
4. 预言机风险:价格操纵、延迟等
5. 升级安全:存储布局、初始化等
请按严重程度(Critical/High/Medium/Low/Informational)分类列出所有问题。7.7 合约事件设计最佳实践
智能合约事件是链上数据监听的基础。良好的事件设计能简化数据管道的实现:
// 良好的事件设计
contract OrderBook {
// 事件包含足够信息,减少外部查询
event OrderPlaced(
uint256 indexed eventId,
address indexed trader,
bool isBuy,
bool isYes,
uint256 amount,
uint256 price,
uint256 timestamp
);
// 使用 indexed 参数优化日志过滤
event TradeExecuted(
uint256 indexed eventId,
address indexed buyer,
address indexed seller,
uint256 amount,
uint256 price,
uint256 fee
);
// 结算事件包含最终结果
event MarketSettled(
uint256 indexed eventId,
bool outcome,
uint256 totalPayout,
uint256 timestamp
);
}事件设计原则:
| 原则 | 说明 | 示例 |
|---|---|---|
| 使用 indexed | 关键字段索引化 | eventId, trader |
| 包含完整信息 | 减少外部查询 | price, amount, fee |
| 命名清晰 | 动词+名词格式 | OrderPlaced, TradeExecuted |
| 时间戳 | 链上时间记录 | block.timestamp |
| 索引数量 | 最多 3 个 indexed | eventId, buyer, seller |
Gas 优化提示:indexed 参数比非 indexed 更便宜,但日志数据不能直接在链上访问。
参考与延伸
[11] Solidity Events Best Practices(2025)— Solidity 事件最佳实践
[12] Ethereum Yellow Paper(2014)— 以太坊黄皮书,理解 EVM 和日志机制
[1] OpenZeppelin Contracts(2025)— 安全合约库
[2] Hardhat Documentation(2025)— 合约开发框架
[3] Solidity Documentation(2025)— Solidity 官方文档
[4] Slither(2025)— 合约安全分析工具
[5] UMA(2025)— 乐观预言机协议
[6] Consensys Smart Contract Best Practices(2025)— 合约安全最佳实践
[7] Trail of Bits Security Reviews(2025)— 安全审计报告
[8] EIP-1967: Proxy Storage Slots(2019)— 代理合约标准
[9] OpenZeppelin Upgrades(2025)— 合约升级工具
[10] Aztec Protocol(2025)— zk 验证技术参考