diff --git a/apps/agent/src/config/schemas.ts b/apps/agent/src/config/schemas.ts index bb6035ad..3d6328f8 100644 --- a/apps/agent/src/config/schemas.ts +++ b/apps/agent/src/config/schemas.ts @@ -75,7 +75,6 @@ const blockNumberServiceSchema = z.object({ const processorSchema = z.object({ msBetweenChecks: z.number().int().positive(), accountingModules: z.object({ - requestModule: addressSchema, responseModule: addressSchema, escalationModule: addressSchema, }), diff --git a/packages/automated-dispute/src/interfaces/protocolProvider.ts b/packages/automated-dispute/src/interfaces/protocolProvider.ts index 7a9e93b4..76de58b5 100644 --- a/packages/automated-dispute/src/interfaces/protocolProvider.ts +++ b/packages/automated-dispute/src/interfaces/protocolProvider.ts @@ -48,20 +48,13 @@ export interface IReadProvider { */ 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; - /** * Gets the list of approved modules' addresses for a given wallet address. * * @param user The address of the user. * @returns A promise that resolves with an array of approved modules for the user. */ - getApprovedModules(user: Address): Promise; + getApprovedModules(user: Address): Promise; } /** @@ -164,13 +157,6 @@ export interface IWriteProvider { */ finalize(request: Request["prophetData"], response: Response["prophetData"]): Promise; - /** - * Approves modules needed by the accounting contract. - * - * @param modules an array of addresses for the modules to be approved - */ - approveAccountingModules(modules: Address[]): Promise; - /** * Approves a module in the accounting extension contract. * diff --git a/packages/automated-dispute/src/providers/protocolProvider.ts b/packages/automated-dispute/src/providers/protocolProvider.ts index e5179e9e..aeccf52d 100644 --- a/packages/automated-dispute/src/providers/protocolProvider.ts +++ b/packages/automated-dispute/src/providers/protocolProvider.ts @@ -1,6 +1,7 @@ import { BlockNumberService, UnsupportedChain } from "@ebo-agent/blocknumber"; import { Caip2ChainId, HexUtils, UnixTimestamp } from "@ebo-agent/shared"; import { + Account, Address, BaseError, Block, @@ -43,7 +44,6 @@ import { import { BlockNumberServiceRequiredError, ErrorFactory, - InvalidAccountOnClient, InvalidBlockHashError, InvalidBlockRangeError, RpcUrlsEmpty, @@ -75,7 +75,7 @@ type ProtocolRpcConfig = { export class ProtocolProvider implements IProtocolProvider { private l1ReadClient: PublicClient>; private l2ReadClient: PublicClient>; - private l2WriteClient: WalletClient>; + private l2WriteClient: WalletClient, Chain, Account>; private readonly blockNumberService?: BlockNumberService; private oracleContract: GetContractReturnType< @@ -135,11 +135,13 @@ export class ProtocolProvider implements IProtocolProvider { abi: oracleAbi, client: this.l2WriteClient, }); + this.epochManagerContract = getContract({ address: contracts.epochManager, abi: epochManagerAbi, client: this.l2ReadClient, }); + this.eboRequestCreatorContract = getContract({ address: contracts.eboRequestCreator, abi: eboRequestCreatorAbi, @@ -148,6 +150,7 @@ export class ProtocolProvider implements IProtocolProvider { wallet: this.l2WriteClient, }, }); + this.bondEscalationContract = getContract({ address: contracts.bondEscalationModule, abi: bondEscalationModuleAbi, @@ -156,6 +159,7 @@ export class ProtocolProvider implements IProtocolProvider { wallet: this.l2WriteClient, }, }); + this.horizonAccountingExtensionContract = getContract({ address: contracts.horizonAccountingExtension, abi: horizonAccountingExtensionAbi, @@ -175,7 +179,6 @@ 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), approveModule: this.approveModule.bind(this), }; @@ -185,7 +188,6 @@ export class ProtocolProvider implements IProtocolProvider { getEvents: this.getEvents.bind(this), getAvailableChains: this.getAvailableChains.bind(this), getAccountingModuleAddress: this.getAccountingModuleAddress.bind(this), - getAccountingApprovedModules: this.getAccountingApprovedModules.bind(this), getApprovedModules: this.getApprovedModules.bind(this), }; @@ -216,7 +218,7 @@ export class ProtocolProvider implements IProtocolProvider { config: RpcConfig, chain: Chain, privateKey: Hex, - ): WalletClient> { + ): WalletClient, Chain, Account> { const { urls, timeout, retryInterval } = config; const account = privateKeyToAccount(privateKey); @@ -257,12 +259,8 @@ export class ProtocolProvider implements IProtocolProvider { * Returns the address of the account used for transactions. * * @returns {Address} The account address. - * @throws {InvalidAccountOnClient} Throws if the write client does not have an assigned account. */ public getAccountAddress(): Address { - if (!this.l2WriteClient.account) { - throw new InvalidAccountOnClient(); - } return this.l2WriteClient.account.address; } @@ -760,20 +758,17 @@ export class ProtocolProvider implements IProtocolProvider { /** * Gets the list of approved modules' addresses for a given user. * - * @param {Address} user - The address of the user. + * @param {Address} user - The address of the user. If not specified, it fallbacks to the L2 account address. * @returns {Promise} A promise that resolves with an array of approved modules for the user. */ - async getApprovedModules(user: Address): Promise { - return [...(await this.horizonAccountingExtensionContract.read.approvedModules([user]))]; - } + async getApprovedModules(user?: Address): Promise { + const bondAddress = user ?? this.getAccountAddress(); - async getAccountingApprovedModules(): Promise { - // TODO: implement actual method - return []; - } + const modules = await this.horizonAccountingExtensionContract.read.approvedModules([ + bondAddress, + ]); - async approveAccountingModules(_modules: Address[]): Promise { - // TODO: implement actual method + return modules; } // TODO: waiting for ChainId to be merged for _chains parameter diff --git a/packages/automated-dispute/src/services/eboProcessor.ts b/packages/automated-dispute/src/services/eboProcessor.ts index 2f435da2..9d55db94 100644 --- a/packages/automated-dispute/src/services/eboProcessor.ts +++ b/packages/automated-dispute/src/services/eboProcessor.ts @@ -1,14 +1,7 @@ import { isNativeError } from "util/types"; import { BlockNumberService } from "@ebo-agent/blocknumber"; -import { - Caip2ChainId, - Caip2Utils, - HexUtils, - ILogger, - stringify, - UnixTimestamp, -} from "@ebo-agent/shared"; -import { Block, ContractFunctionRevertedError } from "viem"; +import { Caip2ChainId, Caip2Utils, ILogger, stringify, UnixTimestamp } from "@ebo-agent/shared"; +import { Address, Block, ContractFunctionRevertedError } from "viem"; import { PastEventEnqueueError, @@ -86,8 +79,8 @@ export class EboProcessor { * @throws {PendingModulesApproval} when there is at least one module pending approval */ private async checkAllModulesApproved() { - const approvedModules: HexUtils[] = - await this.protocolProvider.getAccountingApprovedModules(); + const approvedModules: readonly Address[] = + await this.protocolProvider.getApprovedModules(); const summary: Record<"approved" | "notApproved", Partial> = { approved: {}, diff --git a/packages/automated-dispute/src/templates/index.ts b/packages/automated-dispute/src/templates/index.ts index 1a3bdd54..803f9797 100644 --- a/packages/automated-dispute/src/templates/index.ts +++ b/packages/automated-dispute/src/templates/index.ts @@ -19,12 +19,13 @@ export const pendingApprovedModulesError = ( approvedModules: Partial, notApprovedModules: Partial, ) => { - const approvedModulesList = Object.entries(approvedModules).map( - ([key, value]) => `* ${key} at ${value}\n`, - ); - const notApprovedModulesList = Object.entries(notApprovedModules).map( - ([key, value]) => `* ${key} at ${value}\n`, - ); + const approvedModulesList = Object.entries(approvedModules) + .map(([key, value]) => `* ${key} at ${value}`) + .join("\n"); + + const notApprovedModulesList = Object.entries(notApprovedModules) + .map(([key, value]) => `* ${key} at ${value}`) + .join("\n"); return ` The EBO agent cannot proceed until certain actions are resolved by the operator. diff --git a/packages/automated-dispute/src/types/prophet.ts b/packages/automated-dispute/src/types/prophet.ts index 052aa57c..eb21b609 100644 --- a/packages/automated-dispute/src/types/prophet.ts +++ b/packages/automated-dispute/src/types/prophet.ts @@ -107,7 +107,6 @@ export interface Dispute { } export type AccountingModules = { - requestModule: Address; responseModule: Address; escalationModule: Address; }; diff --git a/packages/automated-dispute/tests/services/eboProcessor.spec.ts b/packages/automated-dispute/tests/services/eboProcessor.spec.ts index 320a4d73..a30282ef 100644 --- a/packages/automated-dispute/tests/services/eboProcessor.spec.ts +++ b/packages/automated-dispute/tests/services/eboProcessor.spec.ts @@ -53,7 +53,7 @@ describe("EboProcessor", () => { notifier, ); - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue([]); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue([]); const result = processor.start(); @@ -87,9 +87,7 @@ describe("EboProcessor", () => { }, }; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue( lastFinalizedBlock, @@ -153,9 +151,7 @@ describe("EboProcessor", () => { number: (currentEpoch.firstBlockNumber + 10n) as UnixTimestamp, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue( lastFinalizedBlock, @@ -182,9 +178,7 @@ describe("EboProcessor", () => { number: (currentEpoch.firstBlockNumber + 10n) as UnixTimestamp, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue( lastFinalizedBlock, @@ -227,9 +221,7 @@ describe("EboProcessor", () => { }, }; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); vi.spyOn(actorsManager, "createActor").mockReturnValue(actor); @@ -279,9 +271,7 @@ describe("EboProcessor", () => { }, }; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); vi.spyOn(actorsManager, "createActor").mockReturnValue(actor); @@ -327,9 +317,7 @@ describe("EboProcessor", () => { }) .mockResolvedValueOnce([]); - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getLastFinalizedBlock") .mockResolvedValueOnce({ number: initialCurrentBlock + 10n } as unknown as Block< @@ -403,9 +391,7 @@ describe("EboProcessor", () => { }, }; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); vi.spyOn(actorsManager, "createActor").mockReturnValue(actor); @@ -440,9 +426,7 @@ describe("EboProcessor", () => { number: currentEpoch.firstBlockNumber + 10n, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); @@ -513,9 +497,7 @@ describe("EboProcessor", () => { number: currentEpoch.firstBlockNumber + 10n, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); @@ -617,9 +599,7 @@ describe("EboProcessor", () => { "finalized" >; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue( lastFinalizedBlock, @@ -663,9 +643,7 @@ describe("EboProcessor", () => { number: 1n, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue( lastFinalizedBlock, @@ -707,9 +685,7 @@ describe("EboProcessor", () => { number: 1n, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue( lastFinalizedBlock, @@ -747,9 +723,7 @@ describe("EboProcessor", () => { number: currentEpoch.number + 10n, } as unknown as Block; - vi.spyOn(protocolProvider, "getAccountingApprovedModules").mockResolvedValue( - allModulesApproved, - ); + vi.spyOn(protocolProvider, "getApprovedModules").mockResolvedValue(allModulesApproved); vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); diff --git a/packages/automated-dispute/tests/services/protocolProvider.spec.ts b/packages/automated-dispute/tests/services/protocolProvider.spec.ts index ba62d243..e2678283 100644 --- a/packages/automated-dispute/tests/services/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/services/protocolProvider.spec.ts @@ -11,7 +11,7 @@ import { http, WaitForTransactionReceiptTimeoutError, } from "viem"; -import { privateKeyToAccount } from "viem/accounts"; +import { privateKeyToAccount, privateKeyToAddress } from "viem/accounts"; import { arbitrum } from "viem/chains"; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; @@ -24,7 +24,6 @@ import { } from "../../src/abis/index.js"; import { BlockNumberServiceRequiredError, - InvalidAccountOnClient, RpcUrlsEmpty, TransactionExecutionError, } from "../../src/exceptions/index.js"; @@ -643,19 +642,6 @@ describe("ProtocolProvider", () => { const expectedAddress = privateKeyToAccount(mockedPrivateKey).address; expect(protocolProvider.getAccountAddress()).toBe(expectedAddress); }); - - it("throws InvalidAccountOnClient when there's no account", () => { - const protocolProvider = new ProtocolProvider( - mockRpcConfig, - mockContractAddress, - mockedPrivateKey, - mockBlockNumberService, - ); - - (protocolProvider["l2WriteClient"] as any).account = undefined; - - expect(() => protocolProvider.getAccountAddress()).toThrow(InvalidAccountOnClient); - }); }); describe("pledgeForDispute", () => { @@ -864,6 +850,29 @@ describe("ProtocolProvider", () => { ).toHaveBeenCalledWith([mockUserAddress]); }); + it("uses private key account as default", async () => { + const protocolProvider = new ProtocolProvider( + mockRpcConfig, + mockContractAddress, + mockedPrivateKey, + ); + + const approvedModules = []; + + const mockHorizonAccountingApprovedModules = vi + .spyOn( + protocolProvider["horizonAccountingExtensionContract"].read, + "approvedModules", + ) + .mockResolvedValue(approvedModules); + + await protocolProvider.getApprovedModules(); + + expect(mockHorizonAccountingApprovedModules).toHaveBeenCalledWith([ + privateKeyToAddress(mockedPrivateKey), + ]); + }); + it("throws error when RPC client fails", async () => { const protocolProvider = new ProtocolProvider( mockRpcConfig,