From b2c609fe02469b375f468b557dd8f80a50d0e13d Mon Sep 17 00:00:00 2001 From: alcercu Date: Thu, 22 Dec 2022 14:51:54 +0100 Subject: [PATCH 1/2] refactor(subgraph): simplify KlerosCore mappings --- subgraph/schema.graphql | 119 ++++--- subgraph/src/DisputeKitClassic.ts | 7 - subgraph/src/KlerosCore.ts | 328 ++++++------------- subgraph/src/entities/Court.ts | 56 ++++ subgraph/src/entities/Dispute.ts | 44 +++ subgraph/src/entities/DisputeKit.ts | 43 +++ subgraph/src/entities/Draw.ts | 16 + subgraph/src/entities/Juror.ts | 18 + subgraph/src/entities/JurorTokensPerCourt.ts | 85 +++++ subgraph/src/entities/Round.ts | 53 +++ subgraph/src/entities/TokenAndEthShift.ts | 16 + subgraph/src/utils.ts | 3 +- subgraph/subgraph.yaml | 6 +- 13 files changed, 508 insertions(+), 286 deletions(-) create mode 100644 subgraph/src/entities/Court.ts create mode 100644 subgraph/src/entities/Dispute.ts create mode 100644 subgraph/src/entities/DisputeKit.ts create mode 100644 subgraph/src/entities/Draw.ts create mode 100644 subgraph/src/entities/Juror.ts create mode 100644 subgraph/src/entities/JurorTokensPerCourt.ts create mode 100644 subgraph/src/entities/Round.ts create mode 100644 subgraph/src/entities/TokenAndEthShift.ts diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 9c339bac4..2d75a6d73 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -1,16 +1,15 @@ enum Period { - Evidence - Commit - Vote - Appeal - Execution + evidence + commit + vote + appeal + execution } type Court @entity { id: ID! - policy: String - hiddenVotes: Boolean! parent: Court + hiddenVotes: Boolean! children: [Court!]! @derivedFrom(field: "parent") minStake: BigInt! alpha: BigInt! @@ -18,14 +17,84 @@ type Court @entity { jurorsForCourtJump: BigInt! timesPerPeriod: [BigInt!]! supportedDisputeKits: [DisputeKit!]! - disputes: [Dispute!]! @derivedFrom(field: "courtID") + disputes: [Dispute!]! @derivedFrom(field: "court") numberDisputes: BigInt! stakedJurors: [JurorTokensPerCourt!]! @derivedFrom(field: "court") numberStakedJurors: BigInt! - tokens: [JurorTokensPerCourt!]! @derivedFrom(field: "court") stake: BigInt! paidETH: BigInt! paidPNK: BigInt! + policy: String +} + +type Dispute @entity { + id: ID! + court: Court! + arbitrated: Bytes! + period: Period! + ruled: Boolean! + lastPeriodChange: BigInt! + rounds: [Round!]! @derivedFrom(field: "dispute") + currentRound: Round! + currentRoundIndex: BigInt! + shifts: [TokenAndETHShift!]! @derivedFrom(field: "dispute") + disputeKitDispute: DisputeKitDispute @derivedFrom(field: "coreDispute") +} + +type Round @entity { + id: ID! # Set to `${dispute.id}-${dispute.rounds.length}` + disputeKit: DisputeKit! + tokensAtStakePerJuror: BigInt! + totalFeesForJurors: BigInt! + nbVotes: BigInt! + repartitions: BigInt! + penalties: BigInt! + drawnJurors: [Draw!]! @derivedFrom(field: "round") + dispute: Dispute! +} + +interface DisputeKitRound { + id: ID! # Set to `${disputeKit.id}-{dispute.id}-{dispute.rounds.length}` +} + +type ClassicRound implements DisputeKitRound @entity { + id: ID! # Set to `${disputeKit.id}-{dispute.id}-{dispute.rounds.length}` + votes: [ClassicVote!]! + winningChoice: BigInt! + counts: [BigInt!]! + tied: Boolean! + totalVoted: BigInt! + totalCommited: BigInt! + paidFees: [BigInt!]! + contributions: [Contribution!]! @derivedFrom(field: "round") + feeRewards: BigInt! + nbVotes: BigInt! +} + +type Contribution @entity { + id: ID! # Set to `${dispute.id}-${round.id}-${contributor}-${choice}` + contributor: Bytes! + amount: BigInt! + choice: BigInt! + round: ClassicRound! +} + +type ClassicVote @entity { + id: ID! +} + +interface DisputeKitDispute { + id: ID! # Set to ${disputeKit.id}-{disputeKit.disputes.length} + coreDispute: Dispute! +} + +type ClassicDispute implements DisputeKitDispute @entity { + id: ID! # Set to ${disputeKit.id}-{disputeKit.disputes.length} + coreDispute: Dispute! + + numberOfChoices: BigInt! + jumped: Boolean! + extraData: Bytes! } type Juror @entity { @@ -66,21 +135,6 @@ type Evidence @entity { sender: Bytes! } -type Round @entity { - id: ID! # Set to `${dispute.id}-${currentRound}` - dispute: Dispute! - disputeKitID: DisputeKit! - tokensAtStakePerJuror: BigInt! - totalFeesForJurors: BigInt! - nbVotes: BigInt! - totalVoted: BigInt! - repartitions: BigInt! - penalties: BigInt! - drawnJurors: [Draw!]! @derivedFrom(field: "round") - votes: [Vote!]! @derivedFrom(field: "round") - currentDecision: BigInt -} - type Vote @entity { id: ID! # Set to `${coreDisputeID}-${coreRoundID}-${jurorAddress}` dispute: Dispute! @@ -98,21 +152,6 @@ type Draw @entity { voteID: BigInt! } -type Dispute @entity { - id: ID! - courtID: Court! - arbitrated: Bytes! - period: Period! - ruled: Boolean! - lastPeriodChange: BigInt! - rounds: [Round!]! @derivedFrom(field: "dispute") - draws: [Draw!]! @derivedFrom(field: "dispute") - votes: [Vote!]! @derivedFrom(field: "dispute") - currentRound: Int! - shifts: [TokenAndETHShift!]! @derivedFrom(field: "dispute") - gatewayDispute: GatewayDispute! @derivedFrom(field: "homeDispute") -} - type DisputeKit @entity { id: ID! address: Bytes @@ -120,7 +159,7 @@ type DisputeKit @entity { children: [DisputeKit!]! @derivedFrom(field: "parent") needsFreezing: Boolean! depthLevel: BigInt! - rounds: [Round!]! @derivedFrom(field: "disputeKitID") + rounds: [Round!]! @derivedFrom(field: "disputeKit") courts: [Court!]! @derivedFrom(field: "supportedDisputeKits") } diff --git a/subgraph/src/DisputeKitClassic.ts b/subgraph/src/DisputeKitClassic.ts index 34d086702..e16b6cde0 100644 --- a/subgraph/src/DisputeKitClassic.ts +++ b/subgraph/src/DisputeKitClassic.ts @@ -39,13 +39,6 @@ export function handleJustificationEvent(event: JustificationEvent): void { ); if (currentRound) { const contract = DisputeKitClassic.bind(event.address); - currentRound.totalVoted = contract.getRoundInfo( - disputeID, - BigInt.fromI32(dispute.currentRound), - BigInt.fromI32(0) - ).value2; - currentRound.currentDecision = contract.currentRuling(disputeID); - currentRound.save(); const juror = event.params._juror.toHexString(); const vote = new Vote( `${disputeID.toString()}-${currentRoundIndex.toString()}-${juror}` diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 64e1185a3..237f7ad97 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -1,5 +1,3 @@ -import { Address, BigInt } from "@graphprotocol/graph-ts"; -import { ZERO } from "./utils"; import { KlerosCore, AppealDecision, @@ -13,19 +11,20 @@ import { StakeSet, TokenAndETHShift as TokenAndETHShiftEvent, } from "../generated/KlerosCore/KlerosCore"; +import { ZERO, ONE } from "./utils"; import { - Juror, - TokenAndETHShift, - JurorTokensPerCourt, - Round, - Draw, - Dispute, - DisputeKit, - Court, -} from "../generated/schema"; + ensureCourt, + createCourtFromEvent, + getFeeForJuror, +} from "./entities/Court"; +import { + createDisputeKitFromEvent, + filterSupportedDisputeKits, +} from "./entities/DisputeKit"; +import { createDisputeFromEvent, ensureDispute } from "./entities/Dispute"; +import { createRoundFromRoundInfo } from "./entities/Round"; import { updateCases, - updateActiveJurors, updatePaidETH, updateStakedPNK, updateCasesRuled, @@ -33,278 +32,137 @@ import { updateRedistributedPNK, getDelta, } from "./datapoint"; +import { ensureJuror } from "./entities/Juror"; +import { + ensureJurorTokensPerCourt, + updateJurorStake, +} from "./entities/JurorTokensPerCourt"; +import { createDrawFromEvent } from "./entities/Draw"; +import { createTokenAndEthShiftFromEvent } from "./entities/TokenAndEthShift"; function getPeriodName(index: i32): string { - const periodArray = ["Evidence", "Commit", "Vote", "Appeal", "Execution"]; + const periodArray = ["evidence", "commit", "vote", "appeal", "execution"]; return periodArray.at(index) || "None"; } export function handleCourtCreated(event: CourtCreated): void { - const court = new Court(event.params._courtID.toString()); - court.hiddenVotes = event.params._hiddenVotes; - court.parent = event.params._parent.toString(); - court.minStake = event.params._minStake; - court.alpha = event.params._alpha; - court.feeForJuror = event.params._feeForJuror; - court.jurorsForCourtJump = event.params._jurorsForCourtJump; - court.timesPerPeriod = event.params._timesPerPeriod; - court.supportedDisputeKits = event.params._supportedDisputeKits.map( - (disputeKitID: BigInt) => disputeKitID.toString() - ); - court.numberDisputes = ZERO; - court.numberStakedJurors = ZERO; - court.stake = ZERO; - court.paidETH = ZERO; - court.paidPNK = ZERO; - court.save(); + createCourtFromEvent(event); } export function handleCourtModified(event: CourtModified): void { - const court = Court.load(event.params._courtID.toString()); - if (court) { - const contract = KlerosCore.bind(event.address); - const courtContractState = contract.courts(event.params._courtID); - court.hiddenVotes = courtContractState.value1; - court.minStake = courtContractState.value2; - court.alpha = courtContractState.value3; - court.feeForJuror = courtContractState.value4; - court.jurorsForCourtJump = courtContractState.value5; - court.timesPerPeriod = contract.getTimesPerPeriod(event.params._courtID); - court.save(); - } + const contract = KlerosCore.bind(event.address); + const courtContractState = contract.courts(event.params._courtID); + const court = ensureCourt(event.params._courtID.toString()); + court.hiddenVotes = courtContractState.value1; + court.minStake = courtContractState.value2; + court.alpha = courtContractState.value3; + court.feeForJuror = courtContractState.value4; + court.jurorsForCourtJump = courtContractState.value5; + court.timesPerPeriod = contract.getTimesPerPeriod(event.params._courtID); + court.save(); } export function handleDisputeKitCreated(event: DisputeKitCreated): void { - const disputeKit = new DisputeKit(event.params._disputeKitID.toString()); - disputeKit.parent = event.params._parent.toString(); - disputeKit.address = event.params._disputeKitAddress; - disputeKit.needsFreezing = false; - const parent = DisputeKit.load(event.params._parent.toString()); - disputeKit.depthLevel = parent - ? parent.depthLevel.plus(BigInt.fromI32(1)) - : BigInt.fromI32(0); - disputeKit.save(); + createDisputeKitFromEvent(event); } export function handleDisputeKitEnabled(event: DisputeKitEnabled): void { - const court = Court.load(event.params._courtID.toString()); - if (court) { - const isEnable = event.params._enable; - const disputeKitID = event.params._disputeKitID.toString(); - court.supportedDisputeKits = isEnable - ? court.supportedDisputeKits.concat([disputeKitID]) - : filterSupportedDisputeKits(court.supportedDisputeKits, disputeKitID); - court.save(); - } -} - -function filterSupportedDisputeKits( - supportedDisputeKits: string[], - disputeKitID: string -): string[] { - let result: string[] = []; - for (let i = 0; i < supportedDisputeKits.length; i++) - if (supportedDisputeKits[i] !== disputeKitID) - result = result.concat([supportedDisputeKits[i]]); - return result; -} - -export function handleAppealDecision(event: AppealDecision): void { - const disputeID = event.params._disputeID; - const dispute = Dispute.load(disputeID.toString()); - if (dispute) { - const contract = KlerosCore.bind(event.address); - const newRoundIndex = dispute.currentRound + 1; - const round = new Round( - `${disputeID.toString()}-${newRoundIndex.toString()}` - ); - const roundInfo = contract.getRoundInfo( - disputeID, - BigInt.fromI64(newRoundIndex) - ); - const courtID = dispute.courtID; - const courtStorage = contract.courts(BigInt.fromString(courtID)); - round.dispute = disputeID.toString(); - round.tokensAtStakePerJuror = roundInfo.value0; - round.totalFeesForJurors = roundInfo.value1; - round.nbVotes = roundInfo.value1.div(courtStorage.value4); - round.totalVoted = BigInt.fromI32(0); - round.repartitions = roundInfo.value2; - round.penalties = roundInfo.value3; - round.disputeKitID = roundInfo.value5.toString(); - dispute.currentRound = newRoundIndex; - round.save(); - dispute.save(); - } + const court = ensureCourt(event.params._courtID.toString()); + const isEnable = event.params._enable; + const disputeKitID = event.params._disputeKitID.toString(); + court.supportedDisputeKits = isEnable + ? court.supportedDisputeKits.concat([disputeKitID]) + : filterSupportedDisputeKits(court.supportedDisputeKits, disputeKitID); + court.save(); } export function handleDisputeCreation(event: DisputeCreation): void { const contract = KlerosCore.bind(event.address); const disputeID = event.params._disputeID; - const dispute = new Dispute(disputeID.toString()); const disputeStorage = contract.disputes(disputeID); - const courtID = disputeStorage.value0; - const court = Court.load(courtID.toString()); - dispute.arbitrated = event.params._arbitrable; - dispute.courtID = courtID.toString(); - dispute.period = "Evidence"; - dispute.ruled = false; - dispute.lastPeriodChange = disputeStorage.value4; - dispute.currentRound = 0; - const roundInfo = contract.getRoundInfo(disputeID, BigInt.fromString("0")); - const round = new Round(`${disputeID.toString()}-0`); - round.dispute = disputeID.toString(); - round.tokensAtStakePerJuror = roundInfo.value0; - round.totalFeesForJurors = roundInfo.value1; - round.nbVotes = court ? roundInfo.value1.div(court.feeForJuror) : ZERO; - round.totalVoted = BigInt.fromI32(0); - round.repartitions = roundInfo.value2; - round.penalties = roundInfo.value3; - round.disputeKitID = roundInfo.value5.toString(); - if (court) { - court.numberDisputes = court.numberDisputes.plus(BigInt.fromI32(1)); - } - dispute.save(); - round.save(); - updateCases(BigInt.fromI32(1), event.block.timestamp); + const courtID = disputeStorage.value0.toString(); + const court = ensureCourt(courtID); + court.numberDisputes = court.numberDisputes.plus(ONE); + court.save(); + createDisputeFromEvent(event); + const roundInfo = contract.getRoundInfo(disputeID, ZERO); + createRoundFromRoundInfo(disputeID, ZERO, court.feeForJuror, roundInfo); + updateCases(ONE, event.block.timestamp); } export function handleNewPeriod(event: NewPeriod): void { - const disputeID = event.params._disputeID; - const dispute = Dispute.load(disputeID.toString()); - if (dispute) { - dispute.period = getPeriodName(event.params._period); - dispute.save(); - } + const disputeID = event.params._disputeID.toString(); + const dispute = ensureDispute(disputeID); + dispute.period = getPeriodName(event.params._period); + dispute.lastPeriodChange = event.block.timestamp; + dispute.save(); } -function updateJurorStake( - jurorAddress: string, - courtID: string, - contract: KlerosCore, - timestamp: BigInt -): void { - const juror = Juror.load(jurorAddress); - const court = Court.load(courtID); - const jurorTokens = JurorTokensPerCourt.load(`${jurorAddress}-${courtID}`); - if (juror && court && jurorTokens) { - const jurorBalance = contract.getJurorBalance( - Address.fromString(jurorAddress), - BigInt.fromString(courtID) - ); - const previousStake = jurorTokens.staked; - jurorTokens.staked = jurorBalance.value0; - jurorTokens.locked = jurorBalance.value1; - jurorTokens.save(); - const stakeDelta = jurorTokens.staked.minus(previousStake); - const previousTotalStake = juror.totalStake; - juror.totalStake = juror.totalStake.plus(stakeDelta); - court.stake = court.stake.plus(stakeDelta); - let activeJurorsDelta: BigInt; - let numberStakedJurorsDelta: BigInt; - if (previousTotalStake.equals(ZERO)) { - activeJurorsDelta = BigInt.fromI32(1); - numberStakedJurorsDelta = BigInt.fromI32(1); - } else if (previousStake.equals(ZERO)) { - activeJurorsDelta = ZERO; - numberStakedJurorsDelta = BigInt.fromI32(1); - } else { - activeJurorsDelta = ZERO; - numberStakedJurorsDelta = ZERO; - } - court.numberStakedJurors = court.numberStakedJurors.plus( - numberStakedJurorsDelta - ); - updateActiveJurors(activeJurorsDelta, timestamp); - juror.save(); - court.save(); - } +export function handleAppealDecision(event: AppealDecision): void { + const contract = KlerosCore.bind(event.address); + const disputeID = event.params._disputeID; + const dispute = ensureDispute(disputeID.toString()); + const newRoundIndex = dispute.currentRoundIndex.plus(ONE); + const roundID = `${disputeID}-${newRoundIndex.toString()}`; + dispute.currentRoundIndex = newRoundIndex; + dispute.currentRound = roundID; + dispute.save(); + const feeForJuror = getFeeForJuror(dispute.court); + const roundInfo = contract.getRoundInfo(disputeID, newRoundIndex); + createRoundFromRoundInfo(disputeID, newRoundIndex, feeForJuror, roundInfo); } export function handleDraw(event: DrawEvent): void { - const disputeID = event.params._disputeID; - const currentRound = event.params._roundID; - const voteID = event.params._voteID; - const drawID = `${disputeID.toString()}-${currentRound.toString()}-${voteID.toString()}`; - const drawnAddress = event.params._address; - const draw = new Draw(drawID); - draw.dispute = disputeID.toString(); - draw.round = currentRound.toString(); - draw.juror = drawnAddress.toHexString(); - draw.voteID = voteID; - draw.save(); - const dispute = Dispute.load(disputeID.toString()); - if (dispute) { - const contract = KlerosCore.bind(event.address); - updateJurorStake( - drawnAddress.toHexString(), - dispute.courtID.toString(), - contract, - event.block.timestamp - ); - } + createDrawFromEvent(event); + const disputeID = event.params._disputeID.toString(); + const dispute = ensureDispute(disputeID); + const contract = KlerosCore.bind(event.address); + updateJurorStake( + event.params._address.toHexString(), + dispute.court, + contract, + event.block.timestamp + ); } export function handleStakeSet(event: StakeSet): void { const jurorAddress = event.params._address.toHexString(); - let juror = Juror.load(jurorAddress); - if (!juror) { - juror = new Juror(jurorAddress); - updateActiveJurors(BigInt.fromI32(1), event.block.timestamp); - } - juror.save(); + ensureJuror(jurorAddress); const courtID = event.params._courtID; - const jurorTokensID = `${jurorAddress}-${courtID.toString()}`; - let jurorTokens = JurorTokensPerCourt.load(jurorTokensID); - let previousStake: BigInt; - if (!jurorTokens) { - jurorTokens = new JurorTokensPerCourt(jurorTokensID); - jurorTokens.juror = jurorAddress; - jurorTokens.court = courtID.toString(); - jurorTokens.staked = ZERO; - jurorTokens.locked = ZERO; - jurorTokens.save(); - previousStake = ZERO; - } else previousStake = jurorTokens.staked; + let jurorTokens = ensureJurorTokensPerCourt(jurorAddress, courtID.toString()); + const previousStake = jurorTokens.staked; + updateJurorStake( jurorAddress, courtID.toString(), KlerosCore.bind(event.address), event.block.timestamp ); + const amountStaked = event.params._newTotalStake; + updateStakedPNK(getDelta(previousStake, amountStaked), event.block.timestamp); } export function handleTokenAndETHShift(event: TokenAndETHShiftEvent): void { + createTokenAndEthShiftFromEvent(event); const jurorAddress = event.params._account.toHexString(); - const disputeID = event.params._disputeID; - const shiftID = `${jurorAddress}-${disputeID.toString()}`; + const disputeID = event.params._disputeID.toString(); const tokenAmount = event.params._tokenAmount; const ethAmount = event.params._ethAmount; - const shift = new TokenAndETHShift(shiftID); - if (tokenAmount.gt(BigInt.fromI32(0))) { + if (tokenAmount.gt(ZERO)) { updateRedistributedPNK(tokenAmount, event.block.timestamp); } updatePaidETH(ethAmount, event.block.timestamp); - shift.juror = jurorAddress; - shift.dispute = disputeID.toString(); - shift.tokenAmount = tokenAmount; - shift.ethAmount = ethAmount; - shift.save(); - const dispute = Dispute.load(disputeID.toString()); - if (dispute) { - const court = Court.load(dispute.courtID.toString()); - if (court) { - updateJurorStake( - jurorAddress, - court.id, - KlerosCore.bind(event.address), - event.block.timestamp - ); - court.paidETH = court.paidETH.plus(ethAmount); - court.paidPNK = court.paidETH.plus(tokenAmount); - court.save(); - } - } + const dispute = ensureDispute(disputeID); + const court = ensureCourt(dispute.court); + updateJurorStake( + jurorAddress, + court.id, + KlerosCore.bind(event.address), + event.block.timestamp + ); + court.paidETH = court.paidETH.plus(ethAmount); + court.paidPNK = court.paidETH.plus(tokenAmount); + court.save(); } diff --git a/subgraph/src/entities/Court.ts b/subgraph/src/entities/Court.ts new file mode 100644 index 000000000..836581b80 --- /dev/null +++ b/subgraph/src/entities/Court.ts @@ -0,0 +1,56 @@ +import { BigInt } from "@graphprotocol/graph-ts"; +import { CourtCreated } from "../../generated/KlerosCore/KlerosCore"; +import { Court } from "../../generated/schema"; +import { ZERO } from "../utils"; + +export function ensureCourt(id: string): Court { + let court = Court.load(id); + + if (court) { + return court; + } + // Should never reach here + court = new Court(id); + + court.hiddenVotes = false; + court.parent = "1"; + court.minStake = ZERO; + court.alpha = ZERO; + court.feeForJuror = ZERO; + court.jurorsForCourtJump = ZERO; + court.timesPerPeriod = [ZERO, ZERO, ZERO, ZERO]; + court.supportedDisputeKits = []; + court.numberDisputes = ZERO; + court.numberStakedJurors = ZERO; + court.stake = ZERO; + court.paidETH = ZERO; + court.paidPNK = ZERO; + court.save(); + + return court; +} + +export function createCourtFromEvent(event: CourtCreated): void { + const court = new Court(event.params._courtID.toString()); + court.hiddenVotes = event.params._hiddenVotes; + court.parent = event.params._parent.toString(); + court.minStake = event.params._minStake; + court.alpha = event.params._alpha; + court.feeForJuror = event.params._feeForJuror; + court.jurorsForCourtJump = event.params._jurorsForCourtJump; + court.timesPerPeriod = event.params._timesPerPeriod; + court.supportedDisputeKits = event.params._supportedDisputeKits.map( + (value) => value.toString() + ); + court.numberDisputes = ZERO; + court.numberStakedJurors = ZERO; + court.stake = ZERO; + court.paidETH = ZERO; + court.paidPNK = ZERO; + court.save(); +} + +export function getFeeForJuror(id: string): BigInt { + const court = ensureCourt(id); + return court.feeForJuror; +} diff --git a/subgraph/src/entities/Dispute.ts b/subgraph/src/entities/Dispute.ts new file mode 100644 index 000000000..ad877bcaf --- /dev/null +++ b/subgraph/src/entities/Dispute.ts @@ -0,0 +1,44 @@ +import { Bytes } from "@graphprotocol/graph-ts"; +import { + KlerosCore, + DisputeCreation, +} from "../../generated/KlerosCore/KlerosCore"; +import { Dispute } from "../../generated/schema"; +import { ZERO } from "../utils"; + +export function ensureDispute(id: string): Dispute { + let dispute = Dispute.load(id); + + if (dispute) { + return dispute; + } + // Should never reach here + dispute = new Dispute(id); + dispute.court = "1"; + dispute.arbitrated = Bytes.fromHexString("0x0"); + dispute.period = "evidence"; + dispute.ruled = false; + dispute.lastPeriodChange = ZERO; + dispute.currentRoundIndex = ZERO; + const roundID = `${id}-${ZERO.toString()}`; + dispute.currentRound = roundID; + dispute.save(); + + return dispute; +} + +export function createDisputeFromEvent(event: DisputeCreation): void { + const contract = KlerosCore.bind(event.address); + const disputeID = event.params._disputeID; + const disputeContractState = contract.disputes(disputeID); + const dispute = new Dispute(disputeID.toString()); + dispute.court = disputeContractState.value0.toString(); + dispute.arbitrated = event.params._arbitrable; + dispute.period = "evidence"; + dispute.ruled = false; + dispute.lastPeriodChange = event.block.timestamp; + dispute.currentRoundIndex = ZERO; + const roundID = `${disputeID.toString()}-${ZERO.toString()}`; + dispute.currentRound = roundID; + dispute.save(); +} diff --git a/subgraph/src/entities/DisputeKit.ts b/subgraph/src/entities/DisputeKit.ts new file mode 100644 index 000000000..1b3573ffc --- /dev/null +++ b/subgraph/src/entities/DisputeKit.ts @@ -0,0 +1,43 @@ +import { Bytes } from "@graphprotocol/graph-ts"; +import { DisputeKitCreated } from "../../generated/KlerosCore/KlerosCore"; +import { DisputeKit } from "../../generated/schema"; +import { ZERO, ONE } from "../utils"; + +export function ensureDisputeKit(id: string): DisputeKit { + let disputeKit = DisputeKit.load(id); + + if (disputeKit) { + return disputeKit; + } + // Should never reach here + disputeKit = new DisputeKit(id); + disputeKit.parent = "0"; + disputeKit.address = Bytes.fromHexString("0x0"); + disputeKit.needsFreezing = false; + const parent = ensureDisputeKit("0"); + disputeKit.depthLevel = id === "0" ? ZERO : parent.depthLevel.plus(ONE); + disputeKit.save(); + + return disputeKit; +} + +export function createDisputeKitFromEvent(event: DisputeKitCreated): void { + const disputeKit = new DisputeKit(event.params._disputeKitID.toString()); + disputeKit.parent = event.params._parent.toString(); + disputeKit.address = event.params._disputeKitAddress; + disputeKit.needsFreezing = false; + const parent = DisputeKit.load(event.params._parent.toString()); + disputeKit.depthLevel = parent ? parent.depthLevel.plus(ONE) : ZERO; + disputeKit.save(); +} + +export function filterSupportedDisputeKits( + supportedDisputeKits: string[], + disputeKitID: string +): string[] { + let result: string[] = []; + for (let i = 0; i < supportedDisputeKits.length; i++) + if (supportedDisputeKits[i] !== disputeKitID) + result = result.concat([supportedDisputeKits[i]]); + return result; +} diff --git a/subgraph/src/entities/Draw.ts b/subgraph/src/entities/Draw.ts new file mode 100644 index 000000000..fc06dbf3d --- /dev/null +++ b/subgraph/src/entities/Draw.ts @@ -0,0 +1,16 @@ +import { Draw as DrawEvent } from "../../generated/KlerosCore/KlerosCore"; +import { Draw } from "../../generated/schema"; + +export function createDrawFromEvent(event: DrawEvent): void { + const disputeID = event.params._disputeID.toString(); + const roundIndex = event.params._roundID; + const roundID = `${disputeID}-${roundIndex.toString()}`; + const voteID = event.params._voteID; + const drawID = `${disputeID}-${roundIndex.toString()}-${voteID.toString()}`; + const draw = new Draw(drawID); + draw.dispute = disputeID; + draw.round = roundID; + draw.juror = event.params._address.toHexString(); + draw.voteID = voteID; + draw.save(); +} diff --git a/subgraph/src/entities/Juror.ts b/subgraph/src/entities/Juror.ts new file mode 100644 index 000000000..cff8a9c67 --- /dev/null +++ b/subgraph/src/entities/Juror.ts @@ -0,0 +1,18 @@ +import { Juror } from "../../generated/schema"; + +export function ensureJuror(id: string): Juror { + let juror = Juror.load(id); + + if (juror) { + return juror; + } + + return createJurorFromAddress(id); +} + +export function createJurorFromAddress(id: string): Juror { + const juror = new Juror(id); + juror.save(); + + return juror; +} diff --git a/subgraph/src/entities/JurorTokensPerCourt.ts b/subgraph/src/entities/JurorTokensPerCourt.ts new file mode 100644 index 000000000..3a9accb77 --- /dev/null +++ b/subgraph/src/entities/JurorTokensPerCourt.ts @@ -0,0 +1,85 @@ +import { BigInt, Address } from "@graphprotocol/graph-ts"; +import { KlerosCore } from "../../generated/KlerosCore/KlerosCore"; +import { JurorTokensPerCourt } from "../../generated/schema"; +import { updateActiveJurors, getDelta } from "../datapoint"; +import { ensureJuror } from "./Juror"; +import { ensureCourt } from "./Court"; +import { ZERO } from "../utils"; + +export function ensureJurorTokensPerCourt( + jurorAddress: string, + courtID: string +): JurorTokensPerCourt { + const id = `${jurorAddress}-${courtID}`; + let jurorTokens = JurorTokensPerCourt.load(id); + + if (jurorTokens) { + return jurorTokens; + } + + jurorTokens = new JurorTokensPerCourt(id); + jurorTokens.juror = jurorAddress; + jurorTokens.court = courtID; + jurorTokens.staked = ZERO; + jurorTokens.locked = ZERO; + jurorTokens.save(); + + return jurorTokens; +} + +export function createJurorTokensPerCourt( + jurorAddress: string, + courtID: string +): JurorTokensPerCourt { + const id = `${jurorAddress}-${courtID}`; + + const jurorTokens = new JurorTokensPerCourt(id); + jurorTokens.juror = jurorAddress; + jurorTokens.court = courtID; + jurorTokens.staked = ZERO; + jurorTokens.locked = ZERO; + jurorTokens.save(); + + return jurorTokens; +} + +export function updateJurorStake( + jurorAddress: string, + courtID: string, + contract: KlerosCore, + timestamp: BigInt +): void { + const juror = ensureJuror(jurorAddress); + const court = ensureCourt(courtID); + const jurorTokens = ensureJurorTokensPerCourt(jurorAddress, courtID); + const jurorBalance = contract.getJurorBalance( + Address.fromString(jurorAddress), + BigInt.fromString(courtID) + ); + const previousStake = jurorTokens.staked; + const previousTotalStake = juror.totalStake; + jurorTokens.staked = jurorBalance.value0; + jurorTokens.locked = jurorBalance.value1; + jurorTokens.save(); + const stakeDelta = getDelta(jurorTokens.staked, previousStake); + juror.totalStake = juror.totalStake.plus(stakeDelta); + court.stake = court.stake.plus(stakeDelta); + let activeJurorsDelta: BigInt; + let numberStakedJurorsDelta: BigInt; + if (previousTotalStake.equals(ZERO)) { + activeJurorsDelta = BigInt.fromI32(1); + numberStakedJurorsDelta = BigInt.fromI32(1); + } else if (previousStake.equals(ZERO)) { + activeJurorsDelta = ZERO; + numberStakedJurorsDelta = BigInt.fromI32(1); + } else { + activeJurorsDelta = ZERO; + numberStakedJurorsDelta = ZERO; + } + court.numberStakedJurors = court.numberStakedJurors.plus( + numberStakedJurorsDelta + ); + updateActiveJurors(activeJurorsDelta, timestamp); + juror.save(); + court.save(); +} diff --git a/subgraph/src/entities/Round.ts b/subgraph/src/entities/Round.ts new file mode 100644 index 000000000..e3ce5e871 --- /dev/null +++ b/subgraph/src/entities/Round.ts @@ -0,0 +1,53 @@ +import { BigInt } from "@graphprotocol/graph-ts"; +import { KlerosCore__getRoundInfoResult } from "../../generated/KlerosCore/KlerosCore"; +import { Round } from "../../generated/schema"; +import { ZERO } from "../utils"; + +export function ensureRound(id: string): Round { + let round = Round.load(id); + + if (round) { + return round; + } + // Should never reach here + round = new Round(id); + round.disputeKit = "0"; + round.tokensAtStakePerJuror = ZERO; + round.totalFeesForJurors = ZERO; + round.nbVotes = ZERO; + round.repartitions = ZERO; + round.penalties = ZERO; + round.dispute = "0"; + round.save(); + + return round; +} + +export function createRoundFromRoundInfo( + disputeID: BigInt, + roundIndex: BigInt, + feeForJuror: BigInt, + roundInfo: KlerosCore__getRoundInfoResult +): void { + const roundID = `${disputeID.toString()}-${roundIndex.toString()}`; + const round = new Round(roundID); + round.disputeKit = roundInfo.value5.toString(); + round.tokensAtStakePerJuror = roundInfo.value0; + round.totalFeesForJurors = roundInfo.value1; + round.nbVotes = roundInfo.value1.div(feeForJuror); + round.repartitions = roundInfo.value2; + round.penalties = roundInfo.value3; + round.dispute = disputeID.toString(); + round.save(); +} + +export function filterSupportedDisputeKits( + supportedDisputeKits: string[], + disputeKitID: string +): string[] { + let result: string[] = []; + for (let i = 0; i < supportedDisputeKits.length; i++) + if (supportedDisputeKits[i] !== disputeKitID) + result = result.concat([supportedDisputeKits[i]]); + return result; +} diff --git a/subgraph/src/entities/TokenAndEthShift.ts b/subgraph/src/entities/TokenAndEthShift.ts new file mode 100644 index 000000000..1a17ced64 --- /dev/null +++ b/subgraph/src/entities/TokenAndEthShift.ts @@ -0,0 +1,16 @@ +import { TokenAndETHShift as TokenAndETHShiftEvent } from "../../generated/KlerosCore/KlerosCore"; +import { TokenAndETHShift } from "../../generated/schema"; + +export function createTokenAndEthShiftFromEvent( + event: TokenAndETHShiftEvent +): void { + const jurorAddress = event.params._account.toHexString(); + const disputeID = event.params._disputeID.toString(); + const shiftID = `${jurorAddress}-${disputeID}`; + const shift = new TokenAndETHShift(shiftID); + shift.juror = jurorAddress; + shift.dispute = disputeID; + shift.tokenAmount = event.params._tokenAmount; + shift.ethAmount = event.params._ethAmount; + shift.save(); +} diff --git a/subgraph/src/utils.ts b/subgraph/src/utils.ts index d0758c940..73b2f99dc 100644 --- a/subgraph/src/utils.ts +++ b/subgraph/src/utils.ts @@ -1,3 +1,4 @@ -import { BigInt, Entity, Value, store } from "@graphprotocol/graph-ts"; +import { BigInt } from "@graphprotocol/graph-ts"; export const ZERO = BigInt.fromI32(0); +export const ONE = BigInt.fromI32(1); diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 5e78d85ae..0f988c46e 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -6,7 +6,7 @@ dataSources: name: KlerosCore network: arbitrum-goerli source: - address: "0xdfea19B51cDe76Aa659bB72401AC14FFCFe42Cde" + address: "0x9Bcb0D261D5B446c0474d0846563C712F8B7bE5E" abi: KlerosCore startBlock: 997981 mapping: @@ -56,7 +56,7 @@ dataSources: name: PolicyRegistry network: arbitrum-goerli source: - address: "0x76262035D1b280cC0b08024177b837893bcAd3DA" + address: "0xaE25ed5eDad0bD932D3Af2e36631849497512068" abi: PolicyRegistry startBlock: 133630 mapping: @@ -76,7 +76,7 @@ dataSources: name: DisputeKitClassic network: arbitrum-goerli source: - address: "0xA2c538AA05BBCc44c213441f6f3777223D2BF9e5" + address: "0x61C72C3258C3D4D05bebD045E7FB0d57695CF43e" abi: DisputeKitClassic startBlock: 133630 mapping: From 84299206f41ca85fd7e09791e057abcb3f7b0ea4 Mon Sep 17 00:00:00 2001 From: alcercu Date: Fri, 30 Dec 2022 12:56:28 +0100 Subject: [PATCH 2/2] feat(subgraph): map DisputeKitClassic using interfaces --- subgraph/schema.graphql | 249 +++++++++++------- subgraph/src/DisputeKitClassic.ts | 122 ++++++--- subgraph/src/KlerosCore.ts | 34 ++- subgraph/src/entities/ClassicContribution.ts | 38 +++ subgraph/src/entities/ClassicDispute.ts | 22 ++ subgraph/src/entities/ClassicEvidenceGroup.ts | 14 + subgraph/src/entities/ClassicRound.ts | 49 ++++ subgraph/src/entities/ClassicVote.ts | 20 ++ subgraph/src/entities/Court.ts | 31 +-- subgraph/src/entities/Dispute.ts | 24 +- subgraph/src/entities/Juror.ts | 20 +- subgraph/src/entities/JurorTokensPerCourt.ts | 9 +- subgraph/subgraph.yaml | 48 +--- 13 files changed, 440 insertions(+), 240 deletions(-) create mode 100644 subgraph/src/entities/ClassicContribution.ts create mode 100644 subgraph/src/entities/ClassicDispute.ts create mode 100644 subgraph/src/entities/ClassicEvidenceGroup.ts create mode 100644 subgraph/src/entities/ClassicRound.ts create mode 100644 subgraph/src/entities/ClassicVote.ts diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 2d75a6d73..54fc6c2d6 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -1,3 +1,7 @@ +######### +# Enums # +######### + enum Period { evidence commit @@ -6,6 +10,85 @@ enum Period { execution } +############## +# Interfaces # +############## + +interface DisputeKitDispute { + id: ID! + coreDispute: Dispute! + localRounds: [DisputeKitRound!]! @derivedFrom(field: "localDispute") + currentLocalRoundIndex: BigInt! +} + +interface DisputeKitRound { + id: ID! + localDispute: DisputeKitDispute! +} + +interface Vote { + id: ID! + coreDispute: Dispute! + localRound: DisputeKitRound! + juror: User! +} + +interface Contribution { + id: ID! + coreDispute: Dispute! + contributor: User! +} + +interface EvidenceGroup { + id: ID! + evidences: [Evidence!]! @derivedFrom(field: "evidenceGroup") + nextEvidenceIndex: BigInt! +} + +interface Evidence { + id: ID! + evidence: String! + evidenceGroup: EvidenceGroup! + sender: User! +} + +############ +# Entities # +############ + +type User @entity { + id: ID! # address + tokens: [JurorTokensPerCourt!]! @derivedFrom(field: "juror") + totalStake: BigInt! + shifts: [TokenAndETHShift!]! @derivedFrom(field: "juror") + draws: [Draw!]! @derivedFrom(field: "juror") + votes: [Vote!]! @derivedFrom(field: "juror") + contributions: [Contribution!]! @derivedFrom(field: "contributor") + evidences: [Evidence!]! @derivedFrom(field: "sender") +} + +type Arbitrated @entity { + id: ID! # address + disputes: [Dispute!]! @derivedFrom(field: "arbitrated") + totalDisputes: BigInt! +} + +type TokenAndETHShift @entity { + id: ID! # user.id-dispute.id + juror: User! + dispute: Dispute! + tokenAmount: BigInt! + ethAmount: BigInt! +} + +type JurorTokensPerCourt @entity { + id: ID! # user.id-court.id + juror: User! + court: Court! + staked: BigInt! + locked: BigInt! +} + type Court @entity { id: ID! parent: Court @@ -30,7 +113,7 @@ type Court @entity { type Dispute @entity { id: ID! court: Court! - arbitrated: Bytes! + arbitrated: Arbitrated! period: Period! ruled: Boolean! lastPeriodChange: BigInt! @@ -42,7 +125,7 @@ type Dispute @entity { } type Round @entity { - id: ID! # Set to `${dispute.id}-${dispute.rounds.length}` + id: ID! # dispute.id-dispute.rounds.length disputeKit: DisputeKit! tokensAtStakePerJuror: BigInt! totalFeesForJurors: BigInt! @@ -53,102 +136,11 @@ type Round @entity { dispute: Dispute! } -interface DisputeKitRound { - id: ID! # Set to `${disputeKit.id}-{dispute.id}-{dispute.rounds.length}` -} - -type ClassicRound implements DisputeKitRound @entity { - id: ID! # Set to `${disputeKit.id}-{dispute.id}-{dispute.rounds.length}` - votes: [ClassicVote!]! - winningChoice: BigInt! - counts: [BigInt!]! - tied: Boolean! - totalVoted: BigInt! - totalCommited: BigInt! - paidFees: [BigInt!]! - contributions: [Contribution!]! @derivedFrom(field: "round") - feeRewards: BigInt! - nbVotes: BigInt! -} - -type Contribution @entity { - id: ID! # Set to `${dispute.id}-${round.id}-${contributor}-${choice}` - contributor: Bytes! - amount: BigInt! - choice: BigInt! - round: ClassicRound! -} - -type ClassicVote @entity { - id: ID! -} - -interface DisputeKitDispute { - id: ID! # Set to ${disputeKit.id}-{disputeKit.disputes.length} - coreDispute: Dispute! -} - -type ClassicDispute implements DisputeKitDispute @entity { - id: ID! # Set to ${disputeKit.id}-{disputeKit.disputes.length} - coreDispute: Dispute! - - numberOfChoices: BigInt! - jumped: Boolean! - extraData: Bytes! -} - -type Juror @entity { - id: ID! # Set to address - tokens: [JurorTokensPerCourt!]! @derivedFrom(field: "juror") - totalStake: BigInt! - shifts: [TokenAndETHShift!]! @derivedFrom(field: "juror") - draws: [Draw!]! @derivedFrom(field: "juror") - votes: [Vote!]! @derivedFrom(field: "juror") -} - -type TokenAndETHShift @entity { - id: ID! # Set to `${juror.id}-${dispute.id}` - juror: Juror! - dispute: Dispute! - tokenAmount: BigInt! - ethAmount: BigInt! -} - -type JurorTokensPerCourt @entity { - id: ID! # Set to `${juror.id}-${court.id}` - juror: Juror! - court: Court! - staked: BigInt! - locked: BigInt! -} - -type EvidenceGroup @entity { - id: ID! - evidences: [Evidence!]! @derivedFrom(field: "evidenceGroup") - lastEvidenceID: BigInt! -} - -type Evidence @entity { - id: ID! # Set to `${evidenceGroupID}-${id}` - evidence: String! - evidenceGroup: EvidenceGroup! - sender: Bytes! -} - -type Vote @entity { - id: ID! # Set to `${coreDisputeID}-${coreRoundID}-${jurorAddress}` - dispute: Dispute! - round: Round! - juror: Juror! - choice: BigInt - justification: String -} - type Draw @entity { - id: ID! # Set to `${dispute.id}-${currentRound}-${voteID}` + id: ID! # dispute.id-currentRound-voteID dispute: Dispute! round: Round! - juror: Juror! + juror: User! voteID: BigInt! } @@ -173,7 +165,7 @@ type GatewayDispute @entity { } type OutgoingBatch @entity { - id: ID! # Set to messageHash + id: ID! # messageHash size: BigInt! epoch: BigInt! batchMerkleRoot: String! @@ -189,3 +181,68 @@ type Counter @entity { casesVoting: BigInt! casesRuled: BigInt! } + +##################### +# ClassicDisputeKit # +##################### + +type ClassicDispute implements DisputeKitDispute @entity { + id: ID! # disputeKit.id-coreDispute + coreDispute: Dispute! + localRounds: [DisputeKitRound!]! @derivedFrom(field: "localDispute") + currentLocalRoundIndex: BigInt! + + numberOfChoices: BigInt! + jumped: Boolean! + extraData: Bytes! +} + +type ClassicRound implements DisputeKitRound @entity { + id: ID! # disputeKit.id-coreDispute-dispute.rounds.length + localDispute: DisputeKitDispute! + + votes: [ClassicVote!]! + winningChoice: BigInt! + counts: [BigInt!]! + tied: Boolean! + totalVoted: BigInt! + totalCommited: BigInt! + paidFees: [BigInt!]! + contributions: [ClassicContribution!]! @derivedFrom(field: "localRound") + feeRewards: BigInt! + fundedChoices: [BigInt!]! +} + +type ClassicVote implements Vote @entity { + id: ID! # disputeKit.id-coreDispute-currentRound-juror + coreDispute: Dispute! + localRound: DisputeKitRound! + juror: User! + + choice: BigInt! + justification: String! +} + +type ClassicEvidenceGroup implements EvidenceGroup @entity { + id: ID! + evidences: [Evidence!]! @derivedFrom(field: "evidenceGroup") + nextEvidenceIndex: BigInt! +} + +type ClassicEvidence implements Evidence @entity { + id: ID! # classicEvidenceGroup.id-nextEvidenceIndex + evidence: String! + evidenceGroup: EvidenceGroup! + sender: User! +} + +type ClassicContribution implements Contribution @entity { + id: ID! # disputeKit.id-dispute.id-classicround.id-contributor-choice + contributor: User! + coreDispute: Dispute! + + localRound: ClassicRound! + amount: BigInt! + choice: BigInt! + rewardWithdrawn: Boolean! +} diff --git a/subgraph/src/DisputeKitClassic.ts b/subgraph/src/DisputeKitClassic.ts index e16b6cde0..a822bd234 100644 --- a/subgraph/src/DisputeKitClassic.ts +++ b/subgraph/src/DisputeKitClassic.ts @@ -1,54 +1,104 @@ import { BigInt } from "@graphprotocol/graph-ts"; import { DisputeKitClassic, + DisputeCreation, Evidence as EvidenceEvent, Justification as JustificationEvent, + Contribution as ContributionEvent, + ChoiceFunded, + Withdrawal, } from "../generated/DisputeKitClassic/DisputeKitClassic"; +import { KlerosCore } from "../generated/KlerosCore/KlerosCore"; +import { ClassicEvidence } from "../generated/schema"; +import { ensureClassicContributionFromEvent } from "./entities/ClassicContribution"; import { - Dispute, - Round, - Evidence, - EvidenceGroup, - Vote, -} from "../generated/schema"; + createClassicDisputeFromEvent, + loadClassicDisputeWithLog, +} from "./entities/ClassicDispute"; +import { ensureClassicEvidenceGroup } from "./entities/ClassicEvidenceGroup"; +import { + createClassicRound, + loadClassicRoundWithLog, + updateChoiceFundingFromContributionEvent, +} from "./entities/ClassicRound"; +import { createClassicVote } from "./entities/ClassicVote"; +import { loadDisputeWithLog } from "./entities/Dispute"; +import { ONE } from "./utils"; + +export const DISPUTEKIT_ID = "1"; + +export function handleDisputeCreation(event: DisputeCreation): void { + const dispute = loadDisputeWithLog(event.params._coreDisputeID.toString()); + if (!dispute) return; + createClassicDisputeFromEvent(event); + createClassicRound(dispute.id, dispute.currentRoundIndex); +} export function handleEvidenceEvent(event: EvidenceEvent): void { - const evidenceGroupID = event.params._evidenceGroupID; - let evidenceGroup = EvidenceGroup.load(evidenceGroupID.toString()); - if (!evidenceGroup) { - evidenceGroup = new EvidenceGroup(evidenceGroupID.toString()); - evidenceGroup.lastEvidenceID = BigInt.fromI32(0); - } - const evidenceID = evidenceGroup.lastEvidenceID.plus(BigInt.fromI32(1)); - const evidence = new Evidence(`${evidenceGroupID}-${evidenceID}`); + const evidenceGroupID = event.params._evidenceGroupID.toHexString(); + const evidenceGroup = ensureClassicEvidenceGroup(evidenceGroupID); + const evidenceIndex = evidenceGroup.nextEvidenceIndex; + evidenceGroup.nextEvidenceIndex = evidenceGroup.nextEvidenceIndex.plus(ONE); + evidenceGroup.save(); + const evidence = new ClassicEvidence( + `${evidenceGroupID}-${evidenceIndex.toString()}` + ); evidence.evidence = event.params._evidence; evidence.evidenceGroup = evidenceGroupID.toString(); - evidence.sender = event.params._party; - evidenceGroup.lastEvidenceID = evidenceID; - evidenceGroup.save(); + evidence.sender = event.params._party.toHexString(); evidence.save(); } export function handleJustificationEvent(event: JustificationEvent): void { - const disputeID = event.params._coreDisputeID; - const dispute = Dispute.load(disputeID.toString()); - if (dispute) { - const currentRoundIndex = dispute.currentRound; - const currentRound = Round.load( - `${disputeID.toString()}-${currentRoundIndex.toString()}` + const coreDisputeID = event.params._coreDisputeID.toString(); + const classicDisputeID = `${DISPUTEKIT_ID}-${coreDisputeID}`; + const classicDispute = loadClassicDisputeWithLog(classicDisputeID); + if (!classicDispute) return; + const currentLocalRoundID = `${ + classicDispute.id + }-${classicDispute.currentLocalRoundIndex.toString()}`; + createClassicVote(currentLocalRoundID, event); +} + +export function handleContributionEvent(event: ContributionEvent): void { + ensureClassicContributionFromEvent(event); + updateChoiceFundingFromContributionEvent(event); +} + +export function handleChoiceFunded(event: ChoiceFunded): void { + const coreDisputeID = event.params._coreDisputeID.toString(); + const coreRoundIndex = event.params._coreRoundID.toString(); + const roundID = `${DISPUTEKIT_ID}-${coreDisputeID}-${coreRoundIndex}`; + + const localRound = loadClassicRoundWithLog(roundID); + if (!localRound) return; + + const currentFeeRewards = localRound.feeRewards; + const deltaFeeRewards = localRound.paidFees[event.params._choice.toI32()]; + localRound.feeRewards = currentFeeRewards.plus(deltaFeeRewards); + const choice = event.params._choice; + localRound.fundedChoices = localRound.fundedChoices.concat([choice]); + + if (localRound.fundedChoices.length > 1) { + const disputeKitClassic = DisputeKitClassic.bind(event.address); + const klerosCore = KlerosCore.bind(disputeKitClassic.core()); + const appealCost = klerosCore.appealCost(BigInt.fromString(coreDisputeID)); + localRound.feeRewards = localRound.feeRewards.minus(appealCost); + + const localDispute = loadClassicDisputeWithLog( + `${DISPUTEKIT_ID}-${coreDisputeID}` ); - if (currentRound) { - const contract = DisputeKitClassic.bind(event.address); - const juror = event.params._juror.toHexString(); - const vote = new Vote( - `${disputeID.toString()}-${currentRoundIndex.toString()}-${juror}` - ); - vote.dispute = disputeID.toString(); - vote.round = `${disputeID.toString()}-${currentRoundIndex.toString()}`; - vote.juror = juror; - vote.choice = event.params._choice; - vote.justification = event.params._justification; - vote.save(); - } + if (!localDispute) return; + const newRoundIndex = localDispute.currentLocalRoundIndex.plus(ONE); + createClassicRound(coreDisputeID, newRoundIndex); } + + localRound.save(); +} + +export function handleWithdrawal(event: Withdrawal): void { + const contribution = ensureClassicContributionFromEvent(event); + if (!contribution) return; + contribution.rewardWithdrawn = true; + contribution.save(); } diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 237f7ad97..85e57255b 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -13,26 +13,24 @@ import { } from "../generated/KlerosCore/KlerosCore"; import { ZERO, ONE } from "./utils"; import { - ensureCourt, createCourtFromEvent, getFeeForJuror, + loadCourtWithLog, } from "./entities/Court"; import { createDisputeKitFromEvent, filterSupportedDisputeKits, } from "./entities/DisputeKit"; -import { createDisputeFromEvent, ensureDispute } from "./entities/Dispute"; +import { createDisputeFromEvent, loadDisputeWithLog } from "./entities/Dispute"; import { createRoundFromRoundInfo } from "./entities/Round"; import { updateCases, updatePaidETH, updateStakedPNK, - updateCasesRuled, - updateCasesVoting, updateRedistributedPNK, getDelta, } from "./datapoint"; -import { ensureJuror } from "./entities/Juror"; +import { ensureUser } from "./entities/Juror"; import { ensureJurorTokensPerCourt, updateJurorStake, @@ -52,7 +50,8 @@ export function handleCourtCreated(event: CourtCreated): void { export function handleCourtModified(event: CourtModified): void { const contract = KlerosCore.bind(event.address); const courtContractState = contract.courts(event.params._courtID); - const court = ensureCourt(event.params._courtID.toString()); + const court = loadCourtWithLog(event.params._courtID.toString()); + if (!court) return; court.hiddenVotes = courtContractState.value1; court.minStake = courtContractState.value2; court.alpha = courtContractState.value3; @@ -67,7 +66,8 @@ export function handleDisputeKitCreated(event: DisputeKitCreated): void { } export function handleDisputeKitEnabled(event: DisputeKitEnabled): void { - const court = ensureCourt(event.params._courtID.toString()); + const court = loadCourtWithLog(event.params._courtID.toString()); + if (!court) return; const isEnable = event.params._enable; const disputeKitID = event.params._disputeKitID.toString(); court.supportedDisputeKits = isEnable @@ -81,7 +81,8 @@ export function handleDisputeCreation(event: DisputeCreation): void { const disputeID = event.params._disputeID; const disputeStorage = contract.disputes(disputeID); const courtID = disputeStorage.value0.toString(); - const court = ensureCourt(courtID); + const court = loadCourtWithLog(courtID); + if (!court) return; court.numberDisputes = court.numberDisputes.plus(ONE); court.save(); createDisputeFromEvent(event); @@ -92,7 +93,8 @@ export function handleDisputeCreation(event: DisputeCreation): void { export function handleNewPeriod(event: NewPeriod): void { const disputeID = event.params._disputeID.toString(); - const dispute = ensureDispute(disputeID); + const dispute = loadDisputeWithLog(disputeID); + if (!dispute) return; dispute.period = getPeriodName(event.params._period); dispute.lastPeriodChange = event.block.timestamp; dispute.save(); @@ -101,7 +103,8 @@ export function handleNewPeriod(event: NewPeriod): void { export function handleAppealDecision(event: AppealDecision): void { const contract = KlerosCore.bind(event.address); const disputeID = event.params._disputeID; - const dispute = ensureDispute(disputeID.toString()); + const dispute = loadDisputeWithLog(disputeID.toString()); + if (!dispute) return; const newRoundIndex = dispute.currentRoundIndex.plus(ONE); const roundID = `${disputeID}-${newRoundIndex.toString()}`; dispute.currentRoundIndex = newRoundIndex; @@ -115,7 +118,8 @@ export function handleAppealDecision(event: AppealDecision): void { export function handleDraw(event: DrawEvent): void { createDrawFromEvent(event); const disputeID = event.params._disputeID.toString(); - const dispute = ensureDispute(disputeID); + const dispute = loadDisputeWithLog(disputeID); + if (!dispute) return; const contract = KlerosCore.bind(event.address); updateJurorStake( event.params._address.toHexString(), @@ -127,7 +131,7 @@ export function handleDraw(event: DrawEvent): void { export function handleStakeSet(event: StakeSet): void { const jurorAddress = event.params._address.toHexString(); - ensureJuror(jurorAddress); + ensureUser(jurorAddress); const courtID = event.params._courtID; let jurorTokens = ensureJurorTokensPerCourt(jurorAddress, courtID.toString()); const previousStake = jurorTokens.staked; @@ -154,8 +158,10 @@ export function handleTokenAndETHShift(event: TokenAndETHShiftEvent): void { updateRedistributedPNK(tokenAmount, event.block.timestamp); } updatePaidETH(ethAmount, event.block.timestamp); - const dispute = ensureDispute(disputeID); - const court = ensureCourt(dispute.court); + const dispute = loadDisputeWithLog(disputeID); + if (!dispute) return; + const court = loadCourtWithLog(dispute.court); + if (!court) return; updateJurorStake( jurorAddress, court.id, diff --git a/subgraph/src/entities/ClassicContribution.ts b/subgraph/src/entities/ClassicContribution.ts new file mode 100644 index 000000000..d82dee48d --- /dev/null +++ b/subgraph/src/entities/ClassicContribution.ts @@ -0,0 +1,38 @@ +import { ClassicContribution } from "../../generated/schema"; +import { + Contribution as ContributionEvent, + Withdrawal, +} from "../../generated/DisputeKitClassic/DisputeKitClassic"; +import { DISPUTEKIT_ID } from "../DisputeKitClassic"; + +export function ensureClassicContributionFromEvent( + 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}`; + const contributor = event.params._contributor.toHexString(); + const choice = event.params._choice; + + const id = `${roundID}-${contributor}-${choice}`; + let classicContribution = ClassicContribution.load(id); + + if (!classicContribution) { + classicContribution = new ClassicContribution(id); + classicContribution.contributor = event.params._contributor.toHexString(); + classicContribution.coreDispute = coreDisputeID; + classicContribution.localRound = roundID; + classicContribution.amount = event.params._amount; + classicContribution.choice = event.params._choice; + classicContribution.rewardWithdrawn = false; + } else { + const currentAmount = classicContribution.amount; + classicContribution.amount = currentAmount.plus(event.params._amount); + } + + classicContribution.save(); + return classicContribution; +} diff --git a/subgraph/src/entities/ClassicDispute.ts b/subgraph/src/entities/ClassicDispute.ts new file mode 100644 index 000000000..6164e0fec --- /dev/null +++ b/subgraph/src/entities/ClassicDispute.ts @@ -0,0 +1,22 @@ +import { log } from "@graphprotocol/graph-ts"; +import { DisputeCreation } from "../../generated/DisputeKitClassic/DisputeKitClassic"; +import { ClassicDispute } from "../../generated/schema"; + +export function loadClassicDisputeWithLog(id: string): ClassicDispute | null { + const classicDispute = ClassicDispute.load(id); + if (!classicDispute) { + log.error("ClassicDispute not found with id: {}", [id]); + return null; + } + return classicDispute; +} + +export function createClassicDisputeFromEvent(event: DisputeCreation): void { + const coreDisputeID = event.params._coreDisputeID.toString(); + const classicDispute = new ClassicDispute(`1-${coreDisputeID}`); + classicDispute.coreDispute = coreDisputeID; + classicDispute.numberOfChoices = event.params._numberOfChoices; + classicDispute.jumped = false; + classicDispute.extraData = event.params._extraData; + classicDispute.save(); +} diff --git a/subgraph/src/entities/ClassicEvidenceGroup.ts b/subgraph/src/entities/ClassicEvidenceGroup.ts new file mode 100644 index 000000000..c9bad9ea1 --- /dev/null +++ b/subgraph/src/entities/ClassicEvidenceGroup.ts @@ -0,0 +1,14 @@ +import { ClassicEvidenceGroup } from "../../generated/schema"; +import { ZERO } from "../utils"; + +export function ensureClassicEvidenceGroup(id: string): ClassicEvidenceGroup { + let classicEvidenceGroup = ClassicEvidenceGroup.load(id); + + if (!classicEvidenceGroup) { + classicEvidenceGroup = new ClassicEvidenceGroup(id); + classicEvidenceGroup.nextEvidenceIndex = ZERO; + classicEvidenceGroup.save(); + } + + return classicEvidenceGroup; +} diff --git a/subgraph/src/entities/ClassicRound.ts b/subgraph/src/entities/ClassicRound.ts new file mode 100644 index 000000000..47dc7ee65 --- /dev/null +++ b/subgraph/src/entities/ClassicRound.ts @@ -0,0 +1,49 @@ +import { BigInt, log } from "@graphprotocol/graph-ts"; +import { Contribution } from "../../generated/DisputeKitClassic/DisputeKitClassic"; +import { ClassicRound } from "../../generated/schema"; +import { ZERO } from "../utils"; + +export function loadClassicRoundWithLog(id: string): ClassicRound | null { + const classicRound = ClassicRound.load(id); + if (!classicRound) { + log.error("ClassicRound not found with id: {}", [id]); + return null; + } + return classicRound; +} + +export function createClassicRound( + disputeID: string, + roundIndex: BigInt +): void { + const id = `1-${disputeID}-${roundIndex.toString()}`; + const classicRound = new ClassicRound(id); + classicRound.votes = []; + classicRound.winningChoice = ZERO; + classicRound.counts = []; + classicRound.tied = true; + classicRound.totalVoted = ZERO; + classicRound.totalCommited = ZERO; + classicRound.paidFees = []; + classicRound.feeRewards = ZERO; + classicRound.fundedChoices = []; + classicRound.save(); +} + +export function updateChoiceFundingFromContributionEvent( + event: Contribution +): void { + const disputeKitID = "1"; + const coreDisputeID = event.params._coreDisputeID.toString(); + const coreRoundIndex = event.params._coreRoundID.toString(); + const roundID = `${disputeKitID}-${coreDisputeID}-${coreRoundIndex}`; + + const classicRound = loadClassicRoundWithLog(roundID); + if (!classicRound) return; + + const choice = event.params._choice; + const amount = event.params._amount; + const currentPaidFees = classicRound.paidFees[choice.toI32()]; + classicRound.paidFees[choice.toI32()] = currentPaidFees.plus(amount); + classicRound.save(); +} diff --git a/subgraph/src/entities/ClassicVote.ts b/subgraph/src/entities/ClassicVote.ts new file mode 100644 index 000000000..94519e1b0 --- /dev/null +++ b/subgraph/src/entities/ClassicVote.ts @@ -0,0 +1,20 @@ +import { Justification } from "../../generated/DisputeKitClassic/DisputeKitClassic"; +import { ClassicVote } from "../../generated/schema"; + +export function createClassicVote( + currentRoundID: string, + event: Justification +): void { + const coreDisputeID = event.params._coreDisputeID.toString(); + const juror = event.params._juror.toHexString(); + + const id = `${currentRoundID}-${juror}`; + const classicVote = new ClassicVote(id); + classicVote.coreDispute = coreDisputeID; + classicVote.localRound = currentRoundID; + classicVote.juror = juror; + classicVote.choice = event.params._choice; + classicVote.justification = event.params._justification; + + classicVote.save(); +} diff --git a/subgraph/src/entities/Court.ts b/subgraph/src/entities/Court.ts index 836581b80..03d3fdb2f 100644 --- a/subgraph/src/entities/Court.ts +++ b/subgraph/src/entities/Court.ts @@ -1,31 +1,15 @@ -import { BigInt } from "@graphprotocol/graph-ts"; +import { BigInt, log } from "@graphprotocol/graph-ts"; import { CourtCreated } from "../../generated/KlerosCore/KlerosCore"; import { Court } from "../../generated/schema"; import { ZERO } from "../utils"; -export function ensureCourt(id: string): Court { - let court = Court.load(id); +export function loadCourtWithLog(id: string): Court | null { + const court = Court.load(id); - if (court) { - return court; + if (!court) { + log.error("Court not found with id: {}", [id]); + return null; } - // Should never reach here - court = new Court(id); - - court.hiddenVotes = false; - court.parent = "1"; - court.minStake = ZERO; - court.alpha = ZERO; - court.feeForJuror = ZERO; - court.jurorsForCourtJump = ZERO; - court.timesPerPeriod = [ZERO, ZERO, ZERO, ZERO]; - court.supportedDisputeKits = []; - court.numberDisputes = ZERO; - court.numberStakedJurors = ZERO; - court.stake = ZERO; - court.paidETH = ZERO; - court.paidPNK = ZERO; - court.save(); return court; } @@ -51,6 +35,7 @@ export function createCourtFromEvent(event: CourtCreated): void { } export function getFeeForJuror(id: string): BigInt { - const court = ensureCourt(id); + const court = loadCourtWithLog(id); + if (!court) return ZERO; return court.feeForJuror; } diff --git a/subgraph/src/entities/Dispute.ts b/subgraph/src/entities/Dispute.ts index ad877bcaf..a29f4a5c7 100644 --- a/subgraph/src/entities/Dispute.ts +++ b/subgraph/src/entities/Dispute.ts @@ -1,4 +1,4 @@ -import { Bytes } from "@graphprotocol/graph-ts"; +import { log } from "@graphprotocol/graph-ts"; import { KlerosCore, DisputeCreation, @@ -6,23 +6,13 @@ import { import { Dispute } from "../../generated/schema"; import { ZERO } from "../utils"; -export function ensureDispute(id: string): Dispute { - let dispute = Dispute.load(id); +export function loadDisputeWithLog(id: string): Dispute | null { + const dispute = Dispute.load(id); - if (dispute) { - return dispute; + if (!dispute) { + log.error("Dispute not found with id: {}", [id]); + return null; } - // Should never reach here - dispute = new Dispute(id); - dispute.court = "1"; - dispute.arbitrated = Bytes.fromHexString("0x0"); - dispute.period = "evidence"; - dispute.ruled = false; - dispute.lastPeriodChange = ZERO; - dispute.currentRoundIndex = ZERO; - const roundID = `${id}-${ZERO.toString()}`; - dispute.currentRound = roundID; - dispute.save(); return dispute; } @@ -33,7 +23,7 @@ export function createDisputeFromEvent(event: DisputeCreation): void { const disputeContractState = contract.disputes(disputeID); const dispute = new Dispute(disputeID.toString()); dispute.court = disputeContractState.value0.toString(); - dispute.arbitrated = event.params._arbitrable; + dispute.arbitrated = event.params._arbitrable.toHexString(); dispute.period = "evidence"; dispute.ruled = false; dispute.lastPeriodChange = event.block.timestamp; diff --git a/subgraph/src/entities/Juror.ts b/subgraph/src/entities/Juror.ts index cff8a9c67..8db9d3790 100644 --- a/subgraph/src/entities/Juror.ts +++ b/subgraph/src/entities/Juror.ts @@ -1,18 +1,18 @@ -import { Juror } from "../../generated/schema"; +import { User } from "../../generated/schema"; -export function ensureJuror(id: string): Juror { - let juror = Juror.load(id); +export function ensureUser(id: string): User { + let user = User.load(id); - if (juror) { - return juror; + if (user) { + return user; } - return createJurorFromAddress(id); + return createUserFromAddress(id); } -export function createJurorFromAddress(id: string): Juror { - const juror = new Juror(id); - juror.save(); +export function createUserFromAddress(id: string): User { + const user = new User(id); + user.save(); - return juror; + return user; } diff --git a/subgraph/src/entities/JurorTokensPerCourt.ts b/subgraph/src/entities/JurorTokensPerCourt.ts index 3a9accb77..d02417395 100644 --- a/subgraph/src/entities/JurorTokensPerCourt.ts +++ b/subgraph/src/entities/JurorTokensPerCourt.ts @@ -2,9 +2,9 @@ import { BigInt, Address } from "@graphprotocol/graph-ts"; import { KlerosCore } from "../../generated/KlerosCore/KlerosCore"; import { JurorTokensPerCourt } from "../../generated/schema"; import { updateActiveJurors, getDelta } from "../datapoint"; -import { ensureJuror } from "./Juror"; -import { ensureCourt } from "./Court"; +import { ensureUser } from "./Juror"; import { ZERO } from "../utils"; +import { loadCourtWithLog } from "./Court"; export function ensureJurorTokensPerCourt( jurorAddress: string, @@ -49,8 +49,9 @@ export function updateJurorStake( contract: KlerosCore, timestamp: BigInt ): void { - const juror = ensureJuror(jurorAddress); - const court = ensureCourt(courtID); + const juror = ensureUser(jurorAddress); + const court = loadCourtWithLog(courtID); + if (!court) return; const jurorTokens = ensureJurorTokensPerCourt(jurorAddress, courtID); const jurorBalance = contract.getJurorBalance( Address.fromString(jurorAddress), diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 0f988c46e..d76f12371 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -90,48 +90,16 @@ dataSources: - name: DisputeKitClassic file: ../contracts/deployments/arbitrumGoerli/DisputeKitClassic.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,indexed uint256,uint256,indexed address,uint256) + # handler: handleWithdrawal + - event: ChoiceFunded(indexed uint256,indexed uint256,indexed uint256) + handler: handleChoiceFunded - event: Evidence(indexed address,indexed uint256,indexed address,string) handler: handleEvidenceEvent - event: Justification(indexed uint256,indexed address,indexed uint256,string) handler: handleJustificationEvent file: ./src/DisputeKitClassic.ts - - kind: ethereum - name: HomeGateway - network: arbitrum-goerli - source: - address: "0xed12799915180a257985631fbD2ead261eD838cf" - abi: HomeGateway - startBlock: 998359 - mapping: - kind: ethereum/events - apiVersion: 0.0.6 - language: wasm/assemblyscript - entities: - - GatewayDispute - abis: - - name: HomeGateway - file: ../contracts/deployments/arbitrumGoerli/HomeGatewayToEthereum.json - eventHandlers: - - event: Dispute(indexed address,indexed uint256,uint256,uint256) - handler: handleDisputeEvent - file: ./src/HomeGateway.ts - - kind: ethereum - name: FastBridgeSender - network: arbitrum-goerli - source: - address: "0x4d18b9792e0D8F5aF696E71dBEDff8fcBEed6e8C" - abi: FastBridgeSender - startBlock: 998358 - mapping: - kind: ethereum/events - apiVersion: 0.0.6 - language: wasm/assemblyscript - entities: - - OutgoingMessage - abis: - - name: FastBridgeSender - file: ../contracts/deployments/arbitrumGoerli/FastBridgeSender.json - eventHandlers: - - event: BatchOutgoing(indexed uint256,uint256,uint256,bytes32) - handler: handleBatchOutgoing - file: ./src/FastBridgeSender.ts