envio 3.13.0 → 3.14.0-subgraph

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/bin.mjs +10 -1
  2. package/index.d.ts +11 -0
  3. package/package.json +8 -20
  4. package/rescript.json +1 -4
  5. package/src/Config.res +78 -100
  6. package/src/Config.res.mjs +55 -64
  7. package/src/Core.res +67 -6
  8. package/src/Core.res.mjs +58 -4
  9. package/src/EventConfigBuilder.res +4 -10
  10. package/src/EventConfigBuilder.res.mjs +1 -3
  11. package/src/EventProcessing.res +6 -6
  12. package/src/EventProcessing.res.mjs +8 -6
  13. package/src/ExitOnCaughtUp.res +5 -1
  14. package/src/ExitOnCaughtUp.res.mjs +8 -3
  15. package/src/HandlerLoader.res +98 -36
  16. package/src/HandlerLoader.res.mjs +89 -24
  17. package/src/HandlerTypeCheck.mjs +14 -0
  18. package/src/HandlerTypeCheckWorker.mjs +68 -0
  19. package/src/Internal.res +1 -1
  20. package/src/LoadLayer.res +39 -7
  21. package/src/LoadLayer.res.mjs +65 -38
  22. package/src/LoadLayer.resi +28 -0
  23. package/src/Main.res +17 -12
  24. package/src/Main.res.mjs +9 -3
  25. package/src/NodeVersion.mjs +13 -0
  26. package/src/Persistence.res +79 -38
  27. package/src/Persistence.res.mjs +41 -10
  28. package/src/PgStorage.res +86 -39
  29. package/src/PgStorage.res.mjs +83 -32
  30. package/src/ResumePlan.res +172 -0
  31. package/src/ResumePlan.res.mjs +122 -0
  32. package/src/Supervisor.res +8 -8
  33. package/src/Supervisor.res.mjs +6 -1
  34. package/src/SyncWrites.res +86 -0
  35. package/src/SyncWrites.res.mjs +85 -0
  36. package/src/TestIndexer.res +14 -4
  37. package/src/TestIndexer.res.mjs +3 -2
  38. package/src/TsModuleHooks.mjs +121 -0
  39. package/src/UserContext.res +377 -62
  40. package/src/UserContext.res.mjs +297 -39
  41. package/src/Utils.res +0 -8
  42. package/src/Worker.res +8 -0
  43. package/src/Worker.res.mjs +11 -0
  44. package/src/bindings/NodeJs.res +8 -7
  45. package/src/bindings/NodeJs.res.mjs +0 -3
  46. package/src/bindings/Pino.res +2 -2
  47. package/src/db/InternalTable.res +52 -2
  48. package/src/db/InternalTable.res.mjs +32 -3
  49. package/src/db/Table.res +10 -0
  50. package/src/db/Table.res.mjs +22 -11
  51. package/src/sources/FuelHyperSync.res +2 -0
  52. package/src/sources/FuelHyperSync.res.mjs +1 -0
  53. package/src/sources/FuelHyperSync.resi +1 -0
  54. package/src/sources/FuelHyperSyncClient.res +25 -8
  55. package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
  56. package/src/sources/FuelHyperSyncSource.res +24 -22
  57. package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
  58. package/src/subgraph/abi-types.ts +63 -0
  59. package/src/subgraph/assemblyscript.ts +85 -0
  60. package/src/subgraph/blocks.ts +183 -0
  61. package/src/subgraph/calls.ts +149 -0
  62. package/src/subgraph/conformance.ts +83 -0
  63. package/src/subgraph/errors.ts +96 -0
  64. package/src/subgraph/graph-cli-worker.mjs +26 -0
  65. package/src/subgraph/graph-cli.ts +190 -0
  66. package/src/subgraph/graph-ts-enums.ts +28 -0
  67. package/src/subgraph/graph-ts-types/VERSION +2 -0
  68. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  69. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  70. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  71. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  72. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  73. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  74. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  75. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  76. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  77. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  78. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  79. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  80. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  81. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  82. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  83. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  84. package/src/subgraph/graph-ts.ts +2285 -0
  85. package/src/subgraph/hosts.ts +158 -0
  86. package/src/subgraph/rpc.ts +65 -0
  87. package/src/subgraph/runtime.ts +595 -0
  88. package/src/subgraph/scope.ts +68 -0
  89. package/src/tui/InitApi.res +85 -0
  90. package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
  91. package/src/tui/Tui.res +142 -249
  92. package/src/tui/Tui.res.mjs +123 -332
  93. package/src/Js.shim.ts +0 -11
  94. package/src/bindings/DateFns.res +0 -74
  95. package/src/bindings/DateFns.res.mjs +0 -22
  96. package/src/sources/FuelSDK.res +0 -38
  97. package/src/sources/FuelSDK.res.mjs +0 -27
  98. package/src/sources/vendored-fuel-abi-coder.js +0 -1792
  99. package/src/tui/bindings/Ink.res +0 -369
  100. package/src/tui/bindings/Ink.res.mjs +0 -66
  101. package/src/tui/bindings/Style.res +0 -123
  102. package/src/tui/bindings/Style.res.mjs +0 -2
  103. package/src/tui/components/BufferedProgressBar.res +0 -40
  104. package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
  105. package/src/tui/components/CustomHooks.res +0 -122
  106. package/src/tui/components/Messages.res +0 -40
  107. package/src/tui/components/Messages.res.mjs +0 -71
  108. package/src/tui/components/SyncETA.res +0 -179
  109. package/src/tui/components/SyncETA.res.mjs +0 -249
  110. package/src/tui/components/TuiData.res +0 -98
  111. package/src/tui/components/TuiData.res.mjs +0 -73
@@ -133,12 +133,15 @@ function $$delete(sql, pgSchema, keys, chainIdModeOpt) {
133
133
  ], {prepare: true});
134
134
  }
135
135
 
136
- function makeGetRowsQuery(pgSchema) {
136
+ function makeGetRowsQuery(pgSchema, onlyConfigOpt) {
137
+ let onlyConfig = onlyConfigOpt !== undefined ? onlyConfigOpt : false;
137
138
  return `SELECT "chain_id" as "chainId",
138
139
  "address" as "address",
139
140
  "contract_id" as "contractId",
140
141
  "registration_block" as "registrationBlock"
141
- FROM "` + pgSchema + `"."` + table$1.tableName + `";`;
142
+ FROM "` + pgSchema + `"."` + table$1.tableName + `"` + (
143
+ onlyConfig ? ` WHERE "registration_block" = ` + AddressRows.configRegistrationBlock.toString() : ""
144
+ ) + `;`;
142
145
  }
143
146
 
144
147
  let EnvioAddresses = {
@@ -264,6 +267,31 @@ WHERE "` + "id" + `" = $2
264
267
  AND "` + "ready_at" + `" IS NULL;`;
265
268
  }
266
269
 
270
+ async function readStoredChains(sql, pgSchema) {
271
+ let match = await Promise.all([
272
+ sql.unsafe(`SELECT "` + "id" + `" as "id",
273
+ "` + "ecosystem" + `" as "ecosystem",
274
+ "` + "start_block" + `" as "startBlock",
275
+ "` + "end_block" + `" as "endBlock",
276
+ "` + "max_reorg_depth" + `" as "maxReorgDepth"
277
+ FROM "` + pgSchema + `"."` + table$2.tableName + `";`),
278
+ sql.unsafe(makeGetRowsQuery(pgSchema, true))
279
+ ]);
280
+ let configAddressesByChain = {};
281
+ match[1].forEach(addressRow => Utils.Dict.push(configAddressesByChain, ChainId.toString(ChainId.normalizeOrThrow(addressRow.chainId)), addressRow));
282
+ return match[0].map(row => {
283
+ let id = ChainId.normalizeOrThrow(row.id);
284
+ return {
285
+ id: id,
286
+ ecosystem: row.ecosystem,
287
+ startBlock: row.startBlock,
288
+ endBlock: Primitive_option.fromNull(row.endBlock),
289
+ maxReorgDepth: row.maxReorgDepth,
290
+ configAddresses: Stdlib_Option.getOr(configAddressesByChain[ChainId.toString(id)], [])
291
+ };
292
+ });
293
+ }
294
+
267
295
  function makeGetInitialStateQuery(pgSchema) {
268
296
  return `SELECT "` + "id" + `" as "id",
269
297
  "` + "start_block" + `" as "startBlock",
@@ -282,7 +310,7 @@ FROM "` + pgSchema + `"."` + table$2.tableName + `";`;
282
310
  async function getInitialState(sql, pgSchema) {
283
311
  let match = await Promise.all([
284
312
  sql.unsafe(makeGetInitialStateQuery(pgSchema)),
285
- sql.unsafe(makeGetRowsQuery(pgSchema))
313
+ sql.unsafe(makeGetRowsQuery(pgSchema, undefined))
286
314
  ]);
287
315
  let addressRowsByChainId = AddressRows.group(match[1]);
288
316
  return match[0].map(rawInitialState => {
@@ -384,6 +412,7 @@ let Chains = {
384
412
  metaFields: metaFields,
385
413
  makeMetaFieldsUpdateQuery: makeMetaFieldsUpdateQuery,
386
414
  makeSetReadyAtQuery: makeSetReadyAtQuery,
415
+ readStoredChains: readStoredChains,
387
416
  makeGetInitialStateQuery: makeGetInitialStateQuery,
388
417
  getInitialState: getInitialState,
389
418
  blockTimeToDb: blockTimeToDb,
package/src/db/Table.res CHANGED
@@ -454,6 +454,7 @@ type sqlParams<'entity> = {
454
454
  quotedNonPrimaryFieldNames: array<string>,
455
455
  arrayFieldTypes: array<string>,
456
456
  byteaColumnIndexes: array<int>,
457
+ booleanArrayColumnIndexes: array<int>,
457
458
  hasArrayField: bool,
458
459
  }
459
460
 
@@ -464,6 +465,10 @@ let toSqlParams = (table: table, ~schema, ~pgSchema, ~chainIdMode: ChainId.mode=
464
465
  // Positions of the bytea columns among the unnest parameters, which the
465
466
  // caller binds as array literals (see `Utils.Bytes.toPgArrayLiteral`).
466
467
  let byteaColumnIndexes = []
468
+ // postgres binds a JS array of booleans as a single `boolean`, whatever the
469
+ // column (https://github.com/porsager/postgres/issues/471), so the VALUES
470
+ // insert binds these columns as integer arrays and casts them back.
471
+ let booleanArrayColumnIndexes = []
467
472
  let hasArrayField = ref(false)
468
473
 
469
474
  let dbSchema: S.t<dict<unknown>> = S.schema(s =>
@@ -499,6 +504,10 @@ let toSqlParams = (table: table, ~schema, ~pgSchema, ~chainIdMode: ChainId.mode=
499
504
  | None => throw(NonExistingTableField(location))
500
505
  }
501
506
  switch field {
507
+ | Field({isArray: true, fieldType: Boolean}) => {
508
+ hasArrayField := true
509
+ booleanArrayColumnIndexes->Array.push(arrayFieldTypes->Array.length)->ignore
510
+ }
502
511
  | Field({isArray: true}) => hasArrayField := true
503
512
  | Field({fieldType: Bytea}) =>
504
513
  byteaColumnIndexes->Array.push(arrayFieldTypes->Array.length)->ignore
@@ -554,6 +563,7 @@ let toSqlParams = (table: table, ~schema, ~pgSchema, ~chainIdMode: ChainId.mode=
554
563
  quotedNonPrimaryFieldNames,
555
564
  arrayFieldTypes,
556
565
  byteaColumnIndexes,
566
+ booleanArrayColumnIndexes,
557
567
  hasArrayField: hasArrayField.contents,
558
568
  }
559
569
  }
@@ -384,6 +384,7 @@ function toSqlParams(table, schema, pgSchema, chainIdModeOpt) {
384
384
  let quotedNonPrimaryFieldNames = [];
385
385
  let arrayFieldTypes = [];
386
386
  let byteaColumnIndexes = [];
387
+ let booleanArrayColumnIndexes = [];
387
388
  let hasArrayField = {
388
389
  contents: false
389
390
  };
@@ -441,11 +442,20 @@ function toSqlParams(table, schema, pgSchema, chainIdModeOpt) {
441
442
  }
442
443
  if (field$1.TAG === "Field") {
443
444
  let match = field$1._0;
444
- if (match.isArray) {
445
- hasArrayField.contents = true;
445
+ let match$1 = match.fieldType;
446
+ let exit = 0;
447
+ if (typeof match$1 !== "object" && match$1 === "Boolean") {
448
+ if (match.isArray) {
449
+ hasArrayField.contents = true;
450
+ booleanArrayColumnIndexes.push(arrayFieldTypes.length);
451
+ }
446
452
  } else {
447
- let tmp = match.fieldType;
448
- if (typeof tmp !== "object" && tmp === "Bytea") {
453
+ exit = 1;
454
+ }
455
+ if (exit === 1) {
456
+ if (match.isArray) {
457
+ hasArrayField.contents = true;
458
+ } else if (typeof match$1 !== "object" && match$1 === "Bytea") {
449
459
  byteaColumnIndexes.push(arrayFieldTypes.length);
450
460
  }
451
461
  }
@@ -459,20 +469,20 @@ function toSqlParams(table, schema, pgSchema, chainIdModeOpt) {
459
469
  quotedNonPrimaryFieldNames.push(quotedDbName);
460
470
  }
461
471
  }
462
- let tmp$1;
472
+ let tmp;
463
473
  if (field$1.TAG === "Field") {
464
474
  let f = field$1._0;
465
475
  let pgFieldType = getPgFieldType(f.fieldType, pgSchema, true, false, f.isNullable, chainIdMode);
466
- let match$1 = f.fieldType;
467
- tmp$1 = typeof match$1 !== "object" ? (
468
- match$1 === "Boolean" ? "INTEGER" + `[]::` + pgFieldType : pgFieldType
476
+ let match$2 = f.fieldType;
477
+ tmp = typeof match$2 !== "object" ? (
478
+ match$2 === "Boolean" ? "INTEGER" + `[]::` + pgFieldType : pgFieldType
469
479
  ) : (
470
- match$1.type === "Enum" ? "TEXT" + `[]::` + pgFieldType : pgFieldType
480
+ match$2.type === "Enum" ? "TEXT" + `[]::` + pgFieldType : pgFieldType
471
481
  );
472
482
  } else {
473
- tmp$1 = "TEXT[]";
483
+ tmp = "TEXT[]";
474
484
  }
475
- arrayFieldTypes.push(tmp$1);
485
+ arrayFieldTypes.push(tmp);
476
486
  dict[location] = s.m(coerceSchema(param.schema));
477
487
  });
478
488
  return dict;
@@ -483,6 +493,7 @@ function toSqlParams(table, schema, pgSchema, chainIdModeOpt) {
483
493
  quotedNonPrimaryFieldNames: quotedNonPrimaryFieldNames,
484
494
  arrayFieldTypes: arrayFieldTypes,
485
495
  byteaColumnIndexes: byteaColumnIndexes,
496
+ booleanArrayColumnIndexes: booleanArrayColumnIndexes,
486
497
  hasArrayField: hasArrayField.contents
487
498
  };
488
499
  }
@@ -1,5 +1,6 @@
1
1
  type logsQueryPage = {
2
2
  items: array<FuelHyperSyncClient.EventItems.item>,
3
+ rejectedLogData: array<FuelHyperSyncClient.EventItems.rejectedLogData>,
3
4
  // Blocks referenced by `items`, one per height.
4
5
  nextBlock: int,
5
6
  archiveHeight: int,
@@ -72,6 +73,7 @@ module GetLogs = {
72
73
  }
73
74
  {
74
75
  items: res.items,
76
+ rejectedLogData: res.rejectedLogData,
75
77
  nextBlock: res.nextBlock,
76
78
  archiveHeight: res.archiveHeight->Option.getOr(0), // TODO: FIXME: Shouldn't have a default here
77
79
  blockStore,
@@ -64,6 +64,7 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
64
64
  }
65
65
  return {
66
66
  items: res.items,
67
+ rejectedLogData: res.rejectedLogData,
67
68
  nextBlock: res.nextBlock,
68
69
  archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0),
69
70
  blockStore: match[1]
@@ -1,5 +1,6 @@
1
1
  type logsQueryPage = {
2
2
  items: array<FuelHyperSyncClient.EventItems.item>,
3
+ rejectedLogData: array<FuelHyperSyncClient.EventItems.rejectedLogData>,
3
4
  nextBlock: int,
4
5
  archiveHeight: int,
5
6
  // Page store owning this page's raw blocks (built on the Rust side); its ids
@@ -27,6 +27,9 @@ module Registration = {
27
27
  kind: kind,
28
28
  // The LogData `rb` value as a decimal string; absent for other kinds.
29
29
  logId?: string,
30
+ // The contract's Fuel ABI, which LogData receipts are decoded against in
31
+ // Rust; absent for other kinds.
32
+ abi?: JSON.t,
30
33
  }
31
34
 
32
35
  let fromOnEventRegistrations = (
@@ -35,12 +38,12 @@ module Registration = {
35
38
  onEventRegistrations->Array.map(reg => {
36
39
  let eventConfig =
37
40
  reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.fuelEventConfig)
38
- let (kind, logId) = switch eventConfig.kind {
39
- | LogData({logId}) => (LogData, Some(logId))
40
- | Mint => (Mint, None)
41
- | Burn => (Burn, None)
42
- | Transfer => (Transfer, None)
43
- | Call => (Call, None)
41
+ let (kind, logId, abi) = switch eventConfig.kind {
42
+ | LogData({logId, abi}) => (LogData, Some(logId), Some(abi))
43
+ | Mint => (Mint, None, None)
44
+ | Burn => (Burn, None, None)
45
+ | Transfer => (Transfer, None, None)
46
+ | Call => (Call, None, None)
44
47
  }
45
48
  {
46
49
  index: reg.index,
@@ -50,6 +53,7 @@ module Registration = {
50
53
  startBlock: reg.startBlock,
51
54
  kind,
52
55
  ?logId,
56
+ ?abi,
53
57
  }
54
58
  })
55
59
  }
@@ -70,7 +74,8 @@ module EventItems = {
70
74
  }
71
75
 
72
76
  // One routed receipt with its kind-specific columns flattened: LogData
73
- // carries `data` (decoded here in JS), Mint/Burn carry `val`/`subId`,
77
+ // carries `params` decoded against the ABI (a receipt its logged type
78
+ // rejects is dropped in Rust), Mint/Burn carry `val`/`subId`,
74
79
  // Transfer/TransferOut/Call carry `amount`/`assetId`/`to` (TransferOut's
75
80
  // wallet recipient normalised into `to`).
76
81
  type item = {
@@ -79,7 +84,7 @@ module EventItems = {
79
84
  txId: string,
80
85
  blockHeight: int,
81
86
  srcAddress: Address.t,
82
- data?: string,
87
+ params?: Internal.eventParams,
83
88
  subId?: string,
84
89
  val?: bigint,
85
90
  amount?: bigint,
@@ -87,10 +92,22 @@ module EventItems = {
87
92
  to?: string,
88
93
  }
89
94
 
95
+ // The page's first LogData receipt a contract-bound registration's ABI
96
+ // rejected, with how many it rejected. The receipts themselves are dropped.
97
+ type rejectedLogData = {
98
+ onEventRegistrationIndex: int,
99
+ blockHeight: int,
100
+ receiptIndex: int,
101
+ txId: string,
102
+ dataLength: int,
103
+ count: int,
104
+ }
105
+
90
106
  type response = {
91
107
  archiveHeight: option<int>,
92
108
  nextBlock: int,
93
109
  items: array<item>,
110
+ rejectedLogData: array<rejectedLogData>,
94
111
  }
95
112
  }
96
113
 
@@ -13,24 +13,28 @@ function fromOnEventRegistrations(onEventRegistrations) {
13
13
  case "Mint" :
14
14
  match$1 = [
15
15
  "Mint",
16
+ undefined,
16
17
  undefined
17
18
  ];
18
19
  break;
19
20
  case "Burn" :
20
21
  match$1 = [
21
22
  "Burn",
23
+ undefined,
22
24
  undefined
23
25
  ];
24
26
  break;
25
27
  case "Transfer" :
26
28
  match$1 = [
27
29
  "Transfer",
30
+ undefined,
28
31
  undefined
29
32
  ];
30
33
  break;
31
34
  case "Call" :
32
35
  match$1 = [
33
36
  "Call",
37
+ undefined,
34
38
  undefined
35
39
  ];
36
40
  break;
@@ -38,7 +42,8 @@ function fromOnEventRegistrations(onEventRegistrations) {
38
42
  } else {
39
43
  match$1 = [
40
44
  "LogData",
41
- match.logId
45
+ match.logId,
46
+ match.abi
42
47
  ];
43
48
  }
44
49
  return {
@@ -48,7 +53,8 @@ function fromOnEventRegistrations(onEventRegistrations) {
48
53
  isWildcard: reg.isWildcard,
49
54
  startBlock: reg.startBlock,
50
55
  kind: match$1[0],
51
- logId: match$1[1]
56
+ logId: match$1[1],
57
+ abi: match$1[2]
52
58
  };
53
59
  });
54
60
  }
@@ -16,6 +16,9 @@ let make = ({chainId, endpointUrl, apiToken, onEventRegistrations, addressStore}
16
16
  // Per source, so one rejected token is reported once rather than on every
17
17
  // height retry for the life of the process.
18
18
  let unauthorizedWarned = ref(false)
19
+ // Registration indexes already warned about undecodable LogData — a stale
20
+ // ABI rejects every receipt, so one line per event is enough.
21
+ let rejectedLogDataWarned = Utils.Set.make()
19
22
 
20
23
  let apiToken = apiToken->HyperSync.requireApiToken
21
24
 
@@ -106,6 +109,24 @@ let make = ({chainId, endpointUrl, apiToken, onEventRegistrations, addressStore}
106
109
 
107
110
  let parsingTimeRef = Performance.now()
108
111
 
112
+ pageUnsafe.rejectedLogData->Array.forEach(rejected => {
113
+ if !(rejectedLogDataWarned->Utils.Set.has(rejected.onEventRegistrationIndex)) {
114
+ rejectedLogDataWarned->Utils.Set.add(rejected.onEventRegistrationIndex)->ignore
115
+ let eventConfig = (
116
+ onEventRegistrations->Array.getUnsafe(rejected.onEventRegistrationIndex)
117
+ ).eventConfig
118
+ logger->Logging.childWarn({
119
+ "msg": `Skipped ${eventConfig.contractName}.${eventConfig.name} LogData receipts whose data doesn't decode against the contract ABI. Check that the ABI matches the deployed contract.`,
120
+ "chainId": chainId,
121
+ "blockNumber": rejected.blockHeight,
122
+ "logIndex": rejected.receiptIndex,
123
+ "txId": rejected.txId,
124
+ "dataLength": rejected.dataLength,
125
+ "skippedInPage": rejected.count,
126
+ })
127
+ }
128
+ })
129
+
109
130
  let parsedQueueItems = pageUnsafe.items->Array.map(item => {
110
131
  // Routing happened in Rust; the item references its registration by
111
132
  // chain-scoped index.
@@ -116,24 +137,8 @@ let make = ({chainId, endpointUrl, apiToken, onEventRegistrations, addressStore}
116
137
  )
117
138
 
118
139
  let params = switch eventConfig.kind {
119
- | LogData({decode}) =>
120
- // Kind-required columns are validated present in Rust before the item
121
- // crosses the boundary.
122
- let data = item.data->Option.getOr("")
123
- try decode(data) catch {
124
- | exn => {
125
- let params = {
126
- "chainId": chainId,
127
- "blockNumber": item.blockHeight,
128
- "logIndex": item.receiptIndex,
129
- }
130
- let logger = Logging.createChildFrom(~logger, ~params)
131
- exn->ErrorHandling.mkLogAndRaise(
132
- ~msg="Failed to decode Fuel LogData receipt, please double check your ABI.",
133
- ~logger,
134
- )
135
- }
136
- }
140
+ // Rust only routes a LogData receipt once its data decoded.
141
+ | LogData(_) => item.params->Option.getUnsafe
137
142
  | Mint | Burn =>
138
143
  (
139
144
  {
@@ -213,10 +218,7 @@ let make = ({chainId, endpointUrl, apiToken, onEventRegistrations, addressStore}
213
218
  let timerRef = Performance.now()
214
219
  let height = try await client->FuelHyperSyncClient.getHeight catch {
215
220
  | exn =>
216
- exn->HyperSync.rethrowLoggingUnauthorized(
217
- ~warned=unauthorizedWarned,
218
- ~product="HyperFuel",
219
- )
221
+ exn->HyperSync.rethrowLoggingUnauthorized(~warned=unauthorizedWarned, ~product="HyperFuel")
220
222
  }
221
223
  let seconds = timerRef->Performance.secondsSince
222
224
  {height, requestStats: [{method: "getHeight", seconds}]}
@@ -18,6 +18,7 @@ function make(param) {
18
18
  let unauthorizedWarned = {
19
19
  contents: false
20
20
  };
21
+ let rejectedLogDataWarned = new Set();
21
22
  let apiToken = HyperSync.requireApiToken(param.apiToken);
22
23
  let client;
23
24
  try {
@@ -82,6 +83,22 @@ function make(param) {
82
83
  let knownHeight$1 = pageUnsafe.archiveHeight;
83
84
  let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
84
85
  let parsingTimeRef = Performance.now();
86
+ pageUnsafe.rejectedLogData.forEach(rejected => {
87
+ if (rejectedLogDataWarned.has(rejected.onEventRegistrationIndex)) {
88
+ return;
89
+ }
90
+ rejectedLogDataWarned.add(rejected.onEventRegistrationIndex);
91
+ let eventConfig = onEventRegistrations[rejected.onEventRegistrationIndex].eventConfig;
92
+ Logging.childWarn(logger, {
93
+ msg: `Skipped ` + eventConfig.contractName + `.` + eventConfig.name + ` LogData receipts whose data doesn't decode against the contract ABI. Check that the ABI matches the deployed contract.`,
94
+ chainId: chainId,
95
+ blockNumber: rejected.blockHeight,
96
+ logIndex: rejected.receiptIndex,
97
+ txId: rejected.txId,
98
+ dataLength: rejected.dataLength,
99
+ skippedInPage: rejected.count
100
+ });
101
+ });
85
102
  let parsedQueueItems = pageUnsafe.items.map(item => {
86
103
  let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
87
104
  let eventConfig = onEventRegistration.eventConfig;
@@ -100,19 +117,7 @@ function make(param) {
100
117
  break;
101
118
  }
102
119
  } else {
103
- let data = Stdlib_Option.getOr(item.data, "");
104
- try {
105
- params = match.decode(data);
106
- } catch (raw_exn) {
107
- let exn = Primitive_exceptions.internalToException(raw_exn);
108
- let params$1 = {
109
- chainId: chainId,
110
- blockNumber: item.blockHeight,
111
- logIndex: item.receiptIndex
112
- };
113
- let logger$1 = Logging.createChildFrom(logger, params$1);
114
- params = ErrorHandling.mkLogAndRaise(logger$1, "Failed to decode Fuel LogData receipt, please double check your ABI.", exn);
115
- }
120
+ params = item.params;
116
121
  }
117
122
  switch (exit) {
118
123
  case 1 :
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Contract-call signatures and ABI type lists as graph codegen writes them.
3
+ * Free of envio imports: the graph-ts shim reads them too.
4
+ */
5
+
6
+ export type ParsedSignature = {
7
+ name: string;
8
+ inputs: string;
9
+ outputs: string;
10
+ };
11
+
12
+ /**
13
+ * graph codegen emits `"balanceOf(address):(uint256)"`. Types only, no names —
14
+ * which is all `parseAbiParameters` needs.
15
+ */
16
+ export function parseSignature(signature: string): ParsedSignature {
17
+ const open = signature.indexOf("(");
18
+ if (open === -1) {
19
+ throw new Error(`Unreadable contract call signature "${signature}"`);
20
+ }
21
+ const name = signature.slice(0, open);
22
+
23
+ let depth = 0;
24
+ let close = -1;
25
+ for (let index = open; index < signature.length; index++) {
26
+ const char = signature[index];
27
+ if (char === "(") depth++;
28
+ if (char === ")") {
29
+ depth--;
30
+ if (depth === 0) {
31
+ close = index;
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ if (close === -1) {
37
+ throw new Error(`Unreadable contract call signature "${signature}"`);
38
+ }
39
+
40
+ const inputs = signature.slice(open + 1, close);
41
+ const rest = signature.slice(close + 1).replace(/^:/, "");
42
+ const outputs = rest.startsWith("(") ? rest.slice(1, -1) : rest;
43
+
44
+ return { name, inputs, outputs };
45
+ }
46
+
47
+ /** `address,(uint256,bytes)` → `["address", "(uint256,bytes)"]`. */
48
+ export function splitTypes(list: string): string[] {
49
+ const types: string[] = [];
50
+ let depth = 0;
51
+ let start = 0;
52
+ for (let index = 0; index < list.length; index++) {
53
+ const char = list[index];
54
+ if (char === "(") depth++;
55
+ else if (char === ")") depth--;
56
+ else if (char === "," && depth === 0) {
57
+ types.push(list.slice(start, index));
58
+ start = index + 1;
59
+ }
60
+ }
61
+ if (list.length > 0) types.push(list.slice(start));
62
+ return types;
63
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The runtime half of `packages/cli/src/subgraph/assemblyscript.rs`, which
3
+ * rewrites every arithmetic and comparison operator in a mapping into a call
4
+ * on `OPERATORS_HELPER`, every `changetype<Foo>(x)` into one to `RETAG_HELPER`,
5
+ * and exports each handler's event class as `EVENT_CLASSES_EXPORT`. The names
6
+ * are shared with that file.
7
+ */
8
+
9
+ export const OPERATORS_HELPER = "__envio_op";
10
+ export const RETAG_HELPER = "__envio_retag";
11
+ export const EVENT_CLASSES_EXPORT = "__envio_event_classes";
12
+
13
+ /**
14
+ * AssemblyScript divides two integers as integers; this truncates only when
15
+ * both operands really are integers and otherwise divides as before — an `f64`
16
+ * divides as a float in AssemblyScript too.
17
+ */
18
+ export function integerDivision(a: unknown, b: unknown): unknown {
19
+ const left = typeof a === "object" && a !== null ? (a as any).valueOf() : a;
20
+ const right = typeof b === "object" && b !== null ? (b as any).valueOf() : b;
21
+
22
+ const integral = (v: unknown) =>
23
+ typeof v === "bigint" || (typeof v === "number" && Number.isInteger(v));
24
+ if (integral(left) && integral(right)) {
25
+ // Only i64 / i64 stays 64-bit; anything narrower is an i32 in AssemblyScript
26
+ // and must come back as a number, or the bigint spreads through every
27
+ // arithmetic that follows.
28
+ if (typeof left === "bigint" && typeof right === "bigint") {
29
+ return left / right;
30
+ }
31
+ return Math.trunc(Number(left) / Number(right));
32
+ }
33
+ return (left as number) / (right as number);
34
+ }
35
+
36
+ /** The graph-ts operator overload `value` declares under `method`, if any. */
37
+ function overload(value: unknown, method: string): Function | undefined {
38
+ if (!isObject(value)) return undefined;
39
+ const fn = (value as Record<string, unknown>)[method];
40
+ return typeof fn === "function" ? fn : undefined;
41
+ }
42
+
43
+ function isObject(value: unknown): boolean {
44
+ return value !== null && typeof value === "object";
45
+ }
46
+
47
+ // Both operands are objects wherever AssemblyScript picks the overload — it
48
+ // doesn't compile `BigInt < i32` — so a primitive on either side is left to
49
+ // JavaScript, which reads the object through `valueOf`.
50
+ const binary =
51
+ (method: string, fallback: (a: any, b: any) => unknown) =>
52
+ (a: unknown, b: unknown): unknown => {
53
+ const declared = isObject(b) ? overload(a, method) : undefined;
54
+ return declared ? declared.call(a, b) : fallback(a, b);
55
+ };
56
+
57
+ // Against null AssemblyScript compares the reference, overload or not.
58
+ const equals = (a: unknown, b: unknown): boolean => {
59
+ const declared = isObject(b) ? overload(a, "equals") : undefined;
60
+ return declared ? (declared.call(a, b) as boolean) : a == b;
61
+ };
62
+
63
+ /**
64
+ * graph-ts overloads the arithmetic and comparison operators on `BigInt`,
65
+ * `BigDecimal` and `ByteArray`, and AssemblyScript calls the overload: a
66
+ * JavaScript operator would coerce them, or compare their identity — so
67
+ * `address == ZERO_ADDRESS` would always be false.
68
+ */
69
+ export const operators = {
70
+ add: binary("plus", (a, b) => a + b),
71
+ sub: binary("minus", (a, b) => a - b),
72
+ mul: binary("times", (a, b) => a * b),
73
+ div: binary("div", integerDivision),
74
+ rem: binary("mod", (a, b) => a % b),
75
+ eq: equals,
76
+ ne: (a: unknown, b: unknown) => !equals(a, b),
77
+ lt: binary("lt", (a, b) => a < b),
78
+ le: binary("le", (a, b) => a <= b),
79
+ gt: binary("gt", (a, b) => a > b),
80
+ ge: binary("ge", (a, b) => a >= b),
81
+ neg: (a: unknown) => {
82
+ const declared = overload(a, "neg");
83
+ return declared ? declared.call(a) : -(a as number);
84
+ },
85
+ };