Skip to content

Conversation

@jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Sep 11, 2025

The goal is to offload Courts related functions to a dedicated contract to reduce the size of KlerosCore which is above 24KB with the current optimizer settings


PR-Codex overview

This PR focuses on refactoring and enhancing the KlerosCore and related contracts by introducing a new CourtRegistry contract, which centralizes court management and modifies how courts and their properties are accessed.

Detailed summary

  • Introduced CourtRegistry contract to manage court data.
  • Replaced direct access to court arrays with courts.get() method.
  • Updated KlerosCore to utilize CourtRegistry for court data.
  • Added functions in CourtRegistry for creating and modifying courts.
  • Removed redundant court struct definitions from KlerosCore.
  • Enhanced dispute kit management in CourtRegistry.
  • Added error handling for unsupported dispute kits.
  • Refactored functions to improve code readability and maintainability.

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

Summary by CodeRabbit

  • New Features
    • Upgradeable Court Registry with hierarchical courts, governance controls, per-court dispute-kit management, events for creation/updates/enablement, and read-only queries (courts, children, timings, support).
  • Refactor
    • Core arbitration now delegates court data and lifecycle operations to the external registry, simplifying in-contract storage and modularizing court/dispute-kit management.
  • Other
    • Sortition and voting paths updated to read court flags from the registry (no user-visible behavior changes).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Warning

Rate limit exceeded

@jaybuidl has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 7a90bbc and 3c5516c.

📒 Files selected for processing (7)
  • contracts/src/arbitration/CourtRegistry.sol (1 hunks)
  • contracts/src/arbitration/KlerosCore.sol (22 hunks)
  • contracts/src/arbitration/SortitionModule.sol (2 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (2 hunks)
  • contracts/src/arbitration/university/SortitionModuleUniversity.sol (3 hunks)
  • contracts/src/libraries/SortitionTrees.sol (1 hunks)
  • contracts/src/test/KlerosCoreMock.sol (1 hunks)

Walkthrough

Adds an upgradeable CourtRegistry contract and refactors KlerosCore and related modules to use it for all court data and operations. Adjusts initialization to accept a CourtRegistry, migrates court creation/queries and dispute-kit checks to the registry, and restores a documented toTreeKey helper in SortitionTrees.

Changes

Cohort / File(s) Summary
New Court Registry
contracts/src/arbitration/CourtRegistry.sol
New UUPS upgradeable contract providing Court struct, owner/core roles, court creation/modification, dispute-kit enablement, getters, validations, events (CourtCreated, CourtModified, DisputeKitEnabled) and errors.
Core refactor to external registry
contracts/src/arbitration/KlerosCore.sol
Replaces in-contract Court[] storage with CourtRegistry integration. initialize accepts CourtRegistry, court creation and all court-related reads/writes moved to registry accessors; removed several court-related errors and view helpers from core.
Sortition modules & helper
contracts/src/arbitration/SortitionModule.sol, contracts/src/arbitration/university/SortitionModuleUniversity.sol, contracts/src/libraries/SortitionTrees.sol
SortitionModule now uses CourtRegistry to traverse parent courts in _setStake. SortitionModuleUniversity adds preparatory imports/comments for registry usage. toTreeKey reintroduced in SortitionTrees with NatSpec; no semantic change.
Dispute kit & test tweaks
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol, contracts/src/test/KlerosCoreMock.sol
DisputeKitClassicBase reads hiddenVotes via core.courts().get(courtID).hiddenVotes. KlerosCoreMock uses courts.get(uint96(_courtId)) to fetch children; signatures unchanged.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Deployer
    participant Core as KlerosCore
    participant Reg as CourtRegistry
    participant DK as DisputeKit(s)

    Deployer->>Reg: initialize(owner, core=Core)
    Deployer->>Core: initialize(..., courts=Reg)

    Note over Core,Reg: Runtime interactions

    Core->>Reg: createCourt(parent, params, kits)
    Reg-->>Core: courtID\nemit CourtCreated

    Core->>Reg: get(courtID)
    Core->>Reg: getTimesPerPeriod(courtID)
    Core->>Reg: isSupported(courtID, disputeKitID)
    alt dispute kit supported
        Core->>DK: proceed with arbitration/appeal flow
        DK-->>Core: results
    else not supported
        Reg-->>Core: returns false -> Core reverts/handles
    end

    Core->>Reg: enableDisputeKit(courtID, kitID, enable) [onlyByCore]
    Reg-->>Core: emit DisputeKitEnabled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Compatibility: ABI change 🗯, Storage layout incompatibility ⚠️

Suggested reviewers

  • unknownunknown1

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Extraction of CourtRegistry from KlerosCore" succinctly and accurately captures the primary change in this PR—moving court-related state and logic into a dedicated CourtRegistry and updating KlerosCore to delegate to it; it is specific, concise, and readable for teammates scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I hop through ledgers, registry new and bright,
Core asks for a court and I stamp it right.
Kits checked and trees keyed in tidy arrays,
Justice in hops and bytes — carrots for praise. 🥕🐇

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/court-registry

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

@netlify
Copy link

netlify bot commented Sep 11, 2025

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

Name Link
🔨 Latest commit 3c5516c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-university/deploys/68c232ff4d5a9000081488b5

@netlify
Copy link

netlify bot commented Sep 11, 2025

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit 3c5516c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/68c232ffbdfd2f0008f7089b
😎 Deploy Preview https://deploy-preview-2137--kleros-v2-testnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@netlify
Copy link

netlify bot commented Sep 11, 2025

Deploy Preview for kleros-v2-neo ready!

Name Link
🔨 Latest commit 3c5516c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/68c232ff7cc7c200089719cd
😎 Deploy Preview https://deploy-preview-2137--kleros-v2-neo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@netlify
Copy link

netlify bot commented Sep 11, 2025

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

Name Link
🔨 Latest commit 3c5516c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet-devtools/deploys/68c232ff92fe2d00070e60bc

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
contracts/src/arbitration/KlerosCore.sol (1)

351-371: Add validation for empty _supportedDisputeKits array.

The function validates individual dispute kit IDs but doesn't check if the _supportedDisputeKits array is empty before the loop. While the CourtRegistry's createCourt will validate this, it's good practice to fail fast with a clear error.

 function createCourt(
     uint96 _parent,
     bool _hiddenVotes,
     uint256 _minStake,
     uint256 _alpha,
     uint256 _feeForJuror,
     uint256 _jurorsForCourtJump,
     uint256[4] memory _timesPerPeriod,
     bytes memory _sortitionExtraData,
     uint256[] memory _supportedDisputeKits
 ) external onlyByOwner {
+    if (_supportedDisputeKits.length == 0) revert WrongDisputeKitIndex();
     uint96 courtID = courts.createCourt(
contracts/src/arbitration/CourtRegistry.sol (2)

93-95: Address the TODO for FORKING_COURT initialization.

The FORKING_COURT is created as an empty court without proper initialization. This TODO should be addressed before production deployment.

Would you like me to help implement the proper initialization for the FORKING_COURT with appropriate properties and emit the CourtCreated event?


173-180: Inconsistent validation message when checking child courts.

When a child court's minStake is less than the new parent's minStake, the error message MinStakeLowerThanParentCourt is misleading since it's actually the child's stake that's problematic.

Consider adding a more specific error for this case:

+    error ChildCourtMinStakeTooLow();
     
     for (uint256 i = 0; i < court.children.length; i++) {
         if (courts[court.children[i]].minStake < _minStake) {
-            revert MinStakeLowerThanParentCourt();
+            revert ChildCourtMinStakeTooLow();
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41a5b77 and 9725056.

📒 Files selected for processing (3)
  • contracts/src/arbitration/CourtRegistry.sol (1 hunks)
  • contracts/src/arbitration/KlerosCore.sol (21 hunks)
  • contracts/src/libraries/SortitionTrees.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-11-19T16:31:08.965Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:167-170
Timestamp: 2024-11-19T16:31:08.965Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the `minStake` parameter is denominated in PNK, not ETH.

Applied to files:

  • contracts/src/arbitration/KlerosCore.sol
📚 Learning: 2024-11-19T16:31:08.965Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:167-170
Timestamp: 2024-11-19T16:31:08.965Z
Learning: In the court hierarchy, child courts' `minStake` must be greater than or equal to their parent court's `minStake`.

Applied to files:

  • contracts/src/arbitration/KlerosCore.sol
📚 Learning: 2024-11-19T16:09:41.467Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:3-17
Timestamp: 2024-11-19T16:09:41.467Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the General Court (ID: 1) can have its `parent` ID set to itself (`"parent": 1`), as there is no parent court with ID 0 currently.

Applied to files:

  • contracts/src/arbitration/KlerosCore.sol
  • contracts/src/arbitration/CourtRegistry.sol
📚 Learning: 2024-11-19T17:18:39.007Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:3-5
Timestamp: 2024-11-19T17:18:39.007Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the General Court (id: 1) intentionally references itself as its parent (`"parent": 1`). This self-reference is acceptable and should not be flagged as an issue in future reviews.

Applied to files:

  • contracts/src/arbitration/CourtRegistry.sol
🔇 Additional comments (7)
contracts/src/libraries/SortitionTrees.sol (1)

185-190: LGTM! Well-documented helper function.

The reintroduced toTreeKey function is properly placed in the Public Views section with clear NatSpec documentation. The implementation correctly wraps a CourtID into a TreeKey for use with sortition trees.

contracts/src/arbitration/KlerosCore.sol (5)

10-10: LGTM! Clean import statement for the new CourtRegistry.

The import statement properly introduces the CourtRegistry contract and the Court struct that will be used throughout the refactored KlerosCore.


1106-1110: Court validation logic looks correct.

The validation properly checks against FORKING_COURT and the total number of courts from the registry. The minimum stake validation also correctly uses the registry's getter.


207-259: ```shell
#!/bin/bash
set -euo pipefail

echo "== Searching for FORKING_COURT and GENERAL_COURT occurrences =="
rg -n -S "FORKING_COURT|GENERAL_COURT" || true

echo "== Searching for createTree, createCourt, enableDisputeKit calls =="
rg -n -S "createTree(|createCourt(|enableDisputeKit(" || true

echo "== Locating CourtRegistry contract files =="
rg -n -S "contract CourtRegistry" || true

REG_FILES=$(rg -l -S "contract CourtRegistry" || true)
for f in $REG_FILES; do
echo "---- $f ----"
rg -n -C5 "function createCourt(" "$f" || true
rg -n -C20 "createCourt(" "$f" || true
done

echo "== Showing KlerosCore.initialize() context =="
KC_FILES=$(rg -l -S "contract KlerosCore" || true)
for f in $KC_FILES; do
echo "---- $f ----"
rg -n -C10 "function initialize(" "$f" || true
done

echo "Done."


---

`1025-1034`: **All _isCourtJumping callers updated to accept _jurorsForCourtJump**

Both calls to _isCourtJumping in contracts/src/arbitration/KlerosCore.sol (lines 1003 and 1045) pass court.jurorsForCourtJump; no other callers were found.

---

`773-773`: **Resolved — penalizedInCourtID comes from validated sources.**  
drawnJurorFromCourtIDs is set in draw as `fromSubcourtID != 0 ? fromSubcourtID : dispute.courtID` (contracts/src/arbitration/KlerosCore.sol:591); dispute.courtID is obtained via `courts.get(courtID)` during dispute creation (contracts/src/arbitration/KlerosCore.sol:502), so `courts.get(penalizedInCourtID).minStake` at contracts/src/arbitration/KlerosCore.sol:773 is accessing a validated court.

</blockquote></details>
<details>
<summary>contracts/src/arbitration/CourtRegistry.sol (1)</summary><blockquote>

`214-244`: **Public view functions are well-implemented.**

The getter functions provide clean interfaces for accessing court data, children, times per period, dispute kit support, and the total number of courts. Good separation of concerns.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +129 to +162
if (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();

courtID = uint96(courts.length);
Court storage court = courts.push();

// Check that Classic DK support was added.
if (!supportedDisputeKits[courtID][DISPUTE_KIT_CLASSIC]) revert MustSupportDisputeKitClassic();

court.parent = _parent;
court.children = new uint256[](0);
court.hiddenVotes = _hiddenVotes;
court.minStake = _minStake;
court.alpha = _alpha;
court.feeForJuror = _feeForJuror;
court.jurorsForCourtJump = _jurorsForCourtJump;
court.timesPerPeriod = _timesPerPeriod;

// Update the parent.
courts[_parent].children.push(courtID);
emit CourtCreated(
uint96(courtID),
_parent,
_hiddenVotes,
_minStake,
_alpha,
_feeForJuror,
_jurorsForCourtJump,
_timesPerPeriod,
_supportedDisputeKits
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical issue: Classic dispute kit support check happens after court creation.

The check for Classic DK support (line 137) occurs after the court has already been pushed to the array (line 134). This means if the check fails, the court is already created but in an invalid state. The check should validate the _supportedDisputeKits array before creating the court.

 function createCourt(
     uint96 _parent,
     bool _hiddenVotes,
     uint256 _minStake,
     uint256 _alpha,
     uint256 _feeForJuror,
     uint256 _jurorsForCourtJump,
     uint256[4] memory _timesPerPeriod,
     uint256[] memory _supportedDisputeKits
 ) external onlyByCore returns (uint96 courtID) {
     if (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
     if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
     if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();
+    
+    // Check that Classic DK will be supported
+    bool hasClassicDK = false;
+    for (uint256 i = 0; i < _supportedDisputeKits.length; i++) {
+        if (_supportedDisputeKits[i] == DISPUTE_KIT_CLASSIC) {
+            hasClassicDK = true;
+            break;
+        }
+    }
+    if (!hasClassicDK) revert MustSupportDisputeKitClassic();

     courtID = uint96(courts.length);
     Court storage court = courts.push();

-    // Check that Classic DK support was added.
-    if (!supportedDisputeKits[courtID][DISPUTE_KIT_CLASSIC]) revert MustSupportDisputeKitClassic();
-
     court.parent = _parent;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();
courtID = uint96(courts.length);
Court storage court = courts.push();
// Check that Classic DK support was added.
if (!supportedDisputeKits[courtID][DISPUTE_KIT_CLASSIC]) revert MustSupportDisputeKitClassic();
court.parent = _parent;
court.children = new uint256[](0);
court.hiddenVotes = _hiddenVotes;
court.minStake = _minStake;
court.alpha = _alpha;
court.feeForJuror = _feeForJuror;
court.jurorsForCourtJump = _jurorsForCourtJump;
court.timesPerPeriod = _timesPerPeriod;
// Update the parent.
courts[_parent].children.push(courtID);
emit CourtCreated(
uint96(courtID),
_parent,
_hiddenVotes,
_minStake,
_alpha,
_feeForJuror,
_jurorsForCourtJump,
_timesPerPeriod,
_supportedDisputeKits
);
}
if (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();
// Check that Classic DK will be supported
bool hasClassicDK = false;
for (uint256 i = 0; i < _supportedDisputeKits.length; i++) {
if (_supportedDisputeKits[i] == DISPUTE_KIT_CLASSIC) {
hasClassicDK = true;
break;
}
}
if (!hasClassicDK) revert MustSupportDisputeKitClassic();
courtID = uint96(courts.length);
Court storage court = courts.push();
court.parent = _parent;
court.children = new uint256[](0);
court.hiddenVotes = _hiddenVotes;
court.minStake = _minStake;
court.alpha = _alpha;
court.feeForJuror = _feeForJuror;
court.jurorsForCourtJump = _jurorsForCourtJump;
court.timesPerPeriod = _timesPerPeriod;
// Update the parent.
courts[_parent].children.push(courtID);
emit CourtCreated(
uint96(courtID),
_parent,
_hiddenVotes,
_minStake,
_alpha,
_feeForJuror,
_jurorsForCourtJump,
_timesPerPeriod,
_supportedDisputeKits
);
}

Comment on lines +202 to +214
function enableDisputeKit(uint96 _courtID, uint256 _disputeKitID, bool _enable) external onlyByCore {
supportedDisputeKits[_courtID][_disputeKitID] = _enable;
emit DisputeKitEnabled(_courtID, _disputeKitID, _enable);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider preventing disabling of Classic dispute kit.

The enableDisputeKit function allows disabling any dispute kit, including the Classic DK. Based on the requirement that all courts must support Classic DK, consider adding validation.

 function enableDisputeKit(uint96 _courtID, uint256 _disputeKitID, bool _enable) external onlyByCore {
+    // Classic dispute kit must always be enabled
+    if (_disputeKitID == DISPUTE_KIT_CLASSIC && !_enable) {
+        revert MustSupportDisputeKitClassic();
+    }
     supportedDisputeKits[_courtID][_disputeKitID] = _enable;
     emit DisputeKitEnabled(_courtID, _disputeKitID, _enable);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function enableDisputeKit(uint96 _courtID, uint256 _disputeKitID, bool _enable) external onlyByCore {
supportedDisputeKits[_courtID][_disputeKitID] = _enable;
emit DisputeKitEnabled(_courtID, _disputeKitID, _enable);
}
function enableDisputeKit(uint96 _courtID, uint256 _disputeKitID, bool _enable) external onlyByCore {
// Classic dispute kit must always be enabled
if (_disputeKitID == DISPUTE_KIT_CLASSIC && !_enable) {
revert MustSupportDisputeKitClassic();
}
supportedDisputeKits[_courtID][_disputeKitID] = _enable;
emit DisputeKitEnabled(_courtID, _disputeKitID, _enable);
}
🤖 Prompt for AI Agents
In contracts/src/arbitration/CourtRegistry.sol around lines 202 to 205, the
enableDisputeKit function currently allows disabling any dispute kit including
the Classic DK; add validation so that when _enable is false the call reverts if
_disputeKitID equals the Classic dispute kit id (use the contract constant/enum
for the Classic DK or the known id value), e.g., require(!(!_enable &&
_disputeKitID == CLASSIC_DISPUTE_KIT_ID), "Cannot disable Classic dispute kit");
keep the existing state update and event emission for other kits.

@jaybuidl jaybuidl force-pushed the refactor/court-registry branch from 7a90bbc to 6c40c01 Compare September 11, 2025 02:22
@sonarqubecloud
Copy link

@jaybuidl
Copy link
Member Author

Abandoning as it makes KlerosCore even bigger

@jaybuidl jaybuidl closed this Sep 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants