Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into fix/discord-webhook
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/automated-dispute/src/exceptions/index.ts
  • Loading branch information
jahabeebs committed Nov 4, 2024
2 parents b1757bd + 0e65a1c commit 139da81
Show file tree
Hide file tree
Showing 34 changed files with 666 additions and 107 deletions.
2 changes: 1 addition & 1 deletion apps/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@ pnpm run test:cov

### End-to-End Testing

The e2e tests simulate scenarios starting at the contract level and extending to the agent, like processing requests and handling disputes. For detailed test scenarios, refer to the files in `test/e2e/scenarios`.
The e2e tests simulate scenarios starting at the contract level and extending to the agent, like processing requests and handling disputes. For detailed test scenarios, refer to the files in [test/e2e/scenarios](./test/e2e/scenarios).

TODO: Add instructions on running e2e tests
18 changes: 9 additions & 9 deletions packages/automated-dispute/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
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 "./notificationFailure.exception.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";
Original file line number Diff line number Diff line change
@@ -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";
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export interface EboRegistryCommand {
/**
* Return the command name
*/
name(): string;

/**
* Run a command to update the registry
*/
Expand Down
4 changes: 1 addition & 3 deletions packages/automated-dispute/src/providers/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import {
RpcUrlsEmpty,
TransactionExecutionError,
} from "../exceptions/index.js";
import { ProphetCodec } from "../external.js";
import {
IProtocolProvider,
IReadProvider,
Expand Down Expand Up @@ -467,7 +466,7 @@ export class ProtocolProvider implements IProtocolProvider {
responseId: HexUtils.normalize(_dispute.responseId) as ResponseId,
requestId: HexUtils.normalize(_dispute.requestId) as RequestId,
},
status: ProphetCodec.decodeDisputeStatus(_status),
status: _status,
blockNumber: event.blockNumber,
},
} as EboEvent<"DisputeStatusUpdated">;
Expand Down Expand Up @@ -525,7 +524,6 @@ export class ProtocolProvider implements IProtocolProvider {
requestId: HexUtils.normalize(_dispute.requestId) as RequestId,
},
caller: _caller as Address,
blockNumber: event.blockNumber,
},
} as EboEvent<"DisputeEscalated">;
}),
Expand Down
92 changes: 74 additions & 18 deletions packages/automated-dispute/src/services/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
InvalidActorState,
InvalidDisputeStatus,
PastEventEnqueueError,
ProphetDecodingError,
RequestMismatch,
ResponseAlreadyProposed,
ResponseNotFound,
Expand All @@ -35,6 +36,7 @@ import {
AddRequest,
AddResponse,
FinalizeRequest,
NoOp,
ProphetCodec,
UpdateDisputeStatus,
} from "../services/index.js";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -207,23 +235,44 @@ 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(
event as EboEvent<"DisputeStatusUpdated">,
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(
Expand Down Expand Up @@ -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 +
Expand Down Expand Up @@ -499,7 +548,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;
}

/**
Expand Down Expand Up @@ -548,7 +597,7 @@ export class EboActor {
// the proposal non-active.
const activeStatus: DisputeStatus[] = ["None", "Active"];

return activeStatus.includes(dispute.status);
return activeStatus.includes(dispute.decodedData.status);
});
}

Expand Down Expand Up @@ -809,6 +858,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);

Expand Down Expand Up @@ -941,7 +996,7 @@ export class EboActor {
private async onDisputeStatusChanged(event: EboEvent<"DisputeStatusUpdated">): Promise<void> {
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}.`);

Expand Down Expand Up @@ -971,6 +1026,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.`,
);
Expand Down
1 change: 1 addition & 0 deletions packages/automated-dispute/src/services/eboProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 139da81

Please sign in to comment.