Skip to content

Commit

Permalink
feat: handle ProposeDisputed event (#21)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GRT-83

## Description
Handles the `ResposeDisputed` event.
  • Loading branch information
0xyaco authored Aug 14, 2024
1 parent 0f70775 commit e1ba843
Show file tree
Hide file tree
Showing 9 changed files with 418 additions and 120 deletions.
123 changes: 112 additions & 11 deletions packages/automated-dispute/src/eboActor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
},
Expand Down Expand Up @@ -216,19 +216,120 @@ export class EboActor {
}
}

public async onResponseDisputed(_event: EboEvent<"ResponseDisputed">): Promise<void> {
// 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<void> {
// TODO: implement
return;
/**
* Handle the `ResponseDisputed` event.
*
* @param event `ResponseDisputed` event.
*/
public async onResponseDisputed(event: EboEvent<"ResponseDisputed">): Promise<void> {
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<boolean> {
// TODO: implement
return true;
/**
* 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,
);

const equalResponses = this.equalResponses(
actorResponse,
proposedResponse.prophetData.response,
);

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}`);

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.`,
);

throw err;
}
}
}

/**
* 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}`);

await this.protocolProvider.pledgeAgainstDispute(
request.prophetData,
dispute.prophetData,
);
} 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.`,
);

throw err;
}
}
}

public async onFinalizeRequest(_event: EboEvent<"RequestFinalizable">): Promise<void> {
Expand Down
12 changes: 11 additions & 1 deletion packages/automated-dispute/src/eboMemoryRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class EboMemoryRegistry implements EboRegistry {
constructor(
private requests: Map<string, Request> = new Map(),
private responses: Map<string, Response> = new Map(),
private dispute: Map<string, Dispute> = new Map(),
private disputes: Map<string, Dispute> = new Map(),
) {}

/** @inheritdoc */
Expand All @@ -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);
}
}
20 changes: 18 additions & 2 deletions packages/automated-dispute/src/interfaces/eboRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Request, Response } from "../types/prophet.js";
import { Dispute, Request, Response } from "../types/prophet.js";

/** Registry that stores Prophet entities (ie. requests, responses and disputes) */
export interface EboRegistry {
Expand All @@ -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;

Expand All @@ -32,4 +32,20 @@ export interface EboRegistry {
* @returns responses map
*/
getResponses(): Map<string, Response>;

/**
* 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;
}
10 changes: 8 additions & 2 deletions packages/automated-dispute/src/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,18 @@ export class ProtocolProvider {
return;
}

async pledgeForDispute(_request: Request, _dispute: Dispute): Promise<void> {
async pledgeForDispute(
_request: Request["prophetData"],
_dispute: Dispute["prophetData"],
): Promise<void> {
// TODO: implement actual method
return;
}

async pledgeAgaintsDispute(_request: Request, _dispute: Dispute): Promise<void> {
async pledgeAgainstDispute(
_request: Request["prophetData"],
_dispute: Dispute["prophetData"],
): Promise<void> {
// TODO: implement actual method
return;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/automated-dispute/src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export interface RequestCreated {
}

export interface ResponseDisputed {
requestId: string;
responseId: string;
disputeId: string;
dispute: Dispute["prophetData"];
}

Expand Down
4 changes: 3 additions & 1 deletion packages/automated-dispute/src/types/prophet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ export interface Response {

export type ResponseBody = Response["prophetData"]["response"];

export type DisputeStatus = "None" | "Active" | "Escalated" | "Won" | "Lost" | "NoResolution";

export interface Dispute {
id: string;
status: string;
status: DisputeStatus;

prophetData: {
disputer: Address;
Expand Down
79 changes: 79 additions & 0 deletions packages/automated-dispute/tests/eboActor/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -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<Caip2ChainId, string[]>([
[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> = {}): 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 };
Loading

0 comments on commit e1ba843

Please sign in to comment.