Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
35da777
feat(subgraph): add fee token to subgraph
kemuru Jul 13, 2023
4020a85
Merge branch 'dev' into feat(subgraph)/add-fee-token-to-subgraph
kemuru Jul 13, 2023
500d28d
feat: new params in subgraph
nhestrompia Jul 14, 2023
b04fd1f
fix: null errors
kemuru Jul 14, 2023
5654c92
refactor(web): binary-to-classic
nhestrompia Jul 18, 2023
361bc8e
fix: null value in feeToken
nhestrompia Jul 20, 2023
1da86ea
fix(web): verdict result components
nhestrompia Jul 27, 2023
476d26f
fix: import order
nhestrompia Jul 27, 2023
264d287
Merge branch 'dev' into fix(web)/verdict-result-components
alcercu Jul 28, 2023
6df5a39
feat: general popup style
nhestrompia Jul 28, 2023
fb1e2ce
feat(web): add stake, withdraw, voting, appeal popups
kemuru Jul 28, 2023
f3d018a
fix(web): staking refetch
kemuru Jul 27, 2023
a2a0e40
refactor(web): change pnkbalance and allowance to autogenerated hooks
kemuru Jul 27, 2023
3aa738a
chore: delete unused custom queries
kemuru Jul 28, 2023
08b4914
Merge branch 'dev' into fix(web)/verdict-result-components
alcercu Aug 1, 2023
8405b72
Merge branch 'dev' into fix(web)/staking-refetch-and-improving-fetchi…
alcercu Aug 1, 2023
8fdda48
feat: logic and behavior implementations
nhestrompia Aug 2, 2023
923b0af
fix: code smells
nhestrompia Aug 2, 2023
c2ce7fd
fix: code smell
nhestrompia Aug 2, 2023
eb8d2d5
Merge branch 'dev' into fix(web)/staking-refetch-and-improving-fetchi…
alcercu Aug 2, 2023
76404b1
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 2, 2023
3b00a2c
Merge pull request #1047 from kleros/refactor(web)/binary-to-classic
alcercu Aug 2, 2023
266e704
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 2, 2023
2ad8518
fix(subgraph): correctly convert fees to eth
alcercu Aug 2, 2023
8d3d192
refactor(web): use subgraph instead of calling contract for overridde…
alcercu Aug 2, 2023
85b5c8d
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 2, 2023
f4285ed
Merge pull request #1056 from kleros/fix(web)/verdict-result-components
alcercu Aug 2, 2023
ae90c62
fix: dashboard-data-and-wallet-connection
nhestrompia Aug 3, 2023
e8801e8
fix(subgraph): avoid creating a feeToken when using native currency
alcercu Aug 3, 2023
327a567
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
33f3532
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
7338bd5
Merge branch 'dev' into fix(web)/staking-refetch-and-improving-fetchi…
alcercu Aug 3, 2023
d270bdd
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
1a86323
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
726d092
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
52f68f7
refactor: code smell and icons refactor
nhestrompia Aug 3, 2023
877bd68
fix(subgraph): correctly handle feeToken
alcercu Aug 3, 2023
2ade29e
Merge pull request #1037 from kleros/feat(subgraph)/add-fee-token-to-…
alcercu Aug 3, 2023
10a65b6
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
30f85f3
Merge pull request #1089 from kleros/fix(web)/dashboard-data-and-wall…
alcercu Aug 3, 2023
b4c64be
chore(web): clean/clear
jaybuidl Aug 3, 2023
26b2cb6
fix: updated icons
nhestrompia Aug 3, 2023
a7ee03e
fix: broken link to the Court Policy
jaybuidl Aug 3, 2023
ccd375a
Merge branch 'fix(web)/staking-refetch-and-improving-fetching-timing'…
alcercu Aug 3, 2023
76b3c6a
Merge pull request #1064 from kleros/feat(web)/various-info-popups
alcercu Aug 3, 2023
e56f4b1
Merge branch 'dev' into fix(web)/staking-refetch-and-improving-fetchi…
alcercu Aug 3, 2023
51cf1ac
fix: parse Markdown in the voting component, not just in the voting h…
jaybuidl Aug 3, 2023
78934e6
Update Web3Provider.tsx
alcercu Aug 4, 2023
363a585
fix(subgraph): correctly track number of votes
alcercu Aug 4, 2023
cadc4f9
fix(web): improve voting history wording
alcercu Aug 4, 2023
94936ba
Merge pull request #1099 from kleros/fix(subgraph)/number-of-votes
alcercu Aug 4, 2023
5692550
fix(web): clear timeout when unmounting component
alcercu Aug 4, 2023
aef101e
fix: typo
jaybuidl Aug 4, 2023
18c56fe
Merge pull request #1101 from kleros/fix(web)/countdown-behaviour
alcercu Aug 4, 2023
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
1 change: 1 addition & 0 deletions bot-pinner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ $ docker-compose up -d
2. Evidence container that awaits new events and then scrapes the latest hashes and submits it to IPFS.
3. `src/peers.txt` contains a list of peers, by default it will add connects to Estuary & Kleros IPFS nodes. This should make it easier to find content by creating a data network around Kleros Court V2.
1. Adding these peers will make it easier to find and replicate content.

## DappNode

:warning: For the following steps, you need access to [a DappNode](https://dappnode.io) system with the IPFS service running.
Expand Down
4 changes: 2 additions & 2 deletions contracts/scripts/simulations/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ task("simulate:cast-commit", "Casts a commit for a drawn juror")
const tx = await disputeKitClassic.connect(wallet).castCommit(...castCommitFunctionArgs);
await tx.wait();

console.log("juror %s casted a commit on txID: %s", wallet.address, tx?.hash);
console.log("juror %s cast a commit on txID: %s", wallet.address, tx?.hash);
});

task("simulate:cast-vote", "Casts a vote for a drawn juror")
Expand All @@ -295,7 +295,7 @@ task("simulate:cast-vote", "Casts a vote for a drawn juror")
const tx = await disputeKitClassic.connect(wallet).castVote(...castVoteFunctionArgs);
await tx.wait();

console.log("juror %s casted a vote on txID: %s", wallet.address, tx?.hash);
console.log("juror %s cast a vote on txID: %s", wallet.address, tx?.hash);
});

task("simulate:fund-appeal", "Funds an appeal on a dispute")
Expand Down
17 changes: 16 additions & 1 deletion subgraph/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -159,6 +162,7 @@ type Round @entity {
penalties: BigInt!
drawnJurors: [Draw!]! @derivedFrom(field: "round")
dispute: Dispute!
feeToken: FeeToken
}

type Draw @entity {
Expand Down Expand Up @@ -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 #
#####################
Expand Down
10 changes: 7 additions & 3 deletions subgraph/src/DisputeKitClassic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,17 @@ export function handleEvidenceEvent(event: EvidenceEvent): void {
}

export function handleJustificationEvent(event: JustificationEvent): void {
const coreDisputeID = event.params._coreDisputeID.toString();
const coreDispute = Dispute.load(coreDisputeID);
const contract = DisputeKitClassic.bind(event.address);
const coreDisputeID = event.params._coreDisputeID;
const coreDispute = Dispute.load(coreDisputeID.toString());
const classicDisputeID = `${DISPUTEKIT_ID}-${coreDisputeID}`;
const classicDispute = ClassicDispute.load(classicDisputeID);
if (!classicDispute || !coreDispute) return;
const choice = event.params._choice;
const coreRoundIndex = coreDispute.currentRoundIndex;
const roundInfo = contract.getRoundInfo(coreDisputeID, coreRoundIndex, choice);
const currentLocalRoundID = classicDispute.id + "-" + classicDispute.currentLocalRoundIndex.toString();
const currentRulingInfo = updateCountsAndGetCurrentRuling(currentLocalRoundID, event.params._choice);
const currentRulingInfo = updateCountsAndGetCurrentRuling(currentLocalRoundID, choice, roundInfo.getChoiceCount());
coreDispute.currentRuling = currentRulingInfo.ruling;
coreDispute.tied = currentRulingInfo.tied;
coreDispute.save();
Expand Down
21 changes: 9 additions & 12 deletions subgraph/src/KlerosCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down Expand Up @@ -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);
}
12 changes: 6 additions & 6 deletions subgraph/src/entities/ClassicRound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ class CurrentRulingInfo {
tied: boolean;
}

export function updateCountsAndGetCurrentRuling(id: string, choice: BigInt): CurrentRulingInfo {
export function updateCountsAndGetCurrentRuling(id: string, choice: BigInt, choiceCount: BigInt): CurrentRulingInfo {
const round = ClassicRound.load(id);
if (!round) return { ruling: ZERO, tied: false };
const choiceNum = choice.toI32();
const updatedCount = round.counts[choiceNum].plus(ONE);
const delta = choiceCount.minus(round.counts[choiceNum]);
let newCounts: BigInt[] = [];
for (let i = 0; i < round.counts.length; i++) {
if (BigInt.fromI32(i).equals(choice)) {
newCounts.push(round.counts[i].plus(ONE));
newCounts.push(choiceCount);
} else {
newCounts.push(round.counts[i]);
}
Expand All @@ -43,14 +43,14 @@ export function updateCountsAndGetCurrentRuling(id: string, choice: BigInt): Cur
if (choice.equals(round.winningChoice)) {
if (round.tied) round.tied = false;
} else {
if (updatedCount.equals(currentWinningCount)) {
if (choiceCount.equals(currentWinningCount)) {
if (!round.tied) round.tied = true;
} else if (updatedCount.gt(currentWinningCount)) {
} else if (choiceCount.gt(currentWinningCount)) {
round.winningChoice = choice;
round.tied = false;
}
}
round.totalVoted = round.totalVoted.plus(ONE);
round.totalVoted = round.totalVoted.plus(delta);
round.save();
return { ruling: round.winningChoice, tied: round.tied };
}
Expand Down
53 changes: 53 additions & 0 deletions subgraph/src/entities/FeeToken.ts
Original file line number Diff line number Diff line change
@@ -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));
}
3 changes: 3 additions & 0 deletions subgraph/src/entities/Round.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ 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();
round.nbVotes = roundInfo.getNbVotes();
round.repartitions = roundInfo.getRepartitions();
round.penalties = roundInfo.getPnkPenalties();
round.dispute = disputeID.toString();
round.feeToken =
feeToken.toHexString() === "0x0000000000000000000000000000000000000000" ? null : feeToken.toHexString();
round.save();
}
84 changes: 58 additions & 26 deletions subgraph/src/entities/TokenAndEthShift.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions subgraph/subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion web/.parcelrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"extends": "@parcel/config-default",
"transformers": {
"*.svg": ["...", "@parcel/transformer-svg-react"]
"*.svg": ["...", "@parcel/transformer-svg-react"],
"tsx:*.svg": ["@parcel/transformer-svg-react"],
"tsx:*": ["..."]
}
}
3 changes: 1 addition & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
},
"packageManager": "[email protected]",
"scripts": {
"clear": "rm -r .parcel-cache",
"clean": "rm dist/bundle.js",
"clear": "rm -fr ../.parcel-cache dist/bundle.js",
"start": "parcel",
"start-local": "REACT_APP_SUBGRAPH_ENDPOINT=http://localhost:8000/subgraphs/name/kleros/kleros-v2-core-local parcel",
"build": "yarn generate && yarn parcel build",
Expand Down
Binary file added web/src/assets/svgs/icons/appeal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading