Skip to content

Commit

Permalink
feat: check for accounting approval modules
Browse files Browse the repository at this point in the history
  • Loading branch information
0xyaco committed Sep 17, 2024
1 parent dcd5d89 commit fc46522
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 31 deletions.
14 changes: 13 additions & 1 deletion apps/agent/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inspect } from "util";
import { EboActorsManager, EboProcessor } from "@ebo-agent/automated-dispute";
import { ProtocolProvider } from "@ebo-agent/automated-dispute/dist/providers/protocolProvider.js";
import { AccountingModules } from "@ebo-agent/automated-dispute/dist/types/prophet.js";
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Logger } from "@ebo-agent/shared";

Expand Down Expand Up @@ -29,6 +30,11 @@ const config = {
},
processor: {
msBetweenChecks: 1,
accountingModules: {
requestModule: "0x01",
responseModule: "0x02",
escalationModule: "0x03",
} as AccountingModules,
},
};

Expand All @@ -49,7 +55,13 @@ const main = async (): Promise<void> => {

const actorsManager = new EboActorsManager();

const processor = new EboProcessor(protocolProvider, blockNumberService, actorsManager, logger);
const processor = new EboProcessor(
config.processor.accountingModules,
protocolProvider,
blockNumberService,
actorsManager,
logger,
);

await processor.start(config.processor.msBetweenChecks);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./pendingModulesApproval.exception.js";
export * from "./processorAlreadyStarted.exception.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AccountingModules } from "../../types/index.js";

export class PendingModulesApproval extends Error {
constructor(
public readonly approvedModules: Partial<AccountingModules>,
public readonly pendingModules: Partial<AccountingModules>,
) {
const approvedModulesStr = Object.entries(approvedModules)
.map(([key, value]) => `(${key}: ${value})`)
.join(", ");

const pendingModulesStr = Object.entries(pendingModules)
.map(([key, value]) => `(${key}: ${value})`)
.join(", ");

super(
`Modules approved: ${approvedModulesStr}\n` +
`Modules pending approval: ${pendingModulesStr}`,
);
}
}
21 changes: 21 additions & 0 deletions packages/automated-dispute/src/interfaces/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ export interface IReadProvider {
* @returns A promise that resolves with an array of chain IDs.
*/
getAvailableChains(): Promise<string[]>;

/**
* Gets the address of the accounting module.
*
* @returns An address that points to the deployed accounting module.
*/
getAccountingModuleAddress(): Address;

/**
* Gets the list of approved modules' addresses based on the wallet's account address.
*
* @returns A promise that resolves with an array of approved modules.
*/
getAccountingApprovedModules(): Promise<Address[]>;
}

/**
Expand Down Expand Up @@ -148,6 +162,13 @@ export interface IWriteProvider {
* @returns A promise that resolves when the request is finalized.
*/
finalize(request: Request["prophetData"], response: Response["prophetData"]): Promise<void>;

/**
* Approves modules needed by the accounting contract.
*
* @param modules an array of addresses for the modules to be approved
*/
approveAccountingModules(modules: Address[]): Promise<void>;
}

/**
Expand Down
17 changes: 17 additions & 0 deletions packages/automated-dispute/src/providers/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class ProtocolProvider implements IProtocolProvider {
settleDispute: this.settleDispute.bind(this),
escalateDispute: this.escalateDispute.bind(this),
finalize: this.finalize.bind(this),
approveAccountingModules: this.approveAccountingModules.bind(this),
};

public read: IReadProvider = {
Expand All @@ -133,6 +134,8 @@ export class ProtocolProvider implements IProtocolProvider {
getEvents: this.getEvents.bind(this),
hasStakedAssets: this.hasStakedAssets.bind(this),
getAvailableChains: this.getAvailableChains.bind(this),
getAccountingModuleAddress: this.getAccountingModuleAddress.bind(this),
getAccountingApprovedModules: this.getAccountingApprovedModules.bind(this),
};

/**
Expand Down Expand Up @@ -257,6 +260,20 @@ export class ProtocolProvider implements IProtocolProvider {
return ["eip155:1", "eip155:42161"];
}

getAccountingModuleAddress(): Address {
// TODO: implement actual method
return "0x01";
}

async getAccountingApprovedModules(): Promise<Address[]> {
// TODO: implement actual method
return [];
}

async approveAccountingModules(_modules: Address[]): Promise<void> {
// TODO: implement actual method
}

// TODO: waiting for ChainId to be merged for _chains parameter
/**
* Creates a request on the EBO Request Creator contract by simulating the transaction
Expand Down
56 changes: 53 additions & 3 deletions packages/automated-dispute/src/services/eboProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { Address, ILogger } from "@ebo-agent/shared";

import { ProcessorAlreadyStarted } from "../exceptions/index.js";
import { PendingModulesApproval, ProcessorAlreadyStarted } from "../exceptions/index.js";
import { ProtocolProvider } from "../providers/protocolProvider.js";
import { alreadyDeletedActorWarning, droppingUnhandledEventsWarning } from "../templates/index.js";
import { ActorRequest, EboEvent, EboEventName, Epoch, RequestId } from "../types/index.js";
import {
alreadyDeletedActorWarning,
droppingUnhandledEventsWarning,
pendingApprovedModulesError,
} from "../templates/index.js";
import {
AccountingModules,
ActorRequest,
EboEvent,
EboEventName,
Epoch,
RequestId,
} from "../types/index.js";
import { EboActorsManager } from "./eboActorsManager.js";

const DEFAULT_MS_BETWEEN_CHECKS = 10 * 60 * 1000; // 10 minutes
Expand All @@ -18,6 +29,7 @@ export class EboProcessor {
private lastCheckedBlock?: bigint;

constructor(
private readonly accountingModules: AccountingModules,
private readonly protocolProvider: ProtocolProvider,
private readonly blockNumberService: BlockNumberService,
private readonly actorsManager: EboActorsManager,
Expand All @@ -32,6 +44,8 @@ export class EboProcessor {
public async start(msBetweenChecks: number = DEFAULT_MS_BETWEEN_CHECKS) {
if (this.eventsInterval) throw new ProcessorAlreadyStarted();

await this.checkAllModulesApproved();

await this.sync(); // Bootstrapping

this.eventsInterval = setInterval(async () => {
Expand All @@ -47,6 +61,42 @@ export class EboProcessor {
}, msBetweenChecks);
}

/**
* Check if all the modules have been granted approval within the accounting module.
*
* @throws {PendingModulesApproval} when there is at least one module pending approval
*/
private async checkAllModulesApproved() {
const approvedModules: Address[] =
await this.protocolProvider.getAccountingApprovedModules();

const summary: Record<"approved" | "notApproved", Partial<AccountingModules>> = {
approved: {},
notApproved: {},
};

for (const [moduleName, moduleAddress] of Object.entries(this.accountingModules)) {
const isApproved = approvedModules.includes(moduleAddress);
const key = (isApproved ? "approved" : "notApproved") as keyof typeof summary;

summary[key] = { ...summary[key], [moduleName]: moduleAddress };
}

if (Object.keys(summary.notApproved).length > 0) {
const accountingModuleAddress = this.protocolProvider.getAccountingModuleAddress();

this.logger.error(
pendingApprovedModulesError(
accountingModuleAddress,
summary["approved"],
summary["notApproved"],
),
);

throw new PendingModulesApproval(summary["approved"], summary["notApproved"]);
}
}

/** Sync new blocks and their events with their corresponding actors. */
private async sync() {
try {
Expand Down
33 changes: 32 additions & 1 deletion packages/automated-dispute/src/templates/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { RequestId } from "../types/prophet.js";
import { Address } from "viem";

import { AccountingModules, RequestId } from "../types/prophet.js";

export const alreadyDeletedActorWarning = (requestId: RequestId) => `
Actor handling request ${requestId} was already deleted.
Expand All @@ -11,3 +13,32 @@ Dropping events for request ${requestId} because no actor is handling it and the
The request likely started before the current epoch's first block, which will not be handled by the agent.
`;

export const pendingApprovedModulesError = (
horizonAddress: Address,
approvedModules: Partial<AccountingModules>,
notApprovedModules: Partial<AccountingModules>,
) => {
const approvedModulesList = Object.entries(approvedModules).map(
([key, value]) => `* ${key} at ${value}\n`,
);
const notApprovedModulesList = Object.entries(notApprovedModules).map(
([key, value]) => `* ${key} at ${value}\n`,
);

return `
The EBO agent cannot proceed until certain actions are resolved by the operator.
The following modules already have approvals from HorizonAccountingExtension at ${horizonAddress}:
${approvedModulesList}
The following modules need approval from HorizonAccountingExtension at ${horizonAddress}:
${notApprovedModulesList}
To grant the necessary approvals, please run the script located at:
apps/scripts/approveAccountingModules.ts
Once approvals are completed, restart the EBO agent to continue.
`;
};
6 changes: 6 additions & 0 deletions packages/automated-dispute/src/types/prophet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,9 @@ export interface Dispute {
requestId: RequestId;
};
}

export type AccountingModules = {
requestModule: Address;
responseModule: Address;
escalationModule: Address;
};
19 changes: 17 additions & 2 deletions packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ import { ILogger } from "@ebo-agent/shared";
import { ProtocolProvider } from "../../src/providers/index.js";
import { EboProcessor } from "../../src/services";
import { EboActorsManager } from "../../src/services/index.js";
import { AccountingModules } from "../../src/types/prophet.js";
import {
DEFAULT_MOCKED_PROTOCOL_CONTRACTS,
mockedPrivateKey,
} from "../services/eboActor/fixtures.js";

export function buildEboProcessor(logger: ILogger) {
export function buildEboProcessor(
logger: ILogger,
accountingModules: AccountingModules = {
requestModule: "0x01",
responseModule: "0x02",
escalationModule: "0x03",
},
) {
const protocolProviderRpcUrls = ["http://localhost:8538"];
const protocolProvider = new ProtocolProvider(
protocolProviderRpcUrls,
Expand All @@ -24,7 +32,14 @@ export function buildEboProcessor(logger: ILogger) {
const blockNumberService = new BlockNumberService(blockNumberRpcUrls, logger);

const actorsManager = new EboActorsManager();
const processor = new EboProcessor(protocolProvider, blockNumberService, actorsManager, logger);

const processor = new EboProcessor(
accountingModules,
protocolProvider,
blockNumberService,
actorsManager,
logger,
);

return {
processor,
Expand Down
Loading

0 comments on commit fc46522

Please sign in to comment.