envio 3.3.0-alpha.8 → 3.3.0-alpha.9
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.
- package/package.json +6 -6
- package/src/ChainState.res +104 -37
- package/src/ChainState.res.mjs +80 -19
- package/src/ChainState.resi +4 -1
- package/src/CrossChainState.res +56 -29
- package/src/CrossChainState.res.mjs +11 -2
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +16 -17
- package/src/EventConfigBuilder.res.mjs +7 -6
- package/src/FetchState.res +151 -130
- package/src/FetchState.res.mjs +105 -88
- package/src/HandlerLoader.res +1 -1
- package/src/HandlerLoader.res.mjs +1 -1
- package/src/HandlerRegister.res +476 -305
- package/src/HandlerRegister.res.mjs +276 -209
- package/src/HandlerRegister.resi +11 -6
- package/src/Internal.res +23 -4
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- package/src/Main.res +29 -143
- package/src/Main.res.mjs +25 -88
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateItems.res +1 -1
- package/src/sources/Evm.res +4 -1
- package/src/sources/Evm.res.mjs +1 -1
- package/src/sources/Fuel.res +3 -1
- package/src/sources/Fuel.res.mjs +1 -1
- package/src/sources/HyperSyncSource.res +22 -33
- package/src/sources/HyperSyncSource.res.mjs +17 -26
- package/src/sources/RpcSource.res +28 -39
- package/src/sources/RpcSource.res.mjs +18 -31
- package/src/sources/SourceManager.res +1 -7
- package/src/sources/SourceManager.res.mjs +1 -2
package/src/LogSelection.res
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
exception MissingRequiredTopic0
|
|
2
|
-
let makeTopicSelection = (~topic0, ~topic1=[], ~topic2=[], ~topic3=[])
|
|
2
|
+
let makeTopicSelection = (~topic0, ~topic1=[], ~topic2=[], ~topic3=[]): result<
|
|
3
|
+
Internal.topicSelection,
|
|
4
|
+
exn,
|
|
5
|
+
> =>
|
|
3
6
|
if topic0->Utils.Array.isEmpty {
|
|
4
7
|
Error(MissingRequiredTopic0)
|
|
5
8
|
} else {
|
|
@@ -37,8 +40,8 @@ let compressTopicSelections = (topicSelections: array<Internal.topicSelection>)
|
|
|
37
40
|
switch topic0sOfSelectionsWithoutFilters {
|
|
38
41
|
| [] => selectionsWithFilters
|
|
39
42
|
| topic0 =>
|
|
40
|
-
let selectionWithoutFilters = {
|
|
41
|
-
|
|
43
|
+
let selectionWithoutFilters: Internal.topicSelection = {
|
|
44
|
+
topic0,
|
|
42
45
|
topic1: [],
|
|
43
46
|
topic2: [],
|
|
44
47
|
topic3: [],
|
|
@@ -57,20 +60,33 @@ let make = (~addresses, ~topicSelections) => {
|
|
|
57
60
|
{addresses, topicSelections}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
// Expand a resolved topic selection into concrete topic values for a query:
|
|
64
|
+
// `ContractAddresses` markers become the given partition addresses encoded as
|
|
65
|
+
// topics; `Values` pass through.
|
|
66
|
+
let materializeTopicFilter = (filter: Internal.topicFilter, ~addresses: array<Address.t>) =>
|
|
67
|
+
switch filter {
|
|
68
|
+
| Values(values) => values
|
|
69
|
+
| ContractAddresses(_) => addresses->Array.map(TopicFilter.fromAddress)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let materializeTopicSelections = (
|
|
73
|
+
topicSelections: array<Internal.resolvedTopicSelection>,
|
|
74
|
+
~addresses: array<Address.t>,
|
|
75
|
+
): array<Internal.topicSelection> =>
|
|
76
|
+
topicSelections->Array.map(({topic0, topic1, topic2, topic3}): Internal.topicSelection => {
|
|
77
|
+
topic0,
|
|
78
|
+
topic1: topic1->materializeTopicFilter(~addresses),
|
|
79
|
+
topic2: topic2->materializeTopicFilter(~addresses),
|
|
80
|
+
topic3: topic3->materializeTopicFilter(~addresses),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
type parsedWhere = {
|
|
84
|
+
resolvedWhere: Internal.resolvedWhere,
|
|
62
85
|
filterByAddresses: bool,
|
|
63
86
|
// Indexed params filtered by `chain.<Contract>.addresses`, in disjunctive
|
|
64
87
|
// normal form (outer array OR of AND-groups). Empty unless `filterByAddresses`.
|
|
65
88
|
// Consumed by the codegen of the event's `clientAddressFilter`.
|
|
66
89
|
addressFilterParamGroups: array<array<string>>,
|
|
67
|
-
// `_gte` from the top-level `block` filter of the user's `where`,
|
|
68
|
-
// resolved at build time (per-chain via the `probeChainId`). The
|
|
69
|
-
// caller uses this to override the per-event `startBlock` — a
|
|
70
|
-
// `where`-derived startBlock always wins over contract-level
|
|
71
|
-
// config, so users can widen or narrow individual event ranges
|
|
72
|
-
// without touching `config.yaml`.
|
|
73
|
-
startBlock: option<int>,
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
// Inner schema for the event `block` filter chunk: `{_gte?}`.
|
|
@@ -123,24 +139,12 @@ let extractStartBlock = (
|
|
|
123
139
|
}
|
|
124
140
|
}
|
|
125
141
|
|
|
126
|
-
// Build the
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
let makeChainArg = (
|
|
132
|
-
let chainObj = Dict.make()
|
|
133
|
-
chainObj->Dict.set("id", chainId->Obj.magic)
|
|
134
|
-
chainObj->Dict.set(contractName, {"addresses": addresses}->Obj.magic)
|
|
135
|
-
chainObj
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Build the detection-time `chain` argument. `chain.<ContractName>.addresses`
|
|
139
|
-
// is a getter so the runtime can tell whether the callback actually reads
|
|
140
|
-
// it; the contract sub-object itself is built via `defineProperty` only
|
|
141
|
-
// because its `addresses` field needs the getter — the enclosing chainObj
|
|
142
|
-
// is a plain JS object.
|
|
143
|
-
let makeDetectionChainArg = (
|
|
142
|
+
// Build the `chain` argument passed into a `where` callback.
|
|
143
|
+
// `chain.<ContractName>.addresses` is a getter so the runtime can tell
|
|
144
|
+
// whether the callback actually reads it; the contract sub-object itself is
|
|
145
|
+
// built via `defineProperty` only because its `addresses` field needs the
|
|
146
|
+
// getter — the enclosing chainObj is a plain JS object.
|
|
147
|
+
let makeChainArg = (
|
|
144
148
|
~contractName: string,
|
|
145
149
|
~chainId: int,
|
|
146
150
|
~getAddresses: unit => array<Address.t>,
|
|
@@ -155,7 +159,7 @@ let makeDetectionChainArg = (
|
|
|
155
159
|
chainObj
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
// Sentinel returned by `chain.<Contract>.addresses
|
|
162
|
+
// Sentinel returned by `chain.<Contract>.addresses`. A Proxy
|
|
159
163
|
// whose traps throw on any access, so the only non-throwing use is passing it
|
|
160
164
|
// straight through as a param filter value — which `extractAddressFilterGroups`
|
|
161
165
|
// then finds by identity. Misuse (spread/map/index/...) fails loud at the site.
|
|
@@ -173,78 +177,46 @@ let makeAddressesProbe: (
|
|
|
173
177
|
return new Proxy([], {get: trap});
|
|
174
178
|
}`)
|
|
175
179
|
|
|
176
|
-
|
|
177
|
-
// (DNF: object => one AND-group, array => OR of groups), neutralizing each match
|
|
178
|
-
// to `[]` so later passes can't touch the throwing Proxy. Throws when the
|
|
179
|
-
// callback read the addresses but didn't use them as a param filter value, since
|
|
180
|
-
// the caller only invokes this once it knows the addresses were read.
|
|
181
|
-
let extractAddressFilterGroupsOrThrow = (
|
|
182
|
-
result: JSON.t,
|
|
183
|
-
~probe: array<Address.t>,
|
|
184
|
-
~contractName: string,
|
|
185
|
-
): array<array<string>> => {
|
|
186
|
-
let groups = []
|
|
187
|
-
let scanGroup = (paramsObj: dict<JSON.t>) => {
|
|
188
|
-
let names = []
|
|
189
|
-
paramsObj->Utils.Dict.forEachWithKey((value, key) => {
|
|
190
|
-
if value === probe->(Utils.magic: array<Address.t> => JSON.t) {
|
|
191
|
-
names->Array.push(key)->ignore
|
|
192
|
-
paramsObj->Dict.set(key, []->(Utils.magic: array<unknown> => JSON.t))
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
if names->Utils.Array.isEmpty->not {
|
|
196
|
-
groups->Array.push(names)->ignore
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
switch result {
|
|
200
|
-
| Object(obj) =>
|
|
201
|
-
switch obj->Dict.get("params") {
|
|
202
|
-
| Some(Object(p)) => scanGroup(p)
|
|
203
|
-
| Some(Array(arr)) =>
|
|
204
|
-
arr->Array.forEach(item =>
|
|
205
|
-
switch item {
|
|
206
|
-
| Object(p) => scanGroup(p)
|
|
207
|
-
| _ => ()
|
|
208
|
-
}
|
|
209
|
-
)
|
|
210
|
-
| _ => ()
|
|
211
|
-
}
|
|
212
|
-
| _ => ()
|
|
213
|
-
}
|
|
214
|
-
if groups->Utils.Array.isEmpty {
|
|
215
|
-
JsError.throwWithMessage(
|
|
216
|
-
`Invalid where configuration for ${contractName}. The callback reads \`chain.${contractName}.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.${contractName}.addresses } }.`,
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
groups
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let parseEventFiltersOrThrow = {
|
|
180
|
+
let parseWhereOrThrow = {
|
|
223
181
|
let emptyTopics = []
|
|
224
182
|
let noopGetter = _ => emptyTopics
|
|
225
183
|
|
|
226
184
|
(
|
|
227
|
-
~
|
|
185
|
+
~where: option<JSON.t>,
|
|
228
186
|
~sighash,
|
|
229
|
-
~params
|
|
187
|
+
~params: array<string>,
|
|
230
188
|
~contractName: string,
|
|
231
|
-
~
|
|
189
|
+
~chainId: int,
|
|
232
190
|
~onEventBlockFilterSchema: S.t<option<unknown>>,
|
|
233
191
|
~topic1=noopGetter,
|
|
234
192
|
~topic2=noopGetter,
|
|
235
193
|
~topic3=noopGetter,
|
|
236
|
-
):
|
|
237
|
-
let
|
|
238
|
-
let
|
|
239
|
-
let
|
|
194
|
+
): parsedWhere => {
|
|
195
|
+
let addressFilterParamGroups = []
|
|
196
|
+
let readAddresses = ref(false)
|
|
197
|
+
let addressesSentinel = makeAddressesProbe(~contractName)
|
|
198
|
+
let sentinelJson = addressesSentinel->(Utils.magic: array<Address.t> => JSON.t)
|
|
240
199
|
let topic0 = [sighash->EvmTypes.Hex.fromStringUnsafe]
|
|
241
200
|
let default = {
|
|
242
201
|
Internal.topic0,
|
|
243
|
-
topic1: emptyTopics,
|
|
244
|
-
topic2: emptyTopics,
|
|
245
|
-
topic3: emptyTopics,
|
|
202
|
+
topic1: Values(emptyTopics),
|
|
203
|
+
topic2: Values(emptyTopics),
|
|
204
|
+
topic3: Values(emptyTopics),
|
|
246
205
|
}
|
|
247
206
|
|
|
207
|
+
// Topic positions map 1:1 onto the event's indexed params in declared
|
|
208
|
+
// order, so the sentinel check keys off `params[index]`. The sentinel must
|
|
209
|
+
// be detected before the topic getter runs — the getter would otherwise
|
|
210
|
+
// touch the throwing Proxy while encoding.
|
|
211
|
+
let topicFilterAt = (paramsFilter: dict<JSON.t>, ~index, ~getter) =>
|
|
212
|
+
switch params
|
|
213
|
+
->Array.get(index)
|
|
214
|
+
->Option.flatMap(name => paramsFilter->Utils.Dict.dangerouslyGetNonOption(name)) {
|
|
215
|
+
| Some(value) if value === sentinelJson =>
|
|
216
|
+
Internal.ContractAddresses({contractName: contractName})
|
|
217
|
+
| _ => Values(getter(paramsFilter))
|
|
218
|
+
}
|
|
219
|
+
|
|
248
220
|
// Build a single topic selection from one indexed-param record (the
|
|
249
221
|
// inside of `params`). Validates that the keys are actual indexed
|
|
250
222
|
// parameters of the event — TS type checking doesn't catch this when
|
|
@@ -253,18 +225,25 @@ let parseEventFiltersOrThrow = {
|
|
|
253
225
|
if paramsFilter->Utils.Dict.isEmpty {
|
|
254
226
|
default
|
|
255
227
|
} else {
|
|
256
|
-
|
|
228
|
+
let sentinelParamNames = []
|
|
229
|
+
paramsFilter->Utils.Dict.forEachWithKey((value, key) => {
|
|
257
230
|
if params->Array.includes(key)->not {
|
|
258
231
|
JsError.throwWithMessage(
|
|
259
232
|
`Invalid where configuration. The event doesn't have an indexed parameter "${key}" and can't use it for filtering`,
|
|
260
233
|
)
|
|
261
234
|
}
|
|
235
|
+
if value === sentinelJson {
|
|
236
|
+
sentinelParamNames->Array.push(key)->ignore
|
|
237
|
+
}
|
|
262
238
|
})
|
|
239
|
+
if sentinelParamNames->Utils.Array.isEmpty->not {
|
|
240
|
+
addressFilterParamGroups->Array.push(sentinelParamNames)->ignore
|
|
241
|
+
}
|
|
263
242
|
{
|
|
264
243
|
Internal.topic0,
|
|
265
|
-
topic1: topic1
|
|
266
|
-
topic2: topic2
|
|
267
|
-
topic3: topic3
|
|
244
|
+
topic1: paramsFilter->topicFilterAt(~index=0, ~getter=topic1),
|
|
245
|
+
topic2: paramsFilter->topicFilterAt(~index=1, ~getter=topic2),
|
|
246
|
+
topic3: paramsFilter->topicFilterAt(~index=2, ~getter=topic3),
|
|
268
247
|
}
|
|
269
248
|
}
|
|
270
249
|
}
|
|
@@ -289,7 +268,7 @@ let parseEventFiltersOrThrow = {
|
|
|
289
268
|
//
|
|
290
269
|
// The runtime accepts both the function form (the only form ReScript
|
|
291
270
|
// exposes) and a top-level static object form (TypeScript convenience).
|
|
292
|
-
let parse = (where: JSON.t): array<Internal.
|
|
271
|
+
let parse = (where: JSON.t): array<Internal.resolvedTopicSelection> => {
|
|
293
272
|
if where === Obj.magic(true) {
|
|
294
273
|
[default]
|
|
295
274
|
} else if where === Obj.magic(false) {
|
|
@@ -335,84 +314,42 @@ let parseEventFiltersOrThrow = {
|
|
|
335
314
|
}
|
|
336
315
|
}
|
|
337
316
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
317
|
+
// The callback is invoked exactly once per chain, at registration time,
|
|
318
|
+
// with the chain's real configured id. `chain.<Contract>.addresses` yields
|
|
319
|
+
// the throwing-Proxy sentinel, which the param parser turns into a
|
|
320
|
+
// `ContractAddresses` marker; addresses are expanded from the marker only
|
|
321
|
+
// when a source query is built.
|
|
322
|
+
// A misused Proxy (or any throw from the callback) propagates as-is —
|
|
323
|
+
// the Proxy's guidance message surfaces without wrapping.
|
|
324
|
+
let (topicSelections, startBlock) = switch where {
|
|
325
|
+
| None => ([default], None)
|
|
326
|
+
| Some(where) =>
|
|
327
|
+
let whereValue = if typeof(where) === #function {
|
|
328
|
+
let fn = where->(Utils.magic: JSON.t => Internal.onEventWhereArgs<_> => JSON.t)
|
|
329
|
+
let chain = makeChainArg(~contractName, ~chainId, ~getAddresses=() => {
|
|
330
|
+
readAddresses := true
|
|
331
|
+
addressesSentinel
|
|
332
|
+
})
|
|
333
|
+
fn({chain: chain->Obj.magic})
|
|
334
|
+
} else {
|
|
335
|
+
where
|
|
342
336
|
}
|
|
343
|
-
|
|
344
|
-
if
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// a detection chain arg whose `chain.<ContractName>.addresses` getter
|
|
348
|
-
// flips a flag. The probe uses this chain's real configured id, so
|
|
349
|
-
// handlers that branch on `chain.id` are exercised along the path
|
|
350
|
-
// they take for this chain. Event configs are built per-chain, so
|
|
351
|
-
// each chain gets a `filterByAddresses` verdict that matches its
|
|
352
|
-
// own callback behaviour.
|
|
353
|
-
//
|
|
354
|
-
// The probe result is also reused to extract the per-event
|
|
355
|
-
// `startBlock` (from `where.block`) for this chain — a second
|
|
356
|
-
// invocation would risk observing different state for callbacks
|
|
357
|
-
// that close over mutable references.
|
|
358
|
-
// A misused Proxy (or any throw from the callback) propagates as-is —
|
|
359
|
-
// the Proxy's guidance message surfaces without wrapping.
|
|
360
|
-
let addressesProbe = makeAddressesProbe(~contractName)
|
|
361
|
-
let chain = makeDetectionChainArg(
|
|
362
|
-
~contractName,
|
|
363
|
-
~chainId=probeChainId,
|
|
364
|
-
~getAddresses=() => {
|
|
365
|
-
filterByAddresses := true
|
|
366
|
-
addressesProbe
|
|
367
|
-
},
|
|
337
|
+
let topicSelections = whereValue->parse
|
|
338
|
+
if readAddresses.contents && addressFilterParamGroups->Utils.Array.isEmpty {
|
|
339
|
+
JsError.throwWithMessage(
|
|
340
|
+
`Invalid where configuration for ${contractName}. The callback reads \`chain.${contractName}.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.${contractName}.addresses } }.`,
|
|
368
341
|
)
|
|
369
|
-
let probedResult = fn({chain: chain->Obj.magic})
|
|
370
|
-
if filterByAddresses.contents {
|
|
371
|
-
addressFilterParamGroups :=
|
|
372
|
-
extractAddressFilterGroupsOrThrow(probedResult, ~probe=addressesProbe, ~contractName)
|
|
373
|
-
}
|
|
374
|
-
startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, probedResult)
|
|
375
|
-
if filterByAddresses.contents {
|
|
376
|
-
chain => Internal.Dynamic(
|
|
377
|
-
addresses => {
|
|
378
|
-
let chainArg = makeChainArg(
|
|
379
|
-
~contractName,
|
|
380
|
-
~chainId=chain->ChainMap.Chain.toChainId,
|
|
381
|
-
~addresses,
|
|
382
|
-
)
|
|
383
|
-
fn({chain: chainArg->Obj.magic})->parse
|
|
384
|
-
},
|
|
385
|
-
)
|
|
386
|
-
} else {
|
|
387
|
-
// No probed chain referenced the contract — cache as Static
|
|
388
|
-
// per chain to avoid recomputing topic selections each batch.
|
|
389
|
-
// The addresses getter throws: if a code path the probe didn't
|
|
390
|
-
// exercise reads `chain.<Contract>.addresses` at runtime, silent
|
|
391
|
-
// [] would produce wrong topics — throw a user-friendly error
|
|
392
|
-
// instead so the user rewrites the callback to surface the
|
|
393
|
-
// dependency up-front.
|
|
394
|
-
chain => {
|
|
395
|
-
let chainId = chain->ChainMap.Chain.toChainId
|
|
396
|
-
let chainArg = makeDetectionChainArg(~contractName, ~chainId, ~getAddresses=() =>
|
|
397
|
-
JsError.throwWithMessage(
|
|
398
|
-
`Invalid where configuration. Event callback for contract "${contractName}" read \`chain.${contractName}.addresses\` at runtime but the probe didn't detect the access on chainId ${chainId->Int.toString}. Move the \`chain.${contractName}.addresses\` read above any \`chain.id\` branching so the probe picks up the dependency and switches to the dynamic fetch path.`,
|
|
399
|
-
)
|
|
400
|
-
)
|
|
401
|
-
Internal.Static(fn({chain: chainArg->Obj.magic})->parse)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} else {
|
|
405
|
-
startBlock := extractStartBlock(~onEventBlockFilterSchema, ~contractName, eventFilters)
|
|
406
|
-
let static: Internal.eventFilters = Static(eventFilters->parse)
|
|
407
|
-
_ => static
|
|
408
342
|
}
|
|
343
|
+
(topicSelections, extractStartBlock(~onEventBlockFilterSchema, ~contractName, whereValue))
|
|
409
344
|
}
|
|
410
345
|
|
|
411
346
|
{
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
347
|
+
resolvedWhere: {
|
|
348
|
+
topicSelections,
|
|
349
|
+
startBlock,
|
|
350
|
+
},
|
|
351
|
+
filterByAddresses: addressFilterParamGroups->Utils.Array.isEmpty->not,
|
|
352
|
+
addressFilterParamGroups,
|
|
416
353
|
}
|
|
417
354
|
}
|
|
418
355
|
}
|
package/src/LogSelection.res.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as TopicFilter from "./TopicFilter.res.mjs";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
7
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
@@ -76,6 +77,23 @@ function make(addresses, topicSelections) {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
function materializeTopicFilter(filter, addresses) {
|
|
81
|
+
if (filter.TAG === "Values") {
|
|
82
|
+
return filter._0;
|
|
83
|
+
} else {
|
|
84
|
+
return addresses.map(TopicFilter.fromAddress);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function materializeTopicSelections(topicSelections, addresses) {
|
|
89
|
+
return topicSelections.map(param => ({
|
|
90
|
+
topic0: param.topic0,
|
|
91
|
+
topic1: materializeTopicFilter(param.topic1, addresses),
|
|
92
|
+
topic2: materializeTopicFilter(param.topic2, addresses),
|
|
93
|
+
topic3: materializeTopicFilter(param.topic3, addresses)
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
let eventBlockRangeSchema = S$RescriptSchema.strict(S$RescriptSchema.object(s => ({
|
|
80
98
|
_gte: s.f("_gte", S$RescriptSchema.option(S$RescriptSchema.int))
|
|
81
99
|
})));
|
|
@@ -100,16 +118,7 @@ function extractStartBlock(where, onEventBlockFilterSchema, contractName) {
|
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
|
|
103
|
-
function makeChainArg(contractName, chainId,
|
|
104
|
-
let chainObj = {};
|
|
105
|
-
chainObj["id"] = chainId;
|
|
106
|
-
chainObj[contractName] = {
|
|
107
|
-
addresses: addresses
|
|
108
|
-
};
|
|
109
|
-
return chainObj;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function makeDetectionChainArg(contractName, chainId, getAddresses) {
|
|
121
|
+
function makeChainArg(contractName, chainId, getAddresses) {
|
|
113
122
|
let contractObj = Object.create(null);
|
|
114
123
|
Object.defineProperty(contractObj, "addresses", {
|
|
115
124
|
enumerable: true,
|
|
@@ -133,85 +142,78 @@ let makeAddressesProbe = (function (contractName) {
|
|
|
133
142
|
return new Proxy([], {get: trap});
|
|
134
143
|
});
|
|
135
144
|
|
|
136
|
-
function extractAddressFilterGroupsOrThrow(result, probe, contractName) {
|
|
137
|
-
let groups = [];
|
|
138
|
-
let scanGroup = paramsObj => {
|
|
139
|
-
let names = [];
|
|
140
|
-
Utils.Dict.forEachWithKey(paramsObj, (value, key) => {
|
|
141
|
-
if (value === probe) {
|
|
142
|
-
names.push(key);
|
|
143
|
-
paramsObj[key] = [];
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
if (!Utils.$$Array.isEmpty(names)) {
|
|
148
|
-
groups.push(names);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
if (typeof result === "object" && result !== null && !Array.isArray(result)) {
|
|
153
|
-
let match = result["params"];
|
|
154
|
-
if (match !== undefined) {
|
|
155
|
-
if (Array.isArray(match)) {
|
|
156
|
-
match.forEach(item => {
|
|
157
|
-
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
158
|
-
return scanGroup(item);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
} else {
|
|
162
|
-
switch (typeof match) {
|
|
163
|
-
case "object" :
|
|
164
|
-
scanGroup(match);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (Utils.$$Array.isEmpty(groups)) {
|
|
171
|
-
Stdlib_JsError.throwWithMessage(`Invalid where configuration for ` + contractName + `. The callback reads \`chain.` + contractName + `.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.` + contractName + `.addresses } }.`);
|
|
172
|
-
}
|
|
173
|
-
return groups;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
145
|
let emptyTopics = [];
|
|
177
146
|
|
|
178
147
|
function noopGetter(param) {
|
|
179
148
|
return emptyTopics;
|
|
180
149
|
}
|
|
181
150
|
|
|
182
|
-
function
|
|
151
|
+
function parseWhereOrThrow(where, sighash, params, contractName, chainId, onEventBlockFilterSchema, topic1Opt, topic2Opt, topic3Opt) {
|
|
183
152
|
let topic1 = topic1Opt !== undefined ? topic1Opt : noopGetter;
|
|
184
153
|
let topic2 = topic2Opt !== undefined ? topic2Opt : noopGetter;
|
|
185
154
|
let topic3 = topic3Opt !== undefined ? topic3Opt : noopGetter;
|
|
186
|
-
let
|
|
155
|
+
let addressFilterParamGroups = [];
|
|
156
|
+
let readAddresses = {
|
|
187
157
|
contents: false
|
|
188
158
|
};
|
|
189
|
-
let
|
|
190
|
-
let startBlock;
|
|
159
|
+
let addressesSentinel = makeAddressesProbe(contractName);
|
|
191
160
|
let topic0 = [sighash];
|
|
161
|
+
let default_topic1 = {
|
|
162
|
+
TAG: "Values",
|
|
163
|
+
_0: emptyTopics
|
|
164
|
+
};
|
|
165
|
+
let default_topic2 = {
|
|
166
|
+
TAG: "Values",
|
|
167
|
+
_0: emptyTopics
|
|
168
|
+
};
|
|
169
|
+
let default_topic3 = {
|
|
170
|
+
TAG: "Values",
|
|
171
|
+
_0: emptyTopics
|
|
172
|
+
};
|
|
192
173
|
let $$default = {
|
|
193
174
|
topic0: topic0,
|
|
194
|
-
topic1:
|
|
195
|
-
topic2:
|
|
196
|
-
topic3:
|
|
175
|
+
topic1: default_topic1,
|
|
176
|
+
topic2: default_topic2,
|
|
177
|
+
topic3: default_topic3
|
|
197
178
|
};
|
|
198
|
-
let
|
|
199
|
-
|
|
200
|
-
|
|
179
|
+
let topicFilterAt = (paramsFilter, index, getter) => {
|
|
180
|
+
let value = Stdlib_Option.flatMap(params[index], name => paramsFilter[name]);
|
|
181
|
+
if (value !== undefined && value === addressesSentinel) {
|
|
182
|
+
return {
|
|
183
|
+
TAG: "ContractAddresses",
|
|
184
|
+
contractName: contractName
|
|
185
|
+
};
|
|
201
186
|
} else {
|
|
202
|
-
Utils.Dict.forEachWithKey(paramsFilter, (param, key) => {
|
|
203
|
-
if (!params.includes(key)) {
|
|
204
|
-
return Stdlib_JsError.throwWithMessage(`Invalid where configuration. The event doesn't have an indexed parameter "` + key + `" and can't use it for filtering`);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
187
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
topic2: topic2(paramsFilter),
|
|
211
|
-
topic3: topic3(paramsFilter)
|
|
188
|
+
TAG: "Values",
|
|
189
|
+
_0: getter(paramsFilter)
|
|
212
190
|
};
|
|
213
191
|
}
|
|
214
192
|
};
|
|
193
|
+
let paramsRecordToTopicSelection = paramsFilter => {
|
|
194
|
+
if (Utils.Dict.isEmpty(paramsFilter)) {
|
|
195
|
+
return $$default;
|
|
196
|
+
}
|
|
197
|
+
let sentinelParamNames = [];
|
|
198
|
+
Utils.Dict.forEachWithKey(paramsFilter, (value, key) => {
|
|
199
|
+
if (!params.includes(key)) {
|
|
200
|
+
Stdlib_JsError.throwWithMessage(`Invalid where configuration. The event doesn't have an indexed parameter "` + key + `" and can't use it for filtering`);
|
|
201
|
+
}
|
|
202
|
+
if (value === addressesSentinel) {
|
|
203
|
+
sentinelParamNames.push(key);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
if (!Utils.$$Array.isEmpty(sentinelParamNames)) {
|
|
208
|
+
addressFilterParamGroups.push(sentinelParamNames);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
topic0: topic0,
|
|
212
|
+
topic1: topicFilterAt(paramsFilter, 0, topic1),
|
|
213
|
+
topic2: topicFilterAt(paramsFilter, 1, topic2),
|
|
214
|
+
topic3: topicFilterAt(paramsFilter, 2, topic3)
|
|
215
|
+
};
|
|
216
|
+
};
|
|
215
217
|
let acceptedWhereKeys = [
|
|
216
218
|
"params",
|
|
217
219
|
"block"
|
|
@@ -258,58 +260,41 @@ function parseEventFiltersOrThrow(eventFilters, sighash, params, contractName, p
|
|
|
258
260
|
return Stdlib_JsError.throwWithMessage("Invalid where configuration. Expected `params` to be an object or an array of objects");
|
|
259
261
|
}
|
|
260
262
|
};
|
|
261
|
-
let
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
let chain =
|
|
266
|
-
|
|
267
|
-
return
|
|
263
|
+
let match;
|
|
264
|
+
if (where !== undefined) {
|
|
265
|
+
let whereValue;
|
|
266
|
+
if (typeof where === "function") {
|
|
267
|
+
let chain = makeChainArg(contractName, chainId, () => {
|
|
268
|
+
readAddresses.contents = true;
|
|
269
|
+
return addressesSentinel;
|
|
268
270
|
});
|
|
269
|
-
|
|
271
|
+
whereValue = where({
|
|
270
272
|
chain: chain
|
|
271
273
|
});
|
|
272
|
-
if (filterByAddresses.contents) {
|
|
273
|
-
addressFilterParamGroups = extractAddressFilterGroupsOrThrow(probedResult, addressesProbe, contractName);
|
|
274
|
-
}
|
|
275
|
-
startBlock = extractStartBlock(probedResult, onEventBlockFilterSchema, contractName);
|
|
276
|
-
getEventFiltersOrThrow = filterByAddresses.contents ? chain => ({
|
|
277
|
-
TAG: "Dynamic",
|
|
278
|
-
_0: addresses => {
|
|
279
|
-
let chainArg = makeChainArg(contractName, chain, addresses);
|
|
280
|
-
return parse(eventFilters({
|
|
281
|
-
chain: chainArg
|
|
282
|
-
}));
|
|
283
|
-
}
|
|
284
|
-
}) : chain => {
|
|
285
|
-
let chainArg = makeDetectionChainArg(contractName, chain, () => Stdlib_JsError.throwWithMessage(`Invalid where configuration. Event callback for contract "` + contractName + `" read \`chain.` + contractName + `.addresses\` at runtime but the probe didn't detect the access on chainId ` + chain.toString() + `. Move the \`chain.` + contractName + `.addresses\` read above any \`chain.id\` branching so the probe picks up the dependency and switches to the dynamic fetch path.`));
|
|
286
|
-
return {
|
|
287
|
-
TAG: "Static",
|
|
288
|
-
_0: parse(eventFilters({
|
|
289
|
-
chain: chainArg
|
|
290
|
-
}))
|
|
291
|
-
};
|
|
292
|
-
};
|
|
293
274
|
} else {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
};
|
|
299
|
-
getEventFiltersOrThrow = param => $$static;
|
|
275
|
+
whereValue = where;
|
|
276
|
+
}
|
|
277
|
+
let topicSelections = parse(whereValue);
|
|
278
|
+
if (readAddresses.contents && Utils.$$Array.isEmpty(addressFilterParamGroups)) {
|
|
279
|
+
Stdlib_JsError.throwWithMessage(`Invalid where configuration for ` + contractName + `. The callback reads \`chain.` + contractName + `.addresses\` but doesn't use it as an indexed-param filter value. Use it directly, e.g. { params: { to: chain.` + contractName + `.addresses } }.`);
|
|
300
280
|
}
|
|
281
|
+
match = [
|
|
282
|
+
topicSelections,
|
|
283
|
+
extractStartBlock(whereValue, onEventBlockFilterSchema, contractName)
|
|
284
|
+
];
|
|
301
285
|
} else {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
getEventFiltersOrThrow = param => $$static$1;
|
|
286
|
+
match = [
|
|
287
|
+
[$$default],
|
|
288
|
+
undefined
|
|
289
|
+
];
|
|
307
290
|
}
|
|
308
291
|
return {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
292
|
+
resolvedWhere: {
|
|
293
|
+
topicSelections: match[0],
|
|
294
|
+
startBlock: match[1]
|
|
295
|
+
},
|
|
296
|
+
filterByAddresses: !Utils.$$Array.isEmpty(addressFilterParamGroups),
|
|
297
|
+
addressFilterParamGroups: addressFilterParamGroups
|
|
313
298
|
};
|
|
314
299
|
}
|
|
315
300
|
|
|
@@ -319,12 +304,12 @@ export {
|
|
|
319
304
|
hasFilters,
|
|
320
305
|
compressTopicSelections,
|
|
321
306
|
make,
|
|
307
|
+
materializeTopicFilter,
|
|
308
|
+
materializeTopicSelections,
|
|
322
309
|
eventBlockRangeSchema,
|
|
323
310
|
extractStartBlock,
|
|
324
311
|
makeChainArg,
|
|
325
|
-
makeDetectionChainArg,
|
|
326
312
|
makeAddressesProbe,
|
|
327
|
-
|
|
328
|
-
parseEventFiltersOrThrow,
|
|
313
|
+
parseWhereOrThrow,
|
|
329
314
|
}
|
|
330
315
|
/* eventBlockRangeSchema Not a pure module */
|