Skip to content

feat(contracts): forking mechanism TDD scaffold (skeletons + Foundry tests) - #2399

Draft
jaybuidl wants to merge 1 commit into
feat/forking-kit-specsfrom
claude/kleros-forking-mechanism-vln1we
Draft

feat(contracts): forking mechanism TDD scaffold (skeletons + Foundry tests)#2399
jaybuidl wants to merge 1 commit into
feat/forking-kit-specsfrom
claude/kleros-forking-mechanism-vln1we

Conversation

@jaybuidl

@jaybuidl jaybuidl commented Jun 15, 2026

Copy link
Copy Markdown
Member

What

Test-first scaffold for the forking mechanism (spec: contracts/docs/layer-1-core/07-forking.md, on feat/forking-kit-specs). Foundry suites encode the spec's guarantees / invariants / worked examples, and compile-ready contract skeletons let them run red until the implementation pass turns each suite green.

This deliberately ships scaffolding, not finished logic — skeleton bodies revert NotImplemented() or return inert defaults. The diff is small and the red/green line is clean.

TDD state (current)

Suite State Notes
ForkToken.t.sol 🟢 green ForkToken is fully implemented — the TDD sanity anchor
DisputeKitForking.t.sol 🟢 green the real inert IDisputeKit surface; voting stubs assert NotImplemented
ForkSettlement.t.sol 🟢 green wiring + access control + stub assertions
ForkMath.t.sol 🔴 red the two canonical worked examples (B fork=30, C fork=23) + edges
SortitionModule_Freeze.t.sol 🔴 red freeze / capture-without-refund (access control already green)
KlerosCore_ForkingCourt.t.sol 🔴 red (1) forking-court config (hooks' access control already green)
Forking_Integration.t.sol 🔴 red GC appeal exhaustion → forking-court jump → lifecycle

Existing core suites (KlerosCore_Staking/Initialization/Appeals) still pass — changes to existing contracts are additive only.

New contracts (skeletons)

  • ForkMath — removal fixed point, one threshold-sorted list per losing option (internal-only; exercised via ForkMathHarness).
  • DisputeKitForking — reworked IDisputeKit at the Core boundary. createDispute's 3rd arg is numberOfChoices per the interface (the earlier draft misnamed it finalRuling).
  • ForkSettlement — paginated Capture → MainDistrib → Mint → Done phase machine (separate contract per Q-007).
  • ForkToken — fully-working mintable ERC-20, one per minority fork.
  • PNKHolderEscrow — tier-2 participation path.
  • ISlashDestination — isolates the slash-destination decision (Q-006).

Additive changes to existing contracts

New surface only; appeal / execute / appealCost / initialize behavior is untouched (filled in the implementation pass):

  • SortitionModule: stakingFrozen flag; freeze / unfreeze / captureUnstakeAllCourts (+ ISortitionModule).
  • KlerosCore: forkSettlement storage + setForkSettlement; captureStakeForForking / distributeForking hooks (onlyForkSettlement); ForkingRoundStarted event; AppealNotAllowed / NotForkingCourt errors.
  • KlerosProxies: DisputeKitForkingProxy.

Implementation-pass map (what turns each red suite green)

  1. ForkMath logic → ForkMath.t.sol.
  2. SortitionModule freeze guard + captureUnstakeAllCourtsSortitionModule_Freeze.t.sol.
  3. KlerosCore forking guards (appeal/appealCost/_getCompatibleNextRoundSettings/execute) + forking-court config in initializeKlerosCore_ForkingCourt.t.sol + the jump in Forking_Integration.t.sol.
  4. DisputeKitForking commit/reveal/finalize + ForkSettlement settle → integration lifecycle.

⚠️ Draft: several suites are intentionally red (they are the acceptance criteria for the follow-up implementation work). CI running forge test will report these failures by design.

Note: the spec and Q-016 (locked-PNK joiner baseline) live on feat/forking-kit-specs; the open-questions register entry will be added when those docs land on this branch.

https://claude.ai/code/session_01W7bRbuy6gnrpKX5VctbC9K


Generated by Claude Code


PR-Codex overview

This PR introduces the implementation of a forking mechanism in the Kleros arbitration system, including new contracts for dispute handling, token minting, and stake management during forking rounds.

Detailed summary

  • Added DisputeKitForkingProxy and DisputeTemplateRegistryProxy contracts.
  • Introduced ISlashDestination interface for handling slashed PNK.
  • Enhanced ISortitionModule with freeze and unfreeze functions.
  • Created ForkToken contract for minting tokens for minority forks.
  • Implemented ForkMath library for handling voting mechanics.
  • Developed ForkSettlement for managing the settlement process.
  • Added ForkMathHarness for testing the ForkMath library.
  • Established SortitionModule to manage stake freezing during forking.
  • Created tests for various components, including Forking_Integration_Test and SortitionModule_Freeze_Test.

The following files were skipped due to too many changes: contracts/src/arbitration/dispute-kits/DisputeKitForking.sol

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Added forking court support for disputes, enabling terminal forking rounds as an alternative resolution path.
    • Implemented stake freezing mechanism during forking periods to prevent staking mutations.
    • Added fork settlement process with staged phases for managing dispute outcomes.
    • Introduced fork tokens as ERC-20 assets for tracking forking outcomes.
    • Implemented PNK holder escrow voting for tier-2 participation in forking disputes.
  • Bug Fixes

    • Disabled appeals after disputes enter forking court to prevent invalid appeal attempts.

…tests)

Scaffolds the forking mechanism (docs/layer-1-core/07-forking.md) test-first:
Foundry suites encode the spec's guarantees, invariants, and the two canonical
worked examples, and compile-ready contract skeletons let them run red until the
implementation pass turns them green one suite at a time.

New contracts (skeletons; bodies revert NotImplemented or return defaults):
- ForkMath: removal fixed point (one threshold-sorted list per losing option).
- DisputeKitForking: reworked IDisputeKit at the Core boundary; inert views are
  real, commit/reveal/finalize are stubbed. createDispute's 3rd arg is
  numberOfChoices per IDisputeKit (the earlier draft misnamed it finalRuling).
- ForkSettlement: paginated Capture/MainDistrib/Mint/Done phase machine.
- ForkToken: fully implemented mintable ERC-20 (one per minority fork) — green.
- PNKHolderEscrow: tier-2 participation path.
- ISlashDestination: isolates the slash-destination decision (Q-006).

Additive-only changes to existing contracts (new surface stubbed; existing
appeal/execute/appealCost/initialize behavior untouched, to be filled in the
implementation pass):
- SortitionModule: stakingFrozen flag; freeze/unfreeze/captureUnstakeAllCourts.
- KlerosCore: forkSettlement storage + setForkSettlement; captureStakeForForking
  and distributeForking hooks (onlyForkSettlement); ForkingRoundStarted event;
  AppealNotAllowed/NotForkingCourt errors.
- ISortitionModule / KlerosProxies: matching declarations + DisputeKitForkingProxy.

Tests (test/foundry/): ForkMath (canonical fixtures), ForkToken (green anchor),
SortitionModule_Freeze, DisputeKitForking, ForkSettlement, KlerosCore_ForkingCourt,
Forking_Integration, plus ForkingTestBase wiring the jump routing. Existing core
suites still pass (no regressions).

https://claude.ai/code/session_01W7bRbuy6gnrpKX5VctbC9K
@netlify

netlify Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploy Preview for kleros-v2-testnet-devtools failed. Why did it fail? →

Name Link
🔨 Latest commit 402961d
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet-devtools/deploys/6a2ff95f46fb900008ab74df

@netlify

netlify Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploy Preview for kleros-v2-neo failed. Why did it fail? →

Name Link
🔨 Latest commit 402961d
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/6a2ff95f7ddb300008f53a20

@netlify

netlify Bot commented Jun 15, 2026

Copy link
Copy Markdown

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit 402961d
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/6a2ff95ffc2f670008f97dc1
😎 Deploy Preview https://deploy-preview-2399--kleros-v2-testnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR introduces a TDD scaffolding for Kleros v2's forking mechanism. New contracts DisputeKitForking, ForkMath, ForkSettlement, PNKHolderEscrow, and ForkToken are added alongside extensions to KlerosCore and SortitionModule. All state-changing logic reverts NotImplemented(); interfaces, storage, events, errors, access control, and Foundry test suites define the complete specification.

Changes

Forking Mechanism TDD Scaffold

Layer / File(s) Summary
Interface contracts and KlerosCore/SortitionModule storage extensions
contracts/src/arbitration/interfaces/ISlashDestination.sol, contracts/src/arbitration/interfaces/ISortitionModule.sol, contracts/src/arbitration/KlerosCore.sol, contracts/src/arbitration/SortitionModule.sol
ISortitionModule gains freeze/unfreeze/captureUnstakeAllCourts; ISlashDestination is introduced. KlerosCore adds forkSettlement storage, ForkingRoundStarted event, onlyByForkSettlement modifier, setForkSettlement governance function, captureStakeForForking/distributeForking stubs, and four new custom errors. SortitionModule adds stakingFrozen flag, freeze lifecycle events, three core-restricted stub functions, and freeze-guard errors.
ForkMath library: data structures and stub API
contracts/src/arbitration/dispute-kits/ForkMath.sol
New ForkMath library declares VoterNode and OptionList storage structs modeling a per-option threshold-sorted doubly-linked list with finalization state and upgrade gaps. Four internal functions (search, insert, finalizeStep, isSurvivor) are declared with stub bodies reverting NotImplemented.
DisputeKitForking contract and UUPS proxy
contracts/src/arbitration/dispute-kits/DisputeKitForking.sol, contracts/src/proxy/KlerosProxies.sol
DisputeKitForking implements IDisputeKit as a terminal upgradeable forking kit with full UUPS/initializer structure. createDispute stores choices and marks initialization; draw returns zeroes; currentRuling reports winner post-winnerDetermined; commitVote/revealVote/finalize/search revert NotImplemented; getNextRoundSettings reverts UnsupportedOperation; hashForkVote is a live pure helper. DisputeKitForkingProxy wraps it in a UUPSProxy.
ForkToken ERC-20 with owner-restricted mint
contracts/src/token/ForkToken.sol
ForkToken inherits ERC20/Ownable, sets name/symbol/owner in constructor, restricts mint to the owner, and re-introduces increaseAllowance/decreaseAllowance helpers with an explicit underflow check in decreaseAllowance.
ForkSettlement state machine and PNKHolderEscrow
contracts/src/arbitration/dispute-kits/ForkSettlement.sol, contracts/src/arbitration/dispute-kits/PNKHolderEscrow.sol
ForkSettlement defines a Capture→MainDistrib→Mint→Done phase machine with Settlement struct, full storage, events, onlyByDK modifier, constructor wiring, and stub entrypoints (initSettle, settle, slash, forceUnfreeze) all reverting NotImplemented. PNKHolderEscrow stores tier-2 EscrowVote state per dispute/holder, declares lifecycle events, wires constructor, provides live revealOf view, and stubs deposit/commit/reveal/settleEscrow.
ForkMathHarness and ForkingTestBase test fixtures
contracts/src/test/ForkMathHarness.sol, contracts/test/foundry/ForkingTestBase.sol
ForkMathHarness wraps ForkMath internals for Foundry with insert, finalizeStep, and view accessors. ForkingTestBase extends KlerosCore_TestBase, deploys DisputeKitForking behind a UUPSProxy, registers it on FORKING_COURT, rewires GENERAL_COURT jump settings, and deploys/wires ForkSettlement and PNKHolderEscrow.
Unit tests: ForkToken, ForkMath, DisputeKitForking, ForkSettlement
contracts/test/foundry/ForkToken.t.sol, contracts/test/foundry/ForkMath.t.sol, contracts/test/foundry/DisputeKitForking.t.sol, contracts/test/foundry/ForkSettlement.t.sol
ForkToken_Test covers metadata, owner-restricted mint, supply, and allowance helpers. ForkMath_Test asserts removal fixed-point examples and edge cases as spec fixtures. DisputeKitForking_Test covers IDisputeKit surface, access control, inert views, and NotImplemented reverts. ForkSettlement_Test covers wiring assertions, DK-only access control, and NotImplemented placeholder reverts with inline invariant documentation.
Integration tests: Forking_Integration, KlerosCore_ForkingCourt, SortitionModule_Freeze
contracts/test/foundry/Forking_Integration.t.sol, contracts/test/foundry/KlerosCore_ForkingCourt.t.sol, contracts/test/foundry/SortitionModule_Freeze.t.sol
Forking_Integration_Test drives General Court → appeal → FORKING_COURT jump, asserting court switch, staking freeze, zero-vote round routing, AppealNotAllowed post-jump, and full-lifecycle ruling resolution. KlerosCore_ForkingCourt_Test asserts staking prohibition, DK registration, hook access control, stub reverts, and period configuration (marked failing). SortitionModule_Freeze_Test validates freeze/unfreeze guards, mutation reverts while frozen, capture-without-refund semantics, and Core-only access.

Sequence Diagram(s)

sequenceDiagram
  participant Juror
  participant KlerosCore
  participant SortitionModule
  participant DisputeKitForking
  participant ForkSettlement

  rect rgba(173, 216, 230, 0.5)
    note over Juror,KlerosCore: Appeal into forking court
    Juror->>KlerosCore: appeal() — fund into FORKING_COURT
    KlerosCore->>SortitionModule: freeze(disputeID)
    SortitionModule-->>KlerosCore: stakingFrozen=true, emit StakingFreezeEngaged
    KlerosCore->>DisputeKitForking: createDispute(coreDisputeID, numberOfChoices)
    KlerosCore-->>Juror: emit ForkingRoundStarted
  end

  rect rgba(255, 200, 150, 0.5)
    note over Juror,KlerosCore: Forking round voting (stubs)
    Juror->>DisputeKitForking: commitVote(coreDisputeID, commit)
    DisputeKitForking-->>Juror: revert NotImplemented
    Juror->>DisputeKitForking: revealVote(coreDisputeID, choice, threshold, salt)
    DisputeKitForking-->>Juror: revert NotImplemented
    Juror->>KlerosCore: appeal() after forking
    KlerosCore-->>Juror: revert AppealNotAllowed
  end

  rect rgba(180, 255, 180, 0.5)
    note over ForkSettlement,SortitionModule: Settlement (stubs)
    ForkSettlement->>KlerosCore: captureStakeForForking(joiner)
    KlerosCore-->>ForkSettlement: revert NotImplemented
    ForkSettlement->>SortitionModule: unfreeze()
    SortitionModule-->>ForkSettlement: revert NotImplemented
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • tractorss

Poem

🐇 Hop, hop, the forks align,
New courts and stubs in every line,
NotImplemented blocks the way —
But spec is set for a future day!
The rabbit drafts the grand design,
Soon the ForkMath will be fine. 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(contracts): forking mechanism TDD scaffold (skeletons + Foundry tests)' accurately describes the main change: introducing a test-driven development scaffold with skeleton contracts and tests for the forking mechanism.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/kleros-forking-mechanism-vln1we

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
contracts/test/foundry/SortitionModule_Freeze.t.sol (1)

89-98: ⚡ Quick win

Add unfreeze access-control assertion to match the test intent.

The section states freeze/unfreeze/capture are Core-only, but this test only checks freeze and capture. Add a non-core unfreeze revert assertion to fully lock the contract boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/test/foundry/SortitionModule_Freeze.t.sol` around lines 89 - 98,
The test_onlyCoreCanFreeze test in SortitionModule_Freeze.t.sol is incomplete.
While the comment states it tests that freeze, unfreeze, and capture are
Core-only operations, the test only verifies freeze and captureUnstakeAllCourts.
Add another assertion block to the test that checks the unfreeze method also
reverts with KlerosCoreOnly when called by a non-core account (other), using
vm.expectRevert and vm.prank to match the pattern of the existing checks.
contracts/test/foundry/ForkToken.t.sol (1)

43-49: ⚡ Quick win

Assert per-holder balances in the multi-mint supply test.

Line 48 verifies total supply, but this test can still pass if minted amounts are misrouted between recipients. Add holder-level balance assertions to lock the behavior.

Proposed test hardening
     function test_mint_tracksSupplyAcrossHolders() public {
         vm.startPrank(settlement);
         token.mint(alice, 700);
         token.mint(bob, 300);
         vm.stopPrank();
+        assertEq(token.balanceOf(alice), 700, "alice genesis balance mismatch");
+        assertEq(token.balanceOf(bob), 300, "bob genesis balance mismatch");
         assertEq(token.totalSupply(), 1000, "supply must equal the sum of genesis balances");
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/test/foundry/ForkToken.t.sol` around lines 43 - 49, In the
test_mint_tracksSupplyAcrossHolders function, add holder-level balance
assertions to verify that alice received exactly 700 tokens and bob received
exactly 300 tokens after their respective mint calls. Currently the test only
asserts the total supply equals 1000, which can pass even if tokens are
misdirected between recipients. Use assertEq to check token.balanceOf(alice) and
token.balanceOf(bob) against their expected amounts to ensure the minted amounts
are routed to the correct recipients.
contracts/test/foundry/ForkSettlement.t.sol (1)

41-45: ⚡ Quick win

Add a DK-authorized slash placeholder-revert test.

This file verifies slash access control, but it doesn’t assert current RED behavior for the authorized path. Add a test where msg.sender == address(forkingDK) and slash reverts ForkSettlement.NotImplemented.

Suggested test addition
+    function test_slash_notImplemented() public {
+        vm.expectRevert(ForkSettlement.NotImplemented.selector);
+        vm.prank(address(forkingDK));
+        forkSettlement.slash(DISPUTE_ID, staker1, 100);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/test/foundry/ForkSettlement.t.sol` around lines 41 - 45, Add a new
test function in ForkSettlement.t.sol after test_slash_onlyByDK that verifies
the authorized path behavior. This test should use vm.prank to set the caller to
the forkingDK address, then expect the slash function to revert with
ForkSettlement.NotImplemented selector before calling forkSettlement.slash with
test parameters (DISPUTE_ID, staker1, and an amount). This validates that while
the DisputeKit has permission to call slash, the function currently returns a
NotImplemented revert rather than executing the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/src/arbitration/dispute-kits/ForkSettlement.sol`:
- Around line 105-111: The constructor in ForkSettlement.sol does not validate
that the _disputeKit parameter is not address(0), which could brick the contract
if an invalid address is passed. Add a require statement in the constructor to
validate that _disputeKit is not address(0), placing this check before the
assignment to the disputeKit state variable. If the validation fails, the
transaction should revert with a descriptive error message indicating that
_disputeKit cannot be the zero address.

In `@contracts/src/arbitration/dispute-kits/PNKHolderEscrow.sol`:
- Around line 77-79: The setForkSettlement function has two issues: it uses
NotImplemented() error which is inappropriate for an authorization check and
should be replaced with a proper auth-related error, and it accepts address(0)
for _forkSettlement which could cause routing misconfiguration. Add a require
statement to validate that _forkSettlement is not the zero address and revert
with an appropriate error if it is, and change the existing require statement to
use an explicit authorization error instead of NotImplemented().
- Around line 69-73: The constructor in PNKHolderEscrow does not validate that
the _owner parameter is non-zero, which allows owner to be set to address(0) and
makes the setForkSettlement function permanently unreachable and the escrow
unconfigurable. Add a require statement in the constructor to validate that
_owner is not address(0) before assigning it to the owner state variable.

In `@contracts/test/foundry/DisputeKitForking.t.sol`:
- Around line 27-35: The test function
`test_createDispute_storesNumberOfChoices` does not actually assert that the
numberOfChoices value (3) is stored correctly, despite its name suggesting this
is the primary test objective. After the first vm.prank and
forkingDK.createDispute call, add an assertion that verifies the stored
numberOfChoices for DISPUTE_ID equals 3 by using an existing getter method on
the forkingDK contract to retrieve the stored numberOfChoices value.

In `@contracts/test/foundry/Forking_Integration.t.sol`:
- Line 69: The test is using hardcoded appeal funding amounts (0.63 ether) in
multiple calls to disputeKit.fundAppeal, which creates brittle tests that fail
when fee configurations change. Instead of hardcoding this value, compute the
required appeal amount at runtime from the current protocol state (likely from
the dispute kit configuration or related fee parameters) and use that computed
value for all fundAppeal calls throughout the test. This ensures the test
remains resilient to changes in protocol fees.

---

Nitpick comments:
In `@contracts/test/foundry/ForkSettlement.t.sol`:
- Around line 41-45: Add a new test function in ForkSettlement.t.sol after
test_slash_onlyByDK that verifies the authorized path behavior. This test should
use vm.prank to set the caller to the forkingDK address, then expect the slash
function to revert with ForkSettlement.NotImplemented selector before calling
forkSettlement.slash with test parameters (DISPUTE_ID, staker1, and an amount).
This validates that while the DisputeKit has permission to call slash, the
function currently returns a NotImplemented revert rather than executing the
logic.

In `@contracts/test/foundry/ForkToken.t.sol`:
- Around line 43-49: In the test_mint_tracksSupplyAcrossHolders function, add
holder-level balance assertions to verify that alice received exactly 700 tokens
and bob received exactly 300 tokens after their respective mint calls. Currently
the test only asserts the total supply equals 1000, which can pass even if
tokens are misdirected between recipients. Use assertEq to check
token.balanceOf(alice) and token.balanceOf(bob) against their expected amounts
to ensure the minted amounts are routed to the correct recipients.

In `@contracts/test/foundry/SortitionModule_Freeze.t.sol`:
- Around line 89-98: The test_onlyCoreCanFreeze test in
SortitionModule_Freeze.t.sol is incomplete. While the comment states it tests
that freeze, unfreeze, and capture are Core-only operations, the test only
verifies freeze and captureUnstakeAllCourts. Add another assertion block to the
test that checks the unfreeze method also reverts with KlerosCoreOnly when
called by a non-core account (other), using vm.expectRevert and vm.prank to
match the pattern of the existing checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c076e58c-f9bb-40b4-95ab-45141e84c229

📥 Commits

Reviewing files that changed from the base of the PR and between 0f19d23 and 402961d.

📒 Files selected for processing (19)
  • contracts/src/arbitration/KlerosCore.sol
  • contracts/src/arbitration/SortitionModule.sol
  • contracts/src/arbitration/dispute-kits/DisputeKitForking.sol
  • contracts/src/arbitration/dispute-kits/ForkMath.sol
  • contracts/src/arbitration/dispute-kits/ForkSettlement.sol
  • contracts/src/arbitration/dispute-kits/PNKHolderEscrow.sol
  • contracts/src/arbitration/interfaces/ISlashDestination.sol
  • contracts/src/arbitration/interfaces/ISortitionModule.sol
  • contracts/src/proxy/KlerosProxies.sol
  • contracts/src/test/ForkMathHarness.sol
  • contracts/src/token/ForkToken.sol
  • contracts/test/foundry/DisputeKitForking.t.sol
  • contracts/test/foundry/ForkMath.t.sol
  • contracts/test/foundry/ForkSettlement.t.sol
  • contracts/test/foundry/ForkToken.t.sol
  • contracts/test/foundry/ForkingTestBase.sol
  • contracts/test/foundry/Forking_Integration.t.sol
  • contracts/test/foundry/KlerosCore_ForkingCourt.t.sol
  • contracts/test/foundry/SortitionModule_Freeze.t.sol

Comment on lines +105 to +111
constructor(address _owner, KlerosCore _core, address _disputeKit) {
owner = _owner;
core = _core;
pinakion = _core.pinakion();
disputeKit = _disputeKit;
slashDestination = ISlashDestination(address(this)); // Baseline Q-006: absorb into redistribution.
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate _disputeKit in constructor to prevent a bricked settlement contract.

If _disputeKit is address(0), onlyByDK can never pass, so initSettle/slash become permanently unreachable.

Suggested fix
 contract ForkSettlement {
@@
     error NotImplemented();
     error DisputeKitOnly();
+    error InvalidDisputeKit();
@@
     constructor(address _owner, KlerosCore _core, address _disputeKit) {
+        require(_disputeKit != address(0), InvalidDisputeKit());
         owner = _owner;
         core = _core;
         pinakion = _core.pinakion();
         disputeKit = _disputeKit;
         slashDestination = ISlashDestination(address(this)); // Baseline Q-006: absorb into redistribution.
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/arbitration/dispute-kits/ForkSettlement.sol` around lines 105 -
111, The constructor in ForkSettlement.sol does not validate that the
_disputeKit parameter is not address(0), which could brick the contract if an
invalid address is passed. Add a require statement in the constructor to
validate that _disputeKit is not address(0), placing this check before the
assignment to the disputeKit state variable. If the validation fails, the
transaction should revert with a descriptive error message indicating that
_disputeKit cannot be the zero address.

Comment on lines +69 to +73
constructor(address _owner, KlerosCore _core) {
owner = _owner;
core = _core;
pinakion = _core.pinakion();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject zero owner in constructor.

Deploying with owner = address(0) makes setForkSettlement permanently unreachable and leaves escrow unconfigurable.

Suggested fix
 contract PNKHolderEscrow {
@@
     error NotImplemented();
     error KlerosCoreOnly();
     error ForkSettlementOnly();
+    error InvalidOwner();
@@
     constructor(address _owner, KlerosCore _core) {
+        require(_owner != address(0), InvalidOwner());
         owner = _owner;
         core = _core;
         pinakion = _core.pinakion();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/arbitration/dispute-kits/PNKHolderEscrow.sol` around lines 69 -
73, The constructor in PNKHolderEscrow does not validate that the _owner
parameter is non-zero, which allows owner to be set to address(0) and makes the
setForkSettlement function permanently unreachable and the escrow
unconfigurable. Add a require statement in the constructor to validate that
_owner is not address(0) before assigning it to the owner state variable.

Comment on lines +77 to +79
function setForkSettlement(address _forkSettlement) external {
require(msg.sender == owner, NotImplemented());
forkSettlement = _forkSettlement;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use explicit auth/config errors in setForkSettlement and disallow zero address.

NotImplemented() here masks auth failures, and accepting address(0) allows avoidable routing misconfiguration for settlement handoff.

Suggested fix
 contract PNKHolderEscrow {
@@
     error NotImplemented();
     error KlerosCoreOnly();
     error ForkSettlementOnly();
+    error OwnerOnly();
+    error InvalidForkSettlement();
@@
     function setForkSettlement(address _forkSettlement) external {
-        require(msg.sender == owner, NotImplemented());
+        require(msg.sender == owner, OwnerOnly());
+        require(_forkSettlement != address(0), InvalidForkSettlement());
         forkSettlement = _forkSettlement;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/arbitration/dispute-kits/PNKHolderEscrow.sol` around lines 77 -
79, The setForkSettlement function has two issues: it uses NotImplemented()
error which is inappropriate for an authorization check and should be replaced
with a proper auth-related error, and it accepts address(0) for _forkSettlement
which could cause routing misconfiguration. Add a require statement to validate
that _forkSettlement is not the zero address and revert with an appropriate
error if it is, and change the existing require statement to use an explicit
authorization error instead of NotImplemented().

Comment on lines +27 to +35
function test_createDispute_storesNumberOfChoices() public {
vm.prank(address(core));
forkingDK.createDispute(DISPUTE_ID, 0, 3, "", 0);
assertTrue(forkingDK.initialized(DISPUTE_ID), "dispute must be marked initialized");

vm.expectRevert(DisputeKitForking.ForkAlreadyInitiated.selector);
vm.prank(address(core));
forkingDK.createDispute(DISPUTE_ID, 0, 3, "", 0);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

test_createDispute_storesNumberOfChoices is missing the key assertion.

Line 27 names this as a number-of-choices storage test, but the body only checks initialization and duplicate-creation revert. Please assert that the stored numberOfChoices for the dispute is 3 (via existing getter or a small test helper).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/test/foundry/DisputeKitForking.t.sol` around lines 27 - 35, The
test function `test_createDispute_storesNumberOfChoices` does not actually
assert that the numberOfChoices value (3) is stored correctly, despite its name
suggesting this is the primary test objective. After the first vm.prank and
forkingDK.createDispute call, add an assertion that verifies the stored
numberOfChoices for DISPUTE_ID equals 3 by using an existing getter method on
the forkingDK contract to retrieve the stored numberOfChoices value.


// Funding the appeal must route the court jump to the forking court (DK is jumping to forking).
vm.prank(crowdfunder1);
disputeKit.fundAppeal{value: 0.63 ether}(DISPUTE_ID, 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid hardcoded appeal funding amounts in integration tests.

Using a fixed 0.63 ether ties test outcomes to mutable fee config and can cause unrelated failures. Compute the required appeal amount from protocol state at runtime, then fund with that value.

Also applies to: 85-85, 97-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/test/foundry/Forking_Integration.t.sol` at line 69, The test is
using hardcoded appeal funding amounts (0.63 ether) in multiple calls to
disputeKit.fundAppeal, which creates brittle tests that fail when fee
configurations change. Instead of hardcoding this value, compute the required
appeal amount at runtime from the current protocol state (likely from the
dispute kit configuration or related fee parameters) and use that computed value
for all fundAppeal calls throughout the test. This ensures the test remains
resilient to changes in protocol fees.

@jaybuidl
jaybuidl changed the base branch from dev to feat/forking-kit-specs June 15, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants