envio 3.3.0-alpha.1 → 3.3.0-alpha.10

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 (136) hide show
  1. package/evm.schema.json +10 -0
  2. package/index.d.ts +45 -25
  3. package/package.json +7 -7
  4. package/src/Address.res +5 -2
  5. package/src/Address.res.mjs +3 -1
  6. package/src/Api.res +1 -1
  7. package/src/Api.res.mjs +1 -1
  8. package/src/Batch.res +0 -6
  9. package/src/Batch.res.mjs +1 -12
  10. package/src/BatchProcessing.res +7 -12
  11. package/src/BatchProcessing.res.mjs +8 -7
  12. package/src/Bin.res +3 -0
  13. package/src/Bin.res.mjs +4 -0
  14. package/src/ChainFetching.res +43 -9
  15. package/src/ChainFetching.res.mjs +34 -15
  16. package/src/ChainMetadata.res +1 -11
  17. package/src/ChainMetadata.res.mjs +1 -10
  18. package/src/ChainState.res +605 -151
  19. package/src/ChainState.res.mjs +427 -104
  20. package/src/ChainState.resi +72 -4
  21. package/src/Config.res +61 -33
  22. package/src/Config.res.mjs +50 -10
  23. package/src/ContractRegisterContext.res +2 -12
  24. package/src/ContractRegisterContext.res.mjs +3 -5
  25. package/src/Core.res +14 -3
  26. package/src/CrossChainState.res +113 -89
  27. package/src/CrossChainState.res.mjs +64 -60
  28. package/src/CrossChainState.resi +1 -1
  29. package/src/Ecosystem.res +5 -6
  30. package/src/Ecosystem.res.mjs +3 -3
  31. package/src/Envio.res +17 -11
  32. package/src/EnvioGlobal.res +53 -0
  33. package/src/EnvioGlobal.res.mjs +31 -0
  34. package/src/EventConfigBuilder.res +177 -88
  35. package/src/EventConfigBuilder.res.mjs +99 -42
  36. package/src/EventProcessing.res +74 -28
  37. package/src/EventProcessing.res.mjs +55 -29
  38. package/src/ExitOnCaughtUp.res +10 -2
  39. package/src/ExitOnCaughtUp.res.mjs +10 -1
  40. package/src/FetchState.res +544 -356
  41. package/src/FetchState.res.mjs +412 -471
  42. package/src/HandlerLoader.res +8 -104
  43. package/src/HandlerLoader.res.mjs +2 -81
  44. package/src/HandlerRegister.res +518 -144
  45. package/src/HandlerRegister.res.mjs +285 -133
  46. package/src/HandlerRegister.resi +24 -8
  47. package/src/IndexerState.res +10 -3
  48. package/src/IndexerState.res.mjs +11 -4
  49. package/src/IndexerState.resi +2 -1
  50. package/src/IndexingAddresses.res +108 -0
  51. package/src/IndexingAddresses.res.mjs +101 -0
  52. package/src/IndexingAddresses.resi +34 -0
  53. package/src/Internal.res +155 -46
  54. package/src/Internal.res.mjs +30 -2
  55. package/src/LoadLayer.res +5 -5
  56. package/src/LoadLayer.res.mjs +6 -6
  57. package/src/LogSelection.res +102 -165
  58. package/src/LogSelection.res.mjs +101 -116
  59. package/src/Main.res +71 -236
  60. package/src/Main.res.mjs +60 -145
  61. package/src/Metrics.res +74 -0
  62. package/src/Metrics.res.mjs +75 -0
  63. package/src/PgStorage.res +4 -4
  64. package/src/PgStorage.res.mjs +5 -5
  65. package/src/Prometheus.res +10 -63
  66. package/src/Prometheus.res.mjs +124 -171
  67. package/src/PruneStaleHistory.res +2 -2
  68. package/src/PruneStaleHistory.res.mjs +3 -3
  69. package/src/RawEvent.res +7 -2
  70. package/src/RawEvent.res.mjs +4 -4
  71. package/src/Rollback.res +3 -3
  72. package/src/Rollback.res.mjs +4 -4
  73. package/src/RollbackCommit.res +4 -1
  74. package/src/RollbackCommit.res.mjs +3 -2
  75. package/src/SimulateDeadInputTracker.res +85 -0
  76. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  77. package/src/SimulateDeadInputTracker.resi +12 -0
  78. package/src/SimulateItems.res +91 -23
  79. package/src/SimulateItems.res.mjs +55 -32
  80. package/src/TestIndexer.res +54 -40
  81. package/src/TestIndexer.res.mjs +37 -30
  82. package/src/bindings/Performance.res +7 -0
  83. package/src/bindings/Performance.res.mjs +21 -0
  84. package/src/bindings/Performance.resi +7 -0
  85. package/src/bindings/Viem.res +0 -41
  86. package/src/bindings/Viem.res.mjs +1 -43
  87. package/src/sources/BlockStore.res +46 -0
  88. package/src/sources/BlockStore.res.mjs +24 -0
  89. package/src/sources/EventRouter.res +36 -33
  90. package/src/sources/EventRouter.res.mjs +13 -14
  91. package/src/sources/Evm.res +83 -72
  92. package/src/sources/Evm.res.mjs +53 -69
  93. package/src/sources/EvmChain.res +18 -20
  94. package/src/sources/EvmChain.res.mjs +15 -15
  95. package/src/sources/EvmRpcClient.res +74 -5
  96. package/src/sources/EvmRpcClient.res.mjs +13 -4
  97. package/src/sources/FieldMask.res +39 -0
  98. package/src/sources/FieldMask.res.mjs +42 -0
  99. package/src/sources/Fuel.res +9 -6
  100. package/src/sources/Fuel.res.mjs +5 -10
  101. package/src/sources/HyperFuelSource.res +46 -50
  102. package/src/sources/HyperFuelSource.res.mjs +64 -54
  103. package/src/sources/HyperSync.res +39 -7
  104. package/src/sources/HyperSync.res.mjs +57 -37
  105. package/src/sources/HyperSync.resi +12 -2
  106. package/src/sources/HyperSyncClient.res +22 -111
  107. package/src/sources/HyperSyncClient.res.mjs +2 -30
  108. package/src/sources/HyperSyncHeightStream.res +1 -8
  109. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  110. package/src/sources/HyperSyncSource.res +94 -107
  111. package/src/sources/HyperSyncSource.res.mjs +82 -86
  112. package/src/sources/Rpc.res +15 -47
  113. package/src/sources/Rpc.res.mjs +25 -56
  114. package/src/sources/RpcSource.res +320 -467
  115. package/src/sources/RpcSource.res.mjs +268 -385
  116. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  117. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  118. package/src/sources/SimulateSource.res +10 -4
  119. package/src/sources/SimulateSource.res.mjs +16 -6
  120. package/src/sources/Source.res +34 -6
  121. package/src/sources/SourceManager.res +77 -19
  122. package/src/sources/SourceManager.res.mjs +63 -21
  123. package/src/sources/SourceManager.resi +10 -0
  124. package/src/sources/Svm.res +31 -16
  125. package/src/sources/Svm.res.mjs +36 -13
  126. package/src/sources/SvmHyperSyncClient.res +5 -32
  127. package/src/sources/SvmHyperSyncSource.res +159 -134
  128. package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
  129. package/src/sources/TransactionStore.res +49 -0
  130. package/src/sources/TransactionStore.res.mjs +30 -0
  131. package/src/tui/Tui.res +13 -16
  132. package/src/tui/Tui.res.mjs +12 -14
  133. package/svm.schema.json +49 -37
  134. package/src/bindings/Hrtime.res +0 -58
  135. package/src/bindings/Hrtime.res.mjs +0 -90
  136. package/src/bindings/Hrtime.resi +0 -30
@@ -18,10 +18,10 @@ function convertFieldsToJson(fields) {
18
18
  }
19
19
  }
20
20
 
21
- function make(eventItem, block, transaction, params, srcAddress, cleanUpRawEventFieldsInPlace) {
21
+ function make(eventItem, block, transaction, params, srcAddress, blockHash, blockTimestamp, cleanUpRawEventFieldsInPlace) {
22
22
  let logIndex = eventItem.logIndex;
23
23
  let blockNumber = eventItem.blockNumber;
24
- let eventConfig = eventItem.eventConfig;
24
+ let eventConfig = eventItem.onEventRegistration.eventConfig;
25
25
  let eventId = EventUtils.packEventIndex(blockNumber, logIndex);
26
26
  let blockFields = convertFieldsToJson(block);
27
27
  let transactionFields = convertFieldsToJson(transaction);
@@ -36,8 +36,8 @@ function make(eventItem, block, transaction, params, srcAddress, cleanUpRawEvent
36
36
  block_number: blockNumber,
37
37
  log_index: logIndex,
38
38
  src_address: srcAddress,
39
- block_hash: eventItem.blockHash,
40
- block_timestamp: eventItem.timestamp,
39
+ block_hash: blockHash,
40
+ block_timestamp: blockTimestamp,
41
41
  block_fields: blockFields,
42
42
  transaction_fields: transactionFields,
43
43
  params: params$2
package/src/Rollback.res CHANGED
@@ -18,7 +18,7 @@ let getLastKnownValidBlock = async (
18
18
  ->ChainState.reorgDetection
19
19
  ->ReorgDetection.getThresholdBlockNumbersBelowBlock(
20
20
  ~blockNumber=reorgBlockNumber,
21
- ~knownHeight=(chainState->ChainState.fetchState).knownHeight,
21
+ ~knownHeight=chainState->ChainState.knownHeight,
22
22
  )
23
23
 
24
24
  switch scannedBlockNumbers {
@@ -94,7 +94,7 @@ and executeRollback = async (
94
94
  ~scheduleFetch,
95
95
  ~scheduleProcessing,
96
96
  ) => {
97
- let startTime = Hrtime.makeTimer()
97
+ let startTime = Performance.now()
98
98
 
99
99
  let chainState = state->IndexerState.getChainState(~chain=reorgChain)
100
100
 
@@ -194,7 +194,7 @@ and executeRollback = async (
194
194
  "targetCheckpointId": rollbackTargetCheckpointId,
195
195
  })
196
196
  Prometheus.RollbackSuccess.increment(
197
- ~timeSeconds=Hrtime.timeSince(startTime)->Hrtime.toSecondsFloat,
197
+ ~timeSeconds=Performance.secondsSince(startTime),
198
198
  ~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
199
199
  )
200
200
 
@@ -1,11 +1,11 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
4
  import * as Logging from "./Logging.res.mjs";
6
5
  import * as Writing from "./Writing.res.mjs";
7
6
  import * as ChainState from "./ChainState.res.mjs";
8
7
  import * as Prometheus from "./Prometheus.res.mjs";
8
+ import * as Performance from "./bindings/Performance.res.mjs";
9
9
  import * as IndexerState from "./IndexerState.res.mjs";
10
10
  import * as Stdlib_Float from "@rescript/runtime/lib/es6/Stdlib_Float.js";
11
11
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
@@ -18,7 +18,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
18
18
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
19
19
 
20
20
  async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime) {
21
- let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(ChainState.reorgDetection(chainState), reorgBlockNumber, ChainState.fetchState(chainState).knownHeight);
21
+ let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(ChainState.reorgDetection(chainState), reorgBlockNumber, ChainState.knownHeight(chainState));
22
22
  if (scannedBlockNumbers.length === 0) {
23
23
  return ChainState.getHighestBlockBelowThreshold(chainState);
24
24
  }
@@ -32,7 +32,7 @@ async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime)
32
32
  }
33
33
 
34
34
  async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing) {
35
- let startTime = Hrtime.makeTimer();
35
+ let startTime = Performance.now();
36
36
  let chainState = IndexerState.getChainState(state, reorgChain);
37
37
  let logger = Logging.createChildFrom(ChainState.logger(chainState), {
38
38
  action: "Rollback",
@@ -70,7 +70,7 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
70
70
  beforeCheckpointId: IndexerState.committedCheckpointId(state),
71
71
  targetCheckpointId: rollbackTargetCheckpointId
72
72
  });
73
- Prometheus.RollbackSuccess.increment(Hrtime.toSecondsFloat(Hrtime.timeSince(startTime)), rollbackedProcessedEvents);
73
+ Prometheus.RollbackSuccess.increment(Performance.secondsSince(startTime), rollbackedProcessedEvents);
74
74
  IndexerState.completeRollback(state, eventsProcessedDiffByChain);
75
75
  scheduleFetch();
76
76
  return scheduleProcessing();
@@ -6,7 +6,10 @@
6
6
  type args = {chainId: int, rollbackToBlock: int}
7
7
  type callback = args => promise<unit>
8
8
 
9
- let callbacks: array<callback> = []
9
+ // Lives in the process-wide `EnvioGlobal` record so callbacks registered
10
+ // through a duplicate envio module instance still fire.
11
+ let callbacks =
12
+ EnvioGlobal.value.rollbackCommitCallbacks->(Utils.magic: array<unknown> => array<callback>)
10
13
 
11
14
  let register = (callback: callback) => {
12
15
  callbacks->Array.push(callback)
@@ -1,8 +1,9 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
4
+ import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
4
5
 
5
- let callbacks = [];
6
+ let callbacks = EnvioGlobal.value.rollbackCommitCallbacks;
6
7
 
7
8
  function register(callback) {
8
9
  callbacks.push(callback);
@@ -32,4 +33,4 @@ export {
32
33
  register,
33
34
  fire,
34
35
  }
35
- /* No side effect */
36
+ /* EnvioGlobal Not a pure module */
@@ -0,0 +1,85 @@
1
+ // Lives on IndexerState and is fed each processed batch, so ChainState and the
2
+ // fetch loop carry no simulate-specific state.
3
+
4
+ // Match a provided item to a processed one by its (chain, block, logIndex)
5
+ // coordinate rather than object identity, so matching survives any copy or
6
+ // transform of the item between the source and the batch.
7
+ let itemKey = (item: Internal.item): string =>
8
+ switch item {
9
+ | Internal.Event({chain, blockNumber, logIndex}) =>
10
+ `${chain
11
+ ->ChainMap.Chain.toChainId
12
+ ->Int.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}`
13
+ | _ => "" // non-event items are never a provided simulate input
14
+ }
15
+
16
+ // `index` is the item's position in its chain's `simulate` array, reported back
17
+ // so a user finds it without echoing its fields.
18
+ type entry = {chainId: int, index: int, key: string}
19
+
20
+ type t = {mutable unprocessed: array<entry>}
21
+
22
+ let makeFromConfig = (config: Config.t): option<t> => {
23
+ let entries =
24
+ config.chainMap
25
+ ->ChainMap.values
26
+ ->Array.flatMap(chainConfig =>
27
+ switch chainConfig.sourceConfig {
28
+ | Config.CustomSources(sources) =>
29
+ sources->Array.flatMap(source =>
30
+ source.simulateItems
31
+ ->Option.getOr([])
32
+ ->Array.mapWithIndex(
33
+ (item, index) => {
34
+ chainId: chainConfig.id,
35
+ index,
36
+ key: itemKey(item),
37
+ },
38
+ )
39
+ )
40
+ | _ => []
41
+ }
42
+ )
43
+ switch entries {
44
+ | [] => None
45
+ | _ => Some({unprocessed: entries})
46
+ }
47
+ }
48
+
49
+ let recordProcessed = (t: t, ~batch: Batch.t) => {
50
+ let processedKeys = batch.items->Array.map(itemKey)->Utils.Set.fromArray
51
+ t.unprocessed = t.unprocessed->Array.filter(entry => !(processedKeys->Utils.Set.has(entry.key)))
52
+ }
53
+
54
+ // Unrouted item indices grouped by chain, in the order chains were first seen.
55
+ let unroutedByChain = (t: t): array<(int, array<int>)> => {
56
+ let indicesByChain = Dict.make()
57
+ let chainOrder = []
58
+ t.unprocessed->Array.forEach(entry => {
59
+ let key = entry.chainId->Int.toString
60
+ if indicesByChain->Dict.get(key)->Option.isNone {
61
+ chainOrder->Array.push(entry.chainId)->ignore
62
+ }
63
+ indicesByChain->Utils.Dict.push(key, entry.index)
64
+ })
65
+ chainOrder->Array.map(chainId => (chainId, indicesByChain->Dict.getUnsafe(chainId->Int.toString)))
66
+ }
67
+
68
+ let failureMessage = (t: t): option<string> =>
69
+ switch t->unroutedByChain {
70
+ | [] => None
71
+ | byChain =>
72
+ let count = byChain->Array.reduce(0, (acc, (_chainId, indices)) => acc + indices->Array.length)
73
+ let itemWord = count === 1 ? "item" : "items"
74
+ let lines =
75
+ byChain
76
+ ->Array.map(((chainId, indices)) =>
77
+ ` - chain ${chainId->Int.toString}: ${indices
78
+ ->Array.map(index => index->Int.toString)
79
+ ->Array.join(", ")}`
80
+ )
81
+ ->Array.join("\n")
82
+ Some(
83
+ `simulate: ${count->Int.toString} ${itemWord} you passed to simulate never reached a handler, so nothing ran for them. Each was filtered out before the handler — usually a non-wildcard srcAddress that isn't indexed for the contract, or a where/block filter that excluded the event. Unrouted items, by index in each chain's simulate array:\n${lines}`,
84
+ )
85
+ }
@@ -0,0 +1,73 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "./Utils.res.mjs";
4
+ import * as ChainMap from "./ChainMap.res.mjs";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+
8
+ function itemKey(item) {
9
+ if (item.kind === 0) {
10
+ return item.chain.toString() + `:` + item.blockNumber.toString() + `:` + item.logIndex.toString();
11
+ } else {
12
+ return "";
13
+ }
14
+ }
15
+
16
+ function makeFromConfig(config) {
17
+ let entries = ChainMap.values(config.chainMap).flatMap(chainConfig => {
18
+ let sources = chainConfig.sourceConfig;
19
+ if (sources.TAG === "CustomSources") {
20
+ return sources._0.flatMap(source => Stdlib_Option.getOr(source.simulateItems, []).map((item, index) => ({
21
+ chainId: chainConfig.id,
22
+ index: index,
23
+ key: itemKey(item)
24
+ })));
25
+ } else {
26
+ return [];
27
+ }
28
+ });
29
+ if (entries.length !== 0) {
30
+ return {
31
+ unprocessed: entries
32
+ };
33
+ }
34
+ }
35
+
36
+ function recordProcessed(t, batch) {
37
+ let processedKeys = new Set(batch.items.map(itemKey));
38
+ t.unprocessed = t.unprocessed.filter(entry => !processedKeys.has(entry.key));
39
+ }
40
+
41
+ function unroutedByChain(t) {
42
+ let indicesByChain = {};
43
+ let chainOrder = [];
44
+ t.unprocessed.forEach(entry => {
45
+ let key = entry.chainId.toString();
46
+ if (Stdlib_Option.isNone(indicesByChain[key])) {
47
+ chainOrder.push(entry.chainId);
48
+ }
49
+ Utils.Dict.push(indicesByChain, key, entry.index);
50
+ });
51
+ return chainOrder.map(chainId => [
52
+ chainId,
53
+ indicesByChain[chainId.toString()]
54
+ ]);
55
+ }
56
+
57
+ function failureMessage(t) {
58
+ let byChain = unroutedByChain(t);
59
+ if (byChain.length === 0) {
60
+ return;
61
+ }
62
+ let count = Stdlib_Array.reduce(byChain, 0, (acc, param) => acc + param[1].length | 0);
63
+ let itemWord = count === 1 ? "item" : "items";
64
+ let lines = byChain.map(param => ` - chain ` + param[0].toString() + `: ` + param[1].map(index => index.toString()).join(", ")).join("\n");
65
+ return `simulate: ` + count.toString() + ` ` + itemWord + ` you passed to simulate never reached a handler, so nothing ran for them. Each was filtered out before the handler — usually a non-wildcard srcAddress that isn't indexed for the contract, or a where/block filter that excluded the event. Unrouted items, by index in each chain's simulate array:\n` + lines;
66
+ }
67
+
68
+ export {
69
+ makeFromConfig,
70
+ recordProcessed,
71
+ failureMessage,
72
+ }
73
+ /* Utils Not a pure module */
@@ -0,0 +1,12 @@
1
+ type t
2
+
3
+ // Builds a tracker from the run's config, seeded with every item each simulate
4
+ // source provided. None when no chain has a simulate source.
5
+ let makeFromConfig: Config.t => option<t>
6
+
7
+ // Drop the batch's items from the not-yet-routed set, matched by event coordinate.
8
+ let recordProcessed: (t, ~batch: Batch.t) => unit
9
+
10
+ // The error to fail the run with when items never reached a handler, listing them
11
+ // by chain and index within that chain's `simulate` array. None when all routed.
12
+ let failureMessage: t => option<string>
@@ -195,6 +195,51 @@ let findEventConfig = (~config: Config.t, ~contractName: string, ~eventName: str
195
195
  found.contents
196
196
  }
197
197
 
198
+ let dummySrcAddress = Address.unsafeFromString("0x0000000000000000000000000000000000000000")
199
+
200
+ // First address configured for `contractName` on the simulated chain. Only the
201
+ // simulated chain is consulted — a contract's address on another chain has no
202
+ // meaning here.
203
+ let firstContractAddress = (~chainConfig: Config.chain, ~contractName: string): option<
204
+ Address.t,
205
+ > => {
206
+ let found = ref(None)
207
+ chainConfig.contracts->Array.forEach(contract => {
208
+ if found.contents->Option.isNone && contract.name === contractName {
209
+ found := contract.addresses->Array.get(0)
210
+ }
211
+ })
212
+ found.contents
213
+ }
214
+
215
+ let deriveSrcAddress = (
216
+ ~providedSrcAddress: option<Address.t>,
217
+ ~eventConfig: Internal.eventConfig,
218
+ ~chainConfig: Config.chain,
219
+ ~config: Config.t,
220
+ ): Address.t => {
221
+ switch providedSrcAddress {
222
+ // Canonicalize to the configured casing; the fallback addresses below already
223
+ // come from the parsed config and need no normalization. Relaxed (not
224
+ // Config.normalizeUserAddress) so test placeholders like "0xfoo" are allowed.
225
+ | Some(addr) => config->Config.normalizeSimulateAddress(addr)
226
+ | None =>
227
+ if (
228
+ HandlerRegister.isWildcard(
229
+ ~contractName=eventConfig.contractName,
230
+ ~eventName=eventConfig.name,
231
+ )
232
+ ) {
233
+ dummySrcAddress
234
+ } else {
235
+ switch firstContractAddress(~chainConfig, ~contractName=eventConfig.contractName) {
236
+ | Some(addr) => addr
237
+ | None => dummySrcAddress
238
+ }
239
+ }
240
+ }
241
+ }
242
+
198
243
  let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Config.chain): array<
199
244
  Internal.item,
200
245
  > => {
@@ -205,8 +250,11 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
205
250
  let currentLogIndex = ref(0)
206
251
 
207
252
  let items = []
253
+ // Coordinate "block:logIndex" -> the index of the first item that claimed it,
254
+ // used to reject two items resolving to the same (block, logIndex).
255
+ let seenCoordinates = Dict.make()
208
256
 
209
- simulateItems->Array.forEach(rawJson => {
257
+ simulateItems->Array.forEachWithIndex((rawJson, itemIndex) => {
210
258
  let raw = rawJson->(Utils.magic: JSON.t => rawSimulateItem)
211
259
 
212
260
  switch (raw->getContract, raw->getEvent) {
@@ -230,44 +278,41 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
230
278
  }
231
279
  let params = paramsJson->S.convertOrThrow(eventConfig.simulateParamsSchema)
232
280
 
281
+ // An explicit logIndex advances the auto-increment counter past itself, so a
282
+ // later item that omits logIndex picks up after it instead of colliding.
233
283
  let logIndex = switch item.logIndex {
234
- | Some(li) => li
284
+ | Some(li) =>
285
+ if li >= currentLogIndex.contents {
286
+ currentLogIndex := li + 1
287
+ }
288
+ li
235
289
  | None =>
236
290
  let li = currentLogIndex.contents
237
291
  currentLogIndex := li + 1
238
292
  li
239
293
  }
240
294
 
241
- let srcAddress = switch item.srcAddress {
242
- | Some(addr) => addr
243
- | None =>
244
- // Use first address from contract config
245
- let addr = ref(Address.unsafeFromString("0x0000000000000000000000000000000000000000"))
246
- chainConfig.contracts->Array.forEach(contract => {
247
- if contract.name === contractName {
248
- switch contract.addresses->Array.get(0) {
249
- | Some(a) => addr := a
250
- | None => ()
251
- }
252
- }
253
- })
254
- addr.contents
255
- }
295
+ let srcAddress = deriveSrcAddress(
296
+ ~providedSrcAddress=item.srcAddress,
297
+ ~eventConfig,
298
+ ~chainConfig,
299
+ ~config,
300
+ )
256
301
 
257
302
  let rawItem = rawJson->(Utils.magic: JSON.t => {..})
258
303
  let blockJson: option<JSON.t> =
259
304
  rawItem["block"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
260
305
  let transactionJson: option<JSON.t> =
261
306
  rawItem["transaction"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
262
- let (block, blockNumber, timestamp, blockHash) = switch config.ecosystem.name {
307
+ let (block, blockNumber) = switch config.ecosystem.name {
263
308
  | Fuel =>
264
309
  let block = parseFuelSimulateBlock(~defaultBlockNumber=currentBlock.contents, ~blockJson)
265
310
  let blockFields = block->(Utils.magic: Internal.eventBlock => fuelSimulateBlock)
266
- (block, blockFields.height, blockFields.time, blockFields.id)
311
+ (block, blockFields.height)
267
312
  | Evm =>
268
313
  let block = parseEvmSimulateBlock(~defaultBlockNumber=currentBlock.contents, ~blockJson)
269
314
  let blockFields = block->(Utils.magic: Internal.eventBlock => evmSimulateBlock)
270
- (block, blockFields.number, blockFields.timestamp, blockFields.hash)
315
+ (block, blockFields.number)
271
316
  | Svm => JsError.throwWithMessage("simulate is not supported for SVM ecosystem")
272
317
  }
273
318
  let transaction = switch config.ecosystem.name {
@@ -279,15 +324,38 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
279
324
  // Update currentBlock for subsequent items
280
325
  currentBlock := blockNumber
281
326
 
327
+ // A simulate item must land on a distinct (block, logIndex): event ordering
328
+ // and the dead-input tracker both key on it. Catch explicit duplicates and
329
+ // explicit-vs-auto-increment collisions here, naming both offending indices.
330
+ let coordinate = `${blockNumber->Int.toString}:${logIndex->Int.toString}`
331
+ switch seenCoordinates->Dict.get(coordinate) {
332
+ | Some(firstIndex) =>
333
+ JsError.throwWithMessage(
334
+ `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->Int.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`,
335
+ )
336
+ | None => seenCoordinates->Dict.set(coordinate, itemIndex)
337
+ }
338
+
339
+ // Build a real registration the same way `HandlerRegister.finishRegistration`
340
+ // does at startup (not a stub), so the address filter and `where`
341
+ // behave identically to real indexing — the dead-input tracker relies
342
+ // on `clientAddressFilter` actually gating unrouted items.
343
+ let onEventRegistration = HandlerRegister.buildOnEventRegistration(
344
+ ~config,
345
+ ~chainId,
346
+ ~eventConfig,
347
+ )
348
+
282
349
  items
283
350
  ->Array.push(
284
351
  Internal.Event({
285
- eventConfig,
286
- timestamp,
352
+ onEventRegistration,
287
353
  chain,
288
354
  blockNumber,
289
- blockHash,
290
355
  logIndex,
356
+ // Simulate keeps the transaction inline on the payload, so the store
357
+ // key is unused.
358
+ transactionIndex: 0,
291
359
  payload: (
292
360
  {
293
361
  contractName: eventConfig.contractName,
@@ -1,10 +1,12 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Config from "./Config.res.mjs";
3
4
  import * as Address from "./Address.res.mjs";
4
5
  import * as ChainMap from "./ChainMap.res.mjs";
5
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
7
  import * as SimulateSource from "./sources/SimulateSource.res.mjs";
7
8
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
+ import * as HandlerRegister from "./HandlerRegister.res.mjs";
8
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
9
11
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
10
12
 
@@ -135,6 +137,36 @@ function findEventConfig(config, contractName, eventName) {
135
137
  return found.contents;
136
138
  }
137
139
 
140
+ let dummySrcAddress = "0x0000000000000000000000000000000000000000";
141
+
142
+ function firstContractAddress(chainConfig, contractName) {
143
+ let found = {
144
+ contents: undefined
145
+ };
146
+ chainConfig.contracts.forEach(contract => {
147
+ if (Stdlib_Option.isNone(found.contents) && contract.name === contractName) {
148
+ found.contents = contract.addresses[0];
149
+ return;
150
+ }
151
+ });
152
+ return found.contents;
153
+ }
154
+
155
+ function deriveSrcAddress(providedSrcAddress, eventConfig, chainConfig, config) {
156
+ if (providedSrcAddress !== undefined) {
157
+ return Config.normalizeSimulateAddress(config, Primitive_option.valFromOption(providedSrcAddress));
158
+ }
159
+ if (HandlerRegister.isWildcard(eventConfig.contractName, eventConfig.name)) {
160
+ return dummySrcAddress;
161
+ }
162
+ let addr = firstContractAddress(chainConfig, eventConfig.contractName);
163
+ if (addr !== undefined) {
164
+ return Primitive_option.valFromOption(addr);
165
+ } else {
166
+ return dummySrcAddress;
167
+ }
168
+ }
169
+
138
170
  function parse(simulateItems, config, chainConfig) {
139
171
  let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
140
172
  let chainId = chainConfig.id;
@@ -146,7 +178,8 @@ function parse(simulateItems, config, chainConfig) {
146
178
  contents: 0
147
179
  };
148
180
  let items = [];
149
- simulateItems.forEach(rawJson => {
181
+ let seenCoordinates = {};
182
+ simulateItems.forEach((rawJson, itemIndex) => {
150
183
  let match = rawJson.contract;
151
184
  let match$1 = rawJson.event;
152
185
  if (match === undefined) {
@@ -163,32 +196,16 @@ function parse(simulateItems, config, chainConfig) {
163
196
  let li = rawJson.logIndex;
164
197
  let logIndex;
165
198
  if (li !== undefined) {
199
+ if (li >= currentLogIndex.contents) {
200
+ currentLogIndex.contents = li + 1 | 0;
201
+ }
166
202
  logIndex = li;
167
203
  } else {
168
204
  let li$1 = currentLogIndex.contents;
169
205
  currentLogIndex.contents = li$1 + 1 | 0;
170
206
  logIndex = li$1;
171
207
  }
172
- let addr = rawJson.srcAddress;
173
- let srcAddress;
174
- if (addr !== undefined) {
175
- srcAddress = Primitive_option.valFromOption(addr);
176
- } else {
177
- let addr$1 = {
178
- contents: "0x0000000000000000000000000000000000000000"
179
- };
180
- chainConfig.contracts.forEach(contract => {
181
- if (contract.name !== match) {
182
- return;
183
- }
184
- let a = contract.addresses[0];
185
- if (a !== undefined) {
186
- addr$1.contents = Primitive_option.valFromOption(a);
187
- return;
188
- }
189
- });
190
- srcAddress = addr$1.contents;
191
- }
208
+ let srcAddress = deriveSrcAddress(rawJson.srcAddress, eventConfig, chainConfig, config);
192
209
  let blockJson = rawJson.block;
193
210
  let blockJson$1 = (blockJson == null) ? undefined : Primitive_option.some(blockJson);
194
211
  let transactionJson = rawJson.transaction;
@@ -200,18 +217,14 @@ function parse(simulateItems, config, chainConfig) {
200
217
  let block = parseEvmSimulateBlock(currentBlock.contents, blockJson$1);
201
218
  match$3 = [
202
219
  block,
203
- block.number,
204
- block.timestamp,
205
- block.hash
220
+ block.number
206
221
  ];
207
222
  break;
208
223
  case "fuel" :
209
224
  let block$1 = parseFuelSimulateBlock(currentBlock.contents, blockJson$1);
210
225
  match$3 = [
211
226
  block$1,
212
- block$1.height,
213
- block$1.time,
214
- block$1.id
227
+ block$1.height
215
228
  ];
216
229
  break;
217
230
  case "svm" :
@@ -233,14 +246,21 @@ function parse(simulateItems, config, chainConfig) {
233
246
  break;
234
247
  }
235
248
  currentBlock.contents = blockNumber;
249
+ let coordinate = blockNumber.toString() + `:` + logIndex.toString();
250
+ let firstIndex = seenCoordinates[coordinate];
251
+ if (firstIndex !== undefined) {
252
+ Stdlib_JsError.throwWithMessage(`simulate: items at index ` + firstIndex.toString() + ` and ` + itemIndex.toString() + ` on chain ` + chainId.toString() + ` both resolve to block ` + blockNumber.toString() + `, logIndex ` + logIndex.toString() + `. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`);
253
+ } else {
254
+ seenCoordinates[coordinate] = itemIndex;
255
+ }
256
+ let onEventRegistration = HandlerRegister.buildOnEventRegistration(config, chainId, eventConfig, undefined);
236
257
  items.push({
237
258
  kind: 0,
238
- eventConfig: eventConfig,
239
- timestamp: match$3[2],
259
+ onEventRegistration: onEventRegistration,
240
260
  chain: chain,
241
261
  blockNumber: blockNumber,
242
- blockHash: match$3[3],
243
262
  logIndex: logIndex,
263
+ transactionIndex: 0,
244
264
  payload: {
245
265
  contractName: eventConfig.contractName,
246
266
  eventName: eventConfig.name,
@@ -248,8 +268,8 @@ function parse(simulateItems, config, chainConfig) {
248
268
  chainId: chainId,
249
269
  srcAddress: srcAddress,
250
270
  logIndex: logIndex,
251
- transaction: transaction,
252
- block: match$3[0]
271
+ transaction: Primitive_option.some(transaction),
272
+ block: Primitive_option.some(match$3[0])
253
273
  }
254
274
  });
255
275
  });
@@ -317,6 +337,9 @@ export {
317
337
  fuelSimulateTransactionSchema,
318
338
  parseFuelSimulateTransaction,
319
339
  findEventConfig,
340
+ dummySrcAddress,
341
+ firstContractAddress,
342
+ deriveSrcAddress,
320
343
  parse,
321
344
  patchConfig,
322
345
  }