Skip to content
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

Fix/use answer id in voting #1839

Merged
merged 12 commits into from
Jan 30, 2025
Merged
Changes from 7 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
12 changes: 8 additions & 4 deletions kleros-sdk/src/dataMappings/utils/disputeDetailsSchema.ts
Original file line number Diff line number Diff line change
@@ -33,15 +33,19 @@ export enum QuestionType {
export const QuestionTypeSchema = z.nativeEnum(QuestionType);

export const AnswerSchema = z.object({
id: z
.string()
.regex(/^0x[0-9a-fA-F]+$/)
.optional(),
id: z.string().regex(/^0x[0-9a-fA-F]+$/),
title: z.string(),
description: z.string(),
reserved: z.boolean().optional(),
});

export const RefuseToArbitrateAnswer = {
id: "0x0",
title: "Refuse to Arbitrate / Invalid",
description: "Refuse to Arbitrate / Invalid",
reserved: true,
};

export const AttachmentSchema = z.object({
label: z.string(),
uri: z.string(),
8 changes: 7 additions & 1 deletion kleros-sdk/src/dataMappings/utils/populateTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import mustache from "mustache";
import { DisputeDetails } from "./disputeDetailsTypes";
import DisputeDetailsSchema from "./disputeDetailsSchema";
import DisputeDetailsSchema, { RefuseToArbitrateAnswer } from "./disputeDetailsSchema";

export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDetails => {
const render = mustache.render(mustacheTemplate, data);
@@ -11,5 +11,11 @@ export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDe
throw validation.error;
}

// Filter out any existing answer with id 0 and add our standard Refuse to Arbitrate option
(dispute as DisputeDetails).answers = [
RefuseToArbitrateAnswer,
...((dispute as DisputeDetails).answers.filter((answer) => answer.id && BigInt(answer.id) !== BigInt(0)) || []),
];

return dispute;
};
11 changes: 0 additions & 11 deletions kleros-sdk/src/utils/getDispute.ts
Original file line number Diff line number Diff line change
@@ -56,16 +56,5 @@ export const getDispute = async (disputeParameters: GetDisputeParameters): Promi

const populatedTemplate = populateTemplate(templateData, data);

// Filter out any existing answer with id 0 and add our standard Refuse to Arbitrate option
populatedTemplate.answers = [
{
id: "0x0",
title: "Refuse to Arbitrate / Invalid",
description: "Refuse to Arbitrate / Invalid",
reserved: true,
},
...(populatedTemplate.answers?.filter((answer) => answer.id && Number(answer.id) !== 0) || []),
];

return populatedTemplate;
};
13 changes: 11 additions & 2 deletions subgraph/core/schema.graphql
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@ interface Evidence {
fileTypeExtension: String
}


############
# Entities #
############
@@ -265,17 +266,25 @@ type ClassicDispute implements DisputeKitDispute @entity {
extraData: Bytes!
}

type Answer @entity {
id: ID! # classicRound.id-answerId
answerId: BigInt!
count: BigInt!
paidFee: BigInt!
funded: Boolean!
localRound: ClassicRound!
}

type ClassicRound implements DisputeKitRound @entity {
id: ID! # disputeKit.id-coreDispute-dispute.rounds.length
localDispute: DisputeKitDispute!
votes: [Vote!]! @derivedFrom(field: "localRound")
answers: [Answer!]! @derivedFrom(field: "localRound")

winningChoice: BigInt!
counts: [BigInt!]!
tied: Boolean!
totalVoted: BigInt!
totalCommited: BigInt!
paidFees: [BigInt!]!
contributions: [ClassicContribution!]! @derivedFrom(field: "localRound")
feeRewards: BigInt!
totalFeeDispersed: BigInt!
8 changes: 7 additions & 1 deletion subgraph/core/src/DisputeKitClassic.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import { ensureClassicContributionFromEvent } from "./entities/ClassicContributi
import { createClassicDisputeFromEvent } from "./entities/ClassicDispute";
import {
createClassicRound,
ensureAnswer,
updateChoiceFundingFromContributionEvent,
updateCountsAndGetCurrentRuling,
} from "./entities/ClassicRound";
@@ -101,11 +102,16 @@ export function handleChoiceFunded(event: ChoiceFunded): void {
const localRound = ClassicRound.load(roundID);
if (!localRound) return;

const answer = ensureAnswer(roundID, choice);

const currentFeeRewards = localRound.feeRewards;
const deltaFeeRewards = localRound.paidFees[choice.toI32()];
const deltaFeeRewards = answer.paidFee;
localRound.feeRewards = currentFeeRewards.plus(deltaFeeRewards);
localRound.fundedChoices = localRound.fundedChoices.concat([choice]);

answer.funded = true;
answer.save();

if (localRound.fundedChoices.length > 1) {
const disputeKitClassic = DisputeKitClassic.bind(event.address);
const klerosCore = KlerosCore.bind(disputeKitClassic.core());
57 changes: 30 additions & 27 deletions subgraph/core/src/entities/ClassicRound.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { BigInt } from "@graphprotocol/graph-ts";
import { Contribution } from "../../generated/DisputeKitClassic/DisputeKitClassic";
import { ClassicRound } from "../../generated/schema";
import { ONE, ZERO } from "../utils";
import { Answer, ClassicRound } from "../../generated/schema";
import { ZERO } from "../utils";

export function createClassicRound(disputeID: string, numberOfChoices: BigInt, roundIndex: BigInt): void {
const choicesLength = numberOfChoices.plus(ONE);
const localDisputeID = `1-${disputeID}`;
const id = `${localDisputeID}-${roundIndex.toString()}`;
const classicRound = new ClassicRound(id);
classicRound.localDispute = localDisputeID;
classicRound.winningChoice = ZERO;
classicRound.counts = new Array<BigInt>(choicesLength.toI32()).fill(ZERO);
classicRound.tied = true;
classicRound.totalVoted = ZERO;
classicRound.totalCommited = ZERO;
classicRound.paidFees = new Array<BigInt>(choicesLength.toI32()).fill(ZERO);
classicRound.feeRewards = ZERO;
classicRound.appealFeesDispersed = false;
classicRound.totalFeeDispersed = ZERO;
@@ -27,21 +24,31 @@ class CurrentRulingInfo {
tied: boolean;
}

export function ensureAnswer(localRoundId: string, answerId: BigInt): Answer {
const id = `${localRoundId}-${answerId}`;
let answer = Answer.load(id);
if (answer) return answer;
answer = new Answer(id);
answer.answerId = answerId;
answer.count = ZERO;
answer.paidFee = ZERO;
answer.funded = false;
answer.localRound = localRoundId;
return answer;
}

export function updateCountsAndGetCurrentRuling(id: string, choice: BigInt, delta: BigInt): CurrentRulingInfo {
const round = ClassicRound.load(id);
if (!round) return { ruling: ZERO, tied: false };
const choiceNum = choice.toI32();
const newChoiceCount = round.counts[choiceNum].plus(delta);
let newCounts: BigInt[] = [];
for (let i = 0; i < round.counts.length; i++) {
if (BigInt.fromI32(i).equals(choice)) {
newCounts.push(newChoiceCount);
} else {
newCounts.push(round.counts[i]);
}
}
round.counts = newCounts;
const currentWinningCount = round.counts[round.winningChoice.toI32()];
const answer = ensureAnswer(id, choice);

answer.count = answer.count.plus(delta);

const newChoiceCount = answer.count;

const winningAnswer = ensureAnswer(id, round.winningChoice);
const currentWinningCount = winningAnswer.count;

if (choice.equals(round.winningChoice)) {
if (round.tied) round.tied = false;
} else {
@@ -53,6 +60,8 @@ export function updateCountsAndGetCurrentRuling(id: string, choice: BigInt, delt
}
}
round.totalVoted = round.totalVoted.plus(delta);

answer.save();
round.save();
return { ruling: round.winningChoice, tied: round.tied };
}
@@ -68,15 +77,9 @@ export function updateChoiceFundingFromContributionEvent(event: Contribution): v

const choice = event.params._choice;
const amount = event.params._amount;
const currentPaidFees = classicRound.paidFees[choice.toI32()];
let newPaidFees: BigInt[] = [];
for (let i = 0; i < classicRound.paidFees.length; i++) {
if (BigInt.fromI32(i).equals(choice)) {
newPaidFees.push(currentPaidFees.plus(amount));
} else {
newPaidFees.push(classicRound.paidFees[i]);
}
}
classicRound.paidFees = newPaidFees;
const answer = ensureAnswer(roundID, choice);
answer.paidFee = answer.paidFee.plus(amount);

answer.save();
classicRound.save();
}
8 changes: 3 additions & 5 deletions web/src/components/Verdict/DisputeTimeline.tsx
Original file line number Diff line number Diff line change
@@ -92,20 +92,18 @@ const useItems = (disputeDetails?: DisputeDetailsQuery, arbitrable?: `0x${string
const dispute = disputeDetails?.dispute;
if (dispute) {
const rulingOverride = dispute.overridden;
const parsedDisputeFinalRuling = parseInt(dispute.currentRuling);
const currentPeriodIndex = Periods[dispute.period];

return localRounds?.reduce<TimelineItems>(
(acc, { winningChoice }, index) => {
const parsedRoundChoice = parseInt(winningChoice);
const isOngoing = index === localRounds.length - 1 && currentPeriodIndex < 3;
const roundTimeline = rounds?.[index].timeline;

const icon = dispute.ruled && !rulingOverride && index === localRounds.length - 1 ? ClosedCaseIcon : "";
const answers = disputeData?.answers;
acc.push({
title: `Jury Decision - Round ${index + 1}`,
party: isOngoing ? "Voting is ongoing" : getVoteChoice(parsedRoundChoice, answers),
party: isOngoing ? "Voting is ongoing" : getVoteChoice(winningChoice, answers),
subtitle: isOngoing
? ""
: `${formatDate(roundTimeline?.[Periods.vote])} / ${
@@ -124,10 +122,10 @@ const useItems = (disputeDetails?: DisputeDetailsQuery, arbitrable?: `0x${string
rightSided: true,
Icon: StyledClosedCircle,
});
} else if (rulingOverride && parsedDisputeFinalRuling !== parsedRoundChoice) {
} else if (rulingOverride && dispute.currentRuling !== winningChoice) {
acc.push({
title: "Won by Appeal",
party: getVoteChoice(parsedDisputeFinalRuling, answers),
party: getVoteChoice(dispute.currentRuling, answers),
subtitle: formatDate(roundTimeline?.[Periods.appeal]),
rightSided: true,
Icon: ClosedCaseIcon,
7 changes: 6 additions & 1 deletion web/src/hooks/queries/useClassicAppealQuery.ts
Original file line number Diff line number Diff line change
@@ -25,7 +25,12 @@ const classicAppealQuery = graphql(`
localRounds {
... on ClassicRound {
winningChoice
paidFees
answers {
answerId
count
paidFee
funded
}
fundedChoices
appealFeesDispersed
totalFeeDispersed
48 changes: 30 additions & 18 deletions web/src/hooks/useClassicAppealContext.tsx
Original file line number Diff line number Diff line change
@@ -12,19 +12,22 @@ import { isUndefined } from "utils/index";
import { useAppealCost } from "queries/useAppealCost";
import { useClassicAppealQuery, ClassicAppealQuery } from "queries/useClassicAppealQuery";
import { useDisputeKitClassicMultipliers } from "queries/useDisputeKitClassicMultipliers";
import { Answer, DisputeDetails } from "@kleros/kleros-sdk";

type Option = Answer & { paidFee?: string; funded?: boolean };
interface ICountdownContext {
loserSideCountdown?: number;
winnerSideCountdown?: number;
isLoading?: boolean;
}

const CountdownContext = createContext<ICountdownContext>({});

const OptionsContext = createContext<string[] | undefined>(undefined);
const OptionsContext = createContext<Option[] | undefined>(undefined);

interface ISelectedOptionContext {
selectedOption: number | undefined;
setSelectedOption: (arg0: number) => void;
selectedOption: Option | undefined;
setSelectedOption: (arg0: Option) => void;
}
const SelectedOptionContext = createContext<ISelectedOptionContext>({
selectedOption: undefined,
@@ -34,14 +37,13 @@ const SelectedOptionContext = createContext<ISelectedOptionContext>({

interface IFundingContext {
winningChoice: string | undefined;
paidFees: bigint[] | undefined;
loserRequiredFunding: bigint | undefined;
winnerRequiredFunding: bigint | undefined;
fundedChoices: string[] | undefined;
}

const FundingContext = createContext<IFundingContext>({
winningChoice: undefined,
paidFees: undefined,
loserRequiredFunding: undefined,
winnerRequiredFunding: undefined,
fundedChoices: undefined,
@@ -53,17 +55,16 @@ export const ClassicAppealProvider: React.FC<{
const { id } = useParams();
const { data } = useClassicAppealQuery(id);
const dispute = data?.dispute;
const paidFees = getPaidFees(data?.dispute);
const winningChoice = getWinningChoice(data?.dispute);
const { data: appealCost } = useAppealCost(id);
const arbitrable = data?.dispute?.arbitrated.id;
const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrable);
const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrable as `0x${string}`);
const { data: multipliers } = useDisputeKitClassicMultipliers();
const options = ["Refuse to Arbitrate"].concat(
disputeDetails?.answers?.map((answer: { title: string; description: string }) => {
return answer.title;
})
);

const [selectedOption, setSelectedOption] = useState<Option>();

const options = useMemo(() => getOptions(disputeDetails, data?.dispute), [disputeDetails, data]);

const loserSideCountdown = useLoserSideCountdown(
dispute?.lastPeriodChange,
dispute?.court.timesPerPeriod[Periods.appeal],
@@ -85,7 +86,6 @@ export const ClassicAppealProvider: React.FC<{
[appealCost, multipliers]
);
const fundedChoices = getFundedChoices(data?.dispute);
const [selectedOption, setSelectedOption] = useState<number | undefined>();

return (
<CountdownContext.Provider
@@ -101,12 +101,11 @@ export const ClassicAppealProvider: React.FC<{
value={useMemo(
() => ({
winningChoice,
paidFees,
loserRequiredFunding,
winnerRequiredFunding,
fundedChoices,
}),
[winningChoice, paidFees, loserRequiredFunding, winnerRequiredFunding, fundedChoices]
[winningChoice, loserRequiredFunding, winnerRequiredFunding, fundedChoices]
)}
>
<OptionsContext.Provider value={options}>{children}</OptionsContext.Provider>
@@ -133,9 +132,22 @@ const getCurrentLocalRound = (dispute?: ClassicAppealQuery["dispute"]) => {
return getLocalRounds(dispute.disputeKitDispute)[adjustedRoundIndex];
};

const getPaidFees = (dispute?: ClassicAppealQuery["dispute"]) => {
const currentLocalRound = getCurrentLocalRound(dispute);
return currentLocalRound?.paidFees.map((amount: string) => BigInt(amount));
const getOptions = (dispute?: DisputeDetails, classicDispute?: ClassicAppealQuery["dispute"]) => {
if (!dispute) return [];
const currentLocalRound = getCurrentLocalRound(classicDispute);
const classicAnswers = currentLocalRound?.answers;

const options = dispute.answers.map((answer) => {
const classicAnswer = classicAnswers?.find((classicAnswer) => BigInt(classicAnswer.answerId) == BigInt(answer.id));
// converting hexadecimal id to stringified bigint to match id fomr subgraph
return {
...answer,
id: BigInt(answer.id).toString(),
paidFee: classicAnswer?.paidFee ?? "0",
funded: classicAnswer?.funded ?? false,
};
});
return options;
};

const getFundedChoices = (dispute?: ClassicAppealQuery["dispute"]) => {
14 changes: 7 additions & 7 deletions web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ interface IAppealHistory {

const AppealHistory: React.FC<IAppealHistory> = ({ isAppealMiniGuideOpen, toggleAppealMiniGuide }) => {
const options = useOptionsContext();
const { winningChoice, paidFees, fundedChoices } = useFundingContext();
const { winningChoice, fundedChoices } = useFundingContext();

return options && options.length > 2 ? (
<div>
@@ -38,13 +38,13 @@ const AppealHistory: React.FC<IAppealHistory> = ({ isAppealMiniGuideOpen, toggle
/>
</AppealHeader>
<OptionsContainer>
{options.map((option, index) => (
{options?.map((option) => (
<OptionCard
key={option + index}
text={option}
winner={index.toString() === winningChoice}
funding={BigInt(paidFees?.[index] ?? "0")}
required={fundedChoices?.includes(index.toString()) ? BigInt(paidFees?.[index] ?? "0") : undefined}
key={option.id}
text={option.title}
winner={option.id === winningChoice}
funding={BigInt(option.paidFee ?? 0)}
required={fundedChoices?.includes(option.id) ? BigInt(option.paidFee ?? 0) : undefined}
canBeSelected={false}
/>
))}
2 changes: 1 addition & 1 deletion web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ const useFundAppeal = (parsedAmount, insufficientBalance) => {
query: {
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance,
},
args: [BigInt(id ?? 0), BigInt(selectedOption ?? 0)],
args: [BigInt(id ?? 0), BigInt(selectedOption?.id ?? 0)],
value: parsedAmount,
});

Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ interface IStageOne {
}

const StageOne: React.FC<IStageOne> = ({ setAmount }) => {
const { paidFees, winningChoice, loserRequiredFunding, winnerRequiredFunding, fundedChoices } = useFundingContext();
const { winningChoice, loserRequiredFunding, winnerRequiredFunding } = useFundingContext();
const options = useOptionsContext();
const { loserSideCountdown } = useCountdownContext();
const { selectedOption, setSelectedOption } = useSelectedOptionContext();
@@ -39,22 +39,21 @@ const StageOne: React.FC<IStageOne> = ({ setAmount }) => {
<StageExplainer countdown={loserSideCountdown} stage={1} />
<label> Which option do you want to fund? </label>
<OptionsContainer>
{!isUndefined(paidFees) &&
!isUndefined(winnerRequiredFunding) &&
{!isUndefined(winnerRequiredFunding) &&
!isUndefined(loserRequiredFunding) &&
options?.map((answer: string, i: number) => {
const requiredFunding = i.toString() === winningChoice ? winnerRequiredFunding : loserRequiredFunding;
options?.map((option) => {
const requiredFunding = option.id === winningChoice ? winnerRequiredFunding : loserRequiredFunding;
return (
<OptionCard
key={answer}
text={answer}
selected={i === selectedOption}
winner={i.toString() === winningChoice}
funding={paidFees[i] ? BigInt(paidFees[i]) : 0n}
key={option.id}
text={option.title}
selected={option.id === selectedOption?.id}
winner={option.id === winningChoice}
funding={BigInt(option.paidFee ?? 0)}
required={requiredFunding}
canBeSelected={!fundedChoices?.includes(i.toString())}
canBeSelected={!option?.funded}
onClick={() => {
setSelectedOption(i);
setSelectedOption(option);
setAmount(formatUnitsWei(requiredFunding));
}}
/>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useMemo } from "react";
import styled from "styled-components";

import Skeleton from "react-loading-skeleton";
@@ -31,31 +31,33 @@ interface IStageTwo {
}

const StageTwo: React.FC<IStageTwo> = ({ setAmount }) => {
const { paidFees, winningChoice, winnerRequiredFunding, fundedChoices } = useFundingContext();
const { winningChoice, winnerRequiredFunding, fundedChoices } = useFundingContext();
const { winnerSideCountdown } = useCountdownContext();
const options = useOptionsContext();
const { selectedOption, setSelectedOption } = useSelectedOptionContext();
const choice = useMemo(() => options?.find((option) => option.id === winningChoice), [options, winningChoice]);

useEffect(() => {
if (!isUndefined(winningChoice)) setSelectedOption(parseInt(winningChoice));
if (!isUndefined(choice)) setSelectedOption(choice);
if (!isUndefined(winnerRequiredFunding)) setAmount(formatUnitsWei(winnerRequiredFunding));
}, [winnerRequiredFunding, winningChoice]);
}, [winnerRequiredFunding, choice]);

return (
<Container>
{!isUndefined(winningChoice) && !isUndefined(fundedChoices) && !isUndefined(paidFees) ? (
{!isUndefined(choice) && !isUndefined(fundedChoices) ? (
<>
{fundedChoices.length > 0 && !fundedChoices.includes(winningChoice) ? (
{fundedChoices.length > 0 && !choice.funded ? (
<>
<StageExplainer stage={2} countdown={winnerSideCountdown} />
<OptionsContainer>
<OptionCard
text={options![winningChoice!]}
selected={parseInt(winningChoice) === selectedOption}
text={choice.title}
selected={choice.id === selectedOption?.id}
winner={true}
funding={paidFees![winningChoice!] ? BigInt(paidFees![winningChoice!]) : 0n}
funding={BigInt(choice.paidFee ?? 0)}
required={winnerRequiredFunding!}
canBeSelected={false}
onClick={() => setSelectedOption(parseInt(winningChoice!, 10))}
onClick={() => setSelectedOption(choice)}
/>
</OptionsContainer>
</>
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React from "react";
import styled from "styled-components";

import Skeleton from "react-loading-skeleton";

import { Box } from "@kleros/ui-components-library";

import HourglassIcon from "svgs/icons/hourglass.svg";

import { useFundingContext, useOptionsContext } from "hooks/useClassicAppealContext";
import { useOptionsContext } from "hooks/useClassicAppealContext";
import { secondsToDayHourMinute } from "utils/date";
import { isUndefined } from "utils/index";

@@ -56,7 +54,6 @@ const StageOneExplanation: React.FC = () => (
);

const StageTwoExplanation: React.FC = () => {
const { fundedChoices } = useFundingContext();
const options = useOptionsContext();
return (
<div>
@@ -69,14 +66,8 @@ const StageTwoExplanation: React.FC = () => {
</label>
<label>
{" "}
Following choice was funded in the stage 1 :{" "}
<small>
{!isUndefined(fundedChoices) && !isUndefined(options)
? fundedChoices.map((choice) =>
isUndefined(options[choice]) ? <Skeleton key={choice} width={50} height={18} /> : options[choice]
)
: null}
</small>
Following choices were funded in the stage 1 :{" "}
<small>{options?.map((option) => (option?.funded ? option.title : null))}</small>
</label>
</div>
);
7 changes: 3 additions & 4 deletions web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import React, { useState } from "react";

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

import { useOptionsContext, useSelectedOptionContext } from "hooks/useClassicAppealContext";
import { useSelectedOptionContext } from "hooks/useClassicAppealContext";
import { isUndefined } from "utils/index";

import HowItWorks from "components/HowItWorks";
@@ -23,7 +23,6 @@ const Classic: React.FC<IClassic> = ({ isAppealMiniGuideOpen, toggleAppealMiniGu
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [amount, setAmount] = useState("");
const { selectedOption } = useSelectedOptionContext();
const options = useOptionsContext();

return (
<>
@@ -34,7 +33,7 @@ const Classic: React.FC<IClassic> = ({ isAppealMiniGuideOpen, toggleAppealMiniGu
popupType={PopupType.APPEAL}
setIsOpen={setIsPopupOpen}
setAmount={setAmount}
option={!isUndefined(options) && !isUndefined(selectedOption) ? options[selectedOption] : ""}
option={!isUndefined(selectedOption) ? selectedOption.title : ""}
amount={amount}
/>
)}
@@ -48,7 +47,7 @@ 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} setAmount={setAmount} setIsOpen={setIsPopupOpen} />
<Fund amount={amount as `${number}`} setAmount={setAmount} setIsOpen={setIsPopupOpen} />
</>
);
};
6 changes: 3 additions & 3 deletions web/src/pages/Cases/CaseDetails/Voting/Classic/Commit.tsx
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch })
const [_, setSalt] = useLocalStorage(saltKey);

const handleCommit = useCallback(
async (choice: number) => {
async (choice: bigint) => {
const message = { message: saltKey };
const rawSalt = !isUndefined(signingAccount)
? await signingAccount.signMessage(message)
@@ -56,12 +56,12 @@ const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch })
if (isUndefined(rawSalt)) return;

const salt = keccak256(rawSalt);
setSalt(JSON.stringify({ salt, choice }));
setSalt(JSON.stringify({ salt, choice: choice.toString() }));
const commit = keccak256(encodePacked(["uint256", "uint256"], [BigInt(choice), BigInt(salt)]));
const { request } = await simulateDisputeKitClassicCastCommit(wagmiConfig, {
args: [parsedDisputeID, parsedVoteIDs, commit],
});
if (walletClient) {
if (walletClient && publicClient) {
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient).then(({ status }) => {
setIsOpen(status);
});
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import { isUndefined } from "utils/index";
import { EnsureChain } from "components/EnsureChain";

import JustificationArea from "./JustificationArea";
import { Answer } from "@kleros/kleros-sdk";

const MainContainer = styled.div`
width: 100%;
@@ -41,23 +42,23 @@ const RefuseToArbitrateContainer = styled.div`

interface IOptions {
arbitrable: `0x${string}`;
handleSelection: (arg0: number) => Promise<void>;
handleSelection: (arg0: bigint) => Promise<void>;
justification?: string;
setJustification?: (arg0: string) => void;
}

const Options: React.FC<IOptions> = ({ arbitrable, handleSelection, justification, setJustification }) => {
const { id } = useParams();
const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrable);
const [chosenOption, setChosenOption] = useState(-1);
const [chosenOption, setChosenOption] = useState(BigInt(-1));
const [isSending, setIsSending] = useState(false);

const onClick = useCallback(
async (id: number) => {
async (id: bigint) => {
setIsSending(true);
setChosenOption(id);
await handleSelection(id);
setChosenOption(-1);
setChosenOption(BigInt(-1));
setIsSending(false);
},
[handleSelection, setChosenOption, setIsSending]
@@ -66,19 +67,19 @@ const Options: React.FC<IOptions> = ({ arbitrable, handleSelection, justificatio
return id ? (
<>
<MainContainer dir="auto">
<ReactMarkdown>{disputeDetails?.question}</ReactMarkdown>
<ReactMarkdown>{disputeDetails?.question ?? ""}</ReactMarkdown>
{!isUndefined(justification) && !isUndefined(setJustification) ? (
<JustificationArea {...{ justification, setJustification }} />
) : null}
<OptionsContainer>
{disputeDetails?.answers?.map((answer: { title: string; description: string }, i: number) => {
{disputeDetails?.answers?.map((answer: Answer) => {
return (
<EnsureChain key={answer.title}>
<Button
text={answer.title}
disabled={isSending}
isLoading={chosenOption === i + 1}
onClick={() => onClick(i + 1)}
isLoading={chosenOption === BigInt(answer.id)}
onClick={() => onClick(BigInt(answer.id))}
/>
</EnsureChain>
);
@@ -91,8 +92,8 @@ const Options: React.FC<IOptions> = ({ arbitrable, handleSelection, justificatio
variant="secondary"
text="Refuse to Arbitrate"
disabled={isSending}
isLoading={chosenOption === 0}
onClick={() => onClick(0)}
isLoading={chosenOption === BigInt(0)}
onClick={() => onClick(BigInt(0))}
/>
</EnsureChain>
</RefuseToArbitrateContainer>
20 changes: 10 additions & 10 deletions web/src/pages/Cases/CaseDetails/Voting/Classic/Reveal.tsx
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";
import InfoCard from "components/InfoCard";

import JustificationArea from "./JustificationArea";
import { Answer } from "@kleros/kleros-sdk";

const Container = styled.div`
width: 100%;
@@ -65,15 +66,15 @@ const Reveal: React.FC<IReveal> = ({ arbitrable, voteIDs, setIsOpen, commit, isR
const handleReveal = useCallback(async () => {
setIsSending(true);
const { salt, choice } = isUndefined(storedSaltAndChoice)
? await getSaltAndChoice(signingAccount, generateSigningAccount, saltKey, disputeDetails.answers, commit)
? await getSaltAndChoice(signingAccount, generateSigningAccount, saltKey, disputeDetails?.answers, commit)
: JSON.parse(storedSaltAndChoice);
if (isUndefined(choice)) return;
const { request } = await catchShortMessage(
simulateDisputeKitClassicCastVote(wagmiConfig, {
args: [parsedDisputeID, parsedVoteIDs, BigInt(choice), BigInt(salt), justification],
})
);
if (request && walletClient) {
if (request && walletClient && publicClient) {
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient).then(({ status }) => {
setIsOpen(status);
});
@@ -102,7 +103,7 @@ const Reveal: React.FC<IReveal> = ({ arbitrable, voteIDs, setIsOpen, commit, isR
) : isRevealPeriod ? (
<>
<ReactMarkdownWrapper dir="auto">
<ReactMarkdown>{disputeDetails?.question}</ReactMarkdown>
<ReactMarkdown>{disputeDetails?.question ?? ""}</ReactMarkdown>
</ReactMarkdownWrapper>
<JustificationArea {...{ justification, setJustification }} />
<StyledButton
@@ -124,7 +125,7 @@ const getSaltAndChoice = async (
signingAccount: PrivateKeyAccount | undefined,
generateSigningAccount: () => Promise<PrivateKeyAccount | undefined> | undefined,
saltKey: string,
answers: { title: string; description: string }[],
answers: Answer[],
commit: string
) => {
const message = { message: saltKey };
@@ -137,16 +138,15 @@ const getSaltAndChoice = async (
if (isUndefined(rawSalt)) return;
const salt = keccak256(rawSalt);

answers.unshift({ title: "Refuse To Arbitrate", description: "Refuse To Arbitrate" });
const { choice } = answers.reduce<{ found: boolean; choice: number }>(
(acc, _, i) => {
const { choice } = answers.reduce<{ found: boolean; choice: bigint }>(
(acc, answer) => {
if (acc.found) return acc;
const innerCommit = keccak256(encodePacked(["uint256", "uint256"], [BigInt(i), BigInt(salt)]));
const innerCommit = keccak256(encodePacked(["uint256", "uint256"], [BigInt(answer.id), BigInt(salt)]));
if (innerCommit === commit) {
return { found: true, choice: i };
return { found: true, choice: BigInt(answer.id) };
} else return acc;
},
{ found: false, choice: -1 }
{ found: false, choice: BigInt(-1) }
);
return { salt, choice };
};
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ const VoteStatus: React.FC<{

return (
<StyledLabel>
{isUndefined(choice) ? "Pending Vote" : <StyledSmall>{getVoteChoice(parseInt(choice), answers)}</StyledSmall>}
{isUndefined(choice) ? "Pending Vote" : <StyledSmall>{getVoteChoice(choice, answers)}</StyledSmall>}
</StyledLabel>
);
};
Original file line number Diff line number Diff line change
@@ -114,7 +114,7 @@ const AccordionContent: React.FC<{

return (
<AccordionContentContainer>
{!isUndefined(choice) && <VotedText dir="auto">{getVoteChoice(parseInt(choice), answers)}</VotedText>}
{!isUndefined(choice) && <VotedText dir="auto">{getVoteChoice(choice, answers)}</VotedText>}

{justification ? (
<JustificationText dir="auto">{justification}</JustificationText>
13 changes: 7 additions & 6 deletions web/src/utils/getVoteChoice.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Answer } from "@kleros/kleros-sdk";
import { isUndefined } from ".";

export const getVoteChoice = (vote: number, answers: { title: string }[]) => {
const selectedAnswer = answers?.[vote - 1]?.title;
if (vote === 0) {
return "Refuse to arbitrate";
} else if (!isUndefined(selectedAnswer)) {
return selectedAnswer;
export const getVoteChoice = (vote: string, answers: Answer[]) => {
// answer.id is hexadecimal number
const selectedAnswer = answers?.find((answer) => BigInt(answer.id) === BigInt(vote));

if (!isUndefined(selectedAnswer)) {
return selectedAnswer.title;
} else {
return `Answer 0x${vote}`;
}