From 53146817a66e0bd0ac62dff99f544b42b932e026 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Fri, 9 Aug 2024 12:28:44 -0300 Subject: [PATCH 1/5] feat: implement EboActor.onProposeDisputed handler --- packages/automated-dispute/src/eboActor.ts | 96 +++++++- .../src/eboMemoryRegistry.ts | 12 +- .../src/interfaces/eboRegistry.ts | 20 +- .../automated-dispute/src/protocolProvider.ts | 10 +- .../automated-dispute/src/types/events.ts | 4 +- .../automated-dispute/src/types/prophet.ts | 31 ++- .../tests/eboActor/fixtures.ts | 2 +- .../tests/eboActor/onResponseDisputed.spec.ts | 226 ++++++++++++++++++ 8 files changed, 379 insertions(+), 22 deletions(-) create mode 100644 packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index 5b92ab0..a259e77 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -199,19 +199,97 @@ export class EboActor { } } - public async onResponseDisputed(_event: EboEvent<"ResponseDisputed">): Promise { - // TODO: implement - return; + /** + * Returns the active actor request. + * + * @throws {InvalidActorState} when the request has not been added to the registry yet. + * @returns the actor `Request` + */ + private getActorRequest() { + const request = this.registry.getRequest(this.actorRequest.id); + + if (request === undefined) throw new InvalidActorState(); + + return request; } - private async disputeProposal(_dispute: Dispute): Promise { - // TODO: implement - return; + /** + * Handle the `ResponseDisputed` event. + * + * @param event `ResponseDisputed` event. + */ + public async onResponseDisputed(event: EboEvent<"ResponseDisputed">): Promise { + this.shouldHandleRequest(event.metadata.dispute.requestId); + + const dispute: Dispute = { + id: event.metadata.disputeId, + status: "Active", + prophetData: event.metadata.dispute, + }; + + this.registry.addDispute(event.metadata.disputeId, dispute); + + const request = this.getActorRequest(); + const proposedResponse = this.registry.getResponse(event.metadata.responseId); + + if (!proposedResponse) throw new InvalidActorState(); + + const isValidDispute = await this.isValidDispute(proposedResponse); + + if (isValidDispute) await this.pledgeFor(request, dispute); + else await this.pledgeAgainst(request, dispute); } - private async isValidDispute(_dispute: Dispute): Promise { - // TODO: implement - return true; + private async isValidDispute(proposedResponse: Response) { + const actorResponse = await this.buildResponse( + proposedResponse.prophetData.response.chainId, + ); + + const equalResponses = this.equalResponses( + actorResponse, + proposedResponse.prophetData.response, + ); + + return !equalResponses; + } + + private async pledgeFor(request: Request, dispute: Dispute) { + try { + this.logger.info(`Pledging against dispute ${dispute.id}`); + + await this.protocolProvider.pledgeForDispute(request.prophetData, dispute.prophetData); + } catch (err) { + if (err instanceof ContractFunctionRevertedError) { + this.logger.warn(`Pledging for dispute ${dispute.id} was reverted. Skipping...`); + } else { + this.logger.error( + `Actor handling request ${this.actorRequest.id} is not able to continue.`, + ); + + throw err; + } + } + } + + private async pledgeAgainst(request: Request, dispute: Dispute) { + try { + this.logger.info(`Pledging for dispute ${dispute.id}`); + + await this.protocolProvider.pledgeAgainstDispute( + request.prophetData, + dispute.prophetData, + ); + } catch (err) { + if (err instanceof ContractFunctionRevertedError) { + this.logger.warn(`Pledging on dispute ${dispute.id} was reverted. Skipping...`); + } else { + this.logger.error( + `Actor handling request ${this.actorRequest.id} is not able to continue.`, + ); + + throw err; + } + } } public async onFinalizeRequest(_event: EboEvent<"RequestFinalizable">): Promise { diff --git a/packages/automated-dispute/src/eboMemoryRegistry.ts b/packages/automated-dispute/src/eboMemoryRegistry.ts index 5da93a2..24640f2 100644 --- a/packages/automated-dispute/src/eboMemoryRegistry.ts +++ b/packages/automated-dispute/src/eboMemoryRegistry.ts @@ -5,7 +5,7 @@ export class EboMemoryRegistry implements EboRegistry { constructor( private requests: Map = new Map(), private responses: Map = new Map(), - private dispute: Map = new Map(), + private disputes: Map = new Map(), ) {} /** @inheritdoc */ @@ -27,4 +27,14 @@ export class EboMemoryRegistry implements EboRegistry { public getResponses() { return this.responses; } + + /** @inheritdoc */ + public getResponse(responseId: string): Response | undefined { + return this.responses.get(responseId); + } + + /** @inheritdoc */ + public addDispute(disputeId: string, dispute: Dispute): void { + this.disputes.set(disputeId, dispute); + } } diff --git a/packages/automated-dispute/src/interfaces/eboRegistry.ts b/packages/automated-dispute/src/interfaces/eboRegistry.ts index 8d32e46..143f0db 100644 --- a/packages/automated-dispute/src/interfaces/eboRegistry.ts +++ b/packages/automated-dispute/src/interfaces/eboRegistry.ts @@ -1,4 +1,4 @@ -import { Request, Response } from "../types/prophet.js"; +import { Dispute, DisputeStatus, Request, Response } from "../types/prophet.js"; /** Registry that stores Prophet entities (ie. requests, responses and disputes) */ export interface EboRegistry { @@ -22,7 +22,7 @@ export interface EboRegistry { * Add a `Response` by ID. * * @param responseId the ID of the `Response` to use as index - * @param response the `Resopnse` + * @param response the `Response` */ addResponse(responseId: string, response: Response): void; @@ -32,4 +32,20 @@ export interface EboRegistry { * @returns responses map */ getResponses(): Map; + + /** + * Get a `Response` by ID. + * + * @param responseId response ID + * @returns the `Response` if already added into registry, `undefined` otherwise + */ + getResponse(responseId: string): Response | undefined; + + /** + * Add a dispute by ID. + * + * @param disputeId the ID of the `Dispute` to use as index + * @param dispute the `Dispute` + */ + addDispute(disputeId: string, dispute: Dispute): void; } diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index b6c80ab..76a178d 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -211,12 +211,18 @@ export class ProtocolProvider { return; } - async pledgeForDispute(_request: Request, _dispute: Dispute): Promise { + async pledgeForDispute( + _request: Request["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise { // TODO: implement actual method return; } - async pledgeAgaintsDispute(_request: Request, _dispute: Dispute): Promise { + async pledgeAgainstDispute( + _request: Request["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise { // TODO: implement actual method return; } diff --git a/packages/automated-dispute/src/types/events.ts b/packages/automated-dispute/src/types/events.ts index 31cf2e0..1ebca59 100644 --- a/packages/automated-dispute/src/types/events.ts +++ b/packages/automated-dispute/src/types/events.ts @@ -32,9 +32,9 @@ export interface RequestCreated { } export interface ResponseDisputed { - requestId: string; responseId: string; - dispute: Dispute; + disputeId: string; + dispute: Dispute["prophetData"]; } export interface DisputeStatusChanged { diff --git a/packages/automated-dispute/src/types/prophet.ts b/packages/automated-dispute/src/types/prophet.ts index c7f2364..5951948 100644 --- a/packages/automated-dispute/src/types/prophet.ts +++ b/packages/automated-dispute/src/types/prophet.ts @@ -14,11 +14,32 @@ export interface Response { proposer: Address; requestId: string; - // To be byte-encode when sending it to Prophet - response: { - chainId: Caip2ChainId; // TODO: Pending on-chain definition on CAIP-2 usage - block: bigint; - epoch: bigint; + prophetData: Readonly<{ + proposer: Address; + requestId: string; + + // To be byte-encode when sending it to Prophet + response: { + chainId: Caip2ChainId; // TODO: Pending on-chain definition on CAIP-2 usage + block: bigint; + epoch: bigint; + }; + }>; +} + +export type ResponseBody = Response["prophetData"]["response"]; + +export type DisputeStatus = "None" | "Active" | "Escalated" | "Won" | "Lost" | "NoResolution"; + +export interface Dispute { + id: string; + status: DisputeStatus; + + prophetData: { + disputer: Address; + proposer: Address; + responseId: string; + requestId: string; }; } diff --git a/packages/automated-dispute/tests/eboActor/fixtures.ts b/packages/automated-dispute/tests/eboActor/fixtures.ts index c6a4a83..c5c114d 100644 --- a/packages/automated-dispute/tests/eboActor/fixtures.ts +++ b/packages/automated-dispute/tests/eboActor/fixtures.ts @@ -1,6 +1,6 @@ import { Address } from "viem"; -import { Request } from "../../src/types/prophet"; +import { Request, Response } from "../../src/types/prophet"; export const DEFAULT_MOCKED_PROTOCOL_CONTRACTS = { oracle: "0x123456" as Address, diff --git a/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts b/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts new file mode 100644 index 0000000..ca9e721 --- /dev/null +++ b/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts @@ -0,0 +1,226 @@ +import { BlockNumberService } from "@ebo-agent/blocknumber"; +import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; +import { ILogger } from "@ebo-agent/shared"; +import { Address, ContractFunctionRevertedError } from "viem"; +import { describe, expect, it, vi } from "vitest"; + +import { EboActor } from "../../src/eboActor"; +import { EboMemoryRegistry } from "../../src/eboMemoryRegistry"; +import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception"; +import { ProtocolProvider } from "../../src/protocolProvider"; +import { EboEvent } from "../../src/types/events"; +import { Response } from "../../src/types/prophet"; +import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures"; + +const logger: ILogger = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), +}; + +describe("onResponseDisputed", () => { + const event: EboEvent<"ResponseDisputed"> = { + name: "ResponseDisputed", + blockNumber: 1n, + logIndex: 1, + metadata: { + disputeId: "0x03", + responseId: "0x02", + dispute: { + requestId: "0x01", + responseId: "0x02", + disputer: "0x11", + proposer: "0x12", + }, + }, + }; + + it("pledges for dispute if proposal should be different", async () => { + const mockEpochBlockNumber = 1n; + + const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ + requestId: "0x01", + indexedChainId: "eip155:1", + mockActorResponse: { + mockBlockNumber: mockEpochBlockNumber, + mockEpoch: 1n, + }, + }); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( + mockEpochBlockNumber + 1n, + ); + + const mockPledgeForDispute = vi.spyOn(protocolProvider, "pledgeForDispute"); + + await actor.onResponseDisputed(event); + + expect(mockPledgeForDispute).toHaveBeenCalled(); + }); + + it("pledges against dispute if proposal is ok", async () => { + const mockEpochBlockNumber = 1n; + + const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ + requestId: "0x01", + indexedChainId: "eip155:1", + mockActorResponse: { + mockBlockNumber: mockEpochBlockNumber, + mockEpoch: 1n, + }, + }); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(mockEpochBlockNumber); + + const mockPledgeForDispute = vi.spyOn(protocolProvider, "pledgeAgainstDispute"); + + await actor.onResponseDisputed(event); + + expect(mockPledgeForDispute).toHaveBeenCalled(); + }); + + it("adds dispute to registry", async () => { + const { actor, registry } = scaffoldEboActor({ + requestId: "0x01", + indexedChainId: "eip155:1", + mockActorResponse: { + mockBlockNumber: 1n, + mockEpoch: 1n, + }, + }); + + const addResponseMock = vi.spyOn(registry, "addDispute"); + + await actor.onResponseDisputed(event); + + expect(addResponseMock).toHaveBeenCalled(); + }); + + it("resolves if the pledge is reverted", async () => { + const mockEpochBlockNumber = 1n; + + const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ + requestId: "0x01", + indexedChainId: "eip155:1", + mockActorResponse: { + mockBlockNumber: mockEpochBlockNumber, + mockEpoch: 1n, + }, + }); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( + mockEpochBlockNumber + 1n, + ); + + vi.spyOn(protocolProvider, "pledgeForDispute").mockRejectedValue( + Object.create(ContractFunctionRevertedError.prototype), + ); + + expect(actor.onResponseDisputed(event)).resolves.toBeUndefined(); + }); + + it("throws if protocol provider cannot complete pledge", () => { + const mockEpochBlockNumber = 1n; + + const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ + requestId: "0x01", + indexedChainId: "eip155:1", + mockActorResponse: { + mockBlockNumber: mockEpochBlockNumber, + mockEpoch: 1n, + }, + }); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( + mockEpochBlockNumber + 1n, + ); + + vi.spyOn(protocolProvider, "pledgeForDispute").mockRejectedValue(new Error()); + + expect(actor.onResponseDisputed(event)).rejects.toThrow(); + }); + + it("throws if the response's request is not handled by actor", () => { + const { actor } = scaffoldEboActor({ + requestId: "0x01", + indexedChainId: "eip155:1", + mockActorResponse: { + mockBlockNumber: 1n, + mockEpoch: 1n, + }, + }); + + const otherRequestEvent = { + ...event, + metadata: { + ...event.metadata, + dispute: { + ...event.metadata.dispute, + requestId: "0x02", + }, + }, + }; + + expect(actor.onResponseDisputed(otherRequestEvent)).rejects.toThrow(InvalidActorState); + }); +}); + +function scaffoldEboActor(mockedValues: { + requestId: Address; + indexedChainId: Caip2ChainId; + mockActorResponse: { mockBlockNumber: bigint; mockEpoch: bigint }; +}) { + const { requestId, indexedChainId, mockActorResponse } = mockedValues; + const { mockBlockNumber, mockEpoch } = mockActorResponse; + + const protocolProvider = new ProtocolProvider( + ["http://localhost:8538"], + DEFAULT_MOCKED_PROTOCOL_CONTRACTS, + ); + + const chainRpcUrls = new Map(); + chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]); + + const blockNumberService = new BlockNumberService(chainRpcUrls, logger); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(mockBlockNumber); + + const registry = new EboMemoryRegistry(); + const response: Response = { + id: "0x01", + wasDisputed: false, + prophetData: { + proposer: "0x01", + requestId: requestId, + response: { + chainId: indexedChainId, + block: mockBlockNumber, + epoch: mockEpoch, + }, + }, + }; + + vi.spyOn(registry, "getRequest").mockReturnValue(DEFAULT_MOCKED_REQUEST_CREATED_DATA); + vi.spyOn(registry, "getResponse").mockReturnValue(response); + + const requestConfig = { + id: requestId, + epoch: mockEpoch, + epochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), + }; + + const actor = new EboActor( + requestConfig, + protocolProvider, + blockNumberService, + registry, + logger, + ); + + return { + actor, + protocolProvider, + blockNumberService, + registry, + }; +} From 66dc2050f11abeecb86289014cd75b3a1cee1015 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 13 Aug 2024 13:19:45 -0300 Subject: [PATCH 2/5] docs: add docs to dispute methods on EboActor --- packages/automated-dispute/src/eboActor.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index 1a3bd95..055ba5b 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -257,6 +257,13 @@ export class EboActor { else await this.pledgeAgainst(request, dispute); } + /** + * Check if a dispute is valid, comparing the already submitted and disputed proposal with + * the response this actor would propose. + * + * @param proposedResponse the already submitted response + * @returns true if the hypothetical proposal is different that the submitted one, false otherwise + */ private async isValidDispute(proposedResponse: Response) { const actorResponse = await this.buildResponse( proposedResponse.prophetData.response.chainId, @@ -270,6 +277,12 @@ export class EboActor { return !equalResponses; } + /** + * Pledge in favor of the dispute. + * + * @param request the dispute's `Request` + * @param dispute the `Dispute` + */ private async pledgeFor(request: Request, dispute: Dispute) { try { this.logger.info(`Pledging against dispute ${dispute.id}`); @@ -288,6 +301,12 @@ export class EboActor { } } + /** + * Pledge against the dispute. + * + * @param request the dispute's `Request` + * @param dispute the `Dispute` + */ private async pledgeAgainst(request: Request, dispute: Dispute) { try { this.logger.info(`Pledging for dispute ${dispute.id}`); From 098eb08bee0ba534ba0c50185ffb561d136ffab6 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Tue, 13 Aug 2024 16:02:21 -0300 Subject: [PATCH 3/5] docs: add TODOs to handle errors when using protocol provider --- packages/automated-dispute/src/eboActor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index 055ba5b..e18fc9a 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -290,8 +290,10 @@ export class EboActor { await this.protocolProvider.pledgeForDispute(request.prophetData, dispute.prophetData); } catch (err) { if (err instanceof ContractFunctionRevertedError) { + // TODO: handle each error appropriately this.logger.warn(`Pledging for dispute ${dispute.id} was reverted. Skipping...`); } else { + // TODO: handle each error appropriately this.logger.error( `Actor handling request ${this.actorRequest.id} is not able to continue.`, ); @@ -317,8 +319,10 @@ export class EboActor { ); } catch (err) { if (err instanceof ContractFunctionRevertedError) { + // TODO: handle each error appropriately this.logger.warn(`Pledging on dispute ${dispute.id} was reverted. Skipping...`); } else { + // TODO: handle each error appropriately this.logger.error( `Actor handling request ${this.actorRequest.id} is not able to continue.`, ); From 094c12b80acbe46b1880f500c73e9c57bd825c09 Mon Sep 17 00:00:00 2001 From: 0xyaco Date: Wed, 14 Aug 2024 11:04:24 -0300 Subject: [PATCH 4/5] refactor: ebo actor testing (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Part of GRT-83 ## Description - Create the `mocks.buildEboActor(r: Request, l: ILogger)` that enable developers to easily build `EboActor` instances, with access to all its dependencies. - Create the `mocks.buildResponse(r: Request)` that enable developers to easily build `Response` instances based on a request, for data consistency between both entities. - General cleaning of tests using new mock methods. --- packages/automated-dispute/src/eboActor.ts | 4 +- .../tests/eboActor/mocks/index.ts | 79 ++++++++ .../tests/eboActor/onResponseDisputed.spec.ts | 190 ++++++------------ .../tests/eboActor/onResponseProposed.spec.ts | 134 +++--------- 4 files changed, 173 insertions(+), 234 deletions(-) create mode 100644 packages/automated-dispute/tests/eboActor/mocks/index.ts diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index e18fc9a..8298a97 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -1,7 +1,7 @@ import { BlockNumberService } from "@ebo-agent/blocknumber"; import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; import { ILogger } from "@ebo-agent/shared"; -import { ContractFunctionRevertedError, Hex } from "viem"; +import { ContractFunctionRevertedError } from "viem"; import { InvalidActorState } from "./exceptions/invalidActorState.exception.js"; import { RequestMismatch } from "./exceptions/requestMismatch.js"; @@ -17,7 +17,7 @@ import { Dispute, Request, Response, ResponseBody } from "./types/prophet.js"; export class EboActor { constructor( private readonly actorRequest: { - id: Hex; + id: string; epoch: bigint; epochTimestamp: bigint; }, diff --git a/packages/automated-dispute/tests/eboActor/mocks/index.ts b/packages/automated-dispute/tests/eboActor/mocks/index.ts new file mode 100644 index 0000000..2f85b11 --- /dev/null +++ b/packages/automated-dispute/tests/eboActor/mocks/index.ts @@ -0,0 +1,79 @@ +import { BlockNumberService } from "@ebo-agent/blocknumber"; +import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; +import { ILogger } from "@ebo-agent/shared"; + +import { EboActor } from "../../../src/eboActor"; +import { EboMemoryRegistry } from "../../../src/eboMemoryRegistry"; +import { ProtocolProvider } from "../../../src/protocolProvider"; +import { Request, Response } from "../../../src/types/prophet"; +import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../fixtures"; + +/** + * Builds a base `EboActor` scaffolded with all its dependencies. + * + * @param request a `Request` to populate the `EboActor` with + * @param logger logger + * @returns + */ +function buildEboActor(request: Request, logger: ILogger) { + const { id, chainId, epoch, epochTimestamp } = request; + + const protocolProviderRpcUrls = ["http://localhost:8538"]; + const protocolProvider = new ProtocolProvider( + protocolProviderRpcUrls, + DEFAULT_MOCKED_PROTOCOL_CONTRACTS, + ); + + const blockNumberRpcUrls = new Map([ + [chainId, ["http://localhost:8539"]], + ]); + const blockNumberService = new BlockNumberService(blockNumberRpcUrls, logger); + + const registry = new EboMemoryRegistry(); + + const actor = new EboActor( + { id, epoch, epochTimestamp }, + protocolProvider, + blockNumberService, + registry, + logger, + ); + + return { + actor, + protocolProvider, + blockNumberService, + registry, + logger, + }; +} + +/** + * Helper function to build a response based on a request. + * + * @param request the `Request` to base the response on + * @param attributes custom attributes to set into the response to build + * @returns a `Response` + */ +function buildResponse(request: Request, attributes: Partial = {}): Response { + const baseResponse: Response = { + id: "0x01", + wasDisputed: false, + prophetData: { + proposer: "0x01", + requestId: request.id, + response: { + chainId: request.chainId, + block: 1n, + epoch: request.epoch, + }, + }, + }; + + return { + ...baseResponse, + ...attributes, + }; +} + +export default { buildEboActor, buildResponse }; diff --git a/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts b/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts index ca9e721..4246348 100644 --- a/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts @@ -1,16 +1,12 @@ -import { BlockNumberService } from "@ebo-agent/blocknumber"; -import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; import { ILogger } from "@ebo-agent/shared"; -import { Address, ContractFunctionRevertedError } from "viem"; +import { ContractFunctionRevertedError } from "viem"; import { describe, expect, it, vi } from "vitest"; -import { EboActor } from "../../src/eboActor"; -import { EboMemoryRegistry } from "../../src/eboMemoryRegistry"; import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception"; -import { ProtocolProvider } from "../../src/protocolProvider"; import { EboEvent } from "../../src/types/events"; import { Response } from "../../src/types/prophet"; -import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures"; +import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures"; +import mocks from "./mocks/index.js"; const logger: ILogger = { info: vi.fn(), @@ -20,16 +16,19 @@ const logger: ILogger = { }; describe("onResponseDisputed", () => { + const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; + const response: Response = mocks.buildResponse(actorRequest); + const event: EboEvent<"ResponseDisputed"> = { name: "ResponseDisputed", blockNumber: 1n, logIndex: 1, metadata: { disputeId: "0x03", - responseId: "0x02", + responseId: response.id, dispute: { - requestId: "0x01", - responseId: "0x02", + requestId: actorRequest.id, + responseId: response.id, disputer: "0x11", proposer: "0x12", }, @@ -37,19 +36,16 @@ describe("onResponseDisputed", () => { }; it("pledges for dispute if proposal should be different", async () => { - const mockEpochBlockNumber = 1n; - - const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ - requestId: "0x01", - indexedChainId: "eip155:1", - mockActorResponse: { - mockBlockNumber: mockEpochBlockNumber, - mockEpoch: 1n, - }, - }); + const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( - mockEpochBlockNumber + 1n, + response.prophetData.response.block + 1n, ); const mockPledgeForDispute = vi.spyOn(protocolProvider, "pledgeForDispute"); @@ -60,35 +56,39 @@ describe("onResponseDisputed", () => { }); it("pledges against dispute if proposal is ok", async () => { - const mockEpochBlockNumber = 1n; - - const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ - requestId: "0x01", - indexedChainId: "eip155:1", - mockActorResponse: { - mockBlockNumber: mockEpochBlockNumber, - mockEpoch: 1n, - }, - }); + const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); - vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(mockEpochBlockNumber); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( + response.prophetData.response.block, + ); - const mockPledgeForDispute = vi.spyOn(protocolProvider, "pledgeAgainstDispute"); + const mockPledgeAgainstDispute = vi.spyOn(protocolProvider, "pledgeAgainstDispute"); await actor.onResponseDisputed(event); - expect(mockPledgeForDispute).toHaveBeenCalled(); + expect(mockPledgeAgainstDispute).toHaveBeenCalled(); }); it("adds dispute to registry", async () => { - const { actor, registry } = scaffoldEboActor({ - requestId: "0x01", - indexedChainId: "eip155:1", - mockActorResponse: { - mockBlockNumber: 1n, - mockEpoch: 1n, - }, - }); + const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( + response.prophetData.response.block, + ); + + vi.spyOn(protocolProvider, "pledgeAgainstDispute").mockResolvedValue(); const addResponseMock = vi.spyOn(registry, "addDispute"); @@ -98,19 +98,16 @@ describe("onResponseDisputed", () => { }); it("resolves if the pledge is reverted", async () => { - const mockEpochBlockNumber = 1n; - - const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ - requestId: "0x01", - indexedChainId: "eip155:1", - mockActorResponse: { - mockBlockNumber: mockEpochBlockNumber, - mockEpoch: 1n, - }, - }); + const { actor, blockNumberService, protocolProvider, registry } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( - mockEpochBlockNumber + 1n, + response.prophetData.response.block + 1n, ); vi.spyOn(protocolProvider, "pledgeForDispute").mockRejectedValue( @@ -121,19 +118,16 @@ describe("onResponseDisputed", () => { }); it("throws if protocol provider cannot complete pledge", () => { - const mockEpochBlockNumber = 1n; - - const { actor, blockNumberService, protocolProvider } = scaffoldEboActor({ - requestId: "0x01", - indexedChainId: "eip155:1", - mockActorResponse: { - mockBlockNumber: mockEpochBlockNumber, - mockEpoch: 1n, - }, - }); + const { actor, blockNumberService, protocolProvider, registry } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(registry, "getResponse").mockReturnValue(response); vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( - mockEpochBlockNumber + 1n, + response.prophetData.response.block + 1n, ); vi.spyOn(protocolProvider, "pledgeForDispute").mockRejectedValue(new Error()); @@ -142,14 +136,7 @@ describe("onResponseDisputed", () => { }); it("throws if the response's request is not handled by actor", () => { - const { actor } = scaffoldEboActor({ - requestId: "0x01", - indexedChainId: "eip155:1", - mockActorResponse: { - mockBlockNumber: 1n, - mockEpoch: 1n, - }, - }); + const { actor } = mocks.buildEboActor(actorRequest, logger); const otherRequestEvent = { ...event, @@ -165,62 +152,3 @@ describe("onResponseDisputed", () => { expect(actor.onResponseDisputed(otherRequestEvent)).rejects.toThrow(InvalidActorState); }); }); - -function scaffoldEboActor(mockedValues: { - requestId: Address; - indexedChainId: Caip2ChainId; - mockActorResponse: { mockBlockNumber: bigint; mockEpoch: bigint }; -}) { - const { requestId, indexedChainId, mockActorResponse } = mockedValues; - const { mockBlockNumber, mockEpoch } = mockActorResponse; - - const protocolProvider = new ProtocolProvider( - ["http://localhost:8538"], - DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - ); - - const chainRpcUrls = new Map(); - chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]); - - const blockNumberService = new BlockNumberService(chainRpcUrls, logger); - vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(mockBlockNumber); - - const registry = new EboMemoryRegistry(); - const response: Response = { - id: "0x01", - wasDisputed: false, - prophetData: { - proposer: "0x01", - requestId: requestId, - response: { - chainId: indexedChainId, - block: mockBlockNumber, - epoch: mockEpoch, - }, - }, - }; - - vi.spyOn(registry, "getRequest").mockReturnValue(DEFAULT_MOCKED_REQUEST_CREATED_DATA); - vi.spyOn(registry, "getResponse").mockReturnValue(response); - - const requestConfig = { - id: requestId, - epoch: mockEpoch, - epochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - logger, - ); - - return { - actor, - protocolProvider, - blockNumberService, - registry, - }; -} diff --git a/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts b/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts index 1b5e162..2990582 100644 --- a/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts @@ -1,14 +1,12 @@ -import { BlockNumberService } from "@ebo-agent/blocknumber"; import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; import { ILogger } from "@ebo-agent/shared"; +import { Hex } from "viem"; import { describe, expect, it, vi } from "vitest"; -import { EboActor } from "../../src/eboActor"; -import { EboMemoryRegistry } from "../../src/eboMemoryRegistry"; import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception"; -import { ProtocolProvider } from "../../src/protocolProvider"; import { EboEvent } from "../../src/types/events"; -import { DEFAULT_MOCKED_PROPHET_REQUEST, DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "./fixtures"; +import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.ts"; +import mocks from "./mocks/index.ts"; const logger: ILogger = { info: vi.fn(), @@ -18,29 +16,22 @@ const logger: ILogger = { }; describe("onResponseProposed", () => { - const requestId = "0x01"; - const indexedChainId: Caip2ChainId = "eip155:137"; - - const protocolEpoch = { - currentEpoch: 1n, - currentEpochBlockNumber: 1n, - currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), - }; + const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; const responseProposedEvent: EboEvent<"ResponseProposed"> = { name: "ResponseProposed", blockNumber: 1n, logIndex: 2, metadata: { - requestId: requestId, + requestId: actorRequest.id, responseId: "0x02", response: { proposer: "0x03", - requestId: requestId, + requestId: actorRequest.id, response: { - block: protocolEpoch.currentEpochBlockNumber, - chainId: indexedChainId, - epoch: protocolEpoch.currentEpoch, + block: 1n, + chainId: actorRequest.chainId, + epoch: 1n, }, }, }, @@ -49,14 +40,11 @@ describe("onResponseProposed", () => { const proposeData = responseProposedEvent.metadata.response.response; it("adds the response to the registry", async () => { - const { actor, registry } = mockEboActor({ - requestId, - indexedChainId, - mockActorResponse: { - mockBlockNumber: proposeData.block, - mockEpoch: proposeData.epoch, - }, - }); + const { actor, registry, blockNumberService } = mocks.buildEboActor(actorRequest, logger); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(proposeData.block); const addResponseMock = vi.spyOn(registry, "addResponse"); @@ -66,14 +54,7 @@ describe("onResponseProposed", () => { }); it("throws if the response's request is not handled by actor", () => { - const { actor } = mockEboActor({ - requestId, - indexedChainId, - mockActorResponse: { - mockBlockNumber: proposeData.block, - mockEpoch: proposeData.epoch, - }, - }); + const { actor } = mocks.buildEboActor(actorRequest, logger); const otherRequestEvent = { ...responseProposedEvent, @@ -87,14 +68,14 @@ describe("onResponseProposed", () => { }); it("does not dispute the response if seems valid", async () => { - const { actor, protocolProvider } = mockEboActor({ - requestId, - indexedChainId, - mockActorResponse: { - mockBlockNumber: proposeData.block, - mockEpoch: proposeData.epoch, - }, - }); + const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(proposeData.block); const mockDisputeResponse = vi.spyOn(protocolProvider, "disputeResponse"); @@ -104,14 +85,16 @@ describe("onResponseProposed", () => { }); it("dispute the response if it should be different", async () => { - const { actor, protocolProvider } = mockEboActor({ - requestId, - indexedChainId, - mockActorResponse: { - mockBlockNumber: proposeData.block + 1n, - mockEpoch: proposeData.epoch, - }, - }); + const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor( + actorRequest, + logger, + ); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( + proposeData.block + 1n, + ); const mockDisputeResponse = vi.spyOn(protocolProvider, "disputeResponse"); @@ -120,54 +103,3 @@ describe("onResponseProposed", () => { expect(mockDisputeResponse).toHaveBeenCalled(); }); }); - -// Mocks the basic dependencies behavior of EboActor instance on onResponseProposed -// to validate the proposal without disputing it -function mockEboActor(mockedValues: { - requestId: string; - indexedChainId: Caip2ChainId; - mockActorResponse: { - mockBlockNumber: bigint; - mockEpoch: bigint; - }; -}) { - const { requestId, indexedChainId, mockActorResponse } = mockedValues; - const { mockBlockNumber, mockEpoch } = mockActorResponse; - - const protocolProvider = new ProtocolProvider( - ["http://localhost:8538"], - DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - ); - - const chainRpcUrls = new Map(); - chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]); - - const blockNumberService = new BlockNumberService(chainRpcUrls, logger); - const registry = new EboMemoryRegistry(); - - const requestConfig = { - id: requestId, - epoch: mockEpoch, - epochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - logger, - ); - - vi.spyOn(registry, "getRequest").mockReturnValue(DEFAULT_MOCKED_PROPHET_REQUEST); - - vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(mockBlockNumber); - - return { - actor, - protocolProvider, - blockNumberService, - registry, - logger, - }; -} From 9f9a509741a114c845d4d54f43264a7daf2a1c53 Mon Sep 17 00:00:00 2001 From: Yaco 0x Date: Wed, 14 Aug 2024 11:21:12 -0300 Subject: [PATCH 5/5] fix: lint onResponseProposed --- .../automated-dispute/tests/eboActor/onResponseProposed.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts b/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts index 2990582..6ebac86 100644 --- a/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts @@ -1,6 +1,4 @@ -import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; import { ILogger } from "@ebo-agent/shared"; -import { Hex } from "viem"; import { describe, expect, it, vi } from "vitest"; import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception";