-
Couldn't load subscription status.
- Fork 50
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
base: master
Are you sure you want to change the base?
Conversation
…use of @wagmi/cli
Feat: Dispute Kits helper in contracts package
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Enabled Hardhat viaIR compilation with solc v0.8.30
bumped @kleros/vea-contracts to v0.7.0
chore: viaIR compilation enabled for Foundry with explicit solc v0.8.30
… deprecated for later removal
feat: court llms.txt
✅ Deploy Preview for kleros-v2-testnet ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... 📒 Files selected for processing (46)
WalkthroughLarge refactor: contracts, deployments, tests and frontend updated for v0.13.0 — ownership and error-model changes, IRNG + RNGWithFallback, SortitionTrees extraction, SortitionModule/KlerosCore rewrites, dispute‑kit API expansions, tooling/deployment updates and many test additions/adjustments. Changes
Sequence Diagram(s)%%{init: {"theme":"base","themeVariables":{"actorBorder":"#2b2b2b","actorBg":"#f7fafc","noteBg":"#f0f4f8"}}}%%
sequenceDiagram
autonumber
participant SM as SortitionModule
participant RF as RNGWithFallback
participant PR as PrimaryRNG
participant BH as BlockhashRNG
SM->>RF: requestRandomness()
RF->>PR: requestRandomness() (forward to primary)
RF->>RF: record requestTimestamp
Note over SM,RF: when drawing / receiving randomness
SM->>RF: receiveRandomness()
alt Primary RNG returns value
RF-->>SM: primary random
else Primary RNG timed out or returns 0
RF->>BH: compute fallback from recent blockhash
RF-->>SM: fallback random (emit RNGFallback)
end
SM->>SM: draw(courtID, disputeID, nonce)
SM-->>Caller: (drawnAddress, fromSubcourtID)
%%{init: {"theme":"base","themeVariables":{"actorBorder":"#2b2b2b","actorBg":"#f7fafc","noteBg":"#f0f4f8"}}}%%
sequenceDiagram
autonumber
participant User
participant Core as KlerosCore
participant DK as IDisputeKit
participant SM as SortitionModule
User->>Core: createDispute(... + fee)
Core->>DK: getNbVotesAfterAppeal(prevDK, currentNbVotes)
DK-->>Core: nbVotesNext
Core->>DK: earlyCourtJump(coreDisputeID)?
alt DisputeKit requests jump
Core->>Core: select new court & dispute kit (getJumpDisputeKitID)
Core-->>User: emit CourtJump / DisputeKitJump
else normal path
Core->>SM: schedule postDraw actions
end
Note over Core,DK: subsequent draw/appeal/execution flows use updated signatures and storage gaps
Estimated code review effort🎯 5 (Critical) | ⏱️ ~150+ minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
Comment |
❌ Deploy Preview for kleros-v2-university failed. Why did it fail? →
|
❌ Deploy Preview for kleros-v2-neo-devtools failed. Why did it fail? →
|
❌ Deploy Preview for kleros-v2-testnet-devtools failed. Why did it fail? →
|
There was a problem hiding this 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 runsThe 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 compileIf 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
⛔ Files ignored due to path filters (1)
yarn.lockis 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.tscontracts/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.tscontracts/deployments/index.tscontracts/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.tscontracts/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.tscontracts/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 thatcontracts/deployments/arbitrumSepoliaDevnet.tsdefines the “devnet” deployment on Arbitrum Sepolia, matching the RPC endpoint used ingetDisputeKits(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
DisputeKitcorrectly 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
satisfiesfor type checking demonstrate good defensive programming practices. The logic correctly identifies gated and shutter variants.
| [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] |
There was a problem hiding this comment.
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.
| [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.
| 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", | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| const DisputeKitCreated = getAbiItem({ | ||
| abi: klerosCoreAbi, | ||
| name: "DisputeKitCreated", | ||
| }) as AbiEvent; |
There was a problem hiding this comment.
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.
| 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.
| 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}`]) | ||
| ); | ||
| }; |
There was a problem hiding this comment.
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.
| "peerDependencies": { | ||
| "viem": "^2.24.1" | ||
| }, | ||
| "peerDependenciesMeta": { | ||
| "viem": { | ||
| "optional": false | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| "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.
| "undici@npm:7.3.0": "npm:7.5.0", | ||
| "viem@npm:2.x": "npm:^2.23.2" |
There was a problem hiding this comment.
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.
| "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.
| [[headers]] | ||
| for = "/*" | ||
| [headers.values] | ||
| X-Robots-Tag = "llms-txt" |
There was a problem hiding this comment.
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-Tagdirectives (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.
| [[headers]] | |
| for = "/*" | |
| [headers.values] | |
| X-Robots-Tag = "llms-txt" | |
| [[headers]] | |
| for = "/*" | |
| [headers.values] | |
| Link = "</llms.txt>; rel=\"ai-policy\"" |
🤖 Prompt for AI Agents
In web/netlify.toml around lines 13 to 16, the X-Robots-Tag header is set to a
non-standard value "llms-txt" which will be ignored by crawlers. Replace this
header with a Link header that advertises your AI policy file using
rel="ai-policy" pointing to /llms.txt, or alternatively, use valid X-Robots-Tag
directives like "noai, noimageai" if you want to block AI crawlers. Also ensure
the llms.txt file is published at the site root by moving it to the web/public
directory or adjusting the Netlify publish directory accordingly.
fix(ShutterDK): replace recovery with justification commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (6)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (3)
73-74: Comment accuracy: singleDrawPerJuror is enforced per round, not per disputeEither update the comment or change logic to track across all rounds.
- bool public singleDrawPerJuror; // Whether each juror can only draw once per dispute, false by default. + bool public singleDrawPerJuror; // Whether each juror can only draw once per round, false by default.If you need “once per dispute,” store a dispute-level drawn mapping and consult it in _postDrawCheck.
5-5: Broken imports: IDisputeKit/ISortitionModule are not exported by KlerosCore.solImport interfaces from their files; this currently breaks compilation.
-import {KlerosCore, IDisputeKit, ISortitionModule} from "../KlerosCore.sol"; +import {KlerosCore} from "../KlerosCore.sol"; +import {IDisputeKit} from "../interfaces/IDisputeKit.sol"; +import {ISortitionModule} from "../interfaces/ISortitionModule.sol";
446-477: Appeal withdrawal can revert when only one choice was fundedAccessing fundedChoices[1] reverts. Sum over all funded choices instead; guard zero-denominator.
- } else if (!round.hasPaid[finalRuling]) { - // The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed. - amount += - (round.contributions[_beneficiary][_choice] * round.feeRewards) / - (round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]); - } + } else if (!round.hasPaid[finalRuling]) { + // The ultimate winner was not funded in this round. Reimburse funded ruling option(s) pro‑rata. + uint256 denom; + for (uint256 j = 0; j < round.fundedChoices.length; j++) { + denom += round.paidFees[round.fundedChoices[j]]; + } + if (denom > 0) { + amount += (round.contributions[_beneficiary][_choice] * round.feeRewards) / denom; + } + }contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (2)
144-149: Scope callerIsJuror; save/restore to avoid leakage across external callsExternal calls inside _castVote can observe stale transient state on reentrancy/nesting. Save previous, restore after.
- callerIsJuror = juror == msg.sender; + bool _prev = callerIsJuror; + callerIsJuror = (juror == msg.sender); // `_castVote()` ensures that all the `_voteIDs` do belong to `juror` _castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror); - callerIsJuror = false; + callerIsJuror = _prev;
59-71: Initializer: guard against zero ownerPrevent accidental bricking by validating _owner != address(0).
function initialize( address _owner, KlerosCore _core, address _wNative, uint256 _jumpDisputeKitID ) external initializer { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + if (_owner == address(0)) revert ZeroAddress(); + __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); } @@ - error EmptyJustificationCommit(); - error JustificationCommitmentMismatch(); + error EmptyJustificationCommit(); + error JustificationCommitmentMismatch(); + error ZeroAddress();contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (1)
188-193: Scope callerIsJuror; save/restore to avoid transient state leakageSame pattern as Shutter: compute, call, then restore.
- callerIsJuror = juror == msg.sender; + bool _prev = callerIsJuror; + callerIsJuror = (juror == msg.sender); // `_castVote()` ensures that all the `_voteIDs` do belong to `juror` _castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror); - callerIsJuror = false; + callerIsJuror = _prev;
🧹 Nitpick comments (3)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (1)
36-36: Avoid async in describe blocksMocha doesn’t await async describe callbacks; keep them sync and move awaits into hooks/tests.
-describe("DisputeKitGatedShutter", async () => { +describe("DisputeKitGatedShutter", () => { - describe("Token Gating Features", async () => { + describe("Token Gating Features", () => { - describe("Shutter Features", async () => { + describe("Shutter Features", () => {Also applies to: 37-37, 55-55
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (1)
116-129: Apply onlyByCore at function level to fail fast on unauthorized callsToday, the gating revert runs before Core ACL; add onlyByCore to match base and save gas on unauthorized inputs.
- function createDispute( + function createDispute( uint256 _coreDisputeID, uint256 _coreRoundID, uint256 _numberOfChoices, bytes calldata _extraData, uint256 _nbVotes - ) public override { + ) public override onlyByCore { (address tokenGate, , ) = _extraDataToTokenInfo(_extraData); if (!supportedTokens[tokenGate]) revert TokenNotSupported(tokenGate); - // super.createDispute() ensures access control onlyByCore. - super.createDispute(_coreDisputeID, _coreRoundID, _numberOfChoices, _extraData, _nbVotes); + super.createDispute(_coreDisputeID, _coreRoundID, _numberOfChoices, _extraData, _nbVotes); }contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
492-503: hashVote can be pureNo state is read; mark pure for minor gas/docs clarity.
- ) public view virtual returns (bytes32) { + ) public pure virtual returns (bytes32) { return keccak256(abi.encodePacked(_choice, _salt)); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol(21 hunks)contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol(6 hunks)contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol(3 hunks)contracts/test/arbitration/dispute-kit-gated-shutter.ts(1 hunks)contracts/test/arbitration/dispute-kit-shutter.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- contracts/test/arbitration/dispute-kit-shutter.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-30T17:18:12.895Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#2145
File: contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol:277-286
Timestamp: 2025-09-30T17:18:12.895Z
Learning: In DisputeKitClassicBase.sol's castCommit function, jurors are allowed to re-submit commits during the commit period. The implementation uses a commitCount variable to track only first-time commits (where commit == bytes32(0)) so that totalCommitted is not incremented when a juror updates their existing commit.
Applied to files:
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
🧬 Code graph analysis (1)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (10)
TokenGatedTestContext(27-46)setupTokenGatedTest(165-236)testTokenWhitelistManagement(240-305)testAccessControl(307-323)testUnsupportedTokenErrors(325-368)testERC20Gating(370-431)testERC721Gating(433-494)testERC1155Gating(496-557)testWhitelistIntegration(559-625)testNoTokenGateAddress(627-704)contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (6)
ShutterTestContext(25-50)setupShutterTest(206-325)testCommitPhase(329-447)testNormalFlowBotReveals(449-586)testRecoveryFlowJurorReveals(588-734)testEdgeCasesAndSecurity(736-852)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: hardhat-tests
- GitHub Check: Analyze (javascript)
- GitHub Check: SonarCloud
- GitHub Check: Mend Security Check
- GitHub Check: SonarCloud
- GitHub Check: hardhat-tests
- GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
282-295: Commit re-submissions correctly tracked (ack)Using commitCount avoids double-counting when overriding commits in the period. Looks good. Based on learnings.
| function initialize( | ||
| address _owner, | ||
| KlerosCore _core, | ||
| address _wNative, | ||
| uint256 _jumpDisputeKitID | ||
| ) external initializer { | ||
| __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); | ||
| supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Initializer: guard against zero owner and keep NO_TOKEN_GATE default
Validate _owner to avoid bricking; keep sentinel setup.
function initialize(
address _owner,
KlerosCore _core,
address _wNative,
uint256 _jumpDisputeKitID
) external initializer {
- __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
+ if (_owner == address(0)) revert ZeroAddress();
+ __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
}
@@
- error TokenNotSupported(address tokenGate);
- error EmptyJustificationCommit();
- error JustificationCommitmentMismatch();
+ error TokenNotSupported(address tokenGate);
+ error EmptyJustificationCommit();
+ error JustificationCommitmentMismatch();
+ error ZeroAddress();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function initialize( | |
| address _owner, | |
| KlerosCore _core, | |
| address _wNative, | |
| uint256 _jumpDisputeKitID | |
| ) external initializer { | |
| __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); | |
| supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating | |
| } | |
| function initialize( | |
| address _owner, | |
| KlerosCore _core, | |
| address _wNative, | |
| uint256 _jumpDisputeKitID | |
| ) external initializer { | |
| if (_owner == address(0)) revert ZeroAddress(); | |
| __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); | |
| supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating | |
| } |
🤖 Prompt for AI Agents
In contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol around
lines 83 to 91, the initializer does not validate the provided _owner and could
be called with address(0), which would brick ownership; add a require(_owner !=
address(0), "Owner: zero address") at the start of initialize and then call
__DisputeKitClassicBase_initialize(...); ensure supportedTokens[NO_TOKEN_GATE] =
true remains set so the NO_TOKEN_GATE sentinel default is preserved.
…failure Extra event SortitionModule.StakeDelayedExecutionFailed for possible UX improvements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
contracts/src/arbitration/university/KlerosCoreUniversity.sol (1)
592-617: ValidatefromSubcourtIDreturned by dispute kit.Line 603 receives
fromSubcourtIDfrom an externaldisputeKit.draw()call, and line 610 stores it without bounds checking. Later, line 802 uses this value to indexcourts[penalizedInCourtID].minStake. A malicious or buggy dispute kit could return an out-of-bounds court ID, leading to a revert or undefined behavior.Apply this validation after line 603:
(address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(_disputeID, iteration); if (drawnAddress == address(0)) { revert NoJurorDrawn(); } +if (fromSubcourtID != 0 && fromSubcourtID >= courts.length) { + revert InvalidCourtID(); +} sortitionModule.lockStake(drawnAddress, round.pnkAtStakePerJuror);Add the error declaration:
+error InvalidCourtID();
🧹 Nitpick comments (2)
contracts/src/arbitration/KlerosCore.sol (2)
398-425: Emit events for critical governance changes.Functions like
changeOwner,changeGuardian,changePinakion,changeJurorProsecutionModule, andchangeSortitionModulemodify critical contract state but don't emit events. Emit events to improve transparency and enable off-chain tracking.For example:
+ event OwnerChanged(address indexed _oldOwner, address indexed _newOwner); + event GuardianChanged(address indexed _oldGuardian, address indexed _newGuardian); + event PinakionChanged(IERC20 indexed _oldPinakion, IERC20 indexed _newPinakion); + event JurorProsecutionModuleChanged(address indexed _oldModule, address indexed _newModule); + event SortitionModuleChanged(ISortitionModule indexed _oldModule, ISortitionModule indexed _newModule); + event JurorNftChanged(IERC721 indexed _oldNft, IERC721 indexed _newNft);Then emit in each setter:
function changeOwner(address payable _owner) external onlyByOwner { + emit OwnerChanged(owner, _owner); owner = _owner; }
331-334: Address FORKING_COURT initialization TODO.The FORKING_COURT is initialized with an empty struct and a TODO comment indicates the properties and
CourtCreatedevent are missing. This incomplete initialization should be addressed before deployment.Do you want me to help design the FORKING_COURT parameters or open a tracking issue for this task?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
contracts/hardhat.config.ts(2 hunks)contracts/src/arbitration/KlerosCore.sol(4 hunks)contracts/src/arbitration/SortitionModule.sol(2 hunks)contracts/src/arbitration/university/KlerosCoreUniversity.sol(39 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Analyze (javascript)
- GitHub Check: hardhat-tests
- GitHub Check: SonarCloud
- GitHub Check: Analyze (javascript)
- GitHub Check: SonarCloud
- GitHub Check: hardhat-tests
🔇 Additional comments (14)
contracts/hardhat.config.ts (2)
18-18: LGTM: Script rename aligns with governance refactorThe update from
changeGovernortochangeOwneris consistent with the PR's broader governor→owner rename across contracts and tooling.
29-32: All evm_version and viaIR settings are properly alignedThe verification confirms Foundry's configuration matches Hardhat perfectly:
evm_version = "cancun"✅via_ir = true✅- Solc
0.8.30consistent across both toolchainsThe dynamic viaIR default in Hardhat (
process.env.VIA_IR !== "false") correctly mirrors Foundry's statictruevalue. No bytecode divergence risk.contracts/src/arbitration/university/KlerosCoreUniversity.sol (7)
283-287: Verify instructor self-modification is intentional.The
changeInstructorfunction allows the current instructor to replace themselves (viaonlyByOwnerOrInstructor). If the instructor account is compromised, an attacker could lock out the legitimate instructor. Confirm this is acceptable for the university version, or consider restricting toonlyByOwner.
768-771: Good defensive guard against invalid coherence values.The guard ensures that even if a dispute kit returns a coherence value exceeding 100% (ONE_BASIS_POINT), the contract clamps it to the maximum. This prevents over-penalization and maintains system invariants.
844-850: Good defensive guards for reward coherence values.Both
pnkCoherenceandfeeCoherenceare clamped to ONE_BASIS_POINT, protecting against buggy or malicious dispute kits that might return values exceeding 100%. This ensures rewards are never over-distributed.
873-876: Well-designed fallback for staking rewards.The pattern attempts to stake the PNK reward directly (line 874), which is capital-efficient for the juror, but gracefully falls back to a direct transfer if staking constraints prevent it. This ensures jurors always receive their rewards regardless of staking state.
1083-1123: Refactored_setStakewith_noDelayparameter is well-structured.The new
_noDelayparameter (line 1087) allows the sortition module to bypass delayed stakes when necessary (line 468 passestrue), while user-initiated stakes remain subject to delays (line 458 passesfalse). This maintains security for user operations while giving the system module the flexibility it needs.
11-11: LGTM: Version, gaps, and imports support upgradeability.
- Version bumped to "2.0.0" (line 18) appropriately reflects the major governor→owner refactor.
__gapslots (lines 42, 52, 68) added to structs following UUPS upgrade best practices.- Constants.sol import (line 11) enables usage of
ONE_BASIS_POINTfor clearer basis-point calculations throughout.Also applies to: 18-18, 42-42, 52-52, 68-68
528-529: LGTM: Coherence calculations refactored for granularity.The reward/penalty logic now supports separate PNK and fee coherence values (lines 836-842), enabling more flexible reward models. The consistent use of
ONE_BASIS_POINT(10000) makes basis-point calculations explicit and reduces confusion. The mathematical operations appear sound with appropriate guards in place.Also applies to: 659-660, 759-766, 774-774, 836-842, 853-853, 859-862
contracts/src/arbitration/SortitionModule.sol (5)
204-223: State machine logic looks sound.The phase transition logic correctly enforces timing constraints and dispute requirements:
- Minimum staking time before requesting randomness
- Random number validation before entering drawing phase
- Maximum drawing time as a safety valve to return to staking
The guards against premature phase changes and the dispute counter management appear correct.
233-250: Delayed stake execution logic is well-implemented.The iteration bounds calculation correctly handles the edge case where fewer delayed stakes are available than requested. The failure handling emits an event rather than reverting, allowing batch processing to continue despite individual failures. Cleanup via
deleteproperly reclaims storage.
380-424: Stake update logic correctly propagates through court hierarchy.The implementation properly:
- Maintains the courtIDs array (add on first stake, remove on full unstake)
- Updates stakedPnk balance with deposits and withdrawals
- Propagates stake changes through the court tree from the specific court up to the general court
The backward iteration for courtID removal and the parent court traversal are both correct.
458-466: Leftover PNK withdrawal mechanism is correctly implemented.The withdrawal eligibility check properly ensures:
- The juror has unstaked from all courts (courtIDs.length == 0)
- No tokens remain locked in active disputes (lockedPnk == 0)
- There's actually a positive balance to withdraw
This correctly handles the edge case where a juror unstakes while having locked PNK, and later needs to withdraw those tokens after they unlock.
Also applies to: 512-519
538-547: Assembly usage in_extraDataToTreeKis minimal and safe.The inline assembly correctly extracts the K parameter from extraData with proper bounds checking. The fallback to
DEFAULT_Kwhen extraData is too short handles malformed input gracefully.Note: This assumes callers (KlerosCore.createCourt) provide properly formatted extraData. The boundary check prevents out-of-bounds reads, making this defensive enough.
contracts/hardhat.config.ts
Outdated
| optimizer: { | ||
| enabled: true, | ||
| runs: 100, | ||
| runs: 800, // Constrained by the size of the KlerosCore contract |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify optimizer runs alignment with Foundry
The runs value of 800 is tuned for KlerosCore's size constraints. Confirm that Foundry's optimizer_runs is also set to 800 to prevent bytecode and gas characteristic differences across toolchains. If a mismatch exists, consider the previous suggestion to parameterize via SOLC_OPTIMIZER_RUNS environment variable for consistency.
🏁 Script executed:
#!/bin/bash
# Check Foundry's optimizer_runs setting
echo "== Foundry optimizer_runs =="
rg -n 'optimizer_runs\s*=' contracts/foundry.toml -A 1 -B 1Length of output: 75
Update Foundry optimizer runs to match Hardhat or parameterize both toolchains
Verification shows Foundry is still configured with optimizer_runs = 10000 while Hardhat is now set to 800. This mismatch will produce different bytecode and gas characteristics, potentially causing inconsistencies across deployments. Either:
- Update
foundry.tomltooptimizer_runs = 800to match the KlerosCore size constraint - Parameterize both configurations via a shared
SOLC_OPTIMIZER_RUNSenvironment variable (the previously suggested approach)
🤖 Prompt for AI Agents
In contracts/hardhat.config.ts around line 35, the Hardhat optimizer runs are
set to 800 while Foundry is still using 10000; update configuration so both
toolchains use the same optimizer_runs to avoid bytecode/gas mismatches. Fix by
either (A) changing foundry.toml to optimizer_runs = 800 to match Hardhat, or
(B) parameterizing both toolchains to read a shared SOLC_OPTIMIZER_RUNS
environment variable (set default to 800) and update hardhat.config.ts and
foundry.toml to consume that env var so they remain in sync.
| function changeGuardian(address _guardian) external onlyByOwner { | ||
| guardian = _guardian; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add zero-address validation for additional governance setters.
Similar to the already-flagged issues for changeOwner, changePinakion, and changeSortitionModule, these governance setters lack zero-address validation.
Apply these diffs:
function changeGuardian(address _guardian) external onlyByOwner {
+ if (_guardian == address(0)) revert ZeroAddress();
guardian = _guardian;
} function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByOwner {
+ if (_jurorProsecutionModule == address(0)) revert ZeroAddress();
jurorProsecutionModule = _jurorProsecutionModule;
} function changeJurorNft(IERC721 _jurorNft) external onlyByOwner {
+ if (address(_jurorNft) == address(0)) revert ZeroAddress();
jurorNft = _jurorNft;
}Also applies to: 416-418, 583-585
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 404-406 (and also apply
the same change to 416-418 and 583-585), the governance setter functions
currently assign new addresses without validating against the zero address; add
a require check at the start of each setter (e.g., require(_guardian !=
address(0), "KlerosCore: invalid address")) to revert on zero-address input
before performing the assignment so these functions cannot be set to address(0).
| function createCourt( | ||
| uint96 _parent, | ||
| bool _hiddenVotes, | ||
| uint256 _minStake, | ||
| uint256 _alpha, | ||
| uint256 _feeForJuror, | ||
| uint256 _jurorsForCourtJump, | ||
| uint256[4] memory _timesPerPeriod, | ||
| bytes memory _sortitionExtraData, | ||
| uint256[] memory _supportedDisputeKits | ||
| ) external onlyByOwner { | ||
| if (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt(); | ||
| if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit(); | ||
| if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent(); | ||
|
|
||
| uint96 courtID = uint96(courts.length); | ||
| Court storage court = courts.push(); | ||
|
|
||
| for (uint256 i = 0; i < _supportedDisputeKits.length; i++) { | ||
| if (_supportedDisputeKits[i] == NULL_DISPUTE_KIT || _supportedDisputeKits[i] >= disputeKits.length) { | ||
| revert WrongDisputeKitIndex(); | ||
| } | ||
| _enableDisputeKit(uint96(courtID), _supportedDisputeKits[i], true); | ||
| } | ||
| // Check that Classic DK support was added. | ||
| if (!court.supportedDisputeKits[DISPUTE_KIT_CLASSIC]) revert MustSupportDisputeKitClassic(); | ||
|
|
||
| court.parent = _parent; | ||
| court.children = new uint256[](0); | ||
| court.hiddenVotes = _hiddenVotes; | ||
| court.minStake = _minStake; | ||
| court.alpha = _alpha; | ||
| court.feeForJuror = _feeForJuror; | ||
| court.jurorsForCourtJump = _jurorsForCourtJump; | ||
| court.timesPerPeriod = _timesPerPeriod; | ||
|
|
||
| sortitionModule.createTree(courtID, _sortitionExtraData); | ||
|
|
||
| // Update the parent. | ||
| courts[_parent].children.push(courtID); | ||
| emit CourtCreated( | ||
| courtID, | ||
| _parent, | ||
| _hiddenVotes, | ||
| _minStake, | ||
| _alpha, | ||
| _feeForJuror, | ||
| _jurorsForCourtJump, | ||
| _timesPerPeriod, | ||
| _supportedDisputeKits | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate _feeForJuror > 0 when creating courts.
Creating a court with _feeForJuror set to zero will cause division-by-zero errors later when disputes are created (line 679) or appealed (line 803) in that court, as nbVotes = _feeAmount / feeForJuror will revert.
Apply this diff to add the validation:
function createCourt(
uint96 _parent,
bool _hiddenVotes,
uint256 _minStake,
uint256 _alpha,
uint256 _feeForJuror,
uint256 _jurorsForCourtJump,
uint256[4] memory _timesPerPeriod,
bytes memory _sortitionExtraData,
uint256[] memory _supportedDisputeKits
) external onlyByOwner {
if (courts[_parent].minStake > _minStake) revert MinStakeLowerThanParentCourt();
if (_supportedDisputeKits.length == 0) revert UnsupportedDisputeKit();
if (_parent == FORKING_COURT) revert InvalidForkingCourtAsParent();
+ if (_feeForJuror == 0) revert ZeroFeeForJuror();Add the error declaration near line 1403:
+ error ZeroFeeForJuror();Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 446 to 497, the
createCourt function does not validate that _feeForJuror is > 0 which can later
cause division-by-zero reverts; declare a new custom error (e.g.,
FeeForJurorZero) near line 1403, and add a check in createCourt (after the
existing parent/dispute-kit validations) that reverts with that error when
_feeForJuror == 0 so courts cannot be created with a zero juror fee.
| function changeCourtParameters( | ||
| uint96 _courtID, | ||
| bool _hiddenVotes, | ||
| uint256 _minStake, | ||
| uint256 _alpha, | ||
| uint256 _feeForJuror, | ||
| uint256 _jurorsForCourtJump, | ||
| uint256[4] memory _timesPerPeriod | ||
| ) external onlyByOwner { | ||
| Court storage court = courts[_courtID]; | ||
| if (_courtID != GENERAL_COURT && courts[court.parent].minStake > _minStake) { | ||
| revert MinStakeLowerThanParentCourt(); | ||
| } | ||
| for (uint256 i = 0; i < court.children.length; i++) { | ||
| if (courts[court.children[i]].minStake < _minStake) { | ||
| revert MinStakeHigherThanChildCourt(court.children[i]); | ||
| } | ||
| } | ||
| court.minStake = _minStake; | ||
| court.hiddenVotes = _hiddenVotes; | ||
| court.alpha = _alpha; | ||
| court.feeForJuror = _feeForJuror; | ||
| court.jurorsForCourtJump = _jurorsForCourtJump; | ||
| court.timesPerPeriod = _timesPerPeriod; | ||
| emit CourtModified( | ||
| _courtID, | ||
| _hiddenVotes, | ||
| _minStake, | ||
| _alpha, | ||
| _feeForJuror, | ||
| _jurorsForCourtJump, | ||
| _timesPerPeriod | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate _feeForJuror > 0 when changing court parameters.
Changing a court's feeForJuror to zero will cause division-by-zero errors in subsequent dispute creation or appeals for that court.
Apply this diff:
function changeCourtParameters(
uint96 _courtID,
bool _hiddenVotes,
uint256 _minStake,
uint256 _alpha,
uint256 _feeForJuror,
uint256 _jurorsForCourtJump,
uint256[4] memory _timesPerPeriod
) external onlyByOwner {
Court storage court = courts[_courtID];
if (_courtID != GENERAL_COURT && courts[court.parent].minStake > _minStake) {
revert MinStakeLowerThanParentCourt();
}
+ if (_feeForJuror == 0) revert ZeroFeeForJuror();
for (uint256 i = 0; i < court.children.length; i++) {🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 507 to 540, add
validation that _feeForJuror is greater than zero before assigning it to the
court to prevent division-by-zero errors later; after loading Court storage
court = courts[_courtID] (and before updating court.feeForJuror and emitting
CourtModified) check _feeForJuror > 0 and revert (or require) with a clear error
if it is zero so the function refuses to set a zero fee.
| function changeAcceptedFeeTokens(IERC20 _feeToken, bool _accepted) external onlyByOwner { | ||
| currencyRates[_feeToken].feePaymentAccepted = _accepted; | ||
| emit AcceptedFeeToken(_feeToken, _accepted); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate currency rate is configured before accepting a token.
Accepting a fee token without ensuring its rateInEth is configured will cause createDispute with that token to fail at line 678 when convertEthToTokenAmount is called. Enforce rate configuration before enabling token acceptance.
Apply this diff:
function changeAcceptedFeeTokens(IERC20 _feeToken, bool _accepted) external onlyByOwner {
+ if (_accepted && currencyRates[_feeToken].rateInEth == 0) revert TokenRateNotConfigured();
currencyRates[_feeToken].feePaymentAccepted = _accepted;
emit AcceptedFeeToken(_feeToken, _accepted);
}Add the error:
+ error TokenRateNotConfigured();🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 566 to 569, the
changeAcceptedFeeTokens function currently marks a token accepted without
verifying a configured exchange rate; add a require that
currencyRates[_feeToken].rateInEth > 0 (or another sentinel indicating
configuration) before setting feePaymentAccepted and emitting the event, and use
a clear revert message like "fee token rate not configured".
| function changeCurrencyRates(IERC20 _feeToken, uint64 _rateInEth, uint8 _rateDecimals) external onlyByOwner { | ||
| currencyRates[_feeToken].rateInEth = _rateInEth; | ||
| currencyRates[_feeToken].rateDecimals = _rateDecimals; | ||
| emit NewCurrencyRate(_feeToken, _rateInEth, _rateDecimals); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate _rateInEth > 0 when changing currency rates.
Setting a token's rate to zero will cause division-by-zero at line 1210 in convertEthToTokenAmount, breaking dispute creation with that token. This is the root cause of the issue already flagged in the earlier review comment at lines 1209-1211.
Apply this diff:
function changeCurrencyRates(IERC20 _feeToken, uint64 _rateInEth, uint8 _rateDecimals) external onlyByOwner {
+ if (_rateInEth == 0) revert TokenRateZero();
currencyRates[_feeToken].rateInEth = _rateInEth;
currencyRates[_feeToken].rateDecimals = _rateDecimals;
emit NewCurrencyRate(_feeToken, _rateInEth, _rateDecimals);
}Add the error:
+ error TokenRateZero();🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol around lines 575 to 579, the
changeCurrencyRates function must validate that _rateInEth is greater than zero
to prevent division-by-zero later; add a require at the start of the function
(e.g., require(_rateInEth > 0, "KlerosCore: rate must be > 0")) so the call
reverts when attempting to set a zero rate, then proceed to set rateInEth,
rateDecimals and emit the event.
| if (stakeIncrease) { | ||
| // Check if the stake increase is within the limits. | ||
| if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror) { | ||
| return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror); | ||
| } | ||
| if (totalStaked + stakeChange > maxTotalStaked) { | ||
| return (0, 0, StakingResult.CannotStakeMoreThanMaxTotalStaked); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the logic of the second maxStakePerJuror check.
Line 294 contains two conditions when checking stake increase limits:
if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror)The first condition checks whether the juror's total stake across all courts would exceed maxStakePerJuror (correct). However, the second condition checks whether the stake in this single court would exceed maxStakePerJuror, which doesn't make semantic sense—maxStakePerJuror should be a global per-juror limit across all courts, not a per-court limit.
This may incorrectly reject valid stakes. For example, if maxStakePerJuror is 1000 and a juror tries to stake 800 in one court (with 100 already staked elsewhere), the first check passes (900 < 1000) but the second check fails (800 > limit would be evaluated incorrectly if this is meant to be a per-court cap).
Please verify the intended behavior:
- If
maxStakePerJurorshould only limit the total across all courts, remove the second condition. - If there should be a per-court limit, introduce a separate
maxStakePerCourtconstant and use it in the second condition.
Run this script to check how this affects existing tests and usage:
🏁 Script executed:
#!/bin/bash
# Check for test coverage of stake limit edge cases
rg -n "maxStakePerJuror" -g "*.sol" -g "*.ts" -C3
# Look for any documentation or comments explaining the intended behavior
rg -n "stake.*limit|limit.*stake" -g "*.sol" -C2 | head -50Length of output: 16094
Let me try a different approach with more context:
Let me search for the function signature and calls to understand the currentStake parameter:
---
🏁 Script executed:
```shell
#!/bin/bash
# Extract the full _validateStake function
# This function starts around line 272 based on the original comment
ast-grep --pattern 'function _validateStake($_, $_, $_, $_) {
$$$
}'
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find the _validateStake function and show its full implementation
rg -n "function _validateStake" contracts/src/arbitration/SortitionModule.sol -A 60Length of output: 3032
🏁 Script executed:
#!/bin/bash
# Show the _validateStake function definition with lines around it
rg -n "_validateStake" contracts/src/arbitration/SortitionModule.sol -B 2 -A 50 | head -100
# Also search for calls to _validateStake
echo "--- CALLS TO _validateStake ---"
rg -n "_validateStake\(" contracts/src/arbitration/SortitionModule.sol -B 2 -A 2Length of output: 3401
---
🏁 Script executed:
```shell
#!/bin/bash
# Confirm _stakeOf returns per-court stake
rg -n "function _stakeOf" contracts/src/arbitration/SortitionModule.sol -A 5
# Check if there are multi-court staking tests
echo "--- Multi-court staking tests ---"
rg -n "setStake.*courtID|multiple.*court|multi.*court" contracts/test/arbitration/staking-neo.ts -i -B 1 -A 1
Length of output: 349
Remove the redundant second condition on line 294—it incorrectly applies a global per-juror limit as a per-court cap.
Line 294 checks two conditions:
if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror)The first condition is correct: juror.stakedPnk + stakeChange checks the juror's total stake across all courts (line 279 shows juror.stakedPnk aggregates all courts).
The second condition is wrong: currentStake + stakeChange comes from _stakeOf(_account, _courtID) (line 279), which returns stake in a single court only (verified by lines 529-533). Checking a single court's stake against maxStakePerJuror (a global limit) is semantically incorrect and redundant—if the first condition passes, the second always passes too since one court's stake ≤ total stake.
Delete the second condition:
if (juror.stakedPnk + stakeChange > maxStakePerJuror) {
return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror);
}🤖 Prompt for AI Agents
In contracts/src/arbitration/SortitionModule.sol around lines 292 to 300, remove
the redundant second check in the stakeIncrease branch that compares
currentStake + stakeChange to maxStakePerJuror; keep only the global-per-juror
check using juror.stakedPnk + stakeChange against maxStakePerJuror and return
StakingResult.CannotStakeMoreThanMaxStakePerJuror if it exceeds the limit, so
delete the "|| currentStake + stakeChange > maxStakePerJuror" portion and adjust
the if to a single condition.
…mpDisputeKitIDOnCourtJump
sanity check in Core._getCompatibleNextRoundSettings(), bug fix in appealCost(), naming of the NextRoundSettingsChanged parameters, better comments describing the NextRoundSettings parameters.
…pDisputeKitIDOnCourtJump
Increased Court and DK jump logic flexibility
For accredited Argentinian consumer protection lawyers
feat(KC): allow to pass commit period quicker
|


PR-Codex overview
This PR focuses on refactoring the Kleros arbitration system, including updates to contract ownership, improved code organization, and enhanced functionality in various components, particularly around dispute handling and voting mechanisms.
Detailed summary
governortoownerin multiple contracts for clarity.ReactMarkdownwithMarkdownRendererfor better performance.Summary by CodeRabbit
New Features
Changes
Bug Fixes
Documentation
Tests
Chores