Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions subgraph/core/subgraph.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,43 @@ dataSources:
- event: CommitCast(indexed uint256,indexed address,uint256[],bytes32)
handler: handleCommitCast
file: ./src/DisputeKitClassic.ts
- kind: ethereum
name: DisputeKitGatedArgentinaConsumerProtection
network: _PLACEHOLDER_
source:
address: "_PLACEHOLDER_"
abi: DisputeKitGatedArgentinaConsumerProtection
startBlock: _PLACEHOLDER_
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- ClassicDispute
- ClassicRound
- ClassicVote
- ClassicContribution
abis:
- name: DisputeKitGatedArgentinaConsumerProtection
file: ../../contracts/deployments/_PLACEHOLDER_/DisputeKitGatedArgentinaConsumerProtection.json
- name: DisputeKitClassic # Required on Alchemy
file: ../../contracts/deployments/_PLACEHOLDER_/DisputeKitClassic.json
- name: KlerosCore
file: ../../contracts/deployments/_PLACEHOLDER_/KlerosCore.json
eventHandlers:
- event: DisputeCreation(indexed uint256,uint256,bytes)
handler: handleDisputeCreation
- event: Contribution(indexed uint256,indexed uint256,uint256,indexed address,uint256)
handler: handleContributionEvent
- event: Withdrawal(indexed uint256,uint256,indexed address,uint256)
handler: handleWithdrawal
- event: ChoiceFunded(indexed uint256,indexed uint256,indexed uint256)
handler: handleChoiceFunded
- event: VoteCast(indexed uint256,indexed address,uint256[],indexed uint256,string)
handler: handleVoteCast
- event: CommitCast(indexed uint256,indexed address,uint256[],bytes32)
handler: handleCommitCast
file: ./src/DisputeKitClassic.ts
- kind: ethereum
name: EvidenceModule
network: _PLACEHOLDER_
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

import { Features } from "consts/disputeFeature";

import WithHelpTooltip from "components/WithHelpTooltip";

import { RadioInput, StyledRadio } from ".";

const ArgentinaConsumerProtection: React.FC<RadioInput> = (props) => {
return (
<WithHelpTooltip
tooltipMsg={`Jurors must hold either an accredited professional token or
an accredited consumer protection lawyer token to participate.
At least one drawn juror must hold the consumer protection lawyer token.`}
key={Features.ArgentinaConsumerProtection}
>
<StyledRadio label="Argentina Consumer Protection" small {...props} />
</WithHelpTooltip>
);
};

export default ArgentinaConsumerProtection;
7 changes: 5 additions & 2 deletions web/src/components/DisputeFeatures/Features/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Features } from "consts/disputeFeature";

import WithHelpTooltip from "components/WithHelpTooltip";

import ArgentinaConsumerProtection from "./ArgentinaConsumerProtection";
import ClassicVote from "./ClassicVote";
import GatedErc1155 from "./GatedErc1155";
import GatedErc20 from "./GatedErc20";
Expand All @@ -30,8 +31,8 @@ export const StyledRadio = styled(Radio)`
export const FeatureUIs: Record<Features, FeatureUI> = {
[Features.ShieldedVote]: (props: RadioInput) => (
<WithHelpTooltip
tooltipMsg={`The jurors votes are hidden.
Nobody can see them before the voting period completes.
tooltipMsg={`The jurors votes are hidden.
Nobody can see them before the voting period completes.
It takes place in a single step via Shutter Network`}
key={Features.ShieldedVote}
>
Expand All @@ -48,4 +49,6 @@ export const FeatureUIs: Record<Features, FeatureUI> = {
[Features.GatedErc20]: (props: RadioInput) => <GatedErc20 {...props} />,

[Features.GatedErc1155]: (props: RadioInput) => <GatedErc1155 {...props} />,

[Features.ArgentinaConsumerProtection]: (props: RadioInput) => <ArgentinaConsumerProtection {...props} />,
};
13 changes: 12 additions & 1 deletion web/src/consts/disputeFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum Features {
ClassicEligibility = "classicEligibility",
GatedErc20 = "gatedErc20",
GatedErc1155 = "gatedErc1155",
ArgentinaConsumerProtection = "argentinaConsumerProtection",
}

/** Group of features (like radio buttons per category) */
Expand Down Expand Up @@ -38,7 +39,12 @@ export type DisputeKits = DisputeKit[];
// DEV: the order of features in array , determine the order the radios appear on UI
export const featureGroups: FeatureGroups = {
[Group.Voting]: [Features.ClassicVote, Features.ShieldedVote],
[Group.Eligibility]: [Features.ClassicEligibility, Features.GatedErc20, Features.GatedErc1155],
[Group.Eligibility]: [
Features.ClassicEligibility,
Features.GatedErc20,
Features.GatedErc1155,
Features.ArgentinaConsumerProtection,
],
};

// dispute kits
Expand Down Expand Up @@ -67,6 +73,11 @@ export const disputeKits: DisputeKits = [
],
type: "gated",
},
{
id: 5,
featureSets: [[Features.ClassicVote, Features.ArgentinaConsumerProtection]],
type: "general",
},
];

/** Canonical string for a feature set (order-independent) */
Expand Down
1 change: 1 addition & 0 deletions web/src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ export enum DisputeKits {
Shutter = "Shutter",
Gated = "Token Gated",
GatedShutter = "Token Gated Shutter",
ArgentinaConsumerProtection = "Argentina Consumer Protection",
}
1 change: 1 addition & 0 deletions web/src/hooks/useDisputeKitAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const DISPUTE_KIT_CONFIG = {
[DisputeKits.Shutter]: "disputeKitShutterAddress",
[DisputeKits.Gated]: "disputeKitGatedAddress",
[DisputeKits.GatedShutter]: "disputeKitGatedShutterAddress",
[DisputeKits.ArgentinaConsumerProtection]: "disputeKitGatedArgentinaConsumerProtectionAddress",
} as const;

/**
Expand Down
12 changes: 12 additions & 0 deletions web/src/hooks/useVotingContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useReadDisputeKitShutterIsVoteActive,
useReadDisputeKitGatedIsVoteActive,
useReadDisputeKitGatedShutterIsVoteActive,
useReadDisputeKitGatedArgentinaConsumerProtectionIsVoteActive,
} from "hooks/contracts/generated";
import { useDisputeDetailsQuery } from "hooks/queries/useDisputeDetailsQuery";
import { useDrawQuery } from "hooks/queries/useDrawQuery";
Expand Down Expand Up @@ -81,6 +82,14 @@ export const VotingContextProvider: React.FC<{ children: React.ReactNode }> = ({
args: hookArgs,
});

const argentinaConsumerProtectionVoteResult = useReadDisputeKitGatedArgentinaConsumerProtectionIsVoteActive({
query: {
enabled: isEnabled && disputeKitName === DisputeKits.ArgentinaConsumerProtection,
refetchInterval: REFETCH_INTERVAL,
},
args: hookArgs,
});

// Add a return for each DisputeKit
const hasVoted = useMemo(() => {
switch (disputeKitName) {
Expand All @@ -92,6 +101,8 @@ export const VotingContextProvider: React.FC<{ children: React.ReactNode }> = ({
return gatedVoteResult.data;
case DisputeKits.GatedShutter:
return gatedShutterVoteResult.data;
case DisputeKits.ArgentinaConsumerProtection:
return argentinaConsumerProtectionVoteResult.data;
default:
return undefined;
}
Expand All @@ -101,6 +112,7 @@ export const VotingContextProvider: React.FC<{ children: React.ReactNode }> = ({
shutterVoteResult.data,
gatedVoteResult.data,
gatedShutterVoteResult.data,
argentinaConsumerProtectionVoteResult.data,
]);

const wasDrawn = useMemo(() => !isUndefined(drawData) && drawData.draws.length > 0, [drawData]);
Expand Down
53 changes: 42 additions & 11 deletions web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { useAccount, useBalance, usePublicClient } from "wagmi";

import { Field, Button } from "@kleros/ui-components-library";

import { REFETCH_INTERVAL } from "consts/index";
import { REFETCH_INTERVAL, DisputeKits } from "consts/index";
import {
useSimulateDisputeKitClassicFundAppeal,
useSimulateDisputeKitGatedFundAppeal,
useSimulateDisputeKitGatedArgentinaConsumerProtectionFundAppeal,
useWriteDisputeKitClassicFundAppeal,
useWriteDisputeKitGatedFundAppeal,
useWriteDisputeKitGatedArgentinaConsumerProtectionFundAppeal,
} from "hooks/contracts/generated";
import { useSelectedOptionContext, useFundingContext, useCountdownContext } from "hooks/useClassicAppealContext";
import { useParsedAmount } from "hooks/useParsedAmount";
Expand Down Expand Up @@ -67,16 +69,23 @@ const useNeedFund = () => {
return needFund;
};

const useFundAppeal = (parsedAmount: bigint, isGated: boolean, insufficientBalance?: boolean) => {
const useFundAppeal = (
parsedAmount: bigint,
isGated: boolean,
disputeKitName?: DisputeKits,
insufficientBalance?: boolean
) => {
const { id } = useParams();
const { selectedOption } = useSelectedOptionContext();
const isArgentinaCP = disputeKitName === DisputeKits.ArgentinaConsumerProtection;

const {
data: fundAppealConfig,
isLoading,
isError,
} = useSimulateDisputeKitClassicFundAppeal({
query: {
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance && !isGated,
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance && !isGated && !isArgentinaCP,
},
args: [BigInt(id ?? 0), BigInt(selectedOption?.id ?? 0)],
value: parsedAmount,
Expand All @@ -89,31 +98,52 @@ const useFundAppeal = (parsedAmount: bigint, isGated: boolean, insufficientBalan
isError: isErrorGated,
} = useSimulateDisputeKitGatedFundAppeal({
query: {
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance && isGated,
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance && isGated && !isArgentinaCP,
},
args: [BigInt(id ?? 0), BigInt(selectedOption?.id ?? 0)],
value: parsedAmount,
});
const { writeContractAsync: fundAppealGated } = useWriteDisputeKitGatedFundAppeal();

return isGated
const {
data: fundAppealArgentinaCPConfig,
isLoading: isLoadingArgentinaCP,
isError: isErrorArgentinaCP,
} = useSimulateDisputeKitGatedArgentinaConsumerProtectionFundAppeal({
query: {
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance && isArgentinaCP,
},
args: [BigInt(id ?? 0), BigInt(selectedOption?.id ?? 0)],
value: parsedAmount,
});
const { writeContractAsync: fundAppealArgentinaCP } = useWriteDisputeKitGatedArgentinaConsumerProtectionFundAppeal();

return isArgentinaCP
? {
fundAppeal: fundAppealGated,
fundAppealConfig: fundAppealGatedConfig,
isLoading: isLoadingGated,
isError: isErrorGated,
data: fundAppealArgentinaCPConfig,
isLoading: isLoadingArgentinaCP,
isError: isErrorArgentinaCP,
fundAppeal: fundAppealArgentinaCP,
}
: { fundAppeal, fundAppealConfig, isLoading, isError };
: isGated
? {
fundAppeal: fundAppealGated,
fundAppealConfig: fundAppealGatedConfig,
isLoading: isLoadingGated,
isError: isErrorGated,
}
: { fundAppeal, fundAppealConfig, isLoading, isError };
Comment on lines +121 to +135
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

Return object key mismatch will break Argentina CP funding flow.

The Argentina CP branch returns data as the key for the config object, but the Gated and Classic branches return fundAppealConfig. The destructuring on line 167 expects fundAppealConfig, which will be undefined for Argentina CP disputes. This causes the button click handler (line 204) to silently fail since it checks fundAppealConfig.

🔎 Proposed fix
   return isArgentinaCP
     ? {
-        data: fundAppealArgentinaCPConfig,
+        fundAppealConfig: fundAppealArgentinaCPConfig,
         isLoading: isLoadingArgentinaCP,
         isError: isErrorArgentinaCP,
         fundAppeal: fundAppealArgentinaCP,
       }
     : isGated
📝 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
return isArgentinaCP
? {
fundAppeal: fundAppealGated,
fundAppealConfig: fundAppealGatedConfig,
isLoading: isLoadingGated,
isError: isErrorGated,
data: fundAppealArgentinaCPConfig,
isLoading: isLoadingArgentinaCP,
isError: isErrorArgentinaCP,
fundAppeal: fundAppealArgentinaCP,
}
: { fundAppeal, fundAppealConfig, isLoading, isError };
: isGated
? {
fundAppeal: fundAppealGated,
fundAppealConfig: fundAppealGatedConfig,
isLoading: isLoadingGated,
isError: isErrorGated,
}
: { fundAppeal, fundAppealConfig, isLoading, isError };
return isArgentinaCP
? {
fundAppealConfig: fundAppealArgentinaCPConfig,
isLoading: isLoadingArgentinaCP,
isError: isErrorArgentinaCP,
fundAppeal: fundAppealArgentinaCP,
}
: isGated
? {
fundAppeal: fundAppealGated,
fundAppealConfig: fundAppealGatedConfig,
isLoading: isLoadingGated,
isError: isErrorGated,
}
: { fundAppeal, fundAppealConfig, isLoading, isError };
🤖 Prompt for AI Agents
In web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx around lines 121-135,
the Argentina CP branch returns the config under the key "data" while the Gated
and Classic branches return it as "fundAppealConfig", which makes the
destructuring on line 167 get undefined and breaks the click handler on line
204; update the Argentina CP return to use the same key name (fundAppealConfig)
and keep the returned object shape consistent with the other branches so
fundAppealConfig, fundAppeal, isLoading, and isError are always present.

};

interface IFund {
amount: `${number}`;
setAmount: (val: string) => void;
setIsOpen: (val: boolean) => void;
isGated: boolean;
disputeKitName?: DisputeKits;
}

const Fund: React.FC<IFund> = ({ amount, setAmount, setIsOpen, isGated }) => {
const Fund: React.FC<IFund> = ({ amount, setAmount, setIsOpen, isGated, disputeKitName }) => {
const needFund = useNeedFund();
const { address, isDisconnected } = useAccount();
const { data: balance } = useBalance({
Expand All @@ -137,6 +167,7 @@ const Fund: React.FC<IFund> = ({ amount, setAmount, setIsOpen, isGated }) => {
const { fundAppealConfig, fundAppeal, isLoading, isError } = useFundAppeal(
parsedAmount,
isGated,
disputeKitName,
insufficientBalance
);

Expand Down
11 changes: 9 additions & 2 deletions web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from "react";

import AppealIcon from "svgs/icons/appeal.svg";

import { DisputeKits } from "consts/index";
import { useSelectedOptionContext } from "hooks/useClassicAppealContext";
import { isUndefined } from "utils/index";

Expand All @@ -18,9 +19,10 @@ interface IClassic {
isAppealMiniGuideOpen: boolean;
toggleAppealMiniGuide: () => void;
isGated: boolean;
disputeKitName?: DisputeKits;
}

const Classic: React.FC<IClassic> = ({ isAppealMiniGuideOpen, toggleAppealMiniGuide, isGated }) => {
const Classic: React.FC<IClassic> = ({ isAppealMiniGuideOpen, toggleAppealMiniGuide, isGated, disputeKitName }) => {
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [amount, setAmount] = useState("");
const { selectedOption } = useSelectedOptionContext();
Expand Down Expand Up @@ -48,7 +50,12 @@ const Classic: React.FC<IClassic> = ({ isAppealMiniGuideOpen, toggleAppealMiniGu
</AppealHeader>
<label> The jury decision is appealed when two options are fully funded. </label>
<Options setAmount={setAmount} />
<Fund amount={amount as `${number}`} setAmount={setAmount} setIsOpen={setIsPopupOpen} {...{ isGated }} />
<Fund
amount={amount as `${number}`}
setAmount={setAmount}
setIsOpen={setIsPopupOpen}
{...{ isGated, disputeKitName }}
/>
</>
);
};
Expand Down
7 changes: 5 additions & 2 deletions web/src/pages/Cases/CaseDetails/Appeal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ const Appeal: React.FC<{ currentPeriodIndex: number }> = ({ currentPeriodIndex }
const { data: disputeData } = useDisputeDetailsQuery(id);
const disputeKitAddress = disputeData?.dispute?.currentRound?.disputeKit?.address;
const { disputeKitName } = useDisputeKitAddresses({ disputeKitAddress });
const isClassicDisputeKit = disputeKitName === DisputeKits.Classic || disputeKitName === DisputeKits.Gated;
const isClassicDisputeKit =
disputeKitName === DisputeKits.Classic ||
disputeKitName === DisputeKits.Gated ||
disputeKitName === DisputeKits.ArgentinaConsumerProtection;
const isShutterDisputeKit = disputeKitName === DisputeKits.Shutter || disputeKitName === DisputeKits.GatedShutter;
const isGated = Boolean(disputeKitName?.includes("Gated"));
return (
Expand All @@ -64,7 +67,7 @@ const Appeal: React.FC<{ currentPeriodIndex: number }> = ({ currentPeriodIndex }
<Classic
isAppealMiniGuideOpen={isAppealMiniGuideOpen}
toggleAppealMiniGuide={toggleAppealMiniGuide}
{...{ isGated }}
{...{ isGated, disputeKitName }}
/>
)}
{isShutterDisputeKit && (
Expand Down
18 changes: 15 additions & 3 deletions web/src/pages/Cases/CaseDetails/Voting/Classic/Commit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { useLocalStorage } from "react-use";
import { keccak256, encodePacked } from "viem";
import { useWalletClient, usePublicClient, useConfig } from "wagmi";

import { simulateDisputeKitClassicCastCommit, simulateDisputeKitGatedCastCommit } from "hooks/contracts/generated";
import { DisputeKits } from "consts/index";
import {
simulateDisputeKitClassicCastCommit,
simulateDisputeKitGatedCastCommit,
simulateDisputeKitGatedArgentinaConsumerProtectionCastCommit,
} from "hooks/contracts/generated";
import useSigningAccount from "hooks/useSigningAccount";
import { isUndefined } from "utils/index";
import { wrapWithToast } from "utils/wrapWithToast";
Expand All @@ -26,9 +31,10 @@ interface ICommit {
setIsOpen: (val: boolean) => void;
refetch: () => void;
isGated: boolean;
disputeKitName?: DisputeKits;
}

const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch, isGated }) => {
const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch, isGated, disputeKitName }) => {
const { id } = useParams();
const parsedDisputeID = useMemo(() => BigInt(id ?? 0), [id]);
const parsedVoteIDs = useMemo(() => voteIDs.map((voteID) => BigInt(voteID)), [voteIDs]);
Expand Down Expand Up @@ -60,7 +66,12 @@ const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch, is
setSalt(JSON.stringify({ salt, choice: choice.toString() }));
const commit = keccak256(encodePacked(["uint256", "uint256"], [BigInt(choice), BigInt(salt)]));

const simulate = isGated ? simulateDisputeKitGatedCastCommit : simulateDisputeKitClassicCastCommit;
const simulate =
disputeKitName === DisputeKits.ArgentinaConsumerProtection
? simulateDisputeKitGatedArgentinaConsumerProtectionCastCommit
: isGated
? simulateDisputeKitGatedCastCommit
: simulateDisputeKitClassicCastCommit;

const { request } = await simulate(wagmiConfig, {
args: [parsedDisputeID, parsedVoteIDs, commit],
Expand All @@ -85,6 +96,7 @@ const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch, is
signingAccount,
refetch,
isGated,
disputeKitName,
]
);

Expand Down
Loading
Loading