Skip to content

Commit e85461b

Browse files
committed
refactor(web): extra stats block query algorithm improvement
1 parent 781c226 commit e85461b

File tree

4 files changed

+136
-110
lines changed

4 files changed

+136
-110
lines changed

web/src/components/ExtraStatsDisplay.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,18 @@ const StyledExtraStatTitleSkeleton = styled(StyledSkeleton)`
4242

4343
export interface IExtraStatsDisplay {
4444
title: string;
45-
text: string;
4645
icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
46+
content?: React.ReactNode;
47+
text?: string;
4748
}
4849

49-
const ExtraStatsDisplay: React.FC<IExtraStatsDisplay> = ({ title, text, icon: Icon, ...props }) => {
50+
const ExtraStatsDisplay: React.FC<IExtraStatsDisplay> = ({ title, text, content, icon: Icon, ...props }) => {
5051
return (
5152
<Container {...props}>
5253
<SVGContainer>{<Icon />}</SVGContainer>
5354
<TextContainer>
5455
<label>{title}:</label>
55-
<StyledP>{!isUndefined(text) ? text : <StyledExtraStatTitleSkeleton />}</StyledP>
56+
{content ? content : <StyledP>{!isUndefined(text) ? text : <StyledExtraStatTitleSkeleton />}</StyledP>}
5657
</TextContainer>
5758
</Container>
5859
);
+114-97
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useQuery } from "@tanstack/react-query";
22

33
import { useGraphqlBatcher } from "context/GraphqlBatcher";
4-
import { useMemo } from "react";
4+
import { isUndefined } from "utils/index";
55

66
import { graphql } from "src/graphql";
77
import { HomePageBlockQuery } from "src/graphql/graphql";
@@ -34,11 +34,32 @@ const homePageBlockQuery = graphql(`
3434
}
3535
`);
3636

37-
export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boolean) => {
38-
const isEnabled = blockNumber !== null || allTime;
37+
type Court = HomePageBlockQuery["presentCourts"][number];
38+
type CourtWithTree = Court & {
39+
numberDisputes: number;
40+
numberVotes: number;
41+
feeForJuror: bigint;
42+
effectiveStake: bigint;
43+
treeNumberDisputes: number;
44+
treeNumberVotes: number;
45+
votesPerPnk: number;
46+
treeVotesPerPnk: number;
47+
expectedRewardPerPnk: number;
48+
treeExpectedRewardPerPnk: number;
49+
};
50+
51+
export type HomePageBlockStats = {
52+
mostDisputedCourt: CourtWithTree;
53+
bestDrawingChancesCourt: CourtWithTree;
54+
bestExpectedRewardCourt: CourtWithTree;
55+
courts: CourtWithTree[];
56+
};
57+
58+
export const useHomePageBlockQuery = (blockNumber: number | undefined, allTime: boolean) => {
59+
const isEnabled = !isUndefined(blockNumber) || allTime;
3960
const { graphqlBatcher } = useGraphqlBatcher();
4061

41-
const usedQuery = useQuery({
62+
return useQuery<HomePageBlockStats>({
4263
queryKey: [`homePageBlockQuery${blockNumber}-${allTime}`],
4364
enabled: isEnabled,
4465
staleTime: Infinity,
@@ -48,104 +69,100 @@ export const useHomePageBlockQuery = (blockNumber: number | null, allTime: boole
4869
document: homePageBlockQuery,
4970
variables: { blockNumber },
5071
});
51-
return data;
72+
73+
return processData(data, allTime);
5274
},
5375
});
76+
};
5477

55-
const courtActivityStats = useMemo(() => {
56-
if (usedQuery.data && !usedQuery.isFetching) {
57-
const diffCourts = allTime
58-
? usedQuery.data.presentCourts.map((presentCourt) => ({
59-
...presentCourt,
60-
numberDisputes: presentCourt.numberDisputes,
61-
treeNumberDisputes: presentCourt.numberDisputes,
62-
numberVotes: presentCourt.numberVotes,
63-
treeNumberVotes: presentCourt.numberVotes,
64-
effectiveStake: presentCourt.effectiveStake,
65-
votesPerPnk: Number(presentCourt.numberVotes) / (Number(presentCourt.effectiveStake) / 1e18),
66-
treeVotesPerPnk: Number(presentCourt.numberVotes) / (Number(presentCourt.effectiveStake) / 1e18),
67-
}))
68-
: usedQuery.data.presentCourts.map((presentCourt) => {
69-
const pastCourt = usedQuery.data.pastCourts.find((pastCourt) => pastCourt.id === presentCourt.id);
70-
71-
return {
72-
...presentCourt,
73-
numberDisputes: pastCourt
74-
? presentCourt.numberDisputes - pastCourt.numberDisputes
75-
: presentCourt.numberDisputes,
76-
treeNumberDisputes: pastCourt
77-
? presentCourt.numberDisputes - pastCourt.numberDisputes
78-
: presentCourt.numberDisputes,
79-
numberVotes: pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes,
80-
treeNumberVotes: pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes,
81-
effectiveStake: pastCourt
82-
? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n
83-
: presentCourt.effectiveStake,
84-
votesPerPnk:
85-
Number(pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes) /
86-
(Number(
87-
pastCourt
88-
? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n
89-
: presentCourt.effectiveStake
90-
) /
91-
1e18),
92-
treeVotesPerPnk:
93-
Number(pastCourt ? presentCourt.numberVotes - pastCourt.numberVotes : presentCourt.numberVotes) /
94-
(Number(
95-
pastCourt
96-
? (BigInt(presentCourt.effectiveStake) + BigInt(pastCourt.effectiveStake)) / 2n
97-
: presentCourt.effectiveStake
98-
) /
99-
1e18),
100-
};
101-
});
102-
103-
const mostDisputedCourt = diffCourts.toSorted((a, b) => b.numberDisputes - a.numberDisputes)[0];
104-
// 1. biggest chances of getting drawn
105-
// fact: getting drawn in a parent court also subjects you to its rewards
106-
// so, rewards/disputes trickle down
107-
108-
for (const parent of diffCourts) {
109-
for (const child of diffCourts) {
110-
if (parent.id === child.parent?.id) {
111-
child.treeNumberVotes = String(Number(parent.treeNumberVotes) + Number(child.treeNumberVotes));
112-
}
113-
}
114-
}
78+
const processData = (data: HomePageBlockQuery, allTime: boolean) => {
79+
const presentCourts = data.presentCourts;
80+
const pastCourts = data.pastCourts;
81+
const processedCourts: CourtWithTree[] = Array(presentCourts.length);
82+
const processed = new Set();
11583

116-
for (const parent of diffCourts) {
117-
for (const child of diffCourts) {
118-
if (parent.id === child.parent?.id) {
119-
child.treeVotesPerPnk += parent.votesPerPnk;
120-
}
121-
}
122-
}
123-
const bestDrawingChancesCourt = diffCourts.toSorted((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0];
124-
// 2. expected reward
125-
// since we isolated the exclusive disputes from the cumulative disputes
126-
// we can calculate the "isolated reward" of every court
127-
// after that's done, then just trickle the rewards down
128-
129-
for (const c of diffCourts) {
130-
c.expectedRewardPerPnk = c.votesPerPnk * c.feeForJuror;
131-
c.treeExpectedRewardPerPnk = c.expectedRewardPerPnk;
132-
}
133-
for (const parent of diffCourts) {
134-
for (const child of diffCourts) {
135-
if (parent.id === child.parent?.id) {
136-
child.treeExpectedRewardPerPnk = parent.treeExpectedRewardPerPnk + child.treeExpectedRewardPerPnk;
137-
}
138-
}
139-
}
140-
const bestExpectedRewardCourt = diffCourts.toSorted(
141-
(a, b) => b.treeExpectedRewardPerPnk - a.treeExpectedRewardPerPnk
142-
)[0];
84+
const processCourt = (id: number): CourtWithTree => {
85+
if (processed.has(id)) return processedCourts[id];
86+
87+
processed.add(id);
88+
const court =
89+
!allTime && id < data.pastCourts.length
90+
? addTreeValuesWithDiff(presentCourts[id], pastCourts[id])
91+
: addTreeValues(presentCourts[id]);
92+
const parentIndex = court.parent ? Number(court.parent.id) - 1 : 0;
14393

144-
return { mostDisputedCourt, bestDrawingChancesCourt, bestExpectedRewardCourt, diffCourts };
145-
} else {
146-
return undefined;
94+
if (id === parentIndex) {
95+
processedCourts[id] = court;
96+
return court;
14797
}
148-
}, [usedQuery]);
14998

150-
return courtActivityStats;
99+
processedCourts[id] = {
100+
...court,
101+
treeNumberDisputes: court.treeNumberDisputes + processCourt(parentIndex).treeNumberDisputes,
102+
treeNumberVotes: court.treeNumberVotes + processCourt(parentIndex).treeNumberVotes,
103+
treeVotesPerPnk: court.treeVotesPerPnk + processCourt(parentIndex).treeVotesPerPnk,
104+
treeExpectedRewardPerPnk: court.treeExpectedRewardPerPnk + processCourt(parentIndex).treeExpectedRewardPerPnk,
105+
};
106+
107+
return processedCourts[id];
108+
};
109+
110+
for (const court of presentCourts.toReversed()) {
111+
processCourt(Number(court.id) - 1);
112+
}
113+
114+
processedCourts.reverse();
115+
116+
return {
117+
mostDisputedCourt: getCourtMostDisputes(processedCourts),
118+
bestDrawingChancesCourt: getCourtBestDrawingChances(processedCourts),
119+
bestExpectedRewardCourt: getBestExpectedRewardCourt(processedCourts),
120+
courts: processedCourts,
121+
};
151122
};
123+
124+
const addTreeValues = (court: Court): CourtWithTree => {
125+
const votesPerPnk = Number(court.numberVotes) / (Number(court.effectiveStake) / 1e18);
126+
const expectedRewardPerPnk = votesPerPnk * (Number(court.feeForJuror) / 1e18);
127+
return {
128+
...court,
129+
numberDisputes: Number(court.numberDisputes),
130+
numberVotes: Number(court.numberVotes),
131+
feeForJuror: BigInt(court.feeForJuror) / BigInt(1e18),
132+
effectiveStake: BigInt(court.effectiveStake),
133+
treeNumberDisputes: Number(court.numberDisputes),
134+
treeNumberVotes: Number(court.numberVotes),
135+
votesPerPnk,
136+
treeVotesPerPnk: votesPerPnk,
137+
expectedRewardPerPnk,
138+
treeExpectedRewardPerPnk: expectedRewardPerPnk,
139+
};
140+
};
141+
142+
const addTreeValuesWithDiff = (presentCourt: Court, pastCourt: Court): CourtWithTree => {
143+
const presentCourtWithTree = addTreeValues(presentCourt);
144+
const pastCourtWithTree = addTreeValues(pastCourt);
145+
const diffNumberVotes = presentCourtWithTree.numberVotes - pastCourtWithTree.numberVotes;
146+
const avgEffectiveStake = (presentCourtWithTree.effectiveStake + pastCourtWithTree.effectiveStake) / 2n;
147+
const votesPerPnk = diffNumberVotes / Number(avgEffectiveStake);
148+
const expectedRewardPerPnk = votesPerPnk * Number(presentCourt.feeForJuror);
149+
return {
150+
...presentCourt,
151+
numberDisputes: presentCourtWithTree.numberDisputes - pastCourtWithTree.numberDisputes,
152+
treeNumberDisputes: presentCourtWithTree.treeNumberDisputes - pastCourtWithTree.treeNumberDisputes,
153+
numberVotes: diffNumberVotes,
154+
treeNumberVotes: presentCourtWithTree.treeNumberVotes - pastCourtWithTree.treeNumberVotes,
155+
effectiveStake: avgEffectiveStake,
156+
votesPerPnk,
157+
treeVotesPerPnk: votesPerPnk,
158+
expectedRewardPerPnk,
159+
treeExpectedRewardPerPnk: expectedRewardPerPnk,
160+
};
161+
};
162+
163+
const getCourtMostDisputes = (courts: CourtWithTree[]) =>
164+
courts.toSorted((a: CourtWithTree, b: CourtWithTree) => b.numberDisputes - a.numberDisputes)[0];
165+
const getCourtBestDrawingChances = (courts: CourtWithTree[]) =>
166+
courts.toSorted((a, b) => b.treeVotesPerPnk - a.treeVotesPerPnk)[0];
167+
const getBestExpectedRewardCourt = (courts: CourtWithTree[]) =>
168+
courts.toSorted((a, b) => b.treeExpectedRewardPerPnk - a.treeExpectedRewardPerPnk)[0];

web/src/hooks/queries/useHomePageExtraStats.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { useEffect, useState } from "react";
2-
import { DEFAULT_CHAIN } from "consts/chains";
3-
import { useHomePageBlockQuery } from "./useHomePageBlockQuery";
2+
3+
import { UseQueryResult } from "@tanstack/react-query";
44
import { useBlockNumber } from "wagmi";
5+
56
import { averageBlockTimeInSeconds } from "consts/averageBlockTimeInSeconds";
7+
import { DEFAULT_CHAIN } from "consts/chains";
8+
9+
import { useHomePageBlockQuery, HomePageBlockStats } from "./useHomePageBlockQuery";
10+
11+
type ReturnType = UseQueryResult<HomePageBlockStats, Error>;
612

7-
export const useHomePageExtraStats = (days: number | string) => {
13+
export const useHomePageExtraStats = (days: number | string): ReturnType => {
814
const [pastBlockNumber, setPastBlockNumber] = useState<number>();
915
const currentBlockNumber = useBlockNumber({ chainId: DEFAULT_CHAIN });
1016

@@ -13,7 +19,7 @@ export const useHomePageExtraStats = (days: number | string) => {
1319
const timeInBlocks = Math.floor((days * 24 * 3600) / averageBlockTimeInSeconds[DEFAULT_CHAIN]);
1420
setPastBlockNumber(Number(currentBlockNumber.data) - timeInBlocks);
1521
}
16-
}, [DEFAULT_CHAIN, currentBlockNumber, days]);
22+
}, [currentBlockNumber, days]);
1723

1824
const data = useHomePageBlockQuery(pastBlockNumber, days === "allTime");
1925

web/src/pages/Home/CourtOverview/ExtraStats.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React, { useState } from "react";
22
import styled from "styled-components";
33

4+
import { DropdownSelect } from "@kleros/ui-components-library";
5+
46
import LawBalance from "svgs/icons/law-balance.svg";
57
import LongArrowUp from "svgs/icons/long-arrow-up.svg";
68

79
import { useHomePageExtraStats } from "hooks/queries/useHomePageExtraStats";
10+
811
import ExtraStatsDisplay from "components/ExtraStatsDisplay";
9-
import { DropdownSelect } from "@kleros/ui-components-library";
1012

1113
const StyledCard = styled.div`
1214
display: flex;
@@ -17,24 +19,24 @@ const StyledCard = styled.div`
1719

1820
interface IStat {
1921
title: string;
20-
getText: (data) => string | null;
22+
getText: (data) => string;
2123
icon: React.FC<React.SVGAttributes<SVGElement>>;
2224
}
2325

2426
const stats: IStat[] = [
2527
{
2628
title: "Most Cases",
27-
getText: (data) => data?.mostDisputedCourt?.name,
29+
getText: ({ data }) => data?.mostDisputedCourt?.name,
2830
icon: LongArrowUp,
2931
},
3032
{
3133
title: "Highest drawing chance",
32-
getText: (data) => data?.bestDrawingChancesCourt?.name,
34+
getText: ({ data }) => data?.bestDrawingChancesCourt?.name,
3335
icon: LongArrowUp,
3436
},
3537
{
3638
title: "Highest rewards chance",
37-
getText: (data) => data?.bestExpectedRewardCourt?.name,
39+
getText: ({ data }) => data?.bestExpectedRewardCourt?.name,
3840
icon: LongArrowUp,
3941
},
4042
];
@@ -61,7 +63,7 @@ const ExtraStats = () => {
6163
<StyledCard>
6264
<ExtraStatsDisplay
6365
title="Activity"
64-
text={
66+
content={
6567
<DropdownSelect
6668
smallButton
6769
simpleButton

0 commit comments

Comments
 (0)