世界杯倍数预测跨事件票据内部复核
日期: 2026-06-08 审计方: 内部 Codex 辅助安全复核 范围: 跨事件 Prediction Ticket 重构,覆盖后端/indexer、前端、smoke 工具、Portfolio/Admin 展示,以及 StrikeMultiplierPredictionVault 的 ticket-as-vault-event 兼容性 结论: 本次复核范围内通过。 之前的发布 blocker 已修复,新的独立复核未发现剩余 release-blocking 问题。
执行摘要
这是一份内部 Codex 辅助复核,不是外部第三方审计。
本分支加入真正的跨事件 Prediction Ticket:一个用户票据可以包含来自多个预测事件的 legs,同时通过把每张票据表示为一个 synthetic vault event 来复用现有 StrikeMultiplierPredictionVault ABI。
上一轮复核因后端/accounting projection、synthetic vault lifecycle 安全性、前端确定性幂等 key、intent-only ticket 处理、smoke 金额单位、以及数字 ticket 隐私而阻塞发布。这些问题已修复并重新复核。本次结果为:PASS。
复核范围
复核覆盖 feature/world-cup-cross-event-tickets 分支上的改动:
/home/ubuntu/dev/strike-inframigration
047_world_cup_multiplier_cross_event_tickets.sql/v1/world-cup-multiplier/tickets创建/列表/detail APIlegacy
multiplier_predictionsprojection 与 accounting 兼容ticket settlement projection 与 vault event 同步
本次重构新增的 DB-backed 回归测试
/home/ubuntu/dev/strike-frontend跨事件 ticket builder 与提交路径
ticket API client 类型
Portfolio/Admin 展示
ticket-builder tests 与 smoke 脚本默认值
/home/ubuntu/dev/strikeStrikeMultiplierPredictionVault跨事件 ticket 兼容性测试docs/security/protocol 引用
分模块结论
合约兼容性:PASS
现有 vault ABI 可以把一张跨事件 ticket 表示为一个 synthetic vault event:
submit 使用一个
bytes32 eventId表示 synthetic ticket vault event;ticket 使用一个
bytes32 predictionId;synthetic vault event 结算后,可以用该 ticket prediction id 领取 payout;
synthetic vault event 取消后,可以用该 prediction id 领取 refund;
vault 不需要知道真实 per-leg event ids。
Focused vault tests 与完整 Foundry suite 均通过。
后端/indexer:PASS
后端现在会为本次复核覆盖的所有 ticket 路径创建并更新可靠的 legacy projection:
纯
/world-cup-multiplier/tickets提交会使用第一条 leg 的 event 作为 projection event,upsertmultiplier_predictionsprojection;legacy
/events/{id}/predictions兼容路径仍通过请求中的 event 做 projection;idempotent retry 会更新 projection,而不是静默跳过;
projection receipt snapshot 包含 ticket id、projection event id 与 ticket legs;
event-level accounting 可以继续读取
multiplier_predictions,同时 ticket tables 仍是 ticket/leg 的 canonical source。
Synthetic vault lifecycle 与 claim safety:PASS
实现现在会在 synthetic vault event lifecycle 确认链上结果前,保持本地 ticket 的 public status 为 claim-safe:
本地 per-leg settlement 会记录推导出的 ticket outcome;
confirmed tickets 公开
ticket_status保持accepted,同时在metadata.vaultSettlementPending.localTicketStatus记录本地终态;legacy projections 会同步本地终态,让 event-level accounting 可以更新;
vault event logs 仍可通过
contract_prediction_id或vault_event_id更新 ticket 与 projection status;这样可以避免在 vault event 实际 settle/cancel 前,把 ticket 展示成可 claim/refund。
前端幂等性:PASS
前端不再根据 wallet、entry amount 与 legs 派生 ticket idempotency key。
每次 submit attempt 都生成 nonce-based key;
该 key 只在同一次 in-flight attempt 中复用;
finally中清除 key,因此用户有意重复提交相同 ticket 时会拿到新 key;focused Playwright/unit coverage 验证了重复 ticket 的新 key 与 in-flight retry 复用。
Intent-only tickets:PASS
Intent-only 或未确认资金的 tickets 不会被转换成 paid entitlement。
settlement recomputation 会检查 funding state;
non-confirmed tickets 在 terminal/refund handling 触及时可以本地取消;
cancellation 会记录
localSettlementResultmetadata,并通过funding_status <> confirmedguard 保证安全。
Smoke 工具与隐私:PASS
跨事件 smoke 脚本默认使用明确的 1 USDT base units:
1000000。数字 ticket detail access 现在要求
walletquery parameter。Ticket detail loading 会按
lower(wallet)过滤,wallet 不匹配时返回 not found。Ticket listing 仍保持 wallet-scoped。
之前 blocking findings 的修复状态
B-01:跨事件 tickets 没有进入 legacy settlement/accounting projection
状态: 已修复。
所有已复核的 ticket creation/idempotent 路径都会调用 upsert_legacy_prediction_projection_tx。Projection 使用确定的 projection event,冲突时更新,并带有识别 ticket-derived projection 所需的 metadata。
B-02:Synthetic vault event settlement 未完整接入 claim-safe ticket lifecycle
状态: 本次兼容模型范围内已修复。
后端现在把本地 per-leg settlement 与公开 claim/refund readiness 分离。Confirmed tickets 在 vault event 确认 settle/cancel 前保持 accepted,同时把本地终态记录到 metadata 并同步进 accounting projection。
B-03:确定性前端幂等 key 会合并重复相同 tickets
状态: 已修复。
Ticket submission idempotency keys 现在是每次 submit attempt 的 nonce-based key,仅在当前 in-flight attempt 中复用。
High-severity findings 的修复状态
H-01:Intent-only tickets 不会 settle 或 cancel
状态: 已修复到安全本地处理。
Unfunded/non-confirmed tickets 可以转换为 local cancelled state,不会创建 paid entitlement。Confirmed tickets 则保持 claim-safe,直到 vault 确认。
H-02:Smoke 脚本金额含义不明确
状态: 已修复。
Smoke 脚本默认值现在是明确 base units:ONE_USDT_BASE_UNITS = '1000000'。
H-03:Ticket detail endpoint 可通过数字 id 枚举
状态: 已修复。
Detail endpoint 现在要求 wallet query,并按 wallet 过滤加载 ticket。
剩余约束与非阻塞说明
这仍是内部 Codex 辅助复核,不是第三方审计。
Vault lifetime
MAX_TOTAL_PREDICTIONS = 1,000。已取消/已 finalized 的 predictions 不会释放 slots,因此高流量生产应使用 vault rotation 或 native redesign。合约不验证真实 per-leg event ids、leg outcomes 或 ticket composition。这些事实仍由后端/admin settlement 决定。
前端依赖新的
/v1/world-cup-multiplier/ticketsendpoint,因此后端与前端应原子部署。共享 multi-chain deployment 应在需要时确保 vault event idempotency 包含 chain/contract context。
新复核给出的非阻塞 hardening 建议:
为 wallet-scoped ticket detail access 增加显式 HTTP handler 回归测试;
如果产品策略要求所有 terminal leg 类型都本地取消 intent-only ticket,可再增加一个非 refund terminal intent-only 测试。
验证证据
本次复核完成的命令/checks:
Backend/indexer:
cargo fmt— passedcargo check -p indexer— passed,有既有 dead-code warningscargo test -p indexer world_cup_multiplier --lib --no-run— passedcargo test -p indexer cross_event_ticket --lib— DB-backed SQLx tests 因配置的测试数据库 hostname 无法解析而被环境阻塞;该 filter 中纯 validation tests 在 DB setup 失败前通过
Frontend:
npm run lint— passednpx tsc --noEmit— passednpm run build— passednpx playwright test tests/world-cup-multiplier-ticket-builder.spec.ts --config=/tmp/strike-frontend-playwright-no-webserver.config.cjs— 7 passed
Contracts:
/home/ubuntu/.foundry/bin/forge test --match-contract StrikeMultiplierPredictionVaultTest— 31 passed/home/ubuntu/.foundry/bin/forge test— 620 passed, 0 failed
独立复核:
对当前 cross-repo diffs 的 fresh audit 返回 PASS,没有 release-blocking findings。
Docs:
本页替换上一版 blocked candidate review,更新为当前 PASS review。
发布建议
本次复核范围内,跨事件 Prediction Ticket refactor 已通过对之前 blocker areas 的内部复核。
公开生产使用前,应一起部署后端与前端,验证 live ticket create/list/detail APIs,使用明确 base-unit amounts 对 live API 运行 cross-event smoke script,并确认 Portfolio/API claim/refund states 与 synthetic vault lifecycle 保持一致。
Last updated

