Skip to content

Commit

Permalink
chore: implement appeal module
Browse files Browse the repository at this point in the history
  • Loading branch information
epiqueras committed Sep 21, 2020
1 parent a497b47 commit 9ed7a27
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 23 deletions.
8 changes: 6 additions & 2 deletions _pages/profile/[id]/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ export default function ProfileWithID() {
submission={props.submission}
contract={props.contracts[0]}
/>
<SubmissionDetailsAccordion submission={props.submission} />
<SubmissionDetailsAccordion
submission={props.submission}
contract={props.contracts[0]}
/>
</>
)}
</>
Expand All @@ -68,12 +71,13 @@ export const IdQuery = graphql`
contracts(first: 1) {
...submitProfileCard
...submissionDetailsCardContract
...submissionDetailsAccordionContract
}
submission(id: $id) {
status
registered
...submissionDetailsCardSubmission
...submissionDetailsAccordion
...submissionDetailsAccordionSubmission
}
}
`;
76 changes: 61 additions & 15 deletions _pages/profile/[id]/submission-details-accordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,47 @@ import {
AccordionItem,
AccordionItemHeading,
AccordionItemPanel,
Appeal,
Evidence,
} from "@kleros/components";
import { graphql, useFragment } from "relay-hooks";

import { useEvidenceFile } from "data";
import { challengeReasonEnum, useEvidenceFile } from "data";

const submissionDetailsAccordionFragment = graphql`
fragment submissionDetailsAccordion on Submission {
id
request: requests(orderBy: creationTime, orderDirection: desc, first: 1) {
evidence(orderBy: creationTime, orderDirection: desc) {
id
URI
sender
const submissionDetailsAccordionFragments = {
contract: graphql`
fragment submissionDetailsAccordionContract on Contract {
sharedStakeMultiplier
winnerStakeMultiplier
loserStakeMultiplier
}
`,
submission: graphql`
fragment submissionDetailsAccordionSubmission on Submission {
id
request: requests(orderBy: creationTime, orderDirection: desc, first: 1) {
requester
arbitrator
arbitratorExtraData
evidence(orderBy: creationTime, orderDirection: desc) {
id
URI
sender
}
challenges(orderBy: creationTime) {
id
reason
disputeID
challenger
rounds(orderBy: creationTime, orderDirection: desc, first: 1) {
paidFees
hasPaid
}
}
}
}
}
`;
`,
};
function SubmissionDetailsAccordionItem({ heading, panel }) {
return (
<AccordionItem>
Expand All @@ -29,11 +52,18 @@ function SubmissionDetailsAccordionItem({ heading, panel }) {
</AccordionItem>
);
}
export default function SubmissionDetailsAccordion({ submission }) {
export default function SubmissionDetailsAccordion({ submission, contract }) {
const {
id,
request: [{ evidence }],
} = useFragment(submissionDetailsAccordionFragment, submission);
request: [
{ evidence, challenges, requester, arbitrator, arbitratorExtraData },
],
} = useFragment(submissionDetailsAccordionFragments.submission, submission);
const {
sharedStakeMultiplier,
winnerStakeMultiplier,
loserStakeMultiplier,
} = useFragment(submissionDetailsAccordionFragments.contract, contract);
return (
<Accordion>
<SubmissionDetailsAccordionItem
Expand All @@ -51,7 +81,23 @@ export default function SubmissionDetailsAccordion({ submission }) {
heading="Voting History"
panel="Voting History"
/>
<SubmissionDetailsAccordionItem heading="Appeal" panel="Appeal" />
<SubmissionDetailsAccordionItem
heading="Appeal"
panel={
<Appeal
challenges={challenges.map((challenge) => ({
...challenge,
reason: challengeReasonEnum.parse(challenge.reason),
parties: [requester, challenge.challenger],
}))}
sharedStakeMultiplier={sharedStakeMultiplier}
winnerStakeMultiplier={winnerStakeMultiplier}
loserStakeMultiplier={loserStakeMultiplier}
arbitrator={arbitrator}
arbitratorExtraData={arbitratorExtraData}
/>
}
/>
</Accordion>
);
}
186 changes: 186 additions & 0 deletions components/appeal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { useMemo } from "react";

import Card from "./card";
import Grid from "./grid";
import { NextETHLink } from "./next-router";
import Progress from "./progress";
import Tabs, { Tab, TabList, TabPanel } from "./tabs";
import Text from "./text";
import TimeAgo from "./time-ago";
import { useContract, useWeb3 } from "./web3-provider";

function AppealTabPanelCard({
address,
label,
cost,
paidFees,
hasPaid,
deadline,
reward,
}) {
const { web3 } = useWeb3();
return (
<Card mainSx={{ alignItems: "flex-start", flexDirection: "column" }}>
<NextETHLink address={address}>{address}</NextETHLink>
<Text sx={{ marginBottom: 3 }}>
{label && `Previous round ${label}.`}
</Text>
<Text
sx={{
fontWeight: "bold",
marginBottom: 1,
textAlign: "center",
width: "100%",
}}
>
{cost && `Total Deposit Required: ${web3.utils.fromWei(cost)}`}
</Text>
<Progress
sx={{ marginBottom: 1 }}
value={paidFees}
max={cost?.toString()}
/>
<Text sx={{ marginBottom: 3 }}>
{hasPaid
? "Fully funded."
: deadline &&
(deadline.eq(web3.utils.toBN(0)) ? (
"Previous round is still in progress."
) : (
<TimeAgo datetime={deadline * 1000} />
))}
</Text>
<Text sx={{ fontWeight: "bold" }}>{reward && `Reward ${reward}%`}</Text>
</Card>
);
}
function AppealTabPanel({
sharedStakeMultiplier,
winnerStakeMultiplier,
loserStakeMultiplier,
arbitrator,
challenge: {
disputeID,
parties: [party1, party2],
rounds: [{ paidFees, hasPaid }],
},
arbitratorExtraData,
}) {
const { web3 } = useWeb3();
sharedStakeMultiplier = web3.utils.toBN(sharedStakeMultiplier);
winnerStakeMultiplier = web3.utils.toBN(winnerStakeMultiplier);
loserStakeMultiplier = web3.utils.toBN(loserStakeMultiplier);
const divisor = web3.utils.toBN(10000);
const hundred = web3.utils.toBN(100);
const undecided = {
label: "undecided",
reward: sharedStakeMultiplier.div(sharedStakeMultiplier).mul(hundred),
};
const winner = {
label: "winner",
reward: loserStakeMultiplier.div(winnerStakeMultiplier).mul(hundred),
};
const loser = {
label: "loser",
reward: winnerStakeMultiplier.div(loserStakeMultiplier).mul(hundred),
};

const [appealCost] = useContract(
"klerosLiquid",
"appealCost",
useMemo(
() => ({ address: arbitrator, args: [disputeID, arbitratorExtraData] }),
[arbitrator, disputeID, arbitratorExtraData]
)
);
if (appealCost) {
undecided.cost = appealCost.add(
appealCost.mul(sharedStakeMultiplier).div(divisor)
);
winner.cost = appealCost.add(
appealCost.mul(winnerStakeMultiplier).div(divisor)
);
loser.cost = appealCost.add(
appealCost.mul(loserStakeMultiplier).div(divisor)
);
}

const [appealPeriod] = useContract(
"klerosLiquid",
"appealPeriod",
useMemo(() => ({ address: arbitrator, args: [disputeID] }), [
arbitrator,
disputeID,
])
);
if (appealPeriod) {
undecided.deadline = appealPeriod.end;
winner.deadline = appealPeriod.end;
loser.deadline = appealPeriod.start.add(
appealPeriod.end.sub(appealPeriod.start).div(web3.utils.toBN(2))
);
}

let [currentRuling] = useContract(
"klerosLiquid",
"currentRuling",
useMemo(() => ({ address: arbitrator, args: [disputeID] }), [
arbitrator,
disputeID,
])
);
currentRuling = currentRuling?.toNumber();
return (
<>
<Text sx={{ marginBottom: 2 }}>
Help fund a side’s appeal fees to win part of the other side’s deposit
when your side ultimately wins. If only one side manages to fund their
fees, it automatically wins.
</Text>
<Grid gap={2} columns={2}>
<AppealTabPanelCard
address={party1}
{...[undecided, winner, loser][currentRuling]}
paidFees={paidFees[0]}
hasPaid={hasPaid[0]}
/>
<AppealTabPanelCard
address={party2}
{...[undecided, loser, winner][currentRuling]}
paidFees={paidFees[1]}
hasPaid={hasPaid[1]}
/>
</Grid>
</>
);
}
export default function Appeal({
challenges,
sharedStakeMultiplier,
winnerStakeMultiplier,
loserStakeMultiplier,
arbitrator,
arbitratorExtraData,
}) {
return (
<Tabs>
<TabList>
{challenges.map(({ id, reason: { startCase } }) => (
<Tab key={id}>{startCase}</Tab>
))}
</TabList>
{challenges.map((challenge) => (
<TabPanel key={challenge.id}>
<AppealTabPanel
sharedStakeMultiplier={sharedStakeMultiplier}
winnerStakeMultiplier={winnerStakeMultiplier}
loserStakeMultiplier={loserStakeMultiplier}
arbitrator={arbitrator}
challenge={challenge}
arbitratorExtraData={arbitratorExtraData}
/>
</TabPanel>
))}
</Tabs>
);
}
1 change: 1 addition & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
AccordionItemPanel,
} from "./accordion";
export { default as AccountSettingsPopup } from "./account-settings-popup";
export { default as Appeal } from "./appeal";
export {
default as ArchonProvider,
useArchon,
Expand Down
2 changes: 1 addition & 1 deletion components/next-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const createWrapConnection = (queries, queryEnums) => {
acc[key] =
typeof value === "boolean" ||
Number.isNaN(Number(value)) ||
value?.startsWith("0x")
value.startsWith("0x")
? value
: Number(value);
return acc;
Expand Down
5 changes: 5 additions & 0 deletions components/progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Progress as _Progress } from "theme-ui";

export default function Progress(props) {
return <_Progress {...props} />;
}
17 changes: 12 additions & 5 deletions components/web3-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ const sendStateReducer = (
return { ...state, error };
}
};
const parseRes = (value, web3) =>
typeof value === "boolean" ||
Number.isNaN(Number(value)) ||
value.startsWith("0x")
? value
: web3.utils.toBN(value);
export function useContract(
contract,
method,
Expand Down Expand Up @@ -249,11 +255,12 @@ export function useContract(
type &&
!isSend &&
run().then?.((res) =>
typeof res === "boolean" ||
Number.isNaN(Number(res)) ||
res?.startsWith("0x")
? res
: web3.utils.toBN(res)
typeof res === "object"
? Object.keys(res).reduce((acc, key) => {
acc[key] = parseRes(res[key], web3);
return acc;
}, {})
: parseRes(res, web3)
),
[reCallRef, type, isSend, run, web3]
);
Expand Down
4 changes: 4 additions & 0 deletions subgraph/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ type Challenge @entity {
"""
id: ID!
"""
The challenge's reason.
"""
reason: Reason
"""
The challenge's dispute ID.
"""
disputeID: BigInt
Expand Down
1 change: 1 addition & 0 deletions subgraph/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ export function challengeRequest(call: ChallengeRequestCall): void {
requestIndex,
challengeIndex
);
challenge.reason = callInputsReason;
challenge.disputeID = challengeInfo.value1;
challenge.challenger = call.from;
if (callInputsReason == "Duplicate") {
Expand Down

0 comments on commit 9ed7a27

Please sign in to comment.