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: validate prophet entities id #98

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 packages/automated-dispute/src/exceptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from "./invalidActorState.exception.js";
export * from "./invalidBlockHash.exception.js";
export * from "./invalidBlockRangeError.exception.js";
export * from "./invalidDisputeStatus.exception.js";
export * from "./prophetChecksumMismatch.exception.js";
export * from "./prophetDecodingError.exception.js";
export * from "./requestAlreadyHandled.exception.js";
export * from "./requestMismatch.exception.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class ProphetChecksumMismatch extends Error {
constructor() {
super(`Failed to validate checksum.`);

this.name = "ProphetChecksumMismatch";
}
}
5 changes: 5 additions & 0 deletions packages/automated-dispute/src/services/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
InvalidActorState,
InvalidDisputeStatus,
PastEventEnqueueError,
ProphetChecksumMismatch,
ProphetDecodingError,
RequestMismatch,
ResponseAlreadyProposed,
Expand Down Expand Up @@ -195,6 +196,10 @@ export class EboActor {
{ eventName: event.name },
);

continue;
} else if (err instanceof ProphetChecksumMismatch) {
this.logger.warn("Could not validate entity ID.", { event });

continue;
} else {
throw err;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { CommandAlreadyRun, CommandNotRun } from "../../../exceptions/index.js";
import {
CommandAlreadyRun,
CommandNotRun,
ProphetChecksumMismatch,
} from "../../../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js";
import { ProphetCodec } from "../../../services/index.js";
import { Dispute, EboEvent } from "../../../types/index.js";

export class AddDispute implements EboRegistryCommand {
Expand All @@ -14,6 +19,9 @@ export class AddDispute implements EboRegistryCommand {
event: EboEvent<"ResponseDisputed" | "DisputeEscalated">,
registry: EboRegistry,
): AddDispute {
if (!ProphetCodec.validateDispute(event.metadata.disputeId, event.metadata.dispute))
throw new ProphetChecksumMismatch();

const dispute: Dispute = {
id: event.metadata.disputeId,
createdAt: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { CommandAlreadyRun, CommandNotRun } from "../../../exceptions/index.js";
import {
CommandAlreadyRun,
CommandNotRun,
ProphetChecksumMismatch,
} from "../../../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js";
import { EboEvent, Response, ResponseBody } from "../../../types/index.js";
import { ProphetCodec } from "../../prophetCodec.js";
Expand All @@ -12,6 +16,9 @@ export class AddResponse implements EboRegistryCommand {
) {}

static buildFromEvent(event: EboEvent<"ResponseProposed">, registry: EboRegistry) {
if (!ProphetCodec.validateResponse(event.metadata.responseId, event.metadata.response))
throw new ProphetChecksumMismatch();

const encodedResponse = event.metadata.response.response;
const responseBody: ResponseBody = ProphetCodec.decodeResponse(encodedResponse);

Expand Down
26 changes: 25 additions & 1 deletion packages/automated-dispute/src/services/prophetCodec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Caip2ChainId } from "@ebo-agent/shared";
import {
AbiEventParametersToPrimitiveTypes,

Check failure on line 3 in packages/automated-dispute/src/services/prophetCodec.ts

View workflow job for this annotation

GitHub Actions / lint / Run Linters

'AbiEventParametersToPrimitiveTypes' is defined but never used
AbiParameter,
Address,
ByteArray,
decodeAbiParameters,
encodeAbiParameters,
getAbiItem,
Hex,
keccak256,
toHex,
} from "viem";

import type { BondEscalationStatus } from "../types/prophet.js";
import type { BondEscalationStatus, Dispute } from "../types/prophet.js";
import { ProphetDecodingError } from "../exceptions/index.js";
import { oracleAbi } from "../external.js";
import { DisputeStatus, Request, Response } from "../types/prophet.js";

const REQUEST_MODULE_DATA_REQUEST_ABI_FIELDS = [
Expand Down Expand Up @@ -278,4 +282,24 @@
throw new ProphetDecodingError("escalation.status", toHex(status.toString()));
}
}

static validateResponse(id: Response["id"], response: Response["prophetData"]): boolean {
const proposeResponseAbi = getAbiItem({ abi: oracleAbi, name: "proposeResponse" });
const responseAbi = proposeResponseAbi.inputs[1];

const encodedResponse = encodeAbiParameters([responseAbi], [response]);
const hashedResponse = keccak256(encodedResponse);

return id.toLowerCase() === hashedResponse.toLowerCase();
}

static validateDispute(id: Dispute["id"], dispute: Dispute["prophetData"]): boolean {
const disputeResponseAbi = getAbiItem({ abi: oracleAbi, name: "disputeResponse" });
const disputeAbi = disputeResponseAbi.inputs[2];

const encodedDispute = encodeAbiParameters([disputeAbi], [dispute]);
const hashedDispute = keccak256(encodedDispute);

return id.toLowerCase() === hashedDispute.toLowerCase();
}
}
4 changes: 2 additions & 2 deletions packages/automated-dispute/tests/mocks/eboActor.mocks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId, ILogger, NotificationService, UnixTimestamp } from "@ebo-agent/shared";
import { Mutex } from "async-mutex";
import { Block, pad } from "viem";
import { Block, pad, padHex } from "viem";
import { vi } from "vitest";

import { ProtocolProvider } from "../../src/providers/index.js";
Expand Down Expand Up @@ -220,7 +220,7 @@ export function buildDispute(
status: "Active",
},
prophetData: {
disputer: "0x01",
disputer: padHex("0x1", { size: 20 }),
proposer: response.prophetData.proposer,
requestId: request.id,
responseId: response.id,
Expand Down
7 changes: 7 additions & 0 deletions packages/automated-dispute/tests/services/eboActor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PastEventEnqueueError,
RequestMismatch,
} from "../../src/exceptions/index.js";
import { ProphetCodec } from "../../src/external.js";
import { EboEvent, Request, RequestId, ResponseId } from "../../src/types/index.js";
import mocks from "../mocks/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./eboActor/fixtures.js";
Expand Down Expand Up @@ -186,6 +187,10 @@ describe("EboActor", () => {
});

it("does not allow interleaved event processing", async () => {
const validateResponseMock = vi
.spyOn(ProphetCodec, "validateResponse")
.mockReturnValue(true);

/**
* This case aims to cover the scenario in which the first call keeps awaiting to
* resolve its internal promises while a second call to `processEvents` with
Expand Down Expand Up @@ -280,6 +285,8 @@ describe("EboActor", () => {

expect(callOrder).toEqual([1, 1, 2, 2]);
expect(callOrder).not.toEqual([1, 2, 2, 1]); // Case with no mutexes

validateResponseMock.mockRestore();
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ILogger, UnixTimestamp } from "@ebo-agent/shared";
import { Address } from "viem";
import { describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ProphetCodec } from "../../../src/external.js";
import { EboEvent } from "../../../src/types/index.js";
import mocks from "../../mocks/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js";
Expand All @@ -17,6 +18,14 @@ describe("onDisputeEscalated", () => {
const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const response = mocks.buildResponse(actorRequest);

beforeEach(() => {
vi.spyOn(ProphetCodec, "validateDispute").mockReturnValue(true);
});

afterEach(() => {
vi.resetAllMocks();
});

it("creates the dispute if it does not exist", async () => {
const dispute = mocks.buildDispute(actorRequest, response, {
decodedData: { status: "Escalated" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ILogger, UnixTimestamp } from "@ebo-agent/shared";
import { describe, expect, it, vi } from "vitest";

import { ProphetCodec } from "../../../src/services/prophetCodec.js";
import { ProphetCodec } from "../../../src/services/index.js";
import { DisputeId, EboEvent } from "../../../src/types/index.js";
import mocks from "../../mocks/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Hex } from "viem";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ResponseAlreadyProposed } from "../../../src/exceptions/index.js";
import { ProphetCodec } from "../../../src/services/prophetCodec.js";
import { ProphetCodec } from "../../../src/services/index.js";
import {
EboEvent,
Epoch,
Expand Down Expand Up @@ -51,6 +51,9 @@ describe("EboActor", () => {
vi.spyOn(ProphetCodec, "decodeRequestResponseModuleData").mockReturnValue(
request.decodedData.responseModuleData,
);

vi.spyOn(ProphetCodec, "validateResponse").mockReturnValue(true);
vi.spyOn(ProphetCodec, "encodeResponse").mockReturnValue("0x" as Hex);
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ILogger, UnixTimestamp } from "@ebo-agent/shared";
import { describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ProphetCodec } from "../../../src/services/index.js";
import { EboEvent } from "../../../src/types/events.js";
import { Dispute, Response } from "../../../src/types/prophet.js";
import mocks from "../../mocks/index.js";
Expand All @@ -26,6 +27,14 @@ describe("onResponseDisputed", () => {
},
};

beforeEach(() => {
vi.spyOn(ProphetCodec, "validateDispute").mockReturnValue(true);
});

afterEach(() => {
vi.restoreAllMocks();
});

it("pledges for dispute and proposes a new response if proposal should be different", async () => {
const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor(
actorRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ILogger, UnixTimestamp } from "@ebo-agent/shared";
import { ContractFunctionRevertedError } from "viem";
import { describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { ErrorHandler } from "../../../src/exceptions/errorHandler.js";
import { ErrorFactory } from "../../../src/exceptions/index.js";
Expand Down Expand Up @@ -38,6 +38,14 @@ describe("EboActor", () => {
},
};

beforeEach(() => {
vi.spyOn(ProphetCodec, "validateResponse").mockReturnValue(true);
});

afterEach(() => {
vi.spyOn(ProphetCodec, "validateResponse").mockRestore();
});

it("handles error when disputing the response", async () => {
expect.assertions(3);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, Mock, vi } from "vitest";

import { CommandAlreadyRun, CommandNotRun } from "../../../../src/exceptions/index.js";
import { EboRegistry } from "../../../../src/interfaces/index.js";
import { AddDispute } from "../../../../src/services/index.js";
import { AddDispute, ProphetCodec } from "../../../../src/services/index.js";
import { EboEvent } from "../../../../src/types/index.js";
import mocks from "../../../mocks/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "../../../services/eboActor/fixtures.js";
Expand Down Expand Up @@ -32,6 +32,8 @@ describe("AddDispute", () => {
addDispute: vi.fn(),
removeDispute: vi.fn(),
} as unknown as EboRegistry;

vi.spyOn(ProphetCodec, "validateDispute").mockReturnValue(true);
});

describe("run", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { fail } from "assert";
import { Hex } from "viem";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest";

import {
CommandAlreadyRun,
CommandNotRun,
ProphetChecksumMismatch,
ProphetDecodingError,
} from "../../../../src/exceptions/index.js";
import { EboRegistry } from "../../../../src/interfaces/index.js";
import { AddResponse } from "../../../../src/services/index.js";
import { AddResponse, ProphetCodec } from "../../../../src/services/index.js";
import { EboEvent } from "../../../../src/types/index.js";
import mocks from "../../../mocks/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "../../../services/eboActor/fixtures.js";
Expand Down Expand Up @@ -36,6 +37,20 @@ describe("AddResponse", () => {
addResponse: vi.fn(),
removeResponse: vi.fn(),
} as unknown as EboRegistry;

vi.spyOn(ProphetCodec, "validateResponse").mockReturnValue(true);
});

afterEach(() => {
vi.resetAllMocks();
});

describe("buildFromEvent", () => {
it("throws when response has invalid ID", () => {
expect(() => {
AddResponse.buildFromEvent(event, registry);
}).toThrowError(ProphetChecksumMismatch);
});
});

describe("run", () => {
Expand Down
56 changes: 54 additions & 2 deletions packages/automated-dispute/tests/services/prophetCodec.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { isHex } from "viem";
import { isHex, padHex } from "viem";
import { describe, expect, it } from "vitest";

import { ProphetDecodingError } from "../../src/exceptions";
import { ProphetCodec } from "../../src/services";
import { DisputeStatus, Response } from "../../src/types";
import { DisputeId, DisputeStatus, Response, ResponseId } from "../../src/types";
import { buildDispute, buildRequest, buildResponse } from "../mocks/eboActor.mocks";

describe("ProphetCodec", () => {
describe("encodeResponse", () => {
Expand Down Expand Up @@ -105,4 +106,55 @@ describe("ProphetCodec", () => {
describe.todo("encodeRequestResponseModuleData");
describe.todo("encodeRequestRequestModuleData");
describe.todo("encodeRequestDisputeModuleData");

describe("validateResponse", () => {
const request = buildRequest();
const response = buildResponse(request, {
id: "0xa36520fabcf8d19d153972ff5c123e3e3597e587fb1a23dd7711b79117b9bab0" as ResponseId,
});

it("returns true if checksum is successful", () => {
const validateResponse = ProphetCodec.validateResponse(
response.id,
response["prophetData"],
);

expect(validateResponse).toBe(true);
});

it("returns false if checksum is not success", () => {
const validateResponse = ProphetCodec.validateResponse(
padHex("0x01") as ResponseId,
response["prophetData"],
);

expect(validateResponse).toBe(false);
});
});

describe("validateDispute", () => {
const request = buildRequest();
const response = buildResponse(request);
const dispute = buildDispute(request, response, {
id: "0x0e58386b5c7eaa97b4bd898e06b83c7a5f2094ef90994596f6e57d7b314a29c6",
});

it("returns true if checksum is successful", () => {
const validateDispute = ProphetCodec.validateDispute(
dispute.id,
dispute["prophetData"],
);

expect(validateDispute).toBe(true);
});

it("returns false if checksum is not success", () => {
const validateDispute = ProphetCodec.validateDispute(
padHex("0x01") as DisputeId,
dispute["prophetData"],
);

expect(validateDispute).toBe(false);
});
});
});
Loading