Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement oracle rpc calls #39

Merged
merged 13 commits into from
Sep 16, 2024

Conversation

jahabeebs
Copy link
Collaborator

@jahabeebs jahabeebs commented Sep 11, 2024

🤖 Linear

Closes GRT-141

Description

  • Added proposeResponse, disputeResponse, escalateDispute, and finalize to protocolProvider
  • Added the names of all the errors corresponding to each of these methods in errorFactory.ts, however we decided we're not going to make a separate error class for each error some I'm leaving the names for now and will implement them when we do the error refactoring

@jahabeebs jahabeebs self-assigned this Sep 11, 2024
Copy link

linear bot commented Sep 11, 2024

Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

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

Cool! Some changes needed, leaving a short summary below.

  • Double check that the smart contract function signatures that we are using correspond to our viem calls (the Linear task might be helpful here, as it has the respective link to each of those function signatures)
  • Create a custom error for a receipt status not being === success and apply it into every ProtocolProvider method
  • Add some more testing cases

address: this.oracleContract.address,
abi: oracleAbi,
functionName: "proposeResponse",
args: [requestId, { epoch, chainId, block: blockNumber }],
Copy link
Collaborator

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"] and Response["prophetData"]

Copy link
Collaborator Author

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

});

if (receipt.status !== "success") {
throw new Error("Transaction failed");
Copy link
Collaborator

@0xyaco 0xyaco Sep 11, 2024

Choose a reason for hiding this comment

The 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 TransactionExecutionError) as it needs to be handled by the EboProcessor by retrying the transaction.


Also, given that this error is most likely not going to be a child class of ContractFunctionRevertedError we'll (probably) end up modifying the method we have/adding a new method to the ErrorFactory class:

// ErrorFactory.ts
static async createTransactionError(error) {
  ...
}

static async createContractRevertedError(error) {
  ...
}

For these last ErrorFactory changes mentioned, let's wait until we have a solid design for the error handling and we'll come back to it after that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, for now I added a TransactionExecutionError w/test cases

address: this.oracleContract.address,
abi: oracleAbi,
functionName: "disputeResponse",
args: [requestId, responseId, proposer],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, based on the contract function signatures we should use Request["prophetData"], Response["prophetData'}, Dispute["prophetData"].

I'm surprised that the types are not being enforced by viem with the help of the TypeScript.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

});

if (receipt.status !== "success") {
throw new Error("Transaction failed");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Custom error here!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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


if (receipt.status !== "success") {
throw new Error("Transaction failed");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Custom error here!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

});

if (receipt.status !== "success") {
throw new Error("Transaction failed");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Custom error here!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

await expect(
protocolProvider.proposeResponse("0x123", 1n, "eip155:1" as Caip2ChainId, 100n),
).resolves.not.toThrow();
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's test failures too when:

  • Transaction couldn't be confirmed
  • A ContractFunctionRevertedError is thrown by viem
  • Can the waitForTransactionReceipt throw a timeout error or something like that? Let's test that too

Testing failure behavior (specially in this class) is almost as important as the happy path. This applies to every Oracle provider method implemented.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

✅ added these for proposeResponse

@jahabeebs
Copy link
Collaborator Author

jahabeebs commented Sep 12, 2024

@0xyaco I think I've addressed all the PR comments but have some questions about changes I had to make to eboActor

for example, in proposeResponse I had to refactor to this:

  const response: Response["prophetData"] = {
            // TODO: check if this is the correct proposer
            proposer: request.prophetData.requester,
            requestId: request.id,
            response: responseBody,
        };

but I don't think this is the correct way to get the proposer.

We're getting the proposer in another function by doing

  const eventResponse = event.metadata.response;

then

proposer: eventResponse.proposer

but proposeResponse in eboActor is taking a chainId rather than an event...

similarly, in onResponseProposed I refactored to:

  const dispute: Dispute["prophetData"] = {
            // TODO: check if this is the correct disputer
            disputer: this.actorRequest.id,
            proposer: eventResponse.proposer,
            responseId: event.metadata.responseId,
            requestId: request.id,
        };

but to get the disputer we would do dispute.prophetData.disputer and in onResponseProposed we're only getting an event

Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

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

Great changes, left some other comments!

}

const response: Response["prophetData"] = {
// TODO: check if this is the correct proposer
proposer: request.prophetData.requester,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The proposer must be the address of the transaction sender as Prophet actually checks that the sender === propose.proposer [1].

Viem has some methods that might be helpful for this like getAddresses [2] [3].

[1] https://github.com/defi-wonderland/prophet-core/blob/0954a47f7dbd225ee0d48c9face954a7db6aed30/solidity/contracts/Oracle.sol#L118-L119
[2] https://viem.sh/docs/clients/wallet.html#account-optional
[3] https://viem.sh/docs/actions/wallet/getAddresses.html

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

✅ I added a new function to protocolProvider to get the writeClient account address, added a new custom error and some tests


const dispute: Dispute["prophetData"] = {
// TODO: check if this is the correct disputer
disputer: this.actorRequest.id,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as the proposeResponse case. The disputer is the transaction submitter address.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 80 to 81
_request: Request["prophetData"],
_response: Response["prophetData"],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a small detail, we want to remove those _ prefixes for arguments.

I think they were left there to be consistent with the methods being defined but not implemented (so the params had to be prefixed with an underscore so ESLint could pass).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 93 to 95
_request: Request["prophetData"],
_response: Response["prophetData"],
_dispute: Dispute["prophetData"],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

✅ got rid of the _ in the whole file--no lint issues

response.prophetData.response.block + 1n,
expect.objectContaining({}),
expect.objectContaining({
proposer: expect.any(String),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remember to update this when setting up the proposer, super important to have this also validated

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

indexedEpochBlockNumber,
expect.objectContaining(request.prophetData),
expect.objectContaining({
proposer: expect.any(String),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Test proposer value here too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 245 to 246
const mockRequest = {} as Request["prophetData"];
const mockResponse = {} as Response["prophetData"];
Copy link
Collaborator

Choose a reason for hiding this comment

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

As a sidenote, if for some reason you need to build objects populated with proper data, you have the mock builders for requests, responses and disputes at tests/mocks/eboActor.ts

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the file I replaced all the blank {} with the mocked request in fixtures and added a mocked response and dispute

);

(protocolProvider["readClient"].waitForTransactionReceipt as Mock).mockRejectedValue(
new Error("Transaction receipt timeout"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Check this out, proper error for this scenario!

https://viem.sh/docs/glossary/errors#waitfortransactionreceipttimeouterror

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

thanks, made it more accurate

@jahabeebs jahabeebs requested a review from 0xyaco September 13, 2024 18:42
Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

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

Just one last comment and you've got the green light on my end 💯

@@ -72,11 +82,23 @@ describe("ProtocolProvider", () => {
},
}),
getBlock: vi.fn(),
waitForTransactionReceipt: vi.fn().mockResolvedValue({ status: "success" }),
waitForTransactionReceipt: vi.fn().mockImplementation(async ({ hash }) => {
await new Promise((resolve) => setTimeout(resolve, 100));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you need this timeout here? We don't want to simulate any delay during unit tests, let's just make the function instantly resolve to the return value we want to use.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no it's ok to take out--I removed it

const mockRequest = {} as Request["prophetData"];
const mockResponse = {} as Response["prophetData"];
const mockRequestProphetData = DEFAULT_MOCKED_REQUEST_CREATED_DATA.prophetData;
const mockResponseProphetData = DEFAULT_MOCKED_RESPONSE_DATA.prophetData;
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to change this in this PR, just as a side note.

It might be better to use the mocks.buildResponse(request) helper function here to make it clear that there's a link between the response and the request that you're using.

Suggested change
const mockResponseProphetData = DEFAULT_MOCKED_RESPONSE_DATA.prophetData;
const mockRequest = DEFAULT_MOCKED_RESPONSE_DATA;
const mockResponse = mocks.buildResponse(mockRequest);
await expect(
protocolProvider(mockRequest.prophetData, mockResponse.prophetData)
)

Here you've got that helper function in action.

Comment on lines 559 to 560
const expectedAddress = privateKeyToAccount(mockedPrivateKey).address;
expect(protocolProvider.getAccountAddress()).toBe(expectedAddress);
Copy link
Collaborator

Choose a reason for hiding this comment

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

🍬 🍰

@jahabeebs jahabeebs requested a review from 0xyaco September 13, 2024 20:10
Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

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

Let's gooooo! 🚀 🚀

Copy link
Collaborator

@0xnigir1 0xnigir1 left a comment

Choose a reason for hiding this comment

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

niceee 🚀

@jahabeebs jahabeebs merged commit e58ef51 into dev Sep 16, 2024
5 checks passed
@jahabeebs jahabeebs deleted the feat/grt-141-implement-oracle-rpc-calls branch September 16, 2024 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants