Skip to content

Release #2076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 58 commits into
base: master
Choose a base branch
from
Draft

Release #2076

wants to merge 58 commits into from

Conversation

jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Aug 8, 2025

PR-Codex overview

This PR focuses on updating the Solidity version constraints, enhancing error handling with custom errors, and refactoring the RNG implementation. It also involves deploying a new RNGWithFallback contract and modifying various contracts to utilize this new RNG interface.

Detailed summary

  • Updated Solidity version constraints to >=0.8.0 <0.9.0.
  • Added custom error handling, replacing require statements with if checks and custom errors like GovernorOnly.
  • Deleted unused contracts: RNG.sol, CappedMath.sol, and several deployment scripts.
  • Introduced IRNG interface for RNG implementations.
  • Implemented RNGWithFallback contract for enhanced RNG functionality.
  • Modified SortitionModule and related contracts to use IRNG instead of RNG.
  • Refactored deployment scripts to deploy RNGWithFallback and ensure proper consumer configuration.
  • Updated tests to accommodate changes in contract interfaces and error handling.
  • Enhanced various components with improved logging and error messages.

The following files were skipped due to too many changes: contracts/deploy/00-home-chain-arbitration-neo.ts, web/src/hooks/useVotingContext.tsx, contracts/CHANGELOG.md, contracts/src/gateway/HomeGateway.sol, contracts/src/gateway/ForeignGateway.sol, contracts/src/rng/RNGWithFallback.sol, contracts/src/rng/RandomizerRNG.sol, contracts/src/rng/ChainlinkRNG.sol, contracts/src/arbitration/university/KlerosCoreUniversity.sol, contracts/src/rng/BlockhashRNG.sol, contracts/src/arbitration/KlerosCoreBase.sol, contracts/src/arbitration/KlerosGovernor.sol, contracts/src/arbitration/SortitionModuleBase.sol, contracts/test/rng/index.ts, contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol, yarn.lock, contracts/test/foundry/KlerosCore.t.sol, contracts/audit/METRICS.md, contracts/audit/METRICS.html

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

Summary by CodeRabbit

  • New Features

    • Discover and work with multiple dispute kits; CLI/script to fetch dispute-kit info.
    • Robust randomness: primary VRF with automatic fallback and improved Arbitrum timing/authentication for more reliable draws.
  • Improvements

    • Voting UI supports multiple dispute-kit types; timeline/period display refined and several label text updates.
    • Shutter commit flow validates configuration before proceeding.
  • Bug Fixes

    • Preserve Shutter auto-reveal by not advancing to voting when all commits are cast.
  • Documentation

    • Public llms.txt added; changelog updated (0.12.0/0.13.0).
  • Dependency & Tooling

    • Solidity toolchain, Hardhat/Foundry and packages bumped; viem added as a peer dependency.

Copy link

netlify bot commented Aug 8, 2025

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit 9895c7c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/68a61cf2b62d9d000814355f
😎 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.

Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

Walkthrough

Adds a Viem-based dispute-kit discovery helper and script; migrates RNG to a consumer/IRNG model with RNGWithFallback and timestamp-based BlockhashRNG; replaces many string reverts with custom errors; introduces basis-point coherence APIs; bumps Solidity/tooling and updates frontend to multi-dispute-kit voting and shutter guards.

Changes

Cohort / File(s) Change Summary
Dispute-kit Viem helper & CLI
contracts/deployments/disputeKitsViem.ts, contracts/deployments/index.ts, contracts/scripts/getDisputeKits.ts
New Viem helper to query DisputeKitCreated logs, map IDs→addresses, correlate with local deployed contracts; exported and a runnable script added.
RNG API & implementations
contracts/src/rng/IRNG.sol, contracts/src/rng/*, contracts/src/test/RNGMock.sol
Introduce IRNG (zero-arg requestRandomness/receiveRandomness), migrate RNG implementations (ChainlinkRNG, BlockHashRNG, RandomizerRNG, IncrementalNG, RandomizerRNG) to IRNG, add RNGWithFallback contract, add RNGMock for tests, and update constructors/signatures/consumer APIs.
Sortition / core wiring & deployments
contracts/src/arbitration/SortitionModule*.sol, contracts/deploy/*, contracts/scripts/*
Remove RNG lookahead args from initializers, switch to IRNG typed params, replace changeSortitionModule→changeConsumer wiring, update many deployment scripts and getContract usage to generics, wire RNGWithFallback in deployments.
Custom errors (bulk refactor)
contracts/src/arbitration/**, contracts/src/gateway/**, contracts/src/arbitration/dispute-kits/**, contracts/src/arbitration/arbitrables/**
Replace string require() reverts with if (...) revert ErrorName();, add many error declarations and adjust tests to assert custom-error selectors.
Coherence / basis-point refactor
contracts/src/arbitration/KlerosCoreBase.sol, contracts/src/arbitration/university/*, contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol, contracts/src/libraries/Constants.sol
Remove ALPHA_DIVISOR, add ONE_BASIS_POINT = 10000, split getDegreeOfCoherence into getDegreeOfCoherenceReward/getDegreeOfCoherencePenalty, update reward/penalty math and related event param names.
Library removal & arithmetic changes
contracts/src/libraries/CappedMath.sol, contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol
Delete CappedMath and replace saturating ops with explicit arithmetic and conditionals in ModeratedEvidenceModule.
Tooling, compiler & coverage
contracts/hardhat.config.ts, contracts/foundry.toml, contracts/.solcover.js, contracts/scripts/coverage.sh, contracts/package.json, package.json, cspell.json
Bump solc to 0.8.30 and enable viaIR (env-driven), adjust optimizer, add Foundry profile and file exceptions, add irMinimum to solcover, export VIA_IR=false in coverage script, bump hardhat/@kleros/vea-contracts, make viem a peerDependency and pin root resolution, add IRNG to spelllist.
Tests (unit & Foundry)
contracts/test/**, contracts/test/foundry/KlerosCore.t.sol
Update tests to generic getContract, adapt to IRNG zero-arg flows and RNGWithFallback, assert new custom errors, move many timing checks from block-rolls to time-warp, add RNG fallback tests.
Frontend: multi-kit voting & shutter
web/src/hooks/useVotingContext.tsx, web/src/pages/Cases/CaseDetails/Voting/Shutter/*.tsx
Support multiple dispute-kit vote hooks (Classic/Shutter/Gated/GatedShutter); consolidate vote state and selection; refactor Shutter render to optionally show commit/reveal independently; add env guard in commit flow.
UI copy, timeline and docs
web/src/components/DisputeView/CardLabels/index.tsx, web/src/pages/Cases/CaseDetails/Timeline.tsx, web/src/public/llms.txt, web/netlify.toml
Update UI label strings, refactor timeline to use period-index and conditional Commit visibility, add llms.txt doc file and set X-Robots-Tag: llms-txt header.
Changelog & metrics
contracts/CHANGELOG.md, contracts/scripts/generateMetrics.sh
Update changelog anchors/entries; add script to generate Solidity metrics → METRICS.md.
Misc: small fixes & removals
contracts/src/kleros-v1/.../xKlerosLiquidV2.sol, various contracts/deploy/*.ts removals/changes
Fix import path, remove ALPHA_DIVISOR constant, remove some old deploy scripts and replace deployment helpers with get-or-deploy patterns; many getContract casts → generics.

Sequence Diagram(s)

sequenceDiagram
    participant Script as getDisputeKits.ts
    participant Client as Viem PublicClient
    participant Helper as disputeKitsViem.getDisputeKits
    participant Chain as KlerosCore (on-chain)

    Note over Script,Client: init RPC client (env RPC)
    Script->>Client: create PublicClient
    Script->>Helper: getDisputeKits(client, "devnet")
    Helper->>Chain: fetch DisputeKitCreated logs (filter)
    Chain-->>Helper: logs
    Helper->>Helper: decode logs → id→address map
    Helper->>Helper: correlate addresses with local deployments/contracts
    Helper->>Helper: determine isGated/isShutter flags
    Helper-->>Script: return DisputeKitByIds
Loading
sequenceDiagram
    participant Sort as SortitionModule / Consumer
    participant RNGP as Primary IRNG (Chainlink/Randomizer)
    participant RF as RNGWithFallback
    participant BH as BlockhashRNG (fallback source)
    Note over Sort,RNGP: consumer requests randomness
    Sort->>RF: requestRandomness()
    RF->>RNGP: requestRandomness()
    RNGP-->>RF: (async fulfill via oracle) or no response
    Note over RF: on receiveRandomness call
    Sort->>RF: receiveRandomness()
    alt primary ready
        RF->>RNGP: receiveRandomness()
        RNGP-->>RF: random (non-zero)
        RF-->>Sort: return primary random
    else timed out
        RF->>BH: derive fallback from blockhash (previous block)
        RF-->>Sort: return fallback random (emit RNGFallback)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Package: Contracts

Suggested reviewers

  • unknownunknown1
  • alcercu

Poem

"I hopped through logs and RPC light,
matched kits by moon and built them right.
Errors named and RNGs that fall back too,
timelines tuned and frontend grew.
A rabbit cheers — green builds in sight 🐇"

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

netlify bot commented Aug 8, 2025

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

Name Link
🔨 Latest commit 9895c7c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-university/deploys/68a61cf22dd6c000081d40db

Copy link

netlify bot commented Aug 8, 2025

Deploy Preview for kleros-v2-neo-devtools ready!

Name Link
🔨 Latest commit 9895c7c
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo-devtools/deploys/68a61cf290459e0008f3e7b2
😎 Deploy Preview https://deploy-preview-2076--kleros-v2-neo-devtools.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.

Copy link

netlify bot commented Aug 8, 2025

Deploy Preview for kleros-v2-testnet-devtools ready!

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

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.

Copy link

netlify bot commented Aug 8, 2025

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

Name Link
🔨 Latest commit 9140890
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/6895580d150f77000893db61

Copy link

netlify bot commented Aug 8, 2025

Deploy Preview for kleros-v2-neo ready!

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

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

🧹 Nitpick comments (4)
web/src/hooks/useVotingContext.tsx (3)

45-47: Guard usage of disputeKitAddress in address-to-name resolution

If disputeKitAddress is temporarily undefined while disputeData loads, ensure useDisputeKitAddresses tolerates undefined. If not, gate the hook param.

Possible defensive tweak:

- const { disputeKitName } = useDisputeKitAddresses({ disputeKitAddress });
+ const { disputeKitName } = useDisputeKitAddresses({ disputeKitAddress: disputeKitAddress as `0x${string}` | undefined });

Or add an internal guard in the hook to early-return when the address is undefined.


51-83: Pattern is sound; keep hooks stable and gate via enabled

Calling all four hooks with enabled flags preserves hook order and avoids conditional-hook pitfalls. Refetch cadence is centralized via REFETCH_INTERVAL. LGTM.

If you want to remove repetition, you can extract the common query options:

+  const baseQuery = { refetchInterval: REFETCH_INTERVAL as const };
   const classicVoteResult = useReadDisputeKitClassicIsVoteActive({
-    query: {
-      enabled: isEnabled && disputeKitName === DisputeKits.Classic,
-      refetchInterval: REFETCH_INTERVAL,
-    },
+    query: { ...baseQuery, enabled: isEnabled && disputeKitName === DisputeKits.Classic },
     args: hookArgs,
   });

84-105: Memo switch is correct; consider exposing a loading flag for the active kit

The selector is correct and dependencies are complete. Optional: expose voteStatusLoading from the active hook to improve UX around the hasVoted read.

Example:

const activeVoteStatusLoading = useMemo(() => {
  switch (disputeKitName) {
    case DisputeKits.Classic: return classicVoteResult.isLoading;
    case DisputeKits.Shutter: return shutterVoteResult.isLoading;
    case DisputeKits.Gated: return gatedVoteResult.isLoading;
    case DisputeKits.GatedShutter: return gatedShutterVoteResult.isLoading;
    default: return false;
  }
}, [disputeKitName, classicVoteResult.isLoading, shutterVoteResult.isLoading, gatedVoteResult.isLoading, gatedShutterVoteResult.isLoading]);

// Optionally export it in the context value
web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx (1)

32-39: Behavioral change: commit and reveal can render together—confirm this is intentional

The fragment now allows both <ShutterCommit /> and <Reveal /> to render if both flags are true. If the domain guarantees commit and vote periods are mutually exclusive (as expected), this is fine and equivalent in practice. If not guaranteed, consider restoring exclusivity or adding a runtime assertion.

Option to restore exclusivity:

-  return (
-    <>
-      {shouldShowCommit && (
-        <ShutterCommit {...{ arbitrable, setIsOpen, voteIDs, refetch, dispute, currentPeriodIndex, isGated }} />
-      )}
-      {shouldShowReveal && <Reveal {...{ setIsOpen, voteIDs, isGated }} />}
-    </>
-  );
+  return shouldShowCommit ? (
+    <ShutterCommit {...{ arbitrable, setIsOpen, voteIDs, refetch, dispute, currentPeriodIndex, isGated }} />
+  ) : shouldShowReveal ? (
+    <Reveal {...{ setIsOpen, voteIDs, isGated }} />
+  ) : null;

Additionally, ensure voteIDs can’t be undefined while the component renders. Consider defaulting to an empty array (outside this hunk):

// Above: current line 27
const voteIDs = useMemo(() => drawData?.draws?.map((d) => d.voteIDNum) ?? [], [drawData]);
📜 Review details

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

📥 Commits

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

📒 Files selected for processing (2)
  • web/src/hooks/useVotingContext.tsx (2 hunks)
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (16)
📚 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:

  • web/src/hooks/useVotingContext.tsx
📚 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:

  • web/src/hooks/useVotingContext.tsx
📚 Learning: 2025-05-15T06:50:40.859Z
Learnt from: tractorss
PR: kleros/kleros-v2#1982
File: web/src/pages/Resolver/Landing/index.tsx:62-62
Timestamp: 2025-05-15T06:50:40.859Z
Learning: In the Landing component, it's safe to pass `dispute?.dispute?.arbitrated.id as 0x${string}` to `usePopulatedDisputeData` without additional null checks because the hook internally handles undefined parameters through its `isEnabled` flag and won't execute the query unless all required data is available.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-12-09T12:36:59.441Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1775
File: web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx:0-0
Timestamp: 2024-12-09T12:36:59.441Z
Learning: In the `StakeWithdrawButton` component, the transaction flow logic is tightly linked to component updates, so extracting it into a custom hook does not provide significant benefits.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-10-09T10:22:41.474Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1582
File: web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx:88-90
Timestamp: 2024-10-09T10:22:41.474Z
Learning: Next.js recommends using the `useEffect` hook to set `isClient` and using `suppressHydrationWarning` as a workaround for handling hydration inconsistencies, especially when dealing with data like `knownArbitrables` that may differ between server-side and client-side rendering. This approach is acceptable in TypeScript/React applications, such as in `web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx`.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2025-05-09T13:39:15.086Z
Learnt from: tractorss
PR: kleros/kleros-v2#1982
File: web/src/pages/Resolver/Parameters/NotablePersons/PersonFields.tsx:64-0
Timestamp: 2025-05-09T13:39:15.086Z
Learning: In PersonFields.tsx, the useEffect hook for address validation intentionally uses an empty dependency array to run only once on component mount. This is specifically designed for the dispute duplication flow when aliasesArray is already populated with addresses that need initial validation.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
📚 Learning: 2024-06-27T10:11:54.861Z
Learnt from: nikhilverma360
PR: kleros/kleros-v2#1632
File: web/src/components/DisputeView/DisputeInfo/DisputeInfoList.tsx:37-42
Timestamp: 2024-06-27T10:11:54.861Z
Learning: `useMemo` is used in `DisputeInfoList` to optimize the rendering of `FieldItems` based on changes in `fieldItems`, ensuring that the mapping and truncation operation are only performed when necessary.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
📚 Learning: 2024-12-16T17:17:32.359Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1794
File: web/src/hooks/useStarredCases.tsx:13-18
Timestamp: 2024-12-16T17:17:32.359Z
Learning: In `useStarredCases.tsx`, when handling the `starredCases` Map from local storage, direct mutation is acceptable to prevent the overhead of copying, provided it doesn't adversely affect React's render cycle.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 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:

  • web/src/hooks/useVotingContext.tsx
📚 Learning: 2024-12-06T13:04:50.495Z
Learnt from: kemuru
PR: kleros/kleros-v2#1774
File: web/src/components/CasesDisplay/index.tsx:61-61
Timestamp: 2024-12-06T13:04:50.495Z
Learning: In `web/src/components/CasesDisplay/index.tsx`, the variables `numberDisputes` and `numberClosedDisputes` can sometimes be `NaN`, and should default to `0` using logical OR (`||`) to prevent display issues in the `StatsAndFilters` component.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-11-07T10:48:16.774Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1739
File: web/src/pages/Home/TopJurors/JurorCard/Coherency.tsx:22-26
Timestamp: 2024-11-07T10:48:16.774Z
Learning: In the `Coherency` component (`web/src/pages/Home/TopJurors/JurorCard/Coherency.tsx`), `totalResolvedVotes` is always greater than or equal to `totalCoherentVotes`. When both are zero, `0/0` results in `NaN`, which is acceptable in this context.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-11-19T05:29:56.238Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1744
File: web/src/hooks/queries/useHomePageBlockQuery.ts:71-71
Timestamp: 2024-11-19T05:29:56.238Z
Learning: In `web/src/hooks/queries/useHomePageBlockQuery.ts`, the non-null assertions on `blockNumber!` and `genesisBlock!` within `queryFn` are safe because `isEnabled` ensures that `queryFn` only runs when either `blockNumber` or `genesisBlock` is defined.

Applied to files:

  • web/src/hooks/useVotingContext.tsx
📚 Learning: 2024-10-29T10:13:04.524Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1729
File: web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx:69-69
Timestamp: 2024-10-29T10:13:04.524Z
Learning: In `web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx`, when the button is disabled, the associated logic won't be reached, and certain code paths may exist for TypeScript purposes.

Applied to files:

  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-10-29T10:14:52.512Z
Learnt from: Harman-singh-waraich
PR: kleros/kleros-v2#1729
File: web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx:125-127
Timestamp: 2024-10-29T10:14:52.512Z
Learning: In `web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/index.tsx`, when `isEmailUpdateable` is false, `user?.emailUpdateableAt` is always defined. Therefore, using the non-null assertion `!` with `user?.emailUpdateableAt!` is acceptable because TypeScript may not infer its definiteness.

Applied to files:

  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-10-09T10:23:39.325Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1582
File: web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx:98-103
Timestamp: 2024-10-09T10:23:39.325Z
Learning: In `SelectArbitrable.tsx` of the web-devtools project (React/TypeScript), direct DOM manipulation using `child.click()` is acceptable when considered reasonably safe.

Applied to files:

  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
📚 Learning: 2024-10-09T10:17:37.935Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1582
File: web-devtools/src/app/(main)/dispute-template/page.tsx:324-360
Timestamp: 2024-10-09T10:17:37.935Z
Learning: In this codebase, prefer using the `??` (nullish coalescing operator) over `||` when handling default values for optional properties in React components.

Applied to files:

  • web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
⏰ 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). (4)
  • GitHub Check: contracts-testing
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
🔇 Additional comments (2)
web/src/hooks/useVotingContext.tsx (2)

107-107: Good defaulting for hidden votes

Defaulting isHiddenVotes with ?? false avoids transient undefineds from GraphQL. Matches the codebase’s preference for nullish coalescing over ||.


48-50: Normalize all DisputeKit call arguments to bigint and include id in the enabled guard

Viem/Wagmi read hooks for IsVoteActive expect a tuple of uint256 values as bigint, but here id, roundId, and voteId may be string | number. Also, the current isEnabled guard skips checking id, so the hook could fire with [0n, …].

Proposed diff:

-  const hookArgs = [BigInt(id ?? 0), roundId, voteId] as const;
-  const isEnabled = !isUndefined(roundId) && !isUndefined(voteId);
+  // Helper to coerce string|number to bigint
+  const toBigInt = (v: string | number | bigint) =>
+    typeof v === "bigint" ? v : BigInt(v);
+
+  // Only enable when all three IDs are defined
+  const disputeId  = id      !== undefined ? toBigInt(id)      : undefined;
+  const roundIdBn  = roundId !== undefined ? toBigInt(roundId) : undefined;
+  const voteIdBn   = voteId  !== undefined ? toBigInt(voteId)  : undefined;
+  const isEnabled  =
+    disputeId !== undefined &&
+    roundIdBn !== undefined &&
+    voteIdBn !== undefined;
+
+  // Pass the normalized bigint tuple
+  const hookArgs = [disputeId!, roundIdBn!, voteIdBn!] as const;

Please verify that the generated useReadDisputeKit…IsVoteActive hook signatures indeed expect [bigint, bigint, bigint] and adjust accordingly.

Comment on lines +29 to +31
const shouldShowCommit = id && isCommitPeriod && !commited;
const shouldShowReveal = id && isVotingPeriod;

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

Coerce id to boolean to avoid string leakage into predicates

id && isCommitPeriod && !commited and id && isVotingPeriod may infer string | boolean. Use Boolean(id) (or !!id) to keep these strictly boolean.

Apply:

-  const shouldShowCommit = id && isCommitPeriod && !commited;
-  const shouldShowReveal = id && isVotingPeriod;
+  const hasId = Boolean(id);
+  const shouldShowCommit = hasId && isCommitPeriod && !commited;
+  const shouldShowReveal = hasId && isVotingPeriod;
📝 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 shouldShowCommit = id && isCommitPeriod && !commited;
const shouldShowReveal = id && isVotingPeriod;
const hasId = Boolean(id);
const shouldShowCommit = hasId && isCommitPeriod && !commited;
const shouldShowReveal = hasId && isVotingPeriod;
🤖 Prompt for AI Agents
In web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx around lines 29 to
31, the variables shouldShowCommit and shouldShowReveal use `id` directly in
logical expressions, which can cause the result to be a string or boolean. To
fix this, explicitly coerce `id` to a boolean by replacing `id` with
`Boolean(id)` or `!!id` in these expressions to ensure the predicates are
strictly boolean values.

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

🔭 Outside diff range comments (2)
contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol (2)

186-206: Align modifiers with PR’s custom-error pattern (gas + consistency)

The PR migrates to custom errors; these modifiers still use require(). Switch to custom errors for clearer semantics and reduced bytecode.

Apply within this block:

 modifier onlyDuringPhase(Phase _phase) {
-        require(phase == _phase);
+        if (phase != _phase) revert InvalidPhase(_phase, phase);
         _;
     }

 modifier onlyDuringPeriod(uint256 _disputeID, Period _period) {
-        require(disputes[_disputeID].period == _period);
+        if (disputes[_disputeID].period != _period) revert InvalidPeriod(_disputeID, _period, disputes[_disputeID].period);
         _;
     }

 modifier onlyByGovernor() {
-        require(governor == msg.sender);
+        if (governor != msg.sender) revert GovernorOnly();
         _;
     }

Add these error definitions near the top of the contract (after the enums to allow typed parameters):

error GovernorOnly();
error InvalidPhase(Phase expected, Phase actual);
error InvalidPeriod(uint256 disputeID, Period expected, Period actual);

If useful, I can prepare a follow-up patch converting other requires in this contract to typed errors.


484-485: Replace string reverts with a custom NotSupported() error

These paths use revert("Not supported"); switch to a custom error to align with the migration and save gas.

Define the error once:

error NotSupported();

Then update both call sites:

     ) external override returns (uint256) {
-        revert("Not supported");
+        revert NotSupported();
     }
     ) public pure override returns (uint256 /*cost*/) {
-        revert("Not supported");
+        revert NotSupported();
     }

Also applies to: 629-631

🧹 Nitpick comments (2)
contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol (2)

500-505: Avoid underflow in onTransfer to return false instead of reverting

Subtracting before checking can revert underflow in 0.8.x. If the intention is to gracefully block transfers, pre-check the balance.

-            uint256 newBalance = pinakion.balanceOf(_from) - _amount;
+            uint256 bal = pinakion.balanceOf(_from);
+            if (bal < _amount) return false; // Gracefully block insufficient balance.
+            uint256 newBalance = bal - _amount;
             if (newBalance < jurors[_from].stakedTokens || newBalance < jurors[_from].lockedTokens) return false;

140-145: Minor: simplify NON_PAYABLE_AMOUNT for readability

Equivalent value, clearer intent, avoids exponentiation in constant expression.

-    uint256 public constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH.
+    // 2**255 - 1; An amount higher than the supply of ETH.
+    uint256 public constant NON_PAYABLE_AMOUNT = type(uint256).max / 2;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5353ccf and 3d211cc.

📒 Files selected for processing (3)
  • contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol (3 hunks)
  • contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol (1 hunks)
  • contracts/src/libraries/CappedMath.sol (0 hunks)
💤 Files with no reviewable changes (1)
  • contracts/src/libraries/CappedMath.sol
⏰ 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). (3)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
🔇 Additional comments (2)
contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol (1)

12-12: Import path verified and correct

  • SortitionSumTreeFactory.sol exists at contracts/src/kleros-v1/libraries/SortitionSumTreeFactory.sol.
  • The only import in the contracts/ directory is in contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol and uses the updated relative path:
    - import {SortitionSumTreeFactory} from "../kleros-liquid/libraries/SortitionSumTreeFactory.sol";
    + import {SortitionSumTreeFactory} from "../libraries/SortitionSumTreeFactory.sol";
  • No other occurrences of the old import path remain.

All good to merge.

contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol (1)

317-319: No remaining CappedMath references – subtraction logic is safe

All Solidity contracts and tests were searched for CappedMath, capSub, and “saturating” references; none remain. The new ternary-based subtraction:

uint256 requiredAmount = _moderation.paidFees[uint256(_side)] >= _totalRequired
    ? 0
    : _totalRequired - _moderation.paidFees[uint256(_side)];

correctly mirrors saturating behavior (returns zero instead of underflow). No further changes needed.

Comment on lines +205 to +206
uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
uint256 depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
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

Replace unchecked arithmetic with safe calculations or add overflow protection.

The direct arithmetic operations could potentially overflow, especially when arbitrationCost and multipliers are large values. The removal of CappedMath library eliminates the built-in overflow protection that was previously in place.

Consider adding explicit overflow checks or using SafeMath for these calculations:

-        uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
-        uint256 depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
+        uint256 totalCost;
+        uint256 depositRequired;
+        
+        // Check for overflow in multiplication
+        require(arbitrationCost == 0 || totalCostMultiplier <= type(uint256).max / arbitrationCost, "Overflow in totalCost calculation");
+        totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
+        
+        require(totalCost == 0 || initialDepositMultiplier <= type(uint256).max / totalCost, "Overflow in depositRequired calculation");
+        depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
📝 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
uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
uint256 depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
uint256 totalCost;
uint256 depositRequired;
// Check for overflow in multiplication
require(
arbitrationCost == 0 ||
totalCostMultiplier <= type(uint256).max / arbitrationCost,
"Overflow in totalCost calculation"
);
totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
require(
totalCost == 0 ||
initialDepositMultiplier <= type(uint256).max / totalCost,
"Overflow in depositRequired calculation"
);
depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;

Comment on lines +245 to +250
uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;

uint256 opposition = 3 - uint256(_side);
uint256 depositRequired = moderation.paidFees[opposition] * 2;
if (depositRequired == 0) {
depositRequired = totalCost.mulCap(initialDepositMultiplier) / MULTIPLIER_DIVISOR;
depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
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

Apply consistent overflow protection for arithmetic operations.

Similar to the previous concern, these calculations need overflow protection since the CappedMath library was removed.

Add overflow checks for these calculations:

-        uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
+        // Check for overflow in multiplication
+        require(arbitrationCost == 0 || totalCostMultiplier <= type(uint256).max / arbitrationCost, "Overflow in totalCost calculation");
+        uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;

         uint256 opposition = 3 - uint256(_side);
         uint256 depositRequired = moderation.paidFees[opposition] * 2;
         if (depositRequired == 0) {
-            depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
+            require(totalCost == 0 || initialDepositMultiplier <= type(uint256).max / totalCost, "Overflow in depositRequired calculation");
+            depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
📝 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
uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
uint256 opposition = 3 - uint256(_side);
uint256 depositRequired = moderation.paidFees[opposition] * 2;
if (depositRequired == 0) {
depositRequired = totalCost.mulCap(initialDepositMultiplier) / MULTIPLIER_DIVISOR;
depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
// Check for overflow in multiplication
require(arbitrationCost == 0 || totalCostMultiplier <= type(uint256).max / arbitrationCost, "Overflow in totalCost calculation");
uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;
uint256 opposition = 3 - uint256(_side);
uint256 depositRequired = moderation.paidFees[opposition] * 2;
if (depositRequired == 0) {
require(totalCost == 0 || initialDepositMultiplier <= type(uint256).max / totalCost, "Overflow in depositRequired calculation");
depositRequired = (totalCost * initialDepositMultiplier) / MULTIPLIER_DIVISOR;
🤖 Prompt for AI Agents
In contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol around lines
245 to 250, the arithmetic that used to rely on CappedMath
(multiplications/divisions and a "* 2") needs explicit overflow-safe operations
now that CappedMath was removed; add OpenZeppelin SafeMath usage (add "using
SafeMath for uint256" at the top of the contract and replace raw * and /
operations here with SafeMath calls) so compute totalCost, the doubled paidFees
and the initial deposit calculation via .mul(...).div(...) to ensure
overflow/underflow safety.

@@ -245,12 +242,12 @@
ArbitratorData storage arbitratorData = arbitratorDataList[moderation.arbitratorDataID];

uint256 arbitrationCost = arbitrator.arbitrationCost(arbitratorData.arbitratorExtraData);
uint256 totalCost = arbitrationCost.mulCap(totalCostMultiplier) / MULTIPLIER_DIVISOR;
uint256 totalCost = (arbitrationCost * totalCostMultiplier) / MULTIPLIER_DIVISOR;

uint256 opposition = 3 - uint256(_side);
uint256 depositRequired = moderation.paidFees[opposition] * 2;
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 potential overflow in opposition calculation and depositRequired multiplication.

The multiplication moderation.paidFees[opposition] * 2 could overflow if paidFees[opposition] is extremely large.

Add overflow protection for the doubling calculation:

         uint256 opposition = 3 - uint256(_side);
-        uint256 depositRequired = moderation.paidFees[opposition] * 2;
+        uint256 depositRequired;
+        require(moderation.paidFees[opposition] <= type(uint256).max / 2, "Overflow in depositRequired calculation");
+        depositRequired = moderation.paidFees[opposition] * 2;
📝 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
uint256 depositRequired = moderation.paidFees[opposition] * 2;
uint256 opposition = 3 - uint256(_side);
- uint256 depositRequired = moderation.paidFees[opposition] * 2;
+ uint256 depositRequired;
+ require(
+ moderation.paidFees[opposition] <= type(uint256).max / 2,
+ "Overflow in depositRequired calculation"
+ );
+ depositRequired = moderation.paidFees[opposition] * 2;
🤖 Prompt for AI Agents
contracts/src/arbitration/evidence/ModeratedEvidenceModule.sol around line 248:
the multiplication `moderation.paidFees[opposition] * 2` can overflow; add an
explicit overflow guard before computing depositRequired by either using
SafeMath.mul or a require check such as require(moderation.paidFees[opposition]
<= type(uint256).max / 2, "deposit overflow") and then perform the
multiplication, or if the project uses Solidity >=0.8 rely on built-in checks
but still add a clear require/error message to document the constraint.

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

♻️ Duplicate comments (1)
contracts/CHANGELOG.md (1)

135-136: Remove unpublished 0.13.0 release link to avoid 404

The 0.13.0 tag is marked “Not published yet,” and this link will 404 until it’s live. Comment it out and re-enable upon publishing.

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

If you’d like, I can provide a small script to check link availability before merging.

🧹 Nitpick comments (4)
contracts/CHANGELOG.md (2)

20-20: Tighten wording for clarity (Voting period rule)

Improve readability and remove ambiguity.

-- Do not pass to Voting period if all the commits are cast because it breaks the current Shutter auto-reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))
+- Do not advance to the Voting period when all commits are already cast, as this breaks the current Shutter auto-reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))

30-30: Use “info” instead of “infos”

Minor grammar fix.

-- Add helper function `getDisputeKitsViem` to retrieve a deployment's available dispute kit infos including their capabilities (`isShutter`, `isGated`) ([`5a81f9e`](https://github.com/kleros/kleros-v2/commit/5a81f9ec))
+- Add helper function `getDisputeKitsViem` to retrieve a deployment's available dispute kit info, including their capabilities (`isShutter`, `isGated`) ([`5a81f9e`](https://github.com/kleros/kleros-v2/commit/5a81f9ec))
contracts/src/arbitration/KlerosCoreBase.sol (1)

1179-1181: ⚠️ Renaming InvalidDisputKitParent Is a Breaking Change
The misspelled error selector appears throughout the codebase’s ABIs and deployment artifacts. Renaming it will require updating all of the following (and likely more) to avoid runtime and tooling breakages:

• contracts/src/arbitration/KlerosCoreBase.sol (line 1179)
• contracts/src/arbitration/university/KlerosCoreUniversity.sol (line 1156)
• All ABI-migration JSONs under subgraph/core-neo/abi-migrations and subgraph/core/abi-migrations
• contracts/deployments/.{viem.ts,.json} (Testnet, Mainnet, Devnet, ArbitrumSepoliaDevnet, etc.)
• Any subgraphs, front-end code, or integrations relying on the current selector name

If you still want to correct the typo, update every reference accordingly and regenerate all ABIs/deployments. Otherwise, it’s safer to leave the existing name until the next major version bump.

contracts/test/foundry/KlerosCore.t.sol (1)

1568-1569: Commit-period tests updated to typed errors and explicit time warps — correct with new policy

  • Line 1568: NotCommitPeriod is asserted before entering commit — correct.
  • Lines 1571–1578: Evidence period must elapse; test now warps accordingly — matches KlerosCoreBase change.
  • Lines 1585, 1591: EmptyCommit and ownership checks verified via selectors — good coverage.
  • Lines 1626, 1631, 1634: Vote-phase transition and reveal mismatch checks are now asserted by selector — correct.

These updates align with the new time-gated commit policy and custom-error migration.

You can reduce repetition by extracting small helpers, e.g., warpToPeriod(Period) and createAndDraw(disputeID, nbJurors), to streamline many tests that follow identical staging patterns.

Also applies to: 1571-1573, 1585-1587, 1591-1593, 1626-1627, 1631-1632, 1634-1636

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3d211cc and 257870c.

📒 Files selected for processing (3)
  • contracts/CHANGELOG.md (2 hunks)
  • contracts/src/arbitration/KlerosCoreBase.sol (1 hunks)
  • contracts/test/foundry/KlerosCore.t.sol (16 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). (17)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • 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
  • 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: SonarCloud
  • GitHub Check: contracts-testing
  • GitHub Check: SonarCloud
  • GitHub Check: contracts-testing
🔇 Additional comments (12)
contracts/src/arbitration/KlerosCoreBase.sol (1)

569-573: Commit→Vote is now purely time-gated — good call to avoid Shutter auto-reveal breakage

Removing the areCommitsAllCast fast-path and relying only on the elapsed commit period clarifies behavior and avoids early transitions that could interfere with auto-reveal flows. Tests appear adapted to time progression.

  • Side-effect to acknowledge: early progression to vote is no longer possible even if all commits are in; off-chain tooling/UI must respect the full commit period.

If any scripts or dapps previously triggered passPeriod early upon full commit, ensure they now wait out the commit period. Do you want me to scan the repo for any helper scripts that call passPeriod during commit to suggest updates?

contracts/test/foundry/KlerosCore.t.sol (11)

1140-1141: Typed errors for delayed-stake execution checks — solid migration

Asserting SortitionModuleBase.NoDelayedStakeToExecute and NotStakingPhase by selector improves brittleness and bytecode size over string reason checks. The scenarios covered are correct.

Also applies to: 1153-1155


1273-1274: Snapshot proxy GovernorOnly checks switched to error selectors — LGTM

The negative access tests now validate KlerosCoreSnapshotProxy.GovernorOnly via selector. Good parity with the production change to custom errors.

Also applies to: 1280-1281


1676-1683: Commit timeout gate now enforced with CommitPeriodNotPassed — test reflects the new behavior

Good verification that early pass from commit to vote is disallowed until the full commit period elapses.


1703-1704: Vote-period negative paths switched to custom errors — comprehensive coverage

  • NotVotePeriod, EmptyVoteIDs, ChoiceOutOfBounds, JurorHasToOwnTheVote, and VoteAlreadyCast are now asserted via selectors.
  • Scenarios remain accurate and thorough.

Also applies to: 1721-1722, 1727-1728, 1731-1732, 1739-1741


1905-1909: Quick pass to appeal when all reveals (for committed votes) are done — test matches passPeriod logic

This validates the early Vote→Appeal transition path using areVotesAllCast while commit remains time-gated. Good separation of concerns in tests.


1975-1976: Appeal funding error-paths moved to typed errors — correct

  • ChoiceOutOfBounds for invalid side.
  • AppealFeeIsAlreadyPaid once a side is funded.
    Selectors are used consistently with contract changes.

Also applies to: 1999-2001


2028-2030: Appeal-period timing checks updated to typed errors — robust

  • AppealPeriodIsOver and AppealPeriodIsOverForLoser paths covered at mid and end intervals.
  • Matches the refined revert policy.

Also applies to: 2035-2037, 2043-2045


2214-2216: Prevent operations on the original DK after a parent-DK jump — correct guard

Asserting DisputeKitClassicBase.DisputeJumpedToParentDK on draw protects against stale DK usage post-jump. Good regression coverage.


2598-2600: Leftover PNK withdrawal eligibility uses typed error — accurate

Asserting NotEligibleForWithdrawal before execute finishes unlocks is the right invariant.


2868-2869: Withdrawal gating by dispute resolution and pause state — selectors verify both constraints

  • DisputeNotResolved before ruling execution.
  • CoreIsPaused prevents withdrawal while paused.
    Good enforcement of lifecycle and pause semantics.

Also applies to: 2875-2876


2973-2975: Casting on the wrong DK is blocked — selector-based assertion is correct

NotActiveForCoreDisputeID ensures calls are routed to the DK that owns the dispute after DK changes. Strong safety check.

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

🔭 Outside diff range comments (3)
web/src/components/DisputeView/CardLabels/index.tsx (3)

73-85: Monetary math bug: converting Number → BigInt loses precision; sum in wei using BigInt

Number(val.amount) and Number(val.rewardAmount) can overflow and lose precision before converting back to BigInt, leading to incorrect ETH values shown to users. Do the accumulation entirely in wei (BigInt), then format.

Apply:

-const getFundingRewards = (contributions: ClassicContribution[], closed: boolean) => {
-  if (isUndefined(contributions) || contributions.length === 0) return 0;
-  const contribution = contributions.reduce((acc, val) => {
-    if (isUndefined(val?.rewardAmount) && isUndefined(val?.amount)) return acc;
-    if (closed) {
-      acc += val.rewardAmount === null ? -1 * Number(val.amount) : Number(val.rewardAmount) - Number(val.amount);
-    } else {
-      acc += Number(val.amount);
-    }
-    return acc;
-  }, 0);
-  return Number(formatUnits(BigInt(contribution), 18));
-};
+const getFundingRewards = (contributions: ClassicContribution[] | undefined, closed: boolean) => {
+  if (isUndefined(contributions) || contributions.length === 0) return 0;
+  const totalWei = contributions.reduce<bigint>((acc, val) => {
+    const amountWei = isUndefined(val?.amount) ? 0n : BigInt(val.amount);
+    const rewardWei =
+      isUndefined(val?.rewardAmount) || val.rewardAmount === null ? null : BigInt(val.rewardAmount);
+    if (closed) {
+      return acc + (rewardWei === null ? -amountWei : rewardWei - amountWei);
+    }
+    return acc + amountWei;
+  }, 0n);
+  return Number(formatUnits(totalWei, 18));
+};

103-111: Guard against undefined and simplify flattening of contributions

If localRounds is undefined, the current expression returns undefined instead of []. Also, val.contributions may be undefined. Use flatMap with nullish coalescing.

Apply:

-  const contributions = useMemo(
-    () =>
-      localRounds?.reduce((acc, val) => {
-        acc.push(...val.contributions);
-        return acc;
-      }, []),
-    [localRounds]
-  );
+  const contributions = useMemo(
+    () => localRounds?.flatMap((val) => val?.contributions ?? []) ?? [],
+    [localRounds]
+  );

95-100: Ensure safe optional chaining for array lengths
Directly accessing .length after optional chaining can throw at runtime when the value is undefined. Replace all unsafe length checks with a null-coalescing fallback.

Locations needing fixes:
• web/src/components/DisputeView/CardLabels/index.tsx
– Line 96: currentRound?.drawnJurors.length
– Line 98: item?.drawnJurors.length
– Line 99: localRounds?.[round]?.contributions.length
• web/src/pages/Cases/CaseDetails/Evidence/index.tsx
– Line 133: evidences?.spamEvidences.length

Proposed refactor:

--- a/web/src/components/DisputeView/CardLabels/index.tsx
+++ b/web/src/components/DisputeView/CardLabels/index.tsx
@@ -95,7 +95,7 @@
-  const isDrawnCurrentRound = currentRound?.drawnJurors.length !== 0;
+  const isDrawnCurrentRound = (currentRound?.drawnJurors?.length ?? 0) !== 0;
@@ -97,7 +97,7 @@
-  const isDrawnInDispute = rounds?.some((item) => item?.drawnJurors.length);
+  const isDrawnInDispute = (rounds?.some((item) => (item?.drawnJurors?.length ?? 0) > 0) ?? false);
@@ -98,7 +98,7 @@
-  const hasFundedCurrentRound = localRounds?.[round]?.contributions.length !== 0;
+  const hasFundedCurrentRound = (localRounds?.[round]?.contributions?.length ?? 0) !== 0;
--- a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx
@@ -131,7 +131,7 @@
-  {spamEvidences && evidences?.spamEvidences.length !== 0 ? (
+  {spamEvidences && (evidences?.spamEvidences?.length ?? 0) !== 0 ? (
     <>
       <Divider />
       {/* ... */}
🧹 Nitpick comments (1)
web/src/components/DisputeView/CardLabels/index.tsx (1)

159-162: Nit: remove stray whitespace node

The standalone {" "} adds an extra text node and is unnecessary.

Apply:

-          <RewardsContainer>
-            {" "}
+          <RewardsContainer>
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 257870c and 5ca5c3b.

📒 Files selected for processing (1)
  • web/src/components/DisputeView/CardLabels/index.tsx (1 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: contracts-testing
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: SonarCloud
  • GitHub Check: Scorecards analysis
🔇 Additional comments (1)
web/src/components/DisputeView/CardLabels/index.tsx (1)

64-71: Copy updates improve clarity and user-centric tone — LGTM

The new phrasing (“You were not drawn”, “You can vote now”, etc.) is clearer and more actionable. Icons and colors remain consistent.

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

🧹 Nitpick comments (1)
web/src/pages/Cases/CaseDetails/Timeline.tsx (1)

106-142: Fix subitems countdown misalignment when Commit is hidden

When hiddenVotes is false and the on-chain period is Commit (Periods.commit), the Commit step is omitted from the UI but getSubitems still compares against currentPeriodIndex from the raw timeline. That makes the highlighted “Voting” step show a static per-period duration instead of the current countdown (or “Time’s up!”). The root cause is using raw indices from titles after filtering out Commit.

Refactor to compute a visible titles array and compare against a display index aligned with the filtered list. Also make “Time’s up!” robust for negative values and parse timesPerPeriod strings before formatting.

Apply this diff:

-const useTimeline = (dispute: DisputeDetailsQuery["dispute"], currentPeriodIndex: number) => {
-  const isDesktop = useIsDesktop();
-  const titles = ["Evidence", "Commit", "Voting", "Appeal", "Executed"];
-
-  const deadlineCurrentPeriod = getDeadline(
-    currentPeriodIndex,
-    dispute?.lastPeriodChange,
-    dispute?.court.timesPerPeriod
-  );
-
-  const countdown = useCountdown(deadlineCurrentPeriod);
-  const getSubitems = (index: number): string[] | React.ReactNode[] => {
-    if (typeof countdown !== "undefined" && dispute) {
-      if (index === titles.length - 1) {
-        return [];
-      } else if (index === currentPeriodIndex && countdown === 0) {
-        return ["Time's up!"];
-      } else if (index < currentPeriodIndex) {
-        return [];
-      } else if (index === currentPeriodIndex) {
-        return [secondsToDayHourMinute(countdown)];
-      } else {
-        return [secondsToDayHourMinute(dispute?.court.timesPerPeriod[index])];
-      }
-    }
-    return [<StyledSkeleton key={index} width={60} />];
-  };
-  return titles.flatMap((title, i) => {
-    // if not hidden votes, skip commit index
-    if (!dispute?.court.hiddenVotes && i === Periods.commit) return [];
-    return [
-      {
-        title: i + 1 < titles.length && isDesktop ? `${title} Period` : title,
-        subitems: getSubitems(i),
-      },
-    ];
-  });
-};
+const useTimeline = (dispute: DisputeDetailsQuery["dispute"], currentPeriodIndex: number) => {
+  const isDesktop = useIsDesktop();
+  const allTitles = ["Evidence", "Commit", "Voting", "Appeal", "Executed"];
+  const hiddenVotes = Boolean(dispute?.court.hiddenVotes);
+  // Filter Commit out of the visible timeline when hidden votes are disabled.
+  const visibleTitles = hiddenVotes ? allTitles : allTitles.filter((_, i) => i !== Periods.commit);
+  // Index of the current item in the visible timeline (aligned with visibleTitles).
+  const displayCurrentIndex = currentPeriodToCurrentItem(currentPeriodIndex, hiddenVotes);
+
+  const deadlineCurrentPeriod = getDeadline(
+    currentPeriodIndex,
+    dispute?.lastPeriodChange,
+    dispute?.court.timesPerPeriod
+  );
+
+  const countdown = useCountdown(deadlineCurrentPeriod);
+
+  // Map a visible index back to the raw period index (for timesPerPeriod).
+  const toRawIndex = (i: number) => (hiddenVotes ? i : i >= Periods.commit ? i + 1 : i);
+
+  const getSubitems = (visibleIndex: number): string[] | React.ReactNode[] => {
+    if (typeof countdown !== "undefined" && dispute) {
+      // Last item ("Executed") shows no subitems.
+      if (visibleIndex === visibleTitles.length - 1) {
+        return [];
+      }
+      if (visibleIndex === displayCurrentIndex && countdown <= 0) {
+        return ["Time's up!"];
+      }
+      if (visibleIndex < displayCurrentIndex) {
+        return [];
+      }
+      if (visibleIndex === displayCurrentIndex) {
+        return [secondsToDayHourMinute(Math.max(0, countdown))];
+      }
+      // Future periods: show configured duration for that period.
+      const rawIndex = toRawIndex(visibleIndex);
+      const perPeriod = dispute?.court.timesPerPeriod?.[rawIndex];
+      return [secondsToDayHourMinute(perPeriod ? parseInt(perPeriod, 10) : 0)];
+    }
+    return [<StyledSkeleton key={visibleIndex} width={60} />];
+  };
+
+  return visibleTitles.map((title, visibleIndex) => ({
+    title: visibleIndex + 1 < visibleTitles.length && isDesktop ? `${title} Period` : title,
+    subitems: getSubitems(visibleIndex),
+  }));
+};

Why this helps:

  • The visible list and the display index stay aligned when Commit is omitted.
  • Subitems correctly show countdown (or “Time’s up!”) for the highlighted step.
  • Per-period durations are parsed to numbers before formatting (avoids type issues).
  • Time’s up uses <= 0 and clamps negative countdowns for a cleaner UX.

If you prefer a smaller change, I can generate a minimal patch that preserves flatMap but corrects the index comparisons only.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca5c3b and bd4e169.

📒 Files selected for processing (1)
  • web/src/pages/Cases/CaseDetails/Timeline.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web/src/pages/Cases/CaseDetails/Timeline.tsx (2)
web/src/utils/date.ts (1)
  • secondsToDayHourMinute (10-16)
web/src/components/StyledSkeleton.tsx (1)
  • StyledSkeleton (8-10)
⏰ 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: contracts-testing
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
  • GitHub Check: SonarCloud
  • GitHub Check: Scorecards analysis
🔇 Additional comments (1)
web/src/pages/Cases/CaseDetails/Timeline.tsx (1)

71-71: All useTimeline calls updated to the new signature

I ran a repo-wide search for any three-argument invocations of useTimeline and found none. The only call site at web/src/pages/Cases/CaseDetails/Timeline.tsx:71 already uses the updated (dispute, currentPeriodIndex) signature—no legacy usages remain.

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

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)

742-744: Consider a non-reentrancy guard around execute()

execute() performs multiple external interactions (token transfers, ETH sends, SortitionModule calls) before final storage writes; the comment accurately notes CEI is not strictly followed. Add a nonReentrant guard to the function or refactor to isolate effects before interactions to reduce reentrancy surface. Given the number of external calls, a guard is the simplest robust mitigation.

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

138-140: Add active-dispute guard in notJumped modifier (prevents accidental reads of dispute 0)

Accessing disputes[coreDisputeIDToLocal[_coreDisputeID]] without first verifying coreDisputeIDToActive can read local dispute 0 for unknown IDs and rely on later checks. Guard early to fail fast.

Apply this diff:

 modifier notJumped(uint256 _coreDisputeID) {
-    if (disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped) revert DisputeJumpedToParentDK();
+    if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID();
+    if (disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped) revert DisputeJumpedToParentDK();
     _;
 }
🧹 Nitpick comments (4)
contracts/src/arbitration/university/KlerosCoreUniversity.sol (2)

783-783: Event coherence parameter: confirm intended meaning

You now emit the computed coherence value to TokenAndETHShift. Confirm downstream indexers/analytics expect this to represent “coherence (basis points)” rather than a legacy name (degreeOfCoherency). Names aren’t part of ABI, but aligning docs/specs avoids confusion.

Also applies to: 860-860


1159-1159: Typo in error name: InvalidDisputKitParent

Minor naming nit: consider renaming to InvalidDisputeKitParent for clarity and consistency.

Apply this diff:

-    error InvalidDisputKitParent();
+    error InvalidDisputeKitParent();
contracts/test/foundry/KlerosCore.t.sol (1)

1551-1561: Reduce duplication with small test helpers

You repeatedly advance periods (warp + passPhase/passPeriod) and set up disputes. Consider helper functions for:

  • createAndDrawDispute(...)
  • advanceToCommit/vote/appeal/execution(...)
    This will shrink boilerplate and make scenarios easier to compose.

Also applies to: 1677-1683, 1895-1900, 2052-2058, 2238-2244

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

480-491: NatSpec param ordering is misleading (nit)

hashVote signature is (choice, salt, justification), but the docs list justification before salt. Swap the param docs to match the signature.

Apply this diff:

-     * @param _justification The justification for the vote
-     * @param _salt A random salt for commitment
+     * @param _salt A random salt for commitment
+     * @param _justification The justification for the vote
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd4e169 and 96e48f8.

📒 Files selected for processing (8)
  • contracts/src/arbitration/KlerosCoreBase.sol (5 hunks)
  • contracts/src/arbitration/SortitionModuleBase.sol (7 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (10 hunks)
  • contracts/src/arbitration/interfaces/IDisputeKit.sol (2 hunks)
  • contracts/src/arbitration/university/KlerosCoreUniversity.sol (7 hunks)
  • contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol (1 hunks)
  • contracts/src/libraries/Constants.sol (1 hunks)
  • contracts/test/foundry/KlerosCore.t.sol (18 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol
  • contracts/src/arbitration/university/KlerosCoreUniversity.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/SortitionModuleBase.sol
⏰ 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: contracts-testing
  • GitHub Check: SonarCloud
  • GitHub Check: contracts-testing
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (32)
contracts/src/libraries/Constants.sol (1)

23-24: LGTM! Clean introduction of a standardized basis point constant.

The addition of ONE_BASIS_POINT constant with value 10000 provides a clear, reusable denominator for basis point calculations across the codebase. This improves consistency and readability compared to scattered magic numbers or individual ALPHA_DIVISOR constants.

contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol (1)

12-12: Import path verified: SortitionSumTreeFactory.sol exists in contracts/src/kleros-v1/libraries/ and has been removed from the old location. No further changes needed.

contracts/src/arbitration/interfaces/IDisputeKit.sol (3)

3-3: Pragma range broadened appropriately for library compatibility.

The pragma was relaxed from ^0.8.24 to >=0.8.0 <0.9.0, providing better compatibility across different Solidity versions while maintaining the v0.8.x feature set.


64-78: Well-structured API separation for reward vs penalty coherence calculations.

The split of the original getDegreeOfCoherence function into separate reward and penalty functions improves API clarity:

  • getDegreeOfCoherenceReward returns both PNK and fee coherence values
  • getDegreeOfCoherencePenalty returns only PNK coherence for penalty calculations

The documentation clearly explains the different return values and use cases.


80-93: Good separation of penalty-specific coherence logic.

The dedicated getDegreeOfCoherencePenalty function provides a clean interface for penalty calculations, returning only the PNK coherence value needed for that specific use case.

contracts/src/arbitration/SortitionModuleBase.sol (8)

124-131: Improved error handling with custom errors.

The replacement of string-based require statements with custom errors (GovernorOnly(), KlerosCoreOnly()) provides better gas efficiency and clearer error identification. The logic remains identical while improving the developer experience.


174-186: Enhanced phase transition validation with descriptive errors.

The phase transition logic now uses custom errors that clearly describe each failure condition:

  • MinStakingTimeNotPassed() - Time-based guard
  • NoDisputesThatNeedJurors() - State-based guard
  • RandomNumberNotReady() - External dependency guard
  • DisputesWithoutJurorsAndMaxDrawingTimeNotPassed() - Combined condition guard

This improves debuggability while maintaining the same validation logic.


200-201: Clear validation with descriptive custom errors.

The tree creation validation uses appropriate custom errors:

  • TreeAlreadyExists() for duplicate creation attempts
  • KMustBeGreaterThanOne() for invalid tree parameters

209-210: Proper phase and state validation for delayed stake execution.

The function correctly validates both the current phase (NotStakingPhase()) and the availability of work (NoDelayedStakeToExecute()) before proceeding with delayed stake execution.


419-419: Consistent error handling for withdrawal eligibility.

The NotEligibleForWithdrawal() error provides clear feedback when a juror attempts to withdraw PNK but doesn't meet the eligibility criteria.


443-443: Appropriate phase validation for drawing operations.

The NotDrawingPhase() error ensures that drawing operations can only occur during the correct phase, maintaining the state machine integrity.


692-708: Well-organized custom error definitions.

The comprehensive set of custom errors at the end of the contract provides:

  • Clear naming conventions
  • Appropriate granularity for different failure scenarios
  • Complete coverage of all validation conditions used throughout the contract

This follows Solidity best practices for error handling.


347-355: No mismatch in currentCourtID when calling core.courts

The auto-generated getter for the courts mapping uses a uint256 key. In particular:

  • contracts/src/test/KlerosCoreMock.sol:
    function getCourtChildren(uint256 _courtId) { children = courts[_courtId].children; }

This shows the mapping key is uint256, and Solidity will implicitly upcast a uint96 to uint256. You can safely remove the type-consistency check.

contracts/src/arbitration/KlerosCoreBase.sol (7)

568-572: Clarification comment improves understanding of commit period logic.

The added comment explains that the commit period advancement is intentionally time-gated rather than completion-gated to maintain compatibility with Shutter auto-reveal functionality. This is a valuable clarification for future developers.


768-779: Robust penalty calculation with coherence capping.

The penalty calculation logic correctly:

  1. Uses the new getDegreeOfCoherencePenalty API
  2. Implements a safety guard against coherence values exceeding 100% (ONE_BASIS_POINT)
  3. Applies the penalty using the standardized basis point calculation

The coherence capping is a defensive programming practice that prevents unexpected behavior from dispute kit implementations.


782-782: Correct penalty calculation using standardized constant.

The penalty calculation (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - coherence)) / ONE_BASIS_POINT properly uses the new standardized constant instead of the old ALPHA_DIVISOR, ensuring consistency across the codebase.


827-841: Well-structured reward calculation with dual coherence values.

The reward calculation logic correctly handles the new API that returns separate coherence values for PNK and fee rewards:

  1. Destructures the tuple from getDegreeOfCoherenceReward
  2. Applies safety guards to both coherence values
  3. Uses appropriate coherence values for different reward types

This separation allows for more nuanced reward calculations while maintaining safety bounds.


844-853: Consistent application of coherence to different reward types.

The code correctly applies:

  • pnkCoherence for PNK-related calculations (locked amount and PNK rewards)
  • feeCoherence for fee reward calculations

This distinction allows for different coherence models for different reward types, providing flexibility in the dispute resolution mechanism.


1063-1067: Clean helper function with updated documentation.

The _applyCoherence function is correctly updated to use ONE_BASIS_POINT with appropriate parameter documentation. The function signature and implementation are clear and consistent with the new coherence calculation standard.


1074-1075: Standardized PNK calculation using unified constant.

The _calculatePnkAtStake function correctly uses ONE_BASIS_POINT instead of the old ALPHA_DIVISOR, maintaining consistency with the codebase-wide standardization effort.

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

11-11: Constants import aligns with new basis-point semantics

Importing Constants.sol is appropriate given the use of ONE_BASIS_POINT throughout the file.


528-528: Basis-point staking math: LGTM (double-check alpha scale across callers)

Switching pnkAtStakePerJuror to (minStake * alpha) / ONE_BASIS_POINT correctly aligns with basis-point semantics. As a sanity check, ensure that all alpha inputs across court governance are expressed in the same ONE_BASIS_POINT scale (e.g., 10_000 == 100%).

Also applies to: 657-657


756-767: Defensive clamp on coherence from DK is fine

Clamping coherence to ONE_BASIS_POINT guards the core from DK misbehavior. It's a minor gas cost and acceptable for safety.


770-770: Penalty formula matches intended semantics

Using penalty = pnkAtStakePerJuror * (1 - coherence) / ONE_BASIS_POINT correctly yields 0 penalty for fully coherent jurors and full penalty when coherence is 0.


821-835: Reward coherence clamping mirrors penalty side — good

Clamping both pnkCoherence and feeCoherence mirrors the penalty path and prevents accidental over-rewarding.


837-837: Reward distribution math is sound

  • Unlocking pnkLocked = pnkAtStakePerJuror * pnkCoherence / ONE_BASIS_POINT releases the coherent portion of locked PNK.
  • pnkReward and feeReward prorate by coherence, which allows partial coherence without breaking ties.

Net unlock across penalty+reward passes sums to the originally locked stake (as expected).

Also applies to: 843-847

contracts/test/foundry/KlerosCore.t.sol (2)

1140-1141: Migration to custom error selectors: LGTM

Replacing string-based revert reasons with error selectors improves gas and consistency across the suite. The covered assertions map cleanly to the new error surface.

Also applies to: 1153-1155, 1273-1274, 1280-1281, 1568-1569, 1626-1627, 1631-1632, 1634-1635, 1703-1704, 1721-1722, 1727-1728, 1731-1732, 1975-1976, 2028-2029, 2036-2037, 2043-2044


2316-2341: New coherence tests validate reward/penalty paths well

Good additions validating:

  • getDegreeOfCoherenceReward returns (pnk, fee) coherences in basis points.
  • getDegreeOfCoherencePenalty aligns with the reward side and zero-coherence scenarios.
    These tests exercise the new DK API and the basis-point arithmetic effectively.

Also applies to: 2426-2444

contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (4)

9-9: ONE_BASIS_POINT import centralizes basis-point arithmetic

Good move to reuse the shared constant across DK logic.


272-275: Commit/vote validation flow: LGTM

  • Period checks moved to custom errors.
  • Empty commit/voteIDs and DK activity checks are explicit.
  • Juror ownership and hidden-vote hash validation are correct.
  • Choice bounds check permits 0 as “refuse to arbitrate”, matching the contract’s stated model.

Also applies to: 314-316, 319-319, 328-332


362-367: Appeal funding window and paid-state checks: correct and explicit

  • AppealPeriodIsOver vs. AppealPeriodIsOverForLoser timing split is consistent with LOSER_APPEAL_PERIOD_MULTIPLIER.
  • AppealFeeIsAlreadyPaid prevents double-funding for a choice.

Also applies to: 374-378, 385-385


537-541: Coherence API split (reward/penalty) and internal computation: LGTM

  • getDegreeOfCoherenceReward returns identical pnk/fee coherences for classic; extensible for DK variants.
  • Penalty coherence exposed separately; internal _getDegreeOfCoherence returns 0/ONE_BASIS_POINT as expected based on voted/tie/winner.

Also applies to: 547-556, 557-573

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

♻️ Duplicate comments (1)
contracts/CHANGELOG.md (1)

137-137: Remove unpublished release link for 0.13.0 to avoid 404.

You note “Not published yet” in the header; the link currently points to a tag that likely doesn’t exist.

-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
+<!-- [0.13.0]: (add after publishing) -->
🧹 Nitpick comments (1)
contracts/CHANGELOG.md (1)

22-22: Minor grammar: drop “the”.

Smoother phrasing in changelog prose.

- Do not pass to Voting period if all the commits are cast because it breaks the current Shutter auto-reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))
+ Do not pass to the Voting period if all commits are cast because it breaks the current Shutter auto-reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96e48f8 and 0f31e0e.

📒 Files selected for processing (1)
  • contracts/CHANGELOG.md (2 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). (5)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: contracts-testing
  • GitHub Check: contracts-testing
🔇 Additional comments (1)
contracts/CHANGELOG.md (1)

14-14: Clarify CHANGELOG entry for widened pragma scope

To accurately reflect the files updated, please update the entry in contracts/CHANGELOG.md to:

- Widen the allowed solc version to any v0.8.x for the interfaces only ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
+ Widen the allowed pragma to `>=0.8.0 <0.9.0` for interfaces and RNG contracts (`RNG.sol`, `IRandomizer.sol`) ([#2083](https://github.com/kleros/kleros-v2/issues/2083))

optional_refactors_recommended

- Set the Foundry Solidity version to v0.8.30 and enable the IR pipeline ([#2073](https://github.com/kleros/kleros-v2/issues/2073))
- Widen the allowed solc version to any v0.8.x for the interfaces only ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
- Make `IDisputeKit.getDegreeOfCoherenceReward()` multi-dimensional so different calculations may be applied to PNK rewards, fee rewards and PNK penalties (future-proofing) ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
- Consolidate the constant `ALPHA_DIVISOR` with `ONE_BASIS_POINTS` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
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

Fix constant name and phrasing (typo).

The constant is ONE_BASIS_POINT (singular), not ONE_BASIS_POINTS. Also “consolidate with” is imprecise here.

Apply:

- Consolidate the constant `ALPHA_DIVISOR` with `ONE_BASIS_POINTS` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
+ Replace `ALPHA_DIVISOR` with `ONE_BASIS_POINT` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
📝 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
- Consolidate the constant `ALPHA_DIVISOR` with `ONE_BASIS_POINTS` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
- Replace `ALPHA_DIVISOR` with `ONE_BASIS_POINT` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
🤖 Prompt for AI Agents
In contracts/CHANGELOG.md around line 16, the changelog entry uses the wrong
constant name and imprecise phrasing; change "ONE_BASIS_POINTS" to the correct
constant name "ONE_BASIS_POINT" (singular) and replace "Consolidate the constant
... with" with clearer wording such as "Consolidate the constant ALPHA_DIVISOR
into ONE_BASIS_POINT" or "Replace ALPHA_DIVISOR with ONE_BASIS_POINT" to
accurately reflect the change.

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
60 Security Hotspots
E Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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

Caution

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

⚠️ Outside diff range comments (8)
contracts/deploy/change-arbitrable-dispute-template.ts (1)

36-38: Fix: instanceof EventLog will throw/fail under ethers v6

EventLog is a type (not a runtime class/constructor) in ethers v6, so event instanceof EventLog will either always be false or throw “Right-hand side of 'instanceof' is not callable”. Use a structural check (e.g., 'args' in event) or parse logs via the contract interface.

Apply this minimal fix:

-  tx?.logs?.forEach((event) => {
-    if (event instanceof EventLog) console.log("event: %O", event.args);
-  });
+  tx?.logs?.forEach((event) => {
+    if (typeof event === "object" && event !== null && "args" in event) {
+      console.log("event: %O", (event as any).args);
+    } else {
+      console.log("log: %O", event);
+    }
+  });
contracts/test/arbitration/staking.ts (3)

77-77: Use BigInt literals in assertions to match ethers v6 return types

Several assertions compare BigInt-returning values to number literals (e.g., 0, 2). With ethers v6, Solidity uints decode to bigint, and chai deep equality will fail across types. Replace number literals with BigInt (0n, 2n, etc.).

Representative fixes below (apply similarly to other occurrences in this file):

-        expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([PNK(4000), 0, PNK(2000), 2]);
+        expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([PNK(4000), 0n, PNK(2000), 2n]);
-          expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([
+          expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([
             PNK(5000),
             PNK(300), // we're the only juror so we are drawn 3 times
             PNK(3000),
-            2,
+            2n,
           ]); // stake unchanged, delayed
-        expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([PNK(4000), 0, PNK(2000), 2]);
+        expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([PNK(4000), 0n, PNK(2000), 2n]);
-          expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([
+          expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([
             PNK(3000),
             PNK(300), // we're the only juror so we are drawn 3 times
             PNK(1000),
-            2,
+            2n,
           ]); // stake unchanged, delayed
-          expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([
+          expect(await sortition.getJurorBalance(deployer, 2)).to.be.deep.equal([
             PNK(4000),
             PNK(300), // we're the only juror so we are drawn 3 times
             PNK(2000),
-            2,
+            2n,
           ]); // stake unchanged, delayed

And similarly for the other repeated getJurorBalance deep.equal checks in this file.

Also applies to: 89-89, 115-120, 142-142, 178-183, 265-270, 291-293, 326-327, 353-358


123-124: Normalize delayedStakes assertions to BigInt

Likewise, delayedStakes returns uints (BigInt). Replace 0 with 0n for the numeric fields:

-          expect(await sortition.delayedStakes(1)).to.be.deep.equal([ethers.ZeroAddress, 0, 0, false]); // the 1st delayed stake got deleted
+          expect(await sortition.delayedStakes(1)).to.be.deep.equal([ethers.ZeroAddress, 0n, 0n, false]); // the 1st delayed stake got deleted

Apply the same change to the other delayedStakes[...] assertions in this file.

Also applies to: 186-188, 273-275, 361-363


386-386: BigInt expected for getJurorCourtIDs

Consistently use BigInt literals for court IDs to match ethers v6 decoding:

-      expect(await sortition.getJurorCourtIDs(deployer)).to.be.deep.equal([1, 2]);
+      expect(await sortition.getJurorCourtIDs(deployer)).to.be.deep.equal([1n, 2n]);
contracts/scripts/disputeRelayerBot.ts (2)

107-109: Bug: missing await on tx.wait() leads to broken logging and race on mining

tx = tx.wait(); assigns a Promise to tx. The next line attempts to read transactionHash from a Promise, which will be undefined and can crash or log incorrectly. Also, you likely want to ensure the tx is mined before proceeding/logging.

Apply this fix:

-      tx = tx.wait();
-      logger.info(`relayCreateDispute txId: ${tx.transactionHash}`);
+      const receipt = await tx.wait();
+      logger.info(`relayCreateDispute txId: ${receipt.transactionHash}`);

71-78: Guard against missing DisputeRequest logs and fix unconditional warning

  • If no DisputeRequest logs are present, disputeRequests[0] is undefined and subsequent property access will throw.
  • The warning "More than 1 DisputeRequest event..." is logged unconditionally; it should be conditional and the message can be clearer.

Apply:

-      const disputeRequests: DisputeRequestEventObject[] = fullTxReceipt.logs
+      const disputeRequests: DisputeRequestEventObject[] = fullTxReceipt.logs
         .filter((log) => log.topics[0] === arbitrableInterface.getEventTopic("DisputeRequest"))
         .map((log) => arbitrableInterface.parseLog(log).args as unknown as DisputeRequestEventObject);
-      logger.warn(`More than 1 DisputeRequest event: not supported yet, skipping the others events.`);
+      if (disputeRequests.length === 0) {
+        logger.error("No DisputeRequest events found in transaction; skipping.");
+        return;
+      }
+      if (disputeRequests.length > 1) {
+        logger.warn("More than one DisputeRequest event found in tx; not supported yet — skipping others.");
+      }
 
       const disputeRequest = disputeRequests[0];
       logger.info(`tx events DisputeRequest: ${JSON.stringify(disputeRequest)}`);
contracts/test/arbitration/dispute-kit-gated.ts (1)

24-25: Name collision: local PNK() helper shadows imported PNK type (compile error)

You import PNK from typechain-types (used as the contract type), then declare a const PNK helper. This redeclares the identifier and will fail to compile.

Rename the helper (or import the type via import type { PNK as PNKToken } and update its usage). Minimal change below renames the helper and its only usage.

Apply:

-  const PNK = (amount: BigNumberish) => toBigInt(amount) * 10n ** 18n;
+  const pnkUnits = (amount: BigNumberish) => toBigInt(amount) * 10n ** 18n;
@@
-  const minStake = PNK(200);
+  const minStake = pnkUnits(200);

Alternative (optional): make the import type-only to avoid value-namespace collisions.

-import { 
-  PNK,
+import type {
+  PNK,
   KlerosCore,
   SortitionModule,
   IncrementalNG,
   DisputeKitGated,
   TestERC20,
   TestERC721,
   TestERC1155,
 } from "../../typechain-types";

Also applies to: 30-31, 40-41

contracts/deploy/00-home-chain-arbitration-neo.ts (1)

47-58: Ensure SortitionModuleNeo.initialize args include the RNG lookahead
The initialize function in contracts/src/arbitration/SortitionModuleNeo.sol still expects eight parameters—specifically a _rngLookahead value between _rng and _maxStakePerJuror. The deploy script is currently passing:

  • deployer (governor)
  • klerosCoreAddress (core)
  • minStakingTime
  • maxDrawingTime
  • rngWithFallback.target
  • maxStakePerJuror
  • maxTotalStaked

…which omits the rngLookahead argument and shifts all subsequent parameters out of place.

Locations to update:

  • contracts/deploy/00-home-chain-arbitration-neo.ts (the args array for SortitionModuleNeo)

Suggested diff:

   const sortitionModule = await deployUpgradable(deployments, "SortitionModuleNeo", {
     from: deployer,
     args: [
       deployer,
       klerosCoreAddress,
       minStakingTime,
       maxFreezingTime,
       rngWithFallback.target,
-      maxStakePerJuror,
-      maxTotalStaked,
+      rngLookahead,           // ← insert this
+      maxStakePerJuror,
+      maxTotalStaked,
     ],
   });
♻️ Duplicate comments (2)
contracts/CHANGELOG.md (2)

21-21: Fix constant name (typo) and phrasing

The constant is ONE_BASIS_POINT (singular). “Consolidate … with” is imprecise; “Replace” is clearer.

Apply:

- - Consolidate the constant `ALPHA_DIVISOR` with `ONE_BASIS_POINTS` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))
+ - Replace `ALPHA_DIVISOR` with `ONE_BASIS_POINT` ([#2090](https://github.com/kleros/kleros-v2/issues/2090))

142-143: Remove unpublished 0.13.0 release link to avoid 404

The 0.13.0 link 404s until the tag is published. Hide it until release is live.

Apply:

-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]
+<!-- [0.13.0]: (add after publishing) -->
🧹 Nitpick comments (48)
contracts/deploy/change-arbitrable-dispute-template.ts (4)

4-4: Remove or make EventLog a type-only import

After replacing instanceof, the runtime import is unnecessary and may cause bundlers to look for a non-existent value export. Remove it (or convert to import type if you still annotate types elsewhere).

-import { EventLog } from "ethers";

35-36: Nit: rename tx to receipt and avoid “double-await” confusion

The variable holds a receipt, not a transaction; renaming improves readability.

-  let tx = await (await arbitrable.changeDisputeTemplate(template, "disputeTemplateMapping: TODO")).wait();
-  tx?.logs?.forEach((event) => {
+  const receipt = await (await arbitrable.changeDisputeTemplate(template, "disputeTemplateMapping: TODO")).wait();
+  receipt?.logs?.forEach((event) => {

35-35: Resolve placeholder mapping before merging

"disputeTemplateMapping: TODO" looks like a placeholder that will be persisted on-chain. Consider sourcing this from config/env or deriving it deterministically (e.g., a versioned identifier or keccak256 of the template).

Would you like me to wire this to an env var (with a clear error if missing) or derive it from the template content and network?


41-43: Optional: Declare dependencies/id to make the deploy script idempotent in hardhat-deploy

To ensure execution order and avoid accidental re-runs of this side-effectful script, consider adding dependencies and an id.

Example:

 deployResolver.tags = ["ArbitrableDisputeTemplate"];
+deployResolver.dependencies = ["ArbitrableExample"];
+deployResolver.id = "change-arbitrable-dispute-template@v1";

This helps hardhat-deploy track and order this script relative to the deployment of ArbitrableExample.

contracts/deploy/00-home-chain-resolver.ts (2)

7-9: Remove unused deployments.deploy destructuring

deploy is no longer used after switching to getContractOrDeploy; this may trip noUnusedLocals and fail type-checking.

Apply:

-  const { deploy } = deployments;

18-22: Optional: log the resolved address for traceability

Capturing and logging the DisputeResolver instance helps when auditing CI logs and manual runs.

Apply:

-  await getContractOrDeploy(hre, "DisputeResolver", {
+  const disputeResolver = await getContractOrDeploy(hre, "DisputeResolver", {
     from: deployer,
     args: [klerosCore.address, disputeTemplateRegistry.address],
     log: true,
   });
+  console.log("DisputeResolver at %s", disputeResolver.address);
contracts/test/arbitration/staking.ts (3)

2-2: Prefer type-only imports for TypeChain contract types to avoid runtime imports

Import the TypeChain types as type-only to prevent unintended runtime imports and avoid confusion with the local PNK constant in value space.

-import { PNK, KlerosCore, SortitionModule, ChainlinkRNG, ChainlinkVRFCoordinatorV2Mock } from "../../typechain-types";
+import type { PNK, KlerosCore, SortitionModule, ChainlinkRNG, ChainlinkVRFCoordinatorV2Mock } from "../../typechain-types";

Optional: consider renaming the local PNK value helper (Line 10) to something like toPNK to reduce mental overhead when reading types vs. values.


63-63: Avoid hardcoding VRF requestId = 1; derive it from the emitted event

Hardcoding 1 for fulfillRandomWords can become brittle if deployment order or previous requests change. Prefer extracting the requestId from the RNG request event emitted during passPhase.

Example approach:

  • Capture the tx receipt from sortition.passPhase() (or wherever the RNG request is made).
  • Parse logs for the RNG contract’s “request created” event and read its requestId.
  • Pass that requestId to fulfillRandomWords.

This reduces flakiness and future-proofs the test if request sequencing changes.

Also applies to: 396-396


129-130: Fix misleading comments to match assertions

Two comments contradict the surrounding test expectations.

-          expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore - PNK(1000)); // No PNK transfer
+          expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore - PNK(1000)); // PNK transferred after delayed stake execution
-          expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore + PNK(1000)); // No PNK transfer yet
+          expect(await pnk.balanceOf(deployer)).to.be.equal(balanceBefore + PNK(1000)); // PNK withdrawn after delayed stake execution

Also applies to: 191-193

contracts/scripts/disputeRelayerBot.ts (4)

49-54: Heartbeat is fire-and-forget; consider awaiting and handling errors

Not awaiting means network errors are swallowed. Optional, but better observability if you log status or errors.

   if (HEARTBEAT_URL) {
     logger.debug("Sending heartbeat");
-    fetch(HEARTBEAT_URL);
+    try {
+      const res = await fetch(HEARTBEAT_URL, { method: "GET" });
+      logger.debug(`Heartbeat status: ${res.status}`);
+    } catch (err) {
+      logger.warn(`Heartbeat failed: ${(err as Error).message}`);
+    }
   } else {
     logger.debug("Heartbeat not set up, skipping");
   }

59-110: Wrap event handler body in try/catch to avoid unhandled rejections

The callback is async and performs multiple awaits. Any thrown error becomes an unhandled rejection on the event loop. Wrap the body to log context and bail cleanly.

   foreignGateway.on(
     "CrossChainDisputeOutgoing",
     async (foreignBlockHash, foreignArbitrable, foreignDisputeID, choices, extraData, txReceipt) => {
-      logger.info(
-        `CrossChainDisputeOutgoing: ${foreignBlockHash} ${foreignArbitrable} ${foreignDisputeID} ${choices} ${extraData}`
-      );
-      logger.debug(`tx receipt: ${JSON.stringify(txReceipt)}`);
+      try {
+        logger.info(
+          `CrossChainDisputeOutgoing: ${foreignBlockHash} ${foreignArbitrable} ${foreignDisputeID} ${choices} ${extraData}`
+        );
+        logger.debug(`tx receipt: ${JSON.stringify(txReceipt)}`);
 
-      // txReceipt is missing the full logs for this tx so we need to request it here
-      const fullTxReceipt = await foreignChainProvider.getTransactionReceipt(txReceipt.transactionHash);
+        // txReceipt is missing the full logs for this tx so we need to request it here
+        const fullTxReceipt = await foreignChainProvider.getTransactionReceipt(txReceipt.transactionHash);
 
-      // Retrieve the DisputeRequest event
+        // Retrieve the DisputeRequest event
         const disputeRequests: DisputeRequestEventObject[] = fullTxReceipt.logs
           .filter((log) => log.topics[0] === arbitrableInterface.getEventTopic("DisputeRequest"))
           .map((log) => arbitrableInterface.parseLog(log).args as unknown as DisputeRequestEventObject);
-      logger.warn(`More than 1 DisputeRequest event: not supported yet, skipping the others events.`);
+        if (disputeRequests.length === 0) {
+          logger.error("No DisputeRequest events found in transaction; skipping.");
+          return;
+        }
+        if (disputeRequests.length > 1) {
+          logger.warn("More than one DisputeRequest event found in tx; not supported yet — skipping others.");
+        }
 
-      const disputeRequest = disputeRequests[0];
-      logger.info(`tx events DisputeRequest: ${JSON.stringify(disputeRequest)}`);
+        const disputeRequest = disputeRequests[0];
+        logger.info(`tx events DisputeRequest: ${JSON.stringify(disputeRequest)}`);
 
-      const relayCreateDisputeParams = {
+        const relayCreateDisputeParams = {
           foreignBlockHash,
           foreignChainID,
           foreignArbitrable,
           foreignDisputeID,
           externalDisputeID: disputeRequest._externalDisputeID,
           templateId: disputeRequest._templateId,
           templateUri: disputeRequest._templateUri,
           choices,
           extraData,
-      };
-      logger.info(`Relaying dispute to home chain... ${JSON.stringify(relayCreateDisputeParams)}`);
+        };
+        logger.info(`Relaying dispute to home chain... ${JSON.stringify(relayCreateDisputeParams)}`);
 
-      let tx;
-      if (feeToken === undefined) {
+        let tx;
+        if (feeToken === undefined) {
           // Paying in native Arbitrum ETH
           const cost = (await core.functions["arbitrationCost(bytes)"](extraData)).cost;
           tx = await homeGateway.functions[
             "relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes))"
           ](relayCreateDisputeParams, { value: cost });
-      } else {
+        } else {
           // Paying in ERC20
           const cost = (await core.functions["arbitrationCost(bytes,address)"](extraData, feeToken.address)).cost;
           await (await feeToken.approve(homeGateway.address, cost)).wait();
           tx = await homeGateway.functions[
             "relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes),uint256)"
           ](relayCreateDisputeParams, cost);
-      }
-      tx = tx.wait();
-      logger.info(`relayCreateDispute txId: ${tx.transactionHash}`);
+        }
+        const receipt = await tx.wait();
+        logger.info(`relayCreateDispute txId: ${receipt.transactionHash}`);
+      } catch (err) {
+        logger.error({ err }, "Failed to relay dispute");
+      }
     }
   );

56-61: Deduplicate processing to prevent double-relay on restarts

You already note callbacks can run more than once. Consider deduping per unique key (e.g., ${foreignBlockHash}-${foreignDisputeID}) in-memory and/or via a lightweight store to avoid double spending fees if the home chain call doesn’t revert.

  • Minimal: in-memory Set for current process lifetime.
  • Robust: persistent KV (Redis/S3) keyed by tx hash or event log index.

I can provide a small utility for this if helpful.


112-114: Type the delay helper

Minor typing improvement for clarity and IntelliSense.

-  const delay = (ms) => new Promise((x) => setTimeout(x, ms));
+  const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
contracts/test/integration/index.ts (1)

206-208: Make helper non-async (nit)

logJurorBalance is declared async but contains no awaits. Removing async avoids returning a Promise unnecessarily.

Apply:

-const logJurorBalance = async (result: { totalStaked: bigint; totalLocked: bigint }) => {
+const logJurorBalance = (result: { totalStaked: bigint; totalLocked: bigint }) => {
   console.log("staked=%s, locked=%s", ethers.formatUnits(result.totalStaked), ethers.formatUnits(result.totalLocked));
 };
contracts/test/proxy/index.ts (1)

7-8: Use type-only imports for TypeChain types to avoid runtime module resolution and reduce overhead

These symbols are only used as generic type arguments; importing them as types ensures TS erases the import at runtime and avoids potential module resolution issues with deep, file-qualified TypeChain paths.

Apply this diff:

-import { UpgradedByRewrite as UpgradedByRewriteV1 } from "../../typechain-types/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol";
+import type { UpgradedByRewrite as UpgradedByRewriteV1 } from "../../typechain-types/src/proxy/mock/by-rewrite/UpgradedByRewrite.sol";
-import { UpgradedByRewrite as UpgradedByRewriteV2 } from "../../typechain-types/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol";
+import type { UpgradedByRewrite as UpgradedByRewriteV2 } from "../../typechain-types/src/proxy/mock/by-rewrite/UpgradedByRewriteV2.sol";

Additionally, consider converting the root TypeChain imports on Line 6 to type-only as well (outside the selected range):

import type { UpgradedByInheritanceV1, UpgradedByInheritanceV2 } from "../../typechain-types";
contracts/src/test/RNGMock.sol (2)

2-2: Unify pragma to repo-wide constraint for consistency.

Other new files in this PR use >=0.8.0 <0.9.0. Consider aligning to avoid drift and ease compiler config.

Apply this diff:

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;

16-18: Match interface mutability for clarity.

IRNG.receiveRandomness is intentionally non-view (implementations may emit). While overriding with view compiles, matching the interface signature avoids confusion.

Use:

-    function receiveRandomness() external view override returns (uint256) {
+    function receiveRandomness() external override returns (uint256) {
         return randomNumber;
     }
contracts/src/rng/RNGWithFallback.sol (3)

2-2: Consider aligning pragma with repo-wide range.

Most files in this PR use >=0.8.0 <0.9.0. Aligning avoids mixed version constraints.

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;

73-79: Optional guardrails on governance setters.

Changing critical roles/timeouts to zero can brick usage or make fallback immediate. Add zero-address/zero-timeout checks and emit events for governor/consumer changes for traceability.

Example:

-    function changeGovernor(address _newGovernor) external onlyByGovernor {
-        governor = _newGovernor;
-    }
+    event GovernorChanged(address indexed previousGovernor, address indexed newGovernor);
+    function changeGovernor(address _newGovernor) external onlyByGovernor {
+        require(_newGovernor != address(0), "Governor=0");
+        emit GovernorChanged(governor, _newGovernor);
+        governor = _newGovernor;
+    }

-    function changeConsumer(address _consumer) external onlyByGovernor {
-        consumer = _consumer;
-    }
+    event ConsumerChanged(address indexed previousConsumer, address indexed newConsumer);
+    function changeConsumer(address _consumer) external onlyByGovernor {
+        require(_consumer != address(0), "Consumer=0");
+        emit ConsumerChanged(consumer, _consumer);
+        consumer = _consumer;
+    }

-    function changeFallbackTimeout(uint256 _fallbackTimeoutSeconds) external onlyByGovernor {
+    function changeFallbackTimeout(uint256 _fallbackTimeoutSeconds) external onlyByGovernor {
+        require(_fallbackTimeoutSeconds > 0, "Timeout=0");
         fallbackTimeoutSeconds = _fallbackTimeoutSeconds;
         emit FallbackTimeoutChanged(_fallbackTimeoutSeconds);
     }

93-101: Edge-case: consider checking a request exists before falling back.

If receiveRandomness is called without a prior request, this would still fall back after timeout=0. Not critical given SortitionModule flow, but adding requestTimestamp != 0 makes the intent explicit.

-        if (randomNumber == 0 && block.timestamp > requestTimestamp + fallbackTimeoutSeconds) {
+        if (randomNumber == 0 && requestTimestamp != 0 && block.timestamp > requestTimestamp + fallbackTimeoutSeconds) {
             randomNumber = uint256(blockhash(block.number - 1));
             emit RNGFallback();
         }
contracts/deploy/00-home-chain-arbitration-university.ts (1)

74-82: Typed core instance and guarded currency-rate updates — LGTM.

The try/catch is pragmatic for non-critical deploy steps. Consider logging token addresses alongside errors for easier troubleshooting.

-  } catch (e) {
-    console.error("Failed to change currency rates for token, with error:", e);
+  } catch (e) {
+    console.error(
+      "Failed to change currency rates",
+      { pnk: await pnk.getAddress(), dai: await dai.getAddress(), weth: await weth.getAddress() },
+      "error:",
+      e
+    );
   }
contracts/deploy/change-sortition-module-rng.ts (2)

26-27: Make changeConsumer idempotent and wait for confirmations

Guard the call to avoid an unnecessary transaction if the consumer is already set, and explicitly wait for mining to avoid timing issues with subsequent ops.

Apply this diff:

-  console.log(`chainlinkRng.changeConsumer(${sortitionModule.target})`);
-  await chainlinkRng.changeConsumer(sortitionModule.target);
+  const currentConsumer = await chainlinkRng.consumer();
+  if (currentConsumer !== sortitionModule.target) {
+    console.log(`chainlinkRng.changeConsumer(${sortitionModule.target})`);
+    const tx1 = await chainlinkRng.changeConsumer(sortitionModule.target);
+    await tx1.wait();
+  } else {
+    console.log("chainlinkRng.consumer already set; skipping");
+  }

29-30: Wait for tx confirmation when switching RNG on the SortitionModule

Explicitly awaiting the receipt reduces flakiness in CI and when composing deployments.

-  console.log(`sortitionModule.changeRandomNumberGenerator(${chainlinkRng.target})`);
-  await sortitionModule.changeRandomNumberGenerator(chainlinkRng.target);
+  console.log(`sortitionModule.changeRandomNumberGenerator(${chainlinkRng.target})`);
+  const tx2 = await sortitionModule.changeRandomNumberGenerator(chainlinkRng.target);
+  await tx2.wait();
contracts/src/arbitration/SortitionModuleNeo.sol (3)

77-102: Consider updating totalStaked only after validating outcome

You update totalStaked before delegating to super._validateStake. If the base returns a non-success StakingResult, totalStaked would be mutated without the stake actually changing, causing drift.

  • Move the totalStaked increment/decrement to after the super._validateStake(...) call.
  • Update it only if the returned stakingResult indicates the change will be applied (e.g., Success/OK).

Please confirm the exact success enum value in StakingResult and whether super._validateStake can return a non-successful result without reverting.


35-36: Clarify the docstring for maxStakePerJuror

The code checks juror.stakedPnk + stakeChange, which enforces a per-juror total cap across courts. The doc says “in a court”, which is misleading.

-    /// @param _maxStakePerJuror The maximum amount of PNK a juror can stake in a court.
-    /// @param _maxTotalStaked The maximum amount of PNK that can be staked in all courts.
+    /// @param _maxStakePerJuror The maximum total amount of PNK a juror can stake across all courts.
+    /// @param _maxTotalStaked The maximum total amount of PNK that can be staked across all jurors and courts.

3-3: Pragma version is inconsistent with the PR-wide version update

The PR states a pragma update (e.g., >=0.8.0 <0.9.0 or 0.8.30), but this file still uses ^0.8.24.

If the project-wide target is a range, align it:

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;

Or pin to the agreed version if that’s the standard.

contracts/deploy/00-rng-randomizer.ts (4)

5-5: Import RandomizerRNG for proper typing

You use rng.consumer()/rng.changeConsumer(...) later; a typed instance avoids TS “unknown property” issues and improves DX.

-import { RNGWithFallback } from "../typechain-types";
+import { RNGWithFallback, RandomizerRNG } from "../typechain-types";

23-31: Return a typed contract instance for rng

getContractOrDeploy returns a generic Contract; follow it by grabbing a typed instance for calls.

-  const rng = await getContractOrDeploy(hre, "RandomizerRNG", {
+  await getContractOrDeploy(hre, "RandomizerRNG", {
     from: deployer,
     args: [
       deployer,
       deployer, // The consumer is configured as the RNGWithFallback later
       randomizerOracle.target,
     ],
     log: true,
   });
+  // Use a typed instance for subsequent calls
+  const rng = await ethers.getContract<RandomizerRNG>("RandomizerRNG");

33-44: Parameterize fallback timeout via env var with a sane default

This makes the deployment configurable without code changes.

-  const fallbackTimeoutSeconds = 30 * 60; // 30 minutes
+  const fallbackTimeoutSeconds =
+    process.env.RNG_FALLBACK_TIMEOUT_SECONDS
+      ? Number(process.env.RNG_FALLBACK_TIMEOUT_SECONDS)
+      : 30 * 60; // Default: 30 minutes

45-51: Wait for tx confirmation when switching the consumer

You already guard the call; add wait() to ensure it’s mined before the script exits.

   const rngWithFallback = await ethers.getContract<RNGWithFallback>("RNGWithFallback");
   const rngConsumer = await rng.consumer();
   if (rngConsumer !== rngWithFallback.target) {
     console.log(`rng.changeConsumer(${rngWithFallback.target})`);
-    await rng.changeConsumer(rngWithFallback.target);
+    const tx = await rng.changeConsumer(rngWithFallback.target);
+    await tx.wait();
   }
contracts/src/rng/IncrementalNG.sol (2)

21-27: Docstring and behavior mismatch; also minor style

Comment says “always the same” but the function increments. Update the doc for clarity.

-    /// @dev Get the "random number" (which is always the same).
+    /// @dev Get the current pseudo-random number and increment it deterministically.

3-3: Pragma version is inconsistent with the repo-wide target

This file still uses ^0.8.24. Align to the PR’s chosen pragma.

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;
contracts/src/arbitration/SortitionModule.sol (1)

3-3: Pragma version alignment

Keep the pragma consistent with the PR-wide version update.

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;
contracts/deploy/00-rng-chainlink.ts (2)

60-64: Use HTTPS and include actionable context in the VRF registration hint

Small nit: the VRF site uses HTTPS. Also, including chainId and subId in the log saves time when registering.

Apply:

-  if (!oldRng) {
-    console.log("Register this Chainlink consumer here: http://vrf.chain.link/");
-  }
+  if (!oldRng) {
+    console.log(
+      `Reminder: register the subscription/consumer at https://vrf.chain.link/ (chainId=${chainId}, subId=${subscriptionId})`
+    );
+  }

79-89: Make fallback timeout configurable via env for different networks

30 min may be conservative or aggressive depending on network conditions. Suggest supporting an env override with a sanity check.

Apply:

-  const fallbackTimeoutSeconds = 30 * 60; // 30 minutes
+  const fallbackTimeoutSecondsRaw = process.env.FALLBACK_TIMEOUT_SECONDS;
+  const fallbackTimeoutSeconds =
+    fallbackTimeoutSecondsRaw !== undefined ? Number(fallbackTimeoutSecondsRaw) : 30 * 60; // default: 30 minutes
+  if (!Number.isFinite(fallbackTimeoutSeconds) || fallbackTimeoutSeconds <= 0) {
+    throw new Error(`Invalid FALLBACK_TIMEOUT_SECONDS=${fallbackTimeoutSecondsRaw}`);
+  }
contracts/CHANGELOG.md (1)

19-19: Clarify pragma widening scope and exact range

“for the interfaces only” is misleading given the broader changes described in the PR summary. Also, stating the exact range improves traceability.

Apply:

- - Widen the allowed solc version to any v0.8.x for the interfaces only ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
+ - Widen the allowed pragma to `>=0.8.0 <0.9.0` across interfaces (and other updated modules) ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
contracts/src/rng/ChainlinkRNG.sol (1)

94-98: Guard against setting consumer to the zero address and emit an event

Setting consumer to address(0) would brick requestRandomness(). Add a zero-address check and emit an event for observability.

Apply:

-    function changeConsumer(address _consumer) external onlyByGovernor {
-        consumer = _consumer;
-    }
+    function changeConsumer(address _consumer) external onlyByGovernor {
+        if (_consumer == address(0)) revert InvalidConsumer();
+        address previous = consumer;
+        consumer = _consumer;
+        emit ConsumerChanged(previous, _consumer);
+    }

And add these declarations (outside the selected range):

// Place near other errors or at the top of the contract
error InvalidConsumer();

// Place near other events
event ConsumerChanged(address indexed _previousConsumer, address indexed _newConsumer);
contracts/src/rng/RandomizerRNG.sol (4)

3-3: Align pragma with project-wide compiler constraint

PR summary states moving to >=0.8.0 <0.9.0. This file still uses ^0.8.24. Align to the project-wide constraint to avoid compiler skew across packages.

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;

53-62: Constructor: validate inputs (non-zero addresses) to prevent bricking the RNG

Guard against accidentally deploying with zero addresses for governor/consumer/randomizer. Cheap now, expensive later.

 constructor(address _governor, address _consumer, IRandomizer _randomizer) {
-        governor = _governor;
-        consumer = _consumer;
-        randomizer = _randomizer;
+        if (_governor == address(0) || _consumer == address(0) || address(_randomizer) == address(0)) {
+            revert InvalidAddress();
+        }
+        governor = _governor;
+        consumer = _consumer;
+        randomizer = _randomizer;
         callbackGasLimit = 50000;
 }

Add this error to the bottom error section:

error InvalidAddress();

74-78: Emit event and validate new consumer on changeConsumer()

  • Emit an event so off-chain infra and dashboards can track consumer rotations.
  • Guard against setting zero consumer.
-    function changeConsumer(address _consumer) external onlyByGovernor {
-        consumer = _consumer;
-    }
+    function changeConsumer(address _consumer) external onlyByGovernor {
+        if (_consumer == address(0)) revert InvalidAddress();
+        address old = consumer;
+        consumer = _consumer;
+        emit ConsumerChanged(old, _consumer);
+    }

Add the event (near the other events):

 /// @param randomWords The random values answering the request.
 event RequestFulfilled(uint256 indexed requestId, uint256 randomWords);
+/// Emitted when the consumer is changed by the governor.
+event ConsumerChanged(address indexed oldConsumer, address indexed newConsumer);

26-34: Nit: event docs are VRF-flavored; adjust for Randomizer.ai and single word

The comments mention “VRF Coordinator” and “random values” but this contract uses Randomizer.ai and a single 256-bit value. Consider tightening the docstrings for accuracy. No code changes required if you prefer to leave as-is.

contracts/src/rng/BlockhashRNG.sol (2)

3-3: Align pragma with project-wide compiler constraint

Align to >=0.8.0 <0.9.0 to match the PR-wide pragma update.

-pragma solidity ^0.8.24;
+pragma solidity >=0.8.0 <0.9.0;

46-50: Validate lookaheadTime to avoid degenerate configuration

A zero lookaheadTime removes the intended “request then wait” property and can allow immediate harvesting of current/previous block hash. Add a guard.

 constructor(address _governor, address _consumer, uint256 _lookaheadTime) {
-        governor = _governor;
-        consumer = _consumer;
-        lookaheadTime = _lookaheadTime;
+        if (_governor == address(0) || _consumer == address(0)) revert InvalidAddress();
+        if (_lookaheadTime == 0) revert InvalidLookaheadTime();
+        governor = _governor;
+        consumer = _consumer;
+        lookaheadTime = _lookaheadTime;
 }

Add errors at the bottom:

error InvalidAddress();
error InvalidLookaheadTime();
contracts/src/arbitration/SortitionModuleBase.sol (2)

483-487: Nit: prefer uint256 over uint for consistency

Minor style consistency. Not critical.

-        uint treeIndex = tree.IDsToNodeIndexes[_ID];
+        uint256 treeIndex = tree.IDsToNodeIndexes[_ID];

154-161: Action: document the RNG-consumer precondition or add a safe-guard in changeRandomNumberGenerator

Short summary — I verified the repo: deployment scripts and tests set the RNG consumer before swapping RNGs, but the SortitionModule still calls rng.requestRandomness(...) while in Phase.generating, so an operator mistake (omitting setting the RNG consumer) can revert and brick generating. Recommend documenting the operational requirement and (optionally) adding a sanity check / try-catch to surface a clearer error.

Evidence (selected locations):

  • Deploy scripts that set consumer before swap:
    • contracts/deploy/change-sortition-module-rng.ts — calls chainlinkRng.changeConsumer(sortitionModule.target) then sortitionModule.changeRandomNumberGenerator(...)
    • contracts/deploy/00-home-chain-arbitration.ts — checks rngWithFallback.consumer() and calls rngWithFallback.changeConsumer(sortitionModule.address) if needed
    • contracts/deploy/00-rng-randomizer.ts and contracts/deploy/00-rng-chainlink.ts — similar consumer checks/calls
  • Tests that perform consumer setup before swapping or run as governor:
    • contracts/test/arbitration/dispute-kit-gated.ts — calls sortitionModule.changeRandomNumberGenerator(rng.target)
    • contracts/test/arbitration/draw.ts — same
    • contracts/test/foundry/KlerosCore.t.sol — vm.prank(governor); sortitionModule.changeRandomNumberGenerator(...)
  • RNG contracts expose changeConsumer / consumer:
    • contracts/src/rng/ChainlinkRNG.sol, RandomizerRNG.sol, RNGWithFallback.sol, BlockhashRNG.sol (have changeConsumer / consumer functions)

Recommendation (concise)

  • Minimum: document in the SortitionModule API / deploy README that "When changing RNG while phase == generating the governor must set the new RNG's consumer to the SortitionModule before invoking changeRandomNumberGenerator — otherwise rng.requestRandomness() may revert."
  • Optional (recommended for safer UX): wrap the requestRandomness call in a try/catch and revert with a clear error (or emit an event) to avoid opaque reverts. Example minimal patch:
-        if (phase == Phase.generating) {
-            rng.requestRandomness(block.number + rngLookahead);
-            randomNumberRequestBlock = block.number;
-        }
+        if (phase == Phase.generating) {
+            // Prefer a clear revert instead of an opaque external revert (e.g. ConsumerOnly)
+            try rng.requestRandomness(block.number + rngLookahead) {
+                randomNumberRequestBlock = block.number;
+            } catch {
+                revert RNGRequestFailed(); // add: error RNGRequestFailed();
+            }
+        }

Tag:

contracts/test/rng/index.ts (3)

39-46: Avoid variable shadowing for readability

You already have a top-level deployer variable (string) and re-declare a local deployer. Rename the local to avoid confusion.

-    const [deployer] = await ethers.getSigners();
+    const [blockhashDeployer] = await ethers.getSigners();
...
-      from: deployer.address,
-      args: [deployer.address, deployer.address, 10], // governor, consumer, lookaheadTime (seconds)
+      from: blockhashDeployer.address,
+      args: [blockhashDeployer.address, blockhashDeployer.address, 10], // governor, consumer, lookaheadTime (seconds)

48-65: Add negative-path tests for BlockHashRNG access control

Since receiveRandomness() and requestRandomness() are ConsumerOnly, add tests that a non-consumer gets reverted. This guards regressions in modifiers and helps document the intended access model.

Example:

it("Should revert for non-consumer", async () => {
  const [, other] = await ethers.getSigners();
  await expect(rng.connect(other).requestRandomness()).to.be.revertedWithCustomError(rng, "ConsumerOnly");
  await expect(rng.connect(other).receiveRandomness.staticCall()).to.be.revertedWithCustomError(rng, "ConsumerOnly");
});

Also applies to: 72-81


98-99: Add negative-path tests for changeConsumer governor gating

Tests currently set consumer with the correct governor. Add a non-governor attempt to ensure GovernorOnly is enforced on both ChainlinkRNG and RandomizerRNG.

Example:

it("Should revert non-governor changeConsumer", async () => {
  const [, other] = await ethers.getSigners();
  await expect(rng.connect(other).changeConsumer(other.address)).to.be.revertedWithCustomError(rng, "GovernorOnly");
});

Also applies to: 161-162

@@ -1,7 +1,7 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { HomeChains, isSkipped } from "./utils";
import { deployUpgradable } from "./utils/deployUpgradable";
import { getContractOrDeploy } from "./utils/getContractOrDeploy";
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

getContractOrDeploy can skip required redeploys; this risks stale deployments

The helper returns early if a contract with the same name exists, bypassing hardhat-deploy’s bytecode/args/libraries diffing logic. If the artifact or constructor args change, this will not redeploy and you’ll silently keep the old instance. The prior pattern (always calling deployments.deploy) is idempotent and safer.

Recommended fix: update the helper to always call deployments.deploy and rely on newlyDeployed to detect reuse.

Proposed helper update (contracts/deploy/utils/getContractOrDeploy.ts):

import type { HardhatRuntimeEnvironment } from "hardhat/types";
import type { DeployOptions } from "hardhat-deploy/types";
import type { Contract } from "ethers";

export const getContractOrDeploy = async (
  hre: HardhatRuntimeEnvironment,
  contractName: string,
  options: DeployOptions
): Promise<Contract> => {
  const result = await hre.deployments.deploy(contractName, options);
  if (result.newlyDeployed) {
    console.log(`contract ${contractName} deployed at ${result.address}`);
  } else {
    console.log(`contract ${contractName} already deployed at ${result.address}`);
  }
  return hre.ethers.getContract<Contract>(contractName);
};

Run this quick scan to see all call sites using the helper (so we don’t miss any):


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "getContractOrDeploy call sites:"
rg -nP --type=ts -C2 '\bgetContractOrDeploy\s*\(' contracts
echo
echo "Current helper implementation (first 120 lines):"
sed -n '1,120p' contracts/deploy/utils/getContractOrDeploy.ts

Length of output: 7802


Enforce safe, idempotent deployments in getContractOrDeploy

The current helper in contracts/deploy/utils/getContractOrDeploy.ts only calls hre.deployments.deploy when no existing instance is found via getContractOrNull. This bypasses Hardhat-Deploy’s bytecode/args/libraries diff check, so changes to the artifact or constructor parameters won’t trigger a redeploy and you’ll continue using an outdated contract.

Required update:

  • Always invoke hre.deployments.deploy(contractName, options) and rely on the returned newlyDeployed flag to detect reuse.
  • Preserve logging of whether the contract was newly deployed or already in place.
  • Ensure all existing call sites (e.g., in 00-home-chain-resolver.ts and the other scripts listed by the scan) continue to work without modification.

Example refactored helper (contracts/deploy/utils/getContractOrDeploy.ts):

import type { HardhatRuntimeEnvironment } from "hardhat/types";
import type { DeployOptions } from "hardhat-deploy/types";
import { Contract } from "ethers";

export const getContractOrDeploy = async (
  hre: HardhatRuntimeEnvironment,
  contractName: string,
  options: DeployOptions
): Promise<Contract> => {
  const result = await hre.deployments.deploy(contractName, options);
  if (result.newlyDeployed) {
    console.log(`contract ${contractName} deployed at ${result.address}`);
  } else {
    console.log(`contract ${contractName} already deployed at ${result.address}`);
  }
  return hre.ethers.getContract<Contract>(contractName);
};

Please update the helper implementation accordingly to avoid stale deployments.

🤖 Prompt for AI Agents
In contracts/deploy/00-home-chain-resolver.ts around line 4, the project uses
getContractOrDeploy which currently skips calling hre.deployments.deploy when a
contract exists, bypassing Hardhat-Deploy’s diff checks; update
contracts/deploy/utils/getContractOrDeploy.ts so it always calls await
hre.deployments.deploy(contractName, options), read the returned newlyDeployed
flag to log whether it was newly deployed or reused, and then return the
contract via hre.ethers.getContract(contractName) so all existing call sites
(including 00-home-chain-resolver.ts) continue to work unchanged.

args: [
deployer,
deployer, // The consumer is configured as the SortitionModule later
fallbackTimeoutSeconds,
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

Confirm downstream wiring sets RNGWithFallback’s consumer to the SortitionModule

Comment states this will be configured later. Please confirm the corresponding script/tag runs after this one to complete the wiring.

You can verify with:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -nP --type=ts -C2 '\bgetContract<SortitionModule(Neo)?>\('
rg -nP --type=ts -C2 '\bRNGWithFallback\b' | rg -nP -C2 'changeConsumer\s*\('

Length of output: 5110


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Inspect the sortition module RNG change script for consumer wiring
sed -n '1,200p' contracts/deploy/change-sortition-module-rng.ts

Length of output: 1769


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Show 00-rng-randomizer.ts to verify consumer wiring
sed -n '1,200p' contracts/deploy/00-rng-randomizer.ts

Length of output: 2201


Wire RNGWithFallback’s consumer to the SortitionModule

We didn’t find any calls to rngWithFallback.changeConsumer(...) in the deploy scripts, so the fallback RNG never hands off to the SortitionModule. You need to add a step—either in 00-rng-randomizer.ts (or in a new deploy script that runs immediately afterward)—to point the fallback at the sortition module:

• File: contracts/deploy/00-rng-randomizer.ts
After deploying RNGWithFallback, fetch the SortitionModule and invoke changeConsumer:

  // …existing RNGWithFallback deployment…
  const rngWithFallback = await ethers.getContract<RNGWithFallback>("RNGWithFallback");

  // Wire fallback RNG into SortitionModule
  const sortitionModule = await ethers.getContract<SortitionModule>("SortitionModule");
  console.log(`rngWithFallback.changeConsumer(${sortitionModule.target})`);
  await rngWithFallback.changeConsumer(sortitionModule.target);

Ensure this runs after your other RNG scripts so that SortitionModule is configured to use the fallback.

🤖 Prompt for AI Agents
In contracts/deploy/00-rng-randomizer.ts around line 39, the RNGWithFallback
deployment is missing wiring to the SortitionModule so the fallback never hands
off; after deploying and obtaining the RNGWithFallback instance, fetch the
deployed SortitionModule contract and call RNGWithFallback.changeConsumer(...)
with the SortitionModule address (ensure this step runs after SortitionModule is
deployed and after other RNG deploy scripts so the fallback is configured to
point to the SortitionModule).

Comment on lines +79 to +101
function receiveRandomness() external override onlyByConsumer returns (uint256 randomNumber) {
if (requestTimestamp == 0) return 0; // No requests were made yet.

uint256 expectedTimestamp = requestTimestamp + lookaheadTime;

// Check if enough time has passed
if (block.timestamp < expectedTimestamp) {
return 0; // Not ready yet
}

// Check if we already have a saved random number for this timestamp window
randomNumber = randomNumbers[expectedTimestamp];
if (randomNumber != 0) {
return randomNumber;
}

if (_block < block.number) {
// The random number is not already set and can be.
if (blockhash(_block) != 0x0) {
// Normal case.
randomNumber = uint256(blockhash(_block));
} else {
// The contract was not called in time. Fallback to returning previous blockhash.
randomNumber = uint256(blockhash(block.number - 1));
}
// Use last block hash for randomness
randomNumber = uint256(blockhash(block.number - 1));
if (randomNumber != 0) {
randomNumbers[expectedTimestamp] = randomNumber;
}
randomNumbers[_block] = randomNumber;
return randomNumber;
}
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

Timing-manipulation vulnerability: randomness depends on the caller-chosen block after readiness

As written, once ready, receiveRandomness() derives the random number from blockhash(block.number - 1) at the time of the call and caches it by expectedTimestamp. Since receiveRandomness() is callable by the consumer (and, transitively, passPhase in SortitionModule can be triggered by anyone), the first caller after readiness effectively chooses the block used, enabling timing-based bias.

Mitigation: pre-commit the target block at request time and use that block’s hash after the time gate. One pragmatic approach:

  • Save requestBlock at request time.
  • After readiness, always derive from blockhash(requestBlock + 1) (or +k if you prefer).
  • Cache both the number and the block used to make replays deterministic.
  • Return 0 if the target block’s hash is no longer available (older than 256 blocks).
-    uint256 public requestTimestamp; // Timestamp of the current request
-    mapping(uint256 timestamp => uint256 number) public randomNumbers; // randomNumbers[timestamp] is the random number for this timestamp, 0 otherwise.
+    uint256 public requestTimestamp; // Timestamp of the current request
+    uint256 public requestBlock; // Block number at request time (anchor to remove caller timing bias).
+    mapping(uint256 timestamp => uint256 number) public randomNumbers; // randomNumbers[timestamp] is the random number for this timestamp window, 0 otherwise.
+    mapping(uint256 timestamp => uint256 blockNumber) public randomBlocks; // The block number used to derive that randomness.
-    function requestRandomness() external override onlyByConsumer {
-        requestTimestamp = block.timestamp;
-    }
+    function requestRandomness() external override onlyByConsumer {
+        requestTimestamp = block.timestamp;
+        requestBlock = block.number;
+    }
-        // Use last block hash for randomness
-        randomNumber = uint256(blockhash(block.number - 1));
-        if (randomNumber != 0) {
-            randomNumbers[expectedTimestamp] = randomNumber;
-        }
-        return randomNumber;
+        // Derive from a pre-committed block to avoid timing selection attacks.
+        uint256 targetBlock = requestBlock + 1;
+        randomNumber = uint256(blockhash(targetBlock));
+        if (randomNumber == 0) {
+            // Target block is either not yet mined (should not happen after time gate)
+            // or its hash is no longer available (>256 blocks old). Consider returning 0 to let caller retry or re-request.
+            return 0;
+        }
+        randomNumbers[expectedTimestamp] = randomNumber;
+        randomBlocks[expectedTimestamp] = targetBlock;
+        return randomNumber;

Comment on lines +47 to +55
modifier onlyByGovernor() {
if (governor != msg.sender) revert GovernorOnly();
_;
}

modifier onlyByConsumer() {
if (consumer != msg.sender) revert ConsumerOnly();
_;
}
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

Custom errors must be qualified with IRNG to compile.

GovernorOnly and ConsumerOnly are declared inside IRNG; unqualified references here will not resolve.

Apply:

     modifier onlyByGovernor() {
-        if (governor != msg.sender) revert GovernorOnly();
+        if (governor != msg.sender) revert IRNG.GovernorOnly();
         _;
     }

     modifier onlyByConsumer() {
-        if (consumer != msg.sender) revert ConsumerOnly();
+        if (consumer != msg.sender) revert IRNG.ConsumerOnly();
         _;
     }
📝 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
modifier onlyByGovernor() {
if (governor != msg.sender) revert GovernorOnly();
_;
}
modifier onlyByConsumer() {
if (consumer != msg.sender) revert ConsumerOnly();
_;
}
modifier onlyByGovernor() {
if (governor != msg.sender) revert IRNG.GovernorOnly();
_;
}
modifier onlyByConsumer() {
if (consumer != msg.sender) revert IRNG.ConsumerOnly();
_;
}
🤖 Prompt for AI Agents
In contracts/src/rng/RNGWithFallback.sol around lines 47 to 55, the modifiers
revert using unqualified custom errors GovernorOnly and ConsumerOnly which are
declared in the IRNG interface; update the revert calls to use the
fully-qualified names (revert IRNG.GovernorOnly(); and revert
IRNG.ConsumerOnly();) so the compiler can resolve them, keeping the rest of the
modifier logic unchanged.

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.

4 participants