Skip to content

Conversation

@jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Dec 19, 2025

PR-Codex overview

This PR primarily focuses on enhancing the functionality and robustness of the arbitration system within the Kleros protocol. It introduces new features, refines existing functions, and adds error handling to improve overall stability and usability.

Detailed summary

  • Added virtual modifier to rule function in ArbitrableExample.sol.
  • Updated dispute resolution checks in DisputeKitClassicBase.sol.
  • Enhanced error handling in KlerosCore.sol for token transfers.
  • Introduced SafeTransferFailed event in SafeERC20.sol.
  • Modified stake handling in SortitionModule.sol.
  • Created MaliciousArbitrableMock for testing rule reverts.
  • Added new tests in KlerosCore_Execution.t.sol for various scenarios, including handling failed transfers and malicious behavior.
  • Implemented internal helper functions to streamline testing and improve code readability.

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

Summary by CodeRabbit

  • Bug Fixes

    • Detect and handle token transfer failures (emits a failure event) and hardened execution flows.
    • Corrected total-staked accounting so deposits, withdrawals, and leftover withdrawals update totals consistently.
    • Refined dispute resolution checks to rely on execution-period status.
  • Tests

    • Added coverage for transfer-failure paths, malicious arbitrable reverts, and total-staked scenarios with new test helpers and assertions.
  • New Contracts (Tests)

    • Added a mock contract to simulate arbitrable reverts for testing.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link

netlify bot commented Dec 19, 2025

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

Name Link
🔨 Latest commit 104d89b
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/6945bba52e36730008ece6a5

@netlify
Copy link

netlify bot commented Dec 19, 2025

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

Name Link
🔨 Latest commit 104d89b
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet-devtools/deploys/6945bba581958c000767a43e

@netlify
Copy link

netlify bot commented Dec 19, 2025

Deploy Preview for kleros-v2-neo ready!

Name Link
🔨 Latest commit 104d89b
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/6945bba5246e1400084dbd0f
😎 Deploy Preview https://deploy-preview-2209--kleros-v2-neo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

Refactors stake bookkeeping to update totalStaked during deposit/withdraw application, adds explicit safeTransfer failure handling and event emission, switches dispute resolution checks to Period.execution, makes ArbitrableExample.rule() virtual, adds a MaliciousArbitrableMock, and expands related tests.

Changes

Cohort / File(s) Summary
Core transfer & staking
contracts/src/arbitration/KlerosCore.sol, contracts/src/arbitration/SortitionModule.sol
KlerosCore.transferBySortitionModule now uses whenNotPaused and checks pinakion.safeTransfer return, reverting on failure. SortitionModule moves totalStaked updates out of validation and into _setStake deposit/withdraw branches and withdrawLeftoverPNK.
Library: ERC20 safety
contracts/src/libraries/SafeERC20.sol
Adds event SafeTransferFailed(IERC20 _token, address _to, uint256 _value); safeTransfer computes/returns explicit ok boolean, emits the failure event when ok is false, and returns ok.
Dispute & arbitrable behavior
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol, contracts/src/arbitration/arbitrables/ArbitrableExample.sol
Replaces isRuled boolean check with KlerosCore.Period.execution comparison for resolution validation. Marks ArbitrableExample.rule() as external virtual override.
Mocks & tests
contracts/src/test/MaliciousArbitrableMock.sol, contracts/test/foundry/KlerosCore_Execution.t.sol, contracts/test/foundry/KlerosCore_Staking.t.sol
Adds MaliciousArbitrableMock to simulate reverting rule(). Expands execution tests to cover transfer failures and arbitrable reverts; adds staking tests asserting totalStaked (duplicate test_setStake_totalStaked() present).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • SortitionModule._setStake and all stake-change paths to ensure totalStaked is updated exactly once and edge cases (removals, leftover withdrawals) handled.
    • KlerosCore.transferBySortitionModule usage of SafeERC20.safeTransfer return value and revert semantics.
    • SafeERC20.safeTransfer event emission implications for integrations and tests.
    • DisputeKitClassicBase.withdrawFeesAndRewards change to Period.execution to ensure lifecycle assumptions hold.
    • Duplicate test test_setStake_totalStaked() in KlerosCore_Staking.t.sol.

Possibly related PRs

Suggested reviewers

  • unknownunknown1

Poem

🐰 I hopped through code with careful paws,
Tightened transfers and fixed some claws,
I nudged the stakes where numbers grow,
Watched disputes move through ebb and flow,
Now tests and carrots cheer me as I doze 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Certora Audit Remediations' directly aligns with the PR's main objective of addressing audit findings from Certora across multiple contracts and test files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/certora-audit

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
contracts/test/foundry/KlerosCore_Execution.t.sol (1)

870-900: Clarify test objective for inflated totalStaked scenario.

The test test_inflatedTotalStaked_whenDelayedStakeExecute_whenJurorHasNoFunds appears to validate behavior when a juror stakes more than their PNK balance. However, the test objective and expected outcome are not entirely clear. Please add a comment explaining:

  1. The specific edge case being tested
  2. Whether this is a regression test for a previously identified issue
  3. The expected post-condition (is the inflated totalStaked the correct behavior, or should it be prevented?)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f196723 and a016740.

📒 Files selected for processing (8)
  • contracts/src/arbitration/KlerosCore.sol (1 hunks)
  • contracts/src/arbitration/SortitionModule.sol (2 hunks)
  • contracts/src/arbitration/arbitrables/ArbitrableExample.sol (1 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1 hunks)
  • contracts/src/libraries/SafeERC20.sol (2 hunks)
  • contracts/src/test/MaliciousArbitrableMock.sol (1 hunks)
  • contracts/test/foundry/KlerosCore_Execution.t.sol (10 hunks)
  • contracts/test/foundry/KlerosCore_Staking.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2107
File: contracts/src/arbitration/university/KlerosCoreUniversity.sol:1083-1092
Timestamp: 2025-09-03T19:34:58.056Z
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.
📚 Learning: 2025-09-03T19:34:58.056Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2107
File: contracts/src/arbitration/university/KlerosCoreUniversity.sol:1083-1092
Timestamp: 2025-09-03T19:34:58.056Z
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/SortitionModule.sol
📚 Learning: 2024-11-19T16:31:08.965Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 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/SortitionModule.sol
📚 Learning: 2025-09-04T23:36:16.415Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2126
File: contracts/src/arbitration/KlerosCore.sol:472-489
Timestamp: 2025-09-04T23:36:16.415Z
Learning: In this repo, KlerosCore emits AcceptedFeeToken and NewCurrencyRate events that are declared in contracts/src/arbitration/interfaces/IArbitratorV2.sol; implementations don’t need to redeclare these events.

Applied to files:

  • contracts/test/foundry/KlerosCore_Execution.t.sol
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
📚 Learning: 2024-11-19T05:31:48.701Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 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/foundry/KlerosCore_Execution.t.sol
📚 Learning: 2025-09-30T17:18:12.895Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2145
File: contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol:277-286
Timestamp: 2025-09-30T17:18:12.895Z
Learning: In DisputeKitClassicBase.sol's castCommit function, jurors are allowed to re-submit commits during the commit period. The implementation uses a commitCount variable to track only first-time commits (where commit == bytes32(0)) so that totalCommitted is not incremented when a juror updates their existing commit.

Applied to files:

  • contracts/test/foundry/KlerosCore_Execution.t.sol
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
📚 Learning: 2024-10-07T06:18:23.427Z
Learnt from: kemuru
Repo: kleros/kleros-v2 PR: 1702
File: web/src/pages/Home/TopJurors/JurorCard/index.tsx:10-11
Timestamp: 2024-10-07T06:18:23.427Z
Learning: In the `kleros-v2` codebase, the property `totalResolvedDisputes` should remain and should not be renamed to `totalResolvedVotes`.

Applied to files:

  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.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). (5)
  • GitHub Check: hardhat-tests
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
🔇 Additional comments (11)
contracts/src/arbitration/SortitionModule.sol (2)

385-406: LGTM: totalStaked tracking now explicit in deposit/withdrawal flows.

The refactoring correctly moves totalStaked updates from the validation phase to the application phase within _setStake. This ensures totalStaked reflects actual token movements rather than validated intentions.


463-463: The order of totalStaked decrement in withdrawLeftoverPNK is before the transfer, not after.

The totalStaked -= amount; statement at line 463 executes before core.transferBySortitionModule(_account, amount); at line 464. The test in KlerosCore_Execution.t.sol (lines 488 and 507) properly validates that totalStaked is correctly updated before and after the withdrawal operation.

Likely an incorrect or invalid review comment.

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

627-631: LGTM: Explicit transfer failure handling added.

The explicit return value check for pinakion.safeTransfer strengthens failure handling by immediately reverting with TransferFailed() when the transfer fails. This complements the SafeERC20 update that now returns a boolean and emits SafeTransferFailed.

contracts/src/arbitration/arbitrables/ArbitrableExample.sol (1)

152-152: LGTM: Marking rule() as virtual enables proper test mock extension.

The addition of the virtual modifier allows derived contracts (like the newly introduced MaliciousArbitrableMock) to override the rule() function for testing purposes without changing the base implementation.

contracts/src/libraries/SafeERC20.sol (1)

18-42: LGTM: SafeTransferFailed event and return value improve transfer failure handling.

The addition of the SafeTransferFailed event and the boolean return value from safeTransfer enables explicit failure handling and observability. This allows callers like KlerosCore.transferBySortitionModule to check the result and revert with context-specific errors.

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

478-479: LGTM: Resolution check now uses Period.execution instead of isRuled flag.

The change validates dispute resolution by checking the period state (Period.execution) rather than the isRuled flag. This ensures withdrawals are aligned with the execution phase and provides a more direct validation of the dispute state.

contracts/src/test/MaliciousArbitrableMock.sol (1)

1-52: LGTM: Test mock enables arbitrable revert scenario testing.

The MaliciousArbitrableMock contract properly extends ArbitrableExample and provides a controllable revert mechanism via the doRevert flag. This enables testing of edge cases where the arbitrable's rule() function reverts, which is valuable for validating that withdrawal paths remain functional even when ruling execution fails.

contracts/test/foundry/KlerosCore_Execution.t.sol (4)

5-12: LGTM: Test imports updated for new failure scenarios.

The imports for SafeERC20, console, and MaliciousArbitrableMock support the new test cases for transfer failures and arbitrable revert scenarios.


566-617: LGTM: Comprehensive test for failed fee token transfer.

The test_execute_feeToken_failedTransfer test validates the SafeTransferFailed event emission and confirms that execution continues correctly even when individual reward transfers fail. The test properly simulates a transfer failure by depleting the contract's fee token balance.


721-761: LGTM: Test validates withdrawal path when arbitrable reverts.

The test_executeRuling_arbitrableRevert test confirms that withdrawal functionality remains operational even when the arbitrable's rule() function reverts. This is critical for ensuring users can always claim their funds regardless of malicious or buggy arbitrable implementations.


1003-1055: LGTM: Helper functions improve test maintainability.

The internal helper functions (_assertJurorBalance, _stakeBalanceForJuror, _stakePnk_createDispute_moveToDrawingPhase, _drawJurors_advancePeriodToVoting, _vote_execute) reduce code duplication and improve test readability by encapsulating common test setup patterns.

Comment on lines +144 to +168
function test_setStake_totalStaked() public {
// Increase
vm.prank(staker1);
core.setStake(GENERAL_COURT, 4000);
vm.prank(staker1);
core.setStake(GENERAL_COURT, 5001);
vm.prank(staker2);
core.setStake(GENERAL_COURT, 1000);
vm.prank(staker2);
core.setStake(GENERAL_COURT, 1500);

assertEq(sortitionModule.totalStaked(), 6501, "Wrong totalStaked");

// Decrease
vm.prank(staker1);
core.setStake(GENERAL_COURT, 3000);
vm.prank(staker1);
core.setStake(GENERAL_COURT, 2500);
vm.prank(staker2);
core.setStake(GENERAL_COURT, 1400);
vm.prank(staker2);
core.setStake(GENERAL_COURT, 1200);

assertEq(sortitionModule.totalStaked(), 3700, "Wrong totalStaked");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate test function.

The test_setStake_totalStaked() function appears to be defined only once in the provided code (lines 144-168). However, the AI summary indicates there are two identical occurrences. Please verify and remove any duplicate definition if present, as duplicate test functions can cause compilation issues or test execution confusion.

#!/bin/bash
# Description: Check for duplicate test_setStake_totalStaked function definitions

# Search for all occurrences of the function definition
rg -nP --type=sol 'function\s+test_setStake_totalStaked\s*\(\s*\)\s+public'
🤖 Prompt for AI Agents
contracts/test/foundry/KlerosCore_Staking.t.sol lines ~144-168: the
test_setStake_totalStaked function is duplicated elsewhere causing potential
compilation/test confusion; locate the other identical definition (search for
"function test_setStake_totalStaked() public") and remove the duplicate
definition, or if both are needed, rename one to a unique test name and ensure
its assertions remain correct so only a single function name exists per test;
run the test suite to verify compilation and that the intended assertions still
execute.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
contracts/src/test/MaliciousArbitrableMock.sol (2)

12-14: Consider adding access control for test predictability.

The changeBehaviour() function has no access control, which is acceptable for a test mock but could lead to unexpected test behavior if called from unintended addresses. For test predictability, consider restricting to onlyByOwner inherited from the parent pattern.


36-49: Consider using super.rule() to avoid code duplication.

The override duplicates the parent's validation and state update logic. Using super.rule() would be more maintainable and ensure the mock stays in sync with any future changes to ArbitrableExample.rule().

🔎 Proposed refactor
     function rule(uint256 _arbitratorDisputeID, uint256 _ruling) external override {
         if (doRevert) revert RuleReverted();
-
-        uint256 localDisputeID = externalIDtoLocalID[_arbitratorDisputeID];
-        DisputeStruct storage dispute = disputes[localDisputeID];
-        if (msg.sender != address(arbitrator)) revert ArbitratorOnly();
-        if (_ruling > dispute.numberOfRulingOptions) revert RulingOutOfBounds();
-        if (dispute.isRuled) revert DisputeAlreadyRuled();
-
-        dispute.isRuled = true;
-        dispute.ruling = _ruling;
-
-        emit Ruling(IArbitratorV2(msg.sender), _arbitratorDisputeID, dispute.ruling);
+        ArbitrableExample.rule(_arbitratorDisputeID, _ruling);
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a016740 and ad3006d.

📒 Files selected for processing (8)
  • contracts/src/arbitration/KlerosCore.sol (1 hunks)
  • contracts/src/arbitration/SortitionModule.sol (2 hunks)
  • contracts/src/arbitration/arbitrables/ArbitrableExample.sol (1 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1 hunks)
  • contracts/src/libraries/SafeERC20.sol (2 hunks)
  • contracts/src/test/MaliciousArbitrableMock.sol (1 hunks)
  • contracts/test/foundry/KlerosCore_Execution.t.sol (10 hunks)
  • contracts/test/foundry/KlerosCore_Staking.t.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/src/arbitration/KlerosCore.sol
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2024-10-21T10:32:16.970Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 1703
File: kleros-sdk/src/utils/getDispute.ts:38-40
Timestamp: 2024-10-21T10:32:16.970Z
Learning: The variables 'arbitrableChainID' and 'externalDisputeID' are required by the context to have uppercase 'ID', so they should remain unchanged even if the corresponding source properties use 'Id'.

Applied to files:

  • contracts/src/arbitration/arbitrables/ArbitrableExample.sol
📚 Learning: 2025-09-03T19:34:58.056Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2107
File: contracts/src/arbitration/university/KlerosCoreUniversity.sol:1083-1092
Timestamp: 2025-09-03T19:34:58.056Z
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/SortitionModule.sol
📚 Learning: 2024-11-19T16:31:08.965Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 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/SortitionModule.sol
📚 Learning: 2025-09-04T23:36:16.415Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2126
File: contracts/src/arbitration/KlerosCore.sol:472-489
Timestamp: 2025-09-04T23:36:16.415Z
Learning: In this repo, KlerosCore emits AcceptedFeeToken and NewCurrencyRate events that are declared in contracts/src/arbitration/interfaces/IArbitratorV2.sol; implementations don’t need to redeclare these events.

Applied to files:

  • contracts/test/foundry/KlerosCore_Execution.t.sol
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
📚 Learning: 2024-11-19T05:31:48.701Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 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/foundry/KlerosCore_Execution.t.sol
📚 Learning: 2025-09-30T17:18:12.895Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2145
File: contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol:277-286
Timestamp: 2025-09-30T17:18:12.895Z
Learning: In DisputeKitClassicBase.sol's castCommit function, jurors are allowed to re-submit commits during the commit period. The implementation uses a commitCount variable to track only first-time commits (where commit == bytes32(0)) so that totalCommitted is not incremented when a juror updates their existing commit.

Applied to files:

  • contracts/test/foundry/KlerosCore_Execution.t.sol
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
📚 Learning: 2024-10-07T06:18:23.427Z
Learnt from: kemuru
Repo: kleros/kleros-v2 PR: 1702
File: web/src/pages/Home/TopJurors/JurorCard/index.tsx:10-11
Timestamp: 2024-10-07T06:18:23.427Z
Learning: In the `kleros-v2` codebase, the property `totalResolvedDisputes` should remain and should not be renamed to `totalResolvedVotes`.

Applied to files:

  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.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). (14)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: hardhat-tests
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (16)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)

478-479: Ensure dispute withdrawals only happen after execution period is reached.

The check using period != KlerosCore.Period.execution is the correct approach. The execution period is entered exclusively when a dispute reaches its final resolution state after voting completes and all appeals expire or resolve, making it semantically equivalent to a resolved dispute. This pattern is used consistently throughout KlerosCore for controlling state-dependent operations.

contracts/src/libraries/SafeERC20.sol (2)

18-22: LGTM! Event declaration is well-documented.

The SafeTransferFailed event provides useful observability for failed transfers, enabling off-chain monitoring and debugging.


40-42: The asymmetry between safeTransfer and safeTransferFrom is intentional and justified by usage patterns. safeTransfer emits a SafeTransferFailed event on failure because it's used for non-critical operations (reward/penalty distributions) where failure may go unhandled; conversely, all 7 safeTransferFrom usages explicitly check the return value and revert immediately, since it's used for critical deposit operations that must fail explicitly. The design is consistent with the risk profiles of each function's usage context.

contracts/src/arbitration/arbitrables/ArbitrableExample.sol (1)

152-163: LGTM! Adding virtual modifier enables proper extensibility.

This change correctly allows derived contracts like MaliciousArbitrableMock to override the rule() function for testing purposes, while maintaining all existing access control and validation logic.

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

144-168: LGTM! Test correctly validates totalStaked accounting.

The test properly verifies the new totalStaked bookkeeping behavior:

  • Stake increases: 5001 (staker1) + 1500 (staker2) = 6501 ✓
  • Stake decreases: 2500 (staker1) + 1200 (staker2) = 3700 ✓

This provides good coverage for the refactored totalStaked tracking in SortitionModule._setStake(). The previous review comment about a duplicate function appears to be stale as only one definition is present in the provided code.

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

385-406: LGTM! totalStaked bookkeeping correctly tracks deposit/withdrawal flows.

The refactored logic properly maintains totalStaked invariants:

  • Deposits (line 392): totalStaked increases in sync with juror.stakedPnk
  • Withdrawals (line 395): totalStaked decreases in sync with juror.stakedPnk

The else branch executes when _pnkDeposit == 0, which handles both actual withdrawals (_pnkWithdrawal > 0) and no-op cases (_pnkWithdrawal == 0) safely since subtracting zero has no effect.


460-465: LGTM! Leftover withdrawal correctly updates totalStaked.

The sequence is correct:

  1. Retrieve leftover amount
  2. Zero out juror.stakedPnk
  3. Decrease totalStaked by the withdrawn amount
  4. Transfer tokens

This ensures totalStaked remains consistent with actual token holdings in the contract.

contracts/test/foundry/KlerosCore_Execution.t.sol (9)

101-103: Good addition: Balance consistency checks.

These assertions verify that totalStaked remains consistent with actual token balances throughout the execution flow, improving test reliability.

Also applies to: 149-150, 153-153


487-498: Good test coverage for SafeERC20 failure handling.

This segment properly tests the new TransferFailed error path by artificially draining the core balance and verifying the expected revert, then restoring state to continue the test.


546-546: Verify: Vote choice changed from 2 to 1.

The vote choice was modified without clear explanation. Since staker1 holds all votes, either choice should result in the same outcome, but confirm this change aligns with the test's intent.


566-617: Excellent test coverage for fee token transfer failures.

This test properly validates that when safeTransfer fails due to insufficient balance, the SafeTransferFailed event is emitted and execution completes without reverting, while correctly preventing token transfer to the juror.


721-761: Excellent security test: Withdrawal works despite malicious arbitrable.

This test validates that jurors can withdraw fees and rewards even when the arbitrable contract maliciously reverts during executeRuling(). This is critical for ensuring jurors aren't financially penalized by misbehaving arbitrables.


842-844: Intentional change: Withdrawals now independent of executeRuling.

The commented-out executeRuling() call and accompanying comment clarify that withdrawals should work even if executeRuling() hasn't been called or has reverted. This aligns with the resilience testing against malicious arbitrables.


1001-1055: Excellent refactor: Internal helpers improve test maintainability.

These helper functions consolidate common test patterns, reducing duplication and improving readability. The naming is clear and each function has a well-defined, single responsibility.


870-900: Test appears incomplete: Missing assertions for expected behavior.

This test demonstrates a potential totalStaked inflation scenario when delayed stakes are executed with insufficient juror funds, but lacks clear assertions:

  1. No verification that totalStaked is actually inflated (lines 888, 894 just log values)
  2. No expectRevert for the arithmetic overflow mentioned in comments (lines 897-898)
  3. Final _stakeBalanceForJuror(staker2, 20000) call at line 900 has no assertion about expected outcome

Consider adding:

  • assertEq or assertGt to verify totalStaked inflation after executeDelayedStakes
  • vm.expectRevert if overflow is expected when staker2 tries to stake
  • Or assertions proving the bug is fixed if this is a regression test

Would you like me to suggest complete assertions based on the expected behavior?

⛔ Skipped due to learnings
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2107
File: contracts/src/arbitration/university/KlerosCoreUniversity.sol:1083-1092
Timestamp: 2025-09-03T19:34:58.056Z
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.

5-5: LGTM! MaliciousArbitrableMock contract exists in the codebase.

The new imports properly support the enhanced test coverage for SafeERC20 failure handling and malicious arbitrable behavior.

@jaybuidl jaybuidl self-assigned this Dec 19, 2025
@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad3006d and 104d89b.

📒 Files selected for processing (1)
  • contracts/src/arbitration/KlerosCore.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-04T23:36:16.415Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 2126
File: contracts/src/arbitration/KlerosCore.sol:472-489
Timestamp: 2025-09-04T23:36:16.415Z
Learning: In this repo, KlerosCore emits AcceptedFeeToken and NewCurrencyRate events that are declared in contracts/src/arbitration/interfaces/IArbitratorV2.sol; implementations don’t need to redeclare these events.

Applied to files:

  • contracts/src/arbitration/KlerosCore.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). (14)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Analyze (javascript)
  • GitHub Check: hardhat-tests
🔇 Additional comments (1)
contracts/src/arbitration/KlerosCore.sol (1)

627-627: LGTM! Consistent pause protection for token transfers.

Adding whenNotPaused is appropriate and aligns with other token transfer functions like setStake and execute.

if (msg.sender != address(sortitionModule)) revert SortitionModuleOnly();
// Note eligibility is checked in SortitionModule.
pinakion.safeTransfer(_account, _amount);
if (!pinakion.safeTransfer(_account, _amount)) revert TransferFailed();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verify consistent error handling for all safeTransfer calls.

The explicit return value check is appropriate given the SafeERC20 modifications. However, there are other safeTransfer calls in this file that don't check return values:

  • Line 965: pinakion.safeTransfer(owner, _params.pnkPenaltiesInRound);
  • Line 1023: pinakion.safeTransfer(account, pnkReward);
  • Line 1045: pinakion.safeTransfer(owner, leftoverPnkReward);
  • Line 1290: _feeToken.safeTransfer(_recipient, _amount);

If SafeERC20 now returns false instead of reverting on failure, these unchecked calls could fail silently, leading to incorrect state (e.g., rewards not distributed but marked as paid).

Run this script to verify all safeTransfer usage patterns:

#!/bin/bash
# Find all safeTransfer calls in KlerosCore.sol and show context

echo "=== All safeTransfer calls in KlerosCore.sol ==="
rg -n -B2 -A2 'safeTransfer(From)?\(' contracts/src/arbitration/KlerosCore.sol

echo -e "\n=== Checking if SafeERC20 implementation returns boolean ==="
rg -n -A10 'function safeTransfer' contracts/src/libraries/SafeERC20.sol
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCore.sol (lines ~630, 965, 1023, 1045, and
1290) some pinakion/_feeToken.safeTransfer calls are not checking the boolean
return introduced by the modified SafeERC20, causing silent failures; update
each unchecked call to validate the return and revert on false (e.g., wrap each
transfer in an if check that reverts with TransferFailed() on a false return) or
alternatively replace those calls with a helper that asserts the transfer
succeeded; apply the same pattern used at line 630 to all listed transfers so
state changes (rewards/fees) cannot be marked as paid when the token transfer
failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants