kleros/kleros-v2

View on GitHub
subgraph/core/src/KlerosCore.ts

Summary

Maintainability
D
2 days
Test Coverage
import {
  KlerosCore,
  AppealDecision,
  DisputeCreation,
  DisputeKitCreated,
  DisputeKitEnabled,
  CourtCreated,
  CourtModified,
  Draw as DrawEvent,
  NewPeriod,
  TokenAndETHShift as TokenAndETHShiftEvent,
  CourtJump,
  Ruling,
  AcceptedFeeToken,
} from "../generated/KlerosCore/KlerosCore";
import { ZERO, ONE } from "./utils";
import { createCourtFromEvent } from "./entities/Court";
import { createDisputeKitFromEvent, filterSupportedDisputeKits } from "./entities/DisputeKit";
import { createDisputeFromEvent } from "./entities/Dispute";
import { createRoundFromRoundInfo, updateRoundTimeline } from "./entities/Round";
import { updateCases, updateCasesAppealing, updateCasesRuled, updateCasesVoting } from "./datapoint";
import { addUserActiveDispute, computeCoherenceScore, ensureUser } from "./entities/User";
import { updateJurorStake } from "./entities/JurorTokensPerCourt";
import { createDrawFromEvent } from "./entities/Draw";
import { updateTokenAndEthShiftFromEvent } from "./entities/TokenAndEthShift";
import { updateArbitrableCases } from "./entities/Arbitrable";
import { ClassicVote, Court, Dispute, Draw, Round, User } from "../generated/schema";
import { BigInt } from "@graphprotocol/graph-ts";
import { updatePenalty } from "./entities/Penalty";
import { ensureFeeToken } from "./entities/FeeToken";
import { getAndIncrementPeriodCounter } from "./entities/PeriodIndexCounter";
import { SortitionModule } from "../generated/SortitionModule/SortitionModule";

function getPeriodName(index: i32): string {
  const periodArray = ["evidence", "commit", "vote", "appeal", "execution"];
  return periodArray.at(index) || "None";
}

export function handleCourtCreated(event: CourtCreated): void {
  createCourtFromEvent(event);
}

export function handleCourtModified(event: CourtModified): void {
  const court = Court.load(event.params._courtID.toString());
  if (!court) return;
  court.hiddenVotes = event.params._hiddenVotes;
  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.save();
}

export function handleDisputeKitCreated(event: DisputeKitCreated): void {
  createDisputeKitFromEvent(event);
}

export function handleDisputeKitEnabled(event: DisputeKitEnabled): void {
  const court = Court.load(event.params._courtID.toString());
  if (!court) return;
  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 disputeStorage = contract.disputes(disputeID);
  const courtID = disputeStorage.value0.toString();
  const court = Court.load(courtID);
  if (!court) return;
  court.numberDisputes = court.numberDisputes.plus(ONE);

  const roundInfo = contract.getRoundInfo(disputeID, ZERO);
  court.numberVotes = court.numberVotes.plus(roundInfo.nbVotes);

  court.save();
  createDisputeFromEvent(event);
  createRoundFromRoundInfo(KlerosCore.bind(event.address), disputeID, ZERO, roundInfo);
  const arbitrable = event.params._arbitrable.toHexString();
  updateArbitrableCases(arbitrable, ONE);
  updateCases(ONE, event.block.timestamp);
}

export function handleNewPeriod(event: NewPeriod): void {
  const disputeID = event.params._disputeID;
  const dispute = Dispute.load(disputeID.toString());
  if (!dispute) return;
  const court = Court.load(dispute.court);
  if (!court) return;

  if (dispute.period.includes("vote")) {
    court.numberVotingDisputes = court.numberVotingDisputes.minus(ONE);
    updateCasesVoting(BigInt.fromI32(-1), event.block.timestamp);
  } else if (dispute.period.includes("appeal")) {
    let juror: User;
    for (let i = 0; i < dispute.jurors.entries.length; i++) {
      juror = ensureUser(dispute.jurors.entries[i].value.toString());
      juror.totalAppealingDisputes = juror.totalAppealingDisputes.minus(ONE);
      juror.save();
    }
    court.numberAppealingDisputes = court.numberAppealingDisputes.minus(ONE);
    updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp);
  }

  const newPeriod = getPeriodName(event.params._period);
  if (newPeriod === "vote") {
    court.numberVotingDisputes = court.numberVotingDisputes.plus(ONE);
    updateCasesVoting(ONE, event.block.timestamp);
  } else if (newPeriod === "appeal") {
    let juror: User;
    for (let i = 0; i < dispute.jurors.entries.length; i++) {
      juror = ensureUser(dispute.jurors.entries[i].value.toString());
      juror.totalAppealingDisputes = juror.totalAppealingDisputes.plus(ONE);
      juror.save();
    }
    court.numberAppealingDisputes = court.numberAppealingDisputes.plus(ONE);
    updateCasesAppealing(ONE, event.block.timestamp);
  } else if (newPeriod === "execution") {
    const contract = KlerosCore.bind(event.address);
    const currentRulingInfo = contract.currentRuling(disputeID);
    dispute.currentRuling = currentRulingInfo.getRuling();
    dispute.overridden = currentRulingInfo.getOverridden();
    dispute.tied = currentRulingInfo.getTied();

    const rounds = dispute.rounds.load();
    for (let i = 0; i < rounds.length; i++) {
      const round = Round.load(rounds[i].id);
      if (!round) continue;

      const draws = round.drawnJurors.load();
      // Iterate over all draws in the round
      for (let j = 0; j < draws.length; j++) {
        const draw = Draw.load(draws[j].id);
        if (!draw) continue;

        // Since this is a ClassicVote entity, this will only work for the Classic DisputeKit (which has ID "1").
        const vote = ClassicVote.load(`${round.disputeKit}-${draw.id}`);

        if (!vote) continue;

        const juror = ensureUser(draw.juror);
        juror.totalResolvedVotes = juror.totalResolvedVotes.plus(ONE);

        if (vote.choice === null) continue;

        // Check if the vote choice matches the final ruling
        if (vote.choice!.equals(dispute.currentRuling)) {
          juror.totalCoherentVotes = juror.totalCoherentVotes.plus(ONE);
        }

        // Recalculate coherenceScore
        if (juror.totalResolvedVotes.gt(ZERO)) {
          juror.coherenceScore = computeCoherenceScore(juror.totalCoherentVotes, juror.totalResolvedVotes);
        }

        juror.save();
      }
    }
  }

  dispute.period = newPeriod;
  dispute.lastPeriodChange = event.block.timestamp;
  dispute.lastPeriodChangeBlockNumber = event.block.number;
  dispute.periodNotificationIndex = getAndIncrementPeriodCounter(newPeriod);
  if (newPeriod !== "execution") {
    dispute.periodDeadline = event.block.timestamp.plus(court.timesPerPeriod[event.params._period]);
  } else {
    dispute.periodDeadline = BigInt.fromU64(U64.MAX_VALUE);
  }
  updateRoundTimeline(disputeID.toString(), newPeriod, event.block.timestamp);
  dispute.save();
  court.save();
}

export function handleRuling(event: Ruling): void {
  updateCasesRuled(ONE, event.block.timestamp);
  const disputeID = event.params._disputeID;
  const dispute = Dispute.load(disputeID.toString());
  if (!dispute) return;
  dispute.ruled = true;
  dispute.save();
  const court = Court.load(dispute.court);
  if (!court) return;
  court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE);
  court.save();
}

export function handleAppealDecision(event: AppealDecision): void {
  const contract = KlerosCore.bind(event.address);
  const disputeID = event.params._disputeID;
  const dispute = Dispute.load(disputeID.toString());
  if (!dispute) return;
  const newRoundIndex = dispute.currentRoundIndex.plus(ONE);
  const roundID = `${disputeID}-${newRoundIndex.toString()}`;
  dispute.currentRoundIndex = newRoundIndex;
  dispute.currentRound = roundID;
  dispute.save();
  const roundInfo = contract.getRoundInfo(disputeID, newRoundIndex);

  const disputeStorage = contract.disputes(disputeID);
  const courtID = disputeStorage.value0.toString();
  const court = Court.load(courtID);
  if (!court) return;

  court.numberVotes = court.numberVotes.plus(roundInfo.nbVotes);
  court.save();

  createRoundFromRoundInfo(KlerosCore.bind(event.address), disputeID, newRoundIndex, roundInfo);
}

export function handleCourtJump(event: CourtJump): void {
  const dispute = Dispute.load(event.params._disputeID.toString());
  if (!dispute) return;
  dispute.court = event.params._toCourtID.toString();
  dispute.save();
}

export function handleDraw(event: DrawEvent): void {
  createDrawFromEvent(event);
  const disputeID = event.params._disputeID.toString();
  const dispute = Dispute.load(disputeID);
  if (!dispute) return;
  const klerosCore = KlerosCore.bind(event.address);
  const sortitionModule = SortitionModule.bind(klerosCore.sortitionModule());

  const jurorAddress = event.params._address.toHexString();
  updateJurorStake(jurorAddress, dispute.court, sortitionModule, event.block.timestamp);
  addUserActiveDispute(jurorAddress, disputeID);

  const roundIndex = event.params._roundID;
  const roundID = `${disputeID}-${roundIndex.toString()}`;

  const currentRound = Round.load(roundID);
  if (!currentRound) return;

  if (currentRound.nbVotes.toI32() === currentRound.drawnJurors.load().length) {
    currentRound.jurorsDrawn = true;
    currentRound.save();
  }
}

export function handleTokenAndETHShift(event: TokenAndETHShiftEvent): void {
  updatePenalty(event);
  updateTokenAndEthShiftFromEvent(event);
  const jurorAddress = event.params._account.toHexString();
  const disputeID = event.params._disputeID.toString();
  const dispute = Dispute.load(disputeID);
  if (!dispute) return;
  const court = Court.load(dispute.court);
  if (!court) return;
  const klerosCore = KlerosCore.bind(event.address);
  const sortitionModule = SortitionModule.bind(klerosCore.sortitionModule());
  updateJurorStake(jurorAddress, court.id, sortitionModule, event.block.timestamp);

  const roundIndex = event.params._roundID;
  const roundID = `${disputeID}-${roundIndex.toString()}`;

  const round = Round.load(roundID);
  if (!round) return;

  const roundInfo = klerosCore.getRoundInfo(event.params._disputeID, roundIndex);
  const repartitions = roundInfo.repartitions;
  const nbVotes = roundInfo.nbVotes;

  if (repartitions >= nbVotes) {
    round.jurorRewardsDispersed = true;
    round.save();
  }
}

export function handleAcceptedFeeToken(event: AcceptedFeeToken): void {
  ensureFeeToken(event.params._token, event.address);
}