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

Fuel Mint support Part 1 - Implement HyperFuel query selection for Mint receipts #214

Merged
merged 11 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,11 @@ jobs:
pnpm hardhat compile --verbose
pnpm res:build

- name: test_codegen test TS types
if: steps.changes.outputs.testChanges == 'true'
working-directory: scenarios/test_codegen
run: pnpm ts:test

- name: test_codegen test
if: steps.changes.outputs.testChanges == 'true'
working-directory: scenarios/test_codegen
run: |
pnpm ts:test
pnpm test

- name: erc20_multichain_factory build
Expand All @@ -157,4 +153,5 @@ jobs:
if: steps.changes.outputs.testChanges == 'true'
working-directory: scenarios/fuel_test
run: |
pnpm ts:test
pnpm test
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ scenarios/**/*.res.js
scenarios/**/*.gen.ts
scenarios/**/lib/*
scenarios/**/node_modules/*
scenarios/**/generated
4 changes: 2 additions & 2 deletions codegenerator/cli/npm/envio/fuel.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@
"type": "object",
"properties": {
"name": {
"description": "A reference to a struct in the ABI or a unique name for the provided log_id",
"description": "Name of the event in the HyperIndex generated code",
"type": "string"
},
"logId": {
"description": "A reference to a log_id in the ABI",
"description": "An identifier of a logged type from ABI. Used for indexing LogData receipts. The option can be omitted when the event name matches the logged struct/enum name.",
"type": [
"string",
"null"
Expand Down
9 changes: 4 additions & 5 deletions codegenerator/cli/src/config_parsing/human_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,12 @@ pub mod fuel {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct EventConfig {
#[schemars(
description = "A reference to a struct in the ABI or a unique name for the provided \
log_id"
)]
#[schemars(description = "Name of the event in the HyperIndex generated code")]
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(description = "A reference to a log_id in the ABI")]
#[schemars(
description = "An identifier of a logged type from ABI. Used for indexing LogData receipts. The option can be omitted when the event name matches the logged struct/enum name."
)]
pub log_id: Option<String>,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,33 @@ let registerContractHandlers = (
{{#with chain_config.network_config.hyperfuel_config as | hyperfuel_config |}}
module(HyperFuelWorker.Make({
let chain = chain
let contracts = contracts
Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally to get rid of the old contract config

let endpointUrl = "{{hyperfuel_config.endpoint_url}}"
let eventModLookup =
contracts
->Belt.Array.flatMap(contract => contract.events)
->EventModLookup.fromArrayOrThrow(~chain)
let contracts: array<HyperFuelWorker.contract> = [
{{#each chain_config.codegen_contracts as | contract |}}
{
name: "{{contract.name.capitalized}}",
addresses: [
{{#each contract.addresses as | address |}}
"{{address}}"->Address.unsafeFromString,
{{/each}}
],
events: [
{{#each contract.events as | event |}}
{
name: "{{event.name.capitalized}}",
logId: Types.{{contract.name.capitalized}}.{{event.name.capitalized}}.sighash,
{{!-- mint: {{#if event.mint}}true{{else}}false{{/if}}, --}}
eventMod: module(Types.{{contract.name.capitalized}}.{{event.name.capitalized}}),
Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally to pass everything via the event options and get rid of the generated eventMod (use it only for types)

},
{{/each}}
]
},
{{/each}}
]
}))
{{/with}}
{{/if}}
Expand Down
15 changes: 14 additions & 1 deletion codegenerator/cli/templates/static/codegen/src/bindings/Fuel.res
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
type receiptType = | @as(6) LogData
// 0 = Call
// 1 = Return,
// 2 = ReturnData,
// 3 = Panic,
// 4 = Revert,
// 5 = Log,
// 6 = LogData,
// 7 = Transfer,
// 8 = Transferout,
// 9 = ScriptResult,
// 10 = MessageOut,
// 11 = Mint,
// 12 = Burn,
type receiptType = | @as(6) LogData | @as(11) Mint | @as(12) Burn

@module("./vendored-fuel-abi-coder.js")
external transpileAbi: Js.Json.t => Ethers.abi = "transpileAbi"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,129 @@ open Belt

exception EventRoutingFailed

type event = {
name: string,
logId?: string,
mint?: bool,
eventMod: module(Types.Event),
}

type contract = {
name: string,
addresses: array<Address.t>,
events: array<event>,
}

let makeGetRecieptsSelectionOrThrow = (~contracts: array<contract>) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Extracted it to a factory, I'm going to write a few tests tomorrow. Ideally to do the same for HyperSync. Also, what's good about it that it'll allow to get rid of Evm specific fields from EventMod

let logDataReceiptTypeSelection: array<Fuel.receiptType> = [LogData]
let mintReceiptTypeSelection: array<Fuel.receiptType> = [Mint]

// only transactions with status 1 (success)
let txStatusSelection = [1]

let mint: ref<[#None | #Wildcard | #NonWildcard]> = ref(#None)
let nonWildcardRbsByContract = Js.Dict.empty()
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is rbs short for?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh I understand it's plural of "rb" 👍🏼

let wildcardRbs = []

let wildcardMintSelection = (
{
receiptType: mintReceiptTypeSelection,
txStatus: txStatusSelection,
}: HyperFuelClient.QueryTypes.receiptSelection
)
DZakh marked this conversation as resolved.
Show resolved Hide resolved
let maybeWildcardLogSelection = switch wildcardRbs {
| [] => None
| wildcardRbs =>
Some(
(
{
receiptType: logDataReceiptTypeSelection,
txStatus: txStatusSelection,
rb: wildcardRbs,
}: HyperFuelClient.QueryTypes.receiptSelection
),
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this always the None case? I don't see where wildcardRbs gets any values added before this.

Copy link
Member Author

Choose a reason for hiding this comment

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

True, caught this as well, while I writing tests


contracts->Array.forEach(contract => {
let nonWildcardRbs = []
contract.events->Array.forEach(event => {
let module(Event) = event.eventMod
let {isWildcard} = Event.handlerRegister->Types.HandlerTypes.Register.getEventOptions
switch event {
| {mint: true} => mint := (isWildcard ? #Wildcard : #NonWildcard)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like "mint" can be over written with "#Wildcard or #NonWildcard", what should happen if both appear?

| {logId} => {
let rb = logId->BigInt.fromStringUnsafe
if isWildcard {
wildcardRbs->Array.push(rb)->ignore
} else {
nonWildcardRbs->Array.push(rb)->ignore
}
}
| _ => Js.Exn.raiseError(`Event ${Event.name} is not a mint or log`)
}
})
nonWildcardRbsByContract->Js.Dict.set(contract.name, nonWildcardRbs)
})

(~contractAddressMapping, ~shouldApplyWildcards) => {
let selection: array<HyperFuelClient.QueryTypes.receiptSelection> = []

//Instantiate each time to add new registered contract addresses
contracts->Array.forEach(contract => {
switch contractAddressMapping->ContractAddressingMap.getAddressesFromContractName(
~contractName=contract.name,
) {
| [] => ()
| addresses => {
if mint.contents == #NonWildcard {
selection
->Js.Array2.push({
rootContractId: addresses,
receiptType: mintReceiptTypeSelection,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any chance you can leave some comments on the conditional logic here? It's a little difficult to understand from just the code.

txStatus: txStatusSelection,
})
->ignore
}
switch nonWildcardRbsByContract->Utils.Dict.dangerouslyGetNonOption(contract.name) {
| None => ()
| Some(nonWildcardRbs) =>
selection
->Js.Array2.push({
rootContractId: addresses,
receiptType: logDataReceiptTypeSelection,
txStatus: txStatusSelection,
rb: nonWildcardRbs,
})
->ignore
}
}
}
})

if shouldApplyWildcards {
if mint.contents == #Wildcard {
selection
->Array.push(wildcardMintSelection)
->ignore
}
switch maybeWildcardLogSelection {
| None => ()
| Some(wildcardLogSelection) =>
selection
->Array.push(wildcardLogSelection)
->ignore
}
}

selection
}
}

Comment on lines +134 to +139
Copy link
Collaborator

Choose a reason for hiding this comment

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

Whew 😅 this function was quite difficult to read. Maybe we can break it up a bit. I worry about logic errors creeping in when it's difficult to read end to end.

module Make = (
T: {
let chain: ChainMap.Chain.t
let contracts: array<Config.contract>
let contracts: array<contract>
let endpointUrl: string
let eventModLookup: EventModLookup.t
},
Expand Down Expand Up @@ -90,22 +209,7 @@ module Make = (
}
}

let wildcardLogSelection = T.contracts->Belt.Array.keepMap((contract): option<
HyperFuel.contractReceiptQuery,
> => {
let wildcardRb = contract.events->Belt.Array.keepMap(event => {
let module(Event) = event
let {isWildcard} = Event.handlerRegister->Types.HandlerTypes.Register.getEventOptions
isWildcard ? Some(Event.sighash->BigInt.fromStringUnsafe) : None
})
switch wildcardRb {
| [] => None
| _ =>
Some({
rb: wildcardRb,
})
}
})
let getRecieptsSelectionOrThrow = makeGetRecieptsSelectionOrThrow(~contracts=T.contracts)

let getNextPage = async (
~fromBlock,
Expand All @@ -128,40 +232,17 @@ module Make = (
)

//Instantiate each time to add new registered contract addresses
let contractsReceiptQuery = T.contracts->Belt.Array.keepMap((contract): option<
HyperFuel.contractReceiptQuery,
> => {
switch contractAddressMapping->ContractAddressingMap.getAddressesFromContractName(
~contractName=contract.name,
) {
| [] => None
| addresses =>
Some({
addresses,
rb: contract.events->Array.keepMap(eventMod => {
let module(Event: Types.Event) = eventMod
let {isWildcard} = Event.handlerRegister->Types.HandlerTypes.Register.getEventOptions
isWildcard ? None : Some(Event.sighash->BigInt.fromStringUnsafe)
}),
})
}
})

let contractsReceiptQuery = shouldApplyWildcards
? contractsReceiptQuery->Array.concat(wildcardLogSelection)
Copy link
Member Author

Choose a reason for hiding this comment

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

Bye bye concat

: contractsReceiptQuery
let recieptsSelection = getRecieptsSelectionOrThrow(
~contractAddressMapping,
~shouldApplyWildcards,
)

let startFetchingBatchTimeRef = Hrtime.makeTimer()

//fetch batch
let pageUnsafe = await Helpers.queryLogsPageWithBackoff(
() =>
HyperFuel.queryLogsPage(
~serverUrl=T.endpointUrl,
~fromBlock,
~toBlock,
~contractsReceiptQuery,
),
HyperFuel.queryLogsPage(~serverUrl=T.endpointUrl, ~fromBlock, ~toBlock, ~recieptsSelection),
logger,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ type blockNumberAndHash = {

type logsQueryPage = hyperSyncPage<item>

type contractReceiptQuery = {
addresses?: array<Address.t>,
rb: array<bigint>,
}

type missingParams = {
queryName: string,
missingParams: array<string>,
Expand Down Expand Up @@ -89,27 +84,15 @@ let queryErrorToMsq = (e: queryError): string => {
type queryResponse<'a> = result<'a, queryError>

module LogsQuery = {
let receiptTypeSelection: array<Fuel.receiptType> = [LogData]
// only transactions with status 1 (success)
let txStatusSelection = [1]

let makeRequestBody = (
~fromBlock,
~toBlockInclusive,
~contractsReceiptQuery: array<contractReceiptQuery>,
~recieptsSelection,
): HyperFuelClient.QueryTypes.query => {
let receipts = contractsReceiptQuery->Js.Array2.map((
q
): HyperFuelClient.QueryTypes.receiptSelection => {
rootContractId: ?q.addresses,
receiptType: receiptTypeSelection,
rb: q.rb,
txStatus: txStatusSelection,
})
Comment on lines -101 to -108
Copy link
Member Author

Choose a reason for hiding this comment

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

Like how everything became much lighter after moving it to hyperFuelWorker

{
fromBlock,
toBlockExclusive: toBlockInclusive + 1,
receipts,
receipts: recieptsSelection,
fieldSelection: {
receipt: [
TxId,
Expand Down Expand Up @@ -198,16 +181,13 @@ module LogsQuery = {
}
}

let queryLogsPage = async (
~serverUrl,
~fromBlock,
~toBlock,
~contractsReceiptQuery,
): queryResponse<logsQueryPage> => {
let queryLogsPage = async (~serverUrl, ~fromBlock, ~toBlock, ~recieptsSelection): queryResponse<
logsQueryPage,
> => {
let query: HyperFuelClient.QueryTypes.query = makeRequestBody(
~fromBlock,
~toBlockInclusive=toBlock,
~contractsReceiptQuery,
~recieptsSelection,
)

let hyperFuelClient = CachedClients.getClient(serverUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ type blockNumberAndHash = {

type logsQueryPage = hyperSyncPage<item>

type contractReceiptQuery = {
addresses?: array<Address.t>,
rb: array<bigint>,
}

type missingParams = {
queryName: string,
missingParams: array<string>,
Expand All @@ -47,7 +42,7 @@ let queryLogsPage: (
~serverUrl: string,
~fromBlock: int,
~toBlock: int,
~contractsReceiptQuery: array<contractReceiptQuery>,
~recieptsSelection: array<HyperFuelClient.QueryTypes.receiptSelection>,
) => promise<queryResponse<logsQueryPage>>

let queryBlockData: (
Expand Down
Loading
Loading