> For the complete documentation index, see [llms.txt](https://igra-labs.gitbook.io/igralabs-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://igra-labs.gitbook.io/igralabs-docs/for-attesters/challenger-guide.md).

# Challenger Guide

Challengers monitor attesters and submit on-chain proofs of misbehavior to earn rewards. Anyone can act as a challenger — the role is permissionless. You need a full Igra node and an Igra wallet with iKAS for gas.

All challenger functions are on the **Attestation Diamond** ([`0xc24Df70E408739aeF6bF594fd41db4632dF49188`](https://explorer.igralabs.com/address/0xc24Df70E408739aeF6bF594fd41db4632dF49188)).

> **Note:** All economic parameters (ratios, cooldowns, minimum stake) are configurable by DAO governance vote. The values listed in this guide reflect the current on-chain configuration. Challenger rewards are paid in IGRA tokens and are deducted directly from the penalized attester's stake — there is no separate reward pool.

## Overview

Challengers detect two categories of offenses:

| Category                                               | Severity                         | Reward                | Function                                       |
| ------------------------------------------------------ | -------------------------------- | --------------------- | ---------------------------------------------- |
| **Slashing** — provable attack-enabling wrongdoing     | 1/16 of stake                    | 50% of slash amount   | `slash()`                                      |
| **Penalty** — operational failures or ambiguous faults | \~1/11,520 or \~1/4,860 of stake | 50% of penalty amount | `penalizeMissed()` / `penalizeInvalidWindow()` |

## Attestation Window Stages

Every attestation has a two-stage lifecycle measured in Igra blocks from the attested block:

```
Block N        N+256              N+512           N+8191
  |  Stage 1    |    Stage 2       |                |
  | (submit)    | (submit+challenge)|               |
  |             |  penalties here   |               |
  |             |  invalid-hash slash here --------->|
  |             |  double-sign slash: no expiry      |
```

* **Stage 1 (blocks 1–256)**: Attesters submit attestations. No challenges allowed.
* **Stage 2 (blocks 257–512)**: Penalties can be submitted. Attesters may still submit.
* **Slashing window (blocks 257–8191)**: Invalid block hash slashes can be submitted.
* **Double-sign slashing (blocks 257+)**: No upper bound — contradicting attestations can be slashed forever.

## The Challenge Cases

### Case 1: Invalid Block Hash (Slash)

An attester signed an attestation with an incorrect `l2BlockHash` for a valid window.

```solidity
function slash(
    AttestData[] calldata data,      // length 1: the invalid attestation
    bytes[] calldata signatures,     // the attester's signature
    DelegationAuth calldata auth     // zeroed if non-delegated
) external;
```

**Requirements:**

* Block must be in slashing window (257–8191 blocks old)
* Window must be valid (ISC recognized by the L2Oracle contract)
* Block hash must be incorrect (differs from on-chain `blockhash()`)

**How to detect:** Compare the `l2BlockHash` in the attestation calldata against the actual block hash from your full node.

### Case 2: Contradicting Attestations (Slash)

Same attester signed two different attestations for the same window (same `nextWindowIsc`, different data).

```solidity
function slash(
    AttestData[] calldata data,      // length 2: both attestations
    bytes[] calldata signatures,     // both signatures (same signer)
    DelegationAuth calldata auth     // zeroed if non-delegated
) external;
```

**Requirements:**

* Block must be past Stage 1 (257+ blocks old, no upper bound)
* Both signatures must recover to the same signer
* Both must reference the same `nextWindowIsc`
* The attestation data must differ

**How to detect:** Index all attestation calldata by `(attester, nextWindowIsc)`. If you see two different `AttestData` values for the same key, you have a contradicting attestation.

### Case 3 & 4: Invalid / Reorged Window (Penalty)

An attester submitted an attestation referencing a window that doesn't exist or was removed by an L1 reorg.

```solidity
function penalizeInvalidWindow(
    AttestData calldata data,        // the attestation with invalid ISC
    bytes calldata signature,        // the attester's (or operator's) signature
    DelegationAuth calldata auth     // zeroed if non-delegated
) external;
```

**Requirements:**

* Block must be in penalty window (257–512 blocks old)
* ISC must be invalid (not recognized by the L2Oracle contract)
* Each attestation can only be penalized once (no frequency limit per attester)

> **Note:** Unlike `penalizeMissed()`, this function does not require the attester to be in Active state — any registered attester (including those in PendingActivation or PendingExit) can be penalized for invalid window attestations.

**How to detect:** Check if the `nextWindowIsc` in the attestation calldata is recognized by the L2Oracle contract. If `isValidIsc()` returns false, the attestation references a non-existent or reorged window.

### Case 5 & 6: Missed Attestation (Penalty)

An attester was selected for a window but did not submit an attestation.

```solidity
function penalizeMissed(
    address attester,                // the attester who missed
    uint32 l2BlockNumber,            // the block they should have attested
    uint64 nextWindowDaaScore,       // DAA score of the next L1 window
    uint32 daaScoreDelta             // DAA score delta between windows
) external;
```

**Requirements:**

* Attester must be active
* Block must be in penalty window (257–512 blocks old)
* Attester must have been selected for the window (checked via `isSelected()`)
* Attester must not have attested (checked via bitmap)
* Rate-limited: one penalty per attester per cooldown period (\~2,700 blocks)

> **Note:** The caller must supply `nextWindowDaaScore` and `daaScoreDelta` — these values are not available from on-chain state alone. You must derive them from L1/Igra block window data via your full node.

**How to detect:**

1. For each window start block, compute which attesters were selected using `isSelected(attester, referenceIsc)`
2. After Stage 1 ends (256 blocks), first verify the block is in tracking range with `isBlockInRange(attester, blockNumber)`, then call `assertAttestationMissing(attester, blockNumber)` to check if they attested
3. If selected but missing, submit `penalizeMissed`

> **Warning:** `assertAttestationMissing()` returns `false` for blocks that have rotated out of the attester's bitmap tracking range. Always call `isBlockInRange()` first to confirm the block is trackable — otherwise you may get a false negative.

## Handling Delegated Attestations

Some attesters use **delegated attestation** — a separate operator submits transactions on behalf of the staked controller. The on-chain calldata uses `attestDelegated()` (selector `0xd5b1840d`) instead of `attest()` (selector `0xc84c8ea3`).

When challenging a delegated attestation, you must pass the `DelegationAuth` from the original attestation transaction calldata. For non-delegated attestations, pass a zeroed `DelegationAuth`:

```solidity
// Non-delegated: zero auth
DelegationAuth({ controller: address(0), expiry: 0, signature: "" })

// Delegated: extract from the original attestDelegated() calldata
DelegationAuth({ controller: <from calldata>, expiry: <from calldata>, signature: <from calldata> })
```

The `DelegationAuth` parameter applies to `slash()` and `penalizeInvalidWindow()`. It is not needed for `penalizeMissed()` since that function identifies the attester by address directly.

> **Note:** When slashing delegated attestations, the delegation expiry is NOT checked — the authorization was valid at the time of the original attestation, and the proof remains valid regardless.

## View Functions

Use these to query attester state before submitting challenges:

```bash
# Check if an attester missed a specific block
cast call 0xc24Df70E408739aeF6bF594fd41db4632dF49188 \
  'assertAttestationMissing(address,uint32)(bool)' $ATTESTER $BLOCK \
  --rpc-url https://rpc.igralabs.com:8545

# Check if a block is within trackable range for an attester
cast call 0xc24Df70E408739aeF6bF594fd41db4632dF49188 \
  'isBlockInRange(address,uint32)(bool)' $ATTESTER $BLOCK \
  --rpc-url https://rpc.igralabs.com:8545

# Get the bitmap tracking range for an attester
cast call 0xc24Df70E408739aeF6bF594fd41db4632dF49188 \
  'getAttesterTrackingRange(address)(uint32,uint32)' $ATTESTER \
  --rpc-url https://rpc.igralabs.com:8545

# Check if attester is selected for a window
cast call 0xc24Df70E408739aeF6bF594fd41db4632dF49188 \
  'isSelected(address,bytes32)(bool)' $ATTESTER $REFERENCE_ISC \
  --rpc-url https://rpc.igralabs.com:8545
```

## Monitoring Attestations

Subscribe to `AttestationRecorded` events on the Diamond to track all attestations in real time:

```
event AttestationRecorded(address indexed attester, AttestData data)
```

**Event topic:** `0x05de9fb2ca9ff42584f4cc8d6e699148ff2514cd0b1ee690f66e7744584a7553`

To decode attestation calldata from L1 transactions, handle both selectors:

| Selector     | Function                                           | Signer                                     |
| ------------ | -------------------------------------------------- | ------------------------------------------ |
| `0xc84c8ea3` | `attest(AttestData,bytes)`                         | Attester signs directly                    |
| `0xd5b1840d` | `attestDelegated(AttestData,bytes,DelegationAuth)` | Operator signs; controller in `auth` field |

## Live Mainnet Configuration

Current parameters (queried from the Config facet). All values are configurable by DAO governance vote.

| Parameter                                | Value                      |
| ---------------------------------------- | -------------------------- |
| Min stake                                | 400,000 IGRA               |
| Penalty cooldown                         | 2,700 blocks (\~45 min)    |
| Slash ratio                              | 1/16 of original stake     |
| Slash challenger reward                  | 50% of slash amount        |
| Missed penalty ratio                     | 1/11,520 of original stake |
| Missed penalty challenger reward         | 50% of penalty amount      |
| Invalid window penalty ratio             | 1/4,860 of original stake  |
| Invalid window penalty challenger reward | 50% of penalty amount      |

## Reward Calculation

Challenger rewards are deducted directly from the penalized attester's staked IGRA. The remainder of the slash or penalty amount stays in the contract as part of the reward bonus pool for attesters.

For a slash:

```
slashAmount = stakedAmount / 16
challengerReward = slashAmount / 2
```

For a missed attestation penalty:

```
penaltyAmount = stakedAmount / 11520
challengerReward = penaltyAmount / 2
```

For an invalid window penalty:

```
penaltyAmount = stakedAmount / 4860
challengerReward = penaltyAmount / 2
```

All amounts are capped at the attester's effective stake (original stake minus cumulative penalties and slashes). Rewards are paid in IGRA tokens.

## Events

Listen for these events to track challenge activity:

```solidity
event Slashed(
    address indexed attester,
    address indexed challenger,
    uint256 slashAmount,
    uint256 challengerReward,
    SlashReason reason           // 0 = InvalidBlockHash, 1 = ContradictingAttestations
);

event Penalized(
    address indexed attester,
    address indexed challenger,
    uint256 penaltyAmount,
    uint256 challengerReward,
    PenaltyReason reason         // 0 = MissedAttestation, 1 = InvalidWindow
);
```

## Error Reference

### Challenger Errors

| Error                                      | Meaning                                                       |
| ------------------------------------------ | ------------------------------------------------------------- |
| `AttestationNotStarted()`                  | Validation period hasn't begun                                |
| `LengthMismatch()`                         | `data` and `signatures` arrays have different lengths         |
| `InvalidProof()`                           | Proof array must have 1 or 2 elements                         |
| `AttesterNotRegistered()`                  | Target is not a registered attester                           |
| `AttesterNotActive()`                      | Target is not in Active state (for `penalizeMissed`)          |
| `AttestationsFromDifferentAttesters()`     | Double-sign proof has signatures from different signers       |
| `PenaltyRateLimitExceeded()`               | Penalty cooldown hasn't elapsed for this attester             |
| `InvalidWindowIsc()`                       | Window ISC verification failed during `penalizeMissed`        |
| `InvalidBlockForNextIsc()`                 | `l2BlockNumber` doesn't match the expected window start block |
| `AttesterNotSelected()`                    | Attester wasn't selected for this window                      |
| `AttesterAlreadyAttested()`                | Attester already attested this block                          |
| `AttestationAlreadyPenalized()`            | This attestation was already penalized                        |
| `WindowIsValid()`                          | Can't penalize — the window ISC is valid                      |
| `NoStakeToSlash()` / `NoStakeToPenalize()` | Attester has no remaining effective stake                     |
| `BlockOutOfRange(block, min, max)`         | Block is outside the attester's bitmap tracking range         |
| `InvalidDelegationAuth()`                  | Delegation authorization signature is invalid                 |

### Proof Validation Errors

| Error                         | Meaning                                                                                         |
| ----------------------------- | ----------------------------------------------------------------------------------------------- |
| `ProofAlreadyUsed(proofHash)` | This slash proof was already submitted                                                          |
| `WindowIsNotValid()`          | Can't slash for invalid hash — the window ISC is not recognized (attester may have been honest) |
| `BlockHashIsCorrect()`        | Can't slash — the block hash is actually correct                                                |
| `DifferentWindows()`          | Double-sign proof references different windows                                                  |
| `SameAttestationData()`       | Double-sign proof has identical attestation data                                                |

### Timing Errors

| Error                        | Meaning                                         |
| ---------------------------- | ----------------------------------------------- |
| `BlockInFuture()`            | Block number is in the future                   |
| `AttestationWindowExpired()` | Attestation window (512 blocks) has passed      |
| `PenaltyWindowNotOpen()`     | Still in Stage 1 — penalty window not yet open  |
| `PenaltyWindowExpired()`     | Penalty window (512 blocks) has passed          |
| `SlashingWindowNotOpen()`    | Still in Stage 1 — slashing window not yet open |
| `SlashingWindowExpired()`    | Slashing window (8191 blocks) has passed        |
