Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/dynamic context validation in sdk #1703

Merged
merged 40 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dbb89d2
feat(sdk): dynamic-context-validation
Harman-singh-waraich Oct 8, 2024
85f3668
chore(web): add-graph-api-key-env
Harman-singh-waraich Oct 8, 2024
cdbbcf2
feat(subgraph): fetch-dispute-request-event-data-within-subgraph
Harman-singh-waraich Oct 11, 2024
c59e102
feat(kleros-sdk): get-dispute-function
Harman-singh-waraich Oct 14, 2024
220ea2f
chore(web): update-dispute-population-flow
Harman-singh-waraich Oct 14, 2024
6974fdb
chore(web): configure-sdk-with-web3-context
Harman-singh-waraich Oct 14, 2024
88963e7
chore(kleros-sdk): make-configuring-sdk-explicit
Harman-singh-waraich Oct 14, 2024
3ad42d5
refactor(kleros-sdk): implement-rabbit-ai-feedback
Harman-singh-waraich Oct 14, 2024
d2c8f9c
Merge branch 'dev' into feat/dynamic-context-validation-in-sdk
Harman-singh-waraich Oct 15, 2024
25063c7
chore: fixed the SDK tests, minor tweaks
jaybuidl Oct 15, 2024
3abfbc5
fix(kleros-sdk): public-client-null-check
Harman-singh-waraich Oct 15, 2024
9140518
chore(subgraph): redeploy-subgraphs
Harman-singh-waraich Oct 15, 2024
e8cd12d
fix(sdk): types and unit tests
jaybuidl Oct 15, 2024
d3e4b59
chore(sdk): release configuration for NPM, tsconfig tweaks
jaybuidl Oct 16, 2024
1a11c84
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
98c2907
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
b851f2a
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
57711ba
docs(sdk): readme
jaybuidl Oct 16, 2024
cbdd6d1
chore: clean up
jaybuidl Oct 16, 2024
77b57e4
chore(kleros-sdk): define-entry-points-for-files
Harman-singh-waraich Oct 16, 2024
71f851b
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 16, 2024
ecc9edf
refactor(kleros-sdk): remove-path-aliasing
Harman-singh-waraich Oct 18, 2024
e698548
refactor(kleros-sdk): update-get-dispute-function-parameter-type
Harman-singh-waraich Oct 21, 2024
93e59a1
fix(web): typing
Harman-singh-waraich Oct 21, 2024
18ae12f
Merge branch 'dev' into feat/dynamic-context-validation-in-sdk
Harman-singh-waraich Oct 21, 2024
0a8422f
chore: update-yarn-lock
Harman-singh-waraich Oct 21, 2024
04c80db
chore(kleros-sdk): update-get-dispute-id-spec
Harman-singh-waraich Oct 21, 2024
9b4e9d2
feat(kleros-sdk): better-error-handling-and-optimisations
Harman-singh-waraich Oct 22, 2024
7d5ed21
refactor(kleros-sdk): sonar-cloud-fixes
Harman-singh-waraich Oct 22, 2024
542a8d9
refactor(kleros-sdk): remoev-unused-import
Harman-singh-waraich Oct 22, 2024
3d42edc
refactor(kleros-sdk): address-coderabbit-feedback
Harman-singh-waraich Oct 22, 2024
a387e77
refactor(kleros-sdk): refactor-error-classes
Harman-singh-waraich Oct 22, 2024
56853b9
fix: test mocks
jaybuidl Oct 23, 2024
52b31a3
fix(kleros-sdk): replace-graphql-request-library-with-native-fetch
Harman-singh-waraich Oct 23, 2024
cab784b
chore(kleros-sdk): use-urql-for-gql-queries
Harman-singh-waraich Oct 24, 2024
0562712
feat(kleros-sdk): gql-client-caching
Harman-singh-waraich Oct 24, 2024
078b233
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 25, 2024
128e1e5
chore(sdk): publish script
jaybuidl Oct 25, 2024
d2cb260
chore(sdk): release @kleros/[email protected]
jaybuidl Oct 25, 2024
7b2ccd3
Merge branch 'dev' into feat/dynamic-context-validation-in-sdk
jaybuidl Oct 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kleros-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dependencies": {
"@kleros/kleros-v2-contracts": "workspace:^",
"@reality.eth/reality-eth-lib": "^3.2.30",
"graphql-request": "^7.1.0",
"mustache": "^4.2.0",
"zod": "^3.22.4"
}
Expand Down
2 changes: 1 addition & 1 deletion kleros-sdk/src/dataMappings/executeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const executeAction = async (mapping: ActionMapping, context: Record<stri
}
};

export const executeActions = async (mappings, initialContext = {}) => {
export const executeActions = async (mappings, initialContext: Record<string, any> = {}) => {
const context = { ...initialContext };

for (const mapping of mappings) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import mustache from "mustache";
import retrieveVariables from "./retrieveVariables";

export const replacePlaceholdersWithValues = (mapping: any, context: Record<string, unknown>) => {
const replace = (obj) => {
if (typeof obj === "string") {
validateContext(obj, context);
return mustache.render(obj, context);
} else if (Array.isArray(obj)) {
return obj.map(replace);
Expand All @@ -15,3 +17,18 @@ export const replacePlaceholdersWithValues = (mapping: any, context: Record<stri

return replace(mapping);
};

/**
*
* @param template
* @param context
* @description retrieves all variables from a template and validates if they are provided in the context
*/
const validateContext = (template: string, context: Record<string, unknown>) => {
const variables = retrieveVariables(template);

variables.forEach((variable) => {
if (!context[variable]) throw new Error(`Expected key : "${variable}" to be provided in context.`);
});
return true;
};
20 changes: 20 additions & 0 deletions kleros-sdk/src/dataMappings/utils/retrieveVariables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import mustache from "mustache";

/**
*
* @param template
* @returns Variables[] returns the variables in a template string
* @note This is a naive implementation and wil not work for complex tags, only works for {{}} and {{{}}} tags for now.
* Reference : https://github.com/janl/mustache.js/issues/538
*/
const retrieveVariables = (template: string) =>
mustache
.parse(template)
.filter(function (v) {
return v[0] === "name" || v[0] === "&";
}) // add more conditions here to include more tags
.map(function (v) {
return v[1];
});

export default retrieveVariables;
40 changes: 40 additions & 0 deletions kleros-sdk/src/requests/fetchDisputeDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { request } from "graphql-request";

type DisputeDetailsQueryResponse = {
dispute: {
arbitrated: {
id: string;
};
arbitrableChainId: number;
externalDisputeId: number;
templateId: number;
};
};

const fetchDisputeDetails = (endpoint: string, id: number) => {
const query = `
query DisputeDetails {
dispute(id: ${id}) {
arbitrated {
id
}
arbitrableChainId
externalDisputeId
templateId
}
}
`;

try {
return request<DisputeDetailsQueryResponse>(endpoint, query)
.then((res) => res)
.catch((err) => {
throw new Error(`Error querying Dispute Details , endpoint : ${endpoint}, message : ${err?.message}`);
});
} catch (error: any) {
console.log(`Query Error : ${error?.message}`);
return undefined;
}
};

export default fetchDisputeDetails;
32 changes: 32 additions & 0 deletions kleros-sdk/src/requests/fetchDisputeTemplateFromId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { request } from "graphql-request";

type DisputeTemplateQueryResponse = {
disputeTemplate: {
templateData: string;
templateDataMappings: string;
};
};

const fetchDisputeTemplateFromId = (endpoint: string, id: number) => {
const query = `
query DisputeTemplate {
disputeTemplate(id: ${id}) {
templateData
templateDataMappings
}
}
`;

try {
return request<DisputeTemplateQueryResponse>(endpoint, query)
.then((res) => res)
.catch((err) => {
throw new Error(`Error querying Dispute Template Registry , endpoint : ${endpoint}, message : ${err?.message}`);
});
} catch (error: any) {
console.log(`Query Error : ${error?.message}`);
return undefined;
}
};

export default fetchDisputeTemplateFromId;
19 changes: 7 additions & 12 deletions kleros-sdk/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { createPublicClient, webSocket } from "viem";
import { arbitrumSepolia } from "viem/chains";
import { createPublicClient, type PublicClient } from "viem";
import { SdkConfig } from "./types";

let publicClient;
let publicClient: PublicClient | undefined;

export const configureSDK = (config: { apiKey?: string }) => {
if (config.apiKey) {
const ALCHEMY_API_KEY = config.apiKey;
const transport = webSocket(`wss://arb-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`);
publicClient = createPublicClient({
chain: arbitrumSepolia,
transport,
});
export const configureSDK = (config: SdkConfig) => {
if (config.client) {
publicClient = createPublicClient(config.client);
}
};

export const getPublicClient = () => {
export const getPublicClient = (): PublicClient | undefined => {
if (!publicClient) {
throw new Error("SDK not configured. Please call `configureSDK` before using.");
}
Expand Down
17 changes: 17 additions & 0 deletions kleros-sdk/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PublicClientConfig } from "viem";

export type SdkConfig = {
client: PublicClientConfig;
};

type GetDisputeParametersOptions = {
sdkConfig: SdkConfig;
additionalContext: Record<string, any>;
};

export type GetDisputeParameters = {
disputeId: number;
coreSubgraph: string;
dtrSubgraph: string;
options: GetDisputeParametersOptions | undefined;
};
46 changes: 46 additions & 0 deletions kleros-sdk/src/utils/getDispute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { type GetDisputeParameters } from "src/types";
import { configureSDK } from "src/sdk";
import fetchDisputeDetails from "src/requests/fetchDisputeDetails";
import fetchDisputeTemplateFromId from "src/requests/fetchDisputeTemplateFromId";
import { executeActions } from "dataMappings/executeActions";
import { populateTemplate } from "dataMappings/utils/populateTemplate";
import { DisputeDetails } from "dataMappings/utils/disputeDetailsTypes";

/**
* Retrieves dispute parameters based on the provided dispute ID and subgraph endpoints.
*
* @param {GetDisputeParameters} disputeParameters - The parameters required to get the dispute.
* @param {number} disputeParameters.disputeId - A unique numeric identifier of the dispute in the Kleros Core contract.
* @param {string} disputeParameters.coreSubgraph - Endpoint for the Kleros core subgraph to use.
* @param {string} disputeParameters.dtrSubgraph - Endpoint for the Kleros dispute template registry subgraph.
* @param {GetDisputeParametersOptions | undefined} disputeParameters.options - Optional parameters to configure the SDK and provide additional context, if not configured already.
*/
export const getDispute = async (disputeParameters: GetDisputeParameters): Promise<DisputeDetails | undefined> => {
if (disputeParameters.options?.sdkConfig) {
configureSDK(disputeParameters.options.sdkConfig);
}
const { disputeId, dtrSubgraph, coreSubgraph, options } = disputeParameters;

const disputeDetails = await fetchDisputeDetails(coreSubgraph, disputeId);

if (!disputeDetails || !disputeDetails.dispute) return;

const template = await fetchDisputeTemplateFromId(dtrSubgraph, disputeDetails.dispute.templateId);

if (!template) return;

const { templateData, templateDataMappings } = template.disputeTemplate;

const initialContext = {
arbitrableAddress: disputeDetails.dispute.arbitrated.id,
arbitrableChainID: disputeDetails.dispute.arbitrableChainId,
externalDisputeID: disputeDetails.dispute.externalDisputeId,
...options?.additionalContext,
};

const data = templateDataMappings ? await executeActions(JSON.parse(templateDataMappings), initialContext) : {};

const populatedTemplate = populateTemplate(templateData, data);

return populatedTemplate;
};
4 changes: 4 additions & 0 deletions subgraph/core/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ type Dispute @entity {
jurors: [User!]! @derivedFrom(field: "disputes")
shifts: [TokenAndETHShift!]! @derivedFrom(field: "dispute")
disputeKitDispute: [DisputeKitDispute!]! @derivedFrom(field: "coreDispute")
isCrossChain: Boolean
arbitrableChainId:BigInt
externalDisputeId:BigInt
templateId:BigInt
}

type PeriodIndexCounter @entity {
Expand Down
70 changes: 70 additions & 0 deletions subgraph/core/src/entities/Dispute.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BigInt, ByteArray, crypto, dataSource, ethereum } from "@graphprotocol/graph-ts";
import { KlerosCore, DisputeCreation } from "../../generated/KlerosCore/KlerosCore";
import { Court, Dispute } from "../../generated/schema";
import { ZERO } from "../utils";
Expand Down Expand Up @@ -27,4 +28,73 @@ export function createDisputeFromEvent(event: DisputeCreation): void {
const roundID = `${disputeID.toString()}-${ZERO.toString()}`;
dispute.currentRound = roundID;
dispute.save();

updateDisputeRequestData(event);
}

const DisputeRequest = "DisputeRequest(address,uint256,uint256,uint256,string)";

const DisputeRequestSignature = crypto.keccak256(ByteArray.fromUTF8(DisputeRequest));

// note : we are using bytes32 in place of string as strings cannot be decoded and it breaks the function.
// It is okay for us, as we are interested in the uint256 in front
// _externalDisputeId,_templateId,_tenplateUri
const DisputeRequestTypestring = "(uint256,uint256,bytes32)";

const CrossChainDisputeIncoming =
"CrossChainDisputeIncoming(address,uint256,address,uint256,uint256,uint256,uint256,string)";

const CrossChainDisputeIncomingSignature = crypto.keccak256(ByteArray.fromUTF8(CrossChainDisputeIncoming));

// arbitrator, _arbitrableChainId, _externalDisputeId, _templateId, _templateUri
// note : arbitrable is an indexed arg, so it will topic[1]
const CrossChainDisputeIncomingTypestring = "(address,uint256,uint256,uint256,string)";

export const updateDisputeRequestData = (event: DisputeCreation): void => {
const dispute = Dispute.load(event.params._disputeID.toString());

if (!dispute) return;

const receipt = event.receipt;
if (!receipt) return;

const logs = receipt.logs;

// note that the topic at 0th index is always the event signature
const disputeRequestEventIndex = logs.findIndex((log) => log.topics[0] == DisputeRequestSignature);

const crossChainDisputeEventIndex = logs.findIndex((log) => log.topics[0] == CrossChainDisputeIncomingSignature);

if (crossChainDisputeEventIndex !== -1) {
const crossChainDisputeEvent = logs[crossChainDisputeEventIndex];

const decoded = ethereum.decode(CrossChainDisputeIncomingTypestring, crossChainDisputeEvent.data);

if (!decoded) return;

dispute.isCrossChain = true;
dispute.arbitrableChainId = decoded.toTuple()[1].toBigInt();
dispute.externalDisputeId = decoded.toTuple()[2].toBigInt();
dispute.templateId = decoded.toTuple()[3].toBigInt();
dispute.save();
return;
} else if (disputeRequestEventIndex !== -1) {
const disputeRequestEvent = logs[disputeRequestEventIndex];

const decoded = ethereum.decode(DisputeRequestTypestring, disputeRequestEvent.data);
if (!decoded) return;
dispute.isCrossChain = false;
dispute.arbitrableChainId = getChainId(dataSource.network());
dispute.externalDisputeId = decoded.toTuple()[0].toBigInt();
dispute.templateId = decoded.toTuple()[1].toBigInt();
dispute.save();
return;
}
};

// workaround, since hashmap don't work in subgraphs
function getChainId(name: string): BigInt {
if (name == "arbitrum-one") return BigInt.fromI32(42161);
else if (name == "arbitrum-sepolia") return BigInt.fromI32(421614);
else return BigInt.fromI32(1);
}
13 changes: 7 additions & 6 deletions subgraph/core/subgraph.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
specVersion: 0.0.4
specVersion: 0.0.5
schema:
file: ./schema.graphql
features:
Expand All @@ -14,7 +14,7 @@ dataSources:
startBlock: 3638878
mapping:
kind: ethereum/events
apiVersion: 0.0.6
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- User
Expand All @@ -39,6 +39,7 @@ dataSources:
handler: handleAppealDecision
- event: DisputeCreation(indexed uint256,indexed address)
handler: handleDisputeCreation
receipt: true
- event: Draw(indexed address,indexed uint256,uint256,uint256)
handler: handleDraw
- event: NewPeriod(indexed uint256,uint8)
Expand Down Expand Up @@ -69,7 +70,7 @@ dataSources:
startBlock: 3084568
mapping:
kind: ethereum/events
apiVersion: 0.0.6
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Court
Expand All @@ -89,7 +90,7 @@ dataSources:
startBlock: 3638835
mapping:
kind: ethereum/events
apiVersion: 0.0.6
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- ClassicDispute
Expand Down Expand Up @@ -124,7 +125,7 @@ dataSources:
startBlock: 3638735
mapping:
kind: ethereum/events
apiVersion: 0.0.6
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- ClassicEvidenceGroup
Expand All @@ -145,7 +146,7 @@ dataSources:
startBlock: 3638850
mapping:
kind: ethereum/events
apiVersion: 0.0.6
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- JurorTokensPerCourt
Expand Down
3 changes: 3 additions & 0 deletions web/.env.devnet-university.public
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ export REACT_APP_ARBITRATOR_TYPE=university
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'

# devtools
export REACT_APP_GRAPH_API_KEY=
3 changes: 3 additions & 0 deletions web/.env.devnet.public
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'

# devtools
export REACT_APP_GRAPH_API_KEY=
Loading
Loading