diff --git a/apps/agent/config.tenderly.yml b/apps/agent/config.tenderly.yml new file mode 100644 index 00000000..d8a082f2 --- /dev/null +++ b/apps/agent/config.tenderly.yml @@ -0,0 +1,32 @@ +protocolProvider: + rpcsConfig: + l1: + chainId: eip155:11155111 + transactionReceiptConfirmations: 1 + timeout: 10000 + retryInterval: 150 + l2: + chainId: eip155:421614 + transactionReceiptConfirmations: 1 + timeout: 10000 + retryInterval: 150 + contracts: + oracle: "0x10224eff6B1Caaf5daC49B2e7104b7161484B128" + epochManager: "0x7975475801BEf845f10Ce7784DC69aB1e0344f11" + eboRequestCreator: "0xa13318684281a820304C164427396385C306d870" + bondEscalationModule: "0x52d7728fE87826FfF51b21b303e2FF7cB04F6Aec" + horizonAccountingExtension: "0xbDAB27D1903da4e18B0D1BE873E18924514E52eC" + +blockNumberService: + blockmetaConfig: + baseUrl: "localhost:443" + servicePaths: + blockByTime: /sf.blockmeta.v2.BlockByTime + block: /sf.blockmeta.v2.Block + bearerTokenExpirationWindow: 31536000000 + +processor: + msBetweenChecks: 7500 + accountingModules: + responseModule: "0xb97C59331F89a852Ae21aee215Da28820c533649" + escalationModule: "0x52d7728fE87826FfF51b21b303e2FF7cB04F6Aec" 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/interfaces/eboRegistryCommand.ts b/packages/automated-dispute/src/interfaces/eboRegistryCommand.ts index 0108e3ae..32e302cf 100644 --- a/packages/automated-dispute/src/interfaces/eboRegistryCommand.ts +++ b/packages/automated-dispute/src/interfaces/eboRegistryCommand.ts @@ -1,4 +1,9 @@ export interface EboRegistryCommand { + /** + * Return the command name + */ + name(): string; + /** * Run a command to update the registry */ diff --git a/packages/automated-dispute/src/providers/protocolProvider.ts b/packages/automated-dispute/src/providers/protocolProvider.ts index 704180c5..505a5363 100644 --- a/packages/automated-dispute/src/providers/protocolProvider.ts +++ b/packages/automated-dispute/src/providers/protocolProvider.ts @@ -51,7 +51,6 @@ import { RpcUrlsEmpty, TransactionExecutionError, } from "../exceptions/index.js"; -import { ProphetCodec } from "../external.js"; import { IProtocolProvider, IReadProvider, @@ -470,7 +469,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">; @@ -528,7 +527,6 @@ export class ProtocolProvider implements IProtocolProvider { requestId: HexUtils.normalize(_dispute.requestId) as RequestId, }, caller: _caller as Address, - blockNumber: event.blockNumber, }, } as EboEvent<"DisputeEscalated">; }), diff --git a/packages/automated-dispute/src/services/eboActor.ts b/packages/automated-dispute/src/services/eboActor.ts index 9e2e2f60..dc7046fa 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, @@ -35,6 +36,7 @@ import { AddRequest, AddResponse, FinalizeRequest, + NoOp, ProphetCodec, UpdateDisputeStatus, } from "../services/index.js"; @@ -149,12 +151,38 @@ 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); + + this.logger.debug("Running command..."); + this.logger.debug(stringify({ command: updateStateCommand.name() })); + + updateStateCommand.run(); + + this.logger.debug("Command run successfully."); + } 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()) { + const wasLastEvent = this.eventsQueue.isEmpty(); + const isDisputeEscalatedEvent = event.name === "DisputeEscalated"; + + if (wasLastEvent || isDisputeEscalatedEvent) { // `event` is the last and most recent event thus // it needs to run some RPCs to keep Prophet's flow going on await this.onLastEvent(event); @@ -207,11 +235,23 @@ export class EboActor { this.registry, ); - case "ResponseDisputed": - return AddDispute.buildFromEvent( - event as EboEvent<"ResponseDisputed">, - this.registry, - ); + case "ResponseDisputed": { + const disputeId = (event as EboEvent<"ResponseDisputed">).metadata.disputeId; + const dispute = this.registry.getDispute(disputeId); + + // Prophet's might emit the DisputeEscalated event prior the ResponseDisputed + // event (starting from the 2nd dispute within an EBO request due to the + // BondEscalationModule behavior). + // + // This force the agent to add the Dispute when processing the DisputeEscalated + // event, causing this event to be a no-op. + return dispute + ? NoOp.build() + : AddDispute.buildFromEvent( + event as EboEvent<"ResponseDisputed">, + this.registry, + ); + } case "DisputeStatusUpdated": return UpdateDisputeStatus.buildFromEvent( @@ -219,11 +259,20 @@ export class EboActor { this.registry, ); - case "DisputeEscalated": - return UpdateDisputeStatus.buildFromEvent( - event as EboEvent<"DisputeEscalated">, - this.registry, - ); + case "DisputeEscalated": { + const disputeId = (event as EboEvent<"DisputeEscalated">).metadata.disputeId; + const dispute = this.registry.getDispute(disputeId); + + return dispute + ? UpdateDisputeStatus.buildFromEvent( + event as EboEvent<"DisputeEscalated">, + this.registry, + ) + : AddDispute.buildFromEvent( + event as EboEvent<"DisputeEscalated">, + this.registry, + ); + } case "OracleRequestFinalized": return FinalizeRequest.buildFromEvent( @@ -391,12 +440,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 + @@ -494,7 +543,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; } /** @@ -543,7 +592,7 @@ export class EboActor { // the proposal non-active. const activeStatus: DisputeStatus[] = ["None", "Active"]; - return activeStatus.includes(dispute.status); + return activeStatus.includes(dispute.decodedData.status); }); } @@ -804,6 +853,12 @@ export class EboActor { `Dispute ${event.metadata.disputeId} needs to be added to the internal registry.`, ); + if (dispute.decodedData.status === "Escalated") { + this.logger.warn(`Skipping dispute ${dispute.id} as it's already been escalated`); + + return; + } + const request = this.getActorRequest(); const proposedResponse = this.registry.getResponse(event.metadata.responseId); @@ -936,7 +991,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}.`); @@ -966,6 +1021,7 @@ export class EboActor { private async onDisputeEscalated(event: EboEvent<"DisputeEscalated">) { const request = this.getActorRequest(); + this.logger.info( `Dispute ${event.metadata.disputeId} for request ${request.id} has been escalated.`, ); 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..9d61e508 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/addDispute.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/addDispute.ts @@ -11,7 +11,7 @@ export class AddDispute implements EboRegistryCommand { ) {} public static buildFromEvent( - event: EboEvent<"ResponseDisputed">, + event: EboEvent<"ResponseDisputed" | "DisputeEscalated">, registry: EboRegistry, ): AddDispute { const dispute: Dispute = { @@ -21,13 +21,19 @@ export class AddDispute implements EboRegistryCommand { blockNumber: event.blockNumber, logIndex: event.logIndex, }, - status: "Active", + decodedData: { + status: event.name === "ResponseDisputed" ? "Active" : "Escalated", + }, prophetData: event.metadata.dispute, }; return new AddDispute(registry, dispute); } + name(): string { + return "AddDispute"; + } + run(): void { if (this.wasRun) throw new CommandAlreadyRun(AddDispute.name); diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/addRequest.ts b/packages/automated-dispute/src/services/eboRegistry/commands/addRequest.ts index ee9f6f5f..05a2717b 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/addRequest.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/addRequest.ts @@ -55,6 +55,10 @@ export class AddRequest implements EboRegistryCommand { return new AddRequest(registry, request); } + name(): string { + return "AddRequest"; + } + run(): void { if (this.wasRun) throw new CommandAlreadyRun(AddRequest.name); diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/addResponse.ts b/packages/automated-dispute/src/services/eboRegistry/commands/addResponse.ts index a3487932..7717fcd9 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/addResponse.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/addResponse.ts @@ -31,6 +31,10 @@ export class AddResponse implements EboRegistryCommand { return new AddResponse(registry, response); } + name(): string { + return "AddResponse"; + } + run(): void { if (this.wasRun) throw new CommandAlreadyRun(AddResponse.name); diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/finalizeRequest.ts b/packages/automated-dispute/src/services/eboRegistry/commands/finalizeRequest.ts index dca40859..48433a79 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/finalizeRequest.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/finalizeRequest.ts @@ -23,6 +23,10 @@ export class FinalizeRequest implements EboRegistryCommand { return new FinalizeRequest(registry, request); } + name(): string { + return "FinalizeRequest"; + } + run(): void { if (this.wasRun) throw new CommandAlreadyRun(FinalizeRequest.name); diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/index.ts b/packages/automated-dispute/src/services/eboRegistry/commands/index.ts index aa77846f..81974346 100644 --- a/packages/automated-dispute/src/services/eboRegistry/commands/index.ts +++ b/packages/automated-dispute/src/services/eboRegistry/commands/index.ts @@ -2,4 +2,5 @@ export * from "./addDispute.js"; export * from "./addRequest.js"; export * from "./addResponse.js"; export * from "./finalizeRequest.js"; +export * from "./noOp.js"; export * from "./updateDisputeStatus.js"; diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/noOp.ts b/packages/automated-dispute/src/services/eboRegistry/commands/noOp.ts new file mode 100644 index 00000000..06016982 --- /dev/null +++ b/packages/automated-dispute/src/services/eboRegistry/commands/noOp.ts @@ -0,0 +1,26 @@ +import { CommandAlreadyRun, CommandNotRun } from "../../../exceptions/index.js"; +import { EboRegistryCommand } from "../../../interfaces/index.js"; + +export class NoOp implements EboRegistryCommand { + private wasRun: boolean = false; + + private constructor() {} + + public static build(): NoOp { + return new NoOp(); + } + + name(): string { + return "NoOp"; + } + + run(): void { + if (this.wasRun) throw new CommandAlreadyRun(NoOp.name); + + this.wasRun = true; + } + + undo(): void { + if (!this.wasRun) throw new CommandNotRun(NoOp.name); + } +} diff --git a/packages/automated-dispute/src/services/eboRegistry/commands/updateDisputeStatus.ts b/packages/automated-dispute/src/services/eboRegistry/commands/updateDisputeStatus.ts index 40b0a209..680ecad6 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); @@ -31,6 +32,10 @@ export class UpdateDisputeStatus implements EboRegistryCommand { return event.name === "DisputeStatusUpdated"; } + name(): string { + return "UpdateDisputeStatus"; + } + run(): void { if (this.wasRun) throw new CommandAlreadyRun(UpdateDisputeStatus.name); @@ -38,7 +43,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..b1327e82 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,21 +32,19 @@ export interface ResponseDisputed { export interface DisputeStatusUpdated { disputeId: DisputeId; dispute: Dispute["prophetData"]; - status: DisputeStatus; - blockNumber: bigint; + status: number; } export interface DisputeEscalated { caller: Address; disputeId: DisputeId; - blockNumber: bigint; + dispute: Dispute["prophetData"]; } export interface OracleRequestFinalized { requestId: RequestId; responseId: ResponseId; caller: Address; - blockNumber: bigint; } export type EboEventData = E extends "RequestCreated" diff --git a/packages/automated-dispute/src/types/prophet.ts b/packages/automated-dispute/src/types/prophet.ts index 2670814d..bc1e4d62 100644 --- a/packages/automated-dispute/src/types/prophet.ts +++ b/packages/automated-dispute/src/types/prophet.ts @@ -105,7 +105,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 0473cc4a..ac3751da 100644 --- a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts @@ -99,13 +99,9 @@ export function buildEboActor(request: Request, logger: ILogger) { const eventProcessingMutex = new Mutex(); - let notificationService: NotificationService | undefined; - - if (!notificationService) { - notificationService = { - notifyError: vi.fn().mockResolvedValue(undefined), - }; - } + const notificationService: NotificationService = { + notifyError: vi.fn().mockResolvedValue(undefined), + }; const actor = new EboActor( { id, epoch, chainId }, @@ -124,6 +120,7 @@ export function buildEboActor(request: Request, logger: ILogger) { registry, eventProcessingMutex, logger, + notificationService, }; } @@ -208,6 +205,9 @@ export function buildDispute( 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 ed4be0f9..7104c902 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/onDisputeEscalated.spec.ts b/packages/automated-dispute/tests/services/eboActor/onDisputeEscalated.spec.ts new file mode 100644 index 00000000..cc7d8ebb --- /dev/null +++ b/packages/automated-dispute/tests/services/eboActor/onDisputeEscalated.spec.ts @@ -0,0 +1,107 @@ +import { ILogger } from "@ebo-agent/shared"; +import { Address } from "viem"; +import { describe, expect, it, vi } from "vitest"; + +import { EboEvent } from "../../../src/types/index.js"; +import mocks from "../../mocks/index.ts"; +import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.ts"; + +const logger: ILogger = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), +}; + +describe("onDisputeEscalated", () => { + const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; + const response = mocks.buildResponse(actorRequest); + + it("creates the dispute if it does not exist", async () => { + const dispute = mocks.buildDispute(actorRequest, response, { + decodedData: { status: "Active" }, + }); + + const event: EboEvent<"DisputeEscalated"> = { + name: "DisputeEscalated", + requestId: actorRequest.id, + blockNumber: 1n, + logIndex: 1, + metadata: { + caller: "0x01" as Address, + disputeId: dispute.id, + dispute: dispute.prophetData, + }, + }; + + const { actor, registry } = mocks.buildEboActor(actorRequest, logger); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getDispute").mockReturnValue(undefined); + + const mockAddDispute = vi.spyOn(registry, "addDispute"); + + actor.enqueue(event); + + await actor.processEvents(); + + expect(mockAddDispute).toHaveBeenCalledWith( + expect.objectContaining({ + id: dispute.id, + decodedData: { + status: "Escalated", + }, + }), + ); + }); + + it("logs even when there is a ResponseDisputed event after", async () => { + const dispute = mocks.buildDispute(actorRequest, response, { + decodedData: { status: "Escalated" }, + }); + + const disputeEscalatedEvent: EboEvent<"DisputeEscalated"> = { + name: "DisputeEscalated", + requestId: actorRequest.id, + blockNumber: 1n, + logIndex: 1, + metadata: { + caller: "0x01" as Address, + disputeId: dispute.id, + dispute: dispute.prophetData, + }, + }; + + const responseDisputedEvent: EboEvent<"ResponseDisputed"> = { + name: "ResponseDisputed", + requestId: actorRequest.id, + blockNumber: disputeEscalatedEvent.blockNumber, + logIndex: disputeEscalatedEvent.logIndex + 1, + metadata: { + disputeId: dispute.id, + responseId: response.id, + dispute: dispute.prophetData, + }, + }; + + const { actor, registry } = mocks.buildEboActor(actorRequest, logger); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); + vi.spyOn(registry, "getDispute").mockReturnValueOnce(undefined).mockReturnValue(dispute); + + vi.spyOn(registry, "addDispute").mockImplementation(() => {}); + + // Simulating the DisputeEscalated event coming before the ResponseDisputed event + actor.enqueue(disputeEscalatedEvent); + actor.enqueue(responseDisputedEvent); + + await actor.processEvents(); + + expect(logger.info).toHaveBeenCalledWith( + `Dispute ${dispute.id} for request ${actorRequest.id} has been escalated.`, + ); + }); + + it.todo("notifies whenever a DisputeEscalated event is processed"); +}); diff --git a/packages/automated-dispute/tests/services/eboActor/onDisputeStatusUpdated.spec.ts b/packages/automated-dispute/tests/services/eboActor/onDisputeStatusUpdated.spec.ts index 1320138b..50846297 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, @@ -27,8 +30,8 @@ describe("onDisputeStatusUpdated", () => { metadata: { disputeId: dispute.id, 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 d3c5095a..391cd252 100644 --- a/packages/automated-dispute/tests/services/eboActor/onLastBlockupdated.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor/onLastBlockupdated.spec.ts @@ -199,7 +199,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/eboActor/onRequestFinalized.spec.ts b/packages/automated-dispute/tests/services/eboActor/onRequestFinalized.spec.ts index be6cd281..7257d972 100644 --- a/packages/automated-dispute/tests/services/eboActor/onRequestFinalized.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor/onRequestFinalized.spec.ts @@ -48,6 +48,7 @@ describe("EboActor", () => { vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); const mockFinalizeRequest = { + name: vi.fn(), run: vi.fn(), undo: vi.fn(), } as unknown as FinalizeRequest; diff --git a/packages/automated-dispute/tests/services/eboActor/onResponseDisputed.spec.ts b/packages/automated-dispute/tests/services/eboActor/onResponseDisputed.spec.ts index 2ba21275..1d4fc91a 100644 --- a/packages/automated-dispute/tests/services/eboActor/onResponseDisputed.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor/onResponseDisputed.spec.ts @@ -115,6 +115,32 @@ describe("onResponseDisputed", () => { expect(addResponseMock).toHaveBeenCalled(); }); + it("skips event processing if dispute was already escalated", async () => { + const { actor, registry, protocolProvider } = mocks.buildEboActor(actorRequest, logger); + + const dispute = mocks.buildDispute(actorRequest, response, { + decodedData: { status: "Escalated" }, + }); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); + vi.spyOn(registry, "getDispute").mockReturnValue(dispute); + + const mockPledgeFor = vi.spyOn(protocolProvider, "pledgeForDispute"); + const mockPledgeAgainst = vi.spyOn(protocolProvider, "pledgeAgainstDispute"); + + actor.enqueue(event); + + await actor.processEvents(); + + expect(logger.warn).toHaveBeenCalledWith( + `Skipping dispute ${dispute.id} as it's already been escalated`, + ); + + expect(mockPledgeFor).not.toHaveBeenCalled(); + expect(mockPledgeAgainst).not.toHaveBeenCalled(); + }); + // TODO: handle when error handling of reverts is implemented it.todo("resolves if the pledge is reverted"); it.todo("throws if protocol provider cannot complete pledge"); diff --git a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addDispute.spec.ts b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addDispute.spec.ts index 63bf7b18..7c8b6963 100644 --- a/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addDispute.spec.ts +++ b/packages/automated-dispute/tests/services/eboMemoryRegistry/commands/addDispute.spec.ts @@ -1,3 +1,4 @@ +import { Address } from "viem"; import { beforeEach, describe, expect, it, Mock, vi } from "vitest"; import { CommandAlreadyRun, CommandNotRun } from "../../../../src/exceptions/index.js"; @@ -46,6 +47,31 @@ describe("AddDispute", () => { ); }); + it("adds the dispute when it was escalated first", () => { + const disputeEscalatedEvent: EboEvent<"DisputeEscalated"> = { + ...event, + name: "DisputeEscalated", + metadata: { + caller: "0x01" as Address, + disputeId: dispute.id, + dispute: dispute.prophetData, + }, + }; + + const command = AddDispute.buildFromEvent(disputeEscalatedEvent, registry); + + command.run(); + + expect(registry.addDispute).toHaveBeenCalledWith( + expect.objectContaining({ + id: dispute.id, + decodedData: { + status: "Escalated", + }, + }), + ); + }); + it("throws if the command was already run", () => { const command = AddDispute.buildFromEvent(event, registry); 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 3a95bb52..54c820f4 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",