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 get events #50

Merged
merged 14 commits into from
Oct 14, 2024
Merged

feat: implement get events #50

merged 14 commits into from
Oct 14, 2024

Conversation

jahabeebs
Copy link
Collaborator

🤖 Linear

Closes GRT-143

Description

  • Implementation of getEvents—still have to fix type issue

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

linear bot commented Sep 24, 2024

@jahabeebs jahabeebs force-pushed the feat/grt-143-implement-getEvents branch from dcb0c66 to d845dd9 Compare September 27, 2024 15:20
*/
async getEvents(fromBlock: bigint, toBlock: bigint) {
if (fromBlock > toBlock) {
throw new Error("Invalid block range: fromBlock must be less than or equal to toBlock");
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 use a custom error here + with a message that includes fromBlock and toBlock inside

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 402 to 413
const eventPromises = eventNames.map((eventName) =>
this.readClient.getLogs({
address: this.oracleContract.address,
event: oracleAbi.find(
(e) => e.name === eventName && e.type === "event",
) as AbiEvent,
fromBlock,
toBlock,
}),
);

const allLogs = await Promise.all(eventPromises);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have you tried using the multiple events capabilities [1] of getLogs?

[1] https://viem.sh/docs/actions/public/getLogs#multiple-events

Suggested change
const eventPromises = eventNames.map((eventName) =>
this.readClient.getLogs({
address: this.oracleContract.address,
event: oracleAbi.find(
(e) => e.name === eventName && e.type === "event",
) as AbiEvent,
fromBlock,
toBlock,
}),
);
const allLogs = await Promise.all(eventPromises);
const eventAbis = eventNames.map((eventName) => oracleAbi.find(
(e) => e.name === eventName && e.type === "event",
) as AbiEvent);
const eventPromises = await this.readClient.getLogs({
address: this.oracleContract.address,
events: eventAbis,
fromBlock,
toBlock,
}),
);

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 also use strict mode [2] within getLogs to avoid having to handle nullish event properties.

[2] https://viem.sh/docs/actions/public/getLogs#strict-mode

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 318 to 319
blockNumber: log.blockNumber ?? BigInt(0),
logIndex: log.logIndex ?? 0,
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 know under which context the blockNumber and the logIndex could be nullish? It's kinda critical for the whole event processing to be working with events with these two properties defined.

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 removed the nullish logic, we really only need this for requestId

blockNumber: log.blockNumber ?? BigInt(0),
logIndex: log.logIndex ?? 0,
rawLog: log,
requestId: log.topics[1] as RequestId,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I might be getting this wrong but I suspect we might need to end up doing some per-event logic to populate this property. Let's take two examples out of the Linear task:

Event Name Contract
RequestCreated EBORequestCreator
ResponseDisputed Oracle
event RequestCreated(bytes32 indexed _requestId, uint256 indexed _epoch, string indexed _chainId);
event ResponseDisputed(
  bytes32 indexed _responseId, bytes32 indexed _disputeId, Dispute _dispute, uint256 _blockNumber
);

For RequestCreated events, the requestId value is accessible through something along the lines of log.data.requestId.

For ResponseDisputed events, the requestId value needs to be extracted from the _dispute struct (eg log.data.dispute.requestId.

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 tried this refactor but still getting "Property requestId does not exist on type" for the events--maybe the ABIs aren't matching up to the latest contracts but I need to do some more digging

* @interface DecodedLogArgsMap
* Represents the mapping of event names to their respective argument structures.
*/
export interface DecodedLogArgsMap {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like this approach, @0xkenj1 what do you think? Should we use this to define events' types instead of our current approach?

type EboEventData<E extends EboEventName> = E extends "RequestCreated"
    ? RequestCreated
    : E extends "ResponseProposed"
    ...

One way or another, we need to use a single approach as right now we have events' internal data types defined here and also defined in types/events.ts

Copy link
Collaborator

Choose a reason for hiding this comment

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

Matt says this: https://www.totaltypescript.com/type-vs-interface-which-should-you-use

tldr; type by default, go for interfaces if you expect to do inheritance

data: log.data,
topics: log.topics,
eventName,
strict: false,
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 try strict: true

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

requestId: log.topics[1] as RequestId,
};

const decodedLog = this.decodeLogData(eventName, log);
Copy link
Collaborator

Choose a reason for hiding this comment

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

After setting strict: true while getting and decoding events, is it possible to do something like this to avoid using the switch statements?

Suggested change
const decodedLog = this.decodeLogData(eventName, log);
const decodedLog = this.decodeLogData(eventName, log);
return {
...baseEvent,
metadata: decodedLog
}

I feel like there has to be a way to generalize this somehow, I might be wrong though.

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 haven't found a way to get this to work yet, not sure if the ABI types are coming through properly

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could it be that the event's properties are prefixed with an underscore and we need to replicate those in our codebase?

Let's grab this event for example:

  event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, Response _response);

The corresponding type inside DecodedLogArgsMap is:

    ResponseProposed: {
        requestId: RequestId;
        responseId: string;
        response: string;
        blockNumber: bigint; // This property was removed yesterday
    };

Maybe refactoring those attributes by prefixing them with an underscore would comply with the ABI types? Feel free to do some quick validation on this.

Comment on lines 1010 to 1015
expect(result).toEqual([
{ name: "RequestCreated", blockNumber: 3n, logIndex: 0 },
{ name: "ResponseDisputed", blockNumber: 2n, logIndex: 1 },
{ name: "ResponseProposed", blockNumber: 2n, logIndex: 0 },
{ name: "RequestCreated", blockNumber: 1n, logIndex: 0 },
]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this test case is passing tests, we need to invert the mergeEventStreams old sorting logic to have this pass with blocks sorted the other way round.

Suggested change
expect(result).toEqual([
{ name: "RequestCreated", blockNumber: 3n, logIndex: 0 },
{ name: "ResponseDisputed", blockNumber: 2n, logIndex: 1 },
{ name: "ResponseProposed", blockNumber: 2n, logIndex: 0 },
{ name: "RequestCreated", blockNumber: 1n, logIndex: 0 },
]);
expect(result).toEqual([
{ name: "RequestCreated", blockNumber: 1n, logIndex: 0 },
{ name: "ResponseDisputed", blockNumber: 2n, logIndex: 0 },
{ name: "ResponseProposed", blockNumber: 2n, logIndex: 1 },
{ name: "RequestCreated", blockNumber: 3n, logIndex: 0 },
]);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Inverted these, still need to do some build cleanup before I can test though

@jahabeebs
Copy link
Collaborator Author

jahabeebs commented Oct 2, 2024

@0xyaco I updated the types to be the latest contract types, both in events.ts and the decodedlog types (I know we probably will just stick with one but for simplicity I have them as the same for now)

what I'm wondering is how we should handle all the broken eboActor logic like...

 proposer: eventResponse.proposer

or

        const decodedResponse = ProtocolProvider.decodeResponse(eventResponse.response);

if our event response no longer has a proposer or response object for example how are we constructing these?

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.

@jahabeebs interesting question, check comments!

I'll get back to you on what to do with RequestCreated event, I think that's the only one that might be problematic.

All the other event properties' types that have been fixed (swapping type["prophetData"] with string) will imply that we need to decode the bytes string to generate the actual "prophetData" object with ProtocolProvider.decode*.

@@ -610,6 +611,7 @@ export class EboActor {
const disputer = this.protocolProvider.getAccountAddress();
const dispute: Dispute["prophetData"] = {
disputer: disputer,
// TODO: populate proposer if does not exist on eventResponse
proposer: eventResponse.proposer,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we are ok on this case, as the ResponseProposed event [1] is:

event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, Response _response);

And the Response struct [2] is:

struct Response {
  address proposer;
  bytes32 requestId;
  bytes response;
}

Thus, the proposer can be accessed through event.metadata.proposer while the response body is accessed through ProtocolProvider.decodeResponse(event.metadata.response.response).

Do you know any other case that might be broken?

[1] https://github.com/defi-wonderland/prophet-core/blob/4d28a0ec7e8cb082f4932c52a0845b122a0ca71a/solidity/interfaces/IOracle.sol#L27
[2] https://github.com/defi-wonderland/prophet-core/blob/4d28a0ec7e8cb082f4932c52a0845b122a0ca71a/solidity/interfaces/IOracle.sol#L227-L231

@@ -1,4 +1,4 @@
import { Caip2ChainId } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/src/index.js";
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the blocknumber package exposes Caip2ChainId already, it shouldn't be necessary to use /src/index.js

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed the /src/index.js that I added everywhere--not sure why my IDE was flagging it

@@ -1,4 +1,4 @@
import { Caip2ChainId, Caip2Utils, InvalidChainId } from "@ebo-agent/blocknumber";
import { Caip2ChainId, Caip2Utils, InvalidChainId } from "@ebo-agent/blocknumber/src/index.js";
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

Comment on lines 383 to 330
throw new UnsupportedEvent(`Unsupported event name: ${eventName}`);
throw new Error(`Unsupported event name: ${eventName}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Was removing UnsupportedEvent intentional?

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, I think this happened when i was merging things. Added it back in

Comment on lines 316 to 328
case "ResponseProposed":
requestId = decodedLog.requestId;
break;
case "ResponseDisputed":
requestId = decodedLog.requestId;
break;
case "DisputeStatusChanged":
case "DisputeEscalated":
requestId = decodedLog.requestId;
break;
case "RequestFinalized":
requestId = decodedLog.requestId;
break;
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 add a TODO here and we'll open a new PR later to extract the requestId values out of each event type. I suspect this might be failing to build, right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

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 a TODO over these as well as a TODO over the this.mergeEventStreams because it depends on the requestId logic

requestId: log.topics[1] as RequestId,
};

const decodedLog = this.decodeLogData(eventName, log);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could it be that the event's properties are prefixed with an underscore and we need to replicate those in our codebase?

Let's grab this event for example:

  event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, Response _response);

The corresponding type inside DecodedLogArgsMap is:

    ResponseProposed: {
        requestId: RequestId;
        responseId: string;
        response: string;
        blockNumber: bigint; // This property was removed yesterday
    };

Maybe refactoring those attributes by prefixing them with an underscore would comply with the ABI types? Feel free to do some quick validation on this.

Comment on lines 32 to 33
//TODO: remove request?
request: Request["prophetData"];
Copy link
Collaborator

Choose a reason for hiding this comment

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

We might end up using Oracle's RequestCreated event instead of EBORequestCreator's RequestCreated event. I'll get back to you on this one

Copy link
Collaborator

Choose a reason for hiding this comment

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

So, we'll be using both events, but only reacting to EBORequestCreator.RequestCreated so this can stay as it is.

This will probably cause some type failure during request creation internally in the AddRequest command, as it won't be able to populate the prophetData field; for that, let's add a TODO and fill it in with DEFAULT_MOCKED_REQUEST_CREATED_DATA["prophetData"] mocked data.

We got GRT-205 already created to tackle this in another PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

are the ABIs up to date now since your PR was merged?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oracle's ABI should be up to date, yep. Viem types should be working as expected for all related functionality.

@jahabeebs
Copy link
Collaborator Author

@0xyaco Fixed the merge conflicts, added in the new types (without blockNumber) so at least EboEvent and DecodedLogArgsMap are matching, and fixed all the tests except one (I think we need to do the request ID extraction to fix this)

@jahabeebs jahabeebs requested a review from 0xyaco October 8, 2024 23:56
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.

Almost ready! Feel free to add a it.todo + a TODO comment to the failing test as we can address it later.

Comment on lines 32 to 33
//TODO: remove request?
request: Request["prophetData"];
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, we'll be using both events, but only reacting to EBORequestCreator.RequestCreated so this can stay as it is.

This will probably cause some type failure during request creation internally in the AddRequest command, as it won't be able to populate the prophetData field; for that, let's add a TODO and fill it in with DEFAULT_MOCKED_REQUEST_CREATED_DATA["prophetData"] mocked data.

We got GRT-205 already created to tackle this in another PR.

Comment on lines 32 to 33
//TODO: remove request?
request: Request["prophetData"];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oracle's ABI should be up to date, yep. Viem types should be working as expected for all related functionality.

Comment on lines 404 to 411
const logs = await this.readClient.getLogs({
address: this.eboRequestCreatorContract.address,
event: eboRequestCreatorAbi.find(
(e) => e.type === "event" && e.name === "RequestCreated",
) as AbiEvent,
fromBlock,
toBlock,
});
Copy link
Collaborator

@0xyaco 0xyaco Oct 9, 2024

Choose a reason for hiding this comment

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

I think we can use this.readClient.getContractEvents [1] method to work directly with already decoded events, saving us the hassle of manually decoding the events here! Let's also include the strict: true property too so we don't have nullable event properties and the RPC call does not return non-comply events.

If the getContractEvents thing work, we could do something similar in getOracleEvents method now that we have a working ABI for Oracle; I feel like it's not worth right now for getOracleEvents though, as it'd imply doing N requests and we need to think about the consequences it might have. Let's add an OPTIMIZE comment for getOracleEvents function though, explaining that we could potentially remove the decodeLogData function and get improved typing (because we'd be directly using ABI types) if using a getContractEvents call for each Oracle event — with the trade-off of doing N requests instead of only 1.

[1] https://viem.sh/docs/contract/getContractEvents

@jahabeebs
Copy link
Collaborator Author

jahabeebs commented Oct 9, 2024

@0xyaco thanks for the helpful comments 🙏🏻 I made the following changes based on your comments:

  • Fixed the test that was failing
  • Updated getEBORequestCreatorEvents to use the type safe ways of accessing the ABI (it was with underscores as you suggested) and now it's using this.readClient.getContractEvents and strict: true
  • Added a ts-expect-error to a place in addRequest where we will have to fetch the request differently since we no longer have this on our current ABI

For some reason the build is failing on the CI but not locally 🤔

@jahabeebs jahabeebs requested a review from 0xyaco October 9, 2024 19:55
@0xyaco
Copy link
Collaborator

0xyaco commented Oct 10, 2024

@jahabeebs there might be something wrong with some cached files on the GH actions env? Merge dev into this branch and let's see if it fixes the issue, I'm seeing some error messages as if tsc was trying to check this branch types against dev last merged types.

@jahabeebs
Copy link
Collaborator Author

@jahabeebs there might be something wrong with some cached files on the GH actions env? Merge dev into this branch and let's see if it fixes the issue, I'm seeing some error messages as if tsc was trying to check this branch types against dev last merged types.

You were right about the caching, I merged dev in and fixed some types and now everything is passing 🥳

0xyaco
0xyaco previously approved these changes Oct 10, 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.

Noiceeee let's merge this if the rest of the team is ok with it!

@jahabeebs jahabeebs requested review from 0xkenj1 and 0xnigir1 October 10, 2024 18:09
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.

interesting debate the one on interfaces vs types, not sure tbh

* @interface DecodedLogArgsMap
* Represents the mapping of event names to their respective argument structures.
*/
export interface DecodedLogArgsMap {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Matt says this: https://www.totaltypescript.com/type-vs-interface-which-should-you-use

tldr; type by default, go for interfaces if you expect to do inheritance

Comment on lines 312 to 326
case "ResponseProposed":
abi = oracleAbi;
break;
case "ResponseDisputed":
abi = oracleAbi;
break;
case "DisputeStatusUpdated":
abi = oracleAbi;
break;
case "DisputeEscalated":
abi = oracleAbi;
break;
case "OracleRequestFinalized":
abi = oracleAbi;
break;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
case "ResponseProposed":
abi = oracleAbi;
break;
case "ResponseDisputed":
abi = oracleAbi;
break;
case "DisputeStatusUpdated":
abi = oracleAbi;
break;
case "DisputeEscalated":
abi = oracleAbi;
break;
case "OracleRequestFinalized":
abi = oracleAbi;
break;
case "ResponseProposed":
case "ResponseDisputed":
case "DisputeStatusUpdated":
case "DisputeEscalated":
case "OracleRequestFinalized":
abi = oracleAbi;
break;

idk if you like fallthrough or not xd

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed DecodedLogArgsMap to a type

I changed it to fallthrough because the class is already super long so probably it's for the best even though I like writing things out generally 😅

Comment on lines 404 to 410
const eventNames = [
"ResponseProposed",
"ResponseDisputed",
"DisputeStatusChanged",
"DisputeEscalated",
"RequestFinalized",
];
Copy link
Collaborator

Choose a reason for hiding this comment

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

super minor, extracting this to class level attribute and avoid re-declaring the array on every function call

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

…-getEvents

# Conflicts:
#	packages/automated-dispute/src/providers/protocolProvider.ts
#	packages/automated-dispute/src/services/eboActor.ts
#	packages/automated-dispute/src/services/eboProcessor.ts
#	packages/automated-dispute/src/types/events.ts
#	packages/automated-dispute/src/types/prophet.ts
@jahabeebs jahabeebs requested review from 0xyaco and 0xnigir1 October 11, 2024 21:52
@jahabeebs jahabeebs merged commit b7b63e9 into dev Oct 14, 2024
5 checks passed
@jahabeebs jahabeebs deleted the feat/grt-143-implement-getEvents branch October 14, 2024 12:46
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