diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 135a9d4a0..5aecfcb11 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -96,8 +96,11 @@ type TokenAndETHShift @entity { id: ID! # user.id-dispute.id juror: User! dispute: Dispute! - tokenAmount: BigInt! + pnkAmount: BigInt! ethAmount: BigInt! + isNativeCurrency: Boolean! + feeTokenAmount: BigInt! + feeToken: FeeToken } type JurorTokensPerCourt @entity { @@ -159,6 +162,7 @@ type Round @entity { penalties: BigInt! drawnJurors: [Draw!]! @derivedFrom(field: "round") dispute: Dispute! + feeToken: FeeToken } type Draw @entity { @@ -191,6 +195,17 @@ type Counter @entity { casesRuled: BigInt! } +type FeeToken @entity { + id: ID! # The address of the ERC20 token. + accepted: Boolean! + rateInEth: BigInt! + rateDecimals: Int! + totalPaid: BigInt! + totalPaidInETH: BigInt! + rounds: [Round!] @derivedFrom(field: "feeToken") + tokenAndETHShift: [TokenAndETHShift!] @derivedFrom(field: "feeToken") +} + ##################### # ClassicDisputeKit # ##################### diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 28be0340c..e2c30e895 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -12,21 +12,23 @@ import { TokenAndETHShift as TokenAndETHShiftEvent, Ruling, StakeDelayed, + AcceptedFeeToken, } from "../generated/KlerosCore/KlerosCore"; import { ZERO, ONE } from "./utils"; import { createCourtFromEvent, getFeeForJuror } from "./entities/Court"; import { createDisputeKitFromEvent, filterSupportedDisputeKits } from "./entities/DisputeKit"; import { createDisputeFromEvent } from "./entities/Dispute"; import { createRoundFromRoundInfo } from "./entities/Round"; -import { updateCases, updatePaidETH, updateRedistributedPNK, updateCasesRuled, updateCasesVoting } from "./datapoint"; +import { updateCases, updateCasesRuled, updateCasesVoting } from "./datapoint"; import { addUserActiveDispute, ensureUser } from "./entities/User"; import { updateJurorDelayedStake, updateJurorStake } from "./entities/JurorTokensPerCourt"; import { createDrawFromEvent } from "./entities/Draw"; import { updateTokenAndEthShiftFromEvent } from "./entities/TokenAndEthShift"; import { updateArbitrableCases } from "./entities/Arbitrable"; -import { Court, Dispute } from "../generated/schema"; +import { Court, Dispute, FeeToken } from "../generated/schema"; import { BigInt } from "@graphprotocol/graph-ts"; import { updatePenalty } from "./entities/Penalty"; +import { ensureFeeToken } from "./entities/FeeToken"; function getPeriodName(index: i32): string { const periodArray = ["evidence", "commit", "vote", "appeal", "execution"]; @@ -156,22 +158,17 @@ export function handleStakeDelayed(event: StakeDelayed): void { } export function handleTokenAndETHShift(event: TokenAndETHShiftEvent): void { + updatePenalty(event); updateTokenAndEthShiftFromEvent(event); const jurorAddress = event.params._account.toHexString(); const disputeID = event.params._disputeID.toString(); - const pnkAmount = event.params._pnkAmount; - const feeAmount = event.params._feeAmount; const dispute = Dispute.load(disputeID); if (!dispute) return; const court = Court.load(dispute.court); if (!court) return; updateJurorStake(jurorAddress, court.id, KlerosCore.bind(event.address), event.block.timestamp); - court.paidETH = court.paidETH.plus(feeAmount); - if (pnkAmount.gt(ZERO)) { - court.paidPNK = court.paidPNK.plus(pnkAmount); - updateRedistributedPNK(pnkAmount, event.block.timestamp); - } - updatePaidETH(feeAmount, event.block.timestamp); - updatePenalty(event); - court.save(); +} + +export function handleAcceptedFeeToken(event: AcceptedFeeToken): void { + ensureFeeToken(event.params._token, event.address); } diff --git a/subgraph/src/entities/FeeToken.ts b/subgraph/src/entities/FeeToken.ts new file mode 100644 index 000000000..18cccd867 --- /dev/null +++ b/subgraph/src/entities/FeeToken.ts @@ -0,0 +1,53 @@ +import { BigInt, Address } from "@graphprotocol/graph-ts"; +import { FeeToken } from "../../generated/schema"; +import { KlerosCore } from "../../generated/KlerosCore/KlerosCore"; +import { ZERO } from "../utils"; + +export function ensureFeeToken(tokenAddress: Address, klerosCoreAddress: Address): FeeToken { + const hexTokenAddress = tokenAddress.toHexString(); + let feeToken = FeeToken.load(hexTokenAddress); + if (!feeToken) { + feeToken = new FeeToken(hexTokenAddress); + feeToken.totalPaid = ZERO; + feeToken.totalPaidInETH = ZERO; + } + const contract = KlerosCore.bind(klerosCoreAddress); + const currencyRate = contract.currencyRates(tokenAddress); + feeToken.accepted = currencyRate.value0; + feeToken.rateInEth = currencyRate.value1; + feeToken.rateDecimals = currencyRate.value2; + feeToken.save(); + return feeToken; +} + +export function updateFeeTokenRate(tokenAddress: Address, klerosCoreAddress: Address): void { + const feeToken = ensureFeeToken(tokenAddress, klerosCoreAddress); + const contract = KlerosCore.bind(klerosCoreAddress); + const currencyRate = contract.currencyRates(tokenAddress); + feeToken.accepted = currencyRate.value0; + feeToken.rateInEth = currencyRate.value1; + feeToken.rateDecimals = currencyRate.value2; + feeToken.save(); +} + +export function updateFeeTokenPaid(tokenAddress: Address, klerosCoreAddress: Address, amount: BigInt): void { + const feeToken = ensureFeeToken(tokenAddress, klerosCoreAddress); + const ethAmount = convertTokenAmountToEth(tokenAddress, amount, klerosCoreAddress); + feeToken.totalPaid = feeToken.totalPaid.plus(amount); + feeToken.totalPaidInETH = feeToken.totalPaidInETH.plus(ethAmount); + feeToken.save(); +} + +export function convertEthToTokenAmount(tokenAddress: Address, eth: BigInt, klerosCoreAddress: Address): BigInt { + const feeToken = ensureFeeToken(tokenAddress, klerosCoreAddress); + return eth.times(BigInt.fromI32(10 ** feeToken.rateDecimals)).div(feeToken.rateInEth); +} + +export function convertTokenAmountToEth( + tokenAddress: Address, + tokenAmount: BigInt, + klerosCoreAddress: Address +): BigInt { + const feeToken = ensureFeeToken(tokenAddress, klerosCoreAddress); + return tokenAmount.times(feeToken.rateInEth).div(BigInt.fromI32(10 ** feeToken.rateDecimals)); +} diff --git a/subgraph/src/entities/Round.ts b/subgraph/src/entities/Round.ts index bb369685e..320f5cf57 100644 --- a/subgraph/src/entities/Round.ts +++ b/subgraph/src/entities/Round.ts @@ -9,6 +9,7 @@ export function createRoundFromRoundInfo( ): void { const roundID = `${disputeID.toString()}-${roundIndex.toString()}`; const round = new Round(roundID); + const feeToken = roundInfo.getFeeToken(); round.disputeKit = roundInfo.getDisputeKitID.toString(); round.tokensAtStakePerJuror = roundInfo.getPnkAtStakePerJuror(); round.totalFeesForJurors = roundInfo.getTotalFeesForJurors(); @@ -16,5 +17,7 @@ export function createRoundFromRoundInfo( round.repartitions = roundInfo.getRepartitions(); round.penalties = roundInfo.getPnkPenalties(); round.dispute = disputeID.toString(); + round.feeToken = + feeToken.toHexString() === "0x0000000000000000000000000000000000000000" ? null : feeToken.toHexString(); round.save(); } diff --git a/subgraph/src/entities/TokenAndEthShift.ts b/subgraph/src/entities/TokenAndEthShift.ts index c0252d67c..5fca5dec4 100644 --- a/subgraph/src/entities/TokenAndEthShift.ts +++ b/subgraph/src/entities/TokenAndEthShift.ts @@ -1,36 +1,68 @@ +import { Address, BigInt } from "@graphprotocol/graph-ts"; import { TokenAndETHShift as TokenAndETHShiftEvent } from "../../generated/KlerosCore/KlerosCore"; -import { TokenAndETHShift } from "../../generated/schema"; +import { Court, Dispute, TokenAndETHShift } from "../../generated/schema"; +import { updatePaidETH, updateRedistributedPNK } from "../datapoint"; import { ZERO } from "../utils"; +import { convertTokenAmountToEth, updateFeeTokenPaid } from "./FeeToken"; import { resolveUserDispute } from "./User"; export function updateTokenAndEthShiftFromEvent(event: TokenAndETHShiftEvent): void { - const jurorAddress = event.params._account.toHexString(); - const disputeID = event.params._disputeID.toString(); - const shiftID = `${jurorAddress}-${disputeID}`; - const shift = TokenAndETHShift.load(shiftID); - - if (!shift) { - createTokenAndEthShiftFromEvent(event); - resolveUserDispute(jurorAddress, ZERO, event.params._feeAmount, disputeID); - return; + const jurorAddress = event.params._account; + const disputeID = event.params._disputeID; + const dispute = Dispute.load(disputeID.toString()); + if (!dispute) return; + const court = Court.load(dispute.court); + if (!court) return; + const roundIndex = event.params._roundID; + const feeTokenAddress = event.params._feeToken; + let shift = ensureTokenAndEthShift(jurorAddress, disputeID, roundIndex, feeTokenAddress); + const feeAmount = event.params._feeAmount; + const pnkAmount = event.params._pnkAmount; + let ethAmount: BigInt; + if (feeTokenAddress.toHexString() === "0x0000000000000000000000000000000000000000") { + updateFeeTokenPaid(feeTokenAddress, event.address, feeAmount); + ethAmount = convertTokenAmountToEth(feeTokenAddress, feeAmount, event.address); + shift.feeTokenAmount = shift.feeTokenAmount.plus(feeAmount); + } else { + ethAmount = feeAmount; } - - shift.tokenAmount = shift.tokenAmount.plus(event.params._pnkAmount); - const previousFeeAmount = shift.ethAmount; - const newFeeAmount = shift.ethAmount.plus(event.params._feeAmount); - shift.ethAmount = newFeeAmount; + const previousEthAmount = shift.ethAmount; + const newEthAmount = previousEthAmount.plus(ethAmount); + shift.ethAmount = newEthAmount; + resolveUserDispute(jurorAddress.toHexString(), previousEthAmount, newEthAmount, disputeID.toString()); + court.paidETH = court.paidETH.plus(ethAmount); + updatePaidETH(ethAmount, event.block.timestamp); + if (pnkAmount.gt(ZERO)) { + court.paidPNK = court.paidPNK.plus(pnkAmount); + updateRedistributedPNK(pnkAmount, event.block.timestamp); + } + shift.pnkAmount = shift.pnkAmount.plus(pnkAmount); shift.save(); - resolveUserDispute(jurorAddress, previousFeeAmount, newFeeAmount, disputeID); + court.save(); } -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._pnkAmount; - shift.ethAmount = event.params._feeAmount; - shift.save(); +export function ensureTokenAndEthShift( + jurorAddress: Address, + disputeID: BigInt, + roundIndex: BigInt, + feeTokenAddress: Address +): TokenAndETHShift { + const shiftID = `${jurorAddress.toHexString()}-${disputeID.toString()}-${roundIndex.toString()}`; + let shift = TokenAndETHShift.load(shiftID); + if (!shift) { + shift = new TokenAndETHShift(shiftID); + if (feeTokenAddress !== Address.fromI32(0)) { + shift.isNativeCurrency = false; + shift.feeToken = feeTokenAddress.toHexString(); + } else { + shift.isNativeCurrency = true; + } + shift.feeTokenAmount = ZERO; + shift.ethAmount = ZERO; + shift.juror = jurorAddress.toHexString(); + shift.dispute = disputeID.toString(); + shift.pnkAmount = ZERO; + shift.save(); + } + return shift; } diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index d69b93290..3aa625bfe 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -54,6 +54,8 @@ dataSources: handler: handleTokenAndETHShift - event: Ruling(indexed address,indexed uint256,uint256) handler: handleRuling + - event: AcceptedFeeToken(indexed address,indexed bool) + handler: handleAcceptedFeeToken file: ./src/KlerosCore.ts - kind: ethereum name: PolicyRegistry