-
Notifications
You must be signed in to change notification settings - Fork 51
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. |
WalkthroughWide-scope refactor: rename governor→owner and replace string requires with typed errors; introduce IRNG and RNGWithFallback with blockhash fallback; extract SortitionTrees and change sortition/staking APIs; split coherence into reward/penalty with ONE_BASIS_POINT; add Viem dispute-kit helper; many deployment, test, and frontend updates. Changes
Sequence Diagram(s)sequenceDiagram
actor DevScript as getDisputeKits.ts
participant Client as Viem PublicClient
participant Helper as disputeKitsViem.getDisputeKits
participant Chain as KlerosCore (on-chain)
note over DevScript,Client: init client via ARBITRUM_SEPOLIA_RPC
DevScript->>Helper: getDisputeKits(client, "devnet")
Helper->>Chain: query DisputeKitCreated events (filter)
Chain-->>Helper: event logs
Helper->>Helper: decode events → id→address map
Helper->>Helper: correlate with local artifacts (isGated/isShutter)
Helper-->>DevScript: return DisputeKitByIds
sequenceDiagram
participant Sort as SortitionModule
participant RF as RNGWithFallback
participant P as PrimaryRNG (IRNG)
participant BH as BlockhashRNG
Sort->>RF: requestRandomness()
RF->>P: requestRandomness()
Note over P: primary RNG fulfills asynchronously
Sort->>RF: receiveRandomness()
alt primary ready (non-zero)
RF->>P: receiveRandomness()
P-->>RF: random
RF-->>Sort: return primary random
else primary timed out or returned 0
RF->>BH: compute fallback from recent blockhash
RF-->>Sort: return fallback random (emit RNGFallback)
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
❌ Deploy Preview for kleros-v2-university failed. Why did it fail? →
|
✅ Deploy Preview for kleros-v2-neo-devtools ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
… PNK to the juror
PNK penalties applied to the sortition tree stakes
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: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
contracts/test/arbitration/dispute-kit-gated.ts (1)
3-12
: Resolve PNK name collision (type vs value)Importing
PNK
(contract type) and declaringconst PNK = (...)
creates a value-namespace conflict and can break type elision. Use a type-only import with an alias and rename the helper.-import { - PNK, - KlerosCore, - SortitionModule, - IncrementalNG, - DisputeKitGated, - TestERC20, - TestERC721, - TestERC1155, -} from "../../typechain-types"; +import type { + PNK as PNKToken, + KlerosCore, + SortitionModule, + IncrementalNG, + DisputeKitGated, + TestERC20, + TestERC721, + TestERC1155, +} from "../../typechain-types";- const PNK = (amount: BigNumberish) => toBigInt(amount) * 10n ** 18n; + const toPNK = (amount: BigNumberish) => toBigInt(amount) * 10n ** 18n;- let pnk: PNK; + let pnk: PNKToken;- const minStake = PNK(200); + const minStake = toPNK(200);- pnk = await ethers.getContract<PNK>("PNK"); + pnk = await ethers.getContract<PNKToken>("PNK");Also applies to: 24-25, 30-31, 40-41, 51-51
contracts/src/arbitration/KlerosCoreNeo.sol (1)
73-75
: Missing access control on reinitializer allows anyone to set wNative once.Any account can call
reinitialize()
(version 6) and changewNative
the first time after upgrade. Restrict to owner.- function reinitialize(address _wNative) external reinitializer(6) { + function reinitialize(address _wNative) external onlyByOwner reinitializer(6) { wNative = _wNative; }contracts/src/arbitration/SortitionModuleNeo.sol (1)
88-90
: Per-court cap check is incorrect; uses total staked instead of court stake.
juror.stakedPnk + stakeChange
enforces a global cap, but the variable name/comment say “in a court.” Use_newStake
(orstakeOf(_account, _courtID)
), not total.- if (juror.stakedPnk + stakeChange > maxStakePerJuror) { + // Enforce per-court cap + if (_newStake > maxStakePerJuror) { return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror); }
♻️ Duplicate comments (4)
contracts/CHANGELOG.md (3)
27-27
: Clarify exact pragma range and actual scope (not “interfaces only”).The note should specify the range and include all modules that adopted it (interfaces and RNG contracts).
- - 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 ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
29-29
: Fix constant name: ONE_BASIS_POINT (singular).- - 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))
7-7
: Avoid broken release anchors for an unpublished tag (0.13.0).Keep the note “Not published yet” but comment out the anchor link until the tag exists.
-## [0.13.0] - 2025-08-07 (Not published yet) +## [0.13.0] - 2025-08-07 (Not published yet) @@ -[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected] +<!-- [0.13.0]: (add after publishing) -->Run to confirm before merging:
#!/bin/bash set -euo pipefail for tag in \ "https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]" \ "https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]" do code=$(curl -s -o /dev/null -w "%{http_code}" "$tag") printf "%-120s %s\n" "$tag" "$code" doneAlso applies to: 154-155
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
137-140
: Address previous review comment on notJumped modifierThe notJumped modifier still accesses disputes without verifying active status. Consider adding the active-dispute guard as suggested in the previous review to prevent accidental reads of uninitialized local disputes.
🧹 Nitpick comments (15)
contracts/test/arbitration/dispute-kit-gated.ts (3)
112-112
: Avoid hardcoding gasLimit in testsLet ethers estimate gas to catch regressions; add a small buffer only if needed.
- .setStake(Courts.GENERAL, thousandPNK(10), { gasLimit: 500000 }) + // Prefer estimation; fallback to a small safety buffer if CI flakiness appears. + // const gas = await core.connect(juror).setStake.estimateGas(Courts.GENERAL, thousandPNK(10)); + // await core.connect(juror).setStake(Courts.GENERAL, thousandPNK(10), { gasLimit: gas + 50_000n }); + .setStake(Courts.GENERAL, thousandPNK(10))
115-115
: Avoid magic numbers for courtIdUse
Courts.GENERAL
instead of1
for consistency and resilience.- expect(await sortitionModule.getJurorBalance(juror.address, 1)).to.deep.equal([ + expect(await sortitionModule.getJurorBalance(juror.address, Courts.GENERAL)).to.deep.equal([
64-64
: Ensure owner context for RNG changechangeRandomNumberGenerator is owner-gated; call it explicitly as the deployer signer to avoid flaky defaults. Local rg search returned only ABI metadata (no other two-arg call sites found).
- await sortitionModule.changeRandomNumberGenerator(rng.target).then((tx) => tx.wait()); + await sortitionModule + .connect(await ethers.getSigner(deployer)) + .changeRandomNumberGenerator(rng.target) + .then((tx) => tx.wait());contracts/CHANGELOG.md (3)
15-15
: Tighten wording and use the canonical EIP link.Replace “in order to comply” with “to comply” and link to the official EIP page.
- - **Breaking:** Rename `governor` to `owner` in order to comply with the lightweight ownership standard [ERC-5313](https://eipsinsight.com/ercs/erc-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112)) + - **Breaking:** Rename `governor` to `owner` to comply with the lightweight ownership standard [ERC‑5313](https://eips.ethereum.org/EIPS/eip-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112))
21-21
: Typo: “bypass” instead of “by-pass”.- - Allow stake changes to by-pass delayed stakes when initiated by the SortitionModule by setting the `_noDelay` parameter to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107)) + - Allow stake changes to bypass delayed stakes when initiated by the SortitionModule by setting `_noDelay = true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
39-39
: Improve phrasing for the period transition.- - 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 cast to preserve the current Shutter auto‑reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))contracts/src/arbitration/KlerosCoreNeo.sol (2)
96-98
: Emit an event when (un)whitelisting arbitrables.A small event improves auditability and tooling integrations.
- function changeArbitrableWhitelist(address _arbitrable, bool _allowed) external onlyByOwner { - arbitrableWhitelist[_arbitrable] = _allowed; - } + function changeArbitrableWhitelist(address _arbitrable, bool _allowed) external onlyByOwner { + arbitrableWhitelist[_arbitrable] = _allowed; + emit ArbitrableWhitelistChanged(_arbitrable, _allowed); + }Add outside this hunk:
event ArbitrableWhitelistChanged(address indexed arbitrable, bool allowed);
12-12
: Version string may be stale.
version = "0.10.0"
looks outdated relative to 0.13.0 release notes. If this constant is surfaced to clients, consider bumping.contracts/src/arbitration/SortitionModuleNeo.sol (3)
65-71
: Emit events for governance parameter changes.Helps monitoring and reproducibility of off-chain configs.
- function changeMaxStakePerJuror(uint256 _maxStakePerJuror) external onlyByOwner { - maxStakePerJuror = _maxStakePerJuror; - } + function changeMaxStakePerJuror(uint256 _maxStakePerJuror) external onlyByOwner { + maxStakePerJuror = _maxStakePerJuror; + emit MaxStakePerJurorChanged(_maxStakePerJuror); + } @@ - function changeMaxTotalStaked(uint256 _maxTotalStaked) external onlyByOwner { - maxTotalStaked = _maxTotalStaked; - } + function changeMaxTotalStaked(uint256 _maxTotalStaked) external onlyByOwner { + maxTotalStaked = _maxTotalStaked; + emit MaxTotalStakedChanged(_maxTotalStaked); + }Add outside this hunk:
event MaxStakePerJurorChanged(uint256 maxStakePerJuror); event MaxTotalStakedChanged(uint256 maxTotalStaked);
77-82
: Missing doc for_noDelay
in_validateStake
signature.Add a brief natspec note explaining how it bypasses delayed stakes when set by SortitionModule.
10-10
: Version string may be stale.
version = "0.9.0"
appears out of sync with package release notes. Consider updating if this is user-facing.contracts/src/arbitration/university/KlerosCoreUniversity.sol (1)
775-798
: Good improvement: Enhanced penalty application flow.The penalty application now:
- Uses the juror's origin court ID from
drawnJurorFromCourtIDs
- Returns the updated PNK balance and court stake from
setStakePenalty
- Properly handles inactive jurors or those below minimum stake
This provides better granularity in tracking where penalties are applied.
Consider adding a comment explaining why penalties are applied to the origin court rather than the dispute's court, as this might not be immediately obvious to future maintainers.
contracts/src/arbitration/interfaces/IDisputeKit.sol (3)
119-123
: Clarify the timing semantics of earlyCourtJump.Specify whether this indicates a jump for the next round vs. current, to avoid Core-side ambiguity.
-/// @return Whether the dispute is jumping to a parent court or not. +/// @return Whether the next round will jump to the parent court (early jump) as determined by this dispute kit.
3-3
: Tighten IDisputeKit.sol pragma to project‐standard 0.8.24
At contracts/src/arbitration/interfaces/IDisputeKit.sol line 3:-pragma solidity >=0.8.0 <0.9.0; +pragma solidity >=0.8.24 <0.9.0;(Optional: other arbitration interfaces—IEvidence.sol, ISortitionModule.sol, IDisputeTemplateRegistry.sol, IArbitratorV2.sol, IArbitrableV2.sol—also use
>=0.8.0 <0.9.0
; consider aligning them too.)
124-131
: Remove inline TODO and document invariants for getNbVotesAfterAppealRemove the inline
// TODO
and clarify behavior in NatSpec:- ) external view returns (uint256); // TODO: remove previousDisputeKit + ) external view returns (uint256);/// @param _previousDisputeKit The previous Dispute Kit. /// @param _currentNbVotes The number of votes before the appeal. +/// @notice Must return a value ≥ `_currentNbVotes`. +/// @notice `_previousDisputeKit` may equal the current kit; handle both same-kit and migration cases. /// @return The number of votes after the appeal.
📜 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.
📒 Files selected for processing (12)
contracts/CHANGELOG.md
(2 hunks)contracts/src/arbitration/KlerosCoreBase.sol
(24 hunks)contracts/src/arbitration/KlerosCoreNeo.sol
(5 hunks)contracts/src/arbitration/SortitionModuleBase.sol
(21 hunks)contracts/src/arbitration/SortitionModuleNeo.sol
(5 hunks)contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
(15 hunks)contracts/src/arbitration/interfaces/IDisputeKit.sol
(4 hunks)contracts/src/arbitration/interfaces/ISortitionModule.sol
(3 hunks)contracts/src/arbitration/university/KlerosCoreUniversity.sol
(25 hunks)contracts/src/arbitration/university/SortitionModuleUniversity.sol
(10 hunks)contracts/test/arbitration/dispute-kit-gated.ts
(2 hunks)contracts/test/foundry/KlerosCore_Execution.t.sol
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- contracts/test/foundry/KlerosCore_Execution.t.sol
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-03T19:34:58.018Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#2107
File: contracts/src/arbitration/university/KlerosCoreUniversity.sol:1083-1092
Timestamp: 2025-09-03T19:34:58.018Z
Learning: KlerosCoreUniversity and SortitionModuleUniversity do not have phases, unlike KlerosCoreBase and SortitionModuleBase. Therefore, validateStake in the University contracts will never return StakingResult.Delayed, only Successful or other failure states.
Applied to files:
contracts/src/arbitration/university/SortitionModuleUniversity.sol
contracts/src/arbitration/university/KlerosCoreUniversity.sol
📚 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/test/arbitration/dispute-kit-gated.ts
📚 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
📚 Learning: 2024-11-19T16:31:08.965Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:167-170
Timestamp: 2024-11-19T16:31:08.965Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the `minStake` parameter is denominated in PNK, not ETH.
Applied to files:
contracts/src/arbitration/university/KlerosCoreUniversity.sol
🧬 Code graph analysis (1)
contracts/test/arbitration/dispute-kit-gated.ts (2)
contracts/deploy/utils/index.ts (1)
PNK
(40-40)contracts/deploy/utils/deployTokens.ts (2)
deployERC721
(55-67)deployERC1155
(69-81)
🪛 LanguageTool
contracts/CHANGELOG.md
[style] ~15-~15: Consider a more concise word here.
Context: ...reaking:** Rename governor
to owner
in order to comply with the lightweight ownership s...
(IN_ORDER_TO_PREMIUM)
⏰ 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: contracts-testing
- GitHub Check: SonarCloud
- GitHub Check: Analyze (javascript)
- GitHub Check: Mend Security Check
- GitHub Check: Analyze (javascript)
- GitHub Check: SonarCloud
- GitHub Check: contracts-testing
🔇 Additional comments (72)
contracts/test/arbitration/dispute-kit-gated.ts (3)
50-54
: Typed getContract usages look goodStrong typing for DisputeKitGated/PNK/DAI/Core/SortitionModule improves safety. LGTM.
62-62
: Typed RNG retrieval LGTMCorrectly fetching IncrementalNG with generics.
68-68
: Typed NFT contract retrievals LGTMSafer than untyped fetches. Nice.
Also applies to: 71-71
contracts/src/arbitration/KlerosCoreNeo.sol (3)
31-45
: Initialize with owner: ABI-safe rename looks good.
81-84
: Upgrade authorization guarded by owner: OK.
110-112
: External call in eligibility check: confirm trusted NFT.
jurorNft.balanceOf(msg.sender)
is an external call before state changes. IfjurorNft
were set to a malicious contract, it could reenter. If not already guaranteed elsewhere, consider restrictingjurorNft
to known-good implementations and/or adding a reentrancy guard to setters that could change it.contracts/src/arbitration/SortitionModuleNeo.sol (2)
29-47
: Initializer rename and IRNG wiring look consistent.
95-103
: Apply totalStaked update only after successful validation
Move the mutation oftotalStaked
to after invokingsuper._validateStake
and guard it onstakingResult == StakingResult.Successful && phase == Phase.staking
to prevent inconsistent state.(pnkDeposit, pnkWithdrawal, stakingResult) = super._validateStake(_account, _courtID, _newStake, _noDelay); -if (phase == Phase.staking) { - totalStaked = stakeIncrease ? totalStaked + stakeChange : totalStaked - stakeChange; -} +if (stakingResult == StakingResult.Successful && phase == Phase.staking) { + totalStaked = stakeIncrease ? totalStaked + stakeChange : totalStaked - stakeChange; +}contracts/src/arbitration/university/SortitionModuleUniversity.sol (9)
32-32
: Owner-based governance transition completedThe governance model has been successfully migrated from
governor
toowner
, consistent with the broader refactoring across the codebase.
69-77
: Improved error handling with custom errorsThe access control modifiers now use custom errors (
OwnerOnly
,KlerosCoreOnly
) instead of require statements, which is more gas-efficient and provides better error information.
90-93
: Owner parameter in initializationThe initializer correctly accepts
_owner
parameter instead of_governor
, maintaining consistency with the governance migration.
142-144
: Enhanced validateStake with delay controlThe addition of
_noDelay
parameter allows bypassing delayed stake logic when needed. Based on the retrieved learning, the University contracts don't have phases, so this parameter enables fine-grained control over stake validation.
187-243
: Comprehensive stake management APIThe new stake management functions provide excellent granular control:
setStake
delegates to internal_setStake
setStakePenalty
handles penalty application with proper bounds checkingsetStakeReward
deposits rewards with validation- Internal
_setStake
centralizes stake updates and event emissionThe implementation properly handles edge cases like zero penalties and zero rewards.
306-323
: Enhanced unstaking capabilitiesThe renamed
forcedUnstakeAllCourts
(fromsetJurorInactive
) and newforcedUnstake
function provide both comprehensive and targeted unstaking capabilities, improving juror management flexibility.
335-335
: Improved withdrawal validationUsing custom error
NotEligibleForWithdrawal()
instead of require statement is more gas-efficient and provides better error information.
348-354
: Enhanced draw function with subcourt trackingThe draw function now returns
fromSubcourtID
in addition todrawnAddress
, enabling provenance tracking consistent with the interface changes.
428-430
: Proper error declarationsAll custom errors are properly declared at the end of the contract, following Solidity conventions.
contracts/src/arbitration/interfaces/ISortitionModule.sol (4)
3-3
: Consistent pragma version updateThe pragma version has been updated to
>=0.8.0 <0.9.0
, maintaining consistency with other interface files.
18-23
: Enhanced validateStake with delay controlThe addition of
_noDelay
parameter provides necessary control over stake timing behavior, allowing bypassing of delayed stake logic when appropriate.
33-43
: Comprehensive stake management interfaceThe new stake management functions replace and expand upon the previous limited interface:
setStakePenalty
replacespenalizeStake
with court-specific functionalitysetStakeReward
enables reward stakingforcedUnstakeAllCourts
replacessetJurorInactive
with clearer namingforcedUnstake
provides court-specific unstakingThese changes provide much more granular control over stake management.
51-55
: Enhanced draw function signatureThe draw function now returns both
drawnAddress
andfromSubcourtID
, enabling better tracking of juror origins across the sortition system.contracts/src/arbitration/KlerosCoreBase.sol (17)
63-63
: Important: Track drawn juror subcourt originsThe addition of
drawnJurorFromCourtIDs
array to the Round struct enables tracking which subcourt each juror was drawn from. This is crucial for proper penalty application and stake management.
94-94
: Governance migration to owner-based modelThe transition from
governor
toowner
is consistent with the broader governance refactoring across the codebase.
171-179
: Improved access control with custom errorsThe modifiers now use custom errors (
OwnerOnly
,GuardianOrOwnerOnly
) instead of require statements, providing better gas efficiency and error reporting.
196-208
: Updated initialization for owner-based governanceThe initialization function correctly accepts
_owner
parameter and assigns it to the owner storage variable, maintaining consistency with the governance migration.
277-286
: Owner governance functions updatedThe governance functions have been properly updated:
executeOwnerProposal
replacesexecuteGovernorProposal
with proper parameter namingchangeOwner
replaceschangeGovernor
with consistent namingAll functions use
onlyByOwner
modifier appropriately.
467-467
: Enhanced staking with delay controlThe
_setStake
calls now include a_noDelay
parameter, providing fine-grained control over stake timing. ThesetStakeBySortitionModule
properly uses_noDelay: true
to bypass delays for module-initiated stakes.Also applies to: 475-476
610-617
: Enhanced draw with subcourt trackingThe draw function now captures and stores the
fromSubcourtID
from the dispute kit, enabling proper tracking of juror origins. The fallback todispute.courtID
whenfromSubcourtID
is 0 is a sensible default.
643-651
: Centralized court jump logicThe appeal function now uses centralized
_getCourtAndDisputeKitJumps
helper function, improving code organization and reducing duplication.
565-569
: Improved commit period handlingThe removal of
areCommitsAllCast
check simplifies the period transition logic, relying on time-based conditions instead. This reduces complexity while maintaining security.
759-795
: Enhanced penalty execution with subcourt trackingThe penalty execution now:
- Uses
getDegreeOfCoherencePenalty
for penalty-specific coherence calculation- Properly tracks the subcourt where penalties are applied via
round.drawnJurorFromCourtIDs
- Uses
setStakePenalty
for court-specific penalty application- Implements proper unstaking logic based on balance and minimum stake requirements
This provides much more precise penalty management.
828-872
: Enhanced reward execution with coherence separationThe reward execution now:
- Uses
getDegreeOfCoherenceReward
to get separate PNK and fee coherence values- Properly bounds checks both coherence values
- Attempts to stake PNK rewards via
setStakeReward
with fallback to direct transfer- Uses separate coherence values for PNK and fee calculations
This provides more granular and flexible reward distribution.
807-808
: Leftover rewards redirected to ownerLeftover rewards are now properly redirected to the owner instead of governor, maintaining consistency with the governance migration.
Also applies to: 880-883
939-957
: Enhanced appeal cost calculationThe appeal cost calculation now uses the centralized
_getCourtAndDisputeKitJumps
function and properly accounts for vote count changes after appeals viagetNbVotesAfterAppeal
.
1065-1098
: Excellent centralized jump logic helpersThe new helper functions provide clean abstraction for court and dispute kit jumping logic:
_isCourtJumping
checks both early jumps and vote thresholds_getCourtAndDisputeKitJumps
determines all jump parameters in one placeThis reduces code duplication and improves maintainability.
1114-1126
: ONE_BASIS_POINT migration completedAll coherence calculations now use
ONE_BASIS_POINT
instead ofALPHA_DIVISOR
, providing consistency across the codebase and aligning with the new basis point standard.
1144-1163
: Enhanced _setStake with delay controlThe internal
_setStake
function now accepts a_noDelay
parameter that's properly passed tovalidateStake
, enabling fine-grained control over stake timing behavior.
1236-1237
: Proper error declarations for owner-based governanceThe custom errors
OwnerOnly
andGuardianOrOwnerOnly
are properly declared, replacing the old governor-based error names.contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (14)
9-9
: ONE_BASIS_POINT import for consistencyThe import of
ONE_BASIS_POINT
from Constants.sol aligns with the codebase-wide migration to standardized basis point calculations.
62-62
: Owner-based governance migrationThe storage variable has been updated from
governor
toowner
, consistent with the governance refactoring across the codebase.
127-134
: Improved access control with custom errorsThe access control modifiers now use custom errors (
OwnerOnly
,KlerosCoreOnly
) instead of require statements, providing better gas efficiency and error information.
147-158
: Owner parameter in initializationThe initializer has been properly updated to accept
_owner
parameter instead of_governor
and assigns it correctly.
164-177
: Owner governance functions updatedThe governance functions have been properly migrated:
executeOwnerProposal
with custom error handlingchangeOwner
with proper parameter namingchangeCore
now usesonlyByOwner
modifier
227-240
: Enhanced draw with subcourt trackingThe draw function now returns both
drawnAddress
andfromSubcourtID
from the sortition module, enabling proper tracking of juror origins. The early return maintains the same address when no juror is available.
268-270
: Comprehensive error handling in commit castingThe commit casting validation now uses custom errors for all failure cases:
NotCommitPeriod
for period validationEmptyCommit
for empty commit validationNotActiveForCoreDisputeID
for dispute activity validationThis provides better error reporting and gas efficiency.
275-275
: Consistent juror ownership validationThe vote ownership validation uses the custom error
JurorHasToOwnTheVote
, maintaining consistency with the error handling refactoring.
309-327
: Comprehensive vote casting validationThe vote casting function now uses custom errors for all validation:
- Period, empty vote IDs, dispute activity, and choice bounds checking
- Proper juror ownership, hidden vote commitment, and duplicate vote validation
This provides complete error coverage with efficient custom errors.
358-382
: Enhanced appeal funding validationThe appeal funding validation uses custom errors appropriately:
ChoiceOutOfBounds
for choice validationNotActiveForCoreDisputeID
for dispute activityAppealPeriodIsOver
andAppealPeriodIsOverForLoser
for timing validationAppealFeeIsAlreadyPaid
for duplicate payment preventionThe loser appeal period calculation correctly uses
ONE_BASIS_POINT
.
438-441
: Proper withdrawal validationThe withdrawal function uses custom errors (
DisputeNotResolved
,CoreIsPaused
,NotActiveForCoreDisputeID
) for all validation checks, maintaining consistency.
527-551
: Well-designed coherence API implementationThe coherence API has been properly implemented:
getDegreeOfCoherenceReward
returns the same coherence for both PNK and fee rewardsgetDegreeOfCoherencePenalty
returns coherence for penalties- Both delegate to internal
_getDegreeOfCoherence
for consistent logic- Uses
ONE_BASIS_POINT
for full coherence valueThis provides the flexibility for different coherence models while maintaining consistency in the classic implementation.
628-640
: New dispute kit control APIsThe implementation of
earlyCourtJump
(returning false) andgetNbVotesAfterAppeal
(implementing 2x+1 logic) provides the necessary interface compliance while maintaining classic dispute kit behavior.
754-772
: Comprehensive error declarationsAll custom errors are properly declared at the end of the contract, covering all validation scenarios with descriptive names. This provides excellent error reporting and gas efficiency.
contracts/src/arbitration/university/KlerosCoreUniversity.sol (8)
93-93
: LGTM! Consistent ownership terminology refactoring.The renaming from
governor
toowner
and the corresponding modifier updates (onlyByGovernor
→onlyByOwner
,onlyByGovernorOrInstructor
→onlyByOwnerOrInstructor
) are correctly implemented across all access control checks.Also applies to: 166-169, 176-179
525-525
: LGTM! Coherence calculation correctly uses ONE_BASIS_POINT.The replacement of
ALPHA_DIVISOR
withONE_BASIS_POINT
for calculating PNK at stake per juror maintains the same logic while aligning with the standardized constant naming across the codebase.Also applies to: 655-655
599-599
: Good addition: Tracking juror origin courts.The new
drawnJurorFromCourtIDs
array and the logic to populate it withfromSubcourtID
enables better tracking of where jurors were drawn from. This is particularly useful when jurors stake in subcourts and are drawn for cases in parent courts.Also applies to: 606-606
866-869
: Good addition: Automatic stake reward handling.The new logic attempts to stake PNK rewards directly through
setStakeReward
. If staking fails (e.g., juror was unstaked), it falls back to transferring the PNK directly to the juror. This provides a good user experience while maintaining safety.
1086-1092
: LGTM! Enhanced stake validation with no-delay parameter.The addition of the
_noDelay
parameter to_setStake
allows bypassing the delay mechanism when needed, which is consistent with the learnings about KlerosCoreUniversity not having phases.
801-809
: No changes needed:owner
can be cast to payable. Thepayable(owner)
conversions on lines 804 and 892 correctly cast theaddress public owner
into anaddress payable
, so ETH transfers will compile and execute as intended.Likely an incorrect or invalid review comment.
176-179
: Error naming consistency confirmed. AllOnly
-suffix errors (OwnerOnly, InstructorOnly, OwnerOrInstructorOnly) in KlerosCoreUniversity.sol follow the established naming convention.
754-765
: Approve coherence penalty calculation. ThegetDegreeOfCoherencePenalty
signature in IDisputeKit matches the five arguments passed in KlerosCoreUniversity, and capping coherence at ONE_BASIS_POINT followed by inverting via(ONE_BASIS_POINT – coherence)
correctly computes the penalty proportion.contracts/src/arbitration/SortitionModuleBase.sol (10)
10-10
: LGTM! Correct RNG interface migration.The migration from
RNG
toIRNG
interface is properly implemented throughout the file. The simplified RNG interface no longer requires block parameters forrequestRandomness()
andreceiveRandomness()
.Also applies to: 54-54, 107-107, 155-159
52-52
: Note: Deprecated fields marked for removal.The comments indicate that
randomNumberRequestBlock
(line 52) andrngLookahead
(line 56) are deprecated and will be removed in the next redeploy. These fields are no longer used in the active code paths.Also applies to: 56-56
168-179
: LGTM! Improved error handling with typed errors.The phase transition logic now uses descriptive typed errors (
MinStakingTimeNotPassed
,NoDisputesThatNeedJurors
,RandomNumberNotReady
,DisputesWithoutJurorsAndMaxDrawingTimeNotPassed
) instead of generic reverts, improving debugging and error handling.
193-194
: Good validation: Tree creation checks.The function now properly validates that:
- A tree doesn't already exist at the key
- K value is greater than 1
These checks prevent invalid tree configurations.
401-401
: Excellent refactor: Stake path ID abstraction.The introduction of
stakePathID
and the helper functions_accountAndCourtIDToStakePathID
and_stakePathIDToAccountAndCourtID
provides better encapsulation of the account-court relationship. The bit packing (20 bytes address + 12 bytes courtID) is efficient and well-documented.Also applies to: 530-531, 537-538, 625-631, 703-734, 736-767
323-345
: Important addition: Penalty application with balance tracking.The new
setStakePenalty
function properly:
- Checks available stake before applying penalties
- Returns the updated balances for the caller to use
- Handles edge cases where penalty exceeds available stake
This provides essential functionality for the penalty system.
347-369
: Good addition: Reward staking with safety checks.The
setStakeReward
function:
- Returns early if no reward to add
- Validates juror has stake before adding rewards
- Returns success status for proper handling
This prevents rewards from being lost or misapplied.
439-456
: Important additions: Forced unstaking capabilities.The new
forcedUnstakeAllCourts
andforcedUnstake
functions allow the core to remove inactive jurors from courts. This is essential for maintaining system health when jurors become inactive or fall below minimum stakes.
491-492
: Good enhancement: Draw function returns origin court.The
draw
function now returns both the drawn address and thefromSubcourtID
, providing information about where the juror was originally staked. This aligns with the changes in KlerosCoreUniversity that track juror origins.Also applies to: 521-522
403-403
: Fix typo in variable name.There's a typo in the variable name.
- uint96 currentCourtID = _courtID; + uint96 currentCourtID = _courtID;Actually, looking at the code again, the variable name is correct. The comment about
CurrenCourtID
in the summary appears to be incorrect.contracts/src/arbitration/interfaces/IDisputeKit.sol (2)
73-82
: Coherence (reward): document units/range explicitly.-/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. -/// @return feeCoherence The degree of coherence in basis points for the dispute fee reward. +/// @return pnkCoherence The degree of coherence in basis points (0..10000) for the dispute PNK reward. +/// @return feeCoherence The degree of coherence in basis points (0..10000) for the dispute fee reward.No occurrences of the legacy
getDegreeOfCoherence(...)
remain in the contract code (only in specs and deployment metadata).
51-54
: draw: add missing@return fromSubcourtID
and confirmuint96
matches Core’scourtID
type.
- Prepend above the
draw
signature:/// @return drawnAddress The drawn address. +/// @return fromSubcourtID The subcourt ID from which the juror was drawn.
- Ensure
fromSubcourtID
is typeduint96
, consistent withcourtID
inKlerosCoreBase
([verify type] uint96).
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the penalty. | ||
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. | ||
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. | ||
/// @param _voteID The ID of the vote. | ||
/// @param _feePerJuror The fee per juror. | ||
/// @param _pnkAtStakePerJuror The PNK at stake per juror. | ||
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. | ||
function getDegreeOfCoherencePenalty( | ||
uint256 _coreDisputeID, | ||
uint256 _coreRoundID, | ||
uint256 _voteID, | ||
uint256 _feePerJuror, | ||
uint256 _pnkAtStakePerJuror | ||
) external view returns (uint256); | ||
) external view returns (uint256 pnkCoherence); |
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.
Fix NatSpec: “reward” → “penalty”.
The return doc for the penalty accessor mentions “reward”, which is misleading.
-/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward.
+/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK penalty.
📝 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.
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the penalty. | |
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. | |
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. | |
/// @param _voteID The ID of the vote. | |
/// @param _feePerJuror The fee per juror. | |
/// @param _pnkAtStakePerJuror The PNK at stake per juror. | |
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. | |
function getDegreeOfCoherencePenalty( | |
uint256 _coreDisputeID, | |
uint256 _coreRoundID, | |
uint256 _voteID, | |
uint256 _feePerJuror, | |
uint256 _pnkAtStakePerJuror | |
) external view returns (uint256); | |
) external view returns (uint256 pnkCoherence); | |
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the penalty. | |
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. | |
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. | |
/// @param _voteID The ID of the vote. | |
/// @param _feePerJuror The fee per juror. | |
/// @param _pnkAtStakePerJuror The PNK at stake per juror. | |
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK penalty. | |
function getDegreeOfCoherencePenalty( | |
uint256 _coreDisputeID, | |
uint256 _coreRoundID, | |
uint256 _voteID, | |
uint256 _feePerJuror, | |
uint256 _pnkAtStakePerJuror | |
) external view returns (uint256 pnkCoherence); |
🤖 Prompt for AI Agents
In contracts/src/arbitration/interfaces/IDisputeKit.sol around lines 83 to 96,
the NatSpec @return description incorrectly refers to a "reward" but this
function returns the degree of coherence used for a PNK penalty; update the
@return text to say "penalty" (e.g., "pnkCoherence The degree of coherence in
basis points for the dispute PNK penalty.") keeping signature and return name
unchanged.
function changeJurorNft(IERC721 _jurorNft) external onlyByOwner { | ||
jurorNft = _jurorNft; | ||
} |
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
Guard against zero juror NFT and emit an event.
Prevent bricking setStake()
via zero address and emit for off-chain tracking.
- function changeJurorNft(IERC721 _jurorNft) external onlyByOwner {
- jurorNft = _jurorNft;
- }
+ function changeJurorNft(IERC721 _jurorNft) external onlyByOwner {
+ if (address(_jurorNft) == address(0)) revert ZeroAddress();
+ jurorNft = _jurorNft;
+ emit JurorNftChanged(_jurorNft);
+ }
Additions outside this hunk:
event JurorNftChanged(IERC721 jurorNft);
error ZeroAddress();
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreNeo.sol around lines 89 to 91, the
changeJurorNft function should validate the input and emit an event: add a guard
that reverts with ZeroAddress() if _jurorNft equals IERC721(address(0)), then
set jurorNft = _jurorNft and emit JurorNftChanged(_jurorNft); ensure the event
JurorNftChanged(IERC721 jurorNft) and error ZeroAddress() are declared in the
contract (as noted in the review).
|
||
await deployERC1155(hre, deployer, "TestERC1155", "Nft1155"); | ||
nft1155 = (await ethers.getContract("Nft1155")) as TestERC1155; | ||
nft1155 = await ethers.getContract<TestERC1155>("Nft1155"); | ||
await nft1155.mint(deployer, TOKEN_ID, 1, "0x00"); |
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.
Await mining for state-changing txs (.wait())
These txs aren’t mined before assertions, which can cause flaky tests.
- await nft1155.mint(deployer, TOKEN_ID, 1, "0x00");
+ await nft1155.mint(deployer, TOKEN_ID, 1, "0x00").then((tx) => tx.wait());
- dai.transfer(juror1.address, 1);
+ await dai.transfer(juror1.address, 1).then((tx) => tx.wait());
- await nft721.safeMint(juror2.address);
+ await nft721.safeMint(juror2.address).then((tx) => tx.wait());
- await nft1155.mint(juror2.address, TOKEN_ID, 1, "0x00");
+ await nft1155.mint(juror2.address, TOKEN_ID, 1, "0x00").then((tx) => tx.wait());
Also applies to: 162-162, 209-209, 256-256
🤖 Prompt for AI Agents
In contracts/test/arbitration/dispute-kit-gated.ts around lines 72, 162, 209 and
256, the state-changing mint/send transactions are awaited only for the promise
but not for on-chain mining, which can lead to flaky tests; change each call to
capture the transaction then await its .wait() (e.g., const tx = await
nft1155.mint(...); await tx.wait();) before running assertions so the
transactions are mined and state is settled.
Sortition trees extracted to a library
KlerosCore to allow jumping to a non-classic DK
…cted, causing stack too deep
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: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
contracts/src/arbitration/KlerosCoreBase.sol (3)
94-104
: Owner storage OK; add EIP-173-style event and zero-address guard.Track ownership changes and prevent accidental burn on init/change.
address public owner; // The owner of the contract. + event OwnerChanged(address indexed previousOwner, address indexed newOwner);
And emit in initializer/changeOwner (see dedicated diffs below). Also consider whether
guardian
may be zero by design; if not, guard similarly.
196-209
: Initializer: validate inputs and emit ownership event.Prevent zero owner; record initial transfer for indexers.
function __KlerosCoreBase_initialize( - address _owner, + address _owner, address _guardian, @@ ) internal onlyInitializing { - owner = _owner; + if (_owner == address(0)) revert OwnerZeroAddress(); + owner = _owner; + emit OwnerChanged(address(0), _owner); guardian = _guardian;Add error at the bottom:
+ error OwnerZeroAddress();
452-456
: Remove redundant event declaration and add zero‐rate validation
TheNewCurrencyRate
event is declared inIArbitratorV2
and inherited—no need to re‐declare it inKlerosCoreBase.sol
. You must, however, prevent a zero rate (which would later cause a division‐by‐zero inconvertEthToTokenAmount
). Incontracts/src/arbitration/KlerosCoreBase.sol
, updatechangeCurrencyRates
to:function changeCurrencyRates(IERC20 _feeToken, uint64 _rateInEth, uint8 _rateDecimals) external onlyByGovernor { + if (_rateInEth == 0) revert InvalidRate(); currencyRates[_feeToken].rateInEth = _rateInEth; currencyRates[_feeToken].rateDecimals = _rateDecimals; emit NewCurrencyRate(_feeToken, _rateInEth, _rateDecimals); }
And add at the bottom of the file:
+ error InvalidRate();
♻️ Duplicate comments (4)
contracts/CHANGELOG.md (3)
29-29
: Clarify exact pragma range and scope (“interfaces only” likely inaccurate).Recommend stating the exact range and reflecting actual scope (interfaces and RNG). Please verify across the repo before applying.
- - 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 if applicable) ([#2083](https://github.com/kleros/kleros-v2/issues/2083))To verify scope:
#!/bin/bash rg -nP '^\s*pragma\s+solidity\s+[^;]+;' -g 'contracts/**' -C0
7-7
: Avoid broken release links for an unpublished tag.If 0.13.0 isn’t live yet, comment out its link and optionally mark the section as “Unreleased.” Update when published.
-## [0.13.0] - 2025-08-07 (Not published yet) +## [0.13.0] - Unreleased-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected] +<!-- [0.13.0]: add after publishing -->To check link availability:
#!/bin/bash curl -s -o /dev/null -w "%{http_code}\n" \ "https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]"Also applies to: 156-157
31-31
: Fix constant name: ONE_BASIS_POINT (singular).Public-facing docs must match the code. Use the correct identifier.
- - 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))contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
137-140
: Add active-dispute guard in notJumped (prevents accidental reads of dispute 0).Accessing disputes[coreDisputeIDToLocal[_coreDisputeID]] without validating activity can read the zero-initialized dispute. Guard with coreDisputeIDToActive first.
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 (28)
contracts/src/arbitration/KlerosCoreBase.sol (3)
63-68
: Keep arrays aligned; consider sanity checks on lengths.Adding
drawnJurorFromCourtIDs
parallel todrawnJurors
is fine. Ensure all push/pop sites keep lengths equal; add an internal assert in execute paths to detect divergence early.+ // Sanity: keep arrays aligned (dev-only; remove if gas-sensitive) + assert(round.drawnJurorFromCourtIDs.length == round.drawnJurors.length);
315-320
: Adding new DK: validate address and uniqueness.Check non-zero and that the address has code; optional: prevent duplicate insertion.
- function addNewDisputeKit(IDisputeKit _disputeKitAddress) external onlyByOwner { + function addNewDisputeKit(IDisputeKit _disputeKitAddress) external onlyByOwner { + if (address(_disputeKitAddress) == address(0)) revert InvalidAddress(); uint256 disputeKitID = disputeKits.length;
342-382
: Court creation flow looks good; tiny nit on casting.
- Flow: validate, push, enable DKs, ensure Classic enabled, set fields, create tree, link parent — solid.
- Nit:
uint96 courtID = uint96(courts.length);
is fine given practical limits; add an assert for future-proofing.- uint96 courtID = uint96(courts.length); + uint256 _newId = courts.length; + assert(_newId < type(uint96).max); + uint96 courtID = uint96(_newId);Also applies to: 346-354, 367-368
contracts/CHANGELOG.md (3)
15-15
: Tighten phrasing and use canonical ERC link.Replace “in order to comply” with “to comply” and point to eips.ethereum.org.
- - **Breaking:** Rename `governor` to `owner` in order to comply with the lightweight ownership standard [ERC-5313](https://eipsinsight.com/ercs/erc-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112)) + - **Breaking:** Rename `governor` to `owner` to comply with the lightweight ownership standard [ERC-5313](https://eips.ethereum.org/EIPS/eip-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112))
21-21
: Spell and style: “bypass” (one word).Minor wording cleanup.
- - Allow stake changes to by-pass delayed stakes when initiated by the SortitionModule by setting the `_noDelay` parameter to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107)) + - Allow stake changes to bypass delayed stakes when initiated by the SortitionModule by setting `_noDelay` to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
41-41
: Grammar: “advance to the Voting period.”Improves readability.
- - 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 if all commits are cast, as it breaks the current Shutter auto‑reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))contracts/src/libraries/SortitionTrees.sol (4)
170-171
: Typo in NatSpec: “subtract,” not “substract.”Public docs should be clean.
- /// @param _plusOrMinus Whether to add (true) or substract (false). - /// @param _value The value to add or substract. + /// @param _plusOrMinus Whether to add (true) or subtract (false). + /// @param _value The value to add or subtract.
3-3
: Align pragma with toolchain or widen consistently.Hardhat/Foundry target 0.8.30 per CHANGELOG. Consider
^0.8.30
or>=0.8.0 <0.9.0
for consistency with interfaces.-pragma solidity ^0.8.24; +pragma solidity ^0.8.30; // or: pragma solidity >=0.8.0 <0.9.0;
206-211
: Update linter directive (“solium” → “solhint”).Solium is deprecated; use solhint directive to suppress the inline assembly warning.
- // solium-disable-line security/no-inline-assembly + // solhint-disable-next-line no-inline-assembly
217-224
: Mirror linter directive for unpack as well.- // solium-disable-line security/no-inline-assembly + // solhint-disable-next-line no-inline-assemblycontracts/test/sortition/index.ts (4)
315-334
: Reduce flakiness of weighted draw test.With only 100 samples, randomness modulo arithmetic could underperform the 80% threshold sporadically. Increase draws (e.g., 1_000) or relax the threshold a bit (e.g., > 70%) to stabilize CI.
- const numDraws = 100; + const numDraws = 1000; - expect(draws[getTestAddress(1).toLowerCase()]).to.be.greaterThan(numDraws * 0.8); + expect(draws[getTestAddress(1).toLowerCase()]).to.be.greaterThan(numDraws * 0.7);
631-647
: Make the MaxUint256 “gas limit” test deterministic.Catching an error by matching
/gas/i
is brittle across EVMs/providers. Either assert the call does not revert or skip if environment-dependent.- try { - await sortitionTree.set(0, juror, maxStake); - expect(await sortitionTree.stakeOf(0, juror)).to.equal(maxStake); - } catch (error) { - // Expected to fail due to gas limits, not due to overflow - expect(error).to.match(/gas/i); - } + await expect(sortitionTree.set(0, juror, maxStake)).to.not.be.reverted; + expect(await sortitionTree.stakeOf(0, juror)).to.equal(maxStake);
509-519
: Tighten the expectation on returned court IDs or document rationale.Allowing ranges like
[0,1]
and[1,2]
hides regressions. If the algorithm should return the exact source court, assert equality; otherwise, add a brief comment explaining why multiple values are valid.
15-17
: Add a tiny helper to standardize casting in assertions.Optional convenience to reduce repetition.
// Helper function to create a test juror address const getTestAddress = (index: number): string => accounts[index % accounts.length].address; + // Helper for BigInt casting in ethers v6 assertions + const bn = (v: number | bigint) => BigInt(v);contracts/src/arbitration/interfaces/ISortitionModule.sol (1)
33-40
: Clarify setStakePenalty return semantics.The name
availablePenalty
is ambiguous. If it represents “remaining penalty to apply in other courts,” document it here, or consider renaming in a future major bump. Ensure implementations return the remainder, not the applied amount.contracts/src/arbitration/university/SortitionModuleUniversity.sol (4)
115-118
: Mark createTree as overriding (and consider restricting caller).If
ISortitionModuleUniversity
declarescreateTree
, addoverride
. Also consideronlyByCore
if creation should be core-driven.- function createTree(uint96 _courtID, bytes memory _extraData) external { + function createTree(uint96 _courtID, bytes memory _extraData) external override /* onlyByCore */ {
246-287
: _setStake: minor safety and clarity improvements.
- Consider guarding
juror.stakedPnk -= _pnkWithdrawal;
with an assert (or rely on upstream invariant) to avoid underflow if callers change.- Event: consider making
_courtID
auint96
inStakeSet
for consistency.- event StakeSet(address indexed _address, uint256 _courtID, uint256 _amount, uint256 _amountAllCourts); + event StakeSet(address indexed _address, uint96 _courtID, uint256 _amount, uint256 _amountAllCourts);
69-76
: Initialize: add zero-address checks (optional).Add sanity checks for
_owner
and_core
to fail fast on bad deployments.function initialize(address _owner, KlerosCoreUniversity _core) external reinitializer(1) { - owner = _owner; - core = _core; + if (_owner == address(0)) revert OwnerOnly(); // or a dedicated ZeroAddress error + owner = _owner; + if (address(_core) == address(0)) revert KlerosCoreOnly(); // or a dedicated ZeroAddress error + core = _core; }Also applies to: 88-93
348-351
: draw: returnfromSubcourtID
explicitly (even if zero).Tiny readability improvement; no behavior change.
-function draw(uint96, uint256, uint256) public view override returns (address drawnAddress, uint96 fromSubcourtID) { - drawnAddress = transientJuror; -} +function draw(uint96 _courtID, uint256, uint256) public view override returns (address drawnAddress, uint96 fromSubcourtID) { + drawnAddress = transientJuror; + fromSubcourtID = _courtID; // educational mock: echo back +}contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (5)
173-177
: Normalize changeOwner signature to match storage type.owner is address; changeOwner takes address payable. Make types consistent.
- function changeOwner(address payable _owner) external onlyByOwner { + function changeOwner(address _owner) external onlyByOwner { owner = _owner; }
260-278
: Explicitly bound-check vote IDs to avoid Panic reverts.Accessing round.votes[_voteIDs[i]] without bounds checks can trigger a low-level Panic. Emit a clear error instead.
for (uint256 i = 0; i < _voteIDs.length; i++) { + if (_voteIDs[i] >= round.votes.length) revert VoteIDOutOfBounds(); if (round.votes[_voteIDs[i]].account != msg.sender) revert JurorHasToOwnTheVote(); round.votes[_voteIDs[i]].commit = _commit; }
Also add at the bottom error list:
+ error VoteIDOutOfBounds();
320-328
: Same bound-check for castVote loop.for (uint256 i = 0; i < _voteIDs.length; i++) { + if (_voteIDs[i] >= round.votes.length) revert VoteIDOutOfBounds(); if (round.votes[_voteIDs[i]].account != _juror) revert JurorHasToOwnTheVote(); if (hiddenVotes && round.votes[_voteIDs[i]].commit != voteHash) revert HashDoesNotMatchHiddenVoteCommitment(); if (round.votes[_voteIDs[i]].voted) revert VoteAlreadyCast(); ... }
164-171
: Bubble revert reasons from executeOwnerProposal for easier debugging.Optional: on failure, bubble returndata instead of a generic UnsuccessfulCall. Keeps revert diagnostics.
function executeOwnerProposal(address _destination, uint256 _amount, bytes memory _data) external onlyByOwner { - (bool success, ) = _destination.call{value: _amount}(_data); - if (!success) revert UnsuccessfulCall(); + (bool success, bytes memory returndata) = _destination.call{value: _amount}(_data); + if (!success) { + assembly { + revert(add(returndata, 0x20), mload(returndata)) + } + } }
663-681
: Fix returned name typo (totalCommited → totalCommitted).ABI names matter for tooling/UX; align spelling.
- uint256 totalCommited, + uint256 totalCommitted,contracts/src/test/SortitionTreesMock.sol (2)
58-62
: Avoid external self-calls (this.set) inside loops to save gas and reentrancy surface.Refactor to an internal helper and call it directly.
- function set(uint96 _courtID, address _account, uint256 _value) external { + function set(uint96 _courtID, address _account, uint256 _value) external { + _set(_courtID, _account, _value); + } + + function _set(uint96 _courtID, address _account, uint256 _value) internal { TreeKey key = CourtID.wrap(_courtID).toTreeKey(); bytes32 stakePathID = SortitionTrees.toStakePathID(_account, _courtID); SortitionTrees.set(trees[key], _value, stakePathID); } /// @dev Set stake for a juror across multiple courts (for testing hierarchy) function setStakeInHierarchy(uint96[] calldata _courtIDs, address _account, uint256 _value) external { for (uint256 i = 0; i < _courtIDs.length; i++) { - this.set(_courtIDs[i], _account, _value); + _set(_courtIDs[i], _account, _value); } }
69-73
: Avoid external self-calls (this.stakeOf) in getters.Use an internal helper to read directly.
- function getStakesAcrossCourts( + function getStakesAcrossCourts( address _account, uint96[] calldata _courtIDs ) external view returns (uint256[] memory stakes) { stakes = new uint256[](_courtIDs.length); for (uint256 i = 0; i < _courtIDs.length; i++) { - stakes[i] = this.stakeOf(_courtIDs[i], _account); + stakes[i] = _stakeOf(_courtIDs[i], _account); } } + + function _stakeOf(uint96 _courtID, address _account) internal view returns (uint256) { + TreeKey key = CourtID.wrap(_courtID).toTreeKey(); + bytes32 stakePathID = SortitionTrees.toStakePathID(_account, _courtID); + return SortitionTrees.stakeOf(trees[key], stakePathID); + }contracts/src/arbitration/SortitionModuleBase.sol (2)
303-319
: Docstring mismatch: setStakePenalty is a penalty, not a reward.Fix comment to avoid confusion.
-/// @dev Update the state of the stakes with a PNK reward deposit, called by KC during rewards execution. +/// @dev Apply a PNK penalty (deduction) to the juror’s stake, called by KC during rewards execution.
202-208
: executeDelayedStakes: no-op handling when nothing pending.Current revert uses delayedStakeWriteIndex < delayedStakeReadIndex. When equal (common “empty” case after full drain), revert would not trigger, but loop becomes 0-iter, which is fine. If you prefer explicit UX, consider reverting also on equality.
- if (delayedStakeWriteIndex < delayedStakeReadIndex) revert NoDelayedStakeToExecute(); + if (delayedStakeWriteIndex + 1 <= delayedStakeReadIndex) revert NoDelayedStakeToExecute();
📜 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.
📒 Files selected for processing (10)
contracts/CHANGELOG.md
(2 hunks)contracts/src/arbitration/KlerosCoreBase.sol
(27 hunks)contracts/src/arbitration/SortitionModuleBase.sol
(13 hunks)contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
(14 hunks)contracts/src/arbitration/interfaces/ISortitionModule.sol
(3 hunks)contracts/src/arbitration/university/SortitionModuleUniversity.sol
(11 hunks)contracts/src/libraries/SortitionTrees.sol
(1 hunks)contracts/src/test/SortitionModuleMock.sol
(1 hunks)contracts/src/test/SortitionTreesMock.sol
(1 hunks)contracts/test/sortition/index.ts
(1 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 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
contracts/src/arbitration/KlerosCoreBase.sol
📚 Learning: 2024-11-19T16:09:41.467Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:3-17
Timestamp: 2024-11-19T16:09:41.467Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the General Court (ID: 1) can have its `parent` ID set to itself (`"parent": 1`), as there is no parent court with ID 0 currently.
Applied to files:
contracts/src/arbitration/KlerosCoreBase.sol
🪛 LanguageTool
contracts/CHANGELOG.md
[style] ~15-~15: Consider a more concise word here.
Context: ...reaking:** Rename governor
to owner
in order to comply with the lightweight ownership s...
(IN_ORDER_TO_PREMIUM)
⏰ 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: SonarCloud
- GitHub Check: contracts-testing
- GitHub Check: contracts-testing
- GitHub Check: SonarCloud
- GitHub Check: Analyze (javascript)
🔇 Additional comments (23)
contracts/src/arbitration/KlerosCoreBase.sol (17)
171-179
: Access-control rename to Owner LGTM.
226-227
: Tree creation ordering OK.Creating FORKING and GENERAL trees early matches usage later.
Also applies to: 239-240
261-272
: Pause/unpause semantics consistent.Guardian-or-owner for pause and owner-only for unpause is coherent.
392-417
: Parameter update validations LGTM.Parent/children
minStake
constraints enforced correctly.
423-438
: enableDisputeKits: good guardrails on Classic DK.
467-468
: OnError.Revert choice is correct for user-facing staking.
470-477
: By-sortition staking path: good separation and no-delay.Using
OnError.Return
avoids bricking the module loop.
610-618
: Track subcourt for penalty: good; add bounds fallback already handled.Using
fromSubcourtID != 0 ? fromSubcourtID : dispute.courtID
is safe if DK returns 0 for “none”.
643-651
: Jump helpers integration LGTM.Emitting
CourtJump
on actual jump is correct.
939-957
: appealCost: jump logic OK; TODO on General Court fork should be actionable.Returning
NON_PAYABLE_AMOUNT
is a sentinel; either implement or open a tracked issue so integrators can handle it predictably.
1045-1051
: isDisputeKitJumping: early exit is fine.
1065-1076
: New jump helpers read cleanly; solid separation of concerns.Also applies to: 1078-1098
1114-1118
: ONE_BASIS_POINT migration LGTM.Replacing legacy divisor with basis-point constant is correct and gas-neutral.
Also applies to: 1125-1126
1141-1150
: _setStake signature/usage: good extension for no-delay and error policy.Also applies to: 1162-1164
1236-1238
: Error renames align with owner/guardian migration.
443-446
: AcceptedFeeToken already declared in IArbitratorV2.sol
The event is defined in the IArbitratorV2 interface (contracts/src/arbitration/interfaces/IArbitratorV2.sol:30) and imported by KlerosCoreBase.sol, so no additional declaration is needed.Likely an incorrect or invalid review comment.
565-567
: Confirm commit-period gating in Shutter and add edge-case tests
Ensure removing the “all commits cast” shortcut doesn’t break Shutter auto-reveal. Add tests covering:
- Early full-commit pass in KlerosCoreBase (commit→vote transition when all commits are in before window end)
- Commit gating in KlerosCoreUniversity (University flow)
- Short commit windows and timing edges (warp scenarios)
contracts/src/test/SortitionModuleMock.sol (2)
6-6
: Import looks correct and scoped.
12-12
: LGTM: mock reads SortitionTrees state via typed key.Access pattern aligns with the new library; return values preserved.
contracts/src/arbitration/interfaces/ISortitionModule.sol (1)
51-56
: Resolved — draw 2-tuple is propagated and consumed.
- Interfaces updated: ISortitionModule.draw and IDisputeKit.draw now return (address, uint96). (contracts/src/arbitration/interfaces/ISortitionModule.sol, contracts/src/arbitration/interfaces/IDisputeKit.sol)
- Sortition implements the 2-tuple: SortitionModuleBase.draw returns (address, uint96) and sources the second value from the trees. (contracts/src/arbitration/SortitionModuleBase.sol)
- Dispute kits forward it: DisputeKitClassicBase.draw forwards (drawnAddress, fromSubcourtID) from sortitionModule.draw. (contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol)
- Core consumes it: KlerosCoreBase.draw receives (drawnAddress, fromSubcourtID) and pushes fromSubcourtID into round.drawnJurorFromCourtIDs. (contracts/src/arbitration/KlerosCoreBase.sol)
No further changes required.
contracts/src/arbitration/university/SortitionModuleUniversity.sol (1)
331-339
: Leftover PNK withdrawal: good state-change-before-external-call pattern.Setting
stakedPnk = 0
beforetransferBySortitionModule
prevents reentrancy double-withdrawals. LGTM.contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
615-622
: Confirm isAppealFunded semantics when exactly one choice is funded.Function name/docs suggest “funding finished prematurely when losing side didn’t fund.” Current condition returns true only if fundedChoices.length == 0. Should it also return true when fundedChoices.length == 1 and loser window elapsed?
contracts/src/arbitration/SortitionModuleBase.sol (1)
481-489
: draw should not depend on stale randomNumber across Drawing phases.If Drawing spans multiple disputes, ensure randomNumber is updated every Generating phase and cannot persist across cycles unexpectedly. Consider zeroing randomNumber when entering staking or after Drawing completes.
function createTree(uint96 _courtID, bytes memory _extraData) external; | ||
|
||
function validateStake( | ||
address _account, | ||
uint96 _courtID, | ||
uint256 _newStake | ||
uint256 _newStake, | ||
bool _noDelay | ||
) external returns (uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult); |
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
createTree/validateStake signature changes look consistent; ensure downstream updates.
All implementers/call sites must adopt (uint96 _courtID, bytes _extraData)
and extra _noDelay
param. Recommend verifying overrides and mocks compile against this interface.
Run:
🏁 Script executed:
#!/bin/bash
# Validate overrides across the repo
rg -nP 'interface\s+ISortitionModule\b' -C1
rg -nP 'function\s+createTree\s*\(\s*uint96\s*_courtID,\s*bytes' -C2
rg -nP 'validateStake\s*\(\s*address\s*_account,\s*uint96\s*_courtID,\s*uint256\s*_newStake,\s*bool\s*_noDelay' -C2
rg -nP '(contract|abstract).*SortitionModule.*\b(createTree|validateStake)\b' -C3
Length of output: 317853
Fix ISortitionModule signature mismatch — update implementations & callers to the new signatures (createTree(uint96, bytes) and validateStake(..., bool _noDelay))
rg shows no uses of the new signatures; the repo still uses createTree(bytes32, bytes) and validateStake(address,uint96,uint256). Update these locations at minimum:
- contracts/src/arbitration/SortitionModuleBase.sol — change createTree/validateStake signatures and internals.
- contracts/src/arbitration/SortitionModule.sol — update overrides to match interface.
- contracts/src/arbitration/KlerosCoreBase.sol — update calls to sortitionModule.createTree(...) and sortitionModule.validateStake(..., _noDelay).
- contracts/deployments/** and any tests/mocks that rely on the old ABI — regenerate/update.
🤖 Prompt for AI Agents
In contracts/src/arbitration/interfaces/ISortitionModule.sol around lines 16–23
the interface signatures changed to createTree(uint96 _courtID, bytes
_extraData) and validateStake(address _account, uint96 _courtID, uint256
_newStake, bool _noDelay) returns (uint256 pnkDeposit, uint256 pnkWithdrawal,
StakingResult stakingResult); update the implementations and callers
accordingly: in contracts/src/arbitration/SortitionModuleBase.sol and
contracts/src/arbitration/SortitionModule.sol change the function declarations
to match the new signatures and adjust internal logic to accept a uint96 courtID
for createTree and handle the added _noDelay boolean and the three-value return
from validateStake; in contracts/src/arbitration/KlerosCoreBase.sol update all
calls to sortitionModule.createTree(...) and sortitionModule.validateStake(...,
_noDelay) to pass a uint96 courtID and the new _noDelay flag and to capture the
pnkDeposit/pnkWithdrawal/stakingResult return values; finally update any
deployment scripts, generated ABIs, tests and mocks under
contracts/deployments/** and test files to the new ABI signatures and regenerate
artifacts so builds/tests use the updated interface.
/// @dev Allows the owner to call anything on behalf of the contract. | ||
/// @param _destination The destination of the call. | ||
/// @param _amount The value sent with the call. | ||
/// @param _data The data sent with the call. | ||
function executeGovernorProposal( | ||
address _destination, | ||
uint256 _amount, | ||
bytes memory _data | ||
) external onlyByGovernor { | ||
function executeOwnerProposal(address _destination, uint256 _amount, bytes memory _data) external onlyByOwner { | ||
(bool success, ) = _destination.call{value: _amount}(_data); | ||
if (!success) revert UnsuccessfulCall(); | ||
} | ||
|
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
Owner arbitrary-call: add event and reentrancy guard.
This function can send ETH and call arbitrary code; emit an audit trail and block reentrancy.
- function executeOwnerProposal(address _destination, uint256 _amount, bytes memory _data) external onlyByOwner {
+ event OwnerCallExecuted(address indexed destination, uint256 value, bytes data, bool success);
+ function executeOwnerProposal(address _destination, uint256 _amount, bytes memory _data) external onlyByOwner nonReentrant {
(bool success, ) = _destination.call{value: _amount}(_data);
if (!success) revert UnsuccessfulCall();
+ emit OwnerCallExecuted(_destination, _amount, _data, success);
}
See guard scaffold at the end of this review for a minimal nonReentrant
modifier.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 273 to 281, the
executeOwnerProposal allows sending ETH and calling arbitrary code but lacks an
event and reentrancy protection; add a nonReentrant modifier to the contract
(e.g., using a private uint256 _reentrancyLock with checks and updates) and
apply it to executeOwnerProposal, declare a descriptive event (e.g.,
OwnerExecutedProposal(address indexed destination, uint256 amount, bytes data,
bool success)) and emit it after the call (including success/failure info), and
ensure the modifier prevents reentrancy by setting and clearing the lock and
reverting on reentrancy attempts.
/// @dev Changes the `owner` storage variable. | ||
/// @param _owner The new value for the `owner` storage variable. | ||
function changeOwner(address payable _owner) external onlyByOwner { | ||
owner = _owner; | ||
} |
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
Ownership change: emit event and reject zero-address unless “renounce” is intended.
If renouncing is wanted, expose a dedicated renounceOwner()
; otherwise prevent zero.
- function changeOwner(address payable _owner) external onlyByOwner {
- owner = _owner;
+ function changeOwner(address payable _owner) external onlyByOwner {
+ if (_owner == address(0)) revert OwnerZeroAddress();
+ emit OwnerChanged(owner, _owner);
+ owner = _owner;
}
📝 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.
/// @dev Changes the `owner` storage variable. | |
/// @param _owner The new value for the `owner` storage variable. | |
function changeOwner(address payable _owner) external onlyByOwner { | |
owner = _owner; | |
} | |
/// @dev Changes the `owner` storage variable. | |
/// @param _owner The new value for the `owner` storage variable. | |
function changeOwner(address payable _owner) external onlyByOwner { | |
if (_owner == address(0)) revert OwnerZeroAddress(); | |
emit OwnerChanged(owner, _owner); | |
owner = _owner; | |
} |
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 282 to 286, the
changeOwner function currently assigns owner directly; update it to (1) reject
the zero address with a require(check) to prevent accidental renouncement unless
explicitly intended, (2) emit an OwnershipTransferred (or OwnerChanged) event
with previous and new owner values, and (3) if renouncement should be supported,
implement a separate renounceOwner() function that sets owner to address(0) and
emits the same event; ensure onlyByOwner remains enforced.
/// @dev Changes the `guardian` storage variable. | ||
/// @param _guardian The new value for the `guardian` storage variable. | ||
function changeGuardian(address _guardian) external onlyByGovernor { | ||
function changeGuardian(address _guardian) external onlyByOwner { | ||
guardian = _guardian; | ||
} | ||
|
||
/// @dev Changes the `pinakion` storage variable. | ||
/// @param _pinakion The new value for the `pinakion` storage variable. | ||
function changePinakion(IERC20 _pinakion) external onlyByGovernor { | ||
function changePinakion(IERC20 _pinakion) external onlyByOwner { | ||
pinakion = _pinakion; | ||
} | ||
|
||
/// @dev Changes the `jurorProsecutionModule` storage variable. | ||
/// @param _jurorProsecutionModule The new value for the `jurorProsecutionModule` storage variable. | ||
function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByGovernor { | ||
function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByOwner { | ||
jurorProsecutionModule = _jurorProsecutionModule; | ||
} | ||
|
||
/// @dev Changes the `_sortitionModule` storage variable. | ||
/// Note that the new module should be initialized for all courts. | ||
/// @param _sortitionModule The new value for the `sortitionModule` storage variable. | ||
function changeSortitionModule(ISortitionModule _sortitionModule) external onlyByGovernor { | ||
function changeSortitionModule(ISortitionModule _sortitionModule) external onlyByOwner { | ||
sortitionModule = _sortitionModule; | ||
} | ||
|
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
Change-XXX setters: add basic validation.
changeGuardian
: prevent setting owner as guardian (optional) or zero if not allowed.changePinakion
: disallow zero; consider preventingNATIVE_CURRENCY
.changeJurorProsecutionModule
/changeSortitionModule
: require contract code present; for sortition, verify the module is initialized for all courts or document the exact migration procedure.
- function changePinakion(IERC20 _pinakion) external onlyByOwner {
+ function changePinakion(IERC20 _pinakion) external onlyByOwner {
+ if (address(_pinakion) == address(0)) revert InvalidAddress();
pinakion = _pinakion;
}
@@
- function changeSortitionModule(ISortitionModule _sortitionModule) external onlyByOwner {
- sortitionModule = _sortitionModule;
+ function changeSortitionModule(ISortitionModule _sortitionModule) external onlyByOwner {
+ if (address(_sortitionModule) == address(0)) revert InvalidAddress();
+ sortitionModule = _sortitionModule;
}
Add:
+ error InvalidAddress();
Please confirm where the “initialize for all courts” step is executed during upgrade.
🏁 Script executed:
#!/bin/bash
set -e
# Verify existing validation in changeXXX functions
rg -n -C3 "function changeGuardian" -g "contracts/src/arbitration/KlerosCoreBase.sol"
rg -n -C3 "function changePinakion" -g "contracts/src/arbitration/KlerosCoreBase.sol"
rg -n -C3 "function changeJurorProsecutionModule" -g "contracts/src/arbitration/KlerosCoreBase.sol"
rg -n -C3 "function changeSortitionModule" -g "contracts/src/arbitration/KlerosCoreBase.sol"
# Check court creation and sortition module initialization
rg -n -C3 "function createCourt" -g "contracts/src/arbitration/KlerosCoreBase.sol"
rg -n -C3 "initialize" -g "contracts/src/arbitration/KlerosCoreBase.sol"
Length of output: 5036
🏁 Script executed:
#!/bin/bash
set -e
echo "--- sortitionModule.initialize callers ---"
rg -nP "sortitionModule\\.initialize" -g contracts/src/arbitration/KlerosCoreBase.sol || echo "none found"
echo "--- createCourt definition and body ---"
rg -nP -C10 "function createCourt" -g contracts/src/arbitration/KlerosCoreBase.sol
echo "--- __KlerosCoreBase_initialize implementation ---"
rg -nP -C5 "__KlerosCoreBase_initialize" -g contracts/src/arbitration/KlerosCoreBase.sol
Length of output: 3595
🏁 Script executed:
#!/bin/bash
set -e
echo "----- createCourt implementation (lines 310–380) -----"
sed -n '310,380p' contracts/src/arbitration/KlerosCoreBase.sol
echo
echo "----- All calls to sortitionModule. within file -----"
rg -n "sortitionModule\." -g contracts/src/arbitration/KlerosCoreBase.sol || echo "none found"
Length of output: 5392
Add non-zero address checks to all setters and document sortition module migration
- In
changeGuardian
,changePinakion
,changeJurorProsecutionModule
andchangeSortitionModule
, addand declareif (address(_…Parameter…) == address(0)) revert InvalidAddress();
alongside the other custom errors.error InvalidAddress();
- After swapping in a new
sortitionModule
, existing courts must each callas part of your upgrade or migration script—no such initialization currently exists insortitionModule.createTree(courtID, _sortitionExtraData);
changeSortitionModule
. Document or implement where and how you’ll initialize the new module for all pre-existing courts.
// [0, 1] value that determines how coherent the juror was in this round, in basis points. | ||
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence( | ||
uint256 coherence = disputeKit.getDegreeOfCoherencePenalty( | ||
_params.disputeID, | ||
_params.round, | ||
_params.repartition, | ||
_params.feePerJurorInRound, | ||
_params.pnkAtStakePerJurorInRound | ||
); | ||
if (degreeOfCoherence > ALPHA_DIVISOR) { | ||
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. | ||
degreeOfCoherence = ALPHA_DIVISOR; | ||
|
||
// Guard against degree exceeding 1, though it should be ensured by the dispute kit. | ||
if (coherence > ONE_BASIS_POINT) { | ||
coherence = ONE_BASIS_POINT; | ||
} | ||
|
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
Penalty path: coherence clamp good; consider reentrancy posture.
External calls occur before state sync and token sends (unlock, setStakePenalty, potential ETH send to owner). Add nonReentrant
on execute()
or move all state writes before external interactions where feasible.
Additionally, message comment says “breaks CEI” near round.pnkPenalties
update. If keeping the cached write, guard the whole execute()
with nonReentrant
(see scaffold below).
Also applies to: 773-786, 797-803, 805-816
// [0, 1] value that determines how coherent the juror was in this round, in basis points. | ||
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence( | ||
(uint256 pnkCoherence, uint256 feeCoherence) = disputeKit.getDegreeOfCoherenceReward( | ||
_params.disputeID, | ||
_params.round, | ||
_params.repartition % _params.numberOfVotesInRound, | ||
_params.feePerJurorInRound, | ||
_params.pnkAtStakePerJurorInRound | ||
); | ||
|
||
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. | ||
if (degreeOfCoherence > ALPHA_DIVISOR) { | ||
degreeOfCoherence = ALPHA_DIVISOR; | ||
// Guard against degree exceeding 1, though it should be ensured by the dispute kit. | ||
if (pnkCoherence > ONE_BASIS_POINT) { | ||
pnkCoherence = ONE_BASIS_POINT; | ||
} | ||
if (feeCoherence > ONE_BASIS_POINT) { | ||
feeCoherence = ONE_BASIS_POINT; | ||
} |
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
Rewards path: coherent split by PNK/fees is correct; minor hardening.
- Clamp to ONE_BASIS_POINT is good.
- Consider reentrancy guard on
execute()
due to ETH/WERC20 sends to jurors/owner. - Optional: emit a single “RoundSettled” summary event at end to ease analytics.
Also applies to: 845-855, 857-863, 869-872, 874-893
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 827 to 842, the
coherence clamp is fine but the execute() path performs ETH/WERC20 transfers to
jurors/owner and needs reentrancy hardening: add a reentrancy guard (e.g.,
inherit OpenZeppelin ReentrancyGuard and mark execute() nonReentrant or
implement an internal mutex), ensure checks-effects-interactions by performing
all state updates (marking round settled, updating balances/flags) before any
external token/ETH transfers, and emit a single RoundSettled summary event at
the end of the function summarizing amounts and recipients for easier analytics.
function createTree(uint96 _courtID, bytes memory _extraData) external override onlyByCore { | ||
TreeKey key = CourtID.wrap(_courtID).toTreeKey(); | ||
uint256 K = _extraDataToTreeK(_extraData); | ||
require(tree.K == 0, "Tree already exists."); | ||
require(K > 1, "K must be greater than one."); | ||
tree.K = K; | ||
tree.nodes.push(0); | ||
sortitionSumTrees.createTree(key, K); | ||
} |
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
Validate K ≥ 2 when creating trees (defensive invariant).
Creating a K=0/1 tree breaks sum-tree invariants. Guard before createTree.
function createTree(uint96 _courtID, bytes memory _extraData) external override onlyByCore {
TreeKey key = CourtID.wrap(_courtID).toTreeKey();
uint256 K = _extraDataToTreeK(_extraData);
+ if (K < 2) revert KMustBeGreaterThanOne();
sortitionSumTrees.createTree(key, K);
}
Add error:
error NotDrawingPhase();
+error KMustBeGreaterThanOne();
📝 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 createTree(uint96 _courtID, bytes memory _extraData) external override onlyByCore { | |
TreeKey key = CourtID.wrap(_courtID).toTreeKey(); | |
uint256 K = _extraDataToTreeK(_extraData); | |
require(tree.K == 0, "Tree already exists."); | |
require(K > 1, "K must be greater than one."); | |
tree.K = K; | |
tree.nodes.push(0); | |
sortitionSumTrees.createTree(key, K); | |
} | |
// … elsewhere in the file, alongside other error declarations | |
error NotDrawingPhase(); | |
error KMustBeGreaterThanOne(); | |
function createTree(uint96 _courtID, bytes memory _extraData) external override onlyByCore { | |
TreeKey key = CourtID.wrap(_courtID).toTreeKey(); | |
uint256 K = _extraDataToTreeK(_extraData); | |
if (K < 2) revert KMustBeGreaterThanOne(); | |
sortitionSumTrees.createTree(key, K); | |
} |
🤖 Prompt for AI Agents
In contracts/src/arbitration/SortitionModuleBase.sol around lines 185 to 189,
the computed tree arity K from _extraDataToTreeK(_extraData) is not validated,
allowing K=0 or K=1 which breaks sum-tree invariants; add a guard immediately
after computing K to require K >= 2 and revert with a clear error (either
require(K >= 2, "K must be >= 2") or a custom error) before calling
sortitionSumTrees.createTree(key, K) so creation never proceeds with invalid K.
function setStakePenalty( | ||
address _account, | ||
uint96 _courtID, | ||
uint256 _penalty | ||
) external override onlyByCore returns (uint256 pnkBalance, uint256 newCourtStake, uint256 availablePenalty) { | ||
Juror storage juror = jurors[_account]; | ||
availablePenalty = _penalty; | ||
newCourtStake = _stakeOf(_account, _courtID); | ||
if (juror.stakedPnk < _penalty) { | ||
availablePenalty = juror.stakedPnk; | ||
} | ||
|
||
if (availablePenalty == 0) return (juror.stakedPnk, newCourtStake, 0); // No penalty to apply. | ||
|
||
uint256 currentStake = _stakeOf(_account, _courtID); | ||
uint256 newStake = 0; | ||
if (currentStake >= availablePenalty) { | ||
newStake = currentStake - availablePenalty; | ||
} | ||
_setStake(_account, _courtID, 0, availablePenalty, newStake); | ||
pnkBalance = juror.stakedPnk; // updated by _setStake() | ||
newCourtStake = _stakeOf(_account, _courtID); // updated by _setStake() | ||
} |
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
Fix penalty application to preserve accounting invariants and return remainder.
Current code withdraws _penalty
from stakedPnk
even when the court stake is smaller, leaving stakedPnk
< sum of per-court stakes and returning an availablePenalty
that equals the applied amount, not the remainder. Clamp applied penalty to the lesser of (a) total balance available, (b) the court stake; then return the leftover for upstream cascading.
- ) external override onlyByCore returns (uint256 pnkBalance, uint256 newCourtStake, uint256 availablePenalty) {
- Juror storage juror = jurors[_account];
- availablePenalty = _penalty;
- newCourtStake = _stakeOf(_account, _courtID);
- if (juror.stakedPnk < _penalty) {
- availablePenalty = juror.stakedPnk;
- }
-
- if (availablePenalty == 0) return (juror.stakedPnk, newCourtStake, 0); // No penalty to apply.
-
- uint256 currentStake = _stakeOf(_account, _courtID);
- uint256 newStake = 0;
- if (currentStake >= availablePenalty) {
- newStake = currentStake - availablePenalty;
- }
- _setStake(_account, _courtID, 0, availablePenalty, newStake);
- pnkBalance = juror.stakedPnk; // updated by _setStake()
- newCourtStake = _stakeOf(_account, _courtID); // updated by _setStake()
- }
+ ) external override onlyByCore returns (uint256 pnkBalance, uint256 newCourtStake, uint256 availablePenalty) {
+ Juror storage juror = jurors[_account];
+ // Max we can take overall in this tx
+ uint256 maxApplicable = _penalty <= juror.stakedPnk ? _penalty : juror.stakedPnk;
+ uint256 currentStake = _stakeOf(_account, _courtID);
+ // Apply only what this court can cover
+ uint256 applied = currentStake <= maxApplicable ? currentStake : maxApplicable;
+ if (applied == 0) {
+ return (juror.stakedPnk, currentStake, maxApplicable); // nothing applied here; leftover equals maxApplicable
+ }
+ uint256 newStake = currentStake - applied;
+ _setStake(_account, _courtID, 0, applied, newStake);
+ pnkBalance = juror.stakedPnk; // updated by _setStake()
+ newCourtStake = _stakeOf(_account, _courtID); // updated by _setStake()
+ availablePenalty = maxApplicable - applied; // remainder to be applied to other courts upstream
+ }
📝 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 setStakePenalty( | |
address _account, | |
uint96 _courtID, | |
uint256 _penalty | |
) external override onlyByCore returns (uint256 pnkBalance, uint256 newCourtStake, uint256 availablePenalty) { | |
Juror storage juror = jurors[_account]; | |
availablePenalty = _penalty; | |
newCourtStake = _stakeOf(_account, _courtID); | |
if (juror.stakedPnk < _penalty) { | |
availablePenalty = juror.stakedPnk; | |
} | |
if (availablePenalty == 0) return (juror.stakedPnk, newCourtStake, 0); // No penalty to apply. | |
uint256 currentStake = _stakeOf(_account, _courtID); | |
uint256 newStake = 0; | |
if (currentStake >= availablePenalty) { | |
newStake = currentStake - availablePenalty; | |
} | |
_setStake(_account, _courtID, 0, availablePenalty, newStake); | |
pnkBalance = juror.stakedPnk; // updated by _setStake() | |
newCourtStake = _stakeOf(_account, _courtID); // updated by _setStake() | |
} | |
function setStakePenalty( | |
address _account, | |
uint96 _courtID, | |
uint256 _penalty | |
) external override onlyByCore returns (uint256 pnkBalance, uint256 newCourtStake, uint256 availablePenalty) { | |
Juror storage juror = jurors[_account]; | |
// Max we can take overall in this tx | |
uint256 maxApplicable = _penalty <= juror.stakedPnk ? _penalty : juror.stakedPnk; | |
uint256 currentStake = _stakeOf(_account, _courtID); | |
// Apply only what this court can cover | |
uint256 applied = currentStake <= maxApplicable ? currentStake : maxApplicable; | |
if (applied == 0) { | |
return (juror.stakedPnk, currentStake, maxApplicable); // nothing applied here; leftover equals maxApplicable | |
} | |
uint256 newStake = currentStake - applied; | |
_setStake(_account, _courtID, 0, applied, newStake); | |
pnkBalance = juror.stakedPnk; // updated by _setStake() | |
newCourtStake = _stakeOf(_account, _courtID); // updated by _setStake() | |
availablePenalty = maxApplicable - applied; // remainder to be applied to other courts upstream | |
} |
🤖 Prompt for AI Agents
In contracts/src/arbitration/university/SortitionModuleUniversity.sol around
lines 197-219, the function currently withdraws the full requested _penalty from
stakedPnk regardless of the court-specific stake, breaking accounting and
returning the wrong availablePenalty; fix by computing an appliedPenalty =
min(_penalty, juror.stakedPnk, currentStake) (clamp to available PNK and the
court stake), call _setStake to withdraw only appliedPenalty from the
juror/court, then set pnkBalance = juror.stakedPnk and newCourtStake =
_stakeOf(_account, _courtID) after the update, and return availablePenalty =
_penalty - appliedPenalty so the remainder can cascade upstream.
// While it still has children | ||
uint256 treeIndex = 0; | ||
while ((_tree.K * treeIndex) + 1 < _tree.nodes.length) { | ||
for (uint256 i = 1; i <= _tree.K; i++) { | ||
// Loop over children. | ||
uint256 nodeIndex = (_tree.K * treeIndex) + i; | ||
uint256 nodeValue = _tree.nodes[nodeIndex]; | ||
|
||
if (currentDrawnNumber >= nodeValue) { | ||
// Go to the next child. | ||
currentDrawnNumber -= nodeValue; | ||
} else { | ||
// Pick this child. | ||
treeIndex = nodeIndex; | ||
break; | ||
} | ||
} | ||
} |
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.
draw(): out-of-bounds read risk and potential infinite loop.
Iterating i=1..K unconditionally reads children that may not exist yet (nodeIndex >= nodes.length), which reverts. If no child consumes the draw, the while loop can spin. Fix by bounds-checking each child and failing safely.
- while ((_tree.K * treeIndex) + 1 < _tree.nodes.length) {
- for (uint256 i = 1; i <= _tree.K; i++) {
- // Loop over children.
- uint256 nodeIndex = (_tree.K * treeIndex) + i;
- uint256 nodeValue = _tree.nodes[nodeIndex];
-
- if (currentDrawnNumber >= nodeValue) {
- // Go to the next child.
- currentDrawnNumber -= nodeValue;
- } else {
- // Pick this child.
- treeIndex = nodeIndex;
- break;
- }
- }
- }
+ while ((_tree.K * treeIndex) + 1 < _tree.nodes.length) {
+ bool picked = false;
+ for (uint256 i = 1; i <= _tree.K; i++) {
+ uint256 nodeIndex = (_tree.K * treeIndex) + i;
+ if (nodeIndex >= _tree.nodes.length) continue; // missing child slot => value 0
+ uint256 nodeValue = _tree.nodes[nodeIndex];
+ if (currentDrawnNumber < nodeValue) {
+ treeIndex = nodeIndex;
+ picked = true;
+ break;
+ }
+ currentDrawnNumber -= nodeValue;
+ }
+ if (!picked) revert InvalidTreeState(); // defensive: sums must match
+ }
Add the new error:
error TreeAlreadyExists();
error KMustBeGreaterThanOne();
+ error InvalidTreeState();
📝 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.
// While it still has children | |
uint256 treeIndex = 0; | |
while ((_tree.K * treeIndex) + 1 < _tree.nodes.length) { | |
for (uint256 i = 1; i <= _tree.K; i++) { | |
// Loop over children. | |
uint256 nodeIndex = (_tree.K * treeIndex) + i; | |
uint256 nodeValue = _tree.nodes[nodeIndex]; | |
if (currentDrawnNumber >= nodeValue) { | |
// Go to the next child. | |
currentDrawnNumber -= nodeValue; | |
} else { | |
// Pick this child. | |
treeIndex = nodeIndex; | |
break; | |
} | |
} | |
} | |
// While it still has children | |
uint256 treeIndex = 0; | |
while ((_tree.K * treeIndex) + 1 < _tree.nodes.length) { | |
bool picked = false; | |
for (uint256 i = 1; i <= _tree.K; i++) { | |
uint256 nodeIndex = (_tree.K * treeIndex) + i; | |
if (nodeIndex >= _tree.nodes.length) continue; // missing child slot ⇒ value 0 | |
uint256 nodeValue = _tree.nodes[nodeIndex]; | |
if (currentDrawnNumber < nodeValue) { | |
// Pick this child. | |
treeIndex = nodeIndex; | |
picked = true; | |
break; | |
} | |
// Otherwise subtract and continue. | |
currentDrawnNumber -= nodeValue; | |
} | |
if (!picked) revert InvalidTreeState(); // defensive: sums must match | |
} |
// While it still has children | |
uint256 treeIndex = 0; | |
while ((_tree.K * treeIndex) + 1 < _tree.nodes.length) { | |
for (uint256 i = 1; i <= _tree.K; i++) { | |
// Loop over children. | |
uint256 nodeIndex = (_tree.K * treeIndex) + i; | |
uint256 nodeValue = _tree.nodes[nodeIndex]; | |
if (currentDrawnNumber >= nodeValue) { | |
// Go to the next child. | |
currentDrawnNumber -= nodeValue; | |
} else { | |
// Pick this child. | |
treeIndex = nodeIndex; | |
break; | |
} | |
} | |
} | |
error TreeAlreadyExists(); | |
error KMustBeGreaterThanOne(); | |
error InvalidTreeState(); |
🤖 Prompt for AI Agents
In contracts/src/libraries/SortitionTrees.sol around lines 66 to 83, the loop
unconditionally indexes children and can read past nodes.length or loop forever
if no child consumes the draw; add a bounds check before computing/reading
nodeValue (skip or treat missing child as zero), and if you detect nodeIndex >=
_tree.nodes.length then revert with a new descriptive error (e.g.,
NoChildAtIndex) or handle by breaking out safely; ensure the while loop can
always exit by either selecting a valid child or reverting when no children
exist; also add the new error declaration near the top of the contract/library.
const nodes = await sortitionTree.getTreeNodes(testCase.courtID); | ||
expect(nodes.length).to.equal(1); | ||
expect(nodes[0]).to.equal(0); // Root starts at 0 | ||
|
||
const stack = await sortitionTree.getTreeStack(testCase.courtID); | ||
expect(stack.length).to.equal(0); // Empty stack initially | ||
} |
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
Fix BigInt vs number mismatches in assertions (ethers v6 returns BigInt).
Many contract reads return BigInt; several expects compare to JS numbers, causing fragile or failing tests. Prefer 0n/100n or cast with BigInt()/Number() consistently.
Examples below; please apply the same pattern across the file.
@@
- expect(nodes[0]).to.equal(0); // Root starts at 0
+ expect(nodes[0]).to.equal(0n); // Root starts at 0
- expect(await sortitionTree.stakeOf(0, juror)).to.equal(stake);
- expect(await sortitionTree.getRootSum(0)).to.equal(stake);
+ expect(await sortitionTree.stakeOf(0, juror)).to.equal(BigInt(stake));
+ expect(await sortitionTree.getRootSum(0)).to.equal(BigInt(stake));
- const expectedTotal = stakes.reduce((sum, stake) => sum + stake, 0);
+ const expectedTotal = stakes.reduce((sum, stake) => sum + BigInt(stake), 0n);
expect(await sortitionTree.getRootSum(0)).to.equal(expectedTotal);
- expect(await sortitionTree.getRootSum(0)).to.equal(200 + newStake);
+ expect(await sortitionTree.getRootSum(0)).to.equal(200n + BigInt(newStake));
- expect(await sortitionTree.getNodeIndex(0, juror)).to.equal(0); // Should be cleared
+ expect(await sortitionTree.getNodeIndex(0, juror)).to.equal(0n); // Should be cleared
- expect(await sortitionTree.stakeOf(0, juror4)).to.equal(150);
+ expect(await sortitionTree.stakeOf(0, juror4)).to.equal(150n);
- expect(courtID).to.equal(0);
+ expect(Number(courtID)).to.equal(0);
- const stakes = await sortitionTree.getStakesAcrossCourts(juror, courtIDs);
- for (const retrievedStake of stakes) {
- expect(retrievedStake).to.equal(stake);
- }
+ const stakes = await sortitionTree.getStakesAcrossCourts(juror, courtIDs);
+ for (const retrievedStake of stakes) {
+ expect(retrievedStake).to.equal(BigInt(stake));
+ }
- expect(await sortitionTree.getRootSum(0)).to.equal(700);
+ expect(await sortitionTree.getRootSum(0)).to.equal(700n);
Also applies to: 144-151, 162-165, 179-181, 191-204, 205-213, 221-224, 241-244, 268-270, 280-283, 289-295, 309-314, 331-334, 341-353, 375-378, 390-396, 411-415, 445-446, 475-479, 556-559, 569-587, 609-614, 677-681, 692-695
🤖 Prompt for AI Agents
In contracts/test/sortition/index.ts around lines 99-105 (and also update the
ranges 144-151, 162-165, 179-181, 191-204, 205-213, 221-224, 241-244, 268-270,
280-283, 289-295, 309-314, 331-334, 341-353, 375-378, 390-396, 411-415, 445-446,
475-479, 556-559, 569-587, 609-614, 677-681, 692-695) assertions compare BigInt
results from ethers v6 to JS numbers; change those expectations to use BigInt
literals (e.g., 0n, 100n) or cast the contract return explicitly with
BigInt()/Number() consistently so types match. For each expect(...)
.to.equal(...) replace numeric RHS with BigInt literals or wrap the LHS in
Number(...) if you intend JS numbers, ensuring consistency across all listed
lines, and update any length/element comparisons likewise to compare BigInt to
BigInt or numbers to numbers.
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol (1)
39-55
: Add zero-address validation for _poh and _wNative.
Prevent silent misconfiguration by validating inputs before calling the base initializer.function initialize( address _owner, KlerosCore _core, IProofOfHumanity _poh, address _wNative, uint256 _jumpDisputeKitID ) external reinitializer(1) { + require(address(_poh) != address(0), "DKSR: POH zero"); + require(_wNative != address(0), "DKSR: wNative zero"); __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); poh = _poh; singleDrawPerJuror = true; }Confirmed
reinitializer(1)
does not conflict with any base or sibling initializers.contracts/deploy/00-home-chain-arbitration.ts (1)
142-146
: Fix dependency tag: script depends on RNGWithFallback, not ChainlinkRNGThis deploy retrieves RNGWithFallback (Line 53) and will fail if it hasn’t been deployed yet. Update the dependency tag to ensure ordering.
-deployArbitration.dependencies = ["ChainlinkRNG"]; +deployArbitration.dependencies = ["RNGWithFallback"];
♻️ Duplicate comments (9)
contracts/CHANGELOG.md (3)
30-30
: Clarify pragma widening scope and exact range.Reflect actual range and include RNG contracts if applicable.
- Widen the allowed solc version to any v0.8.x for the interfaces only ([#2083](https://github.com/kleros/kleros-v2/issues/2083)) + Widen pragma to `>=0.8.0 <0.9.0` for interfaces and RNG contracts ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
32-32
: Fix constant name and wording.Use singular and a clearer action verb.
- 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))
158-159
: Remove unpublished 0.13.0 release link to avoid 404s.Keep the anchor commented until the tag is live.
-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected] +<!-- [0.13.0]: (add after publishing) -->Run to confirm availability before uncommenting:
#!/usr/bin/env bash curl -s -o /dev/null -w "%{http_code}\n" \ "https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected]"contracts/src/arbitration/interfaces/IDisputeKit.sol (1)
83-96
: Fix NatSpec: “reward” → “penalty” (still unresolved).The @return text for the penalty accessor incorrectly says “reward”.
-/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. +/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK penalty. function getDegreeOfCoherencePenalty(contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
138-141
: Guard against inactive/unknown disputes in notJumped.Accessing disputes[coreDisputeIDToLocal[_coreDisputeID]] without an active check can read an uninitialized struct and underflow on rounds.length - 1. Add the active guard.
modifier notJumped(uint256 _coreDisputeID) { + if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID(); if (disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped) revert DisputeJumpedToParentDK(); _; }
contracts/src/arbitration/KlerosCoreBase.sol (4)
758-773
: Coherence clamping is good; consider adding reentrancy protection.The penalty execution correctly clamps coherence to ONE_BASIS_POINT and properly calculates penalties. However, the function performs external calls before completing all state updates.
Based on the team's preference for gas optimization (as noted in learnings), the current implementation is acceptable. The comment at line 746 acknowledges the CEI pattern break. Consider adding a
nonReentrant
modifier to the mainexecute()
function if reentrancy becomes a concern.
273-280
: Add event emission and consider reentrancy protection for owner's arbitrary calls.This function allows arbitrary external calls with ETH value. While the owner is trusted, it's good practice to emit an event for auditability and consider reentrancy protection.
+ event OwnerCallExecuted(address indexed destination, uint256 value, bytes data, bool success); function executeOwnerProposal(address _destination, uint256 _amount, bytes memory _data) external onlyByOwner { (bool success, ) = _destination.call{value: _amount}(_data); if (!success) revert UnsuccessfulCall(); + emit OwnerCallExecuted(_destination, _amount, _data, success); }
282-286
: Validate owner address and emit ownership change event.The function should prevent setting a zero address (unless renouncement is intended) and emit an event for transparency.
+ event OwnerChanged(address indexed previousOwner, address indexed newOwner); function changeOwner(address payable _owner) external onlyByOwner { + if (_owner == address(0)) revert InvalidAddress(); + address previousOwner = owner; owner = _owner; + emit OwnerChanged(previousOwner, _owner); }Additionally, define the error:
error InvalidAddress();
288-311
: Add zero address validation for critical setter functions.These setter functions should validate against zero addresses to prevent accidental misconfigurations.
function changeGuardian(address _guardian) external onlyByOwner { + if (_guardian == address(0)) revert InvalidAddress(); guardian = _guardian; } function changePinakion(IERC20 _pinakion) external onlyByOwner { + if (address(_pinakion) == address(0)) revert InvalidAddress(); pinakion = _pinakion; } function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByOwner { + if (_jurorProsecutionModule == address(0)) revert InvalidAddress(); jurorProsecutionModule = _jurorProsecutionModule; } function changeSortitionModule(ISortitionModule _sortitionModule) external onlyByOwner { + if (address(_sortitionModule) == address(0)) revert InvalidAddress(); sortitionModule = _sortitionModule; }Note: When changing the sortition module, ensure all existing courts are properly initialized with the new module through migration scripts.
🧹 Nitpick comments (19)
contracts/CHANGELOG.md (5)
22-22
: Spelling: “bypass” (no hyphen).- Allow stake changes to by-pass delayed stakes when initiated by the SortitionModule by setting the `_noDelay` parameter to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107)) + Allow stake changes to bypass delayed stakes when initiated by the SortitionModule by setting `_noDelay` to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
25-25
: Clarify what switches to timestamps.Blockhash requires block numbers; if the timeout calculation (not the hash lookup) is what moved to timestamps, say so to avoid confusion.
- Use `block.timestamp` rather than `block.number` for `BlockhashRNG` for better reliability on Arbitrum as block production is sporadic depending on network conditions. ([#2054](https://github.com/kleros/kleros-v2/issues/2054)) + Use timestamp-based timeouts instead of block-number-based ones in `BlockhashRNG` to improve reliability on Arbitrum, where block production can be sporadic. ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
43-43
: Grammar/clarity: “advance to the Voting period.”- 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; this preserves the current Shutter auto‑reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))
15-15
: Style + canonical ERC link.Concise phrasing and link to the official EIP page.
- **Breaking:** Rename `governor` to `owner` in order to comply with the lightweight ownership standard [ERC-5313](https://eipsinsight.com/ercs/erc-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112)) + **Breaking:** Rename `governor` to `owner` to comply with [ERC‑5313](https://eips.ethereum.org/EIPS/eip-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112))
31-31
: Confirm whether a separate penalty function/API was added.AI notes suggest splitting coherence into reward/penalty functions. If true, document it explicitly here.
Proposed addendum:
- Add
getDegreeOfCoherencePenalty()
(separate penalty path) and align docs accordingly (#2090)contracts/src/arbitration/interfaces/IDisputeKit.sol (2)
46-55
: Add missing NatSpec for new return value in draw.Document fromSubcourtID to match the expanded signature.
/// @param _nonce Nonce. -/// @return drawnAddress The drawn address. +/// @return drawnAddress The drawn address. +/// @return fromSubcourtID The originating subcourt ID of the drawn juror. function draw(
124-132
: Confirm TODO or remove param.The _previousDisputeKit parameter is marked TODO for removal. Either remove it now (breaking change) or track a follow-up to deprecate it in the next major.
contracts/deploy/00-home-chain-arbitration-neo.ts (1)
39-44
: Nonce-based precompute: add a sanity check.Given the reliance on nonce ordering, assert the deployed KlerosCoreNeo address equals the precomputed klerosCoreAddress after deployment to fail fast if nonces shift.
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (3)
168-175
: Emit event on successful owner call (optional).Add an event to audit privileged calls.
function executeOwnerProposal(address _destination, uint256 _amount, bytes memory _data) external onlyByOwner { (bool success, ) = _destination.call{value: _amount}(_data); if (!success) revert UnsuccessfulCall(); + emit OwnerCallExecuted(_destination, _amount, _data); }
Add this event in the events section:
event OwnerCallExecuted(address indexed destination, uint256 value, bytes data);
177-194
: Ownership/admin setters: add events and align types.
- changeOwner takes address payable but stores address; make the param address for consistency.
- Emit events for changeOwner/changeCore/changeJumpDisputeKitID.
-/// @param _owner The new value for the `owner` storage variable. -function changeOwner(address payable _owner) external onlyByOwner { - owner = _owner; -} +/// @param _owner The new value for the `owner` storage variable. +function changeOwner(address _owner) external onlyByOwner { + owner = _owner; + emit OwnerChanged(_owner); +} - function changeCore(address _core) external onlyByOwner { - core = KlerosCore(_core); -} + function changeCore(address _core) external onlyByOwner { + core = KlerosCore(_core); + emit CoreChanged(_core); +} - function changeJumpDisputeKitID(uint256 _jumpDisputeKitID) external onlyByOwner { - jumpDisputeKitID = _jumpDisputeKitID; -} + function changeJumpDisputeKitID(uint256 _jumpDisputeKitID) external onlyByOwner { + jumpDisputeKitID = _jumpDisputeKitID; + emit JumpDisputeKitIDChanged(_jumpDisputeKitID); +}Add events:
event OwnerChanged(address indexed newOwner); event CoreChanged(address indexed newCore); event JumpDisputeKitIDChanged(uint256 indexed newJumpDisputeKitID);
650-656
: Fix typo and phrasing in NatSpec (“kid” → “kit”).-/// @dev Returns the dispute kid ID be used after court jump by Kleros Core. +/// @dev Returns the dispute kit ID to be used after a court jump by Kleros Core.contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (1)
45-56
: Initializer wiring: OK; consider adding zero-address checks.
Owner/core/wNative/jumpDisputeKitID are forwarded as expected. Add validation to hard-fail on bad params.Apply this diff:
function initialize( address _owner, KlerosCore _core, address _wNative, uint256 _jumpDisputeKitID ) external reinitializer(1) { - __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); + require(_owner != address(0), "DKShutter: owner zero"); + require(address(_core) != address(0), "DKShutter: core zero"); + require(_wNative != address(0), "DKShutter: wNative zero"); + __DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID); }contracts/test/foundry/KlerosCore_Voting.t.sol (3)
14-123
: Commit/reveal happy-path and guards look solid.
Good coverage of period gating, ownership checks, and hash mismatches.
- Consider asserting nbVoters/choiceCount when fetched to avoid unused locals.
161-270
: Direct voting flow: OK; stabilize magic numbers.
Assertions/readability would benefit from named constants for choices (e.g., YES=1, NO=2).Apply this diff snippet at top of the test or within the function:
- vm.prank(staker1); + uint256 constant NO = 2; + vm.prank(staker1); ... - disputeKit.castVote(disputeID, voteIDs, 2, 0, "XYZ"); + disputeKit.castVote(disputeID, voteIDs, NO, 0, "XYZ");
387-486
: Derive newDkID dynamically instead of hard-coding
Replace the hard-coded2
with the length getter, e.g.:- uint256 newDkID = 2; + // Use Core’s getter to avoid relying on fixed ordering + uint256 newDkID = core.getDisputeKitsLength() - 1;Drop the TODO once this is in place. Optionally, assert the proxy’s implementation matches
address(dkLogic)
after initialization to enforce UUPS invariants.contracts/deploy/00-home-chain-arbitration.ts (1)
44-49
: Precomputed KlerosCore address is brittle—add a sanity check after deployIf any extra tx slips in, SortitionModule would be initialized with a wrong core address. Add a post-deploy assert to fail fast.
const core = await hre.ethers.getContract<KlerosCore>("KlerosCore"); + // Sanity check: precomputed vs deployed proxy address must match + if ((klerosCoreAddress ?? "").toLowerCase() !== core.target.toLowerCase()) { + throw new Error( + `KlerosCore address mismatch: precomputed ${klerosCoreAddress} vs deployed ${core.target}` + ); + }contracts/test/foundry/KlerosCore_TestBase.sol (3)
117-124
: Prefer abi.encodeCall over abi.encodeWithSignature for safetyStronger type safety and refactor resilience.
- bytes memory initDataDk = abi.encodeWithSignature( - "initialize(address,address,address,uint256)", - owner, - address(proxyCore), - address(wNative), - DISPUTE_KIT_CLASSIC - ); + bytes memory initDataDk = abi.encodeCall( + DisputeKitClassic.initialize, + (owner, address(proxyCore), address(wNative), DISPUTE_KIT_CLASSIC) + ); - bytes memory initDataSm = abi.encodeWithSignature( - "initialize(address,address,uint256,uint256,address)", - owner, - address(proxyCore), - minStakingTime, - maxDrawingTime, - rng - ); + bytes memory initDataSm = abi.encodeCall( + SortitionModuleMock.initialize, + (owner, address(proxyCore), minStakingTime, maxDrawingTime, address(rng)) + ); - bytes memory initDataRegistry = abi.encodeWithSignature("initialize(address)", owner); + bytes memory initDataRegistry = abi.encodeCall(DisputeTemplateRegistry.initialize, (owner));Also applies to: 128-136, 165-167
112-114
: Initialize BlockHashRNG with a clear zero consumer, then set itMake the intent explicit and avoid passing an uninitialized variable cast to address.
- rngLookahead = 30; - rng = new BlockHashRNG(msg.sender, address(sortitionModule), rngLookahead); + rngLookahead = 30; + rng = new BlockHashRNG(msg.sender, address(0), rngLookahead); ... vm.prank(owner); rng.changeConsumer(address(sortitionModule));Also applies to: 137-141
188-213
: Avoid double external call in _createStandardCourtMicro-opt to store children locally before indexing.
- return uint96(core.getCourtChildren(parent)[core.getCourtChildren(parent).length - 1]); + uint256[] memory children = core.getCourtChildren(parent); + return uint96(children[children.length - 1]);
📜 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.
📒 Files selected for processing (15)
contracts/CHANGELOG.md
(2 hunks)contracts/deploy/00-home-chain-arbitration-neo.ts
(6 hunks)contracts/deploy/00-home-chain-arbitration.ts
(5 hunks)contracts/src/arbitration/KlerosCoreBase.sol
(27 hunks)contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
(2 hunks)contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
(14 hunks)contracts/src/arbitration/dispute-kits/DisputeKitGated.sol
(2 hunks)contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol
(2 hunks)contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol
(2 hunks)contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol
(3 hunks)contracts/src/arbitration/interfaces/IDisputeKit.sol
(4 hunks)contracts/test/foundry/KlerosCore_Appeals.t.sol
(1 hunks)contracts/test/foundry/KlerosCore_Initialization.t.sol
(1 hunks)contracts/test/foundry/KlerosCore_TestBase.sol
(1 hunks)contracts/test/foundry/KlerosCore_Voting.t.sol
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- contracts/test/foundry/KlerosCore_Appeals.t.sol
- contracts/test/foundry/KlerosCore_Initialization.t.sol
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-03T22:48:32.951Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#0
File: :0-0
Timestamp: 2025-09-03T22:48:32.951Z
Learning: In the Kleros v2 codebase, the team prioritizes gas optimization over strict CEI pattern compliance when dealing with trusted contracts. For penalty execution logic, they prefer batching storage writes (`round.pnkPenalties`) rather than updating incrementally after each penalty calculation to save gas costs, as the risk is extremely low between trusted contracts.
Applied to files:
contracts/src/arbitration/KlerosCoreBase.sol
📚 Learning: 2024-11-19T16:09:41.467Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1746
File: contracts/config/courts.v2.mainnet-neo.json:3-17
Timestamp: 2024-11-19T16:09:41.467Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the General Court (ID: 1) can have its `parent` ID set to itself (`"parent": 1`), as there is no parent court with ID 0 currently.
Applied to files:
contracts/src/arbitration/KlerosCoreBase.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/KlerosCoreBase.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 `contracts/config/courts.v2.mainnet-neo.json`, the `minStake` parameter is denominated in PNK, not ETH.
Applied to files:
contracts/deploy/00-home-chain-arbitration-neo.ts
📚 Learning: 2024-12-07T11:39:10.927Z
Learnt from: jaybuidl
PR: kleros/kleros-v2#1778
File: contracts/src/rng/ChainlinkRNG.sol:135-153
Timestamp: 2024-12-07T11:39:10.927Z
Learning: In the `contracts/src/rng/ChainlinkRNG.sol` contract, the `requestRandomness` function is restricted to being called only by the `SortitionModule`, which does not send concurrent requests. Therefore, it's acceptable not to handle multiple random number requests concurrently in this context.
Applied to files:
contracts/deploy/00-home-chain-arbitration-neo.ts
contracts/deploy/00-home-chain-arbitration.ts
🧬 Code graph analysis (2)
contracts/deploy/00-home-chain-arbitration-neo.ts (1)
contracts/deploy/utils/deployUpgradable.ts (1)
deployUpgradable
(37-89)
contracts/deploy/00-home-chain-arbitration.ts (3)
contracts/deploy/utils/deployUpgradable.ts (1)
deployUpgradable
(37-89)contracts/deployments/devnet.viem.ts (1)
klerosCoreAddress
(12320-12322)contracts/deployments/testnet.viem.ts (1)
klerosCoreAddress
(10190-10192)
🪛 LanguageTool
contracts/CHANGELOG.md
[style] ~15-~15: Consider a more concise word here.
Context: ...reaking:** Rename governor
to owner
in order to comply with the lightweight ownership s...
(IN_ORDER_TO_PREMIUM)
⏰ 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). (9)
- GitHub Check: Redirect rules - kleros-v2-university
- GitHub Check: Header rules - kleros-v2-university
- GitHub Check: Pages changed - kleros-v2-university
- 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 (39)
contracts/src/arbitration/interfaces/IDisputeKit.sol (3)
3-3
: Pragma range LGTM.Compatible with implementing contracts using ^0.8.24.
73-82
: Reward coherence API LGTM.Signature and dual return (PNK/fee) are consistent with implementations.
119-123
: earlyCourtJump view added — LGTM.Interface matches implementations returning false by default.
contracts/deploy/00-home-chain-arbitration-neo.ts (4)
48-59
: RNGWithFallback wiring LGTM.Passing rngWithFallback.target into SortitionModuleNeo matches the new consumer model.
96-101
: Consumer update flow LGTM.Idempotent changeConsumer guarded by an equality check.
128-149
: DK wiring and ID derivation LGTM.Constructor args and post-add ID computation align with the new jump semantics.
158-160
: Keep ChainlinkRNG as the dependency. The00-rng-chainlink.ts
script, tagged"ChainlinkRNG"
, deploys both the ChainlinkRNG and RNGWithFallback contracts; there is no script tagged"RNGWithFallback"
.Likely an incorrect or invalid review comment.
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (2)
237-257
: draw path LGTM.Tuple return and 0-address handling align with ISortitionModule and interface.
765-787
: Typed errors LGTM.Clear, specific, and used consistently across code paths.
contracts/src/arbitration/KlerosCoreBase.sol (6)
171-174
: LGTM! Consistent governance terminology update.The modifier rename from
onlyByGovernor
toonlyByOwner
is correctly implemented with the appropriate error check.
176-179
: LGTM! Guardian/owner access modifier properly updated.The combined guardian/owner modifier correctly checks both roles and throws the appropriate renamed error.
827-842
: Coherence split for PNK and fees is correctly implemented.The rewards calculation properly uses separate coherence values for PNK and fees, both clamped to ONE_BASIS_POINT. This provides more granular control over reward distribution.
856-863
: Good implementation of reward staking with fallback mechanism.The code attempts to stake PNK rewards directly through
sortitionModule.setStakeReward()
and falls back to a direct transfer if staking fails. This is a thoughtful design that ensures jurors always receive their rewards.
643-651
: Court jump logic refactoring looks good.The extraction of court and dispute kit jump logic into
_getCourtAndDisputeKitJumps()
improves code organization and maintainability.
1065-1102
: Well-structured helper functions for court jumps.The new
_isCourtJumping()
and_getCourtAndDisputeKitJumps()
functions properly encapsulate the jump logic, making it more testable and maintainable. The fallback to DISPUTE_KIT_CLASSIC when a compatible kit isn't found is a good safety measure.contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (4)
31-31
: Version bump is consistent with release.Version correctly updated from 0.12.0 to 0.13.0.
65-72
: Initializer properly updated for owner-based governance.The initializer correctly accepts
_owner
instead of_governor
and includes the new_jumpDisputeKitID
parameter for court jump functionality.
74-76
: Reinitializer correctly implements jump kit ID update.The reinitializer(10) properly updates the
jumpDisputeKitID
storage variable, enabling the dispute kit jump functionality.
83-86
: Upgrade authorization correctly updated to owner-based control.The
_authorizeUpgrade
function now usesonlyByOwner
modifier, consistent with the governance terminology refactor.contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (4)
30-30
: Version bump is consistent across dispute kits.Version correctly updated to 0.13.0, matching other dispute kit implementations.
46-53
: Initializer properly implements owner governance and jump kit functionality.The function signature and implementation correctly use
_owner
and include_jumpDisputeKitID
for court jump support.
55-57
: Reinitializer correctly updates jump dispute kit ID.The reinitializer(10) properly sets the
jumpDisputeKitID
variable.
64-67
: Access control properly updated for owner-based upgrades.The
_authorizeUpgrade
function correctly uses theonlyByOwner
modifier.contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol (4)
14-14
: Version consistently updated across all dispute kits.Version 0.13.0 aligns with the other dispute kit implementations.
30-37
: Classic dispute kit initializer properly updated.The initializer correctly implements the owner-based governance model and includes the jump dispute kit functionality.
39-41
: Reinitializer implementation is consistent.The reinitializer(10) properly updates the
jumpDisputeKitID
.
48-51
: Upgrade control properly restricted to owner.The
_authorizeUpgrade
function correctly enforces owner-only access.contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol (2)
21-21
: Version bump looks good.
Matches the broader SDK/contract versioning in the PR.
61-63
: Owner-only upgrade gate: OK.
UUPS authorization correctly updated to owner.contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (2)
15-15
: Version bump acknowledged.
Consistent with related DKs.
67-69
: Owner-only upgrade gate: OK.
UUPS authorization correctly restricted to owner.contracts/test/foundry/KlerosCore_Voting.t.sol (4)
125-159
: Timeout checks for commit period: OK.
Reverts and NewPeriod emission expectations align with timing.
272-298
: Vote-period timeout path: OK.
Covers VotePeriodNotPassed and transition to appeal with proper events.
299-327
: Ruling check: OK.
Covers (ruling, tied, overridden) tuple after a batch vote.
329-386
: Quick pass period on single committed vote: OK.
Good assertion that period advances based on committed/cast completeness.contracts/deploy/00-home-chain-arbitration.ts (2)
90-95
: LGTM: consumer wiring uses idempotent changeConsumerChecking rngWithFallback.consumer() before change prevents unnecessary txs and aligns with the new RNGWithFallback API.
53-57
: LGTM: SortitionModule now takes RNGWithFallback targetConstructor args reflect the new RNG integration; min/max timing args remain intact.
contracts/test/foundry/KlerosCore_TestBase.sol (2)
103-106
: Verify PNK initial supply assumptionsTransferring
totalSupply - 2 ether
tomsg.sender
presumes where the initial supply lives. If PNK already mints tomsg.sender
, this is a no-op; if not, it may revert. Confirm PNK’s constructor behavior and adjust (mint/deal) accordingly.
142-156
: LGTM: Core initialization wiring is consistent with new ownership/RNG modelParameters, DK, sortition module, and WNative wiring look coherent for the updated interfaces.
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the penalty. | ||
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. | ||
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. | ||
/// @param _voteID The ID of the vote. | ||
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. | ||
function getDegreeOfCoherencePenalty( | ||
uint256 _coreDisputeID, | ||
uint256 _coreRoundID, | ||
uint256 _voteID, | ||
uint256 /* _feePerJuror */, | ||
uint256 /* _pnkAtStakePerJuror */ | ||
) external view override returns (uint256 pnkCoherence) { | ||
return _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID); | ||
} | ||
|
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.
Fix NatSpec: “reward” → “penalty” in penalty accessor.
-/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward.
+/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK penalty.
function getDegreeOfCoherencePenalty(
📝 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.
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the penalty. | |
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. | |
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. | |
/// @param _voteID The ID of the vote. | |
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. | |
function getDegreeOfCoherencePenalty( | |
uint256 _coreDisputeID, | |
uint256 _coreRoundID, | |
uint256 _voteID, | |
uint256 /* _feePerJuror */, | |
uint256 /* _pnkAtStakePerJuror */ | |
) external view override returns (uint256 pnkCoherence) { | |
return _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID); | |
} | |
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the penalty. | |
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. | |
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. | |
/// @param _voteID The ID of the vote. | |
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK penalty. | |
function getDegreeOfCoherencePenalty( | |
uint256 _coreDisputeID, | |
uint256 _coreRoundID, | |
uint256 _voteID, | |
uint256 /* _feePerJuror */, | |
uint256 /* _pnkAtStakePerJuror */ | |
) external view override returns (uint256 pnkCoherence) { | |
return _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID); | |
} |
🤖 Prompt for AI Agents
In contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol around lines
546 to 560, the NatSpec for getDegreeOfCoherencePenalty incorrectly says "PNK
reward"; update the function comment and @return description to say "PNK
penalty" (or "penalty" instead of "reward") so the documentation matches the
function purpose and name; make no other code changes.
function reinitialize(uint256 _jumpDisputeKitID) external reinitializer(10) { | ||
jumpDisputeKitID = _jumpDisputeKitID; | ||
} |
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.
Critical: reinitialize is publicly callable; lets anyone set jumpDisputeKitID once.
This is an owner-governed parameter; the first arbitrary caller can lock in an unintended DK ID.
Apply this diff to restrict and reuse base logic (emits/validation if present):
-function reinitialize(uint256 _jumpDisputeKitID) external reinitializer(10) {
- jumpDisputeKitID = _jumpDisputeKitID;
-}
+function reinitialize(uint256 _jumpDisputeKitID)
+ external
+ reinitializer(10)
+ onlyByOwner
+{
+ // Prefer the base mutator if available to keep events/validation consistent
+ // e.g., DisputeKitClassicBase.changeJumpDisputeKitID(uint256)
+ changeJumpDisputeKitID(_jumpDisputeKitID);
+}
If changeJumpDisputeKitID is not external, set via an internal helper that mirrors its checks and emits an event.
📝 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 reinitialize(uint256 _jumpDisputeKitID) external reinitializer(10) { | |
jumpDisputeKitID = _jumpDisputeKitID; | |
} | |
function reinitialize(uint256 _jumpDisputeKitID) | |
external | |
reinitializer(10) | |
onlyByOwner | |
{ | |
// Prefer the base mutator if available to keep events/validation consistent | |
// e.g., DisputeKitClassicBase.changeJumpDisputeKitID(uint256) | |
changeJumpDisputeKitID(_jumpDisputeKitID); | |
} |
/// @dev Returns the dispute kid ID be used after court jump by Kleros Core. | ||
/// @return The ID of the dispute kit in Kleros Core disputeKits array. | ||
function getJumpDisputeKitID() external view returns (uint256); | ||
|
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.
Fix typo and phrasing in NatSpec (“kid” → “kit”).
-/// @dev Returns the dispute kid ID be used after court jump by Kleros Core.
+/// @dev Returns the dispute kit ID to be used after a court jump by Kleros Core.
📝 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.
/// @dev Returns the dispute kid ID be used after court jump by Kleros Core. | |
/// @return The ID of the dispute kit in Kleros Core disputeKits array. | |
function getJumpDisputeKitID() external view returns (uint256); | |
/// @dev Returns the dispute kit ID to be used after a court jump by Kleros Core. | |
/// @return The ID of the dispute kit in Kleros Core disputeKits array. | |
function getJumpDisputeKitID() external view returns (uint256); |
🤖 Prompt for AI Agents
In contracts/src/arbitration/interfaces/IDisputeKit.sol around lines 133 to 136,
the NatSpec comment contains a typo and awkward phrasing ("kid" → "kit" and
unclear wording); update the doc comment to read something like: "/// @dev
Returns the dispute kit ID to be used after a court jump by Kleros Core." and
ensure the @return description remains accurate: "/// @return The ID of the
dispute kit in Kleros Core disputeKits array."
import {KlerosCore_TestBase} from "./KlerosCore_TestBase.sol"; | ||
import {KlerosCoreBase} from "../../src/arbitration/KlerosCoreBase.sol"; | ||
import {DisputeKitClassic, DisputeKitClassicBase} from "../../src/arbitration/dispute-kits/DisputeKitClassic.sol"; | ||
import {IDisputeKit} from "../../src/arbitration/interfaces/IDisputeKit.sol"; | ||
import {UUPSProxy} from "../../src/proxy/UUPSProxy.sol"; | ||
import "../../src/libraries/Constants.sol"; | ||
|
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
Verify DisputeKitClassicBase import path.
Base type appears imported from DisputeKitClassic.sol; in other files it lives in DisputeKitClassicBase.sol. Ensure this resolves to the intended symbol.
🏁 Script executed:
#!/bin/bash
# Show where DisputeKitClassicBase is defined and how it's imported across the repo.
rg -nP 'contract\s+DisputeKitClassicBase\b|import\s+\{[^}]*DisputeKitClassicBase' -C2
Length of output: 279793
Fix DisputeKitClassicBase import path
In contracts/test/foundry/KlerosCore_Voting.t.sol, replace the combined import from DisputeKitClassic.sol with separate imports—DisputeKitClassic remains in DisputeKitClassic.sol, but DisputeKitClassicBase must come from DisputeKitClassicBase.sol.
-import {DisputeKitClassic, DisputeKitClassicBase} from "../../src/arbitration/dispute-kits/DisputeKitClassic.sol";
+import {DisputeKitClassic} from "../../src/arbitration/dispute-kits/DisputeKitClassic.sol";
+import {DisputeKitClassicBase} from "../../src/arbitration/dispute-kits/DisputeKitClassicBase.sol";
📝 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.
import {KlerosCore_TestBase} from "./KlerosCore_TestBase.sol"; | |
import {KlerosCoreBase} from "../../src/arbitration/KlerosCoreBase.sol"; | |
import {DisputeKitClassic, DisputeKitClassicBase} from "../../src/arbitration/dispute-kits/DisputeKitClassic.sol"; | |
import {IDisputeKit} from "../../src/arbitration/interfaces/IDisputeKit.sol"; | |
import {UUPSProxy} from "../../src/proxy/UUPSProxy.sol"; | |
import "../../src/libraries/Constants.sol"; | |
import {KlerosCore_TestBase} from "./KlerosCore_TestBase.sol"; | |
import {KlerosCoreBase} from "../../src/arbitration/KlerosCoreBase.sol"; | |
import {DisputeKitClassic} from "../../src/arbitration/dispute-kits/DisputeKitClassic.sol"; | |
import {DisputeKitClassicBase} from "../../src/arbitration/dispute-kits/DisputeKitClassicBase.sol"; | |
import {IDisputeKit} from "../../src/arbitration/interfaces/IDisputeKit.sol"; | |
import {UUPSProxy} from "../../src/proxy/UUPSProxy.sol"; | |
import "../../src/libraries/Constants.sol"; |
🤖 Prompt for AI Agents
In contracts/test/foundry/KlerosCore_Voting.t.sol around lines 4 to 10, the
current combined import pulls DisputeKitClassic and DisputeKitClassicBase from
DisputeKitClassic.sol but DisputeKitClassicBase actually lives in
DisputeKitClassicBase.sol; update the imports so DisputeKitClassic is imported
from ../../src/arbitration/dispute-kits/DisputeKitClassic.sol and
DisputeKitClassicBase is imported from
../../src/arbitration/dispute-kits/DisputeKitClassicBase.sol (keeping other
imports unchanged).
Support for recovery hash in Shutter DK
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: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
contracts/hardhat.config.ts (2)
16-24
: Load dotenv before requiring task scriptsRequire’d task files may read env at import time. Move
dotenv.config()
above therequire("./scripts/...")
lines to ensure env is available.-import * as dotenv from "dotenv"; +import * as dotenv from "dotenv"; +dotenv.config(); @@ -require("./scripts/populatePolicyRegistry"); -require("./scripts/populateCourts"); +require("./scripts/populatePolicyRegistry"); +require("./scripts/populateCourts"); require("./scripts/changeOwner"); @@ -dotenv.config();
302-306
: Watcher path likely wrong (sources are in ./src)
paths.sources
is"./src"
but watcher watches"./contracts"
. It may miss changes.- files: ["./contracts"], + files: ["./src"],contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (1)
139-146
: Bounds-check _voteIDs before indexingcastVoteShutter reads _voteIDs[0] without verifying length, causing an index-out-of-bounds revert instead of the intended EmptyVoteIDs error.
Apply:
function castVoteShutter( uint256 _coreDisputeID, uint256[] calldata _voteIDs, uint256 _choice, uint256 _salt, string memory _justification ) external { + if (_voteIDs.length == 0) revert EmptyVoteIDs(); Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; address juror = dispute.rounds[dispute.rounds.length - 1].votes[_voteIDs[0]].account; ... }
contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (1)
123-139
: Guard against empty vote set incastVoteShutter
.Indexing
_voteIDs[0]
will revert with out-of-bounds; add an explicit check and a dedicated error for clarity.Apply:
function castVoteShutter( @@ - ) external { + ) external { + if (_voteIDs.length == 0) revert EmptyVoteSet(); Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; address juror = dispute.rounds[dispute.rounds.length - 1].votes[_voteIDs[0]].account; @@ callerIsJuror = false; }And add the error (see bottom of file diff below).
♻️ Duplicate comments (6)
contracts/CHANGELOG.md (2)
161-161
: Remove unpublished 0.13.0 release link (still 404).Tag
@kleros/[email protected]
is not present as of 2025-09-04; keep this commented until published. (github.com)Apply:
-[0.13.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%[email protected] +<!-- [0.13.0]: (add after publishing) -->
26-26
: Fix constant name and wording.The constant is
ONE_BASIS_POINT
(singular); “replace” is clearer than “consolidate.”-- 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))contracts/hardhat.config.ts (1)
35-35
: Expose optimizer runs via env; keep default 2000Improves Hardhat/Foundry parity and CI flexibility. This echoes a prior suggestion.
- runs: 2000, + runs: Number(process.env.SOLC_OPTIMIZER_RUNS ?? 2000),contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (2)
547-553
: Fix NatSpec: penalty docstring says “reward” (duplicate of prior review)Update @return description to “PNK penalty”.
-/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. +/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK penalty.
138-141
: Guard notJumped with “active” check (duplicate of prior review)Add NotActiveForCoreDisputeID before checking jumped to fail fast on inactive disputes.
modifier notJumped(uint256 _coreDisputeID) { - if (disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped) revert DisputeJumpedToParentDK(); + if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID(); + if (disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped) revert DisputeJumpedToParentDK(); _; }contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (1)
73-75
: Critical:reinitialize
lacks access control; first caller can setjumpDisputeKitID
.Restrict to owner and reuse the base mutator to keep events/validation consistent. This was flagged previously and remains unresolved.
Apply:
-function reinitialize(uint256 _jumpDisputeKitID) external reinitializer(10) { - jumpDisputeKitID = _jumpDisputeKitID; -} +function reinitialize(uint256 _jumpDisputeKitID) + external + reinitializer(10) + onlyByOwner +{ + changeJumpDisputeKitID(_jumpDisputeKitID); +}
🧹 Nitpick comments (23)
contracts/CHANGELOG.md (6)
7-7
: Use “Unreleased” header format for not-yet-published entries.Align with Common/Keep a Changelog style.
-## [0.13.0] - 2025-08-07 (Not published yet) +## [0.13.0] - Unreleased
15-15
: Tighten phrasing; prefer canonical EIP link.-- **Breaking:** Rename `governor` to `owner` in order to comply with the lightweight ownership standard [ERC-5313](https://eipsinsight.com/ercs/erc-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112)) +- **Breaking:** Rename `governor` to `owner` to comply with lightweight ownership standard [ERC‑5313](https://eips.ethereum.org/EIPS/eip-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112))
29-29
: Clarify pragma widening scope and exact range.Current text says “interfaces only,” but PR scope appears broader. Please confirm and update.
Option A (interfaces only):
- Widen the allowed solc version to any v0.8.x for the interfaces only ([#2083](https://github.com/kleros/kleros-v2/issues/2083)) + Widen interface pragmas to `>=0.8.0 <0.9.0` ([#2083](https://github.com/kleros/kleros-v2/issues/2083))Option B (interfaces + RNG and others):
- Widen the allowed solc version to any v0.8.x for the interfaces only ([#2083](https://github.com/kleros/kleros-v2/issues/2083)) + Widen pragmas to `>=0.8.0 <0.9.0` across interfaces (and RNG contracts) ([#2083](https://github.com/kleros/kleros-v2/issues/2083))
20-21
: Minor style: spell out “third parties.”- Authenticate the calls to the RNGs to prevent 3rd parties from depleting the Chainlink VRF subscription funds ([#2054](https://github.com/kleros/kleros-v2/issues/2054)) + Authenticate the calls to the RNGs to prevent third parties from depleting the Chainlink VRF subscription funds ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
42-42
: Spelling: “bypass” (one word).- Allow stake changes to by-pass delayed stakes when initiated by the SortitionModule by setting the `_noDelay` parameter to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107)) + Allow stake changes to bypass delayed stakes when initiated by the SortitionModule by setting the `_noDelay` parameter to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
46-46
: Grammar: “advance to the Voting period.”- 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 if all commits are cast, as it breaks the current Shutter auto‑reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085))contracts/foundry.toml (2)
5-5
: via_ir = true: good default; keep an escape hatch for comparisons.IR pipeline is generally beneficial, but debugging certain optimizer issues is easier with a no-IR profile.
Proposed profiles:
[profile.default] via_ir = true optimizer = true optimizer_runs = 10000 + +[profile.noir] +via_ir = false +optimizer = true +optimizer_runs = 200Build without IR when needed:
FOUNDRY_PROFILE=noir forge build
.
6-7
: 10k optimizer runs: split profiles to reduce CI time and keep release builds hot.10k can materially slow compiles. Use a fast CI profile and keep 10k for release/gas benchmarking.
[profile.default] solc = "0.8.30" evm_version = "cancun" via_ir = true optimizer = true optimizer_runs = 10000 + +[profile.ci] +optimizer = true +optimizer_runs = 200 +via_ir = true +# Fail on warnings in CI to catch issues early +deny_warnings = true +# Deterministic bytecode for reproducible diffs +bytecode_hash = "none"Use:
FOUNDRY_PROFILE=ci forge build
..github/workflows/contracts-testing.yml (5)
49-54
: Drop the redundantyarn set version
step.
corepack prepare [email protected] --activate
is sufficient;yarn set version
modifies repo files unnecessarily in CI.- name: Set up corepack (for yarn) run: | corepack enable corepack prepare [email protected] --activate - yarn set version 4.9.2
29-44
: Validate allowed egress; avoid hard-coded IPs and add common endpoints.
54.185.253.63:443
is brittle. Consider removing it and, if needed, addapi.github.com:443
andstorage.googleapis.com:443
(node-gyp/prebuilds, solc mirrors).allowed-endpoints: > binaries.soliditylang.org:443 + api.github.com:443 classic.yarnpkg.com:443 github.com:443 nightly.yarnpkg.com:443 nodejs.org:443 objects.githubusercontent.com:443 registry.yarnpkg.com:443 registry.npmjs.org:443 - 54.185.253.63:443 + storage.googleapis.com:443
76-79
: Double-check test invocation context.If you keep PnP, running from the repo root with a workspace target is often cleaner.
- - name: Run Hardhat tests - run: yarn test - working-directory: contracts + - name: Run Hardhat tests + run: yarn workspace @kleros/kleros-v2-contracts test
26-28
: Add job-level concurrency to cancel superseded runs.Saves CI minutes on rapid pushes to the same ref.
hardhat-tests: runs-on: ubuntu-latest + concurrency: + group: hardhat-tests-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true
80-144
: Track Foundry enablement without large commented blocks.Consider a stub job guarded by
if: false
or a separate workflow to keep YAML tidy and make re-enablement a one-line change.Example:
foundry-tests: if: false runs-on: ubuntu-latest steps: - run: echo "Foundry temporarily disabled"contracts/hardhat.config.ts (1)
31-31
: Parameterize EVM version to ease cross-network buildsKeep default “cancun” but allow override via env to unblock networks/tooling that lag.
- evmVersion: "cancun", + evmVersion: process.env.SOLC_EVM_VERSION ?? "cancun",contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (2)
168-181
: Consistent encoding for hashes (nit)You mix abi.encode (full commit) and abi.encodePacked (recovery). For fixed-size (uint256,uint256) both are 64 bytes today, but using a single scheme improves auditability and reduces ambiguity.
Consider:
- return keccak256(abi.encodePacked(_choice, _salt)); + return keccak256(abi.encode(_choice, _salt));
215-229
: Assembly offsets look correct; add brief invariant commentThe 160-byte length guard matches loads at offsets 128 and 160. Add a short note that layout comes from encode(courtID,minJurors,disputeKitID,packedTokenGateAndFlag,tokenId).
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)
177-181
: Validate non-zero owner on transfer (optional)Block accidental renouncement-by-zero here; if zero-owner is desired, make it explicit via a separate function.
function changeOwner(address payable _owner) external onlyByOwner { - owner = _owner; + if (_owner == address(0)) revert UnsuccessfulCall(); // or a new ZeroAddress() error + owner = _owner; }contracts/test/arbitration/dispute-kit-shutter.ts (3)
160-178
: Read commit period from core instead of hard-coding 300sAvoid drift with deployments that tweak timesPerPeriod; fetch from the contract.
- const timesPerPeriod = [300, 300, 300, 300]; - const commitPeriod = timesPerPeriod[Period.commit]; + const court = await core.courts(courtId); + const commitPeriod = Number(court.timesPerPeriod[Period.commit] ?? 300);
138-149
: Avoid assuming disputeId = 0Derive from events or a counter to be robust when tests run with other suites.
- await core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait()); - const disputeId = 0; + const tx = await core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }); + const rc = await tx.wait(); + const disputeId = Number(rc!.logs.find((l) => l.fragment?.name === "DisputeCreation")!.args![0]);
180-203
: Iterating votes via getVoteInfo: consider bounded iterationUsing try/catch sentinel is fine; optionally read votes.length via getRoundInfo to bound the loop and speed up failures.
- const maxVotes = 10; // Reasonable limit for testing + const [, , , , nbVoters] = await disputeKitShutter.getRoundInfo(disputeId, roundIndex, 0); + const maxVotes = Number(nbVoters);contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (3)
101-121
: Minor API hygiene and CEI ordering incastCommitShutter
.
- Add an explicit non-empty
_voteIDs
guard for clearer errors.- Consider moving
recoveryCommitments
writes after_castCommit
to minimize wasted writes on revert and follow checks-effects-interactions.Apply:
function castCommitShutter( @@ - ) external notJumped(_coreDisputeID) { - if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit(); - - uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID]; - Dispute storage dispute = disputes[localDisputeID]; - uint256 localRoundID = dispute.rounds.length - 1; - for (uint256 i = 0; i < _voteIDs.length; i++) { - recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit; - } - - // `_castCommit()` ensures that the caller owns the vote - _castCommit(_coreDisputeID, _voteIDs, _commit); + ) external notJumped(_coreDisputeID) { + if (_voteIDs.length == 0) revert EmptyVoteSet(); + if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit(); + + // `_castCommit()` ensures that the caller owns the vote and period is valid + _castCommit(_coreDisputeID, _voteIDs, _commit); + + uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID]; + Dispute storage dispute = disputes[localDisputeID]; + uint256 localRoundID = dispute.rounds.length - 1; + for (uint256 i = 0; i < _voteIDs.length; i++) { + recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit; + }
192-193
: Complementary error for empty vote sets.Define an explicit error for the new guards.
Apply:
- error EmptyRecoveryCommit(); + error EmptyRecoveryCommit(); + error EmptyVoteSet();
28-28
: DecouplecallerIsJuror
flag from external calls
In castVoteShutter (DisputeKitShutter.sol 133–138) you set a contract-widecallerIsJuror
for the entire_castVote
call, yet_castVote
invokes external functions (core.disputes
andcore.courts
in DisputeKitClassicBase.sol 309–316). If the core contract ever reenters, it could observe unintended juror-path branching in hashVote.
• Compute juror context on the fly instead: passmsg.sender
intohashVote
/_getExpectedVoteHash
rather than relying on a transient storage flag.
📜 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.
📒 Files selected for processing (8)
.github/workflows/contracts-testing.yml
(1 hunks)contracts/CHANGELOG.md
(2 hunks)contracts/foundry.toml
(1 hunks)contracts/hardhat.config.ts
(2 hunks)contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
(15 hunks)contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol
(7 hunks)contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol
(7 hunks)contracts/test/arbitration/dispute-kit-shutter.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
contracts/test/arbitration/dispute-kit-shutter.ts (1)
contracts/deploy/utils/index.ts (1)
PNK
(40-40)
🪛 LanguageTool
contracts/CHANGELOG.md
[style] ~15-~15: Consider a more concise word here.
Context: ...reaking:** Rename governor
to owner
in order to comply with the lightweight ownership s...
(IN_ORDER_TO_PREMIUM)
⏰ 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). (8)
- GitHub Check: Dependabot
- GitHub Check: hardhat-tests
- GitHub Check: SonarCloud
- GitHub Check: Analyze (javascript)
- GitHub Check: Mend Security Check
- GitHub Check: SonarCloud
- GitHub Check: Analyze (javascript)
- GitHub Check: hardhat-tests
🔇 Additional comments (22)
contracts/CHANGELOG.md (1)
162-162
: Confirm 0.12.0 link is live (it is).No change needed; just confirming this anchor works. (github.com)
contracts/foundry.toml (1)
3-4
: Pragmas allow 0.8.30; manual Cancun build verification required
- All detected
pragma solidity
constraints (^0.8.24
,^0.8.28
,>=0.8.0 <0.9.0
,^0.8.0
) accept compiler version 0.8.30.- CI lacks a functioning
forge
; please run locally
forge build -vv --compiler-version 0.8.30 --evm-version cancun
and validate deployments on mainnet, Gnosis, Arbitrum, Sepolia..github/workflows/contracts-testing.yml (1)
45-48
: Good pinning and submodules.Pinned SHAs for actions and recursive submodules look solid.
contracts/hardhat.config.ts (1)
32-32
: viaIR defaulting via env looks goodSane default with easy opt-out for coverage/perf.
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (3)
37-39
: Good: Recovery-commit storage added with clear indexingPer-dispute/round/vote mapping is appropriate and the public getter simplifies tests and tooling.
75-87
: Initializer as reinitializer(1): confirm upgrade pathSwitching to reinitializer(1) is fine for upgrades but will brick fresh proxies if an initializer(1) was already used differently. Confirm no proxy instances have consumed version 1 for this DK in production.
231-253
: Good: gating check is minimal and gas-efficientERC-1155 vs ERC-20/721 balance checks are clean and read-only; default “no gate” path returns early.
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (5)
235-249
: LGTM: draw() return surface updatedReturning (drawnAddress, fromSubcourtID) aligns with ISortitionModule.draw; early-return on zero address avoids extra work.
275-287
: LGTM: commit flow hardens period/active/ownership checksClear revert reasons and batched updates per vote. Event emission preserved.
321-337
: LGTM: vote flow with explicit hidden-votes validationThe HashDoesNotMatchHiddenVoteCommitment and JurorHasToOwnTheVote checks are ordered sensibly; stack-too-deep workaround is clean.
651-657
: LGTM: jump DK fallbackGraceful fallback to DISPUTE_KIT_CLASSIC when unset is sensible.
742-754
: LGTM: default expected-hash accessorVirtual helper cleanly isolates commit source; enables Shutter overrides.
contracts/test/arbitration/dispute-kit-shutter.ts (3)
36-74
: Deterministic RNG + dedicated hidden-vote court: solid setupFixture plus IncrementalNG and explicit court params create a stable testbed.
324-349
: Nice: event assertions and state checks cover the happy pathCovers CommitCastShutter and VoteCast plus getVoteInfo state.
574-598
: Attack-path test is valuableValidates that non-jurors cannot exploit recovery path.
contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol (7)
15-16
: Version constant update looks good.
21-23
: Public recoveryCommitments mapping: OK.Clear separation by (localDisputeID, localRoundID, voteID) makes reads straightforward.
81-85
: UUPS upgrade gate to owner: OK.
176-186
: Expected-hash selection by context looks correct.
3-3
: All target networks support EIP-1153 (transient storage)
Ethereum mainnet (Dencun/Cancun), Arbitrum One (ArbOS v20 “Atlas”), Optimism (Ecotone), Base (Ecotone), and Gnosis Chain (Pectra) all include EIP-1153 as of September 4, 2025.
60-71
: reinitializer(1) safe to use: DisputeKitClassicBase does not declare any external initializer or reinitializer, so version 1 remains unconsumed and this call will succeed.
156-165
: Hashing scheme alignment: verify off-chain commit generators match encodings.Juror-path uses
abi.encodePacked(_choice, _salt)
while non-juror usesabi.encode(_choice, _salt, keccak256(bytes(_justification)))
. Confirm Shutter/off-chain tooling generates_commit
and_recoveryCommit
with these exact encodings to avoid silent mismatches.
permissions: # added using https://github.com/step-security/secure-workflows | ||
contents: read | ||
|
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
Grant actions: read
so the cache can restore.
When you set a restrictive permissions
block, unspecified scopes default to none
. actions/cache
requires actions: read
for restore.
-permissions: # added using https://github.com/step-security/secure-workflows
- contents: read
+permissions: # added using https://github.com/step-security/secure-workflows
+ contents: read
+ actions: read
📝 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.
permissions: # added using https://github.com/step-security/secure-workflows | |
contents: read | |
permissions: # added using https://github.com/step-security/secure-workflows | |
contents: read | |
actions: read |
🤖 Prompt for AI Agents
.github/workflows/contracts-testing.yml around lines 19 to 21: the workflow’s
permissions block only sets contents: read but omits actions: read which
prevents actions/cache from restoring; update the permissions block to
explicitly include actions: read (e.g., add "actions: read" alongside contents:
read) so the cache restore step can access the actions permission.
- name: Cache node modules | ||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1 | ||
env: | ||
cache-name: cache-node-modules | ||
with: | ||
path: | | ||
~/.npm | ||
**/node_modules | ||
key: ${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }} | ||
restore-keys: | | ||
${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}- | ||
|
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
Cache the Yarn Berry artifacts instead of npm/node_modules.
With Yarn 4 (PnP by default), ~/.npm
and **/node_modules
are often unused. Cache .yarn/cache
and install state for real wins.
- - name: Cache node modules
+ - name: Cache Yarn artifacts
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
- env:
- cache-name: cache-node-modules
with:
- path: |
- ~/.npm
- **/node_modules
- key: ${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
- restore-keys: |
- ${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}-
+ path: |
+ .yarn/cache
+ .yarn/install-state.gz
+ key: ${{ runner.os }}-yarn-${{ secrets.CACHE_VERSION }}-${{ hashFiles('**/yarn.lock', '.yarnrc.yml') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-${{ secrets.CACHE_VERSION }}-
📝 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.
- name: Cache node modules | |
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1 | |
env: | |
cache-name: cache-node-modules | |
with: | |
path: | | |
~/.npm | |
**/node_modules | |
key: ${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }} | |
restore-keys: | | |
${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}- | |
- name: Cache Yarn artifacts | |
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1 | |
with: | |
path: | | |
.yarn/cache | |
.yarn/install-state.gz | |
key: ${{ runner.os }}-yarn-${{ secrets.CACHE_VERSION }}-${{ hashFiles('**/yarn.lock', '.yarnrc.yml') }} | |
restore-keys: | | |
${{ runner.os }}-yarn-${{ secrets.CACHE_VERSION }}- |
- name: Install contracts dependencies | ||
run: yarn workspace @kleros/kleros-v2-contracts install | ||
|
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.
Fix install step: Yarn v4 doesn’t support yarn workspace <name> install
.
This will either no-op or fail unless the workspace defines an install
script. Do a root install (Berry) and optionally focus the contracts workspace.
- - name: Install contracts dependencies
- run: yarn workspace @kleros/kleros-v2-contracts install
+ - name: Install dependencies
+ run: |
+ yarn install --immutable
+ yarn workspaces focus @kleros/kleros-v2-contracts
📝 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.
- name: Install contracts dependencies | |
run: yarn workspace @kleros/kleros-v2-contracts install | |
- name: Install dependencies | |
run: | | |
yarn install --immutable | |
yarn workspaces focus @kleros/kleros-v2-contracts |
🤖 Prompt for AI Agents
In .github/workflows/contracts-testing.yml around lines 73-75, the workflow uses
"yarn workspace @kleros/kleros-v2-contracts install", which fails under Yarn v4;
replace that step with a root install (e.g., "yarn install" or "yarn install
--immutable" for CI) and, if you want to limit deps to the contracts workspace,
follow with a workspace focus command such as "yarn workspaces focus
@kleros/kleros-v2-contracts --all" (or similar focus flags) so dependencies are
prepared for that specific workspace.
@@ -26,28 +26,13 @@ const config: HardhatUserConfig = { | |||
solidity: { | |||
compilers: [ | |||
{ | |||
version: "0.8.28", | |||
version: "0.8.30", |
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
Solc 0.8.30: align across toolchains
Ensure Foundry uses the same solc_version
to avoid bytecode/gas drift.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Hardhat =="
rg -n 'version:\s*"0\.8\.\d+"' contracts/hardhat.config.ts -n
echo
echo "== Foundry configs =="
fd -a "foundry.toml" | while read -r f; do
echo ">>> $f"
rg -n 'solc_version|evm_version|optimizer_runs|via_ir' "$f" || true
done
Length of output: 522
Specify solc_version in Foundry config
Add solc_version = "0.8.30"
to contracts/foundry.toml
to match Hardhat’s compiler version and prevent bytecode/gas drift.
🤖 Prompt for AI Agents
In contracts/foundry.toml (add a new or update existing [profile] section), set
solc_version = "0.8.30" to match the Hardhat compiler version declared in
contracts/hardhat.config.ts at line 29; update or insert the solc_version key
under the appropriate profile or the global config so Foundry uses 0.8.30 and
prevents bytecode/gas drift between toolchains.
// * Transient Storage * // | ||
// ************************************* // | ||
|
||
bool transient callerIsJuror; |
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
Avoid transient “callerIsJuror” across external calls; pass explicit context instead
Using a transient flag that influences hashVote/_getExpectedVoteHash across external calls (core.disputes/courts) is fragile and reentrancy-prone. Prefer plumbing explicit context to hashing/expected-hash logic to avoid cross-call leakage.
Apply this refactor:
- bool transient callerIsJuror;
+// Remove transient state; compute juror context per-call.
- function castVoteShutter(...) external {
+ function castVoteShutter(...) external {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
address juror = dispute.rounds[dispute.rounds.length - 1].votes[_voteIDs[0]].account;
-
- callerIsJuror = juror == msg.sender;
- _castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
- callerIsJuror = false;
+ bool isJuror = (juror == msg.sender);
+ _castVoteWithContext(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror, isJuror);
}
- function hashVote(uint256 _choice, uint256 _salt, string memory _justification)
- public view override returns (bytes32) {
- if (callerIsJuror) { return keccak256(abi.encodePacked(_choice, _salt)); }
- ...
- }
+function _hashVoteWithContext(
+ uint256 _choice, uint256 _salt, string memory _justification, bool isJuror
+) internal pure returns (bytes32) {
+ if (isJuror) return keccak256(abi.encodePacked(_choice, _salt));
+ bytes32 justificationHash = keccak256(bytes(_justification));
+ return keccak256(abi.encode(_choice, _salt, justificationHash));
+}
+
+function hashVote(uint256 _choice, uint256 _salt, string memory _justification)
+ public view override returns (bytes32) {
+ // Public helper (no juror context): default to non-juror hashing.
+ bytes32 justificationHash = keccak256(bytes(_justification));
+ return keccak256(abi.encode(_choice, _salt, justificationHash));
+}
- function _getExpectedVoteHash(...) internal view override returns (bytes32) {
- if (callerIsJuror) { return recoveryCommitments[_localDisputeID][_localRoundID][_voteID]; }
- return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
- }
+function _getExpectedVoteHashWithContext(
+ uint256 _localDisputeID, uint256 _localRoundID, uint256 _voteID, bool isJuror
+) internal view returns (bytes32) {
+ return isJuror
+ ? recoveryCommitments[_localDisputeID][_localRoundID][_voteID]
+ : disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
+}
+function _castVoteWithContext(
+ uint256 _coreDisputeID,
+ uint256[] calldata _voteIDs,
+ uint256 _choice,
+ uint256 _salt,
+ string memory _justification,
+ address _juror,
+ bool isJuror
+) internal {
+ // copy body of _castVote but replace:
+ // bytes32 actualVoteHash = hashVote(...);
+ // with:
+ // bytes32 actualVoteHash = _hashVoteWithContext(_choice, _salt, _justification, isJuror);
+ // and _getExpectedVoteHash(...) with _getExpectedVoteHashWithContext(..., isJuror)
+}
If you prefer a smaller diff, at minimum avoid setting the transient flag before external calls and compute the juror context only around hashing/expected-hash checks.
Also applies to: 149-155, 168-181, 193-202
🤖 Prompt for AI Agents
In contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol around line
44 (and also review usages at lines 149-155, 168-181, 193-202), the transient
bool callerIsJuror is set before making external calls which can leak or be
observed across reentrant/external boundaries; instead remove the transient flag
usage across external calls and pass an explicit juror-context parameter into
hashVote and _getExpectedVoteHash (or compute the juror-context locally
immediately before calling those functions). Concretely: stop setting
callerIsJuror prior to any external call, add a jurorContext parameter (or local
variable) that you compute from msg.sender/role right where
hashing/expected-hash is needed, and update hashVote/_getExpectedVoteHash
signatures and all internal call sites to accept the explicit context; if you
opt for a minimal change, ensure callerIsJuror is only assigned and used within
the same non-external-call scope (compute and consume it immediately around the
hashing checks) so it never persists across external calls.
|
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: 1
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/SortitionModuleNeo.sol (1)
95-103
: State drift risk: totalStaked updates occur before super; move after and gate on actual changeIf super._validateStake later returns a non-success result or defers the change (e.g., when _noDelay is false outside staking), totalStaked is still mutated, desynchronizing module state. Update totalStaked only after super and only if the stake actually changed now.
Apply:
- if (phase == Phase.staking) { - if (stakeIncrease) { - totalStaked += stakeChange; - } else { - totalStaked -= stakeChange; - } - } - (pnkDeposit, pnkWithdrawal, stakingResult) = super._validateStake(_account, _courtID, _newStake, _noDelay); + (pnkDeposit, pnkWithdrawal, stakingResult) = super._validateStake(_account, _courtID, _newStake, _noDelay); + // Reflect only immediate, realized stake changes. + uint256 newStakeAfter = stakeOf(_account, _courtID); + if (newStakeAfter != currentStake && (phase == Phase.staking || _noDelay)) { + if (newStakeAfter > currentStake) { + totalStaked += (newStakeAfter - currentStake); + } else { + totalStaked -= (currentStake - newStakeAfter); + } + }
🧹 Nitpick comments (4)
contracts/src/arbitration/SortitionModuleNeo.sol (4)
5-5
: Avoid transitive import of IRNG to reduce couplingPrefer importing IRNG directly from its own interface file instead of re-exporting through SortitionModuleBase to prevent fragile compile-order or circular-dependency issues.
38-47
: Reinitializer bump and IRNG migration—double-check upgrade path and null checks
- Confirm the reinitializer(2) version is compatible with existing proxy history for this contract. Ensure no previously-used version equals 2 (otherwise initialize would be blocked). The presence of initialize4() suggests version bumps elsewhere—please verify the intended call graph.
- Ensure _rng cannot be the zero address (if the base initializer doesn’t already validate it).
65-71
: Emit events for governance changes (+ optional sanity checks)
- Emit events when maxStakePerJuror or maxTotalStaked change to aid observability and indexing.
- Optionally guard against setting values that would make staking impossible (e.g., zero) unless that’s intentional.
Apply this diff and add event declarations:
function changeMaxStakePerJuror(uint256 _maxStakePerJuror) external onlyByOwner { - maxStakePerJuror = _maxStakePerJuror; + uint256 old = maxStakePerJuror; + maxStakePerJuror = _maxStakePerJuror; + emit MaxStakePerJurorChanged(old, _maxStakePerJuror); } function changeMaxTotalStaked(uint256 _maxTotalStaked) external onlyByOwner { - maxTotalStaked = _maxTotalStaked; + uint256 old = maxTotalStaked; + maxTotalStaked = _maxTotalStaked; + emit MaxTotalStakedChanged(old, _maxTotalStaked); }Add inside the contract (near storage):
event MaxStakePerJurorChanged(uint256 previous, uint256 current); event MaxTotalStakedChanged(uint256 previous, uint256 current);
3-3
: Pragma range differs from PR objectivePR notes broadening to >=0.8.0 <0.9.0. This file still pins ^0.8.24. If intentional (e.g., due to base/library constraints), ignore; otherwise, align with the repo-wide pragma or rely on tooling config.
📜 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.
📒 Files selected for processing (2)
contracts/src/arbitration/SortitionModuleNeo.sol
(5 hunks)contracts/src/libraries/Constants.sol
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- contracts/src/libraries/Constants.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). (7)
- GitHub Check: Analyze (javascript)
- GitHub Check: SonarCloud
- GitHub Check: hardhat-tests
- GitHub Check: Scorecards analysis
- GitHub Check: Analyze (javascript)
- GitHub Check: SonarCloud
- GitHub Check: hardhat-tests
🔇 Additional comments (3)
contracts/src/arbitration/SortitionModuleNeo.sol (3)
30-30
: Docs: owner rename is correctParam rename in the initializer docs matches the ownership refactor.
60-62
: UUPS authorization guarded by onlyByOwner—LGTM
80-82
: All_validateStake
call sites updated with the new_noDelay
parameter — Verified that SortitionModuleBase.sol (line 237) and SortitionModuleNeo.sol (line 102) both pass the boolean flag, matching the updated signature.
if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror) { | ||
return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror); | ||
} |
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.
Bug: per-court limit mistakenly uses total juror stake
The check uses juror.stakedPnk (total across courts) against a per-court cap, which can incorrectly reject valid stakes in multi-court scenarios. Compare the desired per-court stake (_newStake) to maxStakePerJuror instead.
Apply:
- if (juror.stakedPnk + stakeChange > maxStakePerJuror || currentStake + stakeChange > maxStakePerJuror) {
+ if (_newStake > maxStakePerJuror) {
return (0, 0, StakingResult.CannotStakeMoreThanMaxStakePerJuror);
}
PR-Codex overview
This PR focuses on refactoring the governance structure in multiple smart contracts, changing references from
governor
toowner
, and updating the Solidity pragma version. It also introduces a newIRNG
interface and modifies various contract initializers and methods accordingly.Detailed summary
governor
toowner
in multiple contracts.^0.8.24
to>=0.8.0 <0.9.0
.IRNG
interface for random number generation.owner
instead ofgovernor
.Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Dependency & Tooling
Tests