Skip to content

Commit 9ba40f8

Browse files
authored
Merge pull request #231 from kleros/fix/evidence-submission-escrow-v1
fix: Escrow V1 evidence submission + appeals
2 parents e76c5d8 + b0fa712 commit 9ba40f8

File tree

5 files changed

+113
-17
lines changed

5 files changed

+113
-17
lines changed

src/app.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ class App extends React.Component {
294294
network: this.state.network
295295
});
296296

297+
//Means it is an EscrowV1 dispute
298+
if (networkMap[this.state.network].ESCROW_V1_CONTRACTS.includes(arbitrableAddress)) {
299+
return this.getArbitrableDisputeIDFromEscrowV1(arbitrableAddress, arbitratorDisputeID);
300+
}
301+
297302
const contract = getContract(
298303
"IDisputeResolver",
299304
arbitrableAddress,
@@ -331,6 +336,26 @@ class App extends React.Component {
331336
}
332337
}
333338

339+
//For EscrowV1 the externalIDtoLocalID function is not available, but the mapping disputeIDtoTransactionID is.
340+
getArbitrableDisputeIDFromEscrowV1 = async (arbitrableAddress, arbitratorDisputeID) => {
341+
try {
342+
//Note that EscrowV1 uses MultipleArbitrableTransaction and MultipleArbitrableTokenTransaction contracts.
343+
//However, we can always use the same ABI here because the function is the same and available in both.
344+
const contract = getContract(
345+
"MultipleArbitrableTokenTransaction",
346+
arbitrableAddress,
347+
this.state.provider
348+
);
349+
350+
const result = await contract.disputeIDtoTransactionID.staticCall(arbitratorDisputeID);
351+
console.debug(`✅ [getArbitrableDisputeID] EscrowV1 success:`, result.toString());
352+
return result;
353+
} catch (error) {
354+
console.error(`❌ [getArbitrableDisputeID] EscrowV1 error:`, error);
355+
return null;
356+
}
357+
}
358+
334359
findArbitrableFromArbitrator = async (arbitratorDisputeID) => {
335360
const { network } = this.state;
336361
const arbitratorAddress = networkMap[network]?.KLEROS_LIQUID;
@@ -1787,6 +1812,13 @@ class App extends React.Component {
17871812
};
17881813

17891814
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+
17901822
const contract = await getSignableContract(
17911823
"IDisputeResolver",
17921824
arbitrableAddress,
@@ -1805,6 +1837,25 @@ class App extends React.Component {
18051837
}
18061838
}
18071839

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+
18081859
withdrawFeesAndRewardsForAllRounds = async (
18091860
arbitrableAddress,
18101861
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"

src/ethereum/interface.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import PolicyRegistry from "../../node_modules/@kleros/kleros/build/contracts/Po
66
import ArbitrableProxy from "../../node_modules/@kleros/arbitrable-proxy-contracts/build/contracts/ArbitrableProxy.json";
77
import IDisputeResolver_v2_0_0 from "../../node_modules/IDRv2/build/contracts/IDisputeResolver.json";
88
import IDisputeResolver_v1_0_2 from "../../node_modules/IDRv1/build/contracts/IDisputeResolver.json";
9+
import MultipleArbitrableTokenTransaction from "../../node_modules/@kleros/kleros-interaction/build/contracts/MultipleArbitrableTokenTransaction.json";
910

1011
import { ethers } from "ethers";
1112

@@ -18,6 +19,7 @@ const contractABIs = {
1819
IArbitrable,
1920
IEvidence,
2021
PolicyRegistry,
22+
MultipleArbitrableTokenTransaction,
2123
};
2224

2325
export const getContract = (contractName, address, provider) => {

src/ethereum/network-contract-mapping.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ const arbitratorDeployedAtBlock = {
2222
1: 7303699,
2323
5: 5893941,
2424
11155111: 3635742,
25-
2625
}
2726

27+
const ESCROW_V1_ETH_MAINNET_ADDRESSES = ["0xE2Dd8CCe2c33a04215074ADb4B5820B765d8Ed9D", "0x0d67440946949fe293b45c52efd8a9b3d51e2522", "0xC25a0b9681ABF6F090AEd71a8c08fB564b41dab6", "0xBCf0d1AD453728F75e9cFD4358ED187598A45e6c"]
28+
const ESCROW_V1_ETH_SEPOLIA_ADDRESSES = ["0x338f1A474e0FB0ae9E913cFA3d7c6Aa19b92015B", "0x9262c1c7810571B189db83F945e7e8b67abcE1c8", "0x58fc7e398B4a1886695ab2C7fE7c31F49393a8c5", "0x6048002b6E93A4A5d93E902F2427D7472790aC97"]
29+
2830
const map = {
2931
1: {
3032
NAME: "Ethereum Mainnet",
3133
KLEROS_LIQUID: arbitrators["1"],
3234
ARBITRABLE_PROXY: ArbitrableProxy.networks[1]?.address,
35+
ESCROW_V1_CONTRACTS: ESCROW_V1_ETH_MAINNET_ADDRESSES,
3336
POLICY_REGISTRY: "0xCf1f07713d5193FaE5c1653C9f61953D048BECe4",
3437
WEB3_PROVIDER: process.env.REACT_APP_WEB3_PROVIDER_URL,
3538
CURRENCY_SHORT: "ETH",
@@ -39,6 +42,7 @@ const map = {
3942
NAME: "Ethereum Testnet Görli",
4043
KLEROS_LIQUID: arbitrators["5"],
4144
ARBITRABLE_PROXY: "0x78ac5F189FC6DAB261437a7B95D11cAcf0234FFe",
45+
ESCROW_V1_CONTRACTS: [],
4246
POLICY_REGISTRY: "0x28c8A3A2E3c8Cd3F795DB83764316a1129a069bA",
4347
WEB3_PROVIDER: process.env.REACT_APP_WEB3_GOERLI_PROVIDER_URL,
4448
CURRENCY_SHORT: "ETH",
@@ -48,6 +52,7 @@ const map = {
4852
NAME: "Gnosis Network",
4953
KLEROS_LIQUID: arbitrators["100"],
5054
ARBITRABLE_PROXY: ArbitrableProxy.networks[100].address,
55+
ESCROW_V1_CONTRACTS: [],
5156
POLICY_REGISTRY: "0x9d494768936b6bDaabc46733b8D53A937A6c6D7e",
5257
WEB3_PROVIDER: process.env.REACT_APP_WEB3_XDAI_PROVIDER_URL,
5358
CURRENCY_SHORT: "xDai",
@@ -58,6 +63,7 @@ const map = {
5863
FOREIGN_KLEROS_LIQUID: arbitrators["1"],
5964
FOREIGN_ARBITRATOR_NETWORK_CODE: "1",
6065
ARBITRABLE_PROXY: null,
66+
ESCROW_V1_CONTRACTS: [],
6167
POLICY_REGISTRY: policyRegistries["1"],
6268
WEB3_PROVIDER: process.env.REACT_APP_UNICHAIN,
6369
CURRENCY_SHORT: "ETH",
@@ -68,6 +74,7 @@ const map = {
6874
FOREIGN_KLEROS_LIQUID: arbitrators["11155111"],
6975
FOREIGN_ARBITRATOR_NETWORK_CODE: "11155111",
7076
ARBITRABLE_PROXY: null,
77+
ESCROW_V1_CONTRACTS: [],
7178
POLICY_REGISTRY: policyRegistries["11155111"],
7279
WEB3_PROVIDER: process.env.REACT_APP_UNICHAIN_SEPOLIA,
7380
CURRENCY_SHORT: "ETH",
@@ -79,13 +86,15 @@ const map = {
7986
CURRENCY_SHORT: "MATIC",
8087
FOREIGN_KLEROS_LIQUID: arbitrators["1"],
8188
FOREIGN_ARBITRATOR_NETWORK_CODE: "1",
89+
ESCROW_V1_CONTRACTS: [],
8290
QUERY_FROM_BLOCK: arbitratorDeployedAtBlock["1"],
8391
},
8492
300: {
8593
NAME: "zkSync Era Testnet Sepolia",
8694
FOREIGN_KLEROS_LIQUID: arbitrators["11155111"],
8795
FOREIGN_ARBITRATOR_NETWORK_CODE: "11155111",
8896
ARBITRABLE_PROXY: null,
97+
ESCROW_V1_CONTRACTS: [],
8998
POLICY_REGISTRY: policyRegistries["11155111"],
9099
WEB3_PROVIDER: "https://sepolia.era.zksync.dev/",
91100
CURRENCY_SHORT: "sETH",
@@ -96,6 +105,7 @@ const map = {
96105
FOREIGN_KLEROS_LIQUID: arbitrators["1"],
97106
FOREIGN_ARBITRATOR_NETWORK_CODE: "1",
98107
ARBITRABLE_PROXY: null,
108+
ESCROW_V1_CONTRACTS: [],
99109
POLICY_REGISTRY: policyRegistries["1"],
100110
WEB3_PROVIDER: "https://mainnet.era.zksync.io",
101111
CURRENCY_SHORT: "ETH",
@@ -106,6 +116,7 @@ const map = {
106116
FOREIGN_KLEROS_LIQUID: arbitrators["1"],
107117
FOREIGN_ARBITRATOR_NETWORK_CODE: "1",
108118
ARBITRABLE_PROXY: null,
119+
ESCROW_V1_CONTRACTS: [],
109120
POLICY_REGISTRY: policyRegistries["1"],
110121
WEB3_PROVIDER: process.env.REACT_APP_REDSTONE,
111122
CURRENCY_SHORT: "ETH",
@@ -116,6 +127,7 @@ const map = {
116127
FOREIGN_KLEROS_LIQUID: arbitrators["1"],
117128
FOREIGN_ARBITRATOR_NETWORK_CODE: "1",
118129
ARBITRABLE_PROXY: null,
130+
ESCROW_V1_CONTRACTS: [],
119131
POLICY_REGISTRY: policyRegistries["1"],
120132
WEB3_PROVIDER: process.env.REACT_APP_ARBITRUM_ONE,
121133
CURRENCY_SHORT: "ETH",
@@ -126,6 +138,7 @@ const map = {
126138
FOREIGN_KLEROS_LIQUID: arbitrators["11155111"],
127139
FOREIGN_ARBITRATOR_NETWORK_CODE: "11155111",
128140
ARBITRABLE_PROXY: null,
141+
ESCROW_V1_CONTRACTS: [],
129142
POLICY_REGISTRY: policyRegistries["11155111"],
130143
WEB3_PROVIDER: process.env.REACT_APP_ARBITRUM_SEPOLIA,
131144
CURRENCY_SHORT: "ETH",
@@ -138,6 +151,7 @@ const map = {
138151
CURRENCY_SHORT: "MATIC",
139152
FOREIGN_KLEROS_LIQUID: arbitrators["5"],
140153
FOREIGN_ARBITRATOR_NETWORK_CODE: "5",
154+
ESCROW_V1_CONTRACTS: [],
141155
QUERY_FROM_BLOCK: arbitratorDeployedAtBlock["5"],
142156
},
143157
10200: {
@@ -147,12 +161,14 @@ const map = {
147161
POLICY_REGISTRY: "0x53FC70FE1EC3a60f8939A62aBCc61bf1A57938D7",
148162
WEB3_PROVIDER: process.env.REACT_APP_WEB3_CHIADO_PROVIDER_URL,
149163
CURRENCY_SHORT: "xDai",
164+
ESCROW_V1_CONTRACTS: [],
150165
QUERY_FROM_BLOCK: 1165867,
151166
},
152167
11155111: {
153168
NAME: "Ethereum Testnet Sepolia",
154169
KLEROS_LIQUID: arbitrators["11155111"],
155170
ARBITRABLE_PROXY: "0x009cA5A0B816156F91B29A93d7688c52480BaB24",
171+
ESCROW_V1_CONTRACTS: ESCROW_V1_ETH_SEPOLIA_ADDRESSES,
156172
POLICY_REGISTRY: policyRegistries["11155111"],
157173
WEB3_PROVIDER: process.env.REACT_APP_WEB3_SEPOLIA_PROVIDER_URL,
158174
CURRENCY_SHORT: "sETH",
@@ -163,6 +179,7 @@ const map = {
163179
FOREIGN_KLEROS_LIQUID: arbitrators["11155111"],
164180
FOREIGN_ARBITRATOR_NETWORK_CODE: "11155111",
165181
ARBITRABLE_PROXY: null,
182+
ESCROW_V1_CONTRACTS: [],
166183
POLICY_REGISTRY: policyRegistries["11155111"],
167184
WEB3_PROVIDER: process.env.REACT_APP_OPTIMISM_SEPOLIA,
168185
CURRENCY_SHORT: "ETH",

0 commit comments

Comments
 (0)