Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fetch pledges for a dispute before settling or escalating #82

Merged
merged 6 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 2 additions & 23 deletions packages/automated-dispute/src/providers/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { arbitrum, arbitrumSepolia, mainnet, sepolia } from "viem/chains";

import type {
BondEscalation,
BondEscalationStatus,
Dispute,
DisputeId,
EboEvent,
Expand Down Expand Up @@ -57,6 +56,7 @@ import {
IWriteProvider,
ProtocolContractsAddresses,
} from "../interfaces/index.js";
import { ProphetCodec } from "../services/index.js";

type RpcConfig = {
chainId: Caip2ChainId;
Expand Down Expand Up @@ -1114,32 +1114,11 @@ export class ProtocolProvider implements IProtocolProvider {

const bondEscalation: BondEscalation = {
disputeId: result.disputeId,
status: this.decodeBondEscalationStatus(result.status),
status: ProphetCodec.decodeBondEscalationStatus(result.disputeId, result.status),
amountOfPledgesForDispute: result.amountOfPledgesForDispute,
amountOfPledgesAgainstDispute: result.amountOfPledgesAgainstDispute,
};

return bondEscalation;
}

/**
* Decodes the BondEscalationStatus enum from the contract.
*
* @param status - The numeric status from the contract.
* @returns The corresponding BondEscalationStatus string.
*/
private decodeBondEscalationStatus(status: number): BondEscalationStatus {
switch (status) {
case 0:
return "Active";
case 1:
return "Resolved";
case 2:
return "Escalated";
case 3:
return "NoResolution";
default:
throw new Error(`Unknown BondEscalationStatus: ${status}`);
}
}
}
22 changes: 13 additions & 9 deletions packages/automated-dispute/src/services/eboActor.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isNativeError } from "util/types";
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId, ILogger, stringify, UnixTimestamp } from "@ebo-agent/shared";
import { Mutex } from "async-mutex";
Expand Down Expand Up @@ -467,23 +468,29 @@ export class EboActor {
response: Response,
dispute: Dispute,
): Promise<void> {
let escalationData;
try {
this.logger.info(`Settling dispute ${dispute.id}...`);

const escalationData = await this.protocolProvider.read.getEscalation(request.id);
escalationData = await this.protocolProvider.read.getEscalation(request.id);
} catch (err: unknown) {
this.logger.error(
`Failed to fetch escalation data for request ${request.id}: ${isNativeError(err) ? err.message : err}`,
);
return;
}

try {
const amountFor = escalationData.amountOfPledgesForDispute;
const amountAgainst = escalationData.amountOfPledgesAgainstDispute;

if (amountFor > amountAgainst) {
if (amountFor !== amountAgainst) {
await this.protocolProvider.settleDispute(
request.prophetData,
response.prophetData,
dispute.prophetData,
);

this.logger.info(`Dispute ${dispute.id} settled.`);
} else if (amountFor <= amountAgainst) {
} else if (amountFor === amountAgainst) {
await this.protocolProvider.escalateDispute(
request.prophetData,
response.prophetData,
Expand All @@ -492,8 +499,6 @@ export class EboActor {

this.logger.info(`Dispute ${dispute.id} escalated.`);
}

this.logger.info(`Dispute ${dispute.id} settled.`);
} catch (err) {
if (err instanceof CustomContractError) {
this.logger.warn(`Call reverted for dispute ${dispute.id} due to: ${err.name}`);
Expand All @@ -505,8 +510,7 @@ export class EboActor {
registry: this.registry,
});
} else {
this.logger.error(`Failed to escalate dispute ${dispute.id}: ${err}`);

this.logger.error(`Failed to settle dispute ${dispute.id}: ${err}`);
throw err;
}
}
Expand Down
23 changes: 23 additions & 0 deletions packages/automated-dispute/src/services/prophetCodec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
toHex,
} from "viem";

import type { BondEscalationStatus } from "../types/prophet.js";
import { ProphetDecodingError } from "../exceptions/index.js";
import { DisputeStatus, Request, Response } from "../types/prophet.js";

Expand Down Expand Up @@ -256,4 +257,26 @@ export class ProphetCodec {
// TODO: throw ProphetEncodingError
return index;
}

/**
* Decodes the BondEscalationStatus enum from the contract.
*
* @param id - The ID of the request.
* @param status - The numeric status from the contract.
* @returns The corresponding BondEscalationStatus string.
*/
static decodeBondEscalationStatus(id: Hex, status: number): BondEscalationStatus {
switch (status) {
case 0:
return "Active";
case 1:
return "Resolved";
case 2:
return "Escalated";
case 3:
return "NoResolution";
default:
throw new ProphetDecodingError(id, toHex(status.toString()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ID is to help us identify the property that was decoded!

Suggested change
throw new ProphetDecodingError(id, toHex(status.toString()));
throw new ProphetDecodingError("escalation.status", toHex(status.toString()));

You can remove the id param from this method too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
}
7 changes: 4 additions & 3 deletions packages/automated-dispute/tests/mocks/eboActor.mocks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId, ILogger, UnixTimestamp } from "@ebo-agent/shared";
import { Mutex } from "async-mutex";
import { Block } from "viem";
import { Block, pad } from "viem";
import { vi } from "vitest";

import { ProtocolProvider } from "../../src/providers/index.js";
Expand Down Expand Up @@ -63,6 +63,7 @@ export function buildEboActor(request: Request, logger: ILogger) {
amountOfPledgesForDispute: BigInt(10),
amountOfPledgesAgainstDispute: BigInt(5),
});

vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue({
number: BigInt(1),
firstBlockNumber: BigInt(100),
Expand Down Expand Up @@ -170,7 +171,7 @@ export function buildResponse(request: Request, attributes: Partial<Response> =
};

const baseResponse: Response = {
id: attributes.id || (("0x" + "02".repeat(32)) as ResponseId),
id: attributes.id || (pad("0x02") as ResponseId),
createdAt: {
timestamp: (request.createdAt.timestamp + 1n) as UnixTimestamp,
blockNumber: request.createdAt.blockNumber + 1n,
Expand Down Expand Up @@ -198,7 +199,7 @@ export function buildDispute(
attributes: Partial<Dispute> = {},
): Dispute {
const baseDispute: Dispute = {
id: attributes.id || (("0x" + "03".repeat(32)) as DisputeId),
id: attributes.id || (pad("0x03") as DisputeId),
status: "Active",
createdAt: {
timestamp: (response.createdAt.timestamp + 1n) as UnixTimestamp,
Expand Down
14 changes: 7 additions & 7 deletions packages/automated-dispute/tests/services/eboActor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ describe("EboActor", () => {
});

describe("settleDispute", () => {
it("escalates dispute when amountOfPledgesForDispute <= amountOfPledgesAgainstDispute", async () => {
it("escalates dispute when amountOfPledgesForDispute === amountOfPledgesAgainstDispute", async () => {
const { actor, protocolProvider } = mocks.buildEboActor(request, logger);
const response = mocks.buildResponse(request);
const dispute = mocks.buildDispute(request, response);
Expand All @@ -527,7 +527,7 @@ describe("EboActor", () => {
disputeId: dispute.id,
status: "Active",
amountOfPledgesForDispute: BigInt(5),
amountOfPledgesAgainstDispute: BigInt(10),
amountOfPledgesAgainstDispute: BigInt(5),
});

const escalateDisputeMock = vi
Expand Down Expand Up @@ -564,22 +564,22 @@ describe("EboActor", () => {
);
});

it("settles dispute when amountOfPledgesForDispute > amountOfPledgesAgainstDispute", async () => {
it("settles dispute when amountOfPledgesForDispute !== amountOfPledgesAgainstDispute", async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is an extremely core case, let's explicitly test both cases here: > and <

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ I split this into two test cases

const { actor, protocolProvider } = mocks.buildEboActor(request, logger);
const response = mocks.buildResponse(request);
const dispute = mocks.buildDispute(request, response);

const settleDisputeMock = vi
.spyOn(protocolProvider, "settleDispute")
.mockResolvedValue();

vi.spyOn(protocolProvider.read, "getEscalation").mockResolvedValue({
disputeId: dispute.id,
status: "Active",
amountOfPledgesForDispute: BigInt(10),
amountOfPledgesAgainstDispute: BigInt(5),
});

const settleDisputeMock = vi
.spyOn(protocolProvider, "settleDispute")
.mockResolvedValue();

await actor["settleDispute"](request, response, dispute);

expect(settleDisputeMock).toHaveBeenCalledWith(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Caip2ChainId, UnixTimestamp } from "@ebo-agent/shared";
import { Address, Hex } from "viem";
import { Address, Hex, pad } from "viem";

import { ProtocolContractsAddresses } from "../../../src/interfaces/index.js";
import { ProphetCodec } from "../../../src/services/prophetCodec.js";
Expand All @@ -24,7 +24,7 @@ export const DEFAULT_MOCKED_PROTOCOL_CONTRACTS: ProtocolContractsAddresses = {
};

export const DEFAULT_MOCKED_RESPONSE_DATA: Response = {
id: ("0x" + "02".repeat(32)) as ResponseId,
id: pad("0x02") as ResponseId,
createdAt: {
timestamp: 1625097600n as UnixTimestamp,
blockNumber: 1n,
Expand Down Expand Up @@ -68,7 +68,7 @@ const DEFAULT_REQUEST_MODULES_DATA = {
};

export const DEFAULT_MOCKED_REQUEST_CREATED_DATA: Request = {
id: ("0x" + "01".repeat(32)) as RequestId,
id: pad("0x01") as RequestId,
createdAt: {
timestamp: BigInt(Date.UTC(2024, 0, 1, 0, 0, 0, 0) / 1000) as UnixTimestamp,
blockNumber: 1n,
Expand Down Expand Up @@ -103,7 +103,7 @@ export const DEFAULT_MOCKED_REQUEST_CREATED_DATA: Request = {
};

export const DEFAULT_MOCKED_DISPUTE_DATA: Dispute = {
id: ("0x" + "03".repeat(32)) as DisputeId,
id: pad("0x03") as DisputeId,
createdAt: 1625097800n,
status: "Active",
prophetData: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe("onDisputeStatusUpdated", () => {
metadata: {
disputeId: dispute.id,
status: "Lost",
status: ProphetCodec.encodeDisputeStatus("Lost"),
dispute: dispute.prophetData,
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UnixTimestamp } from "@ebo-agent/shared";
import { pad } from "viem";
import { describe, expect, it, vi } from "vitest";

import { DisputeWithoutResponse } from "../../../src/exceptions/index.js";
Expand All @@ -15,7 +16,7 @@ describe("EboActor", () => {
const { disputeModuleData } = request.decodedData;

const responseToSettle = mocks.buildResponse(request, {
id: ("0x" + "02".repeat(32)) as ResponseId,
id: pad("0x02") as ResponseId,
});
const disputeToSettle = mocks.buildDispute(request, responseToSettle, {
createdAt: {
Expand Down Expand Up @@ -66,7 +67,7 @@ describe("EboActor", () => {
const newBlockNumber = disputeDeadline + 1n;

vi.spyOn(protocolProvider.read, "getEscalation").mockResolvedValue({
disputeId: ("0x" + "03".repeat(32)) as DisputeId,
disputeId: pad("0x03") as DisputeId,
status: "Active",
amountOfPledgesForDispute: BigInt(10),
amountOfPledgesAgainstDispute: BigInt(5),
Expand Down