# Fraud-Proof 欺诈证明

## 设计目标

* **不需要每笔 tx 都立即验证**：减少 gas 成本
* **任何人都可以反证**：去中心化（permissionless）
* **作弊有经济代价**：50% slash share + 7 天 window
* **诚实 aggregator 几乎零成本**：只承担 stake 锁仓机会成本

## 状态机

```
                          ┌──────────────┐
                          │ open()       │ challenger 付 BOND_AMOUNT (0.1 ETH)
                          │              │ 提交 Merkle proof + fraud claim
                          ▼              │
            ┌──────────────────────────┐ │
            │   Status.Pending         │ │
            └────┬──────────────┬──────┘ │
                 │              │        │
       respond() │              │ 7 days passed
       aggregator│              │
       提交反证  │              │
                 ▼              ▼
   ┌─────────────────────┐  ┌─────────────────────────────┐
   │ Status.ResolvedNoFr │  │ Status.ResolvedFraud        │
   │ (challenger 作弊)   │  │ (aggregator 作弊)           │
   │ challenger bond → treasury │  aggregator full slash     │
   │                     │  │  → StakeManager.treasury     │
   └──────────┬──────────┘  └──────────────┬──────────────┘
              │                            │
        claim() │                            │ claim()
              ▼                            ▼
       ┌─────────────────┐         ┌──────────────────────────┐
       │ Status.Claimed  │         │ Status.Claimed           │
       │ treasury 收 bond │         │ challenger: bond 退回     │
       │                 │         │  + 50% slashed reward    │
       └─────────────────┘         └──────────────────────────┘
```

## ChallengeManager 完整接口

```solidity
interface IChallengeManager {
    function open(
        uint256 batchId,
        uint32 leafIndex,
        BehaviorEventView calldata event_,
        bytes32[] calldata merkleProof
    ) external payable;
    
    function respond(
        uint256 challengeId,
        BehaviorEventView calldata event_,
        bytes32[] calldata merkleProof
    ) external;
    
    function resolveExpired(uint256 challengeId) external;
    
    function claim(uint256 challengeId) external;
    
    function getChallenge(uint256 challengeId) external view returns (Challenge memory);
    function isPending(uint256 batchId, uint32 leafIndex) external view returns (bool);
}
```

## 关键参数

```solidity
// ChallengeManager.sol
uint256 public constant CHALLENGE_WINDOW = 7 days;
uint256 public constant BOND_AMOUNT = 0.1 ether;
uint256 public constant CHALLENGER_REWARD_BPS = 5000; // 50%
uint256 public constant BPS_DENOM = 10000;
```

## 8 类可检测的 Fraud

baby-pob-challenger 的 `FraudDetector` trait 当前实现 8 种检测：

| # | Fraud type                                                     | 检测方法                          |
| - | -------------------------------------------------------------- | ----------------------------- |
| 1 | header.agentId ≠ leaf.agentId (header lying about which agent) | 抽样 leaf 反查                    |
| 2 | leafCount 与实际 leaf 数不符                                         | 重算 merkle root 不一致            |
| 3 | merkleRoot 不可重建                                                | 重算不一致                         |
| 4 | timestamp 早于上一个 batch                                          | 单调性检查                         |
| 5 | spec\_hash 引用不存在                                               | BehaviorSpecRegistry.exists 查 |
| 6 | aggregator 已被 deactivate 但仍 submitBatch                        | StakeManager.activeStake 查    |
| 7 | event 引用未注册的 spec                                              | BehaviorSpecRegistry 查        |
| 8 | event 的 outcome 与 BehaviorSpec evaluation 不一致                  | OPA Rego 重评估                  |

实际 chain 391 真链 e2e 用了 #1。

## Rust Challenger Daemon 架构

```rust
// baby-pob-challenger/src/daemon.rs
pub struct ChallengerDaemon<W, D, O>
where
    W: BatchWatcher,
    D: FraudDetector,
    O: ChallengeOpener,
{
    watcher: W,
    detector: D,
    opener: O,
    cursor: AtomicU64,  // 已处理的 batchId 上限
    poll_interval: Duration,
}

impl<W, D, O> ChallengerDaemon<W, D, O> {
    pub async fn tick(&self) -> DaemonReport {
        let batch_count = self.watcher.get_batch_count().await?;
        let mut report = DaemonReport::default();
        
        let from = self.cursor.load(SeqCst);
        for id in from..batch_count {
            report.batches_seen += 1;
            let batch = self.watcher.get_batch(id).await?;
            
            match self.detector.detect_fraud(id, &batch).await? {
                DetectionResult::NoFraud => {},
                DetectionResult::Fraud(claim) => {
                    self.opener.open_challenge(claim).await?;
                    report.fraud_detected += 1;
                    report.challenges_opened += 1;
                }
            }
            self.cursor.store(id + 1, SeqCst);
        }
        report
    }
    
    pub async fn run_until_shutdown(&self, shutdown_signal: impl Future) {
        loop {
            tokio::select! {
                _ = shutdown_signal => break,
                _ = tokio::time::sleep(self.poll_interval) => {
                    let _ = self.tick().await;
                }
            }
        }
    }
}
```

详见 [B4-Phase2 Challenger Daemon devlog](https://github.com/leeleeEcho/babyDriver_Layer2/blob/main/docs/devlog/2026-Q2/2026-04-30-DAY-SUMMARY.md#6-b4-phase2-challenger-rust-daemon-commit-1f1e29d)。

## 真链 e2e 跑通记录 (commit 023f7aa)

5 笔关键 tx：

```bash
# 1. aggregator deposit 0.5 ETH stake
cast send $STAKE_MANAGER "deposit()" --value 0.5ether \
    --private-key $AGGREGATOR_KEY

# 2. submit fraud batch (header.agentId = 0x97f8..., leaf.agentId = 0xd983...)
cast send $POB_REGISTRY "submitBatch((bytes32,...))..." \
    --private-key $AGGREGATOR_KEY

# 3. challenger open challenge with Merkle proof + 0.1 ETH bond
cast send $CHALLENGE_MANAGER "open(uint256,uint32,...)" \
    0 0 "(0xd983...,...)" "[0x...,0x...]" \
    --value 0.1ether --private-key $CHALLENGER_KEY

# 4. evm_increaseTime 7 days + 1s (anvil-zksync test 用)
cast rpc evm_increaseTime 604801

# 5. resolveExpired → 真 slash 0.5 ETH
cast send $CHALLENGE_MANAGER "resolveExpired(uint256)" 1 \
    --private-key $ANYONE_KEY

# 6. challenger claim → bond return + reward
cast send $CHALLENGE_MANAGER "claim(uint256)" 1 \
    --private-key $CHALLENGER_KEY
```

完整 deployment JSON: `deployments/b4p2-fraud-proof-e2e-chain-391.json` (gitignored)。

## 已修真 bug — CEI Violation

**slither 发现**：`resolveExpired` 中 `c.status = ResolvedFraud` 写在 `stakeManager.slash()` 外部调用之**后**，违反 Checks-Effects-Interactions。

**修复** (commit `7a1c8b5`)：

```solidity
// Before (vulnerable):
stakeManager.slash(c.accusedAggregator, active);  // Interaction
slashed = active;
c.status = Status.ResolvedFraud;                  // Effects 太晚

// After (CEI compliant):
uint256 slashed = active;                          // Checks
c.status = Status.ResolvedFraud;                  // Effects
c.resolvedAt = uint64(block.timestamp);
_slashedAmount[challengeId] = slashed;
emit ChallengeResolved(...);

if (active > 0) {
    stakeManager.slash(c.accusedAggregator, active);  // Interaction 最后
}
```

`forge test --match-contract ChallengeManager` 31 tests 全过。详见 [审计预审报告](https://github.com/leeleeEcho/babyDriver_Layer2/blob/main/docs/audit/pre-audit-self-review-2026-05-07.md)。

## Invariant Suite (16 properties × 256 fuzz runs)

```solidity
// test/Challenge.invariant.t.sol
contract ChallengeInvariantTest is StdInvariant, Test {
    // I1: 任何 challenge 的 bond 必须 ≥ 0.1 ETH
    function invariant_bondNeverBelowMin() public { ... }
    
    // I2: 任何 ResolvedFraud 必须有 _slashedAmount > 0 当 active stake > 0
    function invariant_slashConsistency() public { ... }
    
    // I3: 任何 status=Claimed 必须 _claimed[id] = true
    function invariant_claimedFlag() public { ... }
    
    // I4-I16: ...
}
```

完整 16 个 invariant 跑过 256 round fuzz 全通过。

## 相关

* [Slash 经济学](/algorithms/slash-economics.md) — 经济参数校准
* [Positional Merkle Tree](/algorithms/merkle-tree-positional.md) — 反证算法
* [Examples → Open Challenge](/integration-jie-ru-fang-shi/examples/open-challenge.md) — 实操示例
* [B4-Phase2 W1-W4 devlog](https://github.com/leeleeEcho/babyDriver_Layer2/tree/main/docs/devlog/2026-Q2) — 4 周开发历程


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yellowpaper.axblade.io/protocol-mechanics/fraud-proof-mechanism.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
