Skip to content

Commit f5c1d67

Browse files
committed
feat: gated dispute kit extra data input, extra data encoding refactor
1 parent 1ff6beb commit f5c1d67

File tree

5 files changed

+190
-25
lines changed

5 files changed

+190
-25
lines changed

web/src/context/NewDisputeContext.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ interface IDisputeData extends IDisputeTemplate {
5151
arbitrationCost?: string;
5252
aliasesArray?: AliasArray[];
5353
disputeKitId?: number;
54+
disputeKitData?: IDisputeKitData;
55+
}
56+
57+
export type IDisputeKitData = IGatedDisputeData | ISomeFutureDisputeData;
58+
59+
export interface IGatedDisputeData {
60+
type: "gated";
61+
isERC1155: boolean;
62+
tokenGate: string;
63+
tokenId: string;
64+
}
65+
66+
// Placeholder
67+
export interface ISomeFutureDisputeData {
68+
type: "future";
69+
contract: string;
5470
}
5571

5672
interface INewDisputeContext {

web/src/pages/Resolver/NavigationButtons/SubmitBatchDisputesButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ const SubmitBatchDisputesButton: React.FC = () => {
5252
prepareArbitratorExtradata(
5353
disputeData.courtId ?? "1",
5454
disputeData.numberOfJurors ?? 3,
55-
disputeData.disputeKitId ?? 1
55+
disputeData.disputeKitId ?? 1,
56+
disputeData.disputeKitData
5657
),
5758
JSON.stringify(disputeTemplate),
5859
"",

web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ const SubmitDisputeButton: React.FC = () => {
5555
prepareArbitratorExtradata(
5656
disputeData.courtId ?? "1",
5757
disputeData.numberOfJurors ?? "",
58-
disputeData.disputeKitId ?? 1
58+
disputeData.disputeKitId ?? 1,
59+
disputeData.disputeKitData
5960
),
6061
JSON.stringify(disputeTemplate),
6162
"",

web/src/pages/Resolver/Parameters/Court.tsx

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useMemo } from "react";
1+
import React, { useMemo, useState } from "react";
22
import styled, { css } from "styled-components";
33

4-
import { AlertMessage, DropdownCascader, DropdownSelect } from "@kleros/ui-components-library";
4+
import { AlertMessage, Checkbox, DropdownCascader, DropdownSelect, Field } from "@kleros/ui-components-library";
55

6-
import { useNewDisputeContext } from "context/NewDisputeContext";
6+
import { DisputeKits } from "consts/index";
7+
import { IGatedDisputeData, useNewDisputeContext } from "context/NewDisputeContext";
78
import { rootCourtToItems, useCourtTree } from "hooks/queries/useCourtTree";
89
import { useDisputeKitAddressesAll } from "hooks/useDisputeKitAddresses";
910
import { isUndefined } from "utils/index";
@@ -62,56 +63,149 @@ const StyledDropdownSelect = styled(DropdownSelect)`
6263
)}
6364
`;
6465

66+
const StyledField = styled(Field)`
67+
width: 84vw;
68+
margin-top: 24px;
69+
${landscapeStyle(
70+
() => css`
71+
width: ${responsiveSize(442, 700, 900)};
72+
`
73+
)}
74+
> small {
75+
margin-top: 16px;
76+
}
77+
`;
78+
79+
const StyledCheckbox = styled(Checkbox)`
80+
width: 84vw;
81+
margin-top: 24px;
82+
${landscapeStyle(
83+
() => css`
84+
width: ${responsiveSize(442, 700, 900)};
85+
`
86+
)}
87+
`;
88+
6589
const Court: React.FC = () => {
6690
const { disputeData, setDisputeData } = useNewDisputeContext();
91+
const [isGatedDisputeKit, setIsGatedDisputeKit] = useState(false);
6792
const { data: courtTree } = useCourtTree();
6893
const { data: supportedDisputeKits } = useSupportedDisputeKits(disputeData.courtId);
6994
const items = useMemo(() => !isUndefined(courtTree?.court) && [rootCourtToItems(courtTree.court)], [courtTree]);
7095
const { availableDisputeKits } = useDisputeKitAddressesAll();
96+
7197
const disputeKitOptions = useMemo(() => {
7298
return (
73-
supportedDisputeKits?.court?.supportedDisputeKits.map((dk) => ({
74-
text: availableDisputeKits[dk.address.toLowerCase()] ?? "",
75-
value: dk.id,
76-
})) || []
99+
supportedDisputeKits?.court?.supportedDisputeKits.map((dk) => {
100+
const text = availableDisputeKits[dk.address.toLowerCase()] ?? "";
101+
return {
102+
text,
103+
value: dk.id,
104+
gated: text === DisputeKits.Gated || text === DisputeKits.GatedShutter,
105+
};
106+
}) || []
77107
);
78108
}, [supportedDisputeKits, availableDisputeKits]);
79109

80-
const defaultDisputeKitValue = useMemo(() => {
110+
const selectedDisputeKitId = useMemo(() => {
111+
// If there's only 1 supported dispute kit, select it by default
81112
if (disputeKitOptions.length === 1) {
82-
return disputeKitOptions[0].value; // If there's only 1 supported dispute kit, select it by default
113+
return disputeKitOptions[0].value;
83114
}
84-
return disputeData.disputeKitId ?? 1;
115+
// If there's no saved selection, select nothing
116+
return disputeData.disputeKitId ?? -1;
85117
}, [disputeKitOptions, disputeData.disputeKitId]);
86118

87-
const handleCourtWrite = (courtId: string) => {
88-
setDisputeData({ ...disputeData, courtId, disputeKitId: undefined });
119+
const handleCourtChange = (courtId: string) => {
120+
if (disputeData.courtId !== courtId) {
121+
setDisputeData({ ...disputeData, courtId, disputeKitId: undefined });
122+
}
123+
};
124+
125+
const handleDisputeKitChange = (newValue: string | number) => {
126+
const options = disputeKitOptions.find((dk) => dk.value === String(newValue));
127+
const isNewValueGated = options?.gated ?? false;
128+
const gatedDisputeKitData: IGatedDisputeData | undefined = isNewValueGated
129+
? {
130+
type: "gated",
131+
tokenGate: "",
132+
isERC1155: false,
133+
tokenId: "0",
134+
}
135+
: undefined;
136+
setIsGatedDisputeKit(isNewValueGated);
137+
setDisputeData({ ...disputeData, disputeKitId: Number(newValue), disputeKitData: gatedDisputeKitData });
138+
};
139+
140+
const handleTokenAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => {
141+
const currentData = disputeData.disputeKitData as IGatedDisputeData;
142+
setDisputeData({
143+
...disputeData,
144+
disputeKitData: { ...currentData, tokenGate: event.target.value },
145+
});
89146
};
90147

91-
const handleDisputeKitChange = (newValue: string | number) =>
92-
setDisputeData({ ...disputeData, disputeKitId: Number(newValue) });
148+
const handleERC1155TokenChange = (event: React.ChangeEvent<HTMLInputElement>) => {
149+
const currentData = disputeData.disputeKitData as IGatedDisputeData;
150+
setDisputeData({
151+
...disputeData,
152+
disputeKitData: { ...currentData, isERC1155: event.target.checked },
153+
});
154+
};
155+
156+
const handleTokenIdChange = (event: React.ChangeEvent<HTMLInputElement>) => {
157+
const currentData = disputeData.disputeKitData as IGatedDisputeData;
158+
setDisputeData({
159+
...disputeData,
160+
disputeKitData: { ...currentData, tokenId: event.target.value },
161+
});
162+
};
93163

94164
return (
95165
<Container>
96166
<Header text="Select a court to arbitrate the case" />
97167
{items ? (
98168
<StyledDropdownCascader
99169
items={items}
100-
onSelect={(path: string | number) => typeof path === "string" && handleCourtWrite(path.split("/").pop()!)}
170+
onSelect={(path: string | number) => typeof path === "string" && handleCourtChange(path.split("/").pop()!)}
101171
placeholder="Select Court"
102172
value={`/courts/${disputeData.courtId}`}
103173
/>
104174
) : (
105175
<StyledSkeleton width={240} height={42} />
106176
)}
107-
{disputeData?.courtId && (
177+
{disputeData?.courtId && disputeKitOptions.length > 0 && (
108178
<StyledDropdownSelect
109179
items={disputeKitOptions}
110180
placeholder={{ text: "Select Dispute Kit" }}
111-
defaultValue={defaultDisputeKitValue}
181+
defaultValue={selectedDisputeKitId}
112182
callback={handleDisputeKitChange}
113183
/>
114184
)}
185+
{isGatedDisputeKit && (
186+
<>
187+
<StyledField
188+
dir="auto"
189+
onChange={handleTokenAddressChange}
190+
value={(disputeData.disputeKitData as IGatedDisputeData)?.tokenGate ?? ""}
191+
placeholder="Eg. 0xda10009cbd5d07dd0cecc66161fc93d7c9000da1"
192+
/>
193+
<StyledCheckbox
194+
onChange={handleERC1155TokenChange}
195+
checked={(disputeData.disputeKitData as IGatedDisputeData)?.isERC1155 ?? false}
196+
label="ERC-1155 token"
197+
small={true}
198+
/>
199+
{(disputeData.disputeKitData as IGatedDisputeData)?.isERC1155 && (
200+
<StyledField
201+
dir="auto"
202+
onChange={handleTokenIdChange}
203+
value={(disputeData.disputeKitData as IGatedDisputeData)?.tokenId ?? "0"}
204+
placeholder="Eg. 1"
205+
/>
206+
)}
207+
</>
208+
)}
115209
<AlertMessageContainer>
116210
<AlertMessage
117211
title="Check the courts available beforehand"
Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,65 @@
1+
import { ethers } from "ethers";
2+
3+
import { IDisputeKitData, IGatedDisputeData, ISomeFutureDisputeData } from "context/NewDisputeContext";
4+
5+
/**
6+
* Encodes gated dispute kit data
7+
* @param data Gated dispute kit data
8+
* @returns Encoded hex string
9+
*/
10+
const encodeGatedDisputeData = (data: IGatedDisputeData): string => {
11+
// Packing of tokenGate and isERC1155
12+
// uint88 (padding 11 bytes) + bool (1 byte) + address (20 bytes) = 32 bytes
13+
const packed = ethers.utils.solidityPack(["uint88", "bool", "address"], [0, data.isERC1155, data.tokenGate]);
14+
if (!data.tokenId || !data.isERC1155) data.tokenId = "0";
15+
return ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256"], [packed, data.tokenId]);
16+
};
17+
18+
/**
19+
* Encodes future dispute kit data
20+
* @param data Future dispute kit data
21+
* @returns Encoded hex string
22+
*/
23+
const encodeFutureDisputeData = (data: ISomeFutureDisputeData): string => {
24+
return ethers.utils.defaultAbiCoder.encode(["address"], [data.contract]);
25+
};
26+
27+
/**
28+
* Registry of encoding functions for different dispute kit data types
29+
*/
30+
const disputeKitDataEncoders = {
31+
gated: encodeGatedDisputeData,
32+
future: encodeFutureDisputeData,
33+
} as const;
34+
135
/**
236
* @param subcourtID ID of the court the dispute will take place in
337
* @param noOfVotes Number of votes the dispute will have
438
* @param disputeKit Id of the dispute kit to use
39+
* @param disputeKitData Optional dispute kit specific data
540
* @returns arbitrator extradata passed in while creating a dispute or querying costs
641
*/
7-
export const prepareArbitratorExtradata = (subcourtID: string, noOfVotes: number, disputeKit: number = 1) =>
8-
`0x${
9-
parseInt(subcourtID, 10).toString(16).padStart(64, "0") +
10-
noOfVotes.toString(16).padStart(64, "0") +
11-
disputeKit.toString(16).padStart(64, "0")
12-
}` as `0x{string}`;
42+
export const prepareArbitratorExtradata = (
43+
subcourtID: string,
44+
noOfVotes: number,
45+
disputeKit: number = 1,
46+
disputeKitData?: IDisputeKitData
47+
) => {
48+
let extraData = ethers.utils.defaultAbiCoder.encode(
49+
["uint256", "uint256", "uint256"],
50+
[subcourtID, noOfVotes, disputeKit]
51+
) as `0x{string}`;
52+
if (!disputeKitData) {
53+
console.log("extraData", extraData);
54+
return extraData;
55+
}
56+
57+
const encoder = disputeKitDataEncoders[disputeKitData.type];
58+
if (!encoder) {
59+
throw new Error(`Unknown dispute kit data type: ${disputeKitData.type}`);
60+
}
61+
const encodedDisputeKitData = encoder(disputeKitData as any);
62+
extraData = ethers.utils.hexConcat([extraData, encodedDisputeKitData]) as `0x{string}`;
63+
console.log("extraData", extraData);
64+
return extraData;
65+
};

0 commit comments

Comments
 (0)