Skip to content

Commit

Permalink
feat: validate prophet entities id
Browse files Browse the repository at this point in the history
  • Loading branch information
0xyaco committed Nov 25, 2024
1 parent f687dc5 commit 043311a
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 15 deletions.
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 @@ export class ProphetCodec {
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);
});
});
});

0 comments on commit 043311a

Please sign in to comment.