Skip to content

feat: subgraph support for shutter disputekit in devnet #1966

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feat/shutter-dispute-kit
Choose a base branch
from

Conversation

kemuru
Copy link
Contributor

@kemuru kemuru commented May 6, 2025

you can test this here: https://api.studio.thegraph.com/query/108432/kleros-v2-devnet/version/latest/graphql?que[…]pute+%7B%0A++++++++id%0A++++++%7D%0A++++%7D%0A++%7D%0A%7D

This only supports Devnet.

For Testnet support: would need a script adjustment to tweak the DisputeKitShutter address and deployment block in the subgraph.yaml file (since Devnet and Testnet share the same subgraph.yaml file)

For Neo support: it would be just adding the DisputeKitShutter address and deployment block in its subgraph.yaml file, since that file is independent from Devnet and Testnet so doesn't need script adjustment, add it like:

  - kind: ethereum
    name: DisputeKitShutter
    network: arbitrum-sepolia
    source:
      address: "0x..."
      abi: DisputeKitShutter
      startBlock: 99999999
    mapping:
      kind: ethereum/events
      ...

PR-Codex overview

This PR updates the @kleros/kleros-v2-subgraph package version and enhances the handling of dispute kits in various functions, allowing support for multiple dispute kits. It introduces a new utility function and modifies existing functions to accommodate the new structure.

Detailed summary

  • Updated version in subgraph/package.json from 0.15.2 to 0.16.0.
  • Modified comment in KlerosCore.ts to reflect support for ClassicDisputeKit and ShutterDisputeKit.
  • Added extractDisputeKitIDFromExtraData function in utils.ts.
  • Updated createClassicDisputeFromEvent to accept disputeKitID.
  • Adjusted ensureClassicContributionFromEvent to load Dispute and Round entities, modifying the roundID format.
  • Added disputeKitID parameter to createClassicRound.
  • Enhanced handleDisputeCreation to extract disputeKitID and pass it to functions.
  • Updated handleCommitCast, handleVoteCast, and handleChoiceFunded to use disputeKitID when loading disputes and rounds.
  • Modified handleWithdrawal to accommodate the new disputeKitID structure.

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

Summary by CodeRabbit

  • New Features

    • Added support for the Shutter Dispute Kit on the arbitrum-sepolia network, enabling tracking of disputes, rounds, votes, and contributions for this new contract.
  • Bug Fixes

    • Improved safety with additional checks to prevent issues when required entities are missing.
  • Refactor

    • Switched from using a fixed dispute kit ID to dynamically determining the dispute kit ID based on event data, ensuring compatibility with multiple dispute kits.
  • Documentation

    • Updated comments to clarify support for both Classic and Shutter dispute kits.
  • Chores

    • Updated the package version to 0.16.0.

@kemuru kemuru requested a review from a team as a code owner May 6, 2025 22:14
Copy link
Contributor

coderabbitai bot commented May 6, 2025

Walkthrough

The changes introduce dynamic handling of dispute kit IDs throughout the subgraph code, replacing hardcoded values. Entity ID construction and event processing now extract the dispute kit ID from event data or related entities. A new data source for the Shutter dispute kit is added, and relevant function signatures are updated for flexibility and safety.

Changes

Files/Paths Change Summary
subgraph/core/src/DisputeKitClassic.ts, subgraph/core/src/entities/ClassicDispute.ts, subgraph/core/src/entities/ClassicRound.ts, subgraph/core/src/entities/ClassicContribution.ts Replaced hardcoded dispute kit ID with dynamic extraction from event data or entities; updated entity ID construction and function signatures to accept/use dynamic dispute kit IDs; added null checks for entity existence in handlers.
subgraph/core/src/utils.ts Added utility function extractDisputeKitIDFromExtraData to extract dispute kit ID from event extra data bytes.
subgraph/core/subgraph.yaml Added new data source for DisputeKitShutter on arbitrum-sepolia with event handlers mirroring DisputeKitClassic, enabling support for multiple dispute kits.
subgraph/core/src/KlerosCore.ts Updated comment to clarify that ClassicVote applies to both Classic and Shutter dispute kits; no logic changes.
subgraph/package.json Bumped package version from 0.15.2 to 0.16.0.

Sequence Diagram(s)

sequenceDiagram
    participant Event
    participant Utils
    participant Dispute
    participant Round
    participant ClassicDispute
    participant ClassicRound

    Event->>Utils: extractDisputeKitIDFromExtraData(_extraData)
    Utils-->>Event: disputeKitID
    Event->>ClassicDispute: createClassicDisputeFromEvent(event, disputeKitID)
    ClassicDispute->>ClassicDispute: Construct ID using disputeKitID
    Event->>ClassicRound: createClassicRound(disputeID, choices, roundIndex, disputeKitID)
    ClassicRound->>ClassicRound: Construct ID using disputeKitID
    Event->>Dispute: Load Dispute by coreDisputeID
    Dispute->>Round: Load Round by disputeID and roundIndex
    Round-->>Event: disputeKitID for further entity ID construction
Loading

Possibly related issues

Suggested labels

Type: Feature🗿, Package: Subgraph

Suggested reviewers

  • alcercu
  • jaybuidl

Poem

In the warren where subgraphs grow,
IDs once fixed now flex and flow.
Classic and Shutter, side by side,
Disputes and rounds with dynamic pride.
No more static, now they roam,
Rabbits cheer, “Multi-kit support is home!”
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@kemuru kemuru linked an issue May 6, 2025 that may be closed by this pull request
Copy link

sonarqubecloud bot commented May 6, 2025

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 (4)
subgraph/core/src/entities/ClassicRound.ts (2)

6-13: Missing JSDoc / comments after signature change

createClassicRound now accepts disputeKitID, but the function header or surrounding docs were not updated. A brief comment clarifying the ID format ( <disputeKitID>-<coreDisputeID>-<roundIndex> ) will save future readers time.


85-97: Persist classicRound after answer update only once

Inside updateChoiceFundingFromContributionEvent you answer.save() and then classicRound.save() immediately after.
Because classicRound isn’t modified in this function, the latter save() is unnecessary and costs an extra store write.

-  answer.save();
-  classicRound.save();
+  answer.save();  // classicRound untouched
subgraph/core/src/DisputeKitClassic.ts (2)

34-38: Consider caching the extracted dispute-kit ID

extractDisputeKitIDFromExtraData is inexpensive, yet the value never changes for this dispute.
Persisting it once in the Dispute or ClassicDispute entity (e.g., disputeKit field) would let subsequent handlers fetch it directly instead of re-computing / re-loading Round each time, reducing store look-ups.


41-50: Repeated pattern → extract helper

The sequence
load Dispute → load current Round → read disputeKit is duplicated in several handlers (handleCommitCast, handleVoteCast, handleChoiceFunded, handleWithdrawal). Factor it into a small utility such as:

export function getDisputeKitID(coreDisputeID: string): string | null { ... }

to improve readability and reduce the chance of future inconsistencies.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ee2ceb6 and 317aed6.

📒 Files selected for processing (8)
  • subgraph/core/src/DisputeKitClassic.ts (8 hunks)
  • subgraph/core/src/KlerosCore.ts (1 hunks)
  • subgraph/core/src/entities/ClassicContribution.ts (1 hunks)
  • subgraph/core/src/entities/ClassicDispute.ts (1 hunks)
  • subgraph/core/src/entities/ClassicRound.ts (2 hunks)
  • subgraph/core/src/utils.ts (1 hunks)
  • subgraph/core/subgraph.yaml (1 hunks)
  • subgraph/package.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
subgraph/core/src/DisputeKitClassic.ts (4)
subgraph/core/src/KlerosCore.ts (1)
  • handleDisputeCreation (77-97)
subgraph/core/src/utils.ts (3)
  • extractDisputeKitIDFromExtraData (6-10)
  • ZERO (3-3)
  • ONE (4-4)
subgraph/core/src/entities/ClassicDispute.ts (1)
  • createClassicDisputeFromEvent (6-15)
subgraph/core/src/entities/ClassicRound.ts (1)
  • createClassicRound (6-25)
🪛 Biome (1.9.4)
subgraph/core/src/utils.ts

[error] 1-1: Do not shadow the global "BigInt" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🔇 Additional comments (8)
subgraph/core/src/utils.ts (2)

1-1: Biome static analysis issue can be safely ignored.

The static analysis reports a potential issue with shadowing the global BigInt property, but this is a standard and necessary pattern in The Graph's AssemblyScript SDK. This import is correct and follows the standard practices.

🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Do not shadow the global "BigInt" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


6-10: Solid utility function for extracting dispute kit IDs.

The function correctly extracts the dispute kit ID by:

  1. Getting the last 32 bytes from the extraData
  2. Reversing the byte order (little-endian to big-endian)
  3. Converting to a BigInt and returning as string

This approach enables dynamic handling of dispute kit IDs throughout the subgraph.

subgraph/package.json (1)

3-3: Version bump is appropriate for this feature addition.

The version increment from 0.15.2 to 0.16.0 follows semantic versioning principles, correctly indicating a new feature (adding support for ShutterDisputeKit).

subgraph/core/src/KlerosCore.ts (1)

159-159: Updated comment accurately reflects expanded dispute kit support.

The comment now correctly indicates that the ClassicVote entity works for both the ClassicDisputeKit and the new ShutterDisputeKit, aligning with the PR's goal of supporting multiple dispute kits.

subgraph/core/src/entities/ClassicDispute.ts (2)

1-1: Added necessary Bytes import.

The import of Bytes is now required since this file interacts with the new utility function that processes byte data.


6-8: Well-implemented dynamic dispute kit ID handling.

The function signature now accepts a disputeKitID parameter and properly uses it to construct the ClassicDispute entity ID, replacing the previously hardcoded ID prefix. This change enables consistent entity identification across multiple dispute kits.

subgraph/core/src/entities/ClassicRound.ts (1)

74-86:

❌ Incorrect review comment

Null-check disputeKitID before using it

Same concern as in ClassicContribution: if coreRound.disputeKit is null the derived roundID is malformed.

-  const disputeKitID = coreRound.disputeKit;
+  const disputeKitID = coreRound.disputeKit;
+  if (!disputeKitID) return;   // abort – underlying data is incomplete

Here’s some targeted searching to confirm whether the disputeKit field on Round can actually be null (and whether a guard is needed):


🏁 Script executed:

#!/bin/bash
# 1) Check GraphQL schema for Round entity and disputeKit nullability
rg "type Round" -n -g "*.graphql"
rg "disputeKit" -n -g "*.graphql"

# 2) Inspect the generated TS entity for Round to see how disputeKit is typed
rg "class Round" -n
rg "disputeKit" -C3 -n

Length of output: 96599


DisputeKitID Is Always Defined – No Guard Needed

Based on schema.graphql and the generated Round entity, disputeKit is non-nullable:

  • In schema.graphql:
    type Round @entity { … disputeKit: DisputeKit! … }
  • In subgraph/core/src/entities/Round.ts:
    round.disputeKit = roundInfo.disputeKitID.toString();
    It’s always set before the entity is saved, and the generated AssemblyScript get disputeKit(): string cannot return null.

The same pattern is used throughout (e.g. in ClassicContribution.ts) without issue. You can safely omit the additional if (!disputeKitID) return; check here.

Likely an incorrect or invalid review comment.

subgraph/core/subgraph.yaml (1)

123-158: Validate ABI/event signature parity between Classic & Shutter

DisputeKitShutter re-uses the DisputeKitClassic.ts handlers. This will only work if every event signature in the Shutter ABI exactly matches the Classic ABI.

Please double-check that:

  1. The ABI file at ../../contracts/deployments/arbitrumSepoliaDevnet/DisputeKitShutter.json contains all six events with identical parameter ordering/types.
  2. The startBlock (148 194 178) predates the first emitted event; otherwise initial events will be missed.

A quick grep / diff against the Classic ABI before deploying would prevent runtime failures.

Comment on lines +10 to +20
const coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return null;

const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return null;

const disputeKitID = coreRound.disputeKit;

const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Guard against a missing disputeKit reference

coreRound.disputeKit is assumed to be present and non-empty. If the Round entity was created before the refactor (or gets corrupted), this will evaluate to null and the constructed roundID becomes "null-<coreDisputeID>-<coreRoundIndex>", causing silent data fragmentation.

-  const disputeKitID = coreRound.disputeKit;
+  const disputeKitID = coreRound.disputeKit;
+  if (!disputeKitID) {
+    // Inconsistent data – abort processing to avoid polluting the store
+    return null;
+  }

Fail-fast avoids persisting wrongly-keyed entities and makes the issue visible during indexing.


🏁 Script executed:

#!/bin/bash
echo "=== GraphQL schema files ==="
find . -type f -name "*.graphql"

echo ""
echo "=== 'type Round' and 'disputeKit' in GraphQL schemas ==="
rg -n "type Round" -C2 -g "*.graphql" || true
rg -n "disputeKit" -C2 -g "*.graphql" || true

echo ""
echo "=== Locate and inspect Round entity class ==="
round_files=$(rg -l "export class Round" -g "*.ts")
echo "Found Round.ts files:"
echo "$round_files"

for file in $round_files; do
  echo ""
  echo "------ $file ------"
  rg -n "export class Round" -C3 "$file"
  rg -n "get disputeKit" -C5 "$file" || true
done

Length of output: 3678


Guard against a missing disputeKit reference

The GraphQL schema defines Round.disputeKit as non-nullable, but legacy or corrupted data may still omit it. Add a null check immediately after loading the Round to avoid constructing IDs like "null-<coreDisputeID>-<coreRoundIndex>" and silently polluting the store.

• File: subgraph/core/src/entities/ClassicContribution.ts
• Context: right after assigning disputeKitID

-  const disputeKitID = coreRound.disputeKit;
+  const disputeKitID = coreRound.disputeKit;
+  if (!disputeKitID) {
+    // Inconsistent data – abort processing to avoid polluting the store
+    return null;
+  }

This fail-fast guard ensures any missing disputeKit is surfaced during indexing rather than resulting in fragmented or mis-keyed entities.

📝 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 coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return null;
const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return null;
const disputeKitID = coreRound.disputeKit;
const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;
const coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return null;
const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return null;
const disputeKitID = coreRound.disputeKit;
if (!disputeKitID) {
// Inconsistent data – abort processing to avoid polluting the store
return null;
}
const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;

Comment on lines +184 to +192
const coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return;

const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return;
const disputeKitID = coreRound.disputeKit;

const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;
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

Defensive check for disputeKitID

Replicating the guard here avoids malformed IDs during withdrawals.

  const disputeKitID = coreRound.disputeKit;
-  
+  if (!disputeKitID) return;
📝 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 coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return;
const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return;
const disputeKitID = coreRound.disputeKit;
const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;
const coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return;
const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return;
const disputeKitID = coreRound.disputeKit;
if (!disputeKitID) return;
const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;

Comment on lines +128 to +136
const coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return;

const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return;
const disputeKitID = coreRound.disputeKit;

const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;
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

Early exit when disputeKitID missing

Same defensive check as earlier – without it, roundID may start with "null-".

  const disputeKitID = coreRound.disputeKit;
-  
+  if (!disputeKitID) return;
📝 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 coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return;
const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return;
const disputeKitID = coreRound.disputeKit;
const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;
const coreDispute = Dispute.load(coreDisputeID);
if (!coreDispute) return;
const roundId = `${coreDisputeID}-${coreRoundIndex}`;
const coreRound = Round.load(roundId);
if (!coreRound) return;
const disputeKitID = coreRound.disputeKit;
if (!disputeKitID) return;
const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Subgraph: support for Classic-like Dispute Kits at id > 1
1 participant