From b506597192a18958ed8427d06777cebbc8eacac3 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:54:38 -0500 Subject: [PATCH 01/15] feat: add new exceptions and createRequests --- .env.example | 1 + .gitignore | 5 +- .../src/abis/eboRequestCreator.ts | 409 ++++++++++++++++++ packages/automated-dispute/src/abis/index.ts | 1 + packages/automated-dispute/src/constants.ts | 2 +- packages/automated-dispute/src/eboActor.ts | 16 +- .../src/exceptions/chainNotAdded.exception.ts | 6 + .../contractFunctionReverted.exception.ts | 7 + .../automated-dispute/src/exceptions/index.ts | 5 + .../src/exceptions/invalidEpoch.exception.ts | 6 + .../invalidRequestBody.exception.ts | 6 + .../exceptions/invalidRequester.exception.ts | 6 + .../automated-dispute/src/protocolProvider.ts | 59 ++- 13 files changed, 521 insertions(+), 8 deletions(-) create mode 100644 .env.example create mode 100644 packages/automated-dispute/src/abis/eboRequestCreator.ts create mode 100644 packages/automated-dispute/src/exceptions/chainNotAdded.exception.ts create mode 100644 packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts create mode 100644 packages/automated-dispute/src/exceptions/invalidEpoch.exception.ts create mode 100644 packages/automated-dispute/src/exceptions/invalidRequestBody.exception.ts create mode 100644 packages/automated-dispute/src/exceptions/invalidRequester.exception.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4cd1c23 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +WALLET_CLIENT_PRIVATE_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index d9cc783..02001d2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ node_modules/ dist/ # Coverage -coverage/ \ No newline at end of file +coverage/ + +# IDE project settings +.idea/ \ No newline at end of file diff --git a/packages/automated-dispute/src/abis/eboRequestCreator.ts b/packages/automated-dispute/src/abis/eboRequestCreator.ts new file mode 100644 index 0000000..d2775f3 --- /dev/null +++ b/packages/automated-dispute/src/abis/eboRequestCreator.ts @@ -0,0 +1,409 @@ +export const eboRequestCreatorAbi = [ + { + type: "constructor", + inputs: [ + { name: "_oracle", type: "address", internalType: "contract IOracle" }, + { name: "_epochManager", type: "address", internalType: "contract IEpochManager" }, + { name: "_arbitrator", type: "address", internalType: "address" }, + { name: "_council", type: "address", internalType: "address" }, + { + name: "_requestData", + type: "tuple", + internalType: "struct IOracle.Request", + components: [ + { name: "nonce", type: "uint96", internalType: "uint96" }, + { name: "requester", type: "address", internalType: "address" }, + { name: "requestModule", type: "address", internalType: "address" }, + { name: "responseModule", type: "address", internalType: "address" }, + { name: "disputeModule", type: "address", internalType: "address" }, + { name: "resolutionModule", type: "address", internalType: "address" }, + { name: "finalityModule", type: "address", internalType: "address" }, + { name: "requestModuleData", type: "bytes", internalType: "bytes" }, + { name: "responseModuleData", type: "bytes", internalType: "bytes" }, + { name: "disputeModuleData", type: "bytes", internalType: "bytes" }, + { name: "resolutionModuleData", type: "bytes", internalType: "bytes" }, + { name: "finalityModuleData", type: "bytes", internalType: "bytes" }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "ORACLE", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "contract IOracle" }], + stateMutability: "view", + }, + { + type: "function", + name: "START_EPOCH", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "addChain", + inputs: [{ name: "_chainId", type: "string", internalType: "string" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "arbitrator", + inputs: [], + outputs: [{ name: "__arbitrator", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "confirmCouncil", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "council", + inputs: [], + outputs: [{ name: "__council", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "createRequests", + inputs: [ + { name: "_epoch", type: "uint256", internalType: "uint256" }, + { name: "_chainIds", type: "string[]", internalType: "string[]" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "epochManager", + inputs: [], + outputs: [{ name: "", type: "address", internalType: "contract IEpochManager" }], + stateMutability: "view", + }, + { + type: "function", + name: "pendingCouncil", + inputs: [], + outputs: [{ name: "__pendingCouncil", type: "address", internalType: "address" }], + stateMutability: "view", + }, + { + type: "function", + name: "removeChain", + inputs: [{ name: "_chainId", type: "string", internalType: "string" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "requestData", + inputs: [], + outputs: [ + { name: "nonce", type: "uint96", internalType: "uint96" }, + { name: "requester", type: "address", internalType: "address" }, + { name: "requestModule", type: "address", internalType: "address" }, + { name: "responseModule", type: "address", internalType: "address" }, + { name: "disputeModule", type: "address", internalType: "address" }, + { name: "resolutionModule", type: "address", internalType: "address" }, + { name: "finalityModule", type: "address", internalType: "address" }, + { name: "requestModuleData", type: "bytes", internalType: "bytes" }, + { name: "responseModuleData", type: "bytes", internalType: "bytes" }, + { name: "disputeModuleData", type: "bytes", internalType: "bytes" }, + { name: "resolutionModuleData", type: "bytes", internalType: "bytes" }, + { name: "finalityModuleData", type: "bytes", internalType: "bytes" }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "requestIdPerChainAndEpoch", + inputs: [ + { name: "_chainId", type: "string", internalType: "string" }, + { name: "_epoch", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "_requestId", type: "bytes32", internalType: "bytes32" }], + stateMutability: "view", + }, + { + type: "function", + name: "setArbitrator", + inputs: [{ name: "__arbitrator", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setDisputeModuleData", + inputs: [ + { name: "_disputeModule", type: "address", internalType: "address" }, + { + name: "_disputeModuleData", + type: "tuple", + internalType: "struct IBondEscalationModule.RequestParameters", + components: [ + { + name: "accountingExtension", + type: "address", + internalType: "contract IBondEscalationAccounting", + }, + { name: "bondToken", type: "address", internalType: "contract IERC20" }, + { name: "bondSize", type: "uint256", internalType: "uint256" }, + { name: "maxNumberOfEscalations", type: "uint256", internalType: "uint256" }, + { name: "bondEscalationDeadline", type: "uint256", internalType: "uint256" }, + { name: "tyingBuffer", type: "uint256", internalType: "uint256" }, + { name: "disputeWindow", type: "uint256", internalType: "uint256" }, + ], + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setEpochManager", + inputs: [ + { name: "_epochManager", type: "address", internalType: "contract IEpochManager" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setFinalityModuleData", + inputs: [ + { name: "_finalityModule", type: "address", internalType: "address" }, + { name: "_finalityModuleData", type: "bytes", internalType: "bytes" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setPendingCouncil", + inputs: [{ name: "__pendingCouncil", type: "address", internalType: "address" }], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setRequestModuleData", + inputs: [ + { name: "_requestModule", type: "address", internalType: "address" }, + { + name: "_requestModuleData", + type: "tuple", + internalType: "struct IEBORequestModule.RequestParameters", + components: [ + { name: "epoch", type: "uint256", internalType: "uint256" }, + { name: "chainId", type: "string", internalType: "string" }, + { + name: "accountingExtension", + type: "address", + internalType: "contract IAccountingExtension", + }, + { name: "paymentAmount", type: "uint256", internalType: "uint256" }, + ], + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setResolutionModuleData", + inputs: [ + { name: "_resolutionModule", type: "address", internalType: "address" }, + { name: "_resolutionModuleData", type: "bytes", internalType: "bytes" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setResponseModuleData", + inputs: [ + { name: "_responseModule", type: "address", internalType: "address" }, + { + name: "_responseModuleData", + type: "tuple", + internalType: "struct IBondedResponseModule.RequestParameters", + components: [ + { + name: "accountingExtension", + type: "address", + internalType: "contract IAccountingExtension", + }, + { name: "bondToken", type: "address", internalType: "contract IERC20" }, + { name: "bondSize", type: "uint256", internalType: "uint256" }, + { name: "deadline", type: "uint256", internalType: "uint256" }, + { name: "disputeWindow", type: "uint256", internalType: "uint256" }, + ], + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "ChainAdded", + inputs: [{ name: "_chainId", type: "string", indexed: true, internalType: "string" }], + anonymous: false, + }, + { + type: "event", + name: "ChainRemoved", + inputs: [{ name: "_chainId", type: "string", indexed: true, internalType: "string" }], + anonymous: false, + }, + { + type: "event", + name: "DisputeModuleDataSet", + inputs: [ + { name: "_disputeModule", type: "address", indexed: true, internalType: "address" }, + { + name: "_disputeModuleData", + type: "tuple", + indexed: false, + internalType: "struct IBondEscalationModule.RequestParameters", + components: [ + { + name: "accountingExtension", + type: "address", + internalType: "contract IBondEscalationAccounting", + }, + { name: "bondToken", type: "address", internalType: "contract IERC20" }, + { name: "bondSize", type: "uint256", internalType: "uint256" }, + { name: "maxNumberOfEscalations", type: "uint256", internalType: "uint256" }, + { name: "bondEscalationDeadline", type: "uint256", internalType: "uint256" }, + { name: "tyingBuffer", type: "uint256", internalType: "uint256" }, + { name: "disputeWindow", type: "uint256", internalType: "uint256" }, + ], + }, + ], + anonymous: false, + }, + { + type: "event", + name: "EpochManagerSet", + inputs: [ + { + name: "_epochManager", + type: "address", + indexed: true, + internalType: "contract IEpochManager", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "FinalityModuleDataSet", + inputs: [ + { name: "_finalityModule", type: "address", indexed: true, internalType: "address" }, + { name: "_finalityModuleData", type: "bytes", indexed: false, internalType: "bytes" }, + ], + anonymous: false, + }, + { + type: "event", + name: "RequestCreated", + inputs: [ + { name: "_requestId", type: "bytes32", indexed: true, internalType: "bytes32" }, + { name: "_epoch", type: "uint256", indexed: true, internalType: "uint256" }, + { name: "_chainId", type: "string", indexed: true, internalType: "string" }, + ], + anonymous: false, + }, + { + type: "event", + name: "RequestModuleDataSet", + inputs: [ + { name: "_requestModule", type: "address", indexed: true, internalType: "address" }, + { + name: "_requestModuleData", + type: "tuple", + indexed: false, + internalType: "struct IEBORequestModule.RequestParameters", + components: [ + { name: "epoch", type: "uint256", internalType: "uint256" }, + { name: "chainId", type: "string", internalType: "string" }, + { + name: "accountingExtension", + type: "address", + internalType: "contract IAccountingExtension", + }, + { name: "paymentAmount", type: "uint256", internalType: "uint256" }, + ], + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ResolutionModuleDataSet", + inputs: [ + { name: "_resolutionModule", type: "address", indexed: true, internalType: "address" }, + { name: "_resolutionModuleData", type: "bytes", indexed: false, internalType: "bytes" }, + ], + anonymous: false, + }, + { + type: "event", + name: "ResponseModuleDataSet", + inputs: [ + { name: "_responseModule", type: "address", indexed: true, internalType: "address" }, + { + name: "_responseModuleData", + type: "tuple", + indexed: false, + internalType: "struct IBondedResponseModule.RequestParameters", + components: [ + { + name: "accountingExtension", + type: "address", + internalType: "contract IAccountingExtension", + }, + { name: "bondToken", type: "address", internalType: "contract IERC20" }, + { name: "bondSize", type: "uint256", internalType: "uint256" }, + { name: "deadline", type: "uint256", internalType: "uint256" }, + { name: "disputeWindow", type: "uint256", internalType: "uint256" }, + ], + }, + ], + anonymous: false, + }, + { + type: "event", + name: "SetArbitrator", + inputs: [{ name: "_arbitrator", type: "address", indexed: true, internalType: "address" }], + anonymous: false, + }, + { + type: "event", + name: "SetCouncil", + inputs: [{ name: "_council", type: "address", indexed: true, internalType: "address" }], + anonymous: false, + }, + { + type: "event", + name: "SetPendingCouncil", + inputs: [ + { name: "_pendingCouncil", type: "address", indexed: true, internalType: "address" }, + ], + anonymous: false, + }, + { type: "error", name: "Arbitrable_OnlyArbitrator", inputs: [] }, + { type: "error", name: "Arbitrable_OnlyCouncil", inputs: [] }, + { type: "error", name: "Arbitrable_OnlyPendingCouncil", inputs: [] }, + { type: "error", name: "EBORequestCreator_ChainAlreadyAdded", inputs: [] }, + { type: "error", name: "EBORequestCreator_ChainNotAdded", inputs: [] }, + { type: "error", name: "EBORequestCreator_InvalidEpoch", inputs: [] }, + { type: "error", name: "EBORequestCreator_InvalidNonce", inputs: [] }, +]; diff --git a/packages/automated-dispute/src/abis/index.ts b/packages/automated-dispute/src/abis/index.ts index b4a23ae..a62db90 100644 --- a/packages/automated-dispute/src/abis/index.ts +++ b/packages/automated-dispute/src/abis/index.ts @@ -1,2 +1,3 @@ export * from "./oracle.js"; export * from "./epochManager.js"; +export * from "./eboRequestCreator.js"; diff --git a/packages/automated-dispute/src/constants.ts b/packages/automated-dispute/src/constants.ts index 55948ff..9f0f68b 100644 --- a/packages/automated-dispute/src/constants.ts +++ b/packages/automated-dispute/src/constants.ts @@ -1 +1 @@ -export const ProtocolContractsNames = ["oracle", "epochManager"] as const; +export const ProtocolContractsNames = ["oracle", "epochManager", "eboRequestCreator"] as const; diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index 050c677..e8feb64 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -4,8 +4,12 @@ import { ILogger } from "@ebo-agent/shared"; import { ContractFunctionRevertedError } from "viem"; import { + EBORequestCreator_ChainNotAdded, + EBORequestCreator_InvalidEpoch, + EBORequestModule_InvalidRequester, InvalidActorState, InvalidDisputeStatus, + Oracle_InvalidRequestBody, RequestMismatch, ResponseAlreadyProposed, } from "./exceptions/index.js"; @@ -99,7 +103,17 @@ export class EboActor { await this.proposeResponse(chainId); } catch (err) { if (err instanceof ResponseAlreadyProposed) this.logger.info(err.message); - else throw err; + else if (err instanceof EBORequestCreator_InvalidEpoch) { + // TODO: Handle error + } else if (err instanceof Oracle_InvalidRequestBody) { + // TODO: Handle error + } else if (err instanceof EBORequestModule_InvalidRequester) { + // TODO: Handle error + } else if (err instanceof EBORequestCreator_ChainNotAdded) { + // TODO: Handle error + } else { + throw err; + } } } diff --git a/packages/automated-dispute/src/exceptions/chainNotAdded.exception.ts b/packages/automated-dispute/src/exceptions/chainNotAdded.exception.ts new file mode 100644 index 0000000..2e5912d --- /dev/null +++ b/packages/automated-dispute/src/exceptions/chainNotAdded.exception.ts @@ -0,0 +1,6 @@ +export class EBORequestCreator_ChainNotAdded extends Error { + constructor() { + super("Chain not added"); + this.name = "EBORequestCreator_ChainNotAdded"; + } +} diff --git a/packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts b/packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts new file mode 100644 index 0000000..152d14c --- /dev/null +++ b/packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts @@ -0,0 +1,7 @@ +export class ContractFunctionReverted extends Error { + constructor() { + super(`Contract function reverted`); + + this.name = "ContractFunctionReverted"; + } +} diff --git a/packages/automated-dispute/src/exceptions/index.ts b/packages/automated-dispute/src/exceptions/index.ts index 840498a..133a777 100644 --- a/packages/automated-dispute/src/exceptions/index.ts +++ b/packages/automated-dispute/src/exceptions/index.ts @@ -4,3 +4,8 @@ export * from "./requestAlreadyHandled.exception.js"; export * from "./requestMismatch.exception.js"; export * from "./responseAlreadyProposed.exception.js"; export * from "./rpcUrlsEmpty.exception.js"; +export * from "./chainNotAdded.exception.js"; +export * from "./invalidEpoch.exception.js"; +export * from "./invalidRequestBody.exception.js"; +export * from "./invalidRequester.exception.js"; +export * from "./contractFunctionReverted.exception.js"; diff --git a/packages/automated-dispute/src/exceptions/invalidEpoch.exception.ts b/packages/automated-dispute/src/exceptions/invalidEpoch.exception.ts new file mode 100644 index 0000000..f5dd1d1 --- /dev/null +++ b/packages/automated-dispute/src/exceptions/invalidEpoch.exception.ts @@ -0,0 +1,6 @@ +export class EBORequestCreator_InvalidEpoch extends Error { + constructor() { + super("Invalid epoch"); + this.name = "EBORequestCreator_InvalidEpoch"; + } +} diff --git a/packages/automated-dispute/src/exceptions/invalidRequestBody.exception.ts b/packages/automated-dispute/src/exceptions/invalidRequestBody.exception.ts new file mode 100644 index 0000000..969e261 --- /dev/null +++ b/packages/automated-dispute/src/exceptions/invalidRequestBody.exception.ts @@ -0,0 +1,6 @@ +export class Oracle_InvalidRequestBody extends Error { + constructor() { + super("Invalid request body"); + this.name = "Oracle_InvalidRequestBody"; + } +} diff --git a/packages/automated-dispute/src/exceptions/invalidRequester.exception.ts b/packages/automated-dispute/src/exceptions/invalidRequester.exception.ts new file mode 100644 index 0000000..fdf831b --- /dev/null +++ b/packages/automated-dispute/src/exceptions/invalidRequester.exception.ts @@ -0,0 +1,6 @@ +export class EBORequestModule_InvalidRequester extends Error { + constructor() { + super("Invalid requester"); + this.name = "EBORequestModule_InvalidRequester"; + } +} diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index 7d35550..750f35d 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -3,6 +3,7 @@ import { Timestamp } from "@ebo-agent/shared"; import { Address, createPublicClient, + createWalletClient, fallback, FallbackTransport, getContract, @@ -10,23 +11,38 @@ import { http, HttpTransport, PublicClient, + WalletClient, } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; import { arbitrum } from "viem/chains"; import type { EboEvent, EboEventName } from "./types/events.js"; import type { Dispute, Request, Response } from "./types/prophet.js"; -import { epochManagerAbi, oracleAbi } from "./abis/index.js"; +import { eboRequestCreatorAbi, epochManagerAbi, oracleAbi } from "./abis/index.js"; +import { + ContractFunctionReverted, + EBORequestCreator_ChainNotAdded, + EBORequestCreator_InvalidEpoch, + EBORequestModule_InvalidRequester, + Oracle_InvalidRequestBody, +} from "./exceptions/index.js"; import { RpcUrlsEmpty } from "./exceptions/rpcUrlsEmpty.exception.js"; import { ProtocolContractsAddresses } from "./types/protocolProvider.js"; export class ProtocolProvider { private client: PublicClient>; + private walletClient: WalletClient>; private oracleContract: GetContractReturnType; private epochManagerContract: GetContractReturnType< typeof epochManagerAbi, typeof this.client, Address >; + private eboRequestCreatorContract: GetContractReturnType< + typeof eboRequestCreatorAbi, + typeof this.walletClient, + Address + >; /** * Creates a new ProtocolProvider instance @@ -41,6 +57,15 @@ export class ProtocolProvider { chain: arbitrum, transport: fallback(rpcUrls.map((url) => http(url))), }); + + const account = privateKeyToAccount(process.env.WALLET_CLIENT_PRIVATE_KEY as `0x${string}`); + + this.walletClient = createWalletClient({ + account: account, + chain: arbitrum, + transport: fallback(rpcUrls.map((url) => http(url))), + }); + // Instantiate all the protocol contracts this.oracleContract = getContract({ address: contracts.oracle, @@ -52,6 +77,14 @@ export class ProtocolProvider { abi: epochManagerAbi, client: this.client, }); + this.eboRequestCreatorContract = getContract({ + address: contracts.eboRequestCreator, + abi: eboRequestCreatorAbi, + client: { + public: this.client, + wallet: this.walletClient, + }, + }); } /** @@ -190,10 +223,26 @@ export class ProtocolProvider { } // TODO: waiting for ChainId to be merged for _chains parameter - async createRequest(_epoch: bigint, _chains: string[]): Promise { - // TODO: implement actual method - - return; + async createRequest(epoch: bigint, chains: string[]): Promise { + try { + if (!this.eboRequestCreatorContract?.write?.createRequests) { + throw new Error("createRequests function is not available on the ABI"); + } + await this.eboRequestCreatorContract.write.createRequests([epoch, chains]); + } catch (error) { + if (error instanceof ContractFunctionReverted) { + } else if (error instanceof EBORequestCreator_InvalidEpoch) { + // TODO: Implement error + } else if (error instanceof Oracle_InvalidRequestBody) { + // TODO: Implement error + } else if (error instanceof EBORequestModule_InvalidRequester) { + // TODO: Implement error + } else if (error instanceof EBORequestCreator_ChainNotAdded) { + // TODO: Implement error + } else { + throw error; + } + } } async proposeResponse( From 6f2a0de73063df315d300d0fb54e7c6073d9c59a Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:28:52 -0500 Subject: [PATCH 02/15] feat: fix tests add docs --- .../automated-dispute/src/protocolProvider.ts | 23 +++++++- .../tests/eboActor/fixtures.ts | 3 + .../tests/eboActor/onRequestCreated.spec.ts | 2 + .../tests/eboActorsManager.spec.ts | 2 + .../tests/mocks/eboActor.mocks.ts | 3 +- .../tests/mocks/eboProcessor.mocks.ts | 3 +- .../tests/protocolProvider.spec.ts | 57 +++++++++++++++++-- 7 files changed, 82 insertions(+), 11 deletions(-) diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index eb58099..56b62ee 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -48,22 +48,28 @@ export class ProtocolProvider { * Creates a new ProtocolProvider instance * @param rpcUrls The RPC URLs to connect to the Arbitrum chain * @param contracts The addresses of the protocol contracts that will be instantiated + * @param privateKey The private key of the account that will be used to interact with the contracts */ - constructor(rpcUrls: string[], contracts: ProtocolContractsAddresses) { + constructor( + rpcUrls: string[], + contracts: ProtocolContractsAddresses, + privateKey: `0x${string}`, + ) { if (rpcUrls.length === 0) { throw new RpcUrlsEmpty(); } + this.client = createPublicClient({ chain: arbitrum, transport: fallback(rpcUrls.map((url) => http(url))), }); - const account = privateKeyToAccount(process.env.WALLET_CLIENT_PRIVATE_KEY as `0x${string}`); + const account = privateKeyToAccount(privateKey); this.walletClient = createWalletClient({ - account: account, chain: arbitrum, transport: fallback(rpcUrls.map((url) => http(url))), + account: account, }); // Instantiate all the protocol contracts @@ -202,6 +208,17 @@ export class ProtocolProvider { } // TODO: waiting for ChainId to be merged for _chains parameter + /** + * Creates a new request for the specified epoch and chains. + * + * @param epoch The epoch for which to create the request + * @param chains An array of chain IDs for which to create the request + * @throws {EBORequestCreator_InvalidEpoch} If the epoch is invalid + * @throws {Oracle_InvalidRequestBody} If the request body is invalid + * @throws {EBORequestModule_InvalidRequester} If the requester is invalid + * @throws {EBORequestCreator_ChainNotAdded} If one of the specified chains is not added + * @throws {ContractFunctionReverted} If the contract function reverts for any other reason + */ async createRequest(epoch: bigint, chains: string[]): Promise { try { if (!this.eboRequestCreatorContract?.write?.createRequests) { diff --git a/packages/automated-dispute/tests/eboActor/fixtures.ts b/packages/automated-dispute/tests/eboActor/fixtures.ts index f6aeb65..2283fa7 100644 --- a/packages/automated-dispute/tests/eboActor/fixtures.ts +++ b/packages/automated-dispute/tests/eboActor/fixtures.ts @@ -2,9 +2,12 @@ import { Address } from "viem"; import { Request, RequestId } from "../../src/types/prophet"; +export const privateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + export const DEFAULT_MOCKED_PROTOCOL_CONTRACTS = { oracle: "0x123456" as Address, epochManager: "0x654321" as Address, + eboRequestCreator: "0xabcdef" as Address, }; export const DEFAULT_MOCKED_REQUEST_CREATED_DATA: Request = { diff --git a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts index f80dee1..dedf93d 100644 --- a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts @@ -14,6 +14,7 @@ import mocks from "../mocks/index.js"; import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, DEFAULT_MOCKED_REQUEST_CREATED_DATA, + privateKey, } from "./fixtures.js"; const logger: ILogger = mocks.mockLogger(); @@ -52,6 +53,7 @@ describe("EboActor", () => { protocolProvider = new ProtocolProvider( ["http://localhost:8538"], DEFAULT_MOCKED_PROTOCOL_CONTRACTS, + privateKey, ); const chainRpcUrls = new Map(); diff --git a/packages/automated-dispute/tests/eboActorsManager.spec.ts b/packages/automated-dispute/tests/eboActorsManager.spec.ts index 7760bad..315fdcc 100644 --- a/packages/automated-dispute/tests/eboActorsManager.spec.ts +++ b/packages/automated-dispute/tests/eboActorsManager.spec.ts @@ -10,6 +10,7 @@ import { ProtocolProvider } from "../src/protocolProvider.js"; import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, DEFAULT_MOCKED_REQUEST_CREATED_DATA, + privateKey, } from "./eboActor/fixtures.js"; import mocks from "./mocks/index.js"; @@ -31,6 +32,7 @@ describe("EboActorsManager", () => { protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, DEFAULT_MOCKED_PROTOCOL_CONTRACTS, + privateKey, ); const blockNumberRpcUrls = new Map([ diff --git a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts index 7bfb3a0..c948ce9 100644 --- a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts @@ -7,7 +7,7 @@ import { EboActor } from "../../src/eboActor.js"; import { ProtocolProvider } from "../../src/protocolProvider.js"; import { EboMemoryRegistry } from "../../src/services/index.js"; import { Dispute, Request, Response } from "../../src/types/index.js"; -import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../eboActor/fixtures.js"; +import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, privateKey } from "../eboActor/fixtures.js"; /** * Builds a base `EboActor` scaffolded with all its dependencies. @@ -23,6 +23,7 @@ export function buildEboActor(request: Request, logger: ILogger) { const protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, DEFAULT_MOCKED_PROTOCOL_CONTRACTS, + privateKey, ); const blockNumberRpcUrls = new Map([ diff --git a/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts b/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts index e1191dd..b419f6a 100644 --- a/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts @@ -5,13 +5,14 @@ import { ILogger } from "@ebo-agent/shared"; import { EboActorsManager } from "../../src/eboActorsManager"; import { ProtocolProvider } from "../../src/protocolProvider"; import { EboProcessor } from "../../src/services"; -import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../eboActor/fixtures"; +import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, privateKey } from "../eboActor/fixtures"; export function buildEboProcessor(logger: ILogger) { const protocolProviderRpcUrls = ["http://localhost:8538"]; const protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, DEFAULT_MOCKED_PROTOCOL_CONTRACTS, + privateKey, ); const blockNumberRpcUrls = new Map([ diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index 337c649..a2e9289 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -1,12 +1,14 @@ -import { createPublicClient, fallback, getContract, http } from "viem"; +import { createPublicClient, createWalletClient, fallback, getContract, http } from "viem"; import { arbitrum } from "viem/chains"; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; +import { eboRequestCreatorAbi } from "../src/abis/eboRequestCreator.js"; import { epochManagerAbi } from "../src/abis/epochManager.js"; import { oracleAbi } from "../src/abis/oracle.js"; import { RpcUrlsEmpty } from "../src/exceptions/rpcUrlsEmpty.exception.js"; import { ProtocolProvider } from "../src/index.js"; import { ProtocolContractsAddresses } from "../src/types/index.js"; +import { privateKey } from "./eboActor/fixtures.js"; vi.mock("viem", async () => { const actual = await vi.importActual("viem"); @@ -15,6 +17,7 @@ vi.mock("viem", async () => { http: vi.fn(), fallback: vi.fn(), createPublicClient: vi.fn(), + createWalletClient: vi.fn(), getContract: vi.fn(), }; }); @@ -24,7 +27,9 @@ describe("ProtocolProvider", () => { const mockContractAddress: ProtocolContractsAddresses = { oracle: "0x1234567890123456789012345678901234567890", epochManager: "0x1234567890123456789012345678901234567890", + eboRequestCreator: "0x1234567890123456789012345678901234567890", }; + beforeEach(() => { (getContract as Mock).mockImplementation(({ address, abi }) => { if (abi == oracleAbi && address == mockContractAddress.oracle) { @@ -38,6 +43,13 @@ describe("ProtocolProvider", () => { }, }; } + if (abi == eboRequestCreatorAbi && address == mockContractAddress.eboRequestCreator) { + return { + write: { + createRequests: vi.fn(), + }, + }; + } throw new Error("Invalid contract address or ABI"); }); (http as Mock).mockImplementation((url) => url); @@ -50,12 +62,31 @@ describe("ProtocolProvider", () => { describe("constructor", () => { it("creates a new ProtocolProvider instance successfully", () => { - const protocolProvider = new ProtocolProvider(mockRpcUrls, mockContractAddress); + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); expect(createPublicClient).toHaveBeenCalledWith({ chain: arbitrum, transport: fallback(mockRpcUrls.map((url) => http(url))), }); + + expect(createWalletClient).toHaveBeenCalledWith({ + chain: arbitrum, + transport: fallback(mockRpcUrls.map((url) => http(url))), + account: expect.objectContaining({ + address: expect.any(String), + publicKey: expect.any(String), + signMessage: expect.any(Function), + signTransaction: expect.any(Function), + signTypedData: expect.any(Function), + source: "privateKey", + type: "local", + }), + }); + expect(getContract).toHaveBeenCalledWith({ address: mockContractAddress.oracle, abi: oracleAbi, @@ -68,7 +99,9 @@ describe("ProtocolProvider", () => { }); }); it("throws if rpcUrls are empty", () => { - expect(() => new ProtocolProvider([], mockContractAddress)).toThrowError(RpcUrlsEmpty); + expect(() => new ProtocolProvider([], mockContractAddress, privateKey)).toThrowError( + RpcUrlsEmpty, + ); }); }); describe("getCurrentEpoch", () => { @@ -81,7 +114,11 @@ describe("ProtocolProvider", () => { getBlock: vi.fn().mockResolvedValue({ timestamp: mockEpochTimestamp }), }); - const protocolProvider = new ProtocolProvider(mockRpcUrls, mockContractAddress); + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); (protocolProvider["epochManagerContract"].read.currentEpoch as Mock).mockResolvedValue( mockEpoch, @@ -97,7 +134,11 @@ describe("ProtocolProvider", () => { expect(result.currentEpochBlockNumber).toBe(mockEpochBlock); }); it("throws when current epoch request fails", async () => { - const protocolProvider = new ProtocolProvider(mockRpcUrls, mockContractAddress); + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); const error = new Error("Failed to get current epoch"); const mockEpochBlock = BigInt(12345); @@ -111,7 +152,11 @@ describe("ProtocolProvider", () => { await expect(protocolProvider.getCurrentEpoch()).rejects.toThrow(error); }); it("throws when current epoch block request fails", async () => { - const protocolProvider = new ProtocolProvider(mockRpcUrls, mockContractAddress); + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); const error = new Error("Failed to get current epoch block"); const mockEpoch = BigInt(12345); From 4e859244366cd9fc9f2322a0321b2ffe7890a0fd Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:56:52 -0500 Subject: [PATCH 03/15] feat: add tests --- .../automated-dispute/src/protocolProvider.ts | 13 +- .../tests/protocolProvider.spec.ts | 187 ++++++++++++++---- 2 files changed, 151 insertions(+), 49 deletions(-) diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index 56b62ee..b77b6e9 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -226,15 +226,16 @@ export class ProtocolProvider { } await this.eboRequestCreatorContract.write.createRequests([epoch, chains]); } catch (error) { - if (error instanceof ContractFunctionReverted) { - } else if (error instanceof EBORequestCreator_InvalidEpoch) { - // TODO: Implement error + if (error instanceof EBORequestCreator_InvalidEpoch) { + throw new EBORequestCreator_InvalidEpoch(); } else if (error instanceof Oracle_InvalidRequestBody) { - // TODO: Implement error + throw new Oracle_InvalidRequestBody(); } else if (error instanceof EBORequestModule_InvalidRequester) { - // TODO: Implement error + throw new EBORequestModule_InvalidRequester(); } else if (error instanceof EBORequestCreator_ChainNotAdded) { - // TODO: Implement error + throw new EBORequestCreator_ChainNotAdded(); + } else if (error instanceof ContractFunctionReverted) { + throw new ContractFunctionReverted(); } else { throw error; } diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index a2e9289..aef8348 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -5,6 +5,11 @@ import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; import { eboRequestCreatorAbi } from "../src/abis/eboRequestCreator.js"; import { epochManagerAbi } from "../src/abis/epochManager.js"; import { oracleAbi } from "../src/abis/oracle.js"; +import { EBORequestCreator_ChainNotAdded } from "../src/exceptions/chainNotAdded.exception.js"; +import { ContractFunctionReverted } from "../src/exceptions/contractFunctionReverted.exception.js"; +import { EBORequestCreator_InvalidEpoch } from "../src/exceptions/invalidEpoch.exception.js"; +import { Oracle_InvalidRequestBody } from "../src/exceptions/invalidRequestBody.exception.js"; +import { EBORequestModule_InvalidRequester } from "../src/exceptions/invalidRequester.exception.js"; import { RpcUrlsEmpty } from "../src/exceptions/rpcUrlsEmpty.exception.js"; import { ProtocolProvider } from "../src/index.js"; import { ProtocolContractsAddresses } from "../src/types/index.js"; @@ -104,6 +109,7 @@ describe("ProtocolProvider", () => { ); }); }); + describe("getCurrentEpoch", () => { it("returns currentEpoch and currentEpochBlock successfully", async () => { const mockEpoch = BigInt(1); @@ -171,56 +177,151 @@ describe("ProtocolProvider", () => { }); }); - describe.skip("getEvents", () => { - it("returns all events ordered asc by block and log index"); - it.skip("includes `new epoch` event if needed"); // TODO: confirm if this is needed - it.skip("includes `request can be finalized` event if needed"); // TODO: confirm if this is needed - it("throws if the block range is not consistent"); - it("throws if the RPC client fails"); - }); + describe("createRequest", () => { + it("succeeds if the RPC client sent the request", async () => { + const mockEpoch = BigInt(1); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); - describe.skip("hasStakedAssets", () => { - it("returns true if the address has more than 0 assets staked"); - it("returns false if the address has 0 staked assets"); - }); + const mockCreateRequests = vi.fn().mockResolvedValue(undefined); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); - describe.skip("createRequest", () => { - it("succeeds if the RPC client sent the request"); - // NOTE: Should we validate if the request was created by - // tracking the transaction result somehow? I feel like it's - // somewhat brittle to just wish for the tx to be processed. - it("throws if the epoch is not current"); - it("throws if chains is empty"); - it("throws if the RPC client fails"); - }); + await protocolProvider.createRequest(mockEpoch, mockChains); - describe.skip("getAvailableChains", () => { - it("returns an array of available chains in CAIP-2 compliant format"); - it("throws if the RPC client fails"); - }); + expect(mockCreateRequests).toHaveBeenCalledWith([mockEpoch, mockChains]); + }); - describe.skip("proposeResponse", () => { - it("returns if the RPC client sent the response"); - it("throws if the RPC client fails"); - }); + it("throws EBORequestCreator_InvalidEpoch if the epoch is invalid", async () => { + const mockEpoch = BigInt(0); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); - describe.skip("disputeResponse", () => { - it("returns if the RPC client sent the dispute"); - it("throws if the RPC client fails"); - }); + const mockCreateRequests = vi + .fn() + .mockRejectedValue(new EBORequestCreator_InvalidEpoch()); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); - describe.skip("pledgeForDispute", () => { - it("returns if the RPC client sent the pledge"); - it("throws if the RPC client fails"); - }); + await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( + EBORequestCreator_InvalidEpoch, + ); + }); - describe.skip("pledgeAgainsDispute", () => { - it("returns if the RPC client sent the pledge"); - it("throws if the RPC client fails"); - }); + it("throws Oracle_InvalidRequestBody if the request body is invalid", async () => { + const mockEpoch = BigInt(1); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); + + const mockCreateRequests = vi.fn().mockRejectedValue(new Oracle_InvalidRequestBody()); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); + + await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( + Oracle_InvalidRequestBody, + ); + }); + + it("throws EBORequestModule_InvalidRequester if the requester is invalid", async () => { + const mockEpoch = BigInt(1); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); + + const mockCreateRequests = vi + .fn() + .mockRejectedValue(new EBORequestModule_InvalidRequester()); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); + + await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( + EBORequestModule_InvalidRequester, + ); + }); + + it("throws EBORequestCreator_ChainNotAdded if one of the specified chains is not added", async () => { + const mockEpoch = BigInt(1); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); + + const mockCreateRequests = vi + .fn() + .mockRejectedValue(new EBORequestCreator_ChainNotAdded()); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); + + await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( + EBORequestCreator_ChainNotAdded, + ); + }); + + it("throws ContractFunctionReverted if the contract function reverts for any other reason", async () => { + const mockEpoch = BigInt(1); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); - describe.skip("finalize", () => { - it("returns if the RPC client finalizes the pledge"); - it("throws if the RPC client fails"); + const mockCreateRequests = vi.fn().mockRejectedValue(new ContractFunctionReverted()); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); + + await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( + ContractFunctionReverted, + ); + }); + + it("throws if the RPC client fails", async () => { + const mockEpoch = BigInt(1); + const mockChains = ["eip155:1", "eip155:42161"]; + const protocolProvider = new ProtocolProvider( + mockRpcUrls, + mockContractAddress, + privateKey, + ); + + const mockCreateRequests = vi.fn().mockRejectedValue(new Error("RPC client error")); + vi.spyOn( + protocolProvider["eboRequestCreatorContract"].write, + "createRequests", + ).mockImplementation(mockCreateRequests); + + await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( + "RPC client error", + ); + }); }); }); From 20c66368f1c21b075041f522716b77976f3ae46f Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:57:55 -0500 Subject: [PATCH 04/15] chore: no env --- .env.example | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 4cd1c23..0000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -WALLET_CLIENT_PRIVATE_KEY= \ No newline at end of file From 24ca7b1919256645c50d843c4c14baaaa83a2ba0 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:09:44 -0500 Subject: [PATCH 05/15] fix: no generic revert --- .../contractFunctionReverted.exception.ts | 7 ------- .../automated-dispute/src/exceptions/index.ts | 1 - .../automated-dispute/src/protocolProvider.ts | 4 ---- .../tests/protocolProvider.spec.ts | 21 ------------------- 4 files changed, 33 deletions(-) delete mode 100644 packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts diff --git a/packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts b/packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts deleted file mode 100644 index 152d14c..0000000 --- a/packages/automated-dispute/src/exceptions/contractFunctionReverted.exception.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class ContractFunctionReverted extends Error { - constructor() { - super(`Contract function reverted`); - - this.name = "ContractFunctionReverted"; - } -} diff --git a/packages/automated-dispute/src/exceptions/index.ts b/packages/automated-dispute/src/exceptions/index.ts index 3419370..407bf03 100644 --- a/packages/automated-dispute/src/exceptions/index.ts +++ b/packages/automated-dispute/src/exceptions/index.ts @@ -12,4 +12,3 @@ export * from "./chainNotAdded.exception.js"; export * from "./invalidEpoch.exception.js"; export * from "./invalidRequestBody.exception.js"; export * from "./invalidRequester.exception.js"; -export * from "./contractFunctionReverted.exception.js"; diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index b77b6e9..5502653 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -20,7 +20,6 @@ import type { EboEvent, EboEventName } from "./types/events.js"; import type { Dispute, Request, Response } from "./types/prophet.js"; import { eboRequestCreatorAbi, epochManagerAbi, oracleAbi } from "./abis/index.js"; import { - ContractFunctionReverted, EBORequestCreator_ChainNotAdded, EBORequestCreator_InvalidEpoch, EBORequestModule_InvalidRequester, @@ -217,7 +216,6 @@ export class ProtocolProvider { * @throws {Oracle_InvalidRequestBody} If the request body is invalid * @throws {EBORequestModule_InvalidRequester} If the requester is invalid * @throws {EBORequestCreator_ChainNotAdded} If one of the specified chains is not added - * @throws {ContractFunctionReverted} If the contract function reverts for any other reason */ async createRequest(epoch: bigint, chains: string[]): Promise { try { @@ -234,8 +232,6 @@ export class ProtocolProvider { throw new EBORequestModule_InvalidRequester(); } else if (error instanceof EBORequestCreator_ChainNotAdded) { throw new EBORequestCreator_ChainNotAdded(); - } else if (error instanceof ContractFunctionReverted) { - throw new ContractFunctionReverted(); } else { throw error; } diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index aef8348..2d1be9c 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -6,7 +6,6 @@ import { eboRequestCreatorAbi } from "../src/abis/eboRequestCreator.js"; import { epochManagerAbi } from "../src/abis/epochManager.js"; import { oracleAbi } from "../src/abis/oracle.js"; import { EBORequestCreator_ChainNotAdded } from "../src/exceptions/chainNotAdded.exception.js"; -import { ContractFunctionReverted } from "../src/exceptions/contractFunctionReverted.exception.js"; import { EBORequestCreator_InvalidEpoch } from "../src/exceptions/invalidEpoch.exception.js"; import { Oracle_InvalidRequestBody } from "../src/exceptions/invalidRequestBody.exception.js"; import { EBORequestModule_InvalidRequester } from "../src/exceptions/invalidRequester.exception.js"; @@ -284,26 +283,6 @@ describe("ProtocolProvider", () => { ); }); - it("throws ContractFunctionReverted if the contract function reverts for any other reason", async () => { - const mockEpoch = BigInt(1); - const mockChains = ["eip155:1", "eip155:42161"]; - const protocolProvider = new ProtocolProvider( - mockRpcUrls, - mockContractAddress, - privateKey, - ); - - const mockCreateRequests = vi.fn().mockRejectedValue(new ContractFunctionReverted()); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); - - await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( - ContractFunctionReverted, - ); - }); - it("throws if the RPC client fails", async () => { const mockEpoch = BigInt(1); const mockChains = ["eip155:1", "eip155:42161"]; From 9bd42861b3bd48a86a4b09e320281982b05bfe21 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:28:28 -0500 Subject: [PATCH 06/15] fix: add back deleted tests --- .../tests/protocolProvider.spec.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index 2d1be9c..14281b1 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -176,6 +176,59 @@ describe("ProtocolProvider", () => { }); }); + describe.skip("getEvents", () => { + it("returns all events ordered asc by block and log index"); + it.skip("includes `new epoch` event if needed"); // TODO: confirm if this is needed + it.skip("includes `request can be finalized` event if needed"); // TODO: confirm if this is needed + it("throws if the block range is not consistent"); + it("throws if the RPC client fails"); + }); + + describe.skip("hasStakedAssets", () => { + it("returns true if the address has more than 0 assets staked"); + it("returns false if the address has 0 staked assets"); + }); + + describe.skip("createRequest", () => { + it("succeeds if the RPC client sent the request"); + // NOTE: Should we validate if the request was created by + // tracking the transaction result somehow? I feel like it's + // somewhat brittle to just wish for the tx to be processed. + it("throws if the epoch is not current"); + it("throws if chains is empty"); + it("throws if the RPC client fails"); + }); + + describe.skip("getAvailableChains", () => { + it("returns an array of available chains in CAIP-2 compliant format"); + it("throws if the RPC client fails"); + }); + + describe.skip("proposeResponse", () => { + it("returns if the RPC client sent the response"); + it("throws if the RPC client fails"); + }); + + describe.skip("disputeResponse", () => { + it("returns if the RPC client sent the dispute"); + it("throws if the RPC client fails"); + }); + + describe.skip("pledgeForDispute", () => { + it("returns if the RPC client sent the pledge"); + it("throws if the RPC client fails"); + }); + + describe.skip("pledgeAgainsDispute", () => { + it("returns if the RPC client sent the pledge"); + it("throws if the RPC client fails"); + }); + + describe.skip("finalize", () => { + it("returns if the RPC client finalizes the pledge"); + it("throws if the RPC client fails"); + }); + describe("createRequest", () => { it("succeeds if the RPC client sent the request", async () => { const mockEpoch = BigInt(1); From e548352a69f554cf475249e5e904248a733a941b Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Thu, 5 Sep 2024 17:52:35 -0500 Subject: [PATCH 07/15] feat: add new protocolProvider logic --- .../src/abis/eboRequestCreator.ts | 2 +- .../automated-dispute/src/protocolProvider.ts | 143 +++++++--- .../src/types/protocolProvider.ts | 51 ++++ .../tests/eboActor/fixtures.ts | 3 +- .../tests/eboActor/onRequestCreated.spec.ts | 4 +- .../tests/eboActorsManager.spec.ts | 4 +- .../tests/mocks/eboActor.mocks.ts | 4 +- .../tests/mocks/eboProcessor.mocks.ts | 4 +- .../tests/protocolProvider.spec.ts | 244 +++++------------- 9 files changed, 234 insertions(+), 225 deletions(-) diff --git a/packages/automated-dispute/src/abis/eboRequestCreator.ts b/packages/automated-dispute/src/abis/eboRequestCreator.ts index d2775f3..128910c 100644 --- a/packages/automated-dispute/src/abis/eboRequestCreator.ts +++ b/packages/automated-dispute/src/abis/eboRequestCreator.ts @@ -406,4 +406,4 @@ export const eboRequestCreatorAbi = [ { type: "error", name: "EBORequestCreator_ChainNotAdded", inputs: [] }, { type: "error", name: "EBORequestCreator_InvalidEpoch", inputs: [] }, { type: "error", name: "EBORequestCreator_InvalidNonce", inputs: [] }, -]; +] as const; diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index 5502653..fca0f0a 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -2,12 +2,15 @@ import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; import { Timestamp } from "@ebo-agent/shared"; import { Address, + BaseError, + ContractFunctionRevertedError, createPublicClient, createWalletClient, fallback, FallbackTransport, getContract, GetContractReturnType, + Hex, http, HttpTransport, PublicClient, @@ -26,20 +29,29 @@ import { Oracle_InvalidRequestBody, } from "./exceptions/index.js"; import { RpcUrlsEmpty } from "./exceptions/rpcUrlsEmpty.exception.js"; -import { ProtocolContractsAddresses } from "./types/protocolProvider.js"; +import { + IProtocolProvider, + IReadProvider, + IWriteProvider, + ProtocolContractsAddresses, +} from "./types/protocolProvider.js"; -export class ProtocolProvider { - private client: PublicClient>; - private walletClient: WalletClient>; - private oracleContract: GetContractReturnType; +export class ProtocolProvider implements IProtocolProvider { + private readClient: PublicClient>; + private writeClient: WalletClient>; + private oracleContract: GetContractReturnType< + typeof oracleAbi, + typeof this.readClient, + Address + >; private epochManagerContract: GetContractReturnType< typeof epochManagerAbi, - typeof this.client, + typeof this.readClient, Address >; private eboRequestCreatorContract: GetContractReturnType< typeof eboRequestCreatorAbi, - typeof this.walletClient, + typeof this.writeClient, Address >; @@ -49,23 +61,19 @@ export class ProtocolProvider { * @param contracts The addresses of the protocol contracts that will be instantiated * @param privateKey The private key of the account that will be used to interact with the contracts */ - constructor( - rpcUrls: string[], - contracts: ProtocolContractsAddresses, - privateKey: `0x${string}`, - ) { + constructor(rpcUrls: string[], contracts: ProtocolContractsAddresses, privateKey: Hex) { if (rpcUrls.length === 0) { throw new RpcUrlsEmpty(); } - this.client = createPublicClient({ + this.readClient = createPublicClient({ chain: arbitrum, transport: fallback(rpcUrls.map((url) => http(url))), }); const account = privateKeyToAccount(privateKey); - this.walletClient = createWalletClient({ + this.writeClient = createWalletClient({ chain: arbitrum, transport: fallback(rpcUrls.map((url) => http(url))), account: account, @@ -75,23 +83,42 @@ export class ProtocolProvider { this.oracleContract = getContract({ address: contracts.oracle, abi: oracleAbi, - client: this.client, + client: this.readClient, }); this.epochManagerContract = getContract({ address: contracts.epochManager, abi: epochManagerAbi, - client: this.client, + client: this.readClient, }); this.eboRequestCreatorContract = getContract({ address: contracts.eboRequestCreator, abi: eboRequestCreatorAbi, client: { - public: this.client, - wallet: this.walletClient, + public: this.readClient, + wallet: this.writeClient, }, }); } + public write: IWriteProvider = { + createRequest: this.createRequest.bind(this), + proposeResponse: this.proposeResponse.bind(this), + disputeResponse: this.disputeResponse.bind(this), + pledgeForDispute: this.pledgeForDispute.bind(this), + pledgeAgainstDispute: this.pledgeAgainstDispute.bind(this), + settleDispute: this.settleDispute.bind(this), + escalateDispute: this.escalateDispute.bind(this), + finalize: this.finalize.bind(this), + }; + + public read: IReadProvider = { + getCurrentEpoch: this.getCurrentEpoch.bind(this), + getLastFinalizedBlock: this.getLastFinalizedBlock.bind(this), + getEvents: this.getEvents.bind(this), + hasStakedAssets: this.hasStakedAssets.bind(this), + getAvailableChains: this.getAvailableChains.bind(this), + }; + /** * Gets the current epoch, the block number and its timestamp of the current epoch * @@ -107,7 +134,7 @@ export class ProtocolProvider { this.epochManagerContract.read.currentEpochBlock(), ]); - const currentEpochBlock = await this.client.getBlock({ + const currentEpochBlock = await this.readClient.getBlock({ blockNumber: currentEpochBlockNumber, }); @@ -119,7 +146,7 @@ export class ProtocolProvider { } async getLastFinalizedBlock(): Promise { - const { number } = await this.client.getBlock({ blockTag: "finalized" }); + const { number } = await this.readClient.getBlock({ blockTag: "finalized" }); return number; } @@ -208,33 +235,69 @@ export class ProtocolProvider { // TODO: waiting for ChainId to be merged for _chains parameter /** - * Creates a new request for the specified epoch and chains. + * Creates a request on the EBO Request Creator contract by simulating the transaction + * and then executing it if the simulation is successful. + * + * This function first simulates the `createRequests` call on the EBO Request Creator contract + * to validate that the transaction will succeed. If the simulation is successful, the transaction + * is executed by the `writeContract` method of the wallet client. The function also handles any + * potential errors that may occur during the simulation or transaction execution. * - * @param epoch The epoch for which to create the request - * @param chains An array of chain IDs for which to create the request - * @throws {EBORequestCreator_InvalidEpoch} If the epoch is invalid - * @throws {Oracle_InvalidRequestBody} If the request body is invalid - * @throws {EBORequestModule_InvalidRequester} If the requester is invalid - * @throws {EBORequestCreator_ChainNotAdded} If one of the specified chains is not added + * @param {bigint} epoch - The epoch for which the request is being created. + * @param {string[]} chains - An array of chain identifiers where the request should be created. + * @throws {Error} Throws an error if the chains array is empty or if the transaction fails. + * @throws {EBORequestCreator_InvalidEpoch} Throws if the epoch is invalid. + * @throws {Oracle_InvalidRequestBody} Throws if the request body is invalid. + * @throws {EBORequestModule_InvalidRequester} Throws if the requester is invalid. + * @throws {EBORequestCreator_ChainNotAdded} Throws if the specified chain is not added. + * @returns {Promise} A promise that resolves when the request is successfully created. */ async createRequest(epoch: bigint, chains: string[]): Promise { + if (chains.length === 0) { + throw new Error("Chains array cannot be empty"); + } + try { - if (!this.eboRequestCreatorContract?.write?.createRequests) { - throw new Error("createRequests function is not available on the ABI"); + const { request } = await this.readClient.simulateContract({ + address: this.eboRequestCreatorContract.address, + abi: eboRequestCreatorAbi, + functionName: "createRequests", + args: [epoch, chains], + account: this.writeClient.account, + }); + + const hash = await this.writeClient.writeContract(request); + + const receipt = await this.readClient.waitForTransactionReceipt({ + hash, + confirmations: 1, + }); + + if (receipt.status !== "success") { + throw new Error("Transaction failed"); } - await this.eboRequestCreatorContract.write.createRequests([epoch, chains]); } catch (error) { - if (error instanceof EBORequestCreator_InvalidEpoch) { - throw new EBORequestCreator_InvalidEpoch(); - } else if (error instanceof Oracle_InvalidRequestBody) { - throw new Oracle_InvalidRequestBody(); - } else if (error instanceof EBORequestModule_InvalidRequester) { - throw new EBORequestModule_InvalidRequester(); - } else if (error instanceof EBORequestCreator_ChainNotAdded) { - throw new EBORequestCreator_ChainNotAdded(); - } else { - throw error; + if (error instanceof BaseError) { + const revertError = error.walk( + (err) => err instanceof ContractFunctionRevertedError, + ); + if (revertError instanceof ContractFunctionRevertedError) { + const errorName = revertError.data?.errorName ?? ""; + switch (errorName) { + case "EBORequestCreator_InvalidEpoch": + throw new EBORequestCreator_InvalidEpoch(); + case "Oracle_InvalidRequestBody": + throw new Oracle_InvalidRequestBody(); + case "EBORequestModule_InvalidRequester": + throw new EBORequestModule_InvalidRequester(); + case "EBORequestCreator_ChainNotAdded": + throw new EBORequestCreator_ChainNotAdded(); + default: + throw new Error(`Unknown error: ${errorName}`); + } + } } + throw error; } } diff --git a/packages/automated-dispute/src/types/protocolProvider.ts b/packages/automated-dispute/src/types/protocolProvider.ts index a4387ee..6b0a877 100644 --- a/packages/automated-dispute/src/types/protocolProvider.ts +++ b/packages/automated-dispute/src/types/protocolProvider.ts @@ -1,6 +1,57 @@ +import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; +import { Timestamp } from "@ebo-agent/shared"; import { Address } from "viem"; +import type { EboEvent, EboEventName } from "../types/events.js"; +import type { Dispute, Request, Response } from "../types/prophet.js"; import { ProtocolContractsNames } from "../constants.js"; export type ProtocolContract = (typeof ProtocolContractsNames)[number]; export type ProtocolContractsAddresses = Record; + +export interface IReadProvider { + getCurrentEpoch(): Promise<{ + currentEpoch: bigint; + currentEpochBlockNumber: bigint; + currentEpochTimestamp: Timestamp; + }>; + getLastFinalizedBlock(): Promise; + getEvents(_fromBlock: bigint, _toBlock: bigint): Promise[]>; + hasStakedAssets(_address: Address): Promise; + getAvailableChains(): Promise; +} + +export interface IWriteProvider { + createRequest(epoch: bigint, chains: string[]): Promise; + proposeResponse( + _requestId: string, + _epoch: bigint, + _chainId: Caip2ChainId, + _blockNumber: bigint, + ): Promise; + disputeResponse(_requestId: string, _responseId: string, _proposer: Address): Promise; + pledgeForDispute( + _request: Request["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + pledgeAgainstDispute( + _request: Request["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + settleDispute( + _request: Request["prophetData"], + _response: Response["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + escalateDispute( + _request: Request["prophetData"], + _response: Response["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + finalize(_request: Request["prophetData"], _response: Response["prophetData"]): Promise; +} + +export interface IProtocolProvider { + write: IWriteProvider; + read: IReadProvider; +} diff --git a/packages/automated-dispute/tests/eboActor/fixtures.ts b/packages/automated-dispute/tests/eboActor/fixtures.ts index 2283fa7..634f154 100644 --- a/packages/automated-dispute/tests/eboActor/fixtures.ts +++ b/packages/automated-dispute/tests/eboActor/fixtures.ts @@ -2,7 +2,8 @@ import { Address } from "viem"; import { Request, RequestId } from "../../src/types/prophet"; -export const privateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +export const mockedPrivateKey = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; export const DEFAULT_MOCKED_PROTOCOL_CONTRACTS = { oracle: "0x123456" as Address, diff --git a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts index dedf93d..a522188 100644 --- a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts @@ -14,7 +14,7 @@ import mocks from "../mocks/index.js"; import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, DEFAULT_MOCKED_REQUEST_CREATED_DATA, - privateKey, + mockedPrivateKey, } from "./fixtures.js"; const logger: ILogger = mocks.mockLogger(); @@ -53,7 +53,7 @@ describe("EboActor", () => { protocolProvider = new ProtocolProvider( ["http://localhost:8538"], DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - privateKey, + mockedPrivateKey, ); const chainRpcUrls = new Map(); diff --git a/packages/automated-dispute/tests/eboActorsManager.spec.ts b/packages/automated-dispute/tests/eboActorsManager.spec.ts index 315fdcc..55b9265 100644 --- a/packages/automated-dispute/tests/eboActorsManager.spec.ts +++ b/packages/automated-dispute/tests/eboActorsManager.spec.ts @@ -10,7 +10,7 @@ import { ProtocolProvider } from "../src/protocolProvider.js"; import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, DEFAULT_MOCKED_REQUEST_CREATED_DATA, - privateKey, + mockedPrivateKey, } from "./eboActor/fixtures.js"; import mocks from "./mocks/index.js"; @@ -32,7 +32,7 @@ describe("EboActorsManager", () => { protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - privateKey, + mockedPrivateKey, ); const blockNumberRpcUrls = new Map([ diff --git a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts index c948ce9..4238c06 100644 --- a/packages/automated-dispute/tests/mocks/eboActor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboActor.mocks.ts @@ -7,7 +7,7 @@ import { EboActor } from "../../src/eboActor.js"; import { ProtocolProvider } from "../../src/protocolProvider.js"; import { EboMemoryRegistry } from "../../src/services/index.js"; import { Dispute, Request, Response } from "../../src/types/index.js"; -import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, privateKey } from "../eboActor/fixtures.js"; +import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, mockedPrivateKey } from "../eboActor/fixtures.js"; /** * Builds a base `EboActor` scaffolded with all its dependencies. @@ -23,7 +23,7 @@ export function buildEboActor(request: Request, logger: ILogger) { const protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - privateKey, + mockedPrivateKey, ); const blockNumberRpcUrls = new Map([ diff --git a/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts b/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts index b419f6a..277c26e 100644 --- a/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts @@ -5,14 +5,14 @@ import { ILogger } from "@ebo-agent/shared"; import { EboActorsManager } from "../../src/eboActorsManager"; import { ProtocolProvider } from "../../src/protocolProvider"; import { EboProcessor } from "../../src/services"; -import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, privateKey } from "../eboActor/fixtures"; +import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, mockedPrivateKey } from "../eboActor/fixtures"; export function buildEboProcessor(logger: ILogger) { const protocolProviderRpcUrls = ["http://localhost:8538"]; const protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - privateKey, + mockedPrivateKey, ); const blockNumberRpcUrls = new Map([ diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index 14281b1..e85df6e 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -5,14 +5,10 @@ import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; import { eboRequestCreatorAbi } from "../src/abis/eboRequestCreator.js"; import { epochManagerAbi } from "../src/abis/epochManager.js"; import { oracleAbi } from "../src/abis/oracle.js"; -import { EBORequestCreator_ChainNotAdded } from "../src/exceptions/chainNotAdded.exception.js"; -import { EBORequestCreator_InvalidEpoch } from "../src/exceptions/invalidEpoch.exception.js"; -import { Oracle_InvalidRequestBody } from "../src/exceptions/invalidRequestBody.exception.js"; -import { EBORequestModule_InvalidRequester } from "../src/exceptions/invalidRequester.exception.js"; import { RpcUrlsEmpty } from "../src/exceptions/rpcUrlsEmpty.exception.js"; import { ProtocolProvider } from "../src/index.js"; import { ProtocolContractsAddresses } from "../src/types/index.js"; -import { privateKey } from "./eboActor/fixtures.js"; +import { mockedPrivateKey } from "./eboActor/fixtures.js"; vi.mock("viem", async () => { const actual = await vi.importActual("viem"); @@ -36,10 +32,10 @@ describe("ProtocolProvider", () => { beforeEach(() => { (getContract as Mock).mockImplementation(({ address, abi }) => { - if (abi == oracleAbi && address == mockContractAddress.oracle) { + if (abi === oracleAbi && address === mockContractAddress.oracle) { return {}; } - if (abi == epochManagerAbi && address == mockContractAddress.epochManager) { + if (abi === epochManagerAbi && address === mockContractAddress.epochManager) { return { read: { currentEpoch: vi.fn(), @@ -47,8 +43,11 @@ describe("ProtocolProvider", () => { }, }; } - if (abi == eboRequestCreatorAbi && address == mockContractAddress.eboRequestCreator) { + if (abi === eboRequestCreatorAbi && address === mockContractAddress.eboRequestCreator) { return { + simulate: { + createRequests: vi.fn(), + }, write: { createRequests: vi.fn(), }, @@ -56,6 +55,22 @@ describe("ProtocolProvider", () => { } throw new Error("Invalid contract address or ABI"); }); + + (createPublicClient as Mock).mockImplementation(() => ({ + simulateContract: vi.fn().mockResolvedValue({ + request: { + functionName: "createRequests", + args: [], + }, + }), + getBlock: vi.fn(), + waitForTransactionReceipt: vi.fn().mockResolvedValue({ status: "success" }), + })); + + (createWalletClient as Mock).mockReturnValue({ + writeContract: vi.fn().mockResolvedValue("0xmockedTransactionHash"), + }); + (http as Mock).mockImplementation((url) => url); (fallback as Mock).mockImplementation((transports) => transports); }); @@ -69,7 +84,7 @@ describe("ProtocolProvider", () => { const protocolProvider = new ProtocolProvider( mockRpcUrls, mockContractAddress, - privateKey, + mockedPrivateKey, ); expect(createPublicClient).toHaveBeenCalledWith({ @@ -94,18 +109,18 @@ describe("ProtocolProvider", () => { expect(getContract).toHaveBeenCalledWith({ address: mockContractAddress.oracle, abi: oracleAbi, - client: protocolProvider["client"], + client: protocolProvider["readClient"], }); expect(getContract).toHaveBeenCalledWith({ address: mockContractAddress.epochManager, abi: epochManagerAbi, - client: protocolProvider["client"], + client: protocolProvider["readClient"], }); }); it("throws if rpcUrls are empty", () => { - expect(() => new ProtocolProvider([], mockContractAddress, privateKey)).toThrowError( - RpcUrlsEmpty, - ); + expect( + () => new ProtocolProvider([], mockContractAddress, mockedPrivateKey), + ).toThrowError(RpcUrlsEmpty); }); }); @@ -122,7 +137,7 @@ describe("ProtocolProvider", () => { const protocolProvider = new ProtocolProvider( mockRpcUrls, mockContractAddress, - privateKey, + mockedPrivateKey, ); (protocolProvider["epochManagerContract"].read.currentEpoch as Mock).mockResolvedValue( @@ -137,12 +152,13 @@ describe("ProtocolProvider", () => { expect(result.currentEpoch).toBe(mockEpoch); expect(result.currentEpochBlockNumber).toBe(mockEpochBlock); + expect(result.currentEpochTimestamp).toBe(mockEpochTimestamp); }); it("throws when current epoch request fails", async () => { const protocolProvider = new ProtocolProvider( mockRpcUrls, mockContractAddress, - privateKey, + mockedPrivateKey, ); const error = new Error("Failed to get current epoch"); const mockEpochBlock = BigInt(12345); @@ -156,11 +172,12 @@ describe("ProtocolProvider", () => { await expect(protocolProvider.getCurrentEpoch()).rejects.toThrow(error); }); + it("throws when current epoch block request fails", async () => { const protocolProvider = new ProtocolProvider( mockRpcUrls, mockContractAddress, - privateKey, + mockedPrivateKey, ); const error = new Error("Failed to get current epoch block"); const mockEpoch = BigInt(12345); @@ -176,183 +193,60 @@ describe("ProtocolProvider", () => { }); }); - describe.skip("getEvents", () => { - it("returns all events ordered asc by block and log index"); - it.skip("includes `new epoch` event if needed"); // TODO: confirm if this is needed - it.skip("includes `request can be finalized` event if needed"); // TODO: confirm if this is needed - it("throws if the block range is not consistent"); - it("throws if the RPC client fails"); - }); - - describe.skip("hasStakedAssets", () => { - it("returns true if the address has more than 0 assets staked"); - it("returns false if the address has 0 staked assets"); - }); - - describe.skip("createRequest", () => { - it("succeeds if the RPC client sent the request"); - // NOTE: Should we validate if the request was created by - // tracking the transaction result somehow? I feel like it's - // somewhat brittle to just wish for the tx to be processed. - it("throws if the epoch is not current"); - it("throws if chains is empty"); - it("throws if the RPC client fails"); - }); - - describe.skip("getAvailableChains", () => { - it("returns an array of available chains in CAIP-2 compliant format"); - it("throws if the RPC client fails"); - }); - - describe.skip("proposeResponse", () => { - it("returns if the RPC client sent the response"); - it("throws if the RPC client fails"); - }); - - describe.skip("disputeResponse", () => { - it("returns if the RPC client sent the dispute"); - it("throws if the RPC client fails"); - }); - - describe.skip("pledgeForDispute", () => { - it("returns if the RPC client sent the pledge"); - it("throws if the RPC client fails"); - }); - - describe.skip("pledgeAgainsDispute", () => { - it("returns if the RPC client sent the pledge"); - it("throws if the RPC client fails"); - }); - - describe.skip("finalize", () => { - it("returns if the RPC client finalizes the pledge"); - it("throws if the RPC client fails"); - }); - describe("createRequest", () => { - it("succeeds if the RPC client sent the request", async () => { - const mockEpoch = BigInt(1); - const mockChains = ["eip155:1", "eip155:42161"]; - const protocolProvider = new ProtocolProvider( - mockRpcUrls, - mockContractAddress, - privateKey, - ); + it("creates a request successfully", async () => { + const mockRpcUrls = ["http://localhost:8545"]; + const mockContractAddress: ProtocolContractsAddresses = { + oracle: "0x1234567890123456789012345678901234567890", + epochManager: "0x1234567890123456789012345678901234567890", + eboRequestCreator: "0x1234567890123456789012345678901234567890", + }; - const mockCreateRequests = vi.fn().mockResolvedValue(undefined); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); - - await protocolProvider.createRequest(mockEpoch, mockChains); - - expect(mockCreateRequests).toHaveBeenCalledWith([mockEpoch, mockChains]); - }); - - it("throws EBORequestCreator_InvalidEpoch if the epoch is invalid", async () => { - const mockEpoch = BigInt(0); - const mockChains = ["eip155:1", "eip155:42161"]; const protocolProvider = new ProtocolProvider( mockRpcUrls, mockContractAddress, - privateKey, + mockedPrivateKey, ); - const mockCreateRequests = vi - .fn() - .mockRejectedValue(new EBORequestCreator_InvalidEpoch()); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); - - await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( - EBORequestCreator_InvalidEpoch, - ); - }); - - it("throws Oracle_InvalidRequestBody if the request body is invalid", async () => { - const mockEpoch = BigInt(1); - const mockChains = ["eip155:1", "eip155:42161"]; - const protocolProvider = new ProtocolProvider( - mockRpcUrls, - mockContractAddress, - privateKey, - ); - - const mockCreateRequests = vi.fn().mockRejectedValue(new Oracle_InvalidRequestBody()); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); - - await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( - Oracle_InvalidRequestBody, - ); - }); - - it("throws EBORequestModule_InvalidRequester if the requester is invalid", async () => { - const mockEpoch = BigInt(1); + const mockEpoch = 1n; const mockChains = ["eip155:1", "eip155:42161"]; - const protocolProvider = new ProtocolProvider( - mockRpcUrls, - mockContractAddress, - privateKey, - ); - - const mockCreateRequests = vi - .fn() - .mockRejectedValue(new EBORequestModule_InvalidRequester()); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); - await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( - EBORequestModule_InvalidRequester, + const mockWriteContractResponse = "0xmockedTransactionHash"; + (protocolProvider["writeClient"].writeContract as Mock).mockResolvedValue( + mockWriteContractResponse, ); - }); - it("throws EBORequestCreator_ChainNotAdded if one of the specified chains is not added", async () => { - const mockEpoch = BigInt(1); - const mockChains = ["eip155:1", "eip155:42161"]; - const protocolProvider = new ProtocolProvider( - mockRpcUrls, - mockContractAddress, - privateKey, - ); + await protocolProvider.createRequest(mockEpoch, mockChains); - const mockCreateRequests = vi - .fn() - .mockRejectedValue(new EBORequestCreator_ChainNotAdded()); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); + expect(protocolProvider["readClient"].simulateContract).toHaveBeenCalledWith({ + address: undefined, + abi: eboRequestCreatorAbi, + functionName: "createRequests", + args: [mockEpoch, mockChains], + account: undefined, + }); - await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( - EBORequestCreator_ChainNotAdded, + expect(protocolProvider["writeClient"].writeContract).toHaveBeenCalledWith( + expect.objectContaining({ + functionName: "createRequests", + args: [], + }), ); }); - it("throws if the RPC client fails", async () => { - const mockEpoch = BigInt(1); - const mockChains = ["eip155:1", "eip155:42161"]; + it("throws if chains array is empty", async () => { const protocolProvider = new ProtocolProvider( - mockRpcUrls, - mockContractAddress, - privateKey, + ["http://localhost:8545"], + { + oracle: "0x1234567890123456789012345678901234567890", + epochManager: "0x1234567890123456789012345678901234567890", + eboRequestCreator: "0x1234567890123456789012345678901234567890", + }, + mockedPrivateKey, ); - const mockCreateRequests = vi.fn().mockRejectedValue(new Error("RPC client error")); - vi.spyOn( - protocolProvider["eboRequestCreatorContract"].write, - "createRequests", - ).mockImplementation(mockCreateRequests); - - await expect(protocolProvider.createRequest(mockEpoch, mockChains)).rejects.toThrow( - "RPC client error", + await expect(protocolProvider.createRequest(1n, [])).rejects.toThrow( + "Chains array cannot be empty", ); }); }); From 1696d48b72e6a992bc2af44ded2502794367bfe0 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:10:49 -0500 Subject: [PATCH 08/15] fix: add back deleted tests --- .../tests/protocolProvider.spec.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index e85df6e..4dd4bcc 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -193,6 +193,59 @@ describe("ProtocolProvider", () => { }); }); + describe.skip("getEvents", () => { + it("returns all events ordered asc by block and log index"); + it.skip("includes `new epoch` event if needed"); // TODO: confirm if this is needed + it.skip("includes `request can be finalized` event if needed"); // TODO: confirm if this is needed + it("throws if the block range is not consistent"); + it("throws if the RPC client fails"); + }); + + describe.skip("hasStakedAssets", () => { + it("returns true if the address has more than 0 assets staked"); + it("returns false if the address has 0 staked assets"); + }); + + describe.skip("createRequest", () => { + it("succeeds if the RPC client sent the request"); + // NOTE: Should we validate if the request was created by + // tracking the transaction result somehow? I feel like it's + // somewhat brittle to just wish for the tx to be processed. + it("throws if the epoch is not current"); + it("throws if chains is empty"); + it("throws if the RPC client fails"); + }); + + describe.skip("getAvailableChains", () => { + it("returns an array of available chains in CAIP-2 compliant format"); + it("throws if the RPC client fails"); + }); + + describe.skip("proposeResponse", () => { + it("returns if the RPC client sent the response"); + it("throws if the RPC client fails"); + }); + + describe.skip("disputeResponse", () => { + it("returns if the RPC client sent the dispute"); + it("throws if the RPC client fails"); + }); + + describe.skip("pledgeForDispute", () => { + it("returns if the RPC client sent the pledge"); + it("throws if the RPC client fails"); + }); + + describe.skip("pledgeAgainsDispute", () => { + it("returns if the RPC client sent the pledge"); + it("throws if the RPC client fails"); + }); + + describe.skip("finalize", () => { + it("returns if the RPC client finalizes the pledge"); + it("throws if the RPC client fails"); + }); + describe("createRequest", () => { it("creates a request successfully", async () => { const mockRpcUrls = ["http://localhost:8545"]; From f296f4d12623150f9903abee8cdd7373aa0aafca Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:01:17 -0500 Subject: [PATCH 09/15] feat: address PR comments --- .../automated-dispute/src/interfaces/index.ts | 1 + .../src/interfaces/protocolProvider.ts | 174 ++++++++++++++++++ .../automated-dispute/src/protocolProvider.ts | 29 ++- packages/automated-dispute/src/types/index.ts | 2 +- .../src/types/protocolProvider.ts | 57 ------ .../tests/protocolProvider.spec.ts | 39 ++-- 6 files changed, 227 insertions(+), 75 deletions(-) create mode 100644 packages/automated-dispute/src/interfaces/protocolProvider.ts delete mode 100644 packages/automated-dispute/src/types/protocolProvider.ts diff --git a/packages/automated-dispute/src/interfaces/index.ts b/packages/automated-dispute/src/interfaces/index.ts index 38d57ab..fe9d437 100644 --- a/packages/automated-dispute/src/interfaces/index.ts +++ b/packages/automated-dispute/src/interfaces/index.ts @@ -1,2 +1,3 @@ export * from "./eboRegistry.js"; export * from "./eboRegistryCommand.js"; +export * from "./protocolProvider.js"; diff --git a/packages/automated-dispute/src/interfaces/protocolProvider.ts b/packages/automated-dispute/src/interfaces/protocolProvider.ts new file mode 100644 index 0000000..ce236c7 --- /dev/null +++ b/packages/automated-dispute/src/interfaces/protocolProvider.ts @@ -0,0 +1,174 @@ +import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; +import { Timestamp } from "@ebo-agent/shared"; +import { Address } from "viem"; + +import type { EboEvent, EboEventName } from "../types/events.js"; +import type { Dispute, Request, Response } from "../types/prophet.js"; +import { ProtocolContractsNames } from "../constants.js"; + +export type ProtocolContract = (typeof ProtocolContractsNames)[number]; +export type ProtocolContractsAddresses = Record; + +/** + * IReadProvider defines the read operations that can be performed on the protocol. + */ +export interface IReadProvider { + /** + * Gets the current epoch, along with the block number and its timestamp. + * + * @returns A promise that resolves with the current epoch, block number, and timestamp. + */ + getCurrentEpoch(): Promise<{ + currentEpoch: bigint; + currentEpochBlockNumber: bigint; + currentEpochTimestamp: Timestamp; + }>; + + /** + * Gets the last finalized block number. + * + * @returns A promise that resolves with the block number of the last finalized block. + */ + getLastFinalizedBlock(): Promise; + + /** + * Retrieves events from the protocol within a specified block range. + * + * @param _fromBlock The starting block number. + * @param _toBlock The ending block number. + * @returns A promise that resolves with an array of protocol events. + */ + getEvents(_fromBlock: bigint, _toBlock: bigint): Promise[]>; + + /** + * Checks whether the specified address has staked assets. + * + * @param _address The address to check. + * @returns A promise that resolves with a boolean indicating whether the address has staked assets. + */ + hasStakedAssets(_address: Address): Promise; + + /** + * Gets the list of available chains that the protocol supports. + * + * @returns A promise that resolves with an array of chain IDs. + */ + getAvailableChains(): Promise; +} + +/** + * IWriteProvider defines the write operations that can be performed on the protocol. + */ +export interface IWriteProvider { + /** + * Creates a request on the EBO Request Creator contract. + * + * @param epoch The epoch for which the request is being created. + * @param chains An array of chain identifiers where the request should be created. + * @throws Will throw an error if the chains array is empty or if the transaction fails. + * @returns A promise that resolves when the request is successfully created. + */ + createRequest(epoch: bigint, chains: string[]): Promise; + + /** + * Proposes a response to a request. + * + * @param _requestId The ID of the request. + * @param _epoch The epoch of the request. + * @param _chainId The chain ID where the request was made. + * @param _blockNumber The block number associated with the response. + * @returns A promise that resolves when the response is proposed. + */ + proposeResponse( + _requestId: string, + _epoch: bigint, + _chainId: Caip2ChainId, + _blockNumber: bigint, + ): Promise; + + /** + * Disputes a proposed response. + * + * @param _requestId The ID of the request. + * @param _responseId The ID of the response to dispute. + * @param _proposer The address of the proposer. + * @returns A promise that resolves when the response is disputed. + */ + disputeResponse(_requestId: string, _responseId: string, _proposer: Address): Promise; + + /** + * Pledges support for a dispute. + * + * @param _request The request data for the dispute. + * @param _dispute The dispute data. + * @returns A promise that resolves when the pledge is made. + */ + pledgeForDispute( + _request: Request["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + + /** + * Pledges against a dispute. + * + * @param _request The request data for the dispute. + * @param _dispute The dispute data. + * @returns A promise that resolves when the pledge is made. + */ + pledgeAgainstDispute( + _request: Request["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + + /** + * Settles a dispute by finalizing the response. + * + * @param _request The request data. + * @param _response The response data. + * @param _dispute The dispute data. + * @returns A promise that resolves when the dispute is settled. + */ + settleDispute( + _request: Request["prophetData"], + _response: Response["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + + /** + * Escalates a dispute to a higher authority or layer. + * + * @param _request The request data. + * @param _response The response data. + * @param _dispute The dispute data. + * @returns A promise that resolves when the dispute is escalated. + */ + escalateDispute( + _request: Request["prophetData"], + _response: Response["prophetData"], + _dispute: Dispute["prophetData"], + ): Promise; + + /** + * Finalizes a request after the response and dispute resolution are complete. + * + * @param _request The request data. + * @param _response The response data. + * @returns A promise that resolves when the request is finalized. + */ + finalize(_request: Request["prophetData"], _response: Response["prophetData"]): Promise; +} + +/** + * IProtocolProvider defines the interface for a protocol provider that includes both read and write operations. + */ +export interface IProtocolProvider { + /** + * The write operations available on the protocol. + */ + write: IWriteProvider; + + /** + * The read operations available on the protocol. + */ + read: IReadProvider; +} diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index fca0f0a..9958bf0 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -34,9 +34,14 @@ import { IReadProvider, IWriteProvider, ProtocolContractsAddresses, -} from "./types/protocolProvider.js"; +} from "./interfaces/index.js"; export class ProtocolProvider implements IProtocolProvider { + // TODO: these constants should be env vars + private TRANSACTION_RECEIPT_CONFIRMATIONS = 1; + private TIMEOUT = 10000; + private RETRY_INTERVAL = 150; + private readClient: PublicClient>; private writeClient: WalletClient>; private oracleContract: GetContractReturnType< @@ -68,14 +73,28 @@ export class ProtocolProvider implements IProtocolProvider { this.readClient = createPublicClient({ chain: arbitrum, - transport: fallback(rpcUrls.map((url) => http(url))), + transport: fallback( + rpcUrls.map((url) => + http(url, { + timeout: this.TIMEOUT, + retryDelay: this.RETRY_INTERVAL, + }), + ), + ), }); const account = privateKeyToAccount(privateKey); this.writeClient = createWalletClient({ chain: arbitrum, - transport: fallback(rpcUrls.map((url) => http(url))), + transport: fallback( + rpcUrls.map((url) => + http(url, { + timeout: this.TIMEOUT, + retryDelay: this.RETRY_INTERVAL, + }), + ), + ), account: account, }); @@ -83,7 +102,7 @@ export class ProtocolProvider implements IProtocolProvider { this.oracleContract = getContract({ address: contracts.oracle, abi: oracleAbi, - client: this.readClient, + client: this.writeClient, }); this.epochManagerContract = getContract({ address: contracts.epochManager, @@ -270,7 +289,7 @@ export class ProtocolProvider implements IProtocolProvider { const receipt = await this.readClient.waitForTransactionReceipt({ hash, - confirmations: 1, + confirmations: this.TRANSACTION_RECEIPT_CONFIRMATIONS, }); if (receipt.status !== "success") { diff --git a/packages/automated-dispute/src/types/index.ts b/packages/automated-dispute/src/types/index.ts index f1e142e..518c0ca 100644 --- a/packages/automated-dispute/src/types/index.ts +++ b/packages/automated-dispute/src/types/index.ts @@ -1,3 +1,3 @@ export * from "./events.js"; -export * from "./protocolProvider.js"; +export * from "../interfaces/protocolProvider.js"; export * from "./prophet.js"; diff --git a/packages/automated-dispute/src/types/protocolProvider.ts b/packages/automated-dispute/src/types/protocolProvider.ts deleted file mode 100644 index 6b0a877..0000000 --- a/packages/automated-dispute/src/types/protocolProvider.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; -import { Timestamp } from "@ebo-agent/shared"; -import { Address } from "viem"; - -import type { EboEvent, EboEventName } from "../types/events.js"; -import type { Dispute, Request, Response } from "../types/prophet.js"; -import { ProtocolContractsNames } from "../constants.js"; - -export type ProtocolContract = (typeof ProtocolContractsNames)[number]; -export type ProtocolContractsAddresses = Record; - -export interface IReadProvider { - getCurrentEpoch(): Promise<{ - currentEpoch: bigint; - currentEpochBlockNumber: bigint; - currentEpochTimestamp: Timestamp; - }>; - getLastFinalizedBlock(): Promise; - getEvents(_fromBlock: bigint, _toBlock: bigint): Promise[]>; - hasStakedAssets(_address: Address): Promise; - getAvailableChains(): Promise; -} - -export interface IWriteProvider { - createRequest(epoch: bigint, chains: string[]): Promise; - proposeResponse( - _requestId: string, - _epoch: bigint, - _chainId: Caip2ChainId, - _blockNumber: bigint, - ): Promise; - disputeResponse(_requestId: string, _responseId: string, _proposer: Address): Promise; - pledgeForDispute( - _request: Request["prophetData"], - _dispute: Dispute["prophetData"], - ): Promise; - pledgeAgainstDispute( - _request: Request["prophetData"], - _dispute: Dispute["prophetData"], - ): Promise; - settleDispute( - _request: Request["prophetData"], - _response: Response["prophetData"], - _dispute: Dispute["prophetData"], - ): Promise; - escalateDispute( - _request: Request["prophetData"], - _response: Response["prophetData"], - _dispute: Dispute["prophetData"], - ): Promise; - finalize(_request: Request["prophetData"], _response: Response["prophetData"]): Promise; -} - -export interface IProtocolProvider { - write: IWriteProvider; - read: IReadProvider; -} diff --git a/packages/automated-dispute/tests/protocolProvider.spec.ts b/packages/automated-dispute/tests/protocolProvider.spec.ts index 4dd4bcc..083d794 100644 --- a/packages/automated-dispute/tests/protocolProvider.spec.ts +++ b/packages/automated-dispute/tests/protocolProvider.spec.ts @@ -14,8 +14,8 @@ vi.mock("viem", async () => { const actual = await vi.importActual("viem"); return { ...actual, - http: vi.fn(), - fallback: vi.fn(), + http: vi.fn((url, options) => ({ url, ...options })), + fallback: vi.fn((transports) => transports), createPublicClient: vi.fn(), createWalletClient: vi.fn(), getContract: vi.fn(), @@ -89,12 +89,26 @@ describe("ProtocolProvider", () => { expect(createPublicClient).toHaveBeenCalledWith({ chain: arbitrum, - transport: fallback(mockRpcUrls.map((url) => http(url))), + transport: fallback( + mockRpcUrls.map((url) => + http(url, { + timeout: protocolProvider["TIMEOUT"], + retryDelay: protocolProvider["RETRY_INTERVAL"], + }), + ), + ), }); expect(createWalletClient).toHaveBeenCalledWith({ chain: arbitrum, - transport: fallback(mockRpcUrls.map((url) => http(url))), + transport: fallback( + mockRpcUrls.map((url) => + http(url, { + timeout: protocolProvider["TIMEOUT"], + retryDelay: protocolProvider["RETRY_INTERVAL"], + }), + ), + ), account: expect.objectContaining({ address: expect.any(String), publicKey: expect.any(String), @@ -109,8 +123,9 @@ describe("ProtocolProvider", () => { expect(getContract).toHaveBeenCalledWith({ address: mockContractAddress.oracle, abi: oracleAbi, - client: protocolProvider["readClient"], + client: protocolProvider["writeClient"], }); + expect(getContract).toHaveBeenCalledWith({ address: mockContractAddress.epochManager, abi: epochManagerAbi, @@ -247,14 +262,14 @@ describe("ProtocolProvider", () => { }); describe("createRequest", () => { - it("creates a request successfully", async () => { - const mockRpcUrls = ["http://localhost:8545"]; - const mockContractAddress: ProtocolContractsAddresses = { - oracle: "0x1234567890123456789012345678901234567890", - epochManager: "0x1234567890123456789012345678901234567890", - eboRequestCreator: "0x1234567890123456789012345678901234567890", - }; + const mockRpcUrls = ["http://localhost:8545"]; + const mockContractAddress: ProtocolContractsAddresses = { + oracle: "0x1234567890123456789012345678901234567890", + epochManager: "0x1234567890123456789012345678901234567890", + eboRequestCreator: "0x1234567890123456789012345678901234567890", + }; + it("creates a request successfully", async () => { const protocolProvider = new ProtocolProvider( mockRpcUrls, mockContractAddress, From 04329683fc19f83f5ef9dcdd09c0e838dbef15c5 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:39:52 -0500 Subject: [PATCH 10/15] fix: more merge fixes --- .gitignore | 5 +++- .../tests/eboActor/onRequestCreated.spec.ts | 29 ++----------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 02001d2..5d534a8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ dist/ coverage/ # IDE project settings -.idea/ \ No newline at end of file +.idea/ + +# Mac +.DS_Store \ No newline at end of file diff --git a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts index a47bcfe..bb31d50 100644 --- a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts @@ -1,16 +1,11 @@ import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; import { ILogger } from "@ebo-agent/shared"; import { Address } from "viem"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; -import { ProtocolProvider } from "../../src/protocolProvider.js"; import { EboEvent, Response } from "../../src/types/index.js"; import mocks from "../mocks/index.js"; -import { - DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - DEFAULT_MOCKED_REQUEST_CREATED_DATA, - mockedPrivateKey, -} from "./fixtures.js"; +import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js"; const logger: ILogger = mocks.mockLogger(); @@ -41,26 +36,6 @@ describe("EboActor", () => { }, }; - let protocolProvider: ProtocolProvider; - let blockNumberService: BlockNumberService; - let registry: EboMemoryRegistry; - let eventProcessingMutex: Mutex; - - beforeEach(() => { - protocolProvider = new ProtocolProvider( - ["http://localhost:8538"], - DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - mockedPrivateKey, - ); - - const chainRpcUrls = new Map(); - chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]); - - blockNumberService = new BlockNumberService(chainRpcUrls, logger); - registry = new EboMemoryRegistry(); - eventProcessingMutex = new Mutex(); - }); - it("stores the new request", async () => { const { actor, blockNumberService, protocolProvider, registry } = mocks.buildEboActor(request, logger); From e6bdfcc6b27105626c3eada347a0ae70f9e040e0 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:57:45 -0500 Subject: [PATCH 11/15] feat: add error factory and tests --- .../src/exceptions/errorFactory.ts | 31 ++++++++++++++++ .../automated-dispute/src/protocolProvider.ts | 20 ++--------- .../tests/errorFactory.spec.ts | 35 +++++++++++++++++++ 3 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 packages/automated-dispute/src/exceptions/errorFactory.ts create mode 100644 packages/automated-dispute/tests/errorFactory.spec.ts diff --git a/packages/automated-dispute/src/exceptions/errorFactory.ts b/packages/automated-dispute/src/exceptions/errorFactory.ts new file mode 100644 index 0000000..cda6a19 --- /dev/null +++ b/packages/automated-dispute/src/exceptions/errorFactory.ts @@ -0,0 +1,31 @@ +import { EBORequestCreator_ChainNotAdded } from "./chainNotAdded.exception.js"; +import { EBORequestCreator_InvalidEpoch } from "./invalidEpoch.exception.js"; +import { Oracle_InvalidRequestBody } from "./invalidRequestBody.exception.js"; +import { EBORequestModule_InvalidRequester } from "./invalidRequester.exception.js"; + +/** + * A factory class for creating specific error instances based on the provided error name. + */ +export class ErrorFactory { + /** + * Creates an instance of a specific error class based on the provided error name. + * + * @param {string} errorName - The name of the error to create. + * @returns {Error} An instance of the corresponding error class. + * @throws {Error} If the provided error name is unknown. + */ + public static createError(errorName: string): Error { + switch (errorName) { + case "EBORequestCreator_InvalidEpoch": + return new EBORequestCreator_InvalidEpoch(); + case "Oracle_InvalidRequestBody": + return new Oracle_InvalidRequestBody(); + case "EBORequestModule_InvalidRequester": + return new EBORequestModule_InvalidRequester(); + case "EBORequestCreator_ChainNotAdded": + return new EBORequestCreator_ChainNotAdded(); + default: + return new Error(`Unknown error: ${errorName}`); + } + } +} diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index 9958bf0..ab96887 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -22,12 +22,7 @@ import { arbitrum } from "viem/chains"; import type { EboEvent, EboEventName } from "./types/events.js"; import type { Dispute, Request, Response } from "./types/prophet.js"; import { eboRequestCreatorAbi, epochManagerAbi, oracleAbi } from "./abis/index.js"; -import { - EBORequestCreator_ChainNotAdded, - EBORequestCreator_InvalidEpoch, - EBORequestModule_InvalidRequester, - Oracle_InvalidRequestBody, -} from "./exceptions/index.js"; +import { ErrorFactory } from "./exceptions/errorFactory.js"; import { RpcUrlsEmpty } from "./exceptions/rpcUrlsEmpty.exception.js"; import { IProtocolProvider, @@ -302,18 +297,7 @@ export class ProtocolProvider implements IProtocolProvider { ); if (revertError instanceof ContractFunctionRevertedError) { const errorName = revertError.data?.errorName ?? ""; - switch (errorName) { - case "EBORequestCreator_InvalidEpoch": - throw new EBORequestCreator_InvalidEpoch(); - case "Oracle_InvalidRequestBody": - throw new Oracle_InvalidRequestBody(); - case "EBORequestModule_InvalidRequester": - throw new EBORequestModule_InvalidRequester(); - case "EBORequestCreator_ChainNotAdded": - throw new EBORequestCreator_ChainNotAdded(); - default: - throw new Error(`Unknown error: ${errorName}`); - } + throw ErrorFactory.createError(errorName); } } throw error; diff --git a/packages/automated-dispute/tests/errorFactory.spec.ts b/packages/automated-dispute/tests/errorFactory.spec.ts new file mode 100644 index 0000000..ce65c5f --- /dev/null +++ b/packages/automated-dispute/tests/errorFactory.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; + +import { EBORequestCreator_ChainNotAdded } from "../src/exceptions/chainNotAdded.exception.js"; +import { ErrorFactory } from "../src/exceptions/errorFactory.js"; +import { EBORequestCreator_InvalidEpoch } from "../src/exceptions/invalidEpoch.exception.js"; +import { Oracle_InvalidRequestBody } from "../src/exceptions/invalidRequestBody.exception.js"; +import { EBORequestModule_InvalidRequester } from "../src/exceptions/invalidRequester.exception.js"; + +describe("ErrorFactory", () => { + it("creates EBORequestCreator_InvalidEpoch error", () => { + const error = ErrorFactory.createError("EBORequestCreator_InvalidEpoch"); + expect(error).toBeInstanceOf(EBORequestCreator_InvalidEpoch); + }); + + it("creates Oracle_InvalidRequestBody error", () => { + const error = ErrorFactory.createError("Oracle_InvalidRequestBody"); + expect(error).toBeInstanceOf(Oracle_InvalidRequestBody); + }); + + it("creates EBORequestModule_InvalidRequester error", () => { + const error = ErrorFactory.createError("EBORequestModule_InvalidRequester"); + expect(error).toBeInstanceOf(EBORequestModule_InvalidRequester); + }); + + it("creates EBORequestCreator_ChainNotAdded error", () => { + const error = ErrorFactory.createError("EBORequestCreator_ChainNotAdded"); + expect(error).toBeInstanceOf(EBORequestCreator_ChainNotAdded); + }); + + it("creates generic Error for unknown error name", () => { + const error = ErrorFactory.createError("UnknownError"); + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe("Unknown error: UnknownError"); + }); +}); From ff5ab296d563de0cde31bb3180b245cee15c0a61 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:56:28 -0500 Subject: [PATCH 12/15] chore: add contracts as dev dependencies --- package.json | 2 ++ pnpm-lock.yaml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/package.json b/package.json index 6354c6a..f86cec4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "devDependencies": { "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", + "@defi-wonderland/prophet-core": "0.0.0-5e1cf557", + "@defi-wonderland/prophet-modules": "0.0.0-e52f8cce", "@ianvs/prettier-plugin-sort-imports": "4.3.1", "@types/node": "20.14.12", "@typescript-eslint/eslint-plugin": "7.16.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dada3bd..c07e656 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,12 @@ importers: "@commitlint/config-conventional": specifier: 19.2.2 version: 19.2.2 + "@defi-wonderland/prophet-core": + specifier: 0.0.0-5e1cf557 + version: 0.0.0-5e1cf557 + "@defi-wonderland/prophet-modules": + specifier: 0.0.0-e52f8cce + version: 0.0.0-e52f8cce "@ianvs/prettier-plugin-sort-imports": specifier: 4.3.1 version: 4.3.1(prettier@3.3.3) @@ -378,6 +384,24 @@ packages: integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==, } + "@defi-wonderland/prophet-core@0.0.0-2e39539b": + resolution: + { + integrity: sha512-EdYpDEO1XeO08uQikhOQ6NzG0LWYxANFk272j4vCyLSJ8kRyJNMv69JJCLcq5kV0B9IzXybmqjreemkZ05z3kQ==, + } + + "@defi-wonderland/prophet-core@0.0.0-5e1cf557": + resolution: + { + integrity: sha512-NDNyAnw/qUtoKInfwG12LSy3z1UkXilxwtup6VFMsYZeC+OIKFFslVDaG7QSuuWU1Z5o4Zr5oNDq1N30rnEVMw==, + } + + "@defi-wonderland/prophet-modules@0.0.0-e52f8cce": + resolution: + { + integrity: sha512-zNx602Z/GuisTqi120JLgoWcs3wiMKDYvnXJ/6xdYUCNNBQKk2GyCmS6M5Hu1vTbSfDlNRrugy1cDOkG5sSGdg==, + } + "@esbuild/aix-ppc64@0.21.5": resolution: { @@ -736,6 +760,12 @@ packages: } engines: { node: ">= 8" } + "@openzeppelin/contracts@4.9.5": + resolution: + { + integrity: sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg==, + } + "@pkgjs/parseargs@0.11.0": resolution: { @@ -2875,6 +2905,13 @@ packages: } engines: { node: ">=18" } + solmate@https://codeload.github.com/transmissions11/solmate/tar.gz/bfc9c25865a274a7827fea5abf6e4fb64fc64e6c: + resolution: + { + tarball: https://codeload.github.com/transmissions11/solmate/tar.gz/bfc9c25865a274a7827fea5abf6e4fb64fc64e6c, + } + version: 6.1.0 + source-map-js@1.2.0: resolution: { @@ -3691,6 +3728,16 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 + "@defi-wonderland/prophet-core@0.0.0-2e39539b": {} + + "@defi-wonderland/prophet-core@0.0.0-5e1cf557": {} + + "@defi-wonderland/prophet-modules@0.0.0-e52f8cce": + dependencies: + "@defi-wonderland/prophet-core": 0.0.0-2e39539b + "@openzeppelin/contracts": 4.9.5 + solmate: https://codeload.github.com/transmissions11/solmate/tar.gz/bfc9c25865a274a7827fea5abf6e4fb64fc64e6c + "@esbuild/aix-ppc64@0.21.5": optional: true @@ -3858,6 +3905,8 @@ snapshots: "@nodelib/fs.scandir": 2.1.5 fastq: 1.17.1 + "@openzeppelin/contracts@4.9.5": {} + "@pkgjs/parseargs@0.11.0": optional: true @@ -5051,6 +5100,9 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 + solmate@https://codeload.github.com/transmissions11/solmate/tar.gz/bfc9c25865a274a7827fea5abf6e4fb64fc64e6c: + {} + source-map-js@1.2.0: {} split2@4.2.0: {} From d813f46b03664eec9f8868f899aafe46aaf6df8e Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:58:56 -0500 Subject: [PATCH 13/15] chore: refactor constants --- .../automated-dispute/src/protocolProvider.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index ab96887..8593bd6 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -31,12 +31,12 @@ import { ProtocolContractsAddresses, } from "./interfaces/index.js"; -export class ProtocolProvider implements IProtocolProvider { - // TODO: these constants should be env vars - private TRANSACTION_RECEIPT_CONFIRMATIONS = 1; - private TIMEOUT = 10000; - private RETRY_INTERVAL = 150; +// TODO: these constants should be env vars +const TRANSACTION_RECEIPT_CONFIRMATIONS = 1; +const TIMEOUT = 10000; +const RETRY_INTERVAL = 150; +export class ProtocolProvider implements IProtocolProvider { private readClient: PublicClient>; private writeClient: WalletClient>; private oracleContract: GetContractReturnType< @@ -71,8 +71,8 @@ export class ProtocolProvider implements IProtocolProvider { transport: fallback( rpcUrls.map((url) => http(url, { - timeout: this.TIMEOUT, - retryDelay: this.RETRY_INTERVAL, + timeout: TIMEOUT, + retryDelay: RETRY_INTERVAL, }), ), ), @@ -85,8 +85,8 @@ export class ProtocolProvider implements IProtocolProvider { transport: fallback( rpcUrls.map((url) => http(url, { - timeout: this.TIMEOUT, - retryDelay: this.RETRY_INTERVAL, + timeout: TIMEOUT, + retryDelay: RETRY_INTERVAL, }), ), ), @@ -284,7 +284,7 @@ export class ProtocolProvider implements IProtocolProvider { const receipt = await this.readClient.waitForTransactionReceipt({ hash, - confirmations: this.TRANSACTION_RECEIPT_CONFIRMATIONS, + confirmations: TRANSACTION_RECEIPT_CONFIRMATIONS, }); if (receipt.status !== "success") { From 4bd8c8626e8aaa674d05b4cca136457a069ef607 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:59:58 -0500 Subject: [PATCH 14/15] chore: write client for oracle --- packages/automated-dispute/src/protocolProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index 8593bd6..5a82559 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -41,7 +41,7 @@ export class ProtocolProvider implements IProtocolProvider { private writeClient: WalletClient>; private oracleContract: GetContractReturnType< typeof oracleAbi, - typeof this.readClient, + typeof this.writeClient, Address >; private epochManagerContract: GetContractReturnType< From 848efa791235263da7747e253fbcafbc5cc85582 Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:44:03 -0500 Subject: [PATCH 15/15] fix: address pr comments --- packages/automated-dispute/src/protocolProvider.ts | 2 +- .../src/{exceptions => services}/errorFactory.ts | 10 ++++++---- packages/automated-dispute/tests/errorFactory.spec.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) rename packages/automated-dispute/src/{exceptions => services}/errorFactory.ts (67%) diff --git a/packages/automated-dispute/src/protocolProvider.ts b/packages/automated-dispute/src/protocolProvider.ts index 5a82559..90b96c6 100644 --- a/packages/automated-dispute/src/protocolProvider.ts +++ b/packages/automated-dispute/src/protocolProvider.ts @@ -22,7 +22,6 @@ import { arbitrum } from "viem/chains"; import type { EboEvent, EboEventName } from "./types/events.js"; import type { Dispute, Request, Response } from "./types/prophet.js"; import { eboRequestCreatorAbi, epochManagerAbi, oracleAbi } from "./abis/index.js"; -import { ErrorFactory } from "./exceptions/errorFactory.js"; import { RpcUrlsEmpty } from "./exceptions/rpcUrlsEmpty.exception.js"; import { IProtocolProvider, @@ -30,6 +29,7 @@ import { IWriteProvider, ProtocolContractsAddresses, } from "./interfaces/index.js"; +import { ErrorFactory } from "./services/errorFactory.js"; // TODO: these constants should be env vars const TRANSACTION_RECEIPT_CONFIRMATIONS = 1; diff --git a/packages/automated-dispute/src/exceptions/errorFactory.ts b/packages/automated-dispute/src/services/errorFactory.ts similarity index 67% rename from packages/automated-dispute/src/exceptions/errorFactory.ts rename to packages/automated-dispute/src/services/errorFactory.ts index cda6a19..37fd125 100644 --- a/packages/automated-dispute/src/exceptions/errorFactory.ts +++ b/packages/automated-dispute/src/services/errorFactory.ts @@ -1,7 +1,7 @@ -import { EBORequestCreator_ChainNotAdded } from "./chainNotAdded.exception.js"; -import { EBORequestCreator_InvalidEpoch } from "./invalidEpoch.exception.js"; -import { Oracle_InvalidRequestBody } from "./invalidRequestBody.exception.js"; -import { EBORequestModule_InvalidRequester } from "./invalidRequester.exception.js"; +import { EBORequestCreator_ChainNotAdded } from "../exceptions/chainNotAdded.exception.js"; +import { EBORequestCreator_InvalidEpoch } from "../exceptions/invalidEpoch.exception.js"; +import { Oracle_InvalidRequestBody } from "../exceptions/invalidRequestBody.exception.js"; +import { EBORequestModule_InvalidRequester } from "../exceptions/invalidRequester.exception.js"; /** * A factory class for creating specific error instances based on the provided error name. @@ -15,6 +15,8 @@ export class ErrorFactory { * @throws {Error} If the provided error name is unknown. */ public static createError(errorName: string): Error { + // TODO: need to define structure of each error + // TODO: Need to define some base contract reverted error to distinguish from other errors switch (errorName) { case "EBORequestCreator_InvalidEpoch": return new EBORequestCreator_InvalidEpoch(); diff --git a/packages/automated-dispute/tests/errorFactory.spec.ts b/packages/automated-dispute/tests/errorFactory.spec.ts index ce65c5f..b82bbe7 100644 --- a/packages/automated-dispute/tests/errorFactory.spec.ts +++ b/packages/automated-dispute/tests/errorFactory.spec.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; import { EBORequestCreator_ChainNotAdded } from "../src/exceptions/chainNotAdded.exception.js"; -import { ErrorFactory } from "../src/exceptions/errorFactory.js"; import { EBORequestCreator_InvalidEpoch } from "../src/exceptions/invalidEpoch.exception.js"; import { Oracle_InvalidRequestBody } from "../src/exceptions/invalidRequestBody.exception.js"; import { EBORequestModule_InvalidRequester } from "../src/exceptions/invalidRequester.exception.js"; +import { ErrorFactory } from "../src/services/errorFactory.js"; describe("ErrorFactory", () => { it("creates EBORequestCreator_InvalidEpoch error", () => {