-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement oracle rpc calls #39
Changes from 4 commits
827b90c
2215c57
501eb25
5feae91
6f547be
1ced83a
cf1519c
09a7d83
102864f
706054b
e8976ca
27e95e8
d84a6a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -304,23 +304,96 @@ export class ProtocolProvider implements IProtocolProvider { | |
} | ||
} | ||
|
||
/** | ||
* Proposes a response for a given request. | ||
* | ||
* @param {string} requestId - The ID of the request. | ||
* @param {bigint} epoch - The epoch for which the response is proposed. | ||
* @param {Caip2ChainId} chainId - The chain ID for which the response is proposed. | ||
* @param {bigint} blockNumber - The block number proposed as the response. | ||
* @throws {Error} If the transaction fails or if there's a contract revert. | ||
* @returns {Promise<void>} | ||
*/ | ||
async proposeResponse( | ||
_requestId: string, | ||
_epoch: bigint, | ||
_chainId: Caip2ChainId, | ||
_blockNumber: bigint, | ||
requestId: string, | ||
epoch: bigint, | ||
chainId: Caip2ChainId, | ||
blockNumber: bigint, | ||
): Promise<void> { | ||
// TODO: implement actual method | ||
return; | ||
try { | ||
const { request } = await this.readClient.simulateContract({ | ||
address: this.oracleContract.address, | ||
abi: oracleAbi, | ||
functionName: "proposeResponse", | ||
args: [requestId, { epoch, chainId, block: blockNumber }], | ||
account: this.writeClient.account, | ||
}); | ||
|
||
const hash = await this.writeClient.writeContract(request); | ||
|
||
const receipt = await this.readClient.waitForTransactionReceipt({ | ||
hash, | ||
confirmations: TRANSACTION_RECEIPT_CONFIRMATIONS, | ||
}); | ||
|
||
if (receipt.status !== "success") { | ||
throw new Error("Transaction failed"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll also need to handle this scenario too when processing the event that triggered this action. Let's add a named error for this (probably Also, given that this error is most likely not going to be a child class of // ErrorFactory.ts
static async createTransactionError(error) {
...
}
static async createContractRevertedError(error) {
...
} For these last There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, for now I added a TransactionExecutionError w/test cases |
||
} | ||
} catch (error) { | ||
if (error instanceof BaseError) { | ||
const revertError = error.walk( | ||
(err) => err instanceof ContractFunctionRevertedError, | ||
); | ||
if (revertError instanceof ContractFunctionRevertedError) { | ||
const errorName = revertError.data?.errorName ?? ""; | ||
throw ErrorFactory.createError(errorName); | ||
} | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
async disputeResponse( | ||
_requestId: string, | ||
_responseId: string, | ||
_proposer: Address, | ||
): Promise<void> { | ||
// TODO: implement actual method | ||
return; | ||
/** | ||
* Disputes a proposed response. | ||
* | ||
* @param {string} requestId - The ID of the request. | ||
* @param {string} responseId - The ID of the response being disputed. | ||
* @param {Address} proposer - The address of the proposer of the disputed response. | ||
* @throws {Error} If the transaction fails or if there's a contract revert. | ||
* @returns {Promise<void>} | ||
*/ | ||
async disputeResponse(requestId: string, responseId: string, proposer: Address): Promise<void> { | ||
try { | ||
const { request } = await this.readClient.simulateContract({ | ||
address: this.oracleContract.address, | ||
abi: oracleAbi, | ||
functionName: "disputeResponse", | ||
args: [requestId, responseId, proposer], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, based on the contract function signatures we should use I'm surprised that the types are not being enforced by viem with the help of the TypeScript. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ |
||
account: this.writeClient.account, | ||
}); | ||
|
||
const hash = await this.writeClient.writeContract(request); | ||
|
||
const receipt = await this.readClient.waitForTransactionReceipt({ | ||
hash, | ||
confirmations: TRANSACTION_RECEIPT_CONFIRMATIONS, | ||
}); | ||
|
||
if (receipt.status !== "success") { | ||
throw new Error("Transaction failed"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Custom error here! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ |
||
} | ||
} catch (error) { | ||
if (error instanceof BaseError) { | ||
const revertError = error.walk( | ||
(err) => err instanceof ContractFunctionRevertedError, | ||
); | ||
if (revertError instanceof ContractFunctionRevertedError) { | ||
const errorName = revertError.data?.errorName ?? ""; | ||
throw ErrorFactory.createError(errorName); | ||
} | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
async pledgeForDispute( | ||
|
@@ -348,23 +421,95 @@ export class ProtocolProvider implements IProtocolProvider { | |
return; | ||
} | ||
|
||
/** | ||
* Escalates a dispute to a higher authority. | ||
* | ||
* @param {Request["prophetData"]} request - The request data. | ||
* @param {Response["prophetData"]} response - The response data. | ||
* @param {Dispute["prophetData"]} dispute - The dispute data. | ||
* @throws {Error} If the transaction fails or if there's a contract revert. | ||
* @returns {Promise<void>} | ||
*/ | ||
async escalateDispute( | ||
_request: Request["prophetData"], | ||
_response: Response["prophetData"], | ||
_dispute: Dispute["prophetData"], | ||
request: Request["prophetData"], | ||
response: Response["prophetData"], | ||
dispute: Dispute["prophetData"], | ||
): Promise<void> { | ||
// TODO: implement actual method | ||
return; | ||
} | ||
try { | ||
const { request: simulatedRequest } = await this.readClient.simulateContract({ | ||
address: this.oracleContract.address, | ||
abi: oracleAbi, | ||
functionName: "escalateDispute", | ||
args: [request, response, dispute], | ||
account: this.writeClient.account, | ||
}); | ||
|
||
const hash = await this.writeClient.writeContract(simulatedRequest); | ||
|
||
// Pending confirmation from onchain team | ||
// releasePledge(args):void; | ||
const receipt = await this.readClient.waitForTransactionReceipt({ | ||
hash, | ||
confirmations: TRANSACTION_RECEIPT_CONFIRMATIONS, | ||
}); | ||
|
||
if (receipt.status !== "success") { | ||
throw new Error("Transaction failed"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Custom error here! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ |
||
} | ||
} catch (error) { | ||
if (error instanceof BaseError) { | ||
const revertError = error.walk( | ||
(err) => err instanceof ContractFunctionRevertedError, | ||
); | ||
if (revertError instanceof ContractFunctionRevertedError) { | ||
const errorName = revertError.data?.errorName ?? ""; | ||
throw ErrorFactory.createError(errorName); | ||
} | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Finalizes a request with a given response. | ||
* | ||
* @param {Request["prophetData"]} request - The request data. | ||
* @param {Response["prophetData"]} response - The response data to finalize. | ||
* @throws {Error} If the transaction fails or if there's a contract revert. | ||
* @returns {Promise<void>} | ||
*/ | ||
async finalize( | ||
_request: Request["prophetData"], | ||
_response: Response["prophetData"], | ||
request: Request["prophetData"], | ||
response: Response["prophetData"], | ||
): Promise<void> { | ||
//TODO: implement actual method | ||
return; | ||
try { | ||
const { request: simulatedRequest } = await this.readClient.simulateContract({ | ||
address: this.oracleContract.address, | ||
abi: oracleAbi, | ||
functionName: "finalize", | ||
args: [request, response], | ||
account: this.writeClient.account, | ||
}); | ||
|
||
const hash = await this.writeClient.writeContract(simulatedRequest); | ||
|
||
const receipt = await this.readClient.waitForTransactionReceipt({ | ||
hash, | ||
confirmations: TRANSACTION_RECEIPT_CONFIRMATIONS, | ||
}); | ||
|
||
if (receipt.status !== "success") { | ||
throw new Error("Transaction failed"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Custom error here! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ |
||
} | ||
} catch (error) { | ||
if (error instanceof BaseError) { | ||
const revertError = error.walk( | ||
(err) => err instanceof ContractFunctionRevertedError, | ||
); | ||
if (revertError instanceof ContractFunctionRevertedError) { | ||
const errorName = revertError.data?.errorName ?? ""; | ||
throw ErrorFactory.createError(errorName); | ||
} | ||
} | ||
throw error; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; | ||
import { createPublicClient, createWalletClient, fallback, getContract, http } from "viem"; | ||
import { arbitrum } from "viem/chains"; | ||
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; | ||
|
||
import type { Dispute, Request, Response } from "../src/types/prophet.js"; | ||
import { eboRequestCreatorAbi } from "../src/abis/eboRequestCreator.js"; | ||
import { epochManagerAbi } from "../src/abis/epochManager.js"; | ||
import { oracleAbi } from "../src/abis/oracle.js"; | ||
|
@@ -221,29 +223,55 @@ describe("ProtocolProvider", () => { | |
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("proposeResponse", () => { | ||
it("should successfully propose a response", async () => { | ||
const protocolProvider = new ProtocolProvider( | ||
mockRpcUrls, | ||
mockContractAddress, | ||
mockedPrivateKey, | ||
); | ||
|
||
await expect( | ||
protocolProvider.proposeResponse("0x123", 1n, "eip155:1" as Caip2ChainId, 100n), | ||
).resolves.not.toThrow(); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's test failures too when:
Testing failure behavior (specially in this class) is almost as important as the happy path. This applies to every Oracle provider method implemented. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ added these for proposeResponse |
||
}); | ||
|
||
describe.skip("disputeResponse", () => { | ||
it("returns if the RPC client sent the dispute"); | ||
it("throws if the RPC client fails"); | ||
describe("disputeResponse", () => { | ||
it("should successfully dispute a response", async () => { | ||
const protocolProvider = new ProtocolProvider( | ||
mockRpcUrls, | ||
mockContractAddress, | ||
mockedPrivateKey, | ||
); | ||
|
||
await expect( | ||
protocolProvider.disputeResponse("0x123", "0x456", "0x789"), | ||
).resolves.not.toThrow(); | ||
}); | ||
}); | ||
|
||
describe("escalateDispute", () => { | ||
it("should successfully escalate a dispute", async () => { | ||
const protocolProvider = new ProtocolProvider( | ||
mockRpcUrls, | ||
mockContractAddress, | ||
mockedPrivateKey, | ||
); | ||
|
||
const mockRequest = {} as Request["prophetData"]; | ||
const mockResponse = {} as Response["prophetData"]; | ||
const mockDispute = {} as Dispute["prophetData"]; | ||
|
||
await expect( | ||
protocolProvider.escalateDispute(mockRequest, mockResponse, mockDispute), | ||
).resolves.not.toThrow(); | ||
}); | ||
}); | ||
|
||
describe.skip("pledgeForDispute", () => { | ||
|
@@ -256,9 +284,21 @@ describe("ProtocolProvider", () => { | |
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("finalize", () => { | ||
it("should successfully finalize a request", async () => { | ||
const protocolProvider = new ProtocolProvider( | ||
mockRpcUrls, | ||
mockContractAddress, | ||
mockedPrivateKey, | ||
); | ||
|
||
const mockRequest = {} as Request["prophetData"]; | ||
const mockResponse = {} as Response["prophetData"]; | ||
|
||
await expect( | ||
protocolProvider.finalize(mockRequest, mockResponse), | ||
).resolves.not.toThrow(); | ||
}); | ||
}); | ||
|
||
describe("createRequest", () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old function signature might have been a little bit deprecated.
Based on the Oracle function we might be ok using
Request["prophetData"]
andResponse["prophetData"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, I double checked the interfaces and had to update a couple of their protocolProvider interfaces and implementations