Skip to content

Commit 1044333

Browse files
authored
Merge pull request #1635 from kleros/feat/more-robust-type-validations-in-sdk
feat(sdk): use zod directly for validating dispute templates
2 parents 08104eb + df2b3d9 commit 1044333

File tree

6 files changed

+37
-57
lines changed

6 files changed

+37
-57
lines changed

kleros-sdk/src/dataMappings/executeActions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const executeAction = async (mapping: ActionMapping, context: Record<stri
3131
return await fetchIpfsJsonAction(validateFetchIpfsJsonMapping(mapping));
3232
case "reality":
3333
mapping = validateRealityMapping(mapping);
34-
return await retrieveRealityData(mapping.realityQuestionID, context.arbitrable);
34+
return await retrieveRealityData(mapping.realityQuestionID, context.arbitrableAddress);
3535
default:
3636
throw new Error(`Unsupported action type: ${mapping.type}`);
3737
}

kleros-sdk/src/dataMappings/utils/DisputeDetailsValidator.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

kleros-sdk/src/dataMappings/utils/disputeDetailsSchema.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { z } from "zod";
22
import { isAddress } from "viem";
33
import { normalize } from "viem/ens";
44

5+
const isHexAddress = (str: string): boolean => /^0x[a-fA-F0-9]{40}$/.test(str);
6+
const isHexId = (str: string): boolean => /^0x[a-fA-F0-9]{1,64}$/.test(str);
7+
const isMultiaddr = (str: string): boolean =>
8+
/^\/(?:ip4|ip6|dns4|dns6|dnsaddr|tcp|udp|utp|tls|ws|wss|p2p-circuit|p2p-webrtc-star|p2p-webrtc-direct|p2p-websocket-star|onion|ipfs)(\/[^\s\/]+)+$|^ipfs:\/\/[a-zA-Z0-9]+\/[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)?$/.test(
9+
str
10+
);
11+
512
export const ethAddressSchema = z.string().refine((value) => isAddress(value), {
613
message: "Provided address is invalid.",
714
});
@@ -26,40 +33,41 @@ export enum QuestionType {
2633
export const QuestionTypeSchema = z.nativeEnum(QuestionType);
2734

2835
export const AnswerSchema = z.object({
29-
id: z.string().regex(/^0x[0-9a-fA-F]+$/), // should be a bigint
36+
id: z
37+
.string()
38+
.regex(/^0x[0-9a-fA-F]+$/)
39+
.optional(),
3040
title: z.string(),
3141
description: z.string(),
32-
reserved: z.boolean(),
42+
reserved: z.boolean().optional(),
3343
});
3444

3545
export const AttachmentSchema = z.object({
3646
label: z.string(),
3747
uri: z.string(),
3848
});
3949

40-
export const AliasSchema = z.object({
41-
id: z.string().optional(),
42-
name: z.string(),
43-
address: ethAddressOrEnsNameSchema,
44-
});
50+
export const AliasSchema = z.record(ethAddressOrEnsNameSchema);
51+
52+
const MetadataSchema = z.record(z.unknown());
4553

4654
const DisputeDetailsSchema = z.object({
4755
title: z.string(),
4856
description: z.string(),
4957
question: z.string(),
50-
type: QuestionTypeSchema,
5158
answers: z.array(AnswerSchema),
52-
policyURI: z.string(),
53-
attachment: AttachmentSchema,
54-
frontendUrl: z.string(),
55-
arbitrableChainID: z.string(),
56-
arbitrableAddress: ethAddressSchema,
59+
policyURI: z.string().refine((value) => isMultiaddr(value), {
60+
message: "Provided policy URI is not a valid multiaddr.",
61+
}),
62+
attachment: AttachmentSchema.optional(),
63+
frontendUrl: z.string().optional(),
64+
metadata: MetadataSchema.optional(),
5765
arbitratorChainID: z.string(),
5866
arbitratorAddress: ethAddressSchema,
59-
category: z.string(),
60-
lang: z.string(),
61-
specification: z.string(),
62-
aliases: z.array(AliasSchema).optional(),
67+
category: z.string().optional(),
68+
lang: z.string().optional(),
69+
specification: z.string().optional(),
70+
aliases: AliasSchema.optional(),
6371
version: z.string(),
6472
});
6573

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import mustache from "mustache";
22
import { DisputeDetails } from "./disputeDetailsTypes";
3-
import { validate } from "./DisputeDetailsValidator";
3+
import DisputeDetailsSchema from "./disputeDetailsSchema";
44

55
export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDetails => {
66
const render = mustache.render(mustacheTemplate, data);
77
const dispute = JSON.parse(render);
88

9-
// TODO: the validation below is too strict, it should be fixed, disabled for now, FIXME
10-
if (!validate(dispute)) {
11-
// throw new Error(`Invalid dispute details format: ${JSON.stringify(dispute)}`);
9+
const validation = DisputeDetailsSchema.safeParse(dispute);
10+
if (!validation.success) {
11+
console.error("Validation errors:", validation.error.errors, "\n\nDispute details:", `${JSON.stringify(dispute)}`);
12+
throw new Error("Invalid dispute details format");
1213
}
14+
console.log(dispute);
1315

1416
return dispute;
1517
};

web/src/hooks/queries/usePopulatedDisputeData.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { iArbitrableV2Abi } from "hooks/contracts/generated";
1212
import { useEvidenceGroup } from "queries/useEvidenceGroup";
1313
import { debounceErrorToast } from "utils/debounceErrorToast";
1414
import { isUndefined } from "utils/index";
15+
import { DEFAULT_CHAIN } from "consts/chains";
1516

1617
import { graphql } from "src/graphql";
1718

@@ -64,7 +65,8 @@ export const usePopulatedDisputeData = (disputeID?: string, arbitrableAddress?:
6465

6566
const initialContext = {
6667
disputeID: disputeID,
67-
arbitrable: arbitrableAddress,
68+
arbitrableAddress: arbitrableAddress,
69+
arbitrableChainID: isCrossChainDispute ? crossChainData.crossChainId.toString() : DEFAULT_CHAIN.toString(),
6870
graphApiKey: import.meta.env.REACT_APP_GRAPH_API_KEY,
6971
alchemyApiKey: import.meta.env.ALCHEMY_API_KEY,
7072
externalDisputeID: externalDisputeID,

web/src/pages/DisputeTemplateView/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ const DisputeTemplateView = () => {
166166

167167
setTimeout(() => {
168168
const initialContext = {
169-
arbitrator: debouncedParams._arbitrator,
170-
arbitrable: debouncedParams._arbitrable,
169+
arbitratorAddress: debouncedParams._arbitrator,
170+
arbitrableAddress: debouncedParams._arbitrable,
171171
arbitrableDisputeID: debouncedParams._arbitrableDisputeID,
172172
externalDisputeID: debouncedParams._externalDisputeID,
173173
templateID: debouncedParams._templateId,

0 commit comments

Comments
 (0)