-
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 get events #50
Conversation
Signed-off-by: jahabeebs <[email protected]>
Signed-off-by: jahabeebs <[email protected]>
Signed-off-by: jahabeebs <[email protected]>
dcb0c66
to
d845dd9
Compare
*/ | ||
async getEvents(fromBlock: bigint, toBlock: bigint) { | ||
if (fromBlock > toBlock) { | ||
throw new Error("Invalid block range: fromBlock must be less than or equal to toBlock"); |
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.
Let's use a custom error here + with a message that includes fromBlock
and toBlock
inside
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.
✅
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); |
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.
Have you tried using the multiple events capabilities [1] of getLogs
?
[1] https://viem.sh/docs/actions/public/getLogs#multiple-events
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, | |
}), | |
); |
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.
Let's also use strict mode [2] within getLogs
to avoid having to handle nullish event properties.
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.
✅
blockNumber: log.blockNumber ?? BigInt(0), | ||
logIndex: log.logIndex ?? 0, |
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.
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.
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.
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, |
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.
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
.
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.
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 { |
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.
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
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.
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, |
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.
Let's try strict: true
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.
✅
requestId: log.topics[1] as RequestId, | ||
}; | ||
|
||
const decodedLog = this.decodeLogData(eventName, log); |
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.
After setting strict: true
while getting and decoding events, is it possible to do something like this to avoid using the switch statements?
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.
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.
I haven't found a way to get this to work yet, not sure if the ABI types are coming through properly
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.
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.
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 }, | ||
]); |
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.
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.
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 }, | |
]); |
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.
Inverted these, still need to do some build cleanup before I can test though
@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...
or
if our event response no longer has a proposer or response object for example how are we constructing these? |
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.
@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, |
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.
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"; |
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.
I think the blocknumber
package exposes Caip2ChainId
already, it shouldn't be necessary to use /src/index.js
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.
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"; |
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.
Same here
throw new UnsupportedEvent(`Unsupported event name: ${eventName}`); | ||
throw new Error(`Unsupported event name: ${eventName}`); |
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.
Was removing UnsupportedEvent
intentional?
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.
No, I think this happened when i was merging things. Added it back in
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; |
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.
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?
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.
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.
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); |
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.
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.
//TODO: remove request? | ||
request: Request["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.
We might end up using Oracle's RequestCreated
event instead of EBORequestCreator's RequestCreated
event. I'll get back to you on this one
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.
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.
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.
are the ABIs up to date now since your PR was merged?
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.
Oracle's ABI should be up to date, yep. Viem types should be working as expected for all related functionality.
@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) |
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.
Almost ready! Feel free to add a it.todo
+ a TODO
comment to the failing test as we can address it later.
//TODO: remove request? | ||
request: Request["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.
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.
//TODO: remove request? | ||
request: Request["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.
Oracle's ABI should be up to date, yep. Viem types should be working as expected for all related functionality.
const logs = await this.readClient.getLogs({ | ||
address: this.eboRequestCreatorContract.address, | ||
event: eboRequestCreatorAbi.find( | ||
(e) => e.type === "event" && e.name === "RequestCreated", | ||
) as AbiEvent, | ||
fromBlock, | ||
toBlock, | ||
}); |
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.
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.
@0xyaco thanks for the helpful comments 🙏🏻 I made the following changes based on your comments:
For some reason the build is failing on the CI but not locally 🤔 |
@jahabeebs there might be something wrong with some cached files on the GH actions env? Merge |
You were right about the caching, I merged dev in and fixed some types and now everything is passing 🥳 |
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.
Noiceeee let's merge this if the rest of the team is ok with it!
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.
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 { |
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.
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
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; |
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.
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
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.
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 😅
const eventNames = [ | ||
"ResponseProposed", | ||
"ResponseDisputed", | ||
"DisputeStatusChanged", | ||
"DisputeEscalated", | ||
"RequestFinalized", | ||
]; |
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.
super minor, extracting this to class level attribute and avoid re-declaring the array on every function call
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.
✅
…-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
🤖 Linear
Closes GRT-143
Description