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
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
12 changes: 8 additions & 4 deletions kleros-sdk/src/dataMappings/utils/disputeDetailsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
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);
Expand All @@ -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
Expand Up @@ -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
Expand Up @@ -63,6 +63,7 @@ interface Evidence {
fileTypeExtension: String
}


############
# Entities #
############
Expand Down Expand Up @@ -267,17 +268,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!
Expand Down
8 changes: 7 additions & 1 deletion subgraph/core/src/DisputeKitClassic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ensureClassicContributionFromEvent } from "./entities/ClassicContributi
import { createClassicDisputeFromEvent } from "./entities/ClassicDispute";
import {
createClassicRound,
ensureAnswer,
updateChoiceFundingFromContributionEvent,
updateCountsAndGetCurrentRuling,
} from "./entities/ClassicRound";
Expand Down Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions subgraph/core/src/entities/ClassicContribution.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ClassicContribution } from "../../generated/schema";
import { Contribution as ContributionEvent, Withdrawal } from "../../generated/DisputeKitClassic/DisputeKitClassic";
import { DISPUTEKIT_ID } from "../DisputeKitClassic";
import { ensureUser } from "./User";

export function ensureClassicContributionFromEvent<T>(event: T): ClassicContribution | null {
if (!(event instanceof ContributionEvent) && !(event instanceof Withdrawal)) return null;
const coreDisputeID = event.params._coreDisputeID.toString();
const coreRoundIndex = event.params._coreRoundID.toString();
const roundID = `${DISPUTEKIT_ID}-${coreDisputeID}-${coreRoundIndex}`;
ensureUser(event.params._contributor.toHexString());
const contributor = event.params._contributor.toHexString();
const choice = event.params._choice;

Expand Down
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;
Expand All @@ -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 {
Expand All @@ -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 };
}
Expand All @@ -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();
}
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.10.3",
"version": "0.11.0",
"drtVersion": "0.11.0",
"license": "MIT",
"scripts": {
Expand Down
8 changes: 3 additions & 5 deletions web/src/components/Verdict/DisputeTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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])} / ${
Expand All @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion web/src/hooks/queries/useClassicAppealQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ const classicAppealQuery = graphql(`
localRounds {
... on ClassicRound {
winningChoice
paidFees
answers {
answerId
count
paidFee
funded
}
fundedChoices
appealFeesDispersed
totalFeeDispersed
Expand Down
Loading