Skip to content

Commit b0fa712

Browse files
authored
Merge pull request #233 from kleros/fix/appeal-escrow-v1
fix: escrow v1 appeals
2 parents f2c6662 + c0bed61 commit b0fa712

File tree

3 files changed

+68
-16
lines changed

3 files changed

+68
-16
lines changed

src/app.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,13 @@ class App extends React.Component {
18121812
};
18131813

18141814
appeal = async (arbitrableAddress, arbitrableDisputeID, party, contribution) => {
1815+
//EscrowV1 does not support crowdfunding and so does not have the fundAppeal function, users can only appeal by paying the appeal cost
1816+
if (networkMap[this.state.network].ESCROW_V1_CONTRACTS
1817+
.includes(arbitrableAddress)) {
1818+
1819+
return this.handleEscrowV1Appeal(arbitrableAddress, arbitrableDisputeID, contribution);
1820+
}
1821+
18151822
const contract = await getSignableContract(
18161823
"IDisputeResolver",
18171824
arbitrableAddress,
@@ -1830,6 +1837,25 @@ class App extends React.Component {
18301837
}
18311838
}
18321839

1840+
handleEscrowV1Appeal = async (arbitrableAddress, arbitrableDisputeID, contribution) => {
1841+
const contract = await getSignableContract(
1842+
"MultipleArbitrableTokenTransaction",
1843+
arbitrableAddress,
1844+
this.state.provider
1845+
);
1846+
1847+
try {
1848+
const tx = await contract.appeal(
1849+
arbitrableDisputeID,
1850+
{ value: ethers.parseEther(contribution) }
1851+
);
1852+
return tx.wait();
1853+
} catch (err) {
1854+
console.error("EscrowV1 appeal failed:", err);
1855+
return null;
1856+
}
1857+
}
1858+
18331859
withdrawFeesAndRewardsForAllRounds = async (
18341860
arbitrableAddress,
18351861
arbitrableDisputeID,

src/components/disputeDetails.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import DisputeTimeline from "components/disputeTimeline";
99
import EvidenceTimeline from "components/evidenceTimeline";
1010
import CrowdfundingCard from "components/crowdfundingCard";
1111
import { ethers } from "ethers";
12+
import networkMap from "../ethereum/network-contract-mapping";
1213

1314
import AlertMessage from "components/alertMessage";
1415

@@ -66,6 +67,11 @@ class DisputeDetails extends React.Component {
6667

6768
const { currentRuling, appealCost, multipliers, exceptionalContractAddresses, arbitrated } = this.props;
6869

70+
//Means it is an EscrowV1 dispute, for which there are no calculations needed
71+
if (networkMap[this.props.network].ESCROW_V1_CONTRACTS.includes(arbitrated)) {
72+
return appealCost;
73+
}
74+
6975
let stake;
7076

7177
if (currentRuling == rulingOption || (exceptionalContractAddresses.includes(arbitrated) && currentRuling == 0)) {
@@ -373,6 +379,21 @@ class DisputeDetails extends React.Component {
373379
</Card.Body>
374380
);
375381

382+
renderEscrowV1AppealSection = (disputePeriod, appealCallback, appealCost) => (
383+
<Card.Body>
384+
<div className="h1">{disputePeriod == DISPUTE_PERIOD_APPEAL ? "Appeal the decision" : "Appeal period ended"}</div>
385+
{disputePeriod == DISPUTE_PERIOD_APPEAL && <p className="label">In order to appeal the decision, you need to pay the appeal cost.</p>}
386+
{disputePeriod == DISPUTE_PERIOD_APPEAL && (
387+
<Button
388+
onClick={() =>
389+
appealCallback(0, ethers.formatEther(appealCost))
390+
}>
391+
Appeal - {ethers.formatEther(appealCost)} ETH
392+
</Button>
393+
)}
394+
</Card.Body>
395+
);
396+
376397
// Helper method to render question section
377398
renderQuestionSection = (metaevidenceJSON, arbitratorDisputeID) => (
378399
<Card.Body className={styles.question}>
@@ -444,7 +465,7 @@ class DisputeDetails extends React.Component {
444465
};
445466

446467
// Helper method to render appeal card conditionally
447-
renderAppealCard = (arbitratorDispute, disputePeriod, contributions, multipliers, appealCost, appealPeriod, arbitrated, totalWithdrawable, metaevidenceJSON, currentRuling, appealCallback, exceptionalContractAddresses, activeKey) => {
468+
renderAppealCard = (arbitratorDispute, disputePeriod, contributions, multipliers, appealCost, appealPeriod, arbitrated, totalWithdrawable, metaevidenceJSON, currentRuling, appealCallback, exceptionalContractAddresses, activeKey, isEscrowV1Dispute) => {
448469
if (!arbitratorDispute || disputePeriod < DISPUTE_PERIOD_APPEAL || !contributions || !multipliers || !appealCost || !appealPeriod || !arbitrated) {
449470
return null;
450471
}
@@ -455,7 +476,9 @@ class DisputeDetails extends React.Component {
455476
Appeal
456477
</Accordion.Toggle>
457478
<Accordion.Collapse eventKey="1">
458-
{this.renderAppealSection(disputePeriod, totalWithdrawable, metaevidenceJSON, currentRuling, contributions, appealCallback, exceptionalContractAddresses, arbitrated)}
479+
{isEscrowV1Dispute ?
480+
this.renderEscrowV1AppealSection(disputePeriod, appealCallback, appealCost)
481+
: this.renderAppealSection(disputePeriod, totalWithdrawable, metaevidenceJSON, currentRuling, contributions, appealCallback, exceptionalContractAddresses, arbitrated)}
459482
</Accordion.Collapse>
460483
</Card>
461484
);
@@ -557,7 +580,7 @@ class DisputeDetails extends React.Component {
557580
// Try to use Reality.eth parsed outcomes if available
558581
const realityOutcomes = this.findRealityOutcomes(metaevidenceJSON);
559582
const fallbackTitles = realityOutcomes || ["Yes", "No"]; // Basic fallback for Reality.eth questions
560-
583+
561584
fallbackTitles.forEach((title, index) => {
562585
cards.push(
563586
<Col key={`fallback-${index + 1}`} className="pb-4" xl={8} lg={12} xs={24}>
@@ -672,6 +695,7 @@ class DisputeDetails extends React.Component {
672695
render() {
673696
const {
674697
arbitrated,
698+
network,
675699
metaevidenceJSON,
676700
evidences,
677701
arbitratorDisputeID,
@@ -702,6 +726,7 @@ class DisputeDetails extends React.Component {
702726
}
703727

704728
const disputePeriod = parseInt(arbitratorDispute.period, 10);
729+
const isEscrowV1Dispute = networkMap[network].ESCROW_V1_CONTRACTS.includes(arbitrated);
705730

706731
return (
707732
<section className={styles.disputeDetails}>
@@ -720,7 +745,7 @@ class DisputeDetails extends React.Component {
720745
className={`mt-4 ${styles.accordion}`}
721746
onSelect={this.handleAccordionSelect}
722747
>
723-
{this.renderAppealCard(arbitratorDispute, disputePeriod, contributions, multipliers, appealCost, appealPeriod, arbitrated, totalWithdrawable, metaevidenceJSON, currentRuling, appealCallback, exceptionalContractAddresses, activeKey)}
748+
{this.renderAppealCard(arbitratorDispute, disputePeriod, contributions, multipliers, appealCost, appealPeriod, arbitrated, totalWithdrawable, metaevidenceJSON, currentRuling, appealCallback, exceptionalContractAddresses, activeKey, isEscrowV1Dispute)}
724749

725750
<Card>
726751
<Accordion.Toggle className={activeKey == 2 ? "open" : "closed"} as={Card.Header} eventKey="2">

src/containers/interact.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,11 @@ class Interact extends React.Component {
210210
this.props.getMultipliersCallback(arbitrated).catch(err => {
211211
console.warn('getMultipliersCallback failed:', err.message);
212212
return {
213-
winner: "10000", // 1.0 multiplier in basis points
214-
loser: "10000",
215-
shared: "5000",
216-
divisor: "10000"
217-
};
213+
winnerStakeMultiplier: 10000n,
214+
loserStakeMultiplier: 10000n,
215+
loserAppealPeriodMultiplier: 10000n,
216+
denominator: 10000n
217+
}
218218
})
219219
]);
220220

@@ -412,20 +412,20 @@ class Interact extends React.Component {
412412
renderIncompatibleWarning = () => {
413413
const { metaevidence } = this.state;
414414
const isGenericMetaEvidence = metaevidence?.metaEvidenceJSON?.category === "Non-Standard Contract";
415-
415+
416416
return (
417-
<div style={{
418-
padding: "1rem 2rem",
419-
fontSize: "14px",
417+
<div style={{
418+
padding: "1rem 2rem",
419+
fontSize: "14px",
420420
background: isGenericMetaEvidence ? "#fff3cd" : "#fafafa",
421421
border: isGenericMetaEvidence ? "1px solid #ffeaa7" : "none",
422422
borderRadius: "4px",
423423
marginBottom: "1rem"
424424
}}>
425425
{isGenericMetaEvidence ? (
426426
<>
427-
<b>⚠️ Non-Standard Contract:</b> This arbitrable contract doesn't follow standard Kleros patterns.
428-
The dispute information shown is generic. Limited functionality is available - you may not be able to submit evidence
427+
<b>⚠️ Non-Standard Contract:</b> This arbitrable contract doesn't follow standard Kleros patterns.
428+
The dispute information shown is generic. Limited functionality is available - you may not be able to submit evidence
429429
or fund appeals through this interface.
430430
<br />
431431
<small style={{ color: "#856404", marginTop: "0.5rem", display: "block" }}>
@@ -434,7 +434,7 @@ class Interact extends React.Component {
434434
</>
435435
) : (
436436
<>
437-
<b>View mode only:</b> the arbitrable contract of this dispute is not compatible with the interface of Dispute Resolver.
437+
<b>View mode only:</b> the arbitrable contract of this dispute is not compatible with the interface of Dispute Resolver.
438438
You can't submit evidence or fund appeal on this interface. You can do these on the arbitrable application, if implemented.
439439
</>
440440
)}
@@ -558,6 +558,7 @@ class Interact extends React.Component {
558558
/>
559559
<DisputeDetails
560560
activeAddress={activeAddress}
561+
network={network}
561562
metaevidenceJSON={metaevidence?.metaEvidenceJSON}
562563
evidences={evidences}
563564
ipfsGateway="https://cdn.kleros.link"

0 commit comments

Comments
 (0)