Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(subgraph,web): fix coherent votes at vote level instead of dispute level #1702

Merged
merged 9 commits into from
Oct 7, 2024
5 changes: 3 additions & 2 deletions subgraph/core/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ type User @entity {
resolvedDisputes: [Dispute!]!
totalResolvedDisputes: BigInt!
totalDisputes: BigInt!
totalCoherent: BigInt!
coherenceScore: BigInt!
totalAppealingDisputes: BigInt!
totalCoherentVotes: BigInt!
totalResolvedVotes: BigInt!
coherenceScore: BigInt!
votes: [Vote!]! @derivedFrom(field: "juror")
contributions: [Contribution!]! @derivedFrom(field: "contributor")
evidences: [Evidence!]! @derivedFrom(field: "sender")
Expand Down
39 changes: 37 additions & 2 deletions subgraph/core/src/KlerosCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import { createDisputeKitFromEvent, filterSupportedDisputeKits } from "./entitie
import { createDisputeFromEvent } from "./entities/Dispute";
import { createRoundFromRoundInfo, updateRoundTimeline } from "./entities/Round";
import { updateCases, updateCasesAppealing, updateCasesRuled, updateCasesVoting } from "./datapoint";
import { addUserActiveDispute, ensureUser } from "./entities/User";
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 { Court, Dispute, Round, User } from "../generated/schema";
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";
Expand Down Expand Up @@ -127,6 +127,41 @@ export function handleNewPeriod(event: NewPeriod): void {
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;
Expand Down
2 changes: 1 addition & 1 deletion subgraph/core/src/entities/TokenAndEthShift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function updateTokenAndEthShiftFromEvent(event: TokenAndETHShiftEvent): v
const previousEthAmount = shift.ethAmount;
const newEthAmount = previousEthAmount.plus(ethAmount);
shift.ethAmount = newEthAmount;
resolveUserDispute(jurorAddress.toHexString(), previousEthAmount, newEthAmount, disputeID.toString());
resolveUserDispute(jurorAddress.toHexString(), disputeID.toString());
court.paidETH = court.paidETH.plus(ethAmount);
updatePaidETH(ethAmount, event.block.timestamp);
if (pnkAmount.gt(ZERO)) {
Expand Down
26 changes: 6 additions & 20 deletions subgraph/core/src/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { BigInt, BigDecimal } from "@graphprotocol/graph-ts";
import { User } from "../../generated/schema";
import { ONE, ZERO } from "../utils";

export function computeCoherenceScore(totalCoherent: BigInt, totalResolvedDisputes: BigInt): BigInt {
export function computeCoherenceScore(totalCoherentVotes: BigInt, totalResolvedVotes: BigInt): BigInt {
const smoothingFactor = BigDecimal.fromString("10");

let denominator = totalResolvedDisputes.toBigDecimal().plus(smoothingFactor);
let coherencyRatio = totalCoherent.toBigDecimal().div(denominator);
let denominator = totalResolvedVotes.toBigDecimal().plus(smoothingFactor);
let coherencyRatio = totalCoherentVotes.toBigDecimal().div(denominator);

const coherencyScore = coherencyRatio.times(BigDecimal.fromString("100"));

Expand Down Expand Up @@ -36,7 +36,8 @@ export function createUserFromAddress(id: string): User {
user.totalResolvedDisputes = ZERO;
user.totalAppealingDisputes = ZERO;
user.totalDisputes = ZERO;
user.totalCoherent = ZERO;
user.totalCoherentVotes = ZERO;
user.totalResolvedVotes = ZERO;
user.coherenceScore = ZERO;
user.save();

Expand All @@ -54,28 +55,13 @@ export function addUserActiveDispute(id: string, disputeID: string): void {
user.save();
}

export function resolveUserDispute(id: string, previousFeeAmount: BigInt, feeAmount: BigInt, disputeID: string): void {
export function resolveUserDispute(id: string, disputeID: string): void {
const user = ensureUser(id);
if (user.resolvedDisputes.includes(disputeID)) {
if (previousFeeAmount.gt(ZERO)) {
if (feeAmount.le(ZERO)) {
user.totalCoherent = user.totalCoherent.minus(ONE);
}
} else if (previousFeeAmount.le(ZERO)) {
if (feeAmount.gt(ZERO)) {
user.totalCoherent = user.totalCoherent.plus(ONE);
}
}
user.coherenceScore = computeCoherenceScore(user.totalCoherent, user.totalResolvedDisputes);
user.save();
return;
}
user.resolvedDisputes = user.resolvedDisputes.concat([disputeID]);
user.totalResolvedDisputes = user.totalResolvedDisputes.plus(ONE);
if (feeAmount.gt(ZERO)) {
user.totalCoherent = user.totalCoherent.plus(ONE);
}
user.activeDisputes = user.activeDisputes.minus(ONE);
user.coherenceScore = computeCoherenceScore(user.totalCoherent, user.totalResolvedDisputes);
user.save();
}
2 changes: 1 addition & 1 deletion subgraph/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kleros/kleros-v2-subgraph",
"version": "0.7.4",
"version": "0.7.6",
"license": "MIT",
"scripts": {
"update:core:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia core/subgraph.yaml",
Expand Down
24 changes: 12 additions & 12 deletions web/src/components/Popup/MiniGuides/JurorLevels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,32 +63,32 @@ const userLevelData = [
{
level: 1,
title: "Phytagoras",
totalCoherent: 6,
totalResolvedDisputes: 10,
totalCoherentVotes: 6,
totalResolvedVotes: 10,
},
{
level: 2,
title: "Socrates",
totalCoherent: 7,
totalResolvedDisputes: 10,
totalCoherentVotes: 7,
totalResolvedVotes: 10,
},
{
level: 3,
title: "Plato",
totalCoherent: 8,
totalResolvedDisputes: 10,
totalCoherentVotes: 8,
totalResolvedVotes: 10,
},
{
level: 4,
title: "Aristotle",
totalCoherent: 9,
totalResolvedDisputes: 10,
totalCoherentVotes: 9,
totalResolvedVotes: 10,
},
{
level: 0,
title: "Diogenes",
totalCoherent: 3,
totalResolvedDisputes: 10,
totalCoherentVotes: 3,
totalResolvedVotes: 10,
},
];

Expand All @@ -114,8 +114,8 @@ const RightContent: React.FC<{ currentPage: number }> = ({ currentPage }) => {
<PixelArt level={userData.level} width="189px" height="189px" />
<Coherency
userLevelData={userData}
totalCoherent={userData.totalCoherent}
totalResolvedDisputes={userData.totalResolvedDisputes}
totalCoherentVotes={userData.totalCoherentVotes}
totalResolvedVotes={userData.totalResolvedVotes}
isMiniGuide={true}
/>
</Card>
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useTopUsersByCoherenceScore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const topUsersByCoherenceScoreQuery = graphql(`
users(first: $first, orderBy: $orderBy, orderDirection: $orderDirection) {
id
coherenceScore
totalCoherent
totalCoherentVotes
totalResolvedVotes
totalResolvedDisputes
}
}
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export const userFragment = graphql(`
totalDisputes
totalResolvedDisputes
totalAppealingDisputes
totalCoherent
totalCoherentVotes
totalResolvedVotes
coherenceScore
tokens {
court {
Expand Down
10 changes: 5 additions & 5 deletions web/src/pages/Dashboard/JurorInfo/Coherency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ interface ICoherency {
level: number;
title: string;
};
totalCoherent: number;
totalResolvedDisputes: number;
totalCoherentVotes: number;
totalResolvedVotes: number;
isMiniGuide: boolean;
}

const Coherency: React.FC<ICoherency> = ({ userLevelData, totalCoherent, totalResolvedDisputes, isMiniGuide }) => {
const Coherency: React.FC<ICoherency> = ({ userLevelData, totalCoherentVotes, totalResolvedVotes, isMiniGuide }) => {
const votesContent = (
<label>
Coherent Votes:
<small>
{" "}
{totalCoherent}/{totalResolvedDisputes}{" "}
{totalCoherentVotes}/{totalResolvedVotes}{" "}
</small>
</label>
);
Expand All @@ -51,7 +51,7 @@ const Coherency: React.FC<ICoherency> = ({ userLevelData, totalCoherent, totalRe
<small>{userLevelData.title}</small>
<label>Level {userLevelData.level}</label>
<CircularProgress
progress={parseFloat(((totalCoherent / Math.max(totalResolvedDisputes, 1)) * 100).toFixed(2))}
progress={parseFloat(((totalCoherentVotes / Math.max(totalResolvedVotes, 1)) * 100).toFixed(2))}
/>
{!isMiniGuide ? (
<WithHelpTooltip place="left" {...{ tooltipMsg }}>
Expand Down
12 changes: 6 additions & 6 deletions web/src/pages/Dashboard/JurorInfo/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ const StyledLink = styled.a`
interface IHeader {
levelTitle: string;
levelNumber: number;
totalCoherent: number;
totalResolvedDisputes: number;
totalCoherentVotes: number;
totalResolvedVotes: number;
}

const Header: React.FC<IHeader> = ({ levelTitle, levelNumber, totalCoherent, totalResolvedDisputes }) => {
const Header: React.FC<IHeader> = ({ levelTitle, levelNumber, totalCoherentVotes, totalResolvedVotes }) => {
const [isJurorLevelsMiniGuideOpen, toggleJurorLevelsMiniGuide] = useToggle(false);

const coherencePercentage = parseFloat(((totalCoherent / Math.max(totalResolvedDisputes, 1)) * 100).toFixed(2));
const coherencePercentage = parseFloat(((totalCoherentVotes / Math.max(totalResolvedVotes, 1)) * 100).toFixed(2));
const courtUrl = window.location.origin;
const xPostText = `Hey I've been busy as a Juror on the Kleros court, check out my score: \n\nLevel: ${levelNumber} (${levelTitle})\nCoherence Percentage: ${coherencePercentage}%\nCoherent Votes: ${totalCoherent}/${totalResolvedDisputes}\n\nBe a juror with me! ➡️ ${courtUrl}`;
const xPostText = `Hey I've been busy as a Juror on the Kleros court, check out my score: \n\nLevel: ${levelNumber} (${levelTitle})\nCoherence Percentage: ${coherencePercentage}%\nCoherent Votes: ${totalCoherentVotes}/${totalResolvedVotes}\n\nBe a juror with me! ➡️ ${courtUrl}`;
const xShareUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(xPostText)}`;

return (
Expand All @@ -86,7 +86,7 @@ const Header: React.FC<IHeader> = ({ levelTitle, levelNumber, totalCoherent, tot
toggleMiniGuide={toggleJurorLevelsMiniGuide}
MiniGuideComponent={JurorLevels}
/>
{totalResolvedDisputes > 0 ? (
{totalResolvedVotes > 0 ? (
<StyledLink href={xShareUrl} target="_blank" rel="noreferrer">
<StyledXIcon /> <span>Share your juror score</span>
</StyledLink>
Expand Down
15 changes: 5 additions & 10 deletions web/src/pages/Dashboard/JurorInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,22 @@ const JurorInfo: React.FC = () => {
const { data } = useUserQuery(address?.toLowerCase() as `0x${string}`);
// TODO check graph schema
const coherenceScore = data?.user ? parseInt(data?.user?.coherenceScore) : 0;
const totalCoherent = data?.user ? parseInt(data?.user?.totalCoherent) : 0;
const totalCoherentVotes = data?.user ? parseInt(data?.user?.totalCoherentVotes) : 0;
const totalResolvedVotes = data?.user ? parseInt(data?.user?.totalResolvedVotes) : 0;
const totalResolvedDisputes = data?.user ? parseInt(data?.user?.totalResolvedDisputes) : 0;

const userLevelData = getUserLevelData(coherenceScore);
const userLevelData = getUserLevelData(coherenceScore, totalResolvedDisputes);

return (
<Container>
<Header
levelTitle={userLevelData.title}
levelNumber={userLevelData.level}
totalCoherent={totalCoherent}
totalResolvedDisputes={totalResolvedDisputes}
{...{ totalCoherentVotes, totalResolvedVotes }}
/>
<Card>
<PixelArt level={userLevelData.level} width="189px" height="189px" />
<Coherency
userLevelData={userLevelData}
totalCoherent={totalCoherent}
totalResolvedDisputes={totalResolvedDisputes}
isMiniGuide={false}
/>
<Coherency userLevelData={userLevelData} isMiniGuide={false} {...{ totalCoherentVotes, totalResolvedVotes }} />
<JurorRewards />
</Card>
</Container>
Expand Down
8 changes: 4 additions & 4 deletions web/src/pages/Home/TopJurors/JurorCard/Coherency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ const Container = styled.div`
`;

interface ICoherency {
totalCoherent: number;
totalResolvedDisputes: number;
totalCoherentVotes: number;
totalResolvedVotes: number;
}

const Coherency: React.FC<ICoherency> = ({ totalCoherent, totalResolvedDisputes }) => {
const coherenceRatio = `${totalCoherent}/${totalResolvedDisputes}`;
const Coherency: React.FC<ICoherency> = ({ totalCoherentVotes, totalResolvedVotes }) => {
const coherenceRatio = `${totalCoherentVotes}/${totalResolvedVotes}`;

return <Container>{coherenceRatio}</Container>;
};
Expand Down
10 changes: 6 additions & 4 deletions web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ const Container = styled.div`
interface IDesktopCard {
rank: number;
address: string;
totalCoherent: number;
totalCoherentVotes: number;
totalResolvedVotes: number;
totalResolvedDisputes: number;
coherenceScore: number;
}

const DesktopCard: React.FC<IDesktopCard> = ({
rank,
address,
totalCoherent,
totalCoherentVotes,
totalResolvedVotes,
totalResolvedDisputes,
coherenceScore,
}) => {
Expand All @@ -50,8 +52,8 @@ const DesktopCard: React.FC<IDesktopCard> = ({
<Rank rank={rank} />
<JurorTitle address={address} />
<Rewards address={address} />
<Coherency totalCoherent={totalCoherent} totalResolvedDisputes={totalResolvedDisputes} />
<JurorLevel coherenceScore={coherenceScore} />
<Coherency {...{ totalCoherentVotes, totalResolvedVotes }} />
<JurorLevel {...{ coherenceScore, totalResolvedDisputes }} />
</Container>
);
};
Expand Down
5 changes: 3 additions & 2 deletions web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ const StyledLabel = styled.label`

interface IJurorLevel {
coherenceScore: number;
totalResolvedDisputes: number;
}

const JurorLevel: React.FC<IJurorLevel> = ({ coherenceScore }) => {
const userLevelData = getUserLevelData(coherenceScore);
const JurorLevel: React.FC<IJurorLevel> = ({ coherenceScore, totalResolvedDisputes }) => {
const userLevelData = getUserLevelData(coherenceScore, totalResolvedDisputes);
const level = userLevelData.level;

return (
Expand Down
Loading
Loading