Skip to content

Conversation

jaybuidl
Copy link
Member

@jaybuidl jaybuidl commented Oct 14, 2025

PR-Codex overview

This PR introduces enhancements to the DisputeKitGated and DisputeKitGatedShutter contracts, including new functionality for token gating in dispute creation and improvements in testing frameworks for both kits.

Detailed summary

  • Changed createDispute visibility from external to public in DisputeKitClassicBase.
  • Added changeSupportedTokens function to manage token support in DisputeKitGated and DisputeKitGatedShutter.
  • Updated _extraDataToTokenInfo to be internal in both kits.
  • Introduced TokenNotSupported error for unsupported tokens.
  • Created mock contracts DisputeKitGatedMock and DisputeKitGatedShutterMock for testing.
  • Enhanced test suites for DisputeKitGated and DisputeKitGatedShutter to cover token gating and access control scenarios.
  • Added helper functions in dispute-kit-gated-common.ts for setting up tests and managing token whitelisting.
  • Improved handling of address(0) as a token gate, allowing disputes without token gating.

The following files were skipped due to too many changes: contracts/test/arbitration/dispute-kit-shutter.ts, contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts

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

Summary by CodeRabbit

  • New Features

    • Token-gated dispute support with whitelistable tokens and an option to allow ungated disputes.
    • Admin bulk controls to add/remove supported tokens.
    • Public test mocks exposing extra-data parsing for test verification.
  • Behavior Changes

    • Dispute creation now validates token gating and rejects unsupported tokens (TokenNotSupported).
    • Dispute creation entry widened to permit extensions/overrides.
  • Tests

    • Consolidated, context-driven test harness with shared helpers and new comprehensive suites for token-gating and shutter scenarios.

@jaybuidl jaybuidl linked an issue Oct 14, 2025 that may be closed by this pull request
Copy link

netlify bot commented Oct 14, 2025

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit cefa0cb
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/68ee963f97faa40008918d0b
😎 Deploy Preview https://deploy-preview-2176--kleros-v2-testnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

Copy link

netlify bot commented Oct 14, 2025

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

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

@jaybuidl jaybuidl self-assigned this Oct 14, 2025
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

createDispute visibility made overridable in DisputeKitClassicBase. DisputeKitGated and DisputeKitGatedShutter add token-gating state and checks (supportedTokens, changeSupportedTokens, TokenNotSupported) and override createDispute to validate tokenGate extracted from extraData. Tests refactored to shared helpers and add mocks exposing token parsing.

Changes

Cohort / File(s) Summary
Classic base visibility update
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol
createDispute(...) visibility changed from external override to public virtual override (still guarded by onlyByCore).
Gated dispute kit + mock
contracts/src/arbitration/dispute-kits/DisputeKitGated.sol, contracts/src/test/DisputeKitGatedMock.sol
Added NO_TOKEN_GATE constant and mapping(address => bool) public supportedTokens; initialized supportedTokens[NO_TOKEN_GATE] = true; added changeSupportedTokens(address[] memory, bool) external onlyByOwner; added error TokenNotSupported(address); _extraDataToTokenInfo made internal; createDispute(...) public override parses tokenGate from extraData, reverts if unsupported, else delegates to super.createDispute. Added test mock extraDataToTokenInfo wrapper.
GatedShutter dispute kit + mock
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol, contracts/src/test/DisputeKitGatedShutterMock.sol
Added NO_TOKEN_GATE and mapping(address => bool) public supportedTokens; owner-only changeSupportedTokens; renamed internal helper to _extraDataToTokenInfo and updated call sites; createDispute(...) overridden to validate tokenGate against supportedTokens and revert with TokenNotSupported if unsupported; mock exposes public wrapper for token parsing.
Tests — token-gated helpers and refactors
contracts/test/helpers/dispute-kit-gated-common.ts, contracts/test/helpers/dispute-kit-shutter-common.ts, contracts/test/arbitration/dispute-kit-gated.ts, contracts/test/arbitration/dispute-kit-gated-shutter.ts, contracts/test/arbitration/dispute-kit-shutter.ts
Added comprehensive shared test helpers/contexts (TokenGatedTestContext, ShutterTestContext, encodeExtraData, stakeAndDraw, createDisputeWithToken, period helpers, suites) and refactored specific test files to use setupTokenGatedTest / setupShutterTest and shared test suites; tests now call centralized helpers and use mocks exposing extraData parsing.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Core
  participant Gated as DisputeKitGated
  participant Base as DisputeKitClassicBase
  Core->>Gated: createDispute(coreDisputeID, coreRoundID, choices, extraData, nbVotes)
  note right of Gated: extract tokenGate via _extraDataToTokenInfo(extraData)
  alt tokenGate not supported
    Gated-->>Core: revert TokenNotSupported(tokenGate)
  else supported
    Gated->>Base: super.createDispute(...)
    Base-->>Core: dispute created
  end
Loading
sequenceDiagram
  autonumber
  actor Core
  participant GatedShutter as DisputeKitGatedShutter
  participant Base as DisputeKitClassicBase
  Core->>GatedShutter: createDispute(..., extraData, ...)
  GatedShutter->>GatedShutter: _extraDataToTokenInfo(extraData)
  alt unsupported
    GatedShutter-->>Core: revert TokenNotSupported
  else supported
    GatedShutter->>Base: super.createDispute(...)
    Base-->>Core: success
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • unknownunknown1

Poem

I hop through bytes and check each gate,
A token nods — the buns celebrate.
If blacklisted, I blink and stop my trot,
Else jurors gather — peanut-bun plot. 🐰🎉

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 title accurately and concisely describes the primary change, which is adding token whitelist (supportedTokens mapping and management) to both DisputeKitGated and DisputeKitGatedShutter. It focuses on the main feature without extraneous detail, making it clear to reviewers what the PR implements.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/gated-dk-token-whitelist

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

Copy link

netlify bot commented Oct 14, 2025

Deploy Preview for kleros-v2-neo ready!

Name Link
🔨 Latest commit cefa0cb
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/68ee963ffaa1460008351667
😎 Deploy Preview https://deploy-preview-2176--kleros-v2-neo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (2)

100-107: Emit events for governance transparency.

The changeSupportedTokens function modifies critical state but doesn't emit events. Consider emitting an event to track token whitelist changes for transparency and off-chain monitoring.

Apply this diff:

+    event SupportedTokensChanged(address indexed token, bool supported);
+
     /// @notice Changes the supported tokens.
     /// @param _tokens The tokens to support.
     /// @param _supported Whether the tokens are supported or not.
     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
             supportedTokens[_tokens[i]] = _supported;
+            emit SupportedTokensChanged(_tokens[i], _supported);
         }
     }

100-107: Validate token addresses.

The function doesn't validate that token addresses are non-zero. While address(0) might be intentionally supported for "no gating," explicit validation or documentation would prevent accidental misconfigurations.

Consider adding validation:

     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
+            if (_tokens[i] == address(0) && _supported) revert InvalidTokenAddress();
             supportedTokens[_tokens[i]] = _supported;
         }
     }
contracts/test/arbitration/dispute-kit-gated.ts (1)

162-301: Test coverage gap: whitelist functionality not tested.

The tests verify token-gating behavior (jurors with/without tokens) but don't cover the new changeSupportedTokens governance function or the scenario where a token is not whitelisted (should revert with TokenNotSupported).

Consider adding tests for:

  1. Calling changeSupportedTokens to whitelist tokens
  2. Attempting to create a dispute with an unsupported token (should revert)
  3. Removing tokens from the whitelist

Would you like me to generate these additional test cases?

contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (2)

71-78: Emit events for governance transparency.

The changeSupportedTokens function modifies critical state but doesn't emit events. Consider emitting an event to track token whitelist changes for transparency and off-chain monitoring.

Apply this diff:

+    event SupportedTokensChanged(address indexed token, bool supported);
+
     /// @notice Changes the supported tokens.
     /// @param _tokens The tokens to support.
     /// @param _supported Whether the tokens are supported or not.
     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
             supportedTokens[_tokens[i]] = _supported;
+            emit SupportedTokensChanged(_tokens[i], _supported);
         }
     }

71-78: Validate token addresses.

The function doesn't validate that token addresses are non-zero. While address(0) might be intentionally supported for "no gating," explicit validation or documentation would prevent accidental misconfigurations.

Consider adding validation:

     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
+            if (_tokens[i] == address(0) && _supported) revert InvalidTokenAddress();
             supportedTokens[_tokens[i]] = _supported;
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7e365 and 5a46536.

📒 Files selected for processing (4)
  • contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (4 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (5 hunks)
  • contracts/test/arbitration/dispute-kit-gated.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
contracts/test/arbitration/dispute-kit-gated.ts (1)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
⏰ 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-neo
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Analyze (javascript)
  • GitHub Check: hardhat-tests
🔇 Additional comments (8)
contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol (1)

201-207: LGTM! Visibility change enables token-gating pattern.

Changing from external to public virtual correctly allows derived dispute kits (DisputeKitGated, DisputeKitGatedShutter) to override this function, perform token validation, then delegate to the base implementation via super.createDispute(). The onlyByCore modifier is preserved, ensuring access control remains intact.

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

113-126: Token gating logic is correct.

The override properly extracts the token address from extraData, validates it against the whitelist, and delegates to the base implementation. The comment correctly notes that super.createDispute() enforces onlyByCore access control.


237-252: Visibility change to internal is appropriate.

Changing _extraDataToTokenInfo from public to internal is correct since it's an implementation detail only needed by internal logic and overriding contracts. Tests now use a mock that exposes this publicly.


37-37: Consider initializing supported tokens.

The supportedTokens mapping is not initialized in the initialize function. This means all tokens start as unsupported, requiring explicit whitelisting through governance after deployment. Consider whether any tokens should be supported by default.

Do you want to initialize any tokens as supported by default, or is the empty whitelist intentional?

contracts/test/arbitration/dispute-kit-gated.ts (1)

58-68: Deployment pattern is correct.

The switch to using deployUpgradable and DisputeKitGatedMock aligns with the upgradeable proxy pattern and enables testing of internal functions through the mock contract.

contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (3)

84-97: Token gating logic is correct.

The override properly extracts the token address from extraData, validates it against the whitelist, and delegates to the base implementation. The comment correctly notes that super.createDispute() enforces onlyByCore access control.


113-128: Visibility change to internal is appropriate.

Changing _extraDataToTokenInfo from public to internal is correct since it's an implementation detail only needed by internal logic and overriding contracts. Tests now use a mock that exposes this publicly.


36-36: Consider initializing supported tokens.

The supportedTokens mapping is not initialized in the initialize function. This means all tokens start as unsupported, requiring explicit whitelisting through governance after deployment. Consider whether any tokens should be supported by default.

Do you want to initialize any tokens as supported by default, or is the empty whitelist intentional?

coderabbitai[bot]
coderabbitai bot previously approved these changes Oct 14, 2025
@jaybuidl jaybuidl force-pushed the fix/gated-dk-token-whitelist branch from 17c684f to bcbe228 Compare October 14, 2025 16:49
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: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a46536 and bcbe228.

📒 Files selected for processing (2)
  • contracts/src/test/DisputeKitGatedMock.sol (1 hunks)
  • contracts/test/arbitration/dispute-kit-gated.ts (13 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
contracts/test/arbitration/dispute-kit-gated.ts (2)
contracts/deploy/utils/index.ts (1)
  • PNK (40-40)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
⏰ 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). (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: hardhat-tests
🔇 Additional comments (8)
contracts/test/arbitration/dispute-kit-gated.ts (8)

22-31: LGTM!

The documentation comment provides a clear overview of the test coverage, which is helpful for understanding the test suite structure.


69-80: LGTM!

The deployment using deployUpgradable correctly initializes the mock contract with proper proxy setup, adds it to the core, and enables it for the GENERAL court.


99-100: LGTM!

Whitelisting all tokens by default is a pragmatic approach that allows existing tests to continue working without modification while new tests can explicitly test whitelist behavior.


106-145: LGTM!

The helper functions are well-designed, reduce code duplication, and provide a clean API for test operations. The type handling for string | Addressable is appropriate and flexible.


202-273: LGTM!

The whitelist management tests provide comprehensive coverage including:

  • Single and batch operations
  • Add and remove operations
  • Mixed operations
  • Idempotency (duplicate operations)
  • Access control

The test structure is well-organized with clear descriptions.


275-312: LGTM!

The error handling tests thoroughly verify the TokenNotSupported error for all token types (ERC20, ERC721, ERC1155) and include a state transition test that validates the full lifecycle of removing and re-adding tokens.


455-510: LGTM!

The integration tests validate the complete workflow:

  • Token whitelisting → dispute creation → juror drawing
  • Token removal → dispute rejection
  • State consistency across multiple operations

These tests ensure the whitelist feature integrates correctly with the existing dispute system.


180-180: LGTM!

The call is correctly updated to use the public extraDataToTokenInfo wrapper from DisputeKitGatedMock instead of the now-internal _extraDataToTokenInfo.

Comment on lines +7 to +8
/// @title KlerosCoreMock
/// KlerosCore with view functions to use in Foundry tests.
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

Fix the outdated comment.

The comment references "KlerosCoreMock" but the contract is "DisputeKitGatedMock". This appears to be a copy-paste error from another mock file.

Apply this diff to correct the comment:

-/// @title KlerosCoreMock
-/// KlerosCore with view functions to use in Foundry tests.
+/// @title DisputeKitGatedMock
+/// @dev Test mock that exposes internal functions for testing DisputeKitGated.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// @title KlerosCoreMock
/// KlerosCore with view functions to use in Foundry tests.
/// @title DisputeKitGatedMock
/// @dev Test mock that exposes internal functions for testing DisputeKitGated.
🤖 Prompt for AI Agents
In contracts/src/test/DisputeKitGatedMock.sol around lines 7 to 8, the file
header comment incorrectly says "KlerosCoreMock" while the contract is named
"DisputeKitGatedMock"; update the comment to reference the correct contract name
and purpose (e.g., "DisputeKitGatedMock" and a brief description matching this
mock) so the header accurately reflects the file's contract.

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

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/dispute-kits/DisputeKitGatedShutter.sol (1)

258-279: Validate token is a contract to prevent draw-time reverts

If a non-contract or wrong interface address is whitelisted, _postDrawCheck may revert on balanceOf. Add a code-size check when gating is active to fail early during dispute creation.

     function createDispute(
@@
-        (address tokenGate, , ) = _extraDataToTokenInfo(_extraData);
+        (address tokenGate, , ) = _extraDataToTokenInfo(_extraData);
         if (!supportedTokens[tokenGate]) revert TokenNotSupported(tokenGate);
+        if (tokenGate != NO_TOKEN_GATE) {
+            // Prevent footguns: require a contract address
+            if (tokenGate.code.length == 0) revert TokenNotSupported(tokenGate);
+        }
🧹 Nitpick comments (11)
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (3)

103-111: Emit whitelist change events and consider zero-address policy

  • No event is emitted on whitelist updates; governance changes are silent on-chain.
  • Clarify whether toggling NO_TOKEN_GATE (address(0)) is allowed; currently possible via changeSupportedTokens.

Add an event and emit per token; optionally guard address(0) if you want to keep “no-gate” always enabled.

+    // *              Events               * //
+    event SupportedTokenChanged(address indexed token, bool supported);
@@
     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
-            supportedTokens[_tokens[i]] = _supported;
+            address token = _tokens[i];
+            supportedTokens[token] = _supported;
+            emit SupportedTokenChanged(token, _supported);
         }
     }

116-129: createDispute pre-check OK; consider early “no-gate” fast path

Logic is correct. Minor optimization/readability: early-return when tokenGate == NO_TOKEN_GATE to skip mapping lookup.


240-255: Avoid calldata→memory copy for extraData parsing

This is called from createDispute(bytes calldata _extraData) but accepts bytes memory, incurring a copy. Consider overloading with a calldata variant to save gas at hot paths.

-    function _extraDataToTokenInfo(
-        bytes memory _extraData
-    ) internal pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
+    function _extraDataToTokenInfoCalldata(
+        bytes calldata _extraData
+    ) internal pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
         // Need at least 160 bytes to safely read the parameters
         if (_extraData.length < 160) return (address(0), false, 0);
         assembly {
             let packedTokenGateIsERC1155 := calldataload(_extraData.offset + 0x60) // 4th param (128)
             tokenId := calldataload(_extraData.offset + 0x80) // 5th param (160)
             tokenGate := and(packedTokenGateIsERC1155, 0xffffffffffffffffffffffffffffffffffffffff)
             isERC1155 := and(shr(160, packedTokenGateIsERC1155), 1)
         }
     }

Then use the calldata version in createDispute; keep the memory version for storage/memory call sites.

contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (3)

74-82: Emit events for whitelist changes

Mirror the event in the non-Shutter variant to keep observability consistent.

+    event SupportedTokenChanged(address indexed token, bool supported);
@@
     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
-            supportedTokens[_tokens[i]] = _supported;
+            address token = _tokens[i];
+            supportedTokens[token] = _supported;
+            emit SupportedTokenChanged(token, _supported);
         }
     }

87-100: Fast-path and contract-code guard (same as Shutter)

  • Optional: fast-path when tokenGate == NO_TOKEN_GATE.
  • Recommended: require tokenGate.code.length > 0 when gating is active to avoid draw-time failures.

116-131: extraData parsing: avoid calldata→memory copy

Same suggestion as in Shutter file: provide a calldata overload for _extraDataToTokenInfo and call it from createDispute.

contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (2)

76-85: whitelistTokens signer assumption

Function relies on disputeKit being connected to the owner. If tests ever change signer context, pass the signer explicitly to avoid flakiness.


108-162: stakeAndDraw: disputeId hardcoded to 0

This relies on fresh fixtures per test. If suites evolve to reuse state, derive the disputeId from the CreateDispute event or core state.

contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (3)

185-201: Remove the arbitrary 10‑vote cap when collecting vote IDs

A fixed cap can miss valid votes and cause flakiness. Stop when getVoteInfo reverts, but keep a reasonable safety cap.

Apply this diff:

-  const voteIDs: bigint[] = [];
-  const maxVotes = 10; // Reasonable limit for testing
-
-  for (let i = 0; i < maxVotes; i++) {
+  const voteIDs: bigint[] = [];
+  const SAFETY_CAP = 64; // upper bound to avoid infinite loops
+  for (let i = 0; i < SAFETY_CAP; i++) {
     try {
       const voteInfo = await context.disputeKit.getVoteInfo(disputeId, roundIndex, i);
       if (voteInfo[0] === juror.address) {
         // account is at index 0
         voteIDs.push(BigInt(i));
       }
     } catch {
       // No more votes
       break;
     }
   }

53-56: Narrow the config contractName to a string union

Prevents typos and improves DX.

Apply this diff:

-export interface ShutterTestConfig {
-  contractName: string; // "DisputeKitShutter" or "DisputeKitGatedShutter"
+export interface ShutterTestConfig {
+  contractName: "DisputeKitShutter" | "DisputeKitGatedShutter";
   isGated?: boolean; // Whether to setup token gating
 }

139-141: Optional: use Hardhat network-helpers for time/mine

Switch to @nomicfoundation/hardhat-network-helpers (time.increase, mine) for readability and consistency with Hardhat v3 runtime.

Based on learnings

Also applies to: 165-167

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcbe228 and 700738e.

📒 Files selected for processing (8)
  • contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (6 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (6 hunks)
  • contracts/src/test/DisputeKitGatedShutterMock.sol (1 hunks)
  • contracts/test/arbitration/dispute-kit-gated-shutter.ts (1 hunks)
  • contracts/test/arbitration/dispute-kit-gated.ts (1 hunks)
  • contracts/test/arbitration/dispute-kit-shutter.ts (1 hunks)
  • contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1 hunks)
  • contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
contracts/test/arbitration/dispute-kit-shutter.ts (1)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (7)
  • ShutterTestContext (25-50)
  • setupShutterTest (206-325)
  • testCommitPhase (329-443)
  • testNormalFlowBotReveals (445-582)
  • testRecoveryFlowJurorReveals (584-730)
  • testHashFunctionBehavior (732-772)
  • testEdgeCasesAndSecurity (774-875)
contracts/test/arbitration/dispute-kit-gated.ts (1)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (9)
  • TokenGatedTestContext (27-46)
  • setupTokenGatedTest (165-236)
  • testTokenWhitelistManagement (240-305)
  • testAccessControl (307-323)
  • testUnsupportedTokenErrors (325-368)
  • testERC20Gating (370-431)
  • testERC721Gating (433-494)
  • testERC1155Gating (496-557)
  • testWhitelistIntegration (559-625)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (9)
  • TokenGatedTestContext (27-46)
  • setupTokenGatedTest (165-236)
  • testTokenWhitelistManagement (240-305)
  • testAccessControl (307-323)
  • testUnsupportedTokenErrors (325-368)
  • testERC20Gating (370-431)
  • testERC721Gating (433-494)
  • testERC1155Gating (496-557)
  • testWhitelistIntegration (559-625)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (7)
  • ShutterTestContext (25-50)
  • setupShutterTest (206-325)
  • testCommitPhase (329-443)
  • testNormalFlowBotReveals (445-582)
  • testRecoveryFlowJurorReveals (584-730)
  • testHashFunctionBehavior (732-772)
  • testEdgeCasesAndSecurity (774-875)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1)
  • encodeExtraData (59-74)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (4)
contracts/deploy/utils/index.ts (1)
  • PNK (40-40)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (1)
  • encodeExtraData (71-72)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
contracts/deploy/utils/deployTokens.ts (2)
  • deployERC721 (55-67)
  • deployERC1155 (69-81)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Analyze (javascript)
  • GitHub Check: hardhat-tests
🔇 Additional comments (7)
contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (1)

146-155: Post-draw check assumes prior whitelist validation

OK to skip supportedTokens check here. If you plan to allow dynamic whitelist changes mid-dispute, consider caching tokenGate per dispute at creation to avoid state drift. Otherwise current approach is fine.

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

10-14: Mock wrapper is clean

Public wrapper correctly exposes parsing for tests; no concerns.

contracts/test/arbitration/dispute-kit-gated-shutter.ts (1)

36-72: Shared test harness usage LGTM

Good split between token-gating and Shutter suites with contexts and mocks. No issues spotted.

contracts/test/arbitration/dispute-kit-shutter.ts (1)

12-38: Refactor to shared helpers LGTM

Cleaner suite via shared helpers; maintains coverage and readability.

contracts/test/arbitration/dispute-kit-gated.ts (1)

14-41: Adoption of shared token-gated helpers LGTM

Context-driven tests cover whitelist, access control, and gating paths comprehensively.

contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1)

58-74: Packed encoding matches on-chain parsing

The uint88 + bool + address packing aligns with assembly extraction (addr in low 160 bits, bool at bit 160). Good.

contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (1)

343-349: Verify/align test event args with the dispute‑kit ABI (CommitCastShutter / VoteCast)

  • IDisputeKit.VoteCast matches the test shape: (uint256 indexed coreDisputeID, address indexed juror, uint256[] voteIDs, uint256 indexed choice, string justification).
  • CommitCastShutter is inconsistent across implementations/ABIs in this repo: some variants include a recoveryCommit bytes32 (6 args), others do not (5 args). The test currently expects the 6‑arg form (commit, recoveryCommit, identity, encryptedVote).
  • Action: ensure the test deploys/uses the dispute‑kit implementation whose ABI includes recoveryCommit, or update the test to expect the actual emitted event arguments (remove recoveryCommit from withArgs or assert it separately if present).

Comment on lines +133 to +139
const arbitrationCost = await context.core["arbitrationCost(bytes)"](extraData);

// Create dispute via core contract
await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait());
const disputeId = 0;

await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not hard‑code disputeId=0; parse it from the DisputeCreation event

Hard‑coding to 0 makes tests brittle and wrong once multiple disputes exist. Parse the emitted event to get the actual ID.

Apply this diff:

   const arbitrationCost = await context.core["arbitrationCost(bytes)"](extraData);

   // Create dispute via core contract
-  await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait());
-  const disputeId = 0;
+  const tx = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost });
+  const receipt = await tx.wait();
+  // Parse DisputeCreation(disputeID, ...) to obtain the ID
+  let disputeId: number | undefined;
+  for (const log of receipt.logs) {
+    try {
+      const parsed = context.core.interface.parseLog(log);
+      if (parsed.name === "DisputeCreation") {
+        disputeId = Number(parsed.args[0]);
+        break;
+      }
+    } catch {}
+  }
+  if (disputeId === undefined) throw new Error("DisputeCreation event not found");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const arbitrationCost = await context.core["arbitrationCost(bytes)"](extraData);
// Create dispute via core contract
await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait());
const disputeId = 0;
await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
const arbitrationCost = await context.core["arbitrationCost(bytes)"](extraData);
// Create dispute via core contract
const tx = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost });
const receipt = await tx.wait();
// Parse DisputeCreation(disputeID, ...) to obtain the ID
let disputeId: number | undefined;
for (const log of receipt.logs) {
try {
const parsed = context.core.interface.parseLog(log);
if (parsed.name === "DisputeCreation") {
disputeId = Number(parsed.args[0]);
break;
}
} catch {}
}
if (disputeId === undefined) throw new Error("DisputeCreation event not found");
await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts around lines
133 to 139, the code hard‑codes disputeId = 0 after creating the dispute;
instead capture the transaction receipt from the awaited createDispute call,
find the DisputeCreation event in receipt.events, and extract the emitted
dispute id (typically via event.args.disputeID or the appropriate indexed arg
name/position). Replace the hard‑coded assignment with parsing logic that sets
disputeId from that event so tests use the actual created dispute id.

Comment on lines +160 to +169
export const advanceToVotePeriod = async (context: ShutterTestContext, disputeId: number) => {
// Advance from commit to vote period
const timesPerPeriod = [300, 300, 300, 300]; // Default times from deployment
const commitPeriod = timesPerPeriod[Period.commit];

await network.provider.send("evm_increaseTime", [Number(commitPeriod)]);
await network.provider.send("evm_mine");

await context.core.passPeriod(disputeId).then((tx) => tx.wait());

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

Advance time with a safety buffer; avoid hard‑coding period times

Increasing time by exactly the commit period can be borderline if the contract uses strict > comparisons. Add +1s. Also, consider reading the period durations from the court instead of duplicating [300,300,300,300].

Apply this diff for the buffer:

-  const timesPerPeriod = [300, 300, 300, 300]; // Default times from deployment
+  const timesPerPeriod = [300, 300, 300, 300]; // Keep in sync with court config
   const commitPeriod = timesPerPeriod[Period.commit];

-  await network.provider.send("evm_increaseTime", [Number(commitPeriod)]);
+  await network.provider.send("evm_increaseTime", [Number(commitPeriod) + 1]);
   await network.provider.send("evm_mine");

And consider fetching the actual configured times from Core (e.g., via courts[shutterCourtID].timesPerPeriod or an accessor) to prevent drift.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const advanceToVotePeriod = async (context: ShutterTestContext, disputeId: number) => {
// Advance from commit to vote period
const timesPerPeriod = [300, 300, 300, 300]; // Default times from deployment
const commitPeriod = timesPerPeriod[Period.commit];
await network.provider.send("evm_increaseTime", [Number(commitPeriod)]);
await network.provider.send("evm_mine");
await context.core.passPeriod(disputeId).then((tx) => tx.wait());
export const advanceToVotePeriod = async (context: ShutterTestContext, disputeId: number) => {
// Advance from commit to vote period
const timesPerPeriod = [300, 300, 300, 300]; // Keep in sync with court config
const commitPeriod = timesPerPeriod[Period.commit];
await network.provider.send("evm_increaseTime", [Number(commitPeriod) + 1]);
await network.provider.send("evm_mine");
await context.core.passPeriod(disputeId).then((tx) => tx.wait());
🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts around lines
160 to 169, the test advances time by the exact commit period and uses a
hard‑coded timesPerPeriod array; change the evm_increaseTime call to add a 1s
safety buffer (commitPeriod + 1) to avoid strict > boundary issues, and replace
the fixed timesPerPeriod with a fetch from the deployed Core/Court (read the
configured timesPerPeriod via the appropriate Core/court accessor for the test
context) so the test uses the actual contract durations rather than duplicating
[300,300,300,300].

Comment on lines +733 to +755
describe("Hash Function Behavior", () => {
it("Should return different hashes for juror vs non-juror callers", async () => {
const ctx = context();
const disputeId = await createDisputeAndDraw(ctx, ctx.shutterCourtID, 3, ctx.shutterDKID);
await advanceToCommitPeriod(ctx, disputeId);

const voteIDs = await getVoteIDsForJuror(ctx, disputeId, ctx.juror1);
const { fullCommit, recoveryCommit } = generateCommitments(ctx.choice, ctx.salt, ctx.justification);

await ctx.disputeKit
.connect(ctx.juror1)
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, ctx.identity, ctx.encryptedVote);

await advanceToVotePeriod(ctx, disputeId);

// During castVoteShutter, the contract should use different hash logic
// For juror: hash(choice, salt)
// For non-juror: hash(choice, salt, justificationHash)

// This is tested implicitly by the recovery flow tests above
// The juror can reveal with any justification, while non-juror must provide exact justification
});

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

Test has no assertions; either add checks or skip

This describes behavior but asserts nothing, so it can pass even if behavior changes.

Apply this diff to mark it as pending until assertions are added:

-    it("Should return different hashes for juror vs non-juror callers", async () => {
+    it.skip("Should return different hashes for juror vs non-juror callers (covered implicitly)", async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("Hash Function Behavior", () => {
it("Should return different hashes for juror vs non-juror callers", async () => {
const ctx = context();
const disputeId = await createDisputeAndDraw(ctx, ctx.shutterCourtID, 3, ctx.shutterDKID);
await advanceToCommitPeriod(ctx, disputeId);
const voteIDs = await getVoteIDsForJuror(ctx, disputeId, ctx.juror1);
const { fullCommit, recoveryCommit } = generateCommitments(ctx.choice, ctx.salt, ctx.justification);
await ctx.disputeKit
.connect(ctx.juror1)
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, ctx.identity, ctx.encryptedVote);
await advanceToVotePeriod(ctx, disputeId);
// During castVoteShutter, the contract should use different hash logic
// For juror: hash(choice, salt)
// For non-juror: hash(choice, salt, justificationHash)
// This is tested implicitly by the recovery flow tests above
// The juror can reveal with any justification, while non-juror must provide exact justification
});
describe("Hash Function Behavior", () => {
it.skip("Should return different hashes for juror vs non-juror callers (covered implicitly)", async () => {
const ctx = context();
const disputeId = await createDisputeAndDraw(ctx, ctx.shutterCourtID, 3, ctx.shutterDKID);
await advanceToCommitPeriod(ctx, disputeId);
const voteIDs = await getVoteIDsForJuror(ctx, disputeId, ctx.juror1);
const { fullCommit, recoveryCommit } = generateCommitments(ctx.choice, ctx.salt, ctx.justification);
await ctx.disputeKit
.connect(ctx.juror1)
.castCommitShutter(disputeId, voteIDs, fullCommit, recoveryCommit, ctx.identity, ctx.encryptedVote);
await advanceToVotePeriod(ctx, disputeId);
// During castVoteShutter, the contract should use different hash logic
// For juror: hash(choice, salt)
// For non-juror: hash(choice, salt, justificationHash)
// This is tested implicitly by the recovery flow tests above
// The juror can reveal with any justification, while non-juror must provide exact justification
});
🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts around lines
733 to 755, the "Should return different hashes for juror vs non-juror callers"
test contains no assertions so it always passes; either add explicit assertions
to verify juror vs non-juror hash behavior (e.g., call the contract/hash helper
for both roles and assert outputs differ and that recovery permits juror with
any justification but requires exact justification for non-juror), or mark the
test as pending/skipped (use it.skip or add a comment + return) until concrete
assertions are implemented; update the test accordingly so CI fails when
behavior is incorrect.

@jaybuidl jaybuidl marked this pull request as ready for review October 14, 2025 18:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1)

386-389: Improve type safety for log filtering.

The log filtering uses any type and optional chaining (log.fragment?.name). While functional, this reduces type safety and could hide issues.

Consider adding a type guard or helper function:

// At the top of the file, add a helper for type-safe log filtering
const isDrawLog = (log: any): log is { fragment: { name: string }, address: string, args: any[] } => {
  return log.fragment?.name === "Draw";
};

// Then use it in tests:
const drawLogs = tx?.logs.filter((log) => isDrawLog(log) && log.address === ctx.core.target) || [];

This pattern is repeated throughout the test suites (lines 407, 449, 470, 512, 533, 581, 655, 684), so a shared helper would improve consistency and type safety.

contracts/test/arbitration/dispute-kit-gated.ts (1)

14-16: Combine ESLint disable comments.

The two separate ESLint disable comments can be combined into a single comment for consistency.

 /* eslint-disable no-unused-vars */
-/* eslint-disable no-unused-expressions */
-
+/* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482
+
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 700738e and cefa0cb.

📒 Files selected for processing (3)
  • contracts/test/arbitration/dispute-kit-gated-shutter.ts (1 hunks)
  • contracts/test/arbitration/dispute-kit-gated.ts (1 hunks)
  • contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/test/arbitration/dispute-kit-gated-shutter.ts
🧰 Additional context used
🧬 Code graph analysis (2)
contracts/test/arbitration/dispute-kit-gated.ts (1)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (10)
  • TokenGatedTestContext (27-46)
  • setupTokenGatedTest (165-236)
  • testTokenWhitelistManagement (240-305)
  • testAccessControl (307-323)
  • testUnsupportedTokenErrors (325-368)
  • testERC20Gating (370-431)
  • testERC721Gating (433-494)
  • testERC1155Gating (496-557)
  • testWhitelistIntegration (559-625)
  • testNoTokenGateAddress (627-704)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (4)
contracts/deploy/utils/index.ts (1)
  • PNK (40-40)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (1)
  • encodeExtraData (71-72)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
contracts/deploy/utils/deployTokens.ts (2)
  • deployERC721 (55-67)
  • deployERC1155 (69-81)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
🔇 Additional comments (5)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (4)

1-51: LGTM: Well-structured type definitions.

The type definitions, interfaces, and imports are well-organized and comprehensive. The TokenGatedTestContext interface captures all necessary test state, and the DisputeKitGatedType union type correctly handles both gated variants.


108-162: Note the test limitation regarding EOA-created disputes.

The comment on line 150 correctly notes that the dispute "cannot be executed, in reality it should be created by an arbitrable contract, not an EOA." This is an acceptable test limitation, but it means these tests don't fully validate the end-to-end dispute execution flow.

Additionally, the hardcoded gas limits (lines 125, 130, 161) might be fragile across different EVM versions or network conditions. Consider using automatic gas estimation or documenting why specific limits are needed.


238-704: Comprehensive test coverage for token gating.

The test suites provide excellent coverage of token gating functionality:

  • Whitelist management (add/remove single/multiple tokens, duplicates)
  • Access control (owner-only operations)
  • Error handling for unsupported tokens
  • Token gating for ERC20, ERC721, and ERC1155
  • Whitelist state management across operations
  • Edge case handling for address(0) as "no token gate"

The tests are well-structured, clearly documented, and cover both positive and negative scenarios.


59-74: Token packing format matches Solidity unpacking. Verified that encodeExtraData’s solidityPacked(["uint88","bool","address"]) aligns with _extraDataToTokenInfo’s assembly logic in both DisputeKitGated.sol and DisputeKitGatedShutter.sol.

contracts/test/arbitration/dispute-kit-gated.ts (1)

1-43: Excellent refactoring to shared test helpers.

This test file demonstrates good software engineering practices:

  1. DRY principle: Test logic is centralized in the common helper file, eliminating duplication between DisputeKitGated and DisputeKitGatedShutter tests.

  2. Clear structure: The test file is easy to understand—it sets up context once and delegates to focused test suite functions.

  3. Maintainability: Changes to test logic only need to be made in one place (the common helper), while contract-specific behavior can still be tested separately if needed.

  4. Documentation: The JSDoc comment clearly explains what the test suite covers.

The context factory pattern (() => context) is used correctly to ensure each test gets fresh context via the beforeEach hook.

Comment on lines +202 to +207
const hre = require("hardhat");
await deployERC721(hre, deployer, "TestERC721", "Nft721");
const nft721 = await ethers.getContract<TestERC721>("Nft721");

await deployERC1155(hre, deployer, "TestERC1155", "Nft1155");
const nft1155 = await ethers.getContract<TestERC1155>("Nft1155");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace dynamic require with top-level import.

Using require("hardhat") at runtime (line 202) can cause module resolution issues and loses type safety. The hre object should be imported at the top of the file or passed as a parameter.

Apply this diff to import at the top:

-import { deployments, ethers, getNamedAccounts, network } from "hardhat";
+import { deployments, ethers, getNamedAccounts, network } from "hardhat";
+import type { HardhatRuntimeEnvironment } from "hardhat/types";

Then update the function signature and usage:

-export async function setupTokenGatedTest(config: TokenGatedTestConfig): Promise<TokenGatedTestContext> {
+export async function setupTokenGatedTest(
+  config: TokenGatedTestConfig,
+  hre: HardhatRuntimeEnvironment
+): Promise<TokenGatedTestContext> {
   const { deployer } = await getNamedAccounts();
   const [, juror1, juror2] = await ethers.getSigners();
   
   // ... existing code ...
   
-  const hre = require("hardhat");
   await deployERC721(hre, deployer, "TestERC721", "Nft721");

Note: This would require updating the call sites in the test files to pass hre.

🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-gated-common.ts around lines
202 to 207, replace the runtime require('hardhat') with a top-level import of
hre or accept hre as a function parameter: update the file to import { hre } (or
import HardhatRuntimeEnvironment as hre) at the top or modify the helper
function signature to take hre, then remove the inline require and use the
injected/imported hre variable when calling deployERC721/deployERC1155; finally
adjust all test call sites to pass the hre argument where the helper is invoked.

Comment on lines +232 to +234
// Whitelist all tokens by default to make existing tests work
await whitelistTokens(context, [dai.target, nft721.target, nft1155.target], true);

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

Clarify the comment about "existing tests".

The comment on line 232 states "Whitelist all tokens by default to make existing tests work," but this is a new file being introduced in this PR. This comment is confusing.

Either:

  1. Update the comment to explain why default whitelisting is beneficial for test setup, or
  2. Remove the default whitelisting if it's not actually needed for these tests

Suggested revision:

-  // Whitelist all tokens by default to make existing tests work
+  // Whitelist all tokens by default to simplify test setup (tests can explicitly remove tokens as needed)
   await whitelistTokens(context, [dai.target, nft721.target, nft1155.target], true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Whitelist all tokens by default to make existing tests work
await whitelistTokens(context, [dai.target, nft721.target, nft1155.target], true);
// Whitelist all tokens by default to simplify test setup (tests can explicitly remove tokens as needed)
await whitelistTokens(context, [dai.target, nft721.target, nft1155.target], true);
🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-gated-common.ts around lines
232-234, the comment "Whitelist all tokens by default to make existing tests
work" is confusing for a new file; either replace the comment with a clear
explanation of why default whitelisting is required for the test setup (e.g.,
which tests rely on tokens being whitelisted or that it prevents permission
errors during fixture setup) or remove the default whitelist call and the
comment if none of the tests here actually require whitelisted tokens; update
code accordingly and run the test suite to confirm behavior.

Comment on lines +391 to +404
it("Should draw only the jurors who have some DAI balance", async () => {
const ctx = context();
ctx.dai.transfer(ctx.juror1.address, 1);

const nbOfJurors = 15n;
const tx = await stakeAndDraw(
ctx,
Courts.GENERAL,
nbOfJurors,
ctx.gatedDisputeKitID,
ctx.dai.target,
false,
0
).then((tx) => tx.wait());
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing await on token transfer.

Line 393 is missing await on the ctx.dai.transfer() call. This means the transfer may not complete before stakeAndDraw is called, causing the test to fail or produce incorrect results.

Apply this diff:

     it("Should draw only the jurors who have some DAI balance", async () => {
       const ctx = context();
-      ctx.dai.transfer(ctx.juror1.address, 1);
+      await ctx.dai.transfer(ctx.juror1.address, 1);
 
       const nbOfJurors = 15n;

Note: Check for similar missing await statements throughout the file. I found additional instances at:

  • Line 456: await ctx.nft721.safeMint(ctx.juror2.address); (this one has await, good)
  • Line 519: await ctx.nft1155.mint(ctx.juror2.address, ctx.TOKEN_ID, 1, "0x00"); (this one has await, good)
  • Line 567: await ctx.dai.transfer(ctx.juror1.address, 1); (this one has await, good)
  • Line 589: await ctx.dai.transfer(ctx.juror1.address, 1); (this one has await, good)

So only line 393 is missing the await.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("Should draw only the jurors who have some DAI balance", async () => {
const ctx = context();
ctx.dai.transfer(ctx.juror1.address, 1);
const nbOfJurors = 15n;
const tx = await stakeAndDraw(
ctx,
Courts.GENERAL,
nbOfJurors,
ctx.gatedDisputeKitID,
ctx.dai.target,
false,
0
).then((tx) => tx.wait());
it("Should draw only the jurors who have some DAI balance", async () => {
const ctx = context();
await ctx.dai.transfer(ctx.juror1.address, 1);
const nbOfJurors = 15n;
const tx = await stakeAndDraw(
ctx,
Courts.GENERAL,
nbOfJurors,
ctx.gatedDisputeKitID,
ctx.dai.target,
false,
0
).then((tx) => tx.wait());
// …rest of test…
🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-gated-common.ts around lines
391 to 404, the call to ctx.dai.transfer(ctx.juror1.address, 1) (line 393) is
missing an await which can cause the transfer to not complete before
stakeAndDraw runs; add await before ctx.dai.transfer(...) so the test waits for
the transfer to finish, and scan the file for any other token transfer/mint
calls missing await (lines noted in the review are already awaited).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (7)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (1)

37-39: Nit: avoid async on describe callbacks.

Mocha ignores async return on describe; keep async on hooks/tests only.

contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (3)

74-81: Emit events for whitelist changes.

Governance actions should be observable. Add an event and emit per updated token.

Apply:

+    /// @notice Emitted when supported token status changes.
+    event SupportedTokenChanged(address indexed token, bool supported);
+
     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
             supportedTokens[_tokens[i]] = _supported;
+            emit SupportedTokenChanged(_tokens[i], _supported);
         }
     }

87-101: Consider enforcing access control first (minor).

Currently the whitelist check runs before super.createDispute’s onlyByCore. Calling super first would fail fast for unauthorized callers. Not critical since the pre-check is cheap and data is public anyway.


149-154: Harden balance checks to avoid revert on bad whitelist entries.

If a non-contract or non-compliant token is whitelisted, interface calls may revert and block draws. Prefer safe staticcall; treat failures as zero balance.

Example replacement (helper functions to add elsewhere in the contract):

function _hasERC20or721Balance(address token, address owner) internal view returns (bool) {
    (bool ok, bytes memory ret) = token.staticcall(abi.encodeWithSignature("balanceOf(address)", owner));
    if (!ok || ret.length < 32) return false;
    return abi.decode(ret, (uint256)) > 0;
}

function _hasERC1155Balance(address token, address owner, uint256 id) internal view returns (bool) {
    (bool ok, bytes memory ret) = token.staticcall(abi.encodeWithSignature("balanceOf(address,uint256)", owner, id));
    if (!ok || ret.length < 32) return false;
    return abi.decode(ret, (uint256)) > 0;
}

Then within this block:

-        if (isERC1155) {
-            return IBalanceHolderERC1155(tokenGate).balanceOf(_juror, tokenId) > 0;
-        } else {
-            return IBalanceHolder(tokenGate).balanceOf(_juror) > 0;
-        }
+        return isERC1155
+            ? _hasERC1155Balance(tokenGate, _juror, tokenId)
+            : _hasERC20or721Balance(tokenGate, _juror);
contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (2)

39-41: Emit events when whitelist changes for auditability

Mapping flips are silent. Emit an event per token to ease monitoring and off-chain indexing, and to support revert-free idempotent updates.

Example:

+    event SupportedTokenChanged(address indexed token, bool supported);

     function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
         for (uint256 i = 0; i < _tokens.length; i++) {
             supportedTokens[_tokens[i]] = _supported;
+            emit SupportedTokenChanged(_tokens[i], _supported);
         }
     }

Also applies to: 103-111


116-129: Access control check runs after gating logic

You rely on super.createDispute for onlyByCore. Minor: consider moving access-control earlier (e.g., replicate the modifier or a lightweight check) to fail fast on unauthorized callers, then run gating. Not critical.

Confirm base enforces onlyByCore and no state is mutated before the revert in this override.

contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1)

76-85: Normalize AddressLike instead of toString()

Using toString() on Addressable can yield non-address strings. Prefer AddressLike and ethers.getAddress for safety.

-export const whitelistTokens = async (
-  context: TokenGatedTestContext,
-  tokens: (string | Addressable)[],
-  supported: boolean = true
-) => {
-  const tokenAddresses = tokens.map((token) => (typeof token === "string" ? token : token.toString()));
+export const whitelistTokens = async (
+  context: TokenGatedTestContext,
+  tokens: readonly ethers.AddressLike[],
+  supported: boolean = true
+) => {
+  const tokenAddresses = tokens.map((t) => ethers.getAddress(t as string));
   return context.disputeKit.changeSupportedTokens(tokenAddresses, supported);
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcbe228 and cefa0cb.

📒 Files selected for processing (8)
  • contracts/src/arbitration/dispute-kits/DisputeKitGated.sol (6 hunks)
  • contracts/src/arbitration/dispute-kits/DisputeKitGatedShutter.sol (6 hunks)
  • contracts/src/test/DisputeKitGatedShutterMock.sol (1 hunks)
  • contracts/test/arbitration/dispute-kit-gated-shutter.ts (1 hunks)
  • contracts/test/arbitration/dispute-kit-gated.ts (1 hunks)
  • contracts/test/arbitration/dispute-kit-shutter.ts (1 hunks)
  • contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1 hunks)
  • contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
contracts/test/arbitration/dispute-kit-gated-shutter.ts (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (2)
  • TokenGatedTestContext (27-46)
  • setupTokenGatedTest (165-236)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (2)
  • ShutterTestContext (25-50)
  • setupShutterTest (206-325)
contracts/test/arbitration/dispute-kit-gated.ts (1)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (10)
  • TokenGatedTestContext (27-46)
  • setupTokenGatedTest (165-236)
  • testTokenWhitelistManagement (240-305)
  • testAccessControl (307-323)
  • testUnsupportedTokenErrors (325-368)
  • testERC20Gating (370-431)
  • testERC721Gating (433-494)
  • testERC1155Gating (496-557)
  • testWhitelistIntegration (559-625)
  • testNoTokenGateAddress (627-704)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (2)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (1)
  • encodeExtraData (59-74)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (4)
contracts/deploy/utils/index.ts (1)
  • PNK (40-40)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (1)
  • encodeExtraData (71-72)
contracts/deploy/utils/deployUpgradable.ts (1)
  • deployUpgradable (37-89)
contracts/deploy/utils/deployTokens.ts (2)
  • deployERC721 (55-67)
  • deployERC1155 (69-81)
contracts/test/arbitration/dispute-kit-shutter.ts (1)
contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (7)
  • ShutterTestContext (25-50)
  • setupShutterTest (206-325)
  • testCommitPhase (329-443)
  • testNormalFlowBotReveals (445-582)
  • testRecoveryFlowJurorReveals (584-730)
  • testHashFunctionBehavior (732-772)
  • testEdgeCasesAndSecurity (774-875)
⏰ 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-neo
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: hardhat-tests
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
🔇 Additional comments (13)
contracts/src/test/DisputeKitGatedShutterMock.sol (1)

10-14: Good: test-only wrapper for internal decoder.

Simple, pure passthrough is appropriate for tests.

contracts/test/arbitration/dispute-kit-shutter.ts (1)

32-37: Refactor to shared suites looks good.

Centralizing commit/reveal tests via common helpers reduces duplication.

contracts/test/arbitration/dispute-kit-gated-shutter.ts (2)

45-53: Token-gating shared tests wired correctly.

Good coverage across whitelist, access control, and gating variants.


67-73: Shutter shared tests wired correctly under gated context.

Nice reuse of the same suites with isGated=true.

contracts/test/arbitration/dispute-kit-gated.ts (1)

34-42: Shared gated tests integration looks good.

Concise orchestration; promotes reuse and clarity.

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

116-131: Docs match decoding layout; no update needed.

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

240-255: Decoder offsets and bit unpacking look correct

Length guard, offsets (0x80/0xA0 with bytes length slot accounted), and bit-160 flag extraction match the packed layout used in tests.


258-279: Gating check in _postDrawCheck is sound

NO_TOKEN_GATE shortcut and ERC20/721 vs ERC1155 balance checks are correct. Dependent on createDispute-time whitelist only, which is fine.

If whitelist may change mid‑dispute, confirm the intended behavior is to gate juror eligibility independent of later whitelist flips.


90-91: Allowing address(0) by default is sensible

Keeps non‑gated disputes possible. Intentional and matches tests.

contracts/test/arbitration/helpers/dispute-kit-gated-common.ts (2)

59-75: Encoder matches on-chain decoder

Packed [uint88,bool,address] into bytes32 and ABI-encodes at slot 4 with tokenId at slot 5; aligns with contract decoding.


232-236: Global default whitelist in setup is fine

Pre-whitelisting test tokens keeps legacy tests green. Consider asserting ZeroAddress remains supported if you rely on non-gated flows.

Add a quick check here: expectTokenSupported(ctx, ethers.ZeroAddress, true);

contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts (2)

70-73: Ungated encoder OK

ABI shape [uint256,uint256,uint256] matches on-chain expectations.


224-278: Setup path selection logic is consistent

Using the Mock for gated shutter and enabling hidden votes on General court is reasonable for tests.

Ensure Court IDs and DisputeKit IDs (1=Classic, 2=Shutter) match current Core deployment constants.

Comment on lines +109 to +162
export const stakeAndDraw = async (
context: TokenGatedTestContext,
courtId: number,
minJurors: BigNumberish,
disputeKitId: number,
tokenGate: string | Addressable,
isERC1155: boolean,
tokenId: BigNumberish
) => {
// Stake jurors
for (const juror of [context.juror1, context.juror2]) {
await context.pnk.transfer(juror.address, context.thousandPNK(10)).then((tx) => tx.wait());
expect(await context.pnk.balanceOf(juror.address)).to.equal(context.thousandPNK(10));

await context.pnk
.connect(juror)
.approve(context.core.target, context.thousandPNK(10), { gasLimit: 300000 })
.then((tx) => tx.wait());

await context.core
.connect(juror)
.setStake(Courts.GENERAL, context.thousandPNK(10), { gasLimit: 500000 })
.then((tx) => tx.wait());

expect(await context.sortitionModule.getJurorBalance(juror.address, 1)).to.deep.equal([
context.thousandPNK(10), // totalStaked
0, // totalLocked
context.thousandPNK(10), // stakedInCourt
1, // nbOfCourts
]);
}

const extraData = encodeExtraData(courtId, minJurors, disputeKitId, tokenGate, isERC1155, tokenId);

const tokenInfo = await context.disputeKit.extraDataToTokenInfo(extraData);
expect(tokenInfo[0]).to.equal(tokenGate);
expect(tokenInfo[1]).to.equal(isERC1155);
expect(tokenInfo[2]).to.equal(tokenId);

const arbitrationCost = await context.core["arbitrationCost(bytes)"](extraData);

// Warning: this dispute cannot be executed, in reality it should be created by an arbitrable contract, not an EOA.
const tx = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) =>
tx.wait()
);
const disputeId = 0;

await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
await network.provider.send("evm_mine");
await context.sortitionModule.passPhase().then((tx) => tx.wait()); // Staking -> Generating

await context.sortitionModule.passPhase().then((tx) => tx.wait()); // Generating -> Drawing
return context.core.draw(disputeId, 70, { gasLimit: 10000000 });
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Bug: disputeId is hardcoded to 0; parse it from the receipt

Subsequent tests creating multiple disputes will draw against the wrong dispute. Parse DisputeCreation from the createDispute receipt.

Apply this diff:

-  // Warning: this dispute cannot be executed, in reality it should be created by an arbitrable contract, not an EOA.
-  const tx = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) =>
-    tx.wait()
-  );
-  const disputeId = 0;
+  // Warning: this dispute cannot be executed, in reality it should be created by an arbitrable contract, not an EOA.
+  const receipt = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then(
+    (tx) => tx.wait()
+  );
+  const created = receipt?.logs.find(
+    (log: any) => log.fragment?.name === "DisputeCreation" && log.address === context.core.target
+  );
+  const disputeId = Number(created?.args[0]);

🏁 Script executed:

#!/bin/bash
# Verify the exact event name emitted by KlerosCore on dispute creation.
rg -nP -C2 --type=sol 'event\s+Dispute.*' contracts | sed -n '1,200p'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for DisputeCreation event definitions in Solidity contracts
rg -n 'event\s+DisputeCreation' -g '*.sol' -C3 contracts
rg -n 'DisputeCreation' -g '*.sol' -C3 contracts

Length of output: 11814


Bug: Hardcoded disputeId; extract it from the DisputeCreation event

Subsequent tests creating multiple disputes will draw against the wrong dispute. Replace the hardcoded 0 with the actual ID emitted by createDispute:

-  const receipt = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) =>
-    tx.wait()
-  );
-  const disputeId = 0;
+  const receipt = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) =>
+    tx.wait()
+  );
+  const event = receipt.events?.find((e) => e.event === "DisputeCreation" && e.address === context.core.target);
+  const disputeId = Number(event?.args?._disputeID ?? event?.args?.[0]);

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

🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-gated-common.ts around lines
109-162 the code uses a hardcoded disputeId = 0 after creating a dispute;
extract the actual disputeId from the receipt returned by createDispute instead.
After awaiting tx.wait() (the receipt), find the DisputeCreation event in
receipt.events and pull the dispute id from event.args (or event.args[0]),
convert it to a JS number or BigNumber as needed, and use that variable in the
subsequent draw call instead of the literal 0.

Comment on lines +91 to +147
export const createDisputeAndDraw = async (
context: ShutterTestContext,
courtId: BigNumberish,
minJurors: BigNumberish,
disputeKitId: number
) => {
// Stake jurors
for (const juror of [context.juror1, context.juror2]) {
await context.pnk.transfer(juror.address, context.thousandPNK(10)).then((tx) => tx.wait());
expect(await context.pnk.balanceOf(juror.address)).to.equal(context.thousandPNK(10));

await context.pnk
.connect(juror)
.approve(context.core.target, context.thousandPNK(10), { gasLimit: 300000 })
.then((tx) => tx.wait());

await context.core
.connect(juror)
.setStake(context.shutterCourtID, context.thousandPNK(10), { gasLimit: 500000 })
.then((tx) => tx.wait());

expect(await context.sortitionModule.getJurorBalance(juror.address, context.shutterCourtID)).to.deep.equal([
context.thousandPNK(10), // totalStaked
0, // totalLocked
context.thousandPNK(10), // stakedInCourt
1, // nbOfCourts
]);

// If gated, give tokens to jurors
if (context.isGated && context.dai) {
await context.dai.transfer(juror.address, 1);
}
}

// Use gated extra data if this is a gated dispute kit with DAI token
let extraData: string;
if (context.isGated && context.dai) {
extraData = encodeGatedExtraData(courtId, minJurors, disputeKitId, context.dai.target, false, 0);
} else {
extraData = encodeExtraData(courtId, minJurors, disputeKitId);
}

const arbitrationCost = await context.core["arbitrationCost(bytes)"](extraData);

// Create dispute via core contract
await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait());
const disputeId = 0;

await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
await network.provider.send("evm_mine");
await context.sortitionModule.passPhase().then((tx) => tx.wait()); // Staking -> Generating

await context.sortitionModule.passPhase().then((tx) => tx.wait()); // Generating -> Drawing
await context.core.draw(disputeId, 70, { gasLimit: 10000000 });

return disputeId;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Bug: disputeId is hardcoded to 0; parse it from the receipt

Same issue as gated helper; breaks when multiple disputes exist in one context.

Apply this diff:

-  // Create dispute via core contract
-  await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait());
-  const disputeId = 0;
+  // Create dispute via core contract and fetch disputeId from the receipt
+  const receipt = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then(
+    (tx) => tx.wait()
+  );
+  const created = receipt?.logs.find(
+    (log: any) => log.fragment?.name === "DisputeCreation" && log.address === context.core.target
+  );
+  const disputeId = Number(created?.args[0]);

🏁 Script executed:

#!/bin/bash
# Confirm the event name and args order to decode disputeId reliably.
rg -nP -C3 --type=sol 'event\s+DisputeCreation\(|event\s+DisputeCreated\(' contracts/src | sed -n '1,200p'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for DisputeCreation or DisputeCreated event in solidity contracts
rg -nP -C3 'event\s+(DisputeCreation|DisputeCreated)\b' -g '*.sol'

Length of output: 2536


🏁 Script executed:

#!/bin/bash
# Locate createDisputeAndDraw in helpers and how disputeId is handled elsewhere
rg -nP -C3 'createDisputeAndDraw' -g 'contracts/test/arbitration/helpers/*.ts'
rg -nP -C3 'const\s+receipt' -g 'contracts/test/arbitration/helpers/*.ts'

Length of output: 15828


Extract disputeId from event instead of hardcoded 0
The helper currently sets disputeId = 0, which breaks when more than one dispute exists. Parse it from the DisputeCreation event in the transaction receipt.

-  // Create dispute via core contract
-  await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost }).then((tx) => tx.wait());
-  const disputeId = 0;
+  // Create dispute via core contract and fetch disputeId from the receipt
+  const receipt = await context.core["createDispute(uint256,bytes)"](2, extraData, { value: arbitrationCost })
+    .then((tx) => tx.wait());
+  const createdLog = receipt.logs.find(
+    (log: any) =>
+      log.fragment?.name === "DisputeCreation" && log.address === context.core.target
+  );
+  const disputeId = Number(createdLog?.args[0]);

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

🤖 Prompt for AI Agents
In contracts/test/arbitration/helpers/dispute-kit-shutter-common.ts around lines
91 to 147, the helper currently hardcodes disputeId = 0; instead capture the
transaction returned by context.core["createDispute(uint256,bytes)"](...) await
its .wait() receipt, find the DisputeCreation event in receipt.events (or filter
by event name), extract the disputeId argument from the event (convert from
BigNumber if needed) and assign that value to disputeId; replace the hardcoded 0
with this extracted value so the helper works when multiple disputes exist.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Token-gated Dispute kit and malicious tokens

2 participants