Skip to content

Conversation

@jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Aug 8, 2025

PR-Codex overview

This PR focuses on refactoring the Kleros arbitration system, including updates to contract ownership, improved code organization, and enhanced functionality in various components, particularly around dispute handling and voting mechanisms.

Detailed summary

  • Renamed governor to owner in multiple contracts for clarity.
  • Updated contract interfaces for consistency.
  • Improved dispute kit functionality and added mock contracts for testing.
  • Refactored voting mechanisms to support hidden votes.
  • Enhanced error handling and validation in various components.
  • Replaced ReactMarkdown with MarkdownRenderer for better performance.
  • Updated deployment scripts and configurations for better clarity and maintenance.
  • Added new utility functions for URL validation and external link handling.
  • Improved overall code organization and structure across multiple files.

The following files were skipped due to too many changes: contracts/src/token/Faucet.sol, web/src/components/DisputeFeatures/GroupsUI.tsx, contracts/scripts/populatePolicyRegistry.ts, web/src/pages/Resolver/NavigationButtons/NextButton.tsx, contracts/src/libraries/SafeERC20.sol, web/src/components/DisputePreview/DisputeContext.tsx, contracts/deploy/change-sortition-module-rng.ts, contracts/deploy/00-randomizer-rng.ts, contracts/src/proxy/KlerosProxies.sol, contracts/src/proxy/UUPSProxy.sol, web/src/context/NewDisputeContext.tsx, web/src/pages/Cases/CaseDetails/Voting/Classic/Reveal.tsx, contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol, contracts/deploy/00-chainlink-rng.ts, contracts/test/arbitration/dispute-kit-gated-shutter.ts, contracts/deploy/00-home-chain-arbitration-university.ts, contracts/test/integration/getContractsViem.test.ts, web/src/pages/Cases/CaseDetails/Timeline.tsx, contracts/deployments/contractsViem.ts, web/src/pages/Resolver/Briefing/Description.tsx, contracts/test/arbitration/ruler.ts, contracts/scripts/changeOwner.ts, web/src/pages/Resolver/Parameters/Court/index.tsx, contracts/src/arbitration/devtools/DisputeResolverRuler.sol, contracts/test/arbitration/draw.ts, contracts/src/gateway/interfaces/IForeignGateway.sol, contracts/package.json, contracts/deployments/disputeKitsViem.ts, contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol, contracts/deploy/upgrade-all.ts, contracts/src/arbitration/interfaces/IArbitrableV2.sol, contracts/deployments/contractsEthers.ts, contracts/scripts/populateCourts.ts, web/src/components/DisputeFeatures/Features/GatedErc20.tsx, web/src/pages/Profile/Stakes/Header.tsx, contracts/src/arbitration/view/KlerosCoreSnapshotProxy.sol, web/src/hooks/useVotingContext.tsx, contracts/test/integration/getContractsEthers.test.ts, contracts/src/proxy/UUPSProxiable.sol, contracts/src/arbitration/evidence/EvidenceModule.sol, web/src/components/DisputeFeatures/Features/GatedErc1155.tsx, contracts/src/arbitration/DisputeTemplateRegistry.sol, web/src/components/MarkdownEditor.tsx, web/src/components/ExternalLinkWarning.tsx, contracts/src/rng/RNGWithFallback.sol, contracts/src/arbitration/PolicyRegistry.sol, contracts/src/kleros-v1/kleros-liquid/KlerosLiquidToV2Governor.sol, contracts/test/foundry/KlerosCore_RNG.t.sol, contracts/test/proxy/index.ts, contracts/test/evidence/index.ts, contracts/README.md, web/src/hooks/useTokenAddressValidation.ts, contracts/deploy/00-home-chain-arbitration.ts, contracts/src/gateway/interfaces/IHomeGateway.sol, contracts/scripts/utils/contracts.ts, contracts/src/test/SortitionTreesMock.sol, web/src/pages/Resolver/Parameters/Court/FeatureSelection/index.tsx, contracts/src/rng/BlockhashRNG.sol, .github/workflows/contracts-testing.yml, contracts/src/arbitration/dispute-kits/DisputeKitGatedArgentinaConsumerProtection.sol, contracts/src/rng/RandomizerRNG.sol, contracts/test/integration/index.ts, web/src/components/MarkdownRenderer.tsx, contracts/src/arbitration/interfaces/IArbitratorV2.sol, web/src/consts/disputeFeature.ts, contracts/deploy/00-home-chain-arbitration-neo.ts, web/src/styles/mdxEditorTheme.ts, contracts/src/arbitration/dispute-kits/DisputeKitGated.sol, contracts/src/arbitration/arbitrables/DisputeResolver.sol, contracts/test/foundry/KlerosCore_Disputes.t.sol, contracts/CHANGELOG.md, contracts/src/gateway/ForeignGateway.sol, contracts/src/arbitration/devtools/KlerosCoreRuler.sol, contracts/test/foundry/KlerosCore_Initialization.t.sol, contracts/test/foundry/KlerosCore_TestBase.sol, contracts/src/libraries/SortitionTrees.sol, contracts/src/rng/ChainlinkConsumerBaseV2Plus.sol, contracts/src/arbitration/arbitrables/ArbitrableExample.sol, contracts/test/rng/index.ts, contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol, contracts/test/foundry/KlerosCore_Drawing.t.sol, contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol, contracts/src/rng/ChainlinkRNG.sol, contracts/src/gateway/HomeGateway.sol, contracts/src/arbitration/interfaces/ISortitionModule.sol, contracts/src/arbitration/interfaces/IDisputeKit.sol, contracts/test/arbitration/dispute-kit-gated.ts, contracts/test/arbitration/staking.ts, contracts/deployments/arbitrumSepoliaDevnet/DisputeTemplateRegistryUniversity_Proxy.json, contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol, contracts/src/arbitration/university/SortitionModuleUniversity.sol, contracts/test/foundry/KlerosCore_Governance.t.sol, contracts/deployments/arbitrumSepoliaDevnet/DisputeTemplateRegistryUniversity.json, contracts/src/arbitration/KlerosGovernor.sol, contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol, contracts/test/foundry/DisputeKitGatedArgentinaConsumerProtection_Drawing.t.sol, contracts/test/arbitration/staking-neo.ts, contracts/test/foundry/KlerosCore_Voting.t.sol, contracts/src/arbitration/SortitionModule.sol, contracts/deployments/arbitrumSepoliaDevnet/SortitionModuleUniversity_Proxy.json, contracts/test/arbitration/helpers/dispute-kit-gated-common.ts, contracts/deployments/arbitrumSepoliaDevnet/DisputeKitClassicUniversity_Proxy.json, contracts/test/sortition/index.ts, contracts/test/foundry/KlerosCore_Staking.t.sol, contracts/deployments/arbitrumSepoliaDevnet/KlerosCoreUniversity_Proxy.json, contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts, contracts/deployments/arbitrumSepoliaDevnet/SortitionModuleUniversity.json, contracts/deployments/arbitrumSepoliaDevnet/DisputeKitClassicUniversity.json, contracts/test/foundry/KlerosCore_Execution.t.sol, contracts/deployments/arbitrumSepoliaDevnet/KlerosCoreUniversity.json, contracts/src/arbitration/university/KlerosCoreUniversity.sol, contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol, contracts/deployments/arbitrumSepoliaDevnet/DisputeTemplateRegistryUniversity_Implementation.json, contracts/src/arbitration/KlerosCore.sol, contracts/deployments/arbitrum.ts, contracts/deployments/arbitrumSepoliaDevnet.ts, contracts/deployments/arbitrumSepoliaDevnet/DisputeResolverUniversity.json, contracts/test/foundry/KlerosCore_Appeals.t.sol, contracts/deployments/mainnet.viem.ts, contracts/deployments/devnet.viem.ts, contracts/audit/METRICS.md, yarn.lock, contracts/deployments/arbitrumSepoliaDevnet/SortitionModuleUniversity_Implementation.json, contracts/deployments/arbitrumSepoliaDevnet/KlerosCoreUniversity_Implementation.json, contracts/deployments/arbitrumSepoliaDevnet/DisputeKitClassicUniversity_Implementation.json

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

Summary by CodeRabbit

  • New Features

    • Multiple dispute‑kit support, RNG with fallback, on‑chain token‑gate checks, juror rewards staked/penalized on‑chain, leftover PNK withdrawal.
  • Changes

    • UI: new Stakes header, friendlier card labels, thousands formatting, revised timeline/voting flows; resolver validates gated tokens; improved shutter UI/flows.
  • Bug Fixes

    • Abort shutter commits when API missing; skip zero‑amount transfers/events; safer reward/withdraw handling.
  • Documentation

    • Added llms.txt, X‑Robots‑Tag header, and contract doc build/serve scripts.
  • Tests

    • Expanded RNG, staking, drawing, voting and appeals coverage.
  • Chores

    • Tooling and compiler upgrades; CI/test workflow simplifications.

jaybuidl and others added 23 commits August 5, 2025 13:24
Feat: Dispute Kits helper in contracts package
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Enabled Hardhat viaIR compilation with solc v0.8.30
chore: viaIR compilation enabled for Foundry with explicit solc v0.8.30
@netlify
Copy link

netlify bot commented Aug 8, 2025

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit 877ed81
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/68ffa8c3a8bf4b0008528eda
😎 Deploy Preview https://deploy-preview-2076--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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between c40c12c and 877ed81.

📒 Files selected for processing (46)
  • contracts/deploy/00-home-chain-arbitration-mainnet.ts (7 hunks)
  • contracts/deploy/00-home-chain-arbitration-university.ts (4 hunks)
  • contracts/deploy/00-home-chain-arbitration.ts (5 hunks)
  • contracts/hardhat.config.ts (2 hunks)
  • contracts/src/arbitration/DisputeTemplateRegistry.sol (3 hunks)
  • contracts/src/arbitration/KlerosCore.sol (4 hunks)
  • contracts/src/arbitration/KlerosGovernor.sol (11 hunks)
  • contracts/src/arbitration/PolicyRegistry.sol (4 hunks)
  • contracts/src/arbitration/SortitionModule.sol (2 hunks)
  • contracts/src/arbitration/arbitrables/ArbitrableExample.sol (5 hunks)
  • contracts/src/arbitration/arbitrables/DisputeResolver.sol (6 hunks)
  • contracts/src/arbitration/devtools/DisputeResolverRuler.sol (4 hunks)
  • contracts/src/arbitration/devtools/KlerosCoreRuler.sol (12 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol (2 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (21 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (4 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGatedArgentinaConsumerProtection.sol (1 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (6 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (3 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol (4 hunks)
  • contracts/src/arbitration/evidence/EvidenceModule.sol (3 hunks)
  • contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol (22 hunks)
  • contracts/src/arbitration/interfaces/IArbitrableV2.sol (1 hunks)
  • contracts/src/arbitration/interfaces/IArbitratorV2.sol (3 hunks)
  • contracts/src/arbitration/interfaces/IDisputeKit.sol (3 hunks)
  • contracts/src/arbitration/interfaces/IDisputeTemplateRegistry.sol (2 hunks)
  • contracts/src/arbitration/university/KlerosCoreUniversity.sol (39 hunks)
  • contracts/src/arbitration/university/SortitionModuleUniversity.sol (13 hunks)
  • contracts/src/gateway/ForeignGateway.sol (10 hunks)
  • contracts/src/gateway/HomeGateway.sol (9 hunks)
  • contracts/src/gateway/interfaces/IForeignGateway.sol (2 hunks)
  • contracts/src/gateway/interfaces/IHomeGateway.sol (1 hunks)
  • contracts/src/libraries/Constants.sol (2 hunks)
  • contracts/src/libraries/SafeERC20.sol (3 hunks)
  • contracts/src/proxy/KlerosProxies.sol (2 hunks)
  • contracts/src/rng/BlockhashRNG.sol (1 hunks)
  • contracts/src/rng/IncrementalNG.sol (1 hunks)
  • contracts/src/rng/RNGWithFallback.sol (1 hunks)
  • contracts/src/rng/RandomizerRNG.sol (5 hunks)
  • contracts/src/test/DisputeKitClassicMockUncheckedNextRoundSettings.sol (1 hunks)
  • contracts/src/test/DisputeKitGatedMock.sol (1 hunks)
  • contracts/src/test/DisputeKitGatedShutterMock.sol (1 hunks)
  • contracts/src/test/KlerosCoreMock.sol (1 hunks)
  • contracts/src/test/RNGMock.sol (1 hunks)
  • contracts/src/test/SortitionModuleMock.sol (1 hunks)
  • contracts/src/test/SortitionTreesMock.sol (1 hunks)
 _________________________________________________
< Let’s pair program: you type, I judge lovingly. >
 -------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

Walkthrough

Large refactor: contracts, deployments, tests and frontend updated for v0.13.0 — ownership and error-model changes, IRNG + RNGWithFallback, SortitionTrees extraction, SortitionModule/KlerosCore rewrites, dispute‑kit API expansions, tooling/deployment updates and many test additions/adjustments.

Changes

Cohort / File(s) Summary
Core rewrite & university
contracts/src/arbitration/KlerosCore.sol, contracts/src/arbitration/KlerosCoreUniversity.sol, contracts/src/arbitration/KlerosCoreBase.sol (removed), contracts/src/arbitration/devtools/*, contracts/deploy/*, contracts/deployments/...
Reimplement core as owner-led UUPS: new structs/events (JurorRewardPenalty/LeftoverRewardSent), owner/guardian rename, juror NFT support, multi-token fees, dispute‑kit/court jumps, storage gaps, expanded ABI and deployment artifacts.
Sortition library & module
contracts/src/libraries/SortitionTrees.sol, contracts/src/arbitration/SortitionModule.sol, contracts/src/arbitration/SortitionModuleBase.sol (removed), contracts/src/test/SortitionTreesMock.sol, contracts/src/test/SortitionModuleMock.sol
Extract SortitionTrees (uint96 courtIDs), rewrite SortitionModule as upgradeable with phases, delayed stakes, penalty/reward APIs, forced unstake, leftover PNK withdrawal; draw() now returns (address, fromSubcourtID).
RNG infrastructure
contracts/src/rng/IRNG.sol, contracts/src/rng/RNGWithFallback.sol, BlockhashRNG.sol, ChainlinkRNG.sol, RandomizerRNG.sol, IncrementalNG.sol, contracts/src/test/RNGMock.sol
Add IRNG (no-arg interface) and RNGWithFallback (primary RNG + blockhash fallback on timeout), owner/consumer controls; adapt existing RNGs and add RNGMock for tests.
Dispute kits & voting
contracts/src/arbitration/dispute-kits/*, contracts/src/arbitration/interfaces/IDisputeKit.sol, DisputeKitClassicBase.sol
Expand IDisputeKit (draw returns fromSubcourtID, reward/penalty/coherence APIs, round/vote queries), add jumpDisputeKitID and storage gaps; Shutter/Gated kits add justification/recovery flows and change commit/vote signatures.
Governance & owner rename
contracts/src/**, contracts/scripts/changeOwner.ts (added), many scripts updated (removed changeGovernor task)
Mass rename governor→owner across contracts, modifiers and initializers; replace string require()s with custom errors; add change-owner Hardhat task; update _authorizeUpgrade to owner-only.
Interfaces, constants & removed libs
contracts/src/arbitration/interfaces/*.sol, contracts/src/libraries/Constants.sol, contracts/src/libraries/CappedMath.sol (removed)
Broaden pragmas to >=0.8.0 <0.9.0, add ONE_BASIS_POINT, update many interfaces (IArbitrableV2, IArbitratorV2, ISortitionModule, etc.), remove CappedMath and replace with direct arithmetic.
Deployments & tooling
contracts/deploy/*, contracts/deployments/*, contracts/deployments/disputeKitsViem.ts, contracts/scripts/getDisputeKits.ts, package.json
Consolidate Neo → mainnet naming, add University DisputeTemplateRegistry proxy/impl, wire RNGWithFallback into deploys, add Viem dispute-kit helper and script, add viem peerDependency and forge doc scripts, update typed getContract usage.
Tests (Hardhat & Foundry)
contracts/test/**, contracts/test/foundry/**
New Foundry suites added; Hardhat tests migrated to typed ethers.getContract<T>, adapted to owner-errors, IRNG and sortition API changes, removed RNG lookahead mining, updated expected events and flows.
Frontend & UX
web/src/hooks/*, web/src/pages/*, web/src/utils/extradataToTokenInfo.ts, web/src/components/*, web/netlify.toml, web/src/public/llms.txt
Add token-address validation hooks (ERC20/ERC721/ERC1155), gate validation UI for Gated kits, Shutter env guard, Reveal RFA fallback, voting context multi-DK support, timeline refactor to currentPeriodIndex, copy/format tweaks, Netlify header and llms.txt.
Build & config
contracts/foundry.toml, foundry.toml, contracts/hardhat.config.ts, remappings.txt, contracts/.solcover.js, .github/workflows/contracts-testing.yml
Bump solc to 0.8.30, enable viaIR, adjust optimizer runs, add foundry profile and remappings, set solcover irMinimum, simplify CI to Hardhat-focused workflow.
Library & test helpers
contracts/src/test/SortitionTreesMock.sol, contracts/src/test/DisputeKitGatedMock.sol, contracts/src/test/DisputeKitGatedShutterMock.sol
New mocks/wrappers to exercise SortitionTrees and dispute-kit extraData parsing; expose helpers for unit tests.
Misc & cleanup
deleted Neo-specific files/proxies, updated contracts/src/proxy/*, scripts (contracts/scripts/*), metrics scripts
Remove deprecated Neo variants and deploy scripts, add metrics generation script, shell/lint fixes, UI component swaps (Profile Stakes/Header added, Courts Header removed).

Sequence Diagram(s)

%%{init: {"theme":"base","themeVariables":{"actorBorder":"#2b2b2b","actorBg":"#f7fafc","noteBg":"#f0f4f8"}}}%%
sequenceDiagram
  autonumber
  participant SM as SortitionModule
  participant RF as RNGWithFallback
  participant PR as PrimaryRNG
  participant BH as BlockhashRNG

  SM->>RF: requestRandomness()
  RF->>PR: requestRandomness() (forward to primary)
  RF->>RF: record requestTimestamp

  Note over SM,RF: when drawing / receiving randomness
  SM->>RF: receiveRandomness()
  alt Primary RNG returns value
    RF-->>SM: primary random
  else Primary RNG timed out or returns 0
    RF->>BH: compute fallback from recent blockhash
    RF-->>SM: fallback random (emit RNGFallback)
  end
  SM->>SM: draw(courtID, disputeID, nonce)
  SM-->>Caller: (drawnAddress, fromSubcourtID)
Loading
%%{init: {"theme":"base","themeVariables":{"actorBorder":"#2b2b2b","actorBg":"#f7fafc","noteBg":"#f0f4f8"}}}%%
sequenceDiagram
  autonumber
  participant User
  participant Core as KlerosCore
  participant DK as IDisputeKit
  participant SM as SortitionModule

  User->>Core: createDispute(... + fee)
  Core->>DK: getNbVotesAfterAppeal(prevDK, currentNbVotes)
  DK-->>Core: nbVotesNext
  Core->>DK: earlyCourtJump(coreDisputeID)?
  alt DisputeKit requests jump
    Core->>Core: select new court & dispute kit (getJumpDisputeKitID)
    Core-->>User: emit CourtJump / DisputeKitJump
  else normal path
    Core->>SM: schedule postDraw actions
  end
  Note over Core,DK: subsequent draw/appeal/execution flows use updated signatures and storage gaps
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150+ minutes

Possibly related PRs

Suggested labels

Package: Contracts

Poem

"I nibble code beneath the moonlit log,
I hop through trees of stakes and fog.
Owners tend the carrot chest,
RNG falls back when tests request.
A rabbit cheers — new roots, no slog!" 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title "Release" is too generic for this large, multi-area refactor and does not summarize the primary changes reviewers need to know (e.g., governor→owner rename, IRNG/RNGWithFallback, dispute-kit API updates, tooling and version bumps). Although it indicates a release, it lacks specificity about scope or breaking changes, so a teammate scanning history cannot quickly understand the PR intent. Please update the title to a concise, specific sentence that highlights the main change and at least one key area of impact; for example: "Refactor: rename governor→owner, introduce IRNG and RNGWithFallback, and update dispute-kit APIs" or "Release: bump contracts to 0.13.0 — governance rename, RNG refactor, dispute-kit API changes".
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

@netlify
Copy link

netlify bot commented Aug 8, 2025

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

Name Link
🔨 Latest commit 877ed81
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-university/deploys/68ffa8c3269eed000803cc25

@netlify
Copy link

netlify bot commented Aug 8, 2025

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

Name Link
🔨 Latest commit 877ed81
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo-devtools/deploys/68ffa8c3412ac4000814644c

@netlify
Copy link

netlify bot commented Aug 8, 2025

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

Name Link
🔨 Latest commit 877ed81
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet-devtools/deploys/68ffa8c35ad39a0008d62ee9

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: 7

🧹 Nitpick comments (4)
contracts/hardhat.config.ts (1)

29-35: Verify compile time and bytecode size with viaIR and 10000 runs

The size‐contracts check failed (missing node_modules), and the compile step reported only 1.26 s (likely no real work). Please run the following under contracts/ and share the results to ensure there are no regressions:

  • Install dependencies:
    yarn install
  • Measure bytecode sizes without recompiling:
    yarn hardhat size-contracts --no-compile
  • Clean compile and time it:
    rm -rf cache artifacts
    TIMEFORMAT='compile took %R sec' time yarn hardhat compile

If you observe significant increases in compile times or bytecode sizes (EIP-170 limit concerns), consider lowering optimizer runs (e.g., 200–2000).

contracts/package.json (1)

160-161: Question: is isomorphic-fetch needed on Node 20?

Node 20 has stable global fetch. Adding isomorphic-fetch may be redundant and can introduce polyfill conflicts in some runtimes.

If not strictly needed for browser bundles, consider removing:

   "dependencies": {
     "@chainlink/contracts": "^1.4.0",
     "@kleros/vea-contracts": "^0.7.0",
     "@openzeppelin/contracts": "^5.4.0",
     "@shutter-network/shutter-sdk": "0.0.2",
-    "isomorphic-fetch": "^3.0.0"
   },

If needed in specific scripts, prefer local import over global polyfill.

contracts/CHANGELOG.md (1)

7-15: Mark unreleased changes as “Unreleased” to avoid confusion with published versions.

Labeling 0.13.0 as “Not published yet” while giving it a version can confuse consumers and tools.

Consider:

-## [0.13.0] - 2025-08-07 (Not published yet)
+## [Unreleased]

Move these entries under Unreleased until the tag is published and the package version is bumped.

web/src/public/llms.txt (1)

6-8: Improve sentence structure to avoid repetitive beginnings.

The descriptions are informative, but three consecutive sentences start with "Kleros" which affects readability.

Consider rewording for better flow:

- [Kleros Dispute Cases](https://v2.kleros.builders/#/cases/display/1/desc/all): Provide a platform for viewing and managing decentralized dispute resolution cases.
- [Kleros Decentralized Courts](https://v2.kleros.builders/#/courts): Facilitate decentralized dispute resolution by allowing users to stake tokens, participate as jurors, and view court cases.
- [Kleros Jurors Leaderboard](https://v2.kleros.builders/#/jurors/1/desc/all): Display ranking and statistics of jurors based on coherent voting and rewards in the Kleros decentralized arbitration system.
+ [Kleros Dispute Cases](https://v2.kleros.builders/#/cases/display/1/desc/all): Comprehensive platform for viewing and managing decentralized dispute resolution cases.
+ [Decentralized Courts](https://v2.kleros.builders/#/courts): Enable decentralized dispute resolution by allowing users to stake tokens, participate as jurors, and view court cases.
+ [Jurors Leaderboard](https://v2.kleros.builders/#/jurors/1/desc/all): Rankings and statistics of jurors based on coherent voting and rewards in the decentralized arbitration system.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f979978 and 9140890.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • contracts/CHANGELOG.md (2 hunks)
  • contracts/deployments/disputeKitsViem.ts (1 hunks)
  • contracts/deployments/index.ts (1 hunks)
  • contracts/foundry.toml (1 hunks)
  • contracts/hardhat.config.ts (1 hunks)
  • contracts/package.json (4 hunks)
  • contracts/scripts/getDisputeKits.ts (1 hunks)
  • package.json (1 hunks)
  • web/netlify.toml (1 hunks)
  • web/src/public/llms.txt (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-05-23T17:47:39.947Z
Learnt from: kemuru
PR: kleros/kleros-v2#1994
File: web/vite.config.js:26-33
Timestamp: 2025-05-23T17:47:39.947Z
Learning: In Yarn workspace setups, dependencies defined in individual workspace package.json files (like web/package.json) are typically hoisted to the root node_modules directory. This means vite config paths should point to "../node_modules" from workspace directories to access hoisted dependencies, not to local workspace node_modules.

Applied to files:

  • package.json
📚 Learning: 2024-11-21T23:16:14.816Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1757
File: prettier-config/package.json:7-7
Timestamp: 2024-11-21T23:16:14.816Z
Learning: As of November 2024, ESLint v9.15.0 has been released and is acceptable to use in the project.

Applied to files:

  • contracts/hardhat.config.ts
📚 Learning: 2024-11-21T23:20:28.163Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1757
File: web-devtools/package.json:36-40
Timestamp: 2024-11-21T23:20:28.163Z
Learning: TypeScript-ESLint v8.15.0 is compatible with both ESLint v8 and v9.

Applied to files:

  • contracts/hardhat.config.ts
📚 Learning: 2024-10-14T13:58:25.708Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1703
File: web/src/hooks/queries/usePopulatedDisputeData.ts:58-61
Timestamp: 2024-10-14T13:58:25.708Z
Learning: In `web/src/hooks/queries/usePopulatedDisputeData.ts`, the query and subsequent logic only execute when `disputeData.dispute?.arbitrableChainId` and `disputeData.dispute?.externalDisputeId` are defined, so `initialContext` properties based on these values are safe to use without additional null checks.

Applied to files:

  • contracts/scripts/getDisputeKits.ts
  • contracts/deployments/disputeKitsViem.ts
📚 Learning: 2024-11-19T05:31:48.701Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1744
File: web/src/hooks/useGenesisBlock.ts:9-31
Timestamp: 2024-11-19T05:31:48.701Z
Learning: In `useGenesisBlock.ts`, within the `useEffect` hook, the conditions (`isKlerosUniversity`, `isKlerosNeo`, `isTestnetDeployment`) are mutually exclusive, so multiple imports won't execute simultaneously, and race conditions are not a concern.

Applied to files:

  • contracts/scripts/getDisputeKits.ts
📚 Learning: 2024-10-24T08:16:02.749Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1703
File: kleros-sdk/src/requests/gqlClient.ts:18-18
Timestamp: 2024-10-24T08:16:02.749Z
Learning: In this TypeScript project, when a file (such as `kleros-sdk/src/requests/gqlClient.ts`) exports only a single entity, it's acceptable to use default exports instead of named exports.

Applied to files:

  • contracts/scripts/getDisputeKits.ts
  • contracts/deployments/index.ts
  • contracts/deployments/disputeKitsViem.ts
📚 Learning: 2024-10-22T09:38:20.093Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1703
File: kleros-sdk/src/dataMappings/utils/actionTypes.ts:1-1
Timestamp: 2024-10-22T09:38:20.093Z
Learning: In the TypeScript file `kleros-sdk/src/dataMappings/utils/actionTypes.ts`, the `Abi` type is parsed later in the action functions, so importing `Abi` from `viem` in this file is unnecessary.

Applied to files:

  • contracts/deployments/index.ts
  • contracts/deployments/disputeKitsViem.ts
📚 Learning: 2024-10-28T05:55:12.728Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1716
File: web-devtools/src/app/(main)/dispute-template/CustomContextInputs.tsx:29-42
Timestamp: 2024-10-28T05:55:12.728Z
Learning: In the `CustomContextInputs` component located at `web-devtools/src/app/(main)/dispute-template/CustomContextInputs.tsx`, the `DisputeRequestParams` array is used to exclude certain variables from the custom input since they are already provided in a preceding component. Therefore, converting it to a type is unnecessary.

Applied to files:

  • contracts/deployments/index.ts
  • contracts/deployments/disputeKitsViem.ts
📚 Learning: 2025-01-23T08:14:47.397Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1846
File: prettier-config/index.js:26-26
Timestamp: 2025-01-23T08:14:47.397Z
Learning: The prettier-plugin-solidity plugin is installed in the kleros-v2 repository, even though it's not visible in the sandbox environment's node_modules (which is expected as node_modules is not committed to the repository).

Applied to files:

  • contracts/package.json
📚 Learning: 2025-05-15T06:50:45.650Z
Learnt from: tractorss
PR: kleros/kleros-v2#1982
File: web/src/pages/Resolver/Landing/index.tsx:0-0
Timestamp: 2025-05-15T06:50:45.650Z
Learning: In the Kleros V2 codebase, it's acceptable to use ESLint disable comments for dependency arrays in useEffect hooks when including certain dependencies (like state that is being updated within the effect) would cause infinite loops.

Applied to files:

  • contracts/package.json
📚 Learning: 2025-05-23T17:47:39.947Z
Learnt from: kemuru
PR: kleros/kleros-v2#1994
File: web/vite.config.js:26-33
Timestamp: 2025-05-23T17:47:39.947Z
Learning: The viteStaticCopy plugin configuration in web/vite.config.js correctly copies Shutter SDK files from the installed node_modules location. The path resolve(__dirname, "../node_modules/shutter-network/shutter-sdk/dist/*") works when dependencies are properly installed, despite initial analysis suggesting otherwise due to sandbox environment limitations.

Applied to files:

  • contracts/package.json
📚 Learning: 2024-10-22T10:23:15.789Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1703
File: kleros-sdk/src/sdk.ts:1-3
Timestamp: 2024-10-22T10:23:15.789Z
Learning: In `kleros-sdk/src/sdk.ts`, the `PublicClient` type is used and should not be flagged as unused.

Applied to files:

  • contracts/deployments/disputeKitsViem.ts
🪛 LanguageTool
web/src/public/llms.txt

[style] ~7-~7: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...entralized dispute resolution cases. - [Kleros Decentralized Courts](https://v2.kleros...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~8-~8: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ate as jurors, and view court cases. - [Kleros Jurors Leaderboard](https://v2.kleros.b...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: contracts-testing
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
🔇 Additional comments (11)
contracts/package.json (3)

3-3: Version bump looks good.

0.12.0 aligns with the new peer requirement and helper additions.


157-157: OK to bump @kleros/vea-contracts to ^0.7.0.

Matches the Hardhat/Foundry upgrades and related changes.


124-124: Verify Hardhat Plugin Compatibility with v2.26.2
Ensure all Hardhat plugins in contracts/package.json (lines 135–142) are compatible with Hardhat v2.26.2:

  • hardhat-contract-sizer (l.135)
  • hardhat-deploy (l.136)
  • hardhat-deploy-ethers (l.137)
  • hardhat-deploy-tenderly (l.138)
  • hardhat-docgen (l.139)
  • hardhat-gas-reporter (l.140)
  • hardhat-tracer (l.141)
  • hardhat-watcher (l.142)

If your CI build has passed, you can safely ignore this check.

contracts/foundry.toml (2)

3-7: LGTM! Solidity 0.8.30 upgrade with IR pipeline is well-configured.

The compiler upgrade to 0.8.30 with IR compilation enabled aligns with the coordinated changes across build tools. The optimizer settings with 500 runs and Yul stack allocation are appropriate for production builds.


8-13: Good approach to handle IR compilation compatibility issues.

The test profile and compilation restrictions provide flexibility for files that may have issues with IR compilation. This is a pragmatic solution for the transition period.

contracts/deployments/index.ts (1)

21-22: LGTM! Clean integration of dispute kits functionality.

The export structure follows established patterns and provides a clear API for the new Viem-based dispute kits functionality. The type exports and function aliasing are consistent with the module's existing approach.

contracts/scripts/getDisputeKits.ts (3)

5-13: LGTM! Good environment variable validation and client setup.

The environment variable check and Viem client configuration are properly implemented with appropriate error handling.


26-33: LGTM! Clean script execution pattern.

The main function execution pattern with proper process exit handling follows Node.js best practices.


15-24: No action needed: “devnet” deployment exists for Arbitrum Sepolia
Verified that contracts/deployments/arbitrumSepoliaDevnet.ts defines the “devnet” deployment on Arbitrum Sepolia, matching the RPC endpoint used in getDisputeKits(client, "devnet").

contracts/deployments/disputeKitsViem.ts (2)

5-18: LGTM! Well-structured type definitions.

The type definitions properly model dispute kit contracts and their metadata. The union type for DisputeKit correctly handles the nullable case, and the exported types provide a clean API surface.


66-85: LGTM! Robust contract matching logic.

The address comparison with case normalization and the use of satisfies for type checking demonstrate good defensive programming practices. The logic correctly identifies gated and shutter variants.

Comment on lines +129 to +130
[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
[0.12.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid adding a release link for an unpublished tag.

If 0.13.0 isn’t published, this link will 404. Add it only when the release is live.

-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
+<!-- [0.13.0]: (add after publishing) -->

Optionally confirm release existence before merging:


🏁 Script executed:

#!/bin/bash
# Prints 200 if the tag exists, otherwise likely 404
curl -s -o /dev/null -w "%{http_code}\n" \
"https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]"

Length of output: 132


Remove unpublished release link for v0.13.0 in CHANGELOG.md

The 0.13.0 release tag currently returns 404, so we should not include its link until the release is live.

• File: contracts/CHANGELOG.md
Lines: 129–130

Recommended change:

-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
+<!-- [0.13.0]: (add after publishing) -->
📝 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
[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
[0.12.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
<!-- [0.13.0]: (add after publishing) -->
[0.12.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
🤖 Prompt for AI Agents
In contracts/CHANGELOG.md at lines 129 to 130, the release link for version
0.13.0 leads to a 404 error because the release is not yet published. Remove or
comment out the line containing the 0.13.0 release link to prevent broken links
in the changelog until the release is officially live.

Comment on lines +20 to +30
const fetchDisputeKits = async (client: PublicClient, klerosCoreAddress: `0x${string}`, klerosCoreAbi: Abi) => {
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const logs = await client.getLogs({
address: klerosCoreAddress,
event: DisputeKitCreated,
fromBlock: 0n,
toBlock: "latest",
});
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 performance implications of unbounded block range.

Fetching events from block 0 to latest will become increasingly expensive as the chain grows. Consider adding block range parameters or caching mechanisms for production use.

-const fetchDisputeKits = async (client: PublicClient, klerosCoreAddress: `0x${string}`, klerosCoreAbi: Abi) => {
+const fetchDisputeKits = async (
+  client: PublicClient, 
+  klerosCoreAddress: `0x${string}`, 
+  klerosCoreAbi: Abi,
+  fromBlock: bigint = 0n
+) => {
   const DisputeKitCreated = getAbiItem({
     abi: klerosCoreAbi,
     name: "DisputeKitCreated",
   }) as AbiEvent;
   const logs = await client.getLogs({
     address: klerosCoreAddress,
     event: DisputeKitCreated,
-    fromBlock: 0n,
+    fromBlock,
     toBlock: "latest",
   });
📝 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
const fetchDisputeKits = async (client: PublicClient, klerosCoreAddress: `0x${string}`, klerosCoreAbi: Abi) => {
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const logs = await client.getLogs({
address: klerosCoreAddress,
event: DisputeKitCreated,
fromBlock: 0n,
toBlock: "latest",
});
const fetchDisputeKits = async (
client: PublicClient,
klerosCoreAddress: `0x${string}`,
klerosCoreAbi: Abi,
fromBlock: bigint = 0n
) => {
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const logs = await client.getLogs({
address: klerosCoreAddress,
event: DisputeKitCreated,
fromBlock,
toBlock: "latest",
});
// ...rest of the implementation
};
🤖 Prompt for AI Agents
In contracts/deployments/disputeKitsViem.ts around lines 20 to 30, the event log
fetching uses an unbounded block range from 0 to latest, which can cause
performance issues as the blockchain grows. Modify the function to accept
optional fromBlock and toBlock parameters to limit the block range for fetching
logs, or implement caching to avoid repeated full-range queries. Update the
client.getLogs call to use these parameters instead of fixed values.

Comment on lines +21 to +24
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
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

Unsafe type assertion - add runtime validation.

The type assertion as AbiEvent on line 24 could fail if the ABI doesn't contain the expected event structure.

-  const DisputeKitCreated = getAbiItem({
-    abi: klerosCoreAbi,
-    name: "DisputeKitCreated",
-  }) as AbiEvent;
+  const DisputeKitCreated = getAbiItem({
+    abi: klerosCoreAbi,
+    name: "DisputeKitCreated",
+  });
+  
+  if (!DisputeKitCreated || DisputeKitCreated.type !== 'event') {
+    throw new Error('DisputeKitCreated event not found in ABI');
+  }
📝 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
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
});
if (!DisputeKitCreated || DisputeKitCreated.type !== 'event') {
throw new Error('DisputeKitCreated event not found in ABI');
}
🤖 Prompt for AI Agents
In contracts/deployments/disputeKitsViem.ts around lines 21 to 24, the code uses
a direct type assertion 'as AbiEvent' which can be unsafe if the ABI item does
not match the expected event structure. To fix this, add runtime validation to
check that the returned object from getAbiItem has the required properties of an
AbiEvent before asserting its type. If the validation fails, handle the error
appropriately instead of blindly asserting the type.

Comment on lines +31 to +49
return Object.fromEntries(
logs
.filter((log) => {
const args = log.args as Record<string, unknown>;
return "_disputeKitID" in args && "_disputeKitAddress" in args;
})
.map((log) => {
const { _disputeKitID, _disputeKitAddress } = log.args as {
_disputeKitID: bigint;
_disputeKitAddress: string;
};
return {
disputeKitID: _disputeKitID,
disputeKitAddress: _disputeKitAddress,
};
})
.map(({ disputeKitID, disputeKitAddress }) => [disputeKitID!.toString(), disputeKitAddress as `0x${string}`])
);
};
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

Add error handling for blockchain interactions.

The function lacks error handling for potential RPC failures or malformed event data.

Wrap the blockchain interaction in try-catch:

+  try {
     const logs = await client.getLogs({
       address: klerosCoreAddress,
       event: DisputeKitCreated,
       fromBlock: 0n,
       toBlock: "latest",
     });
+  } catch (error) {
+    throw new Error(`Failed to fetch DisputeKitCreated events: ${error}`);
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contracts/deployments/disputeKitsViem.ts around lines 31 to 49, the code
processes blockchain event logs without any error handling, which risks
unhandled exceptions from RPC failures or malformed data. Wrap the entire log
processing logic inside a try-catch block to catch and handle any errors
gracefully. In the catch block, log or rethrow the error with a clear message to
aid debugging and prevent the application from crashing unexpectedly.

Comment on lines +162 to 169
"peerDependencies": {
"viem": "^2.24.1"
},
"peerDependenciesMeta": {
"viem": {
"optional": false
}
}
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

Peer dependency viem ^2.24.1 conflicts with root resolution (^2.23.2).

This will produce peer warnings/errors and potentially divergent behavior across workspaces.

Either relax the peer to match the resolution, or better, update the root resolution to satisfy the peer (recommended):

Preferred (root package.json):

-    "viem@npm:2.x": "npm:^2.23.2"
+    "viem@npm:2.x": "npm:^2.24.1"

If you must stay on 2.23.x, change this package’s peer to:

-    "viem": "^2.24.1"
+    "viem": "^2.23.0"

…but this may block newer features you rely on.

📝 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
"peerDependencies": {
"viem": "^2.24.1"
},
"peerDependenciesMeta": {
"viem": {
"optional": false
}
}
"peerDependencies": {
- "viem": "^2.24.1"
+ "viem": "^2.23.0"
},
"peerDependenciesMeta": {
"viem": {
"optional": false
}
}
🤖 Prompt for AI Agents
In contracts/package.json around lines 162 to 169, the peer dependency version
for "viem" (^2.24.1) conflicts with the root workspace resolution (^2.23.2),
causing warnings and potential inconsistent behavior. To fix this, update the
root package.json to use "viem" version ^2.24.1 to match the peer dependency, or
alternatively, relax the peer dependency in this package.json to ^2.23.2 if you
must stay on that version, understanding this may limit access to newer
features.

Comment on lines +80 to +81
"undici@npm:7.3.0": "npm:7.5.0",
"viem@npm:2.x": "npm:^2.23.2"
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Align viem resolution with contracts’ peer dependency to avoid peer conflicts.

Root resolution pins viem 2.x to ^2.23.2, while contracts require viem ^2.24.1 as a peer. This will trigger peer resolution warnings/errors in Yarn 4 and could break consumers.

Update the resolution to satisfy the peer:

   "resolutions": {
     ...
-    "viem@npm:2.x": "npm:^2.23.2"
+    "viem@npm:2.x": "npm:^2.24.1"
   },

To check for other mismatches:


🏁 Script executed:

#!/bin/bash
rg -n --json '"viem"\s*:\s*"' | jq -r '.data.path.text + ":" + .data.line_number|tostring + " " + .data.lines.text'

Length of output: 1417


🏁 Script executed:

#!/bin/bash
# Search for all references to “viem” in any JSON file to spot mismatched pins
rg -n '"viem"' -g '*.json'

Length of output: 440


Update viem resolution to match peerDependency (^2.24.1)

All of your sub-packages (contracts, web, kleros-sdk, web-devtools, kleros-app) declare viem at ^2.24.1, but the root resolutions entry still pins viem@npm:2.x to ^2.23.2. This mismatch will trigger peer warnings/errors in Yarn 4 and may break consumers.

• File: package.json (root)
• Change the resolutions.viem@npm:2.x entry

  "resolutions": {
    …
-   "viem@npm:2.x": "npm:^2.23.2"
+   "viem@npm:2.x": "npm:^2.24.1"
  },
📝 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
"undici@npm:7.3.0": "npm:7.5.0",
"viem@npm:2.x": "npm:^2.23.2"
"resolutions": {
"undici@npm:7.3.0": "npm:7.5.0",
"viem@npm:2.x": "npm:^2.24.1"
},
🤖 Prompt for AI Agents
In package.json around lines 80 to 81, the resolutions entry for viem is set to
^2.23.2, which conflicts with the peerDependency version ^2.24.1 declared in
sub-packages. Update the resolutions.viem@npm:2.x entry to ^2.24.1 to align with
the peerDependency version and prevent peer warnings or errors in Yarn 4.

Comment on lines +13 to +16
[[headers]]
for = "/*"
[headers.values]
X-Robots-Tag = "llms-txt"
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Header value likely incorrect; consider advertising llms.txt via Link header instead of X-Robots-Tag.

X-Robots-Tag expects crawler directives (e.g., noindex, noai). Using "llms-txt" here is non-standard and probably ignored. To advertise a policy file, prefer a Link header (and ensure the file is served at the expected path).

Apply one of these:

Option A — advertise a policy file:

 [[headers]]
   for = "/*"
   [headers.values]
-    X-Robots-Tag = "llms-txt"
+    Link = "</llms.txt>; rel=\"ai-policy\""

Option B — if you meant to control AI crawlers:

 [[headers]]
   for = "/*"
   [headers.values]
-    X-Robots-Tag = "llms-txt"
+    X-Robots-Tag = "noai, noimageai"

Also verify the file is published at the root (e.g., web/public/llms.txt → https://site/llms.txt), not under src.

Run to locate the file and confirm its publish path:


🏁 Script executed:

#!/bin/bash
fd -HI --glob "llms.txt"

Length of output: 48


Misuse of X-Robots-Tag; advertise AI policy via Link header

  • The current X-Robots-Tag = "llms-txt" is non-standard and will be ignored by crawlers.
  • To expose llms.txt as your AI policy, use a Link header with rel="ai-policy".
  • If you actually intend to block AI crawlers or image-AI, use valid X-Robots-Tag directives (e.g., noai, noimageai).
  • We located your policy file at web/src/public/llms.txt. Make sure it’s published at the site root (https:///llms.txt)—either move it to web/public/llms.txt or adjust your Netlify publish directory.

Option A — advertise your policy file via Link header:

 [[headers]]
   for = "/*"
   [headers.values]
-    X-Robots-Tag = "llms-txt"
+    Link = "</llms.txt>; rel=\"ai-policy\""

Option B — apply valid X-Robots-Tag directives:

 [[headers]]
   for = "/*"
   [headers.values]
-    X-Robots-Tag = "llms-txt"
+    X-Robots-Tag = "noai, noimageai"
📝 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
[[headers]]
for = "/*"
[headers.values]
X-Robots-Tag = "llms-txt"
[[headers]]
for = "/*"
[headers.values]
Link = "</llms.txt>; rel=\"ai-policy\""
🤖 Prompt for AI Agents
In web/netlify.toml around lines 13 to 16, the X-Robots-Tag header is set to a
non-standard value "llms-txt" which will be ignored by crawlers. Replace this
header with a Link header that advertises your AI policy file using
rel="ai-policy" pointing to /llms.txt, or alternatively, use valid X-Robots-Tag
directives like "noai, noimageai" if you want to block AI crawlers. Also ensure
the llms.txt file is published at the site root by moving it to the web/public
directory or adjusting the Netlify publish directory accordingly.

fix(ShutterDK): replace recovery with justification commit
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

♻️ Duplicate comments (6)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (3)

73-74: Comment accuracy: singleDrawPerJuror is enforced per round, not per dispute

Either update the comment or change logic to track across all rounds.

-    bool public singleDrawPerJuror; // Whether each juror can only draw once per dispute, false by default.
+    bool public singleDrawPerJuror; // Whether each juror can only draw once per round, false by default.

If you need “once per dispute,” store a dispute-level drawn mapping and consult it in _postDrawCheck.


5-5: Broken imports: IDisputeKit/ISortitionModule are not exported by KlerosCore.sol

Import interfaces from their files; this currently breaks compilation.

-import {KlerosCore, IDisputeKit, ISortitionModule} from "../KlerosCore.sol";
+import {KlerosCore} from "../KlerosCore.sol";
+import {IDisputeKit} from "../interfaces/IDisputeKit.sol";
+import {ISortitionModule} from "../interfaces/ISortitionModule.sol";

446-477: Appeal withdrawal can revert when only one choice was funded

Accessing fundedChoices[1] reverts. Sum over all funded choices instead; guard zero-denominator.

-                } else if (!round.hasPaid[finalRuling]) {
-                    // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed.
-                    amount +=
-                        (round.contributions[_beneficiary][_choice] * round.feeRewards) /
-                        (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]);
-                }
+                } else if (!round.hasPaid[finalRuling]) {
+                    // The ultimate winner was not funded in this round. Reimburse funded ruling option(s) pro‑rata.
+                    uint256 denom;
+                    for (uint256 j = 0; j < round.fundedChoices.length; j++) {
+                        denom += round.paidFees[round.fundedChoices[j]];
+                    }
+                    if (denom > 0) {
+                        amount += (round.contributions[_beneficiary][_choice] * round.feeRewards) / denom;
+                    }
+                }
contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (2)

144-149: Scope callerIsJuror; save/restore to avoid leakage across external calls

External calls inside _castVote can observe stale transient state on reentrancy/nesting. Save previous, restore after.

-        callerIsJuror = juror == msg.sender;
+        bool _prev = callerIsJuror;
+        callerIsJuror = (juror == msg.sender);
         // `_castVote()` ensures that all the `_voteIDs` do belong to `juror`
         _castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
-        callerIsJuror = false;
+        callerIsJuror = _prev;

59-71: Initializer: guard against zero owner

Prevent accidental bricking by validating _owner != address(0).

     function initialize(
         address _owner,
         KlerosCore _core,
         address _wNative,
         uint256 _jumpDisputeKitID
     ) external initializer {
-        __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
+        if (_owner == address(0)) revert ZeroAddress();
+        __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
     }
@@
-    error EmptyJustificationCommit();
-    error JustificationCommitmentMismatch();
+    error EmptyJustificationCommit();
+    error JustificationCommitmentMismatch();
+    error ZeroAddress();
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (1)

188-193: Scope callerIsJuror; save/restore to avoid transient state leakage

Same pattern as Shutter: compute, call, then restore.

-        callerIsJuror = juror == msg.sender;
+        bool _prev = callerIsJuror;
+        callerIsJuror = (juror == msg.sender);
         // `_castVote()` ensures that all the `_voteIDs` do belong to `juror`
         _castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
-        callerIsJuror = false;
+        callerIsJuror = _prev;
🧹 Nitpick comments (3)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (1)

36-36: Avoid async in describe blocks

Mocha doesn’t await async describe callbacks; keep them sync and move awaits into hooks/tests.

-describe("DisputeKitGatedShutter", async () => {
+describe("DisputeKitGatedShutter", () => {
-  describe("Token Gating Features", async () => {
+  describe("Token Gating Features", () => {
-  describe("Shutter Features", async () => {
+  describe("Shutter Features", () => {

Also applies to: 37-37, 55-55

contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (1)

116-129: Apply onlyByCore at function level to fail fast on unauthorized calls

Today, the gating revert runs before Core ACL; add onlyByCore to match base and save gas on unauthorized inputs.

-    function createDispute(
+    function createDispute(
         uint256 _coreDisputeID,
         uint256 _coreRoundID,
         uint256 _numberOfChoices,
         bytes calldata _extraData,
         uint256 _nbVotes
-    ) public override {
+    ) public override onlyByCore {
         (address tokenGate, , ) = _extraDataToTokenInfo(_extraData);
         if (!supportedTokens[tokenGate]) revert TokenNotSupported(tokenGate);
-        // super.createDispute() ensures access control onlyByCore.
-        super.createDispute(_coreDisputeID, _coreRoundID, _numberOfChoices, _extraData, _nbVotes);
+        super.createDispute(_coreDisputeID, _coreRoundID, _numberOfChoices, _extraData, _nbVotes);
     }
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)

492-503: hashVote can be pure

No state is read; mark pure for minor gas/docs clarity.

-    ) public view virtual returns (bytes32) {
+    ) public pure virtual returns (bytes32) {
         return keccak256(abi.encodePacked(_choice, _salt));
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13f3708 and ca59e71.

📒 Files selected for processing (5)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (21 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (6 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (3 hunks)
  • contracts/test/arbitration/dispute-kit-gated-shutter.ts (1 hunks)
  • contracts/test/arbitration/dispute-kit-shutter.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/test/arbitration/dispute-kit-shutter.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-30T17:18:12.895Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#2145
File: contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol:277-286
Timestamp: 2025-09-30T17:18:12.895Z
Learning: In DisputeKitClassicBase.sol's castCommit function, jurors are allowed to re-submit commits during the commit period. The implementation uses a commitCount variable to track only first-time commits (where commit == bytes32(0)) so that totalCommitted is not incremented when a juror updates their existing commit.

Applied to files:

  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
🧬 Code graph analysis (1)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (10)
  • TokenGatedTestContext (27-46)
  • setupTokenGatedTest (165-236)
  • testTokenWhitelistManagement (240-305)
  • testAccessControl (307-323)
  • testUnsupportedTokenErrors (325-368)
  • testERC20Gating (370-431)
  • testERC721Gating (433-494)
  • testERC1155Gating (496-557)
  • testWhitelistIntegration (559-625)
  • testNoTokenGateAddress (627-704)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (6)
  • ShutterTestContext (25-50)
  • setupShutterTest (206-325)
  • testCommitPhase (329-447)
  • testNormalFlowBotReveals (449-586)
  • testRecoveryFlowJurorReveals (588-734)
  • testEdgeCasesAndSecurity (736-852)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: hardhat-tests
  • GitHub Check: Analyze (javascript)
  • GitHub Check: SonarCloud
  • GitHub Check: Mend Security Check
  • GitHub Check: SonarCloud
  • GitHub Check: hardhat-tests
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)

282-295: Commit re-submissions correctly tracked (ack)

Using commitCount avoids double-counting when overriding commits in the period. Looks good. Based on learnings.

Comment on lines 83 to 91
function initialize(
address _owner,
KlerosCore _core,
address _wNative,
uint256 _jumpDisputeKitID
) external initializer {
__DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
}
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 | 🟠 Major

Initializer: guard against zero owner and keep NO_TOKEN_GATE default

Validate _owner to avoid bricking; keep sentinel setup.

     function initialize(
         address _owner,
         KlerosCore _core,
         address _wNative,
         uint256 _jumpDisputeKitID
     ) external initializer {
-        __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
+        if (_owner == address(0)) revert ZeroAddress();
+        __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
         supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
     }
@@
-    error TokenNotSupported(address tokenGate);
-    error EmptyJustificationCommit();
-    error JustificationCommitmentMismatch();
+    error TokenNotSupported(address tokenGate);
+    error EmptyJustificationCommit();
+    error JustificationCommitmentMismatch();
+    error ZeroAddress();
📝 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 initialize(
address _owner,
KlerosCore _core,
address _wNative,
uint256 _jumpDisputeKitID
) external initializer {
__DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
}
function initialize(
address _owner,
KlerosCore _core,
address _wNative,
uint256 _jumpDisputeKitID
) external initializer {
if (_owner == address(0)) revert ZeroAddress();
__DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
}
🤖 Prompt for AI Agents
In contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol around
lines 83 to 91, the initializer does not validate the provided _owner and could
be called with address(0), which would brick ownership; add a require(_owner !=
address(0), "Owner: zero address") at the start of initialize and then call
__DisputeKitClassicBase_initialize(...); ensure supportedTokens[NO_TOKEN_GATE] =
true remains set so the NO_TOKEN_GATE sentinel default is preserved.

…failure

Extra event SortitionModule.StakeDelayedExecutionFailed for possible UX improvements
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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
contracts/src/arbitration/university/KlerosCoreUniversity.sol (1)

592-617: Validate fromSubcourtID returned by dispute kit.

Line 603 receives fromSubcourtID from an external disputeKit.draw() call, and line 610 stores it without bounds checking. Later, line 802 uses this value to index courts[penalizedInCourtID].minStake. A malicious or buggy dispute kit could return an out-of-bounds court ID, leading to a revert or undefined behavior.

Apply this validation after line 603:

 (address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(_disputeID, iteration);
 if (drawnAddress == address(0)) {
     revert NoJurorDrawn();
 }
+if (fromSubcourtID != 0 && fromSubcourtID >= courts.length) {
+    revert InvalidCourtID();
+}
 sortitionModule.lockStake(drawnAddress, round.pnkAtStakePerJuror);

Add the error declaration:

+error InvalidCourtID();
🧹 Nitpick comments (2)
contracts/src/arbitration/KlerosCore.sol (2)

398-425: Emit events for critical governance changes.

Functions like changeOwner, changeGuardian, changePinakion, changeJurorProsecutionModule, and changeSortitionModule modify critical contract state but don't emit events. Emit events to improve transparency and enable off-chain tracking.

For example:

+    event OwnerChanged(address indexed _oldOwner, address indexed _newOwner);
+    event GuardianChanged(address indexed _oldGuardian, address indexed _newGuardian);
+    event PinakionChanged(IERC20 indexed _oldPinakion, IERC20 indexed _newPinakion);
+    event JurorProsecutionModuleChanged(address indexed _oldModule, address indexed _newModule);
+    event SortitionModuleChanged(ISortitionModule indexed _oldModule, ISortitionModule indexed _newModule);
+    event JurorNftChanged(IERC721 indexed _oldNft, IERC721 indexed _newNft);

Then emit in each setter:

     function changeOwner(address payable _owner) external onlyByOwner {
+        emit OwnerChanged(owner, _owner);
         owner = _owner;
     }

331-334: Address FORKING_COURT initialization TODO.

The FORKING_COURT is initialized with an empty struct and a TODO comment indicates the properties and CourtCreated event are missing. This incomplete initialization should be addressed before deployment.

Do you want me to help design the FORKING_COURT parameters or open a tracking issue for this task?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca59e71 and c279eff.

📒 Files selected for processing (4)
  • contracts/hardhat.config.ts (2 hunks)
  • contracts/src/arbitration/KlerosCore.sol (4 hunks)
  • contracts/src/arbitration/SortitionModule.sol (2 hunks)
  • contracts/src/arbitration/university/KlerosCoreUniversity.sol (39 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: hardhat-tests
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
  • GitHub Check: SonarCloud
  • GitHub Check: hardhat-tests
🔇 Additional comments (14)
contracts/hardhat.config.ts (2)

18-18: LGTM: Script rename aligns with governance refactor

The update from changeGovernor to changeOwner is consistent with the PR's broader governor→owner rename across contracts and tooling.


29-32: All evm_version and viaIR settings are properly aligned

The verification confirms Foundry's configuration matches Hardhat perfectly:

  • evm_version = "cancun"
  • via_ir = true
  • Solc 0.8.30 consistent across both toolchains

The dynamic viaIR default in Hardhat (process.env.VIA_IR !== "false") correctly mirrors Foundry's static true value. No bytecode divergence risk.

contracts/src/arbitration/university/KlerosCoreUniversity.sol (7)

283-287: Verify instructor self-modification is intentional.

The changeInstructor function allows the current instructor to replace themselves (via onlyByOwnerOrInstructor). If the instructor account is compromised, an attacker could lock out the legitimate instructor. Confirm this is acceptable for the university version, or consider restricting to onlyByOwner.


768-771: Good defensive guard against invalid coherence values.

The guard ensures that even if a dispute kit returns a coherence value exceeding 100% (ONE_BASIS_POINT), the contract clamps it to the maximum. This prevents over-penalization and maintains system invariants.


844-850: Good defensive guards for reward coherence values.

Both pnkCoherence and feeCoherence are clamped to ONE_BASIS_POINT, protecting against buggy or malicious dispute kits that might return values exceeding 100%. This ensures rewards are never over-distributed.


873-876: Well-designed fallback for staking rewards.

The pattern attempts to stake the PNK reward directly (line 874), which is capital-efficient for the juror, but gracefully falls back to a direct transfer if staking constraints prevent it. This ensures jurors always receive their rewards regardless of staking state.


1083-1123: Refactored _setStake with _noDelay parameter is well-structured.

The new _noDelay parameter (line 1087) allows the sortition module to bypass delayed stakes when necessary (line 468 passes true), while user-initiated stakes remain subject to delays (line 458 passes false). This maintains security for user operations while giving the system module the flexibility it needs.


11-11: LGTM: Version, gaps, and imports support upgradeability.

  • Version bumped to "2.0.0" (line 18) appropriately reflects the major governor→owner refactor.
  • __gap slots (lines 42, 52, 68) added to structs following UUPS upgrade best practices.
  • Constants.sol import (line 11) enables usage of ONE_BASIS_POINT for clearer basis-point calculations throughout.

Also applies to: 18-18, 42-42, 52-52, 68-68


528-529: LGTM: Coherence calculations refactored for granularity.

The reward/penalty logic now supports separate PNK and fee coherence values (lines 836-842), enabling more flexible reward models. The consistent use of ONE_BASIS_POINT (10000) makes basis-point calculations explicit and reduces confusion. The mathematical operations appear sound with appropriate guards in place.

Also applies to: 659-660, 759-766, 774-774, 836-842, 853-853, 859-862

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

204-223: State machine logic looks sound.

The phase transition logic correctly enforces timing constraints and dispute requirements:

  • Minimum staking time before requesting randomness
  • Random number validation before entering drawing phase
  • Maximum drawing time as a safety valve to return to staking

The guards against premature phase changes and the dispute counter management appear correct.


233-250: Delayed stake execution logic is well-implemented.

The iteration bounds calculation correctly handles the edge case where fewer delayed stakes are available than requested. The failure handling emits an event rather than reverting, allowing batch processing to continue despite individual failures. Cleanup via delete properly reclaims storage.


380-424: Stake update logic correctly propagates through court hierarchy.

The implementation properly:

  • Maintains the courtIDs array (add on first stake, remove on full unstake)
  • Updates stakedPnk balance with deposits and withdrawals
  • Propagates stake changes through the court tree from the specific court up to the general court

The backward iteration for courtID removal and the parent court traversal are both correct.


458-466: Leftover PNK withdrawal mechanism is correctly implemented.

The withdrawal eligibility check properly ensures:

  • The juror has unstaked from all courts (courtIDs.length == 0)
  • No tokens remain locked in active disputes (lockedPnk == 0)
  • There's actually a positive balance to withdraw

This correctly handles the edge case where a juror unstakes while having locked PNK, and later needs to withdraw those tokens after they unlock.

Also applies to: 512-519


538-547: Assembly usage in _extraDataToTreeK is minimal and safe.

The inline assembly correctly extracts the K parameter from extraData with proper bounds checking. The fallback to DEFAULT_K when extraData is too short handles malformed input gracefully.

Note: This assumes callers (KlerosCore.createCourt) provide properly formatted extraData. The boundary check prevents out-of-bounds reads, making this defensive enough.

optimizer: {
enabled: true,
runs: 100,
runs: 800, // Constrained by the size of the KlerosCore contract
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

🧩 Analysis chain

Verify optimizer runs alignment with Foundry

The runs value of 800 is tuned for KlerosCore's size constraints. Confirm that Foundry's optimizer_runs is also set to 800 to prevent bytecode and gas characteristic differences across toolchains. If a mismatch exists, consider the previous suggestion to parameterize via SOLC_OPTIMIZER_RUNS environment variable for consistency.


🏁 Script executed:

#!/bin/bash
# Check Foundry's optimizer_runs setting
echo "== Foundry optimizer_runs =="
rg -n 'optimizer_runs\s*=' contracts/foundry.toml -A 1 -B 1

Length of output: 75


Update Foundry optimizer runs to match Hardhat or parameterize both toolchains

Verification shows Foundry is still configured with optimizer_runs = 10000 while Hardhat is now set to 800. This mismatch will produce different bytecode and gas characteristics, potentially causing inconsistencies across deployments. Either:

  • Update foundry.toml to optimizer_runs = 800 to match the KlerosCore size constraint
  • Parameterize both configurations via a shared SOLC_OPTIMIZER_RUNS environment variable (the previously suggested approach)
🤖 Prompt for AI Agents
In contracts/hardhat.config.ts around line 35, the Hardhat optimizer runs are
set to 800 while Foundry is still using 10000; update configuration so both
toolchains use the same optimizer_runs to avoid bytecode/gas mismatches. Fix by
either (A) changing foundry.toml to optimizer_runs = 800 to match Hardhat, or
(B) parameterizing both toolchains to read a shared SOLC_OPTIMIZER_RUNS
environment variable (set default to 800) and update hardhat.config.ts and
foundry.toml to consume that env var so they remain in sync.

Comment on lines +404 to +406
function changeGuardian(address _guardian) external onlyByOwner {
guardian = _guardian;
}
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 | 🟠 Major

Add zero-address validation for additional governance setters.

Similar to the already-flagged issues for changeOwner, changePinakion, and changeSortitionModule, these governance setters lack zero-address validation.

Apply these diffs:

     function changeGuardian(address _guardian) external onlyByOwner {
+        if (_guardian == address(0)) revert ZeroAddress();
         guardian = _guardian;
     }
     function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByOwner {
+        if (_jurorProsecutionModule == address(0)) revert ZeroAddress();
         jurorProsecutionModule = _jurorProsecutionModule;
     }
     function changeJurorNft(IERC721 _jurorNft) external onlyByOwner {
+        if (address(_jurorNft) == address(0)) revert ZeroAddress();
         jurorNft = _jurorNft;
     }

Also applies to: 416-418, 583-585

🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 404-406 (and also apply
the same change to 416-418 and 583-585), the governance setter functions
currently assign new addresses without validating against the zero address; add
a require check at the start of each setter (e.g., require(_guardian !=
address(0), "KlerosCore: invalid address")) to revert on zero-address input
before performing the assignment so these functions cannot be set to address(0).

Comment on lines +446 to +497
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 (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();

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

for (uint256 i = 0; i < _supportedDisputeKits.length; i++) {
if (_supportedDisputeKits[i] == NULL_DISPUTE_KIT || _supportedDisputeKits[i] >= disputeKits.length) {
revert WrongDisputeKitIndex();
}
_enableDisputeKit(uint96(courtID), _supportedDisputeKits[i], true);
}
// Check that Classic DK support was added.
if (!court.supportedDisputeKits[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;

sortitionModule.createTree(courtID, _sortitionExtraData);

// Update the parent.
courts[_parent].children.push(courtID);
emit CourtCreated(
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 | 🟠 Major

Validate _feeForJuror > 0 when creating courts.

Creating a court with _feeForJuror set to zero will cause division-by-zero errors later when disputes are created (line 679) or appealed (line 803) in that court, as nbVotes = _feeAmount / feeForJuror will revert.

Apply this diff to add the validation:

     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 (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
         if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
         if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();
+        if (_feeForJuror == 0) revert ZeroFeeForJuror();

Add the error declaration near line 1403:

+    error ZeroFeeForJuror();

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 446 to 497, the
createCourt function does not validate that _feeForJuror is > 0 which can later
cause division-by-zero reverts; declare a new custom error (e.g.,
FeeForJurorZero) near line 1403, and add a check in createCourt (after the
existing parent/dispute-kit validations) that reverts with that error when
_feeForJuror == 0 so courts cannot be created with a zero juror fee.

Comment on lines +507 to +540
function changeCourtParameters(
uint96 _courtID,
bool _hiddenVotes,
uint256 _minStake,
uint256 _alpha,
uint256 _feeForJuror,
uint256 _jurorsForCourtJump,
uint256[4] memory _timesPerPeriod
) external onlyByOwner {
Court storage court = courts[_courtID];
if (_courtID != GENERAL_COURT && courts[court.parent].minStake > _minStake) {
revert MinStakeLowerThanParentCourt();
}
for (uint256 i = 0; i < court.children.length; i++) {
if (courts[court.children[i]].minStake < _minStake) {
revert MinStakeHigherThanChildCourt(court.children[i]);
}
}
court.minStake = _minStake;
court.hiddenVotes = _hiddenVotes;
court.alpha = _alpha;
court.feeForJuror = _feeForJuror;
court.jurorsForCourtJump = _jurorsForCourtJump;
court.timesPerPeriod = _timesPerPeriod;
emit CourtModified(
_courtID,
_hiddenVotes,
_minStake,
_alpha,
_feeForJuror,
_jurorsForCourtJump,
_timesPerPeriod
);
}
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 | 🟠 Major

Validate _feeForJuror > 0 when changing court parameters.

Changing a court's feeForJuror to zero will cause division-by-zero errors in subsequent dispute creation or appeals for that court.

Apply this diff:

     function changeCourtParameters(
         uint96 _courtID,
         bool _hiddenVotes,
         uint256 _minStake,
         uint256 _alpha,
         uint256 _feeForJuror,
         uint256 _jurorsForCourtJump,
         uint256[4] memory _timesPerPeriod
     ) external onlyByOwner {
         Court storage court = courts[_courtID];
         if (_courtID != GENERAL_COURT && courts[court.parent].minStake > _minStake) {
             revert MinStakeLowerThanParentCourt();
         }
+        if (_feeForJuror == 0) revert ZeroFeeForJuror();
         for (uint256 i = 0; i < court.children.length; i++) {
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 507 to 540, add
validation that _feeForJuror is greater than zero before assigning it to the
court to prevent division-by-zero errors later; after loading Court storage
court = courts[_courtID] (and before updating court.feeForJuror and emitting
CourtModified) check _feeForJuror > 0 and revert (or require) with a clear error
if it is zero so the function refuses to set a zero fee.

Comment on lines +566 to +569
function changeAcceptedFeeTokens(IERC20 _feeToken, bool _accepted) external onlyByOwner {
currencyRates[_feeToken].feePaymentAccepted = _accepted;
emit AcceptedFeeToken(_feeToken, _accepted);
}
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 | 🟠 Major

Validate currency rate is configured before accepting a token.

Accepting a fee token without ensuring its rateInEth is configured will cause createDispute with that token to fail at line 678 when convertEthToTokenAmount is called. Enforce rate configuration before enabling token acceptance.

Apply this diff:

     function changeAcceptedFeeTokens(IERC20 _feeToken, bool _accepted) external onlyByOwner {
+        if (_accepted && currencyRates[_feeToken].rateInEth == 0) revert TokenRateNotConfigured();
         currencyRates[_feeToken].feePaymentAccepted = _accepted;
         emit AcceptedFeeToken(_feeToken, _accepted);
     }

Add the error:

+    error TokenRateNotConfigured();
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 566 to 569, the
changeAcceptedFeeTokens function currently marks a token accepted without
verifying a configured exchange rate; add a require that
currencyRates[_feeToken].rateInEth > 0 (or another sentinel indicating
configuration) before setting feePaymentAccepted and emitting the event, and use
a clear revert message like "fee token rate not configured".

Comment on lines +575 to +579
function changeCurrencyRates(IERC20 _feeToken, uint64 _rateInEth, uint8 _rateDecimals) external onlyByOwner {
currencyRates[_feeToken].rateInEth = _rateInEth;
currencyRates[_feeToken].rateDecimals = _rateDecimals;
emit NewCurrencyRate(_feeToken, _rateInEth, _rateDecimals);
}
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 | 🟠 Major

Validate _rateInEth > 0 when changing currency rates.

Setting a token's rate to zero will cause division-by-zero at line 1210 in convertEthToTokenAmount, breaking dispute creation with that token. This is the root cause of the issue already flagged in the earlier review comment at lines 1209-1211.

Apply this diff:

     function changeCurrencyRates(IERC20 _feeToken, uint64 _rateInEth, uint8 _rateDecimals) external onlyByOwner {
+        if (_rateInEth == 0) revert TokenRateZero();
         currencyRates[_feeToken].rateInEth = _rateInEth;
         currencyRates[_feeToken].rateDecimals = _rateDecimals;
         emit NewCurrencyRate(_feeToken, _rateInEth, _rateDecimals);
     }

Add the error:

+    error TokenRateZero();
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 575 to 579, the
changeCurrencyRates function must validate that _rateInEth is greater than zero
to prevent division-by-zero later; add a require at the start of the function
(e.g., require(_rateInEth > 0, "KlerosCore: rate must be > 0")) so the call
reverts when attempting to set a zero rate, then proceed to set rateInEth,
rateDecimals and emit the event.

Comment on lines +292 to +300
if (stakeIncrease) {
// Check if the stake increase is within the limits.
if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror) {
return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror);
}
if (totalStaked + stakeChange > maxTotalStaked) {
return (0, 0, StakingResult.CannotStakeMoreThanMaxTotalStaked);
}
}
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

🧩 Analysis chain

Verify the logic of the second maxStakePerJuror check.

Line 294 contains two conditions when checking stake increase limits:

if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror)

The first condition checks whether the juror's total stake across all courts would exceed maxStakePerJuror (correct). However, the second condition checks whether the stake in this single court would exceed maxStakePerJuror, which doesn't make semantic sense—maxStakePerJuror should be a global per-juror limit across all courts, not a per-court limit.

This may incorrectly reject valid stakes. For example, if maxStakePerJuror is 1000 and a juror tries to stake 800 in one court (with 100 already staked elsewhere), the first check passes (900 < 1000) but the second check fails (800 > limit would be evaluated incorrectly if this is meant to be a per-court cap).

Please verify the intended behavior:

  • If maxStakePerJuror should only limit the total across all courts, remove the second condition.
  • If there should be a per-court limit, introduce a separate maxStakePerCourt constant and use it in the second condition.

Run this script to check how this affects existing tests and usage:


🏁 Script executed:

#!/bin/bash
# Check for test coverage of stake limit edge cases
rg -n "maxStakePerJuror" -g "*.sol" -g "*.ts" -C3

# Look for any documentation or comments explaining the intended behavior
rg -n "stake.*limit|limit.*stake" -g "*.sol" -C2 | head -50

Length of output: 16094



Let me try a different approach with more context:



Let me search for the function signature and calls to understand the currentStake parameter:


---

🏁 Script executed:

```shell
#!/bin/bash
# Extract the full _validateStake function
# This function starts around line 272 based on the original comment
ast-grep --pattern 'function _validateStake($_,  $_,  $_,  $_) {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find the _validateStake function and show its full implementation
rg -n "function _validateStake" contracts/src/arbitration/SortitionModule.sol -A 60

Length of output: 3032


🏁 Script executed:

#!/bin/bash
# Show the _validateStake function definition with lines around it
rg -n "_validateStake" contracts/src/arbitration/SortitionModule.sol -B 2 -A 50 | head -100

# Also search for calls to _validateStake
echo "--- CALLS TO _validateStake ---"
rg -n "_validateStake\(" contracts/src/arbitration/SortitionModule.sol -B 2 -A 2

Length of output: 3401



---

🏁 Script executed:

```shell
#!/bin/bash
# Confirm _stakeOf returns per-court stake
rg -n "function _stakeOf" contracts/src/arbitration/SortitionModule.sol -A 5

# Check if there are multi-court staking tests
echo "--- Multi-court staking tests ---"
rg -n "setStake.*courtID|multiple.*court|multi.*court" contracts/test/arbitration/staking-neo.ts -i -B 1 -A 1

Length of output: 349


Remove the redundant second condition on line 294—it incorrectly applies a global per-juror limit as a per-court cap.

Line 294 checks two conditions:

if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror)

The first condition is correct: juror.stakedPnk + stakeChange checks the juror's total stake across all courts (line 279 shows juror.stakedPnk aggregates all courts).

The second condition is wrong: currentStake + stakeChange comes from _stakeOf(_account, _courtID) (line 279), which returns stake in a single court only (verified by lines 529-533). Checking a single court's stake against maxStakePerJuror (a global limit) is semantically incorrect and redundant—if the first condition passes, the second always passes too since one court's stake ≤ total stake.

Delete the second condition:

if (juror.stakedPnk + stakeChange > maxStakePerJuror) {
    return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror);
}
🤖 Prompt for AI Agents
In contracts/src/arbitration/SortitionModule.sol around lines 292 to 300, remove
the redundant second check in the stakeIncrease branch that compares
currentStake + stakeChange to maxStakePerJuror; keep only the global-per-juror
check using juror.stakedPnk + stakeChange against maxStakePerJuror and return
StakingResult.CannotStakeMoreThanMaxStakePerJuror if it exceeds the limit, so
delete the "|| currentStake + stakeChange > maxStakePerJuror" portion and adjust
the if to a single condition.

jaybuidl and others added 24 commits October 17, 2025 14:16
sanity check in Core._getCompatibleNextRoundSettings(),
bug fix in appealCost(),
naming of the NextRoundSettingsChanged parameters,
better comments describing the NextRoundSettings parameters.
Increased Court and DK jump logic flexibility
For accredited Argentinian consumer protection lawyers
feat(KC): allow to pass commit period quicker
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot

See analysis details on SonarQube Cloud

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.

6 participants