diff --git a/packages/automated-dispute/src/exceptions/index.ts b/packages/automated-dispute/src/exceptions/index.ts index e043d0a0..9f43040a 100644 --- a/packages/automated-dispute/src/exceptions/index.ts +++ b/packages/automated-dispute/src/exceptions/index.ts @@ -1,21 +1,20 @@ +export * from "./blockNumberServiceRequired.exception.js"; +export * from "./customContractError.js"; +export * from "./decodeLogDataFailure.js"; export * from "./eboActor/index.js"; export * from "./eboProcessor/index.js"; export * from "./eboRegistry/index.js"; - +export * from "./errorFactory.js"; +export * from "./invalidAccountOnClient.exception.js"; export * from "./invalidActorState.exception.js"; +export * from "./invalidBlockHash.exception.js"; +export * from "./invalidBlockRangeError.exception.js"; export * from "./invalidDisputeStatus.exception.js"; +export * from "./prophetDecodingError.exception.js"; export * from "./requestAlreadyHandled.exception.js"; export * from "./requestMismatch.exception.js"; export * from "./responseAlreadyProposed.exception.js"; export * from "./rpcUrlsEmpty.exception.js"; export * from "./transactionExecutionError.exception.js"; -export * from "./invalidAccountOnClient.exception.js"; -export * from "./unsupportedEvent.exception.js"; -export * from "./decodeLogDataFailure.js"; -export * from "./invalidBlockRangeError.exception.js"; export * from "./unknownCustomError.exception.js"; -export * from "./invalidBlockHash.exception.js"; -export * from "./unknownDisputeStatus.exception.js"; -export * from "./blockNumberServiceRequired.exception.js"; -export * from "./customContractError.js"; -export * from "./errorFactory.js"; +export * from "./unsupportedEvent.exception.js"; diff --git a/packages/automated-dispute/src/exceptions/prophetDecodingError.exception.ts b/packages/automated-dispute/src/exceptions/prophetDecodingError.exception.ts new file mode 100644 index 00000000..20e01590 --- /dev/null +++ b/packages/automated-dispute/src/exceptions/prophetDecodingError.exception.ts @@ -0,0 +1,13 @@ +import { ByteArray, Hex } from "viem"; + +export class ProphetDecodingError extends Error { + constructor( + public readonly id: string, + public readonly data: ByteArray | Hex, + public readonly err?: Error, + ) { + super(`Failed to decode ${id} with data ${data}.`); + + this.name = "ProphetDecodingError"; + } +} diff --git a/packages/automated-dispute/src/exceptions/unknownDisputeStatus.exception.ts b/packages/automated-dispute/src/exceptions/unknownDisputeStatus.exception.ts deleted file mode 100644 index c683a14a..00000000 --- a/packages/automated-dispute/src/exceptions/unknownDisputeStatus.exception.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class UnknownDisputeStatus extends Error { - constructor(status: number) { - super(`Unknown dispute status ${status}`); - this.name = "UnknownDisputeStatus"; - } -} diff --git a/packages/automated-dispute/src/providers/protocolProvider.ts b/packages/automated-dispute/src/providers/protocolProvider.ts index aeccf52d..3a09326e 100644 --- a/packages/automated-dispute/src/providers/protocolProvider.ts +++ b/packages/automated-dispute/src/providers/protocolProvider.ts @@ -49,7 +49,6 @@ import { RpcUrlsEmpty, TransactionExecutionError, } from "../exceptions/index.js"; -import { ProphetCodec } from "../external.js"; import { IProtocolProvider, IReadProvider, @@ -467,7 +466,7 @@ export class ProtocolProvider implements IProtocolProvider { responseId: HexUtils.normalize(_dispute.responseId) as ResponseId, requestId: HexUtils.normalize(_dispute.requestId) as RequestId, }, - status: ProphetCodec.decodeDisputeStatus(_status), + status: _status, blockNumber: event.blockNumber, }, } as EboEvent<"DisputeStatusUpdated">; diff --git a/packages/automated-dispute/src/services/eboActor.ts b/packages/automated-dispute/src/services/eboActor.ts index 3016c696..5f09f87a 100644 --- a/packages/automated-dispute/src/services/eboActor.ts +++ b/packages/automated-dispute/src/services/eboActor.ts @@ -23,6 +23,7 @@ import { InvalidActorState, InvalidDisputeStatus, PastEventEnqueueError, + ProphetDecodingError, RequestMismatch, ResponseAlreadyProposed, ResponseNotFound, @@ -149,9 +150,26 @@ export class EboActor { while ((event = this.eventsQueue.pop())) { this.lastEventProcessed = event; - const updateStateCommand = this.buildUpdateStateCommand(event); + let updateStateCommand: EboRegistryCommand; - updateStateCommand.run(); + try { + updateStateCommand = this.buildUpdateStateCommand(event); + updateStateCommand.run(); + } catch (err) { + if (err instanceof ProphetDecodingError) { + // Skipping malformed entities + this.logger.warn( + stringify({ + reason: err.err?.name, + message: err.message, + }), + ); + + continue; + } else { + throw err; + } + } try { if (this.eventsQueue.isEmpty()) { @@ -391,12 +409,12 @@ export class EboActor { private getActiveDisputes(): Dispute[] { const disputes = this.registry.getDisputes(); - return disputes.filter((dispute) => dispute.status === "Active"); + return disputes.filter((dispute) => dispute.decodedData.status === "Active"); } // TODO: extract this into another service private canBeSettled(request: Request, dispute: Dispute, atTimestamp: UnixTimestamp): boolean { - if (dispute.status !== "Active") return false; + if (dispute.decodedData.status !== "Active") return false; const { bondEscalationDeadline, tyingBuffer } = request.decodedData.disputeModuleData; const deadline = (dispute.createdAt.timestamp + @@ -499,7 +517,7 @@ export class EboActor { // Response is still able to be disputed if (atTimestamp <= disputeWindow) return false; - return dispute ? ["Lost", "None"].includes(dispute.status) : true; + return dispute ? ["Lost", "None"].includes(dispute.decodedData.status) : true; } /** @@ -548,7 +566,7 @@ export class EboActor { // the proposal non-active. const activeStatus: DisputeStatus[] = ["None", "Active"]; - return activeStatus.includes(dispute.status); + return activeStatus.includes(dispute.decodedData.status); }); } @@ -941,7 +959,7 @@ export class EboActor { private async onDisputeStatusChanged(event: EboEvent<"DisputeStatusUpdated">): Promise { const request = this.getActorRequest(); const disputeId = event.metadata.disputeId; - const disputeStatus = event.metadata.status; + const disputeStatus = ProphetCodec.decodeDisputeStatus(event.metadata.status); this.logger.info(`Dispute ${disputeId} status changed to ${disputeStatus}.`); diff --git a/packages/automated-dispute/src/services/eboProcessor.ts b/packages/automated-dispute/src/services/eboProcessor.ts index 9d55db94..9103ec6b 100644 --- a/packages/automated-dispute/src/services/eboProcessor.ts +++ b/packages/automated-dispute/src/services/eboProcessor.ts @@ -177,6 +177,7 @@ export class EboProcessor { const currentEpoch = await this.protocolProvider.getCurrentEpoch(); this.logger.info(`Current epoch fetched.`); + this.logger.debug(stringify(currentEpoch)); return currentEpoch; } diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/addDispute.ts b/packages/automated-dispute/src/services/eboRegistry/commands/addDispute.ts index e347d205..9da42e29 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/addDispute.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/addDispute.ts @@ -21,7 +21,9 @@ export class AddDispute implements EboRegistryCommand { blockNumber: event.blockNumber, logIndex: event.logIndex, }, - status: "Active", + decodedData: { + status: "Active", + }, prophetData: event.metadata.dispute, }; diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/updateDisputeStatus.ts b/packages/automated-dispute/src/services/eboRegistry/commands/updateDisputeStatus.ts index 40b0a209..7d944127 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/updateDisputeStatus.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/updateDisputeStatus.ts @@ -1,6 +1,7 @@ import { CommandAlreadyRun, CommandNotRun, DisputeNotFound } from "../../../exceptions/index.js"; import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js"; import { DisputeStatus, EboEvent, EboEventName } from "../../../types/index.js"; +import { ProphetCodec } from "../../prophetCodec.js"; export class UpdateDisputeStatus implements EboRegistryCommand { private wasRun: boolean = false; @@ -19,7 +20,7 @@ export class UpdateDisputeStatus implements EboRegistryCommand { const disputeId = event.metadata.disputeId; const status = this.isDisputeStatusChangedEvent(event) - ? event.metadata.status + ? ProphetCodec.decodeDisputeStatus(event.metadata.status) : "Escalated"; return new UpdateDisputeStatus(registry, disputeId, status); @@ -38,7 +39,7 @@ export class UpdateDisputeStatus implements EboRegistryCommand { if (!dispute) throw new DisputeNotFound(this.disputeId); - this.previousStatus = dispute.status; + this.previousStatus = dispute.decodedData.status; this.registry.updateDisputeStatus(this.disputeId, this.status); diff --git a/packages/automated-dispute/src/services/eboRegistry/eboMemoryRegistry.ts b/packages/automated-dispute/src/services/eboRegistry/eboMemoryRegistry.ts index d558dd95..0a708600 100644 --- a/packages/automated-dispute/src/services/eboRegistry/eboMemoryRegistry.ts +++ b/packages/automated-dispute/src/services/eboRegistry/eboMemoryRegistry.ts @@ -96,7 +96,9 @@ export class EboMemoryRegistry implements EboRegistry { this.disputes.set(disputeId, { ...dispute, - status: status, + decodedData: { + status: status, + }, }); } diff --git a/packages/automated-dispute/src/services/prophetCodec.ts b/packages/automated-dispute/src/services/prophetCodec.ts index f000de56..6e54fa0e 100644 --- a/packages/automated-dispute/src/services/prophetCodec.ts +++ b/packages/automated-dispute/src/services/prophetCodec.ts @@ -1,7 +1,15 @@ import { Caip2ChainId } from "@ebo-agent/shared"; -import { Address, decodeAbiParameters, encodeAbiParameters } from "viem"; +import { + AbiParameter, + Address, + ByteArray, + decodeAbiParameters, + encodeAbiParameters, + Hex, + toHex, +} from "viem"; -import { UnknownDisputeStatus } from "../exceptions/unknownDisputeStatus.exception.js"; +import { ProphetDecodingError } from "../exceptions/index.js"; import { DisputeStatus, Request, Response } from "../types/prophet.js"; const REQUEST_MODULE_DATA_REQUEST_ABI_FIELDS = [ @@ -60,6 +68,27 @@ const DISPUTE_STATUS_ENUM: DisputeStatus[] = [ /** Class to encode/decode Prophet's structs into/from a byte array */ export class ProphetCodec { + /** + * Decodes byte-serialized data. + * + * @param id identifier of the decoding to be used in case it fails + * @param params abi fields to use for decoding + * @param data data to be decoded + * @throws {ProphetDecodingError} + * @returns the decoded data + */ + private static decode( + id: string, + params: params, + data: ByteArray | Hex, + ) { + try { + return decodeAbiParameters(params, data); + } catch (err) { + throw new ProphetDecodingError(id, data, err instanceof Error ? err : undefined); + } + } + /** * Decodes the request's request module data bytes into an object. * @@ -70,7 +99,8 @@ export class ProphetCodec { static decodeRequestRequestModuleData( requestModuleData: Request["prophetData"]["requestModuleData"], ): Request["decodedData"]["requestModuleData"] { - const decodeParameters = decodeAbiParameters( + const decodeParameters = this.decode( + "request.requestModuleData", REQUEST_MODULE_DATA_REQUEST_ABI_FIELDS, requestModuleData, ); @@ -107,7 +137,8 @@ export class ProphetCodec { static decodeRequestResponseModuleData( responseModuleData: Request["prophetData"]["responseModuleData"], ): Request["decodedData"]["responseModuleData"] { - const decodedParameters = decodeAbiParameters( + const decodedParameters = this.decode( + "request.responseModuleData", RESPONSE_MODULE_DATA_REQUEST_ABI_FIELDS, responseModuleData, ); @@ -138,7 +169,8 @@ export class ProphetCodec { static decodeRequestDisputeModuleData( disputeModuleData: Request["prophetData"]["disputeModuleData"], ): Request["decodedData"]["disputeModuleData"] { - const decodedParameters = decodeAbiParameters( + const decodedParameters = this.decode( + "request.disputeModuleData", DISPUTE_MODULE_DATA_REQUEST_ABI_FIELDS, disputeModuleData, ); @@ -182,7 +214,11 @@ export class ProphetCodec { static decodeResponse( response: Response["prophetData"]["response"], ): Response["decodedData"]["response"] { - const decodedParameters = decodeAbiParameters(RESPONSE_RESPONSE_ABI_FIELDS, response); + const decodedParameters = this.decode( + "response.response", + RESPONSE_RESPONSE_ABI_FIELDS, + response, + ); return { block: decodedParameters[0], @@ -201,9 +237,23 @@ export class ProphetCodec { * @returns The DisputeStatus string corresponding to the input value. */ static decodeDisputeStatus(status: number): DisputeStatus { - const disputeStatus = DISPUTE_STATUS_ENUM[status]; + const disputeStatus: DisputeStatus | undefined = DISPUTE_STATUS_ENUM[status]; + + if (disputeStatus) return disputeStatus; + else throw new ProphetDecodingError("dispute.status", toHex(status.toString())); + } + + /** + * Encodes a DisputeStatus string into its enum uint8 index. + * + * @param {Response["prophetData"]["response"]} response - The response body bytes. + * @throws {DecodeAbiParametersErrorType} + * @returns {Response["decodedData"]["response"]} Decoded response body object. + */ + static encodeDisputeStatus(status: DisputeStatus): number { + const index = DISPUTE_STATUS_ENUM.indexOf(status); - if (!disputeStatus) throw new UnknownDisputeStatus(status); - else return disputeStatus; + // TODO: throw ProphetEncodingError + return index; } } diff --git a/packages/automated-dispute/src/types/events.ts b/packages/automated-dispute/src/types/events.ts index e54db931..e182604d 100644 --- a/packages/automated-dispute/src/types/events.ts +++ b/packages/automated-dispute/src/types/events.ts @@ -1,15 +1,7 @@ import { UnixTimestamp } from "@ebo-agent/shared"; import { Address, Hex, Log } from "viem"; -import { - Dispute, - DisputeId, - DisputeStatus, - Request, - RequestId, - Response, - ResponseId, -} from "./prophet.js"; +import { Dispute, DisputeId, Request, RequestId, Response, ResponseId } from "./prophet.js"; export type EboEventName = | "RequestCreated" @@ -40,7 +32,7 @@ export interface ResponseDisputed { export interface DisputeStatusUpdated { disputeId: DisputeId; dispute: Dispute["prophetData"]; - status: DisputeStatus; + status: number; blockNumber: bigint; } diff --git a/packages/automated-dispute/src/types/prophet.ts b/packages/automated-dispute/src/types/prophet.ts index eb21b609..4116557d 100644 --- a/packages/automated-dispute/src/types/prophet.ts +++ b/packages/automated-dispute/src/types/prophet.ts @@ -96,7 +96,10 @@ export interface Dispute { blockNumber: bigint; logIndex: number; }; - status: DisputeStatus; + + decodedData: { + status: DisputeStatus; + }; prophetData: { disputer: Address; diff --git a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts index f98b93a1..91ff84ed 100644 --- a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts @@ -196,12 +196,14 @@ export function buildDispute( ): Dispute { const baseDispute: Dispute = { id: "0x01" as DisputeId, - status: "Active", createdAt: { timestamp: (response.createdAt.timestamp + 1n) as UnixTimestamp, blockNumber: response.createdAt.blockNumber + 1n, logIndex: response.createdAt.logIndex + 1, }, + decodedData: { + status: "Active", + }, prophetData: { disputer: "0x01", proposer: response.prophetData.proposer, diff --git a/packages/automated-dispute/tests/services/eboActor.spec.ts b/packages/automated-dispute/tests/services/eboActor.spec.ts index 7efa3055..85f5aa79 100644 --- a/packages/automated-dispute/tests/services/eboActor.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor.spec.ts @@ -100,6 +100,35 @@ describe("EboActor", () => { expect(queue.size()).toEqual(0); }); + it("skips malformed prophet entities", async () => { + const { actor } = mocks.buildEboActor(request, logger); + + const malformedEvent: EboEvent<"RequestCreated"> = { + ...event, + logIndex: 1, + metadata: { + ...event.metadata, + request: { ...request.prophetData, requestModuleData: "0xbad" as Hex }, + }, + }; + + const correctEvent: EboEvent<"RequestCreated"> = { + ...event, + logIndex: 2, + metadata: { + ...event.metadata, + request: { ...request.prophetData }, + }, + }; + + actor["onLastEvent"] = vi.fn().mockImplementation(() => Promise.resolve()); + + actor.enqueue(malformedEvent); + actor.enqueue(correctEvent); + + expect(actor.processEvents()).resolves.not.toThrow(); + }); + it("enqueues again an event if its processing throws", async () => { const { actor } = mocks.buildEboActor(request, logger); const queue = actor["eventsQueue"]; @@ -335,7 +364,7 @@ describe("EboActor", () => { }); const escalatedDispute = mocks.buildDispute(request, disputedResponse, { - status: "Escalated", + decodedData: { status: "Escalated" }, }); const { actor, registry } = mocks.buildEboActor(request, logger); @@ -395,7 +424,7 @@ describe("EboActor", () => { }); const escalatedDispute = mocks.buildDispute(request, disputedResponse, { - status: "Escalated", + decodedData: { status: "Escalated" }, }); const { actor, registry } = mocks.buildEboActor(request, logger); diff --git a/packages/automated-dispute/tests/services/eboActor/onDisputeStatusUpdated.spec.ts b/packages/automated-dispute/tests/services/eboActor/onDisputeStatusUpdated.spec.ts index bef3a2e3..a80cab43 100644 --- a/packages/automated-dispute/tests/services/eboActor/onDisputeStatusUpdated.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor/onDisputeStatusUpdated.spec.ts @@ -18,7 +18,10 @@ describe("onDisputeStatusUpdated", () => { const response = mocks.buildResponse(actorRequest); it("updates the state of the dispute", async () => { - const dispute = mocks.buildDispute(actorRequest, response, { status: "None" }); + const dispute = mocks.buildDispute(actorRequest, response, { + decodedData: { status: "None" }, + }); + const event: EboEvent<"DisputeStatusUpdated"> = { name: "DisputeStatusUpdated", requestId: actorRequest.id, @@ -26,7 +29,7 @@ describe("onDisputeStatusUpdated", () => { logIndex: 1, metadata: { disputeId: "0x01" as DisputeId, - status: "Lost", + status: ProphetCodec.encodeDisputeStatus("Lost"), dispute: dispute.prophetData, blockNumber: 1n, }, @@ -48,7 +51,10 @@ describe("onDisputeStatusUpdated", () => { it("proposes a new response when dispute status goes into NoResolution", async () => { const proposerAddress = "0x1234567890abcdef1234567890abcdef12345678"; - const dispute = mocks.buildDispute(actorRequest, response, { status: "Escalated" }); + const dispute = mocks.buildDispute(actorRequest, response, { + decodedData: { status: "Escalated" }, + }); + const event: EboEvent<"DisputeStatusUpdated"> = { name: "DisputeStatusUpdated", requestId: actorRequest.id, @@ -56,7 +62,7 @@ describe("onDisputeStatusUpdated", () => { logIndex: 1, metadata: { disputeId: "0x01" as DisputeId, - status: "NoResolution", + status: ProphetCodec.encodeDisputeStatus("NoResolution"), dispute: dispute.prophetData, blockNumber: 1n, }, diff --git a/packages/automated-dispute/tests/services/eboActor/onLastBlockupdated.spec.ts b/packages/automated-dispute/tests/services/eboActor/onLastBlockupdated.spec.ts index 70eeaad3..7670c60e 100644 --- a/packages/automated-dispute/tests/services/eboActor/onLastBlockupdated.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor/onLastBlockupdated.spec.ts @@ -191,7 +191,7 @@ describe("EboActor", () => { }); const firstResponseDispute = mocks.buildDispute(request, firstResponse, { - status: "Lost", + decodedData: { status: "Lost" }, createdAt: { timestamp: 5n as UnixTimestamp, blockNumber: 1001n, diff --git a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addRequest.spec.ts b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addRequest.spec.ts index 9ddc2523..b1659ddd 100644 --- a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addRequest.spec.ts +++ b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addRequest.spec.ts @@ -1,12 +1,16 @@ -import { afterEach } from "node:test"; +import { fail } from "assert"; import { UnsupportedChain } from "@ebo-agent/blocknumber"; import { UnixTimestamp } from "@ebo-agent/shared"; 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 } from "../../../../src/exceptions/index.js"; +import { + CommandAlreadyRun, + CommandNotRun, + ProphetDecodingError, +} from "../../../../src/exceptions/index.js"; import { EboRegistry } from "../../../../src/interfaces/index.js"; -import { AddRequest, ProphetCodec } from "../../../../src/services/index.js"; +import { AddRequest } from "../../../../src/services/index.js"; import { EboEvent } from "../../../../src/types/index.js"; import { buildRequest } from "../../../mocks/eboActor.mocks.js"; import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "../../../services/eboActor/fixtures.js"; @@ -34,14 +38,6 @@ describe("AddRequest", () => { addRequest: vi.fn(), removeRequest: vi.fn(), } as unknown as EboRegistry; - - vi.spyOn(ProphetCodec, "decodeRequestDisputeModuleData").mockReturnValue( - request.decodedData.disputeModuleData, - ); - - vi.spyOn(ProphetCodec, "decodeRequestResponseModuleData").mockReturnValue( - request.decodedData.responseModuleData, - ); }); afterEach(() => { @@ -78,6 +74,72 @@ describe("AddRequest", () => { ); }).toThrow(UnsupportedChain); }); + + it("throws if request module data is malformed", () => { + const malformedRequestEvent = { + ...event, + metadata: { + ...event.metadata, + request: { + ...request["prophetData"], + requestModuleData: "0xbad" as Hex, + }, + }, + }; + + try { + AddRequest.buildFromEvent(malformedRequestEvent, registry); + + fail("Expecting error"); + } catch (err) { + expect(err).toBeInstanceOf(ProphetDecodingError); + expect(err.message).toMatch("request.requestModuleData"); + } + }); + + it("throws if response module data is malformed", () => { + const malformedRequestEvent = { + ...event, + metadata: { + ...event.metadata, + request: { + ...request["prophetData"], + responseModuleData: "0xbad" as Hex, + }, + }, + }; + + try { + AddRequest.buildFromEvent(malformedRequestEvent, registry); + + fail("Expecting error"); + } catch (err) { + expect(err).toBeInstanceOf(ProphetDecodingError); + expect(err.message).toMatch("request.responseModuleData"); + } + }); + + it("throws if dispute module data is malformed", () => { + const malformedRequestEvent = { + ...event, + metadata: { + ...event.metadata, + request: { + ...request["prophetData"], + disputeModuleData: "0xbad" as Hex, + }, + }, + }; + + try { + AddRequest.buildFromEvent(malformedRequestEvent, registry); + + fail("Expecting error"); + } catch (err) { + expect(err).toBeInstanceOf(ProphetDecodingError); + expect(err.message).toMatch("request.disputeModuleData"); + } + }); }); describe("run", () => { diff --git a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addResponse.spec.ts b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addResponse.spec.ts index 5a050aac..18d5f4b5 100644 --- a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addResponse.spec.ts +++ b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addResponse.spec.ts @@ -1,6 +1,12 @@ +import { fail } from "assert"; +import { Hex } from "viem"; import { beforeEach, describe, expect, it, Mock, vi } from "vitest"; -import { CommandAlreadyRun, CommandNotRun } from "../../../../src/exceptions/index.js"; +import { + CommandAlreadyRun, + CommandNotRun, + ProphetDecodingError, +} from "../../../../src/exceptions/index.js"; import { EboRegistry } from "../../../../src/interfaces/index.js"; import { AddResponse } from "../../../../src/services/index.js"; import { EboEvent } from "../../../../src/types/index.js"; @@ -50,6 +56,28 @@ describe("AddResponse", () => { expect(() => command.run()).toThrow(CommandAlreadyRun); }); + + it("throws if response is malformed", () => { + const malformedResponseEvent = { + ...event, + metadata: { + ...event.metadata, + response: { + ...response["prophetData"], + response: "0xbad" as Hex, + }, + }, + }; + + try { + AddResponse.buildFromEvent(malformedResponseEvent, registry); + + fail("Expecting error"); + } catch (err) { + expect(err).toBeInstanceOf(ProphetDecodingError); + expect(err.message).toMatch("response.response"); + } + }); }); describe("undo", () => { diff --git a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/updateDisputeStatus.spec.ts b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/updateDisputeStatus.spec.ts index fd6c2b7f..68d2a772 100644 --- a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/updateDisputeStatus.spec.ts +++ b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/updateDisputeStatus.spec.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { CommandAlreadyRun, CommandNotRun } from "../../../../src/exceptions/index.js"; import { EboRegistry } from "../../../../src/interfaces/index.js"; -import { UpdateDisputeStatus } from "../../../../src/services/index.js"; +import { ProphetCodec, UpdateDisputeStatus } 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"; @@ -23,7 +23,9 @@ describe("UpdateDisputeStatus", () => { blockNumber: 1n, dispute: dispute.prophetData, disputeId: dispute.id, - status: dispute.status === "Active" ? "Lost" : "Active", + status: ProphetCodec.encodeDisputeStatus( + dispute.decodedData.status === "Active" ? "Lost" : "Active", + ), }, }; @@ -42,7 +44,7 @@ describe("UpdateDisputeStatus", () => { expect(registry.updateDisputeStatus).toHaveBeenCalledWith( event.metadata.disputeId, - event.metadata.status, + ProphetCodec.decodeDisputeStatus(event.metadata.status), ); }); @@ -80,7 +82,7 @@ describe("UpdateDisputeStatus", () => { it("reverts the dispute status to the previous status", () => { const command = UpdateDisputeStatus.buildFromEvent(event, registry); - const previousStatus = dispute.status; + const previousStatus = dispute.decodedData.status; command.run(); command.undo(); diff --git a/packages/automated-dispute/tests/services/prophetCodec.spec.ts b/packages/automated-dispute/tests/services/prophetCodec.spec.ts index b9767001..388e38b4 100644 --- a/packages/automated-dispute/tests/services/prophetCodec.spec.ts +++ b/packages/automated-dispute/tests/services/prophetCodec.spec.ts @@ -1,7 +1,7 @@ import { isHex } from "viem"; import { describe, expect, it } from "vitest"; -import { UnknownDisputeStatus } from "../../src/exceptions"; +import { ProphetDecodingError } from "../../src/exceptions"; import { ProphetCodec } from "../../src/services"; import { DisputeStatus, Response } from "../../src/types"; @@ -43,23 +43,66 @@ describe("ProphetCodec", () => { }); }); - it("throws UnknownDisputeStatus for invalid status", () => { + it("throws ProphetDecodingError for invalid status", () => { const invalidStatuses = [-1, 6, 999]; invalidStatuses.forEach((status) => { expect(() => { ProphetCodec.decodeDisputeStatus(status); - }).toThrow(UnknownDisputeStatus); + }).toThrow(ProphetDecodingError); }); }); }); - describe.todo("decodeResponse"); + describe("decodeResponse", () => { + it.todo("decodes data succesfully"); + + it("throws when response is malformed", () => { + const malformedData = "0xbad"; + + expect(() => { + ProphetCodec.decodeResponse(malformedData); + }).toThrow(ProphetDecodingError); + }); + }); + + describe("decodeRequestRequestModuleData", () => { + it.todo("decodes data successfully"); + + it("throws when request module data is malformed", () => { + const malformedData = "0xbad"; + + expect(() => { + ProphetCodec.decodeRequestRequestModuleData(malformedData); + }).toThrow(ProphetDecodingError); + }); + }); + + describe("decodeRequestResponseModuleData", () => { + it.todo("decodes data succesfully"); + + it("throws when response module data is malformed", () => { + const malformedData = "0xbad"; + + expect(() => { + ProphetCodec.decodeRequestResponseModuleData(malformedData); + }).toThrow(ProphetDecodingError); + }); + }); + + describe("decodeRequestDisputeModuleData", () => { + it.todo("decodes data succesfully"); + + it("throws when dispute module data is malformed", () => { + const malformedData = "0xbad"; + + expect(() => { + ProphetCodec.decodeRequestDisputeModuleData(malformedData); + }).toThrow(ProphetDecodingError); + }); + }); - describe.todo("decodeRequestRequestModuleData"); - describe.todo("encodeRequestRequestModuleData"); - describe.todo("decodeRequestResponseModuleData"); describe.todo("encodeRequestResponseModuleData"); - describe.todo("decodeRequestDisputeModuleData"); + describe.todo("encodeRequestRequestModuleData"); describe.todo("encodeRequestDisputeModuleData"); }); diff --git a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts index 3d835d95..ce6d1393 100644 --- a/packages/blocknumber/src/providers/evmBlockNumberProvider.ts +++ b/packages/blocknumber/src/providers/evmBlockNumberProvider.ts @@ -1,4 +1,4 @@ -import { ILogger, UnixTimestamp } from "@ebo-agent/shared"; +import { ILogger, stringify, UnixTimestamp } from "@ebo-agent/shared"; import { BigNumber } from "bignumber.js"; import { Block, BlockNotFoundError, FallbackTransport, HttpTransport, PublicClient } from "viem"; @@ -213,6 +213,7 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { if (low > high) throw new UnexpectedSearchRange(low, high); this.logger.debug(`Starting block binary search for timestamp ${timestamp}...`); + this.logger.debug(stringify({ low, high })); while (low <= high) { currentBlockNumber = (high + low) / 2n; @@ -224,6 +225,19 @@ export class EvmBlockNumberProvider implements BlockNumberProvider { `Analyzing block number #${currentBlock.number} with timestamp ${currentBlock.timestamp}`, ); + this.logger.debug( + stringify({ + currentBlock: { + number: currentBlock.number, + timestamp: currentBlock.timestamp, + }, + nextBlock: { + number: nextBlock?.number, + timestamp: nextBlock?.timestamp, + }, + }), + ); + // If no next block with a different timestamp is defined to ensure that the // searched timestamp is between two blocks, it won't be possible to answer. // diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 4cfe887e..45c6bad8 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -6,6 +6,7 @@ export const EBO_SUPPORTED_CHAINS_CONFIG = { namespace: "eip155", references: { ethereum: "1", + sepolia: "11155111", polygon: "137", arbitrum: "42161", arbitrumSepolia: "421614",