Skip to content

Latest commit

 

History

History
159 lines (124 loc) · 15.6 KB

task_240926_17.md

File metadata and controls

159 lines (124 loc) · 15.6 KB

Task: P3-6.2 Adversarial Suite — Threat-Model Doc + Multi-Actor Scenario Tests (#154) + Testnet-Parameter Readiness (#155)

ID: task_240926_17 Assigned to: Robbert Abimbola (@robertocarlous) Created: 2026-09-24 Status: Open (assigned) Dates: Sep 24 – Oct 1, 2026. This is the same 7-day window as task_240926_16: both tasks are due by Oct 1. Repo: https://github.com/InfiniteZeroFoundation/DevNet Base branch: develop — spec written against commit a62a508 (2026-09-24); re-pull before starting regardless of when you begin. Roadmap ref: P3-6.2 (Adversarial & Stress Testing), P3-5.1/5.2 (tokenomics parameters, MECHANISM_DESIGN §9 items 2, 13) Tracking:

  • #154 (closes, pending review). Scope agreed in the #154 review comment, which supersedes the issue's original scope list and acceptance criteria.
  • #155 (Part 3; closes once both the code below and the Fri Sep 25 meeting's values have landed). Scope agreed in the #155 review comment.
  • Discussion #164 (progress updates, questions, PR links). task_240926_16 tracks separately in Discussion #163.

Context

#154's blockers (#146 S1/S2/S5/S6 slashing, #149 S4 dispute second phase) both merged on 2026-09-11/12, so the adversarial suite is unblocked. The #154 review found three problems with the issue as written.

  • Several scenarios have no defined expected outcome today. A test without one ends up either marking a known gap as passing or failing by design.

    • Mis-framed auditor/aggregator scenario: the issue lists "Auditor colluding with aggregator to pass a bad T2 model: S1 slash path". That doesn't happen.
      • Auditors only judge client local models, via an eligibility vote and a score per model index. They never see T1/T2 output. Aggregators only consume the approved set (approvedModelIndexes, DINTaskCoordinator.sol:579).
      • S1 only catches missed votes. S3 (median deviation) is shadow-mode, with s3SlashingEnabled = false (DINTaskAuditor.sol:151).
      • The real cross-role surface is the approved-set handoff, split into three rows in Part 1.
    • Sybil subgroup capture: no defence exists beyond per-identity stake and the S6 capacity cap.
    • Owner + aggregator dispute suppression: resolveDispute/settleRecomputation are onlyOwner, so the owner is the adjudicator.
  • The dispute-lifecycle expectations in the issue are inverted. See §2 below.

  • Every liveness and dispute-lifecycle scenario the issue lists already has a unit test:

    • SlashingInvariants.t.sol (18 tests)
    • PR146SlashingRegression.t.sol (3 tests)
    • DisputeResolution.t.sol (27 tests)

    What's missing is multi-actor, multi-GI scenarios run through the real GI state machine.

So Parts 1–2 go in order: a short threat-model doc (reviewed first), then the scenario suite it defines. Part 3 (#155) is independent: small code changes that get the testnet tokenomics parameters ready to set, ahead of the values being decided at the Fri Sep 25 meeting.

Scope

Part 1 — Developer/design/adversarial-threat-model.md (review gate)

A few pages, not a mechanism design. It covers:

  • Scenario matrix: one row per scenario, with these columns:

    Field Meaning
    Attacker(s) Role(s) and collusion set
    Capability What they control: keys, stake, adjudication, timing
    Protocol response today Contract + function + reason code, or "none"
    Expected test outcome DEFENDED: the attack fails or is slashed, with the amount asserted. COST-BOUNDED: the attack succeeds; the test asserts the cost (stake at risk vs. gain). KNOWN GAP: the attack succeeds; the test asserts that and links a tracking issue.
    Trust assumption e.g. "owner is honest adjudicator (S4, pre-decentralization)", "sequencer doesn't grind blockhash"
  • Minimum rows. Add more if you find them:

    • Sybil operator, N identities, registering across consecutive GIs → S6 capacity cap / escalation
    • Sybil capture of k-of-n seats in an audit batch or T1 subgroup → COST-BOUNDED (stake required vs. seats captured)
    • Recidivist missing votes across GIs → S5 escalation to full slash + jail → excluded from the next GI's registration (isValidatorActive is false while jailed)
    • Auditor bloc approves a poisoned client model (auditor + client collusion; no aggregator needed, since honest aggregators fold in whatever approvedModelIndexes returns) → S3 shadow-mode AuditorScoreDeviation emitted, no slash → KNOWN GAP until S3 graduates
    • T1 aggregator majority submits a wrong CID (aggregators only; no auditor needed) → the wrong CID wins consensus, and the honest dissenter is slashed under AGG_T1_BAD_CONSENSUS. S4 dispute is the only remedy. The doc decides whether this is DEFENDED (via S4, under the owner-adjudicator assumption) or a GAP.
    • Cross-role: one operator holding both roles, plus owner batch-routing → nothing prevents one validator registering as both auditor and aggregator in the same GI (only the shared concurrent-registration cap applies), so one operator controls both the filter and the combiner.
      • Colluding aggregators can skip any robust-aggregation step in the model's aggregator.py (e.g. outlier clipping) that would otherwise drop an approved poisoned model. That part is model-dependent and happens off-chain. On-chain it looks identical to the wrong-CID row, since the contract only sees CIDs, so document it in the doc rather than trying to test it in Forge.
      • The model owner can route the poisoned model into the colluding T1 batch, because _shuffleUintArray is seeded from block.timestamp + msg.sender, which is the same randomness class as #156.
      • The doc should state whether dual-role registration in one GI is intended. If it isn't, it's a KNOWN GAP with a new issue.
    • Challenger right, owner rejects the dispute → KNOWN GAP under the owner-adjudicator assumption
    • Owner steering the dispute recomputation subgroup → DEFENDED once task_240926_16 Part A's future-block seed lands (KNOWN GAP before it)
    • Reward manipulation: auditor submits inflated scores → whether _medianOf bounds their effect on reward share (DINTaskAuditor reward settlement; see RewardEngine.t.sol)
    • Duplicate-update gaming → deferred: depends on BlockFLow fold-in scoring (#39), which isn't implemented. List it, don't test it.
  • Trust-assumptions section: every assumption the DEFENDED rows rely on, stated once.

  • Gaps-before-audit list: the KNOWN GAP rows, each with its tracking issue. Open a new issue where none exists and link it.

Review gate: open the doc as its own PR (or post a draft on #154) and get Umer's sign-off on the matrix, especially the expected-outcome column, before writing Part 2 tests against it. Aim to have it up within the first 2 days of the window.

Part 2 — foundry/test/P3Adversarial.t.sol

  • One test per matrix row, named by outcome class:
    • test_defended_<scenario>
    • test_costBounded_<scenario>
    • test_knownGap_<scenario>
  • Each test gets an attack-narrative NatSpec block: who the attacker is, what they do, what the protocol does, and which matrix row it implements.
  • Run scenarios through the real GI lifecycle (register → LMS → evaluation → aggregation → slash → end). Cross-GI attacks (S5/S6/Sybil) span ≥2 GIs.
  • Don't re-test single-function paths already covered by SlashingInvariants.t.sol, PR146SlashingRegression.t.sol, or DisputeResolution.t.sol. Reuse their setup helpers where you can, rather than duplicating fixtures.
  • KNOWN GAP tests assert the attack succeeds. When the gap closes they fail, and get flipped to test_defended_ in the fixing PR. Say so in each one's NatSpec.

§2 — Corrected dispute semantics (the issue has these inverted)

  • settleRecomputation(confirmed=true): the recomputation confirmed the original CID was wrong. The challenger's bond is returned, and matching original aggregators are slashed (S4_INVALID_AGGREGATION).
  • settleRecomputation(confirmed=false): the original CID stood. The challenger's bond is forfeited, and nobody is slashed.
  • expireDispute: the bond is fully returned, and the fresh subgroup is slashed (S4_FRESH_SUBGROUP_TIMEOUT).

§3 — Sequencing with task_240926_16

  • Part 1 (doc) has no dependency. Start it immediately.
  • Subgroup-steering scenario: write it against task_16 Part A's future-block seed (lockDisputeSeed), not commit-reveal. Before Part A merges, it's a test_knownGap_ that asserts blockhash(block.number - 1) steering works. Flip it in or after Part A's PR.
  • Bond/forfeiture destination assertions: write these after task_16 Part B merges, or assert through its helper. settleRecomputation(false) moves from full-bond-to-treasury to 50/50 burn/treasury.
  • Everything else in Part 2 can proceed in parallel with task_16.

Stretch — stress scenario

Roadmap P3-6.2's "10 simultaneous GIs × 20 clients, gas within L2 budget". Extend GasSimulation.t.sol, which already has at-scale pieces, rather than starting a new harness. This is a stretch goal. If time runs short, list it in the doc as deferred rather than rushing it.

Part 3 — Testnet-parameter readiness (#155)

The actual values (mintCap, dinPerEth and its authority, the four DinEmission params, the S1/S2 default and who may set it, MIN_STAKE) are decisions for the Fri Sep 25 meeting, not for this task. See the decision table in the #155 review. This part makes the code ready, so the decision lands as a config change:

  • 3a. Parameterise foundry/script/DeployPlatform.s.sol.
    • Read these with vm.envOr(...), following the existing pattern in DeployFairLaunchDistributor.s.sol:52-58: dinPerEth, mintCap, the four DinEmission.initialize args (today literals at :131-134), MIN_STAKE, and the S5/S6 params.
    • Default every one to today's value, so local deploys and existing tests are unchanged.
    • After deploy, call DinCoordinator.updateDinPerEth / setMintCap and DinValidatorStake.setMinStake / setS5RecidivismParams / setS6NoParticipationThreshold only when an override is set, and console.log the effective values.
    • Document the env keys in the script header and in .env.example.
    • Extend DeployPlatform.t.sol to assert that overrides take effect, and that defaults are unchanged when unset.
  • 3b. Bug fix: a 0 bps S1/S2 slash fraction bricks the GI.
    • setS1SlashFractionBps (DINTaskAuditor.sol:210) and setS2SlashFractionBps (DINTaskCoordinator.sol:1071) accept 0.
    • The computed s1Amount/s2Amount (DINTaskAuditor.sol:1290, DINTaskCoordinator.sol:888) is then 0, and DinValidatorStake.slashPartial reverts InvalidSlashAmount on a zero amount (:301).
    • So slashAuditors/slashAggregators revert whenever anyone missed a vote or submission, and the GI can't get past slashing. The same happens through rounding if MIN_STAKE * bps / 10000 == 0.
    • Fix both: reject bps == 0 in both setters, and skip the slashPartial call when the computed amount is 0.
    • Add regression tests for the setter guard and the rounding path.
    • Don't change who can set the fraction; that's a meeting decision.
  • 3c. DinEmission NatSpec. The header (DinEmission.sol:20) says emission "decays by (decayBps/10000) per epoch". The code multiplies by decayBps / 10000 (:258), i.e. it's a retention fraction (8000 = 20% decay/epoch). Fix the header to match initialize's @param (:126).
  • 3d. After the meeting: put the agreed values into .env.sepolia_op_devnet (or the testnet env file, if one is created for DevNet 3.0) through 3a's keys, in a small follow-up commit to the same PR or a separate one. If the meeting changes authority (e.g. S1/S2 fractions become DAO-bounded, or updateDinPerEth gets a different gate), that's new contract work: open an issue for it rather than growing this part.

Part 3 touches DINTaskAuditor.sol/DINTaskCoordinator.sol (the two setters and slash loops). Those files are also touched by task_240926_16, so rebase on develop after whichever task_16 PR merges first. The change is small and shouldn't conflict meaningfully.

Deliverables

  • Developer/design/adversarial-threat-model.md: scenario matrix (all minimum rows), trust-assumptions section, and gaps-before-audit list; signed off by Umer before Part 2 tests are written against it
  • foundry/test/P3Adversarial.t.sol:
    • one test per matrix row, named by outcome class, each with an attack-narrative NatSpec block linking its row
    • cross-GI scenarios span ≥2 GIs through the real lifecycle
    • no duplication of single-function paths already covered in the three suites named above
    • dispute expectations match §2
  • Every KNOWN GAP test links an open issue (new issues opened where needed)
  • Full forge build && forge test green
  • Stretch: 10 GIs × 20 clients stress scenario in GasSimulation.t.sol, or listed as deferred in the doc
  • Part 3a: DeployPlatform.s.sol reads the tokenomics params via vm.envOr with today's values as defaults; overrides applied post-deploy and logged; keys documented in .env.example; DeployPlatform.t.sol covers override and default cases
  • Part 3b: setS1SlashFractionBps/setS2SlashFractionBps reject 0; slash loops skip slashPartial when the computed amount is 0; regression tests for both
  • Part 3c: DinEmission header NatSpec says retention, not decay
  • Part 3d: Fri Sep 25 meeting values put into the network env file (follow-up commit once decided)
  • Update #154's issue text (or note the supersession on the issue) so it reflects the doc-first scope and the corrected dispute semantics

Submission

Three PRs from separate branches into develop:

  • PR 1 (Part 1): the threat-model doc, references #154. Merge gate for Part 2's expected outcomes.

  • PR 2 (Part 2): P3Adversarial.t.sol (+ stretch), references #154 (closes). In the description, list:

    • each matrix row → test name → outcome class
    • which rows flipped from knownGap to defended because task_16 had merged by then
  • PR 3 (Part 3): references #155. In the description, list every env key with its default, and note that the 3b fix covers both the setter and the rounding path. The meeting's values follow as 3d. #155 closes when they land.

Post all PR links and progress updates in Discussion #164.

Notes

  • The expected-outcome column is the judgment call in this task. Where it's unclear whether something should be DEFENDED or is a GAP (e.g. the colluding-T1-majority row), flag it in the doc PR. Don't pick silently.
  • Timeline: this shares the Sep 24 – Oct 1 window with task_240926_16's three PRs. If both can't fit, say so early in #164. The likely trade is the stretch stress scenario first, then any rows that depend on task_16 Parts A/B merging.
  • Parts 1–2 are test and doc work only. No contract changes there. If a scenario reveals a real bug, open an issue and add a test_knownGap_ for it; don't fix it in those parts. Part 3's two setter guards + zero-amount skip are the only contract changes in this task.
  • Part 3 doesn't depend on the meeting. 3a–3c can land before Sep 25, and only 3d waits for the values.