Skip to content

Release #2012

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

Draft
wants to merge 28 commits into
base: master
Choose a base branch
from
Draft

Release #2012

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1ff55f8
docs: natspec for KlerosCore view functions
jaybuidl May 14, 2025
036e897
chore: style and button changes
kemuru May 19, 2025
f20c9d4
chore: remove unused code
kemuru May 19, 2025
54d83a7
fix: avoid unnecessary calls draw() when no juror is available, requi…
jaybuidl May 19, 2025
9e8aa9e
fix: reinitializer and version for KlerosCore
jaybuidl May 19, 2025
7a5466b
chore: retrigger deploy preview
kemuru May 20, 2025
1ca9066
Merge pull request #2001 from kleros/chore(web)/style-and-button-changes
alcercu May 20, 2025
29ef9a1
Fix: Update isCurrentRound for previous round on appeal
google-labs-jules[bot] May 22, 2025
3925b6f
Fix: Update isCurrentRound for previous round on appeal
google-labs-jules[bot] May 23, 2025
ad47783
chore: timeline clarify remaining time
kemuru May 26, 2025
349783f
Merge pull request #2009 from kleros/chore/timeline-clarify-remaining…
alcercu May 26, 2025
f370669
fix(subgraph): handle-batched-disputes-request-events
tractorss May 26, 2025
3f532d2
Merge pull request #2010 from kleros/fix/subgraph-batch-dispute-handling
jaybuidl May 27, 2025
20ee602
fix: bug in color loading text in file viewer
kemuru May 27, 2025
282b91d
Merge pull request #2011 from kleros/fix(web)/color-text-loading-bug-…
alcercu May 28, 2025
411b384
Merge branch 'dev' into fix/update-iscurrentround
jaybuidl May 28, 2025
367549c
chore: subgraph version bump
jaybuidl May 28, 2025
30382a0
Merge pull request #2007 from kleros/fix/update-iscurrentround
jaybuidl May 28, 2025
e5cf1df
Merge pull request #2002 from kleros/fix/keeper-bot-useless-draws
jaybuidl May 28, 2025
e7a0759
fix: allow return in case evidence was opened in new tab
kemuru May 28, 2025
dde0dc7
fix: few more links handling
kemuru May 28, 2025
4e95c72
Merge pull request #2013 from kleros/fix/file-viewer-return-button-link
alcercu May 28, 2025
2b51d5e
chore: find-initializer-versions utility
jaybuidl May 28, 2025
4adedb5
chore: find-initializer-versions utility
jaybuidl May 28, 2025
47aaca4
feat: new overview design
kemuru Jun 10, 2025
78508f5
feat: add card labels to the overview
kemuru Jun 11, 2025
f5f2530
chore: tweak
kemuru Jun 11, 2025
0b75799
Merge pull request #2022 from kleros/feat/new-overview-design
alcercu Jun 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/deploy/upgrade-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const deployUpgradeAll: DeployFunction = async (hre: HardhatRuntimeEnvironment)
await upgrade(disputeKitClassic, "initialize6", []);
await upgrade(disputeTemplateRegistry, "initialize2", []);
await upgrade(evidence, "initialize2", []);
await upgrade(core, "initialize4", []);
await upgrade(core, "initialize5", []);
await upgrade(policyRegistry, "initialize2", []);
await upgrade(sortition, "initialize3", []);
};
Expand Down
27 changes: 27 additions & 0 deletions contracts/scripts/find-initializer-versions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#! /usr/bin/env bash

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

declare -A rpcUrls
rpcUrls["arbitrum"]=$(mesc url arbitrum_alchemy)
rpcUrls["arbitrumSepolia"]=$(mesc url arbitrumSepolia_alchemy)
rpcUrls["arbitrumSepoliaDevnet"]=$(mesc url arbitrumSepolia_alchemy)

# event Initialized(uint64 version);
eventTopic=0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2

for c in arbitrum arbitrumSepolia arbitrumSepoliaDevnet; do
echo "--------------------------------"
echo "$c"
echo "--------------------------------"
for f in "$SCRIPT_DIR"/../deployments/"$c"/*_Proxy.json; do
address=$(jq -r .address "$f")
block=$(jq -r .receipt.blockNumber "$f")
basename "$f"
results=$(cast logs --from-block "$block" --to-block latest $eventTopic --address "$address" --rpc-url "${rpcUrls[$c]}" --json | jq -r .[].data)
initializer=$(cast --to-dec "$(echo "$results" | tail -n1)")
version=$(cast call --rpc-url "${rpcUrls[$c]}" "$address" "version()(string)" --json 2>/dev/null | jq -r '.[0]')
echo "$initializer" @v"$version"
echo
done
done
15 changes: 14 additions & 1 deletion contracts/scripts/keeperBot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Cores, getContracts as getContractsForCoreType } from "./utils/contract

let request: <T>(url: string, query: string) => Promise<T>; // Workaround graphql-request ESM import
const { ethers } = hre;
const MAX_DRAW_CALLS_WITHOUT_JURORS = 10;
const MAX_DRAW_ITERATIONS = 30;
const MAX_EXECUTE_ITERATIONS = 20;
const MAX_DELAYED_STAKES_ITERATIONS = 50;
Expand Down Expand Up @@ -248,7 +249,19 @@ const drawJurors = async (dispute: { id: string; currentRoundIndex: string }, it
const { core } = await getContracts();
let success = false;
try {
await core.draw.staticCall(dispute.id, iterations, HIGH_GAS_LIMIT);
const simulatedIterations = iterations * MAX_DRAW_CALLS_WITHOUT_JURORS; // Drawing will be skipped as long as no juror is available in the next MAX_DRAW_CALLS_WITHOUT_JURORS calls to draw() given this nb of iterations.
const { drawnJurors: drawnJurorsBefore } = await core.getRoundInfo(dispute.id, dispute.currentRoundIndex);
const nbDrawnJurors = (await core.draw.staticCall(dispute.id, simulatedIterations, HIGH_GAS_LIMIT)) as bigint;
const extraJurors = nbDrawnJurors - BigInt(drawnJurorsBefore.length);
logger.debug(
`Draw: ${extraJurors} jurors available in the next ${simulatedIterations} iterations for dispute ${dispute.id}`
);
if (extraJurors <= 0n) {
logger.warn(
`Draw: skipping, no jurors available in the next ${simulatedIterations} iterations for dispute ${dispute.id}`
);
return success;
}
} catch (e) {
logger.error(`Draw: will fail for ${dispute.id}, skipping`);
return success;
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/arbitration/KlerosCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {KlerosCoreBase, IDisputeKit, ISortitionModule, IERC20} from "./KlerosCor
/// Core arbitrator contract for Kleros v2.
/// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts.
contract KlerosCore is KlerosCoreBase {
string public constant override version = "0.9.3";
string public constant override version = "0.9.4";

// ************************************* //
// * Constructor * //
Expand Down Expand Up @@ -56,7 +56,7 @@ contract KlerosCore is KlerosCoreBase {
);
}

function initialize4() external reinitializer(4) {
function initialize5() external reinitializer(5) {
// NOP
}

Expand Down
20 changes: 19 additions & 1 deletion contracts/src/arbitration/KlerosCoreBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,8 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
/// @dev Draws jurors for the dispute. Can be called in parts.
/// @param _disputeID The ID of the dispute.
/// @param _iterations The number of iterations to run.
function draw(uint256 _disputeID, uint256 _iterations) external {
/// @return nbDrawnJurors The total number of jurors drawn in the round.
function draw(uint256 _disputeID, uint256 _iterations) external returns (uint256 nbDrawnJurors) {
Dispute storage dispute = disputes[_disputeID];
uint256 currentRound = dispute.rounds.length - 1;
Round storage round = dispute.rounds[currentRound];
Expand All @@ -616,6 +617,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
}
}
round.drawIterations += i;
return round.drawnJurors.length;
}

/// @dev Appeals the ruling of a specified dispute.
Expand Down Expand Up @@ -981,18 +983,34 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
(ruling, tied, overridden) = disputeKit.currentRuling(_disputeID);
}

/// @dev Gets the round info for a specified dispute and round.
/// @dev This function must not be called from a non-view function because it returns a dynamic array which might be very large, theoretically exceeding the block gas limit.
/// @param _disputeID The ID of the dispute.
/// @param _round The round to get the info for.
/// @return round The round info.
function getRoundInfo(uint256 _disputeID, uint256 _round) external view returns (Round memory) {
return disputes[_disputeID].rounds[_round];
}
Comment on lines +986 to 993
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

Add bounds checking for array access.

The function directly accesses disputes[_disputeID].rounds[_round] without validating that the indices are within bounds. This could cause reverts with unhelpful error messages when called with invalid parameters.

Add input validation to provide better error handling:

 function getRoundInfo(uint256 _disputeID, uint256 _round) external view returns (Round memory) {
+    if (_disputeID >= disputes.length) revert InvalidDisputeID();
+    if (_round >= disputes[_disputeID].rounds.length) revert InvalidRoundNumber();
     return disputes[_disputeID].rounds[_round];
 }

You'll need to add the corresponding custom error declarations:

error InvalidDisputeID();
error InvalidRoundNumber();
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 984 to 991, the
function getRoundInfo accesses disputes[_disputeID].rounds[_round] without
checking if _disputeID and _round are valid indices, which can cause unhelpful
reverts. Fix this by adding input validation: first check if _disputeID is less
than the length of the disputes array and revert with a custom error
InvalidDisputeID() if not; then check if _round is less than the length of
disputes[_disputeID].rounds and revert with InvalidRoundNumber() if invalid.
Declare these custom errors in the contract as specified.


/// @dev Gets the PNK at stake per juror for a specified dispute and round.
/// @param _disputeID The ID of the dispute.
/// @param _round The round to get the info for.
/// @return pnkAtStakePerJuror The PNK at stake per juror.
function getPnkAtStakePerJuror(uint256 _disputeID, uint256 _round) external view returns (uint256) {
return disputes[_disputeID].rounds[_round].pnkAtStakePerJuror;
}
Comment on lines 999 to 1001
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

Add bounds checking for array access.

Similar to getRoundInfo, this function lacks input validation for _disputeID and _round parameters.

Apply this fix:

 function getPnkAtStakePerJuror(uint256 _disputeID, uint256 _round) external view returns (uint256) {
+    if (_disputeID >= disputes.length) revert InvalidDisputeID();
+    if (_round >= disputes[_disputeID].rounds.length) revert InvalidRoundNumber();
     return disputes[_disputeID].rounds[_round].pnkAtStakePerJuror;
 }
📝 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
function getPnkAtStakePerJuror(uint256 _disputeID, uint256 _round) external view returns (uint256) {
return disputes[_disputeID].rounds[_round].pnkAtStakePerJuror;
}
function getPnkAtStakePerJuror(uint256 _disputeID, uint256 _round) external view returns (uint256) {
if (_disputeID >= disputes.length) revert InvalidDisputeID();
if (_round >= disputes[_disputeID].rounds.length) revert InvalidRoundNumber();
return disputes[_disputeID].rounds[_round].pnkAtStakePerJuror;
}
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 997 to 999, the
function getPnkAtStakePerJuror accesses the disputes and rounds arrays without
validating that _disputeID and _round are within valid bounds. Add checks to
ensure _disputeID is less than the length of disputes and _round is less than
the length of rounds for the specified dispute before accessing the arrays to
prevent out-of-bounds errors.


/// @dev Gets the number of rounds for a specified dispute.
/// @param _disputeID The ID of the dispute.
/// @return The number of rounds.
function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
return disputes[_disputeID].rounds.length;
}
Comment on lines 1006 to 1008
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

Add bounds checking for dispute ID.

The function should validate the _disputeID parameter before accessing the disputes array.

Apply this fix:

 function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
+    if (_disputeID >= disputes.length) revert InvalidDisputeID();
     return disputes[_disputeID].rounds.length;
 }
📝 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
function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
return disputes[_disputeID].rounds.length;
}
function getNumberOfRounds(uint256 _disputeID) external view returns (uint256) {
if (_disputeID >= disputes.length) revert InvalidDisputeID();
return disputes[_disputeID].rounds.length;
}
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 1004 to 1006, the
function getNumberOfRounds lacks validation for the _disputeID parameter before
accessing the disputes array. Add a check to ensure _disputeID is within valid
bounds (e.g., less than the length of the disputes array) and revert or handle
the error if it is out of range to prevent invalid access.


/// @dev Checks if a given dispute kit is supported by a given court.
/// @param _courtID The ID of the court to check the support for.
/// @param _disputeKitID The ID of the dispute kit to check the support for.
/// @return Whether the dispute kit is supported or not.
function isSupported(uint96 _courtID, uint256 _disputeKitID) external view returns (bool) {
return courts[_courtID].supportedDisputeKits[_disputeKitID];
}
Comment on lines 1014 to 1016
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

Add bounds checking for court ID.

The function should validate the _courtID parameter before accessing the courts array.

Apply this fix:

 function isSupported(uint96 _courtID, uint256 _disputeKitID) external view returns (bool) {
+    if (_courtID >= courts.length) revert InvalidCourtID();
     return courts[_courtID].supportedDisputeKits[_disputeKitID];
 }

Add the corresponding custom error declaration:

error InvalidCourtID();
🤖 Prompt for AI Agents
In contracts/src/arbitration/KlerosCoreBase.sol around lines 1012 to 1014, the
function isSupported accesses the courts mapping with _courtID without
validating if the court ID is valid. To fix this, add a check at the start of
the function to verify that _courtID is within the valid range of courts. If it
is not, revert with the custom error InvalidCourtID(). Also, declare the custom
error InvalidCourtID() in the contract as specified.

Expand Down
4 changes: 2 additions & 2 deletions contracts/src/arbitration/KlerosCoreNeo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
/// Core arbitrator contract for Kleros v2.
/// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts.
contract KlerosCoreNeo is KlerosCoreBase {
string public constant override version = "0.8.0";
string public constant override version = "0.9.4";

// ************************************* //
// * Storage * //
Expand Down Expand Up @@ -67,7 +67,7 @@ contract KlerosCoreNeo is KlerosCoreBase {
jurorNft = _jurorNft;
}

function initialize4() external reinitializer(4) {
function initialize5() external reinitializer(5) {
// NOP
}

Expand Down
13 changes: 11 additions & 2 deletions subgraph/core/src/KlerosCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,19 @@ export function handleAppealDecision(event: AppealDecision): void {
const disputeID = event.params._disputeID;
const dispute = Dispute.load(disputeID.toString());
if (!dispute) return;

// Load the current (previous) round
const previousRoundID = dispute.currentRound;
const previousRound = Round.load(previousRoundID);
if (previousRound) {
previousRound.isCurrentRound = false;
previousRound.save();
}

const newRoundIndex = dispute.currentRoundIndex.plus(ONE);
const roundID = `${disputeID}-${newRoundIndex.toString()}`;
const newRoundID = `${disputeID}-${newRoundIndex.toString()}`;
dispute.currentRoundIndex = newRoundIndex;
dispute.currentRound = roundID;
dispute.currentRound = newRoundID;
dispute.save();
const roundInfo = contract.getRoundInfo(disputeID, newRoundIndex);

Expand Down
30 changes: 28 additions & 2 deletions subgraph/core/src/entities/Dispute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,36 @@ export const updateDisputeRequestData = (event: DisputeCreation): void => {
if (!receipt) return;

const logs = receipt.logs;
const coreDisputeId = event.params._disputeID;

// note that the topic at 0th index is always the event signature
const disputeRequestEventIndex = logs.findIndex((log) => log.topics[0] == DisputeRequestSignature);
const crossChainDisputeEventIndex = logs.findIndex((log) => log.topics[0] == CrossChainDisputeIncomingSignature);
// For DisputeRequestSignature
let disputeRequestEventIndex = -1;
for (let i = 0; i < logs.length; i++) {
let log = logs[i];
if (log.topics.length > 2 && log.topics[0] == DisputeRequestSignature) {
// 3rd indexed argument in event is _arbitratorDisputeId
let decodedId = ethereum.decode("uint256", log.topics[2]);
if (decodedId != null && coreDisputeId.equals(decodedId.toBigInt())) {
disputeRequestEventIndex = i;
break;
}
}
}

// For CrossChainDisputeIncomingSignature
let crossChainDisputeEventIndex = -1;
for (let i = 0; i < logs.length; i++) {
let log = logs[i];
if (log.topics.length > 3 && log.topics[0] == CrossChainDisputeIncomingSignature) {
// 4th indexed argument in event is _arbitratorDisputeId
let decodedId = ethereum.decode("uint256", log.topics[3]);
if (decodedId != null && coreDisputeId.equals(decodedId.toBigInt())) {
crossChainDisputeEventIndex = i;
break;
}
}
}

if (crossChainDisputeEventIndex !== -1) {
const crossChainDisputeEvent = logs[crossChainDisputeEventIndex];
Expand Down
2 changes: 1 addition & 1 deletion subgraph/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kleros/kleros-v2-subgraph",
"version": "0.15.2",
"version": "0.15.4",
"drtVersion": "0.12.0",
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

### Pre-Requisites

If you haven't already, you need to follow all the previous steps of the **Contributing** section of the repo's [Contribution Guidelines](../CONTRIBUTING.md).
If you haven't already, you need to follow all the previous steps of the **Contributing** section of the repo's [Contribution Guidelines](../CONTRIBUTING.md)

### Getting Started

Expand Down
11 changes: 11 additions & 0 deletions web/src/assets/svgs/icons/gavel-executed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 0 additions & 45 deletions web/src/components/AllCasesButton.tsx

This file was deleted.

63 changes: 57 additions & 6 deletions web/src/components/DisputePreview/DisputeContext.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import React from "react";
import React, { useMemo } from "react";
import styled from "styled-components";

import { DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes";
import { useAccount } from "wagmi";

import { INVALID_DISPUTE_DATA_ERROR, RPC_ERROR } from "consts/index";
import { Answer as IAnswer } from "context/NewDisputeContext";
import { isUndefined } from "utils/index";

import { responsiveSize } from "styles/responsiveSize";

import { DisputeDetailsQuery, VotingHistoryQuery } from "src/graphql/graphql";

import ReactMarkdown from "components/ReactMarkdown";
import { StyledSkeleton } from "components/StyledSkeleton";

import { Divider } from "../Divider";
import { ExternalLink } from "../ExternalLink";

import AliasDisplay from "./Alias";
import RulingAndRewardsIndicators from "../Verdict/RulingAndRewardsIndicators";
import CardLabel from "../DisputeView/CardLabels";

const StyledH1 = styled.h1`
margin: 0;
word-wrap: break-word;
font-size: ${responsiveSize(18, 24)};
font-size: ${responsiveSize(20, 26)};
line-height: 24px;
`;

const TitleSection = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;

const ReactMarkdownWrapper = styled.div`
& p:first-of-type {
margin: 0;
Expand Down Expand Up @@ -66,19 +77,59 @@ const AliasesContainer = styled.div`
gap: ${responsiveSize(8, 20)};
`;

const RulingAndRewardsAndLabels = styled.div`
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
`;

interface IDisputeContext {
disputeDetails?: DisputeDetails;
isRpcError?: boolean;
dispute?: DisputeDetailsQuery | undefined;

disputeId?: string;
votingHistory?: VotingHistoryQuery | undefined;
}

export const DisputeContext: React.FC<IDisputeContext> = ({ disputeDetails, isRpcError = false }) => {
export const DisputeContext: React.FC<IDisputeContext> = ({
disputeDetails,
isRpcError = false,
dispute,
disputeId,
votingHistory,
}) => {
const { isDisconnected } = useAccount();
const errMsg = isRpcError ? RPC_ERROR : INVALID_DISPUTE_DATA_ERROR;
const rounds = votingHistory?.dispute?.rounds;
const jurorRewardsDispersed = useMemo(() => Boolean(rounds?.every((round) => round.jurorRewardsDispersed)), [rounds]);
console.log({ jurorRewardsDispersed }, disputeDetails);

return (
<>
<StyledH1 dir="auto">
{isUndefined(disputeDetails) ? <StyledSkeleton /> : (disputeDetails?.title ?? errMsg)}
</StyledH1>
<TitleSection>
<StyledH1 dir="auto">
{isUndefined(disputeDetails) ? <StyledSkeleton /> : (disputeDetails?.title ?? errMsg)}
</StyledH1>
{!isUndefined(disputeDetails) &&
!isUndefined(dispute) &&
!isUndefined(disputeId) &&
!isUndefined(votingHistory) ? (
<RulingAndRewardsAndLabels>
{!isUndefined(Boolean(dispute?.dispute?.ruled)) || jurorRewardsDispersed ? (
<RulingAndRewardsIndicators
ruled={Boolean(dispute?.dispute?.ruled)}
jurorRewardsDispersed={jurorRewardsDispersed}
/>
) : null}
{!isDisconnected ? (
<CardLabel {...{ disputeId }} round={rounds?.length - 1} isList={false} isOverview={true} />
Comment on lines +120 to +126
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

Condition always true → incorrect rendering of indicators

!isUndefined(Boolean(dispute?.dispute?.ruled)) is always true because Boolean(...) never returns undefined.
This means RulingAndRewardsIndicators is rendered even when the dispute is not ruled and rewards are not dispersed.

-            {!isUndefined(Boolean(dispute?.dispute?.ruled)) || jurorRewardsDispersed ? (
+            {(Boolean(dispute?.dispute?.ruled) || jurorRewardsDispersed) && (

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

🤖 Prompt for AI Agents
In web/src/components/DisputePreview/DisputeContext.tsx around lines 120 to 126,
the condition using !isUndefined(Boolean(dispute?.dispute?.ruled)) is always
true because Boolean() never returns undefined, causing
RulingAndRewardsIndicators to render incorrectly. Replace this condition with a
direct check for the ruled property being true, such as dispute?.dispute?.ruled
=== true, combined with jurorRewardsDispersed, to ensure the indicators only
render when the dispute is ruled or rewards are dispersed.

) : null}
</RulingAndRewardsAndLabels>
Comment on lines +127 to +128
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

Possible NaN passed to CardLabel.round

rounds?.length - 1 yields NaN when rounds is undefined.
Guard the prop or default to 0.

-              <CardLabel {...{ disputeId }} round={rounds?.length - 1} isList={false} isOverview={true} />
+              {rounds && (
+                <CardLabel
+                  {...{ disputeId }}
+                  round={Math.max(rounds.length - 1, 0)}
+                  isList={false}
+                  isOverview={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
<CardLabel {...{ disputeId }} round={rounds?.length - 1} isList={false} isOverview={true} />
) : null}
{rounds && (
<CardLabel
{...{ disputeId }}
round={Math.max(rounds.length - 1, 0)}
isList={false}
isOverview={true}
/>
)}
) : null}
🤖 Prompt for AI Agents
In web/src/components/DisputePreview/DisputeContext.tsx around lines 127 to 128,
the expression rounds?.length - 1 can result in NaN if rounds is undefined. To
fix this, ensure rounds is defined before subtracting 1 by using a default value
like 0, for example, replace rounds?.length - 1 with (rounds?.length ?? 1) - 1
or use a conditional to pass 0 when rounds is undefined. This prevents passing
NaN to the CardLabel.round prop.

) : null}
<Divider />
</TitleSection>
{disputeDetails?.question?.trim() || disputeDetails?.description?.trim() ? (
<div>
{disputeDetails?.question?.trim() ? (
Expand Down
Loading
Loading