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: check for accounting approval modules #44

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
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";
Copy link
Collaborator

Choose a reason for hiding this comment

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

dist is pointing to the code that has been built ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

🥴

import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Logger } from "@ebo-agent/shared";

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

Expand All @@ -50,7 +56,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
1,125 changes: 0 additions & 1,125 deletions packages/automated-dispute/src/abis/bondEscalationModule.ts

Large diffs are not rendered by default.

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 @@ -39,6 +39,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 @@ -140,6 +154,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 @@ -143,13 +143,16 @@ 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 = {
getCurrentEpoch: this.getCurrentEpoch.bind(this),
getLastFinalizedBlock: this.getLastFinalizedBlock.bind(this),
getEvents: this.getEvents.bind(this),
getAvailableChains: this.getAvailableChains.bind(this),
getAccountingModuleAddress: this.getAccountingModuleAddress.bind(this),
getAccountingApprovedModules: this.getAccountingApprovedModules.bind(this),
};

/**
Expand Down Expand Up @@ -269,6 +272,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,11 +3,22 @@ import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know this isn't in your PR but we should clean up these dist imports eventually

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Totally! Already did it in #49 's latest commit

import { Address, EBO_SUPPORTED_CHAIN_IDS, ILogger } from "@ebo-agent/shared";

import { ProcessorAlreadyStarted } from "../exceptions/index.js";
import { PendingModulesApproval, ProcessorAlreadyStarted } from "../exceptions/index.js";
import { isRequestCreatedEvent } from "../guards.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 @@ -19,6 +30,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 @@ -33,6 +45,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 @@ -48,6 +62,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)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
for (const [moduleName, moduleAddress] of Object.entries(this.accountingModules)) {
for (const key in this.accountingModules) {

You can do it this way also, just to let you know, not requesting any changes here 🤣

const isApproved = approvedModules.includes(moduleAddress);
const key = isApproved ? "approved" : "notApproved";

summary[key][moduleName as keyof AccountingModules] = 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