envio 2.17.1 → 2.19.0
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 +8 -7
- package/src/Envio.gen.ts +21 -0
- package/src/Envio.res +38 -0
- package/src/Internal.gen.ts +1 -0
- package/src/Internal.res +18 -5
- package/src/LoadManager.res +5 -0
- package/src/LogSelection.res +5 -5
- package/src/Prometheus.res +316 -0
- package/src/Types.ts +25 -0
- package/src/Utils.res +70 -0
- package/src/bindings/BigDecimal.gen.ts +14 -0
- package/src/bindings/BigDecimal.res +61 -0
- package/src/bindings/PromClient.res +58 -0
- package/src/db/Table.res +4 -1
- package/src/ContractAddressingMap.res +0 -115
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "v2.
|
|
3
|
+
"version": "v2.19.0",
|
|
4
4
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
5
5
|
"bin": "./bin.js",
|
|
6
|
-
"
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"types": "./index.d.ts",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/enviodev/hyperindex.git"
|
|
@@ -24,15 +25,15 @@
|
|
|
24
25
|
},
|
|
25
26
|
"homepage": "https://envio.dev",
|
|
26
27
|
"optionalDependencies": {
|
|
27
|
-
"envio-linux-x64": "v2.
|
|
28
|
-
"envio-linux-arm64": "v2.
|
|
29
|
-
"envio-darwin-x64": "v2.
|
|
30
|
-
"envio-darwin-arm64": "v2.
|
|
28
|
+
"envio-linux-x64": "v2.19.0",
|
|
29
|
+
"envio-linux-arm64": "v2.19.0",
|
|
30
|
+
"envio-darwin-x64": "v2.19.0",
|
|
31
|
+
"envio-darwin-arm64": "v2.19.0"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
34
|
"@envio-dev/hypersync-client": "0.6.3",
|
|
34
35
|
"rescript": "11.1.3",
|
|
35
|
-
"rescript-schema": "9.
|
|
36
|
+
"rescript-schema": "9.3.0",
|
|
36
37
|
"viem": "2.21.0"
|
|
37
38
|
},
|
|
38
39
|
"files": [
|
package/src/Envio.gen.ts
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
/* eslint-disable */
|
|
4
4
|
/* tslint:disable */
|
|
5
5
|
|
|
6
|
+
import type {EffectContext as $$effectContext} from './Types.ts';
|
|
7
|
+
|
|
8
|
+
import type {Effect as $$effect} from './Types.ts';
|
|
9
|
+
|
|
6
10
|
import type {Logger as $$logger} from './Types.ts';
|
|
7
11
|
|
|
12
|
+
import type {S_t as RescriptSchema_S_t} from './RescriptSchema.gen';
|
|
13
|
+
|
|
8
14
|
export type logger = $$logger;
|
|
15
|
+
|
|
16
|
+
export type effect<input,output> = $$effect<input,output>;
|
|
17
|
+
|
|
18
|
+
export type effectOptions<input,output> = {
|
|
19
|
+
/** The name of the effect. Used for logging and debugging. */
|
|
20
|
+
readonly name: string;
|
|
21
|
+
/** The input schema of the effect. */
|
|
22
|
+
readonly input: RescriptSchema_S_t<input>;
|
|
23
|
+
/** The output schema of the effect. */
|
|
24
|
+
readonly output: RescriptSchema_S_t<output>
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type effectContext = $$effectContext;
|
|
28
|
+
|
|
29
|
+
export type effectArgs<input> = { readonly input: input; readonly context: effectContext };
|
package/src/Envio.res
CHANGED
|
@@ -10,3 +10,41 @@ type logger = {
|
|
|
10
10
|
error: 'params. (string, ~params: {..} as 'params=?) => unit,
|
|
11
11
|
errorWithExn: (string, exn) => unit,
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
@@warning("-30") // Duplicated type names (input)
|
|
15
|
+
@genType.import(("./Types.ts", "Effect"))
|
|
16
|
+
type rec effect<'input, 'output>
|
|
17
|
+
@genType
|
|
18
|
+
and effectOptions<'input, 'output> = {
|
|
19
|
+
/** The name of the effect. Used for logging and debugging. */
|
|
20
|
+
name: string,
|
|
21
|
+
/** The input schema of the effect. */
|
|
22
|
+
input: S.t<'input>,
|
|
23
|
+
/** The output schema of the effect. */
|
|
24
|
+
output: S.t<'output>,
|
|
25
|
+
}
|
|
26
|
+
@genType.import(("./Types.ts", "EffectContext"))
|
|
27
|
+
and effectContext = {
|
|
28
|
+
log: logger,
|
|
29
|
+
effect: 'input 'output. (effect<'input, 'output>, 'input) => promise<'output>,
|
|
30
|
+
}
|
|
31
|
+
@genType
|
|
32
|
+
and effectArgs<'input> = {
|
|
33
|
+
input: 'input,
|
|
34
|
+
context: effectContext,
|
|
35
|
+
}
|
|
36
|
+
@@warning("+30")
|
|
37
|
+
|
|
38
|
+
let experimental_createEffect = (
|
|
39
|
+
options: effectOptions<'input, 'output>,
|
|
40
|
+
handler: effectArgs<'input> => promise<'output>,
|
|
41
|
+
) => {
|
|
42
|
+
{
|
|
43
|
+
name: options.name,
|
|
44
|
+
handler: handler->(
|
|
45
|
+
Utils.magic: (effectArgs<'input> => promise<'output>) => Internal.effectArgs => promise<
|
|
46
|
+
Internal.effectOutput,
|
|
47
|
+
>
|
|
48
|
+
),
|
|
49
|
+
}->(Utils.magic: Internal.effect => effect<'input, 'output>)
|
|
50
|
+
}
|
package/src/Internal.gen.ts
CHANGED
|
@@ -37,6 +37,7 @@ export type genericHandlerWithLoader<loader,handler,eventFilters> = {
|
|
|
37
37
|
readonly handler: handler;
|
|
38
38
|
readonly wildcard?: boolean;
|
|
39
39
|
readonly eventFilters?: eventFilters;
|
|
40
|
+
/** @deprecated The option is removed starting from v2.19 since we made the default mode even faster than pre-registration. */
|
|
40
41
|
readonly preRegisterDynamicContracts?: boolean
|
|
41
42
|
};
|
|
42
43
|
|
package/src/Internal.res
CHANGED
|
@@ -61,6 +61,9 @@ type genericHandlerWithLoader<'loader, 'handler, 'eventFilters> = {
|
|
|
61
61
|
handler: 'handler,
|
|
62
62
|
wildcard?: bool,
|
|
63
63
|
eventFilters?: 'eventFilters,
|
|
64
|
+
/**
|
|
65
|
+
@deprecated The option is removed starting from v2.19 since we made the default mode even faster than pre-registration.
|
|
66
|
+
*/
|
|
64
67
|
preRegisterDynamicContracts?: bool,
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -73,10 +76,11 @@ type eventConfig = private {
|
|
|
73
76
|
name: string,
|
|
74
77
|
contractName: string,
|
|
75
78
|
isWildcard: bool,
|
|
79
|
+
// Whether the event has an event filter which uses addresses
|
|
80
|
+
filterByAddresses: bool,
|
|
76
81
|
// Usually always false for wildcard events
|
|
77
82
|
// But might be true for wildcard event with dynamic event filter by addresses
|
|
78
83
|
dependsOnAddresses: bool,
|
|
79
|
-
preRegisterDynamicContracts: bool,
|
|
80
84
|
loader: option<loader>,
|
|
81
85
|
handler: option<handler>,
|
|
82
86
|
contractRegister: option<contractRegister>,
|
|
@@ -130,10 +134,6 @@ type eventItem = {
|
|
|
130
134
|
blockNumber: int,
|
|
131
135
|
logIndex: int,
|
|
132
136
|
event: event,
|
|
133
|
-
//Default to false, if an event needs to
|
|
134
|
-
//be reprocessed after it has loaded dynamic contracts
|
|
135
|
-
//This gets set to true and does not try and reload events
|
|
136
|
-
hasRegisteredDynamicContracts?: bool,
|
|
137
137
|
// Reuse logger object for event
|
|
138
138
|
mutable loggerCache?: Pino.t,
|
|
139
139
|
}
|
|
@@ -161,6 +161,19 @@ let fuelTransferParamsSchema = S.schema(s => {
|
|
|
161
161
|
|
|
162
162
|
type entity = private {id: string}
|
|
163
163
|
|
|
164
|
+
type effectInput
|
|
165
|
+
type effectOutput
|
|
166
|
+
type effectContext
|
|
167
|
+
type effectArgs = {
|
|
168
|
+
input: effectInput,
|
|
169
|
+
context: effectContext,
|
|
170
|
+
cacheKey: string,
|
|
171
|
+
}
|
|
172
|
+
type effect = {
|
|
173
|
+
name: string,
|
|
174
|
+
handler: effectArgs => promise<effectOutput>,
|
|
175
|
+
}
|
|
176
|
+
|
|
164
177
|
@genType.import(("./Types.ts", "Invalid"))
|
|
165
178
|
type noEventFilters
|
|
166
179
|
|
package/src/LoadManager.res
CHANGED
package/src/LogSelection.res
CHANGED
|
@@ -59,7 +59,7 @@ let make = (~addresses, ~topicSelections) => {
|
|
|
59
59
|
|
|
60
60
|
type parsedEventFilters = {
|
|
61
61
|
getEventFiltersOrThrow: ChainMap.Chain.t => Internal.eventFilters,
|
|
62
|
-
|
|
62
|
+
filterByAddresses: bool,
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
let parseEventFiltersOrThrow = {
|
|
@@ -74,7 +74,7 @@ let parseEventFiltersOrThrow = {
|
|
|
74
74
|
~topic2=noopGetter,
|
|
75
75
|
~topic3=noopGetter,
|
|
76
76
|
): parsedEventFilters => {
|
|
77
|
-
let
|
|
77
|
+
let filterByAddresses = ref(false)
|
|
78
78
|
let topic0 = [sighash->EvmTypes.Hex.fromStringUnsafe]
|
|
79
79
|
let default = {
|
|
80
80
|
Internal.topic0,
|
|
@@ -145,7 +145,7 @@ let parseEventFiltersOrThrow = {
|
|
|
145
145
|
"addresses",
|
|
146
146
|
{
|
|
147
147
|
get: () => {
|
|
148
|
-
|
|
148
|
+
filterByAddresses := true
|
|
149
149
|
[]
|
|
150
150
|
},
|
|
151
151
|
},
|
|
@@ -154,7 +154,7 @@ let parseEventFiltersOrThrow = {
|
|
|
154
154
|
} catch {
|
|
155
155
|
| _ => ()
|
|
156
156
|
}
|
|
157
|
-
if
|
|
157
|
+
if filterByAddresses.contents {
|
|
158
158
|
chain => Internal.Dynamic(
|
|
159
159
|
addresses => fn({chainId: chain->ChainMap.Chain.toChainId, addresses})->parse,
|
|
160
160
|
)
|
|
@@ -173,7 +173,7 @@ let parseEventFiltersOrThrow = {
|
|
|
173
173
|
|
|
174
174
|
{
|
|
175
175
|
getEventFiltersOrThrow,
|
|
176
|
-
|
|
176
|
+
filterByAddresses: filterByAddresses.contents,
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
let loadEntitiesDurationCounter = PromClient.Counter.makeCounter({
|
|
2
|
+
"name": "load_entities_processing_time_spent",
|
|
3
|
+
"help": "Duration spend on loading entities",
|
|
4
|
+
"labelNames": [],
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
let eventRouterDurationCounter = PromClient.Counter.makeCounter({
|
|
8
|
+
"name": "event_router_processing_time_spent",
|
|
9
|
+
"help": "Duration spend on event routing",
|
|
10
|
+
"labelNames": [],
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
let executeBatchDurationCounter = PromClient.Counter.makeCounter({
|
|
14
|
+
"name": "execute_batch_processing_time_spent",
|
|
15
|
+
"help": "Duration spend on executing batch",
|
|
16
|
+
"labelNames": [],
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
let eventsProcessedCounter = PromClient.Gauge.makeGauge({
|
|
20
|
+
"name": "events_processed",
|
|
21
|
+
"help": "Total number of events processed",
|
|
22
|
+
"labelNames": ["chainId"],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
let reorgsDetectedCounter = PromClient.Counter.makeCounter({
|
|
26
|
+
"name": "reorgs_detected",
|
|
27
|
+
"help": "Total number of reorgs detected",
|
|
28
|
+
"labelNames": ["chainId"],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
let allChainsSyncedToHead = PromClient.Gauge.makeGauge({
|
|
32
|
+
"name": "hyperindex_synced_to_head",
|
|
33
|
+
"help": "All chains fully synced",
|
|
34
|
+
"labelNames": [],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
let sourceChainHeight = PromClient.Gauge.makeGauge({
|
|
38
|
+
"name": "chain_block_height",
|
|
39
|
+
"help": "Chain Height of Source Chain",
|
|
40
|
+
"labelNames": ["chainId"],
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
module Labels = {
|
|
44
|
+
let rec schemaIsString = (schema: S.t<'a>) =>
|
|
45
|
+
switch schema->S.classify {
|
|
46
|
+
| String => true
|
|
47
|
+
| Null(s)
|
|
48
|
+
| Option(s) =>
|
|
49
|
+
s->schemaIsString
|
|
50
|
+
| _ => false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let getLabelNames = (schema: S.t<'a>) =>
|
|
54
|
+
switch schema->S.classify {
|
|
55
|
+
| Object({items}) =>
|
|
56
|
+
let nonStringFields = items->Belt.Array.reduce([], (nonStringFields, item) => {
|
|
57
|
+
if item.schema->schemaIsString {
|
|
58
|
+
nonStringFields
|
|
59
|
+
} else {
|
|
60
|
+
nonStringFields->Belt.Array.concat([item.location])
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
switch nonStringFields {
|
|
65
|
+
| [] => items->Belt.Array.map(item => item.location)->Ok
|
|
66
|
+
| nonStringItems =>
|
|
67
|
+
let nonStringItems = nonStringItems->Js.Array2.joinWith(", ")
|
|
68
|
+
Error(
|
|
69
|
+
`Label schema must be an object with string (or optional string) values. Non string values: ${nonStringItems}`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
| _ => Error("Label schema must be an object")
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let metricNames: Utils.Set.t<string> = Utils.Set.make()
|
|
77
|
+
|
|
78
|
+
module MakeSafePromMetric = (
|
|
79
|
+
M: {
|
|
80
|
+
type t
|
|
81
|
+
let make: {"name": string, "help": string, "labelNames": array<string>} => t
|
|
82
|
+
let labels: (t, 'a) => t
|
|
83
|
+
let handleFloat: (t, float) => unit
|
|
84
|
+
let handleInt: (t, int) => unit
|
|
85
|
+
},
|
|
86
|
+
): {
|
|
87
|
+
type t<'a>
|
|
88
|
+
let makeOrThrow: (~name: string, ~help: string, ~labelSchema: S.t<'a>) => t<'a>
|
|
89
|
+
let handleInt: (t<'a>, ~labels: 'a, ~value: int) => unit
|
|
90
|
+
let handleFloat: (t<'a>, ~labels: 'a, ~value: float) => unit
|
|
91
|
+
} => {
|
|
92
|
+
type t<'a> = {metric: M.t, labelSchema: S.t<'a>}
|
|
93
|
+
|
|
94
|
+
let makeOrThrow = (~name, ~help, ~labelSchema: S.t<'a>): t<'a> =>
|
|
95
|
+
switch labelSchema->Labels.getLabelNames {
|
|
96
|
+
| Ok(labelNames) =>
|
|
97
|
+
if metricNames->Utils.Set.has(name) {
|
|
98
|
+
Js.Exn.raiseError("Duplicate prometheus metric name: " ++ name)
|
|
99
|
+
} else {
|
|
100
|
+
metricNames->Utils.Set.add(name)->ignore
|
|
101
|
+
let metric = M.make({
|
|
102
|
+
"name": name,
|
|
103
|
+
"help": help,
|
|
104
|
+
"labelNames": labelNames,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
{metric, labelSchema}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
| Error(error) => Js.Exn.raiseError(error)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let handleFloat = ({metric, labelSchema}: t<'a>, ~labels: 'a, ~value) =>
|
|
114
|
+
metric
|
|
115
|
+
->M.labels(labels->S.reverseConvertToJsonOrThrow(labelSchema))
|
|
116
|
+
->M.handleFloat(value)
|
|
117
|
+
|
|
118
|
+
let handleInt = ({metric, labelSchema}: t<'a>, ~labels: 'a, ~value) =>
|
|
119
|
+
metric
|
|
120
|
+
->M.labels(labels->S.reverseConvertToJsonOrThrow(labelSchema))
|
|
121
|
+
->M.handleInt(value)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module SafeCounter = MakeSafePromMetric({
|
|
125
|
+
type t = PromClient.Counter.counter
|
|
126
|
+
let make = PromClient.Counter.makeCounter
|
|
127
|
+
let labels = PromClient.Counter.labels
|
|
128
|
+
let handleInt = PromClient.Counter.incMany
|
|
129
|
+
let handleFloat = PromClient.Counter.incMany->Utils.magic
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
module SafeGauge = MakeSafePromMetric({
|
|
133
|
+
type t = PromClient.Gauge.gauge
|
|
134
|
+
let make = PromClient.Gauge.makeGauge
|
|
135
|
+
let labels = PromClient.Gauge.labels
|
|
136
|
+
let handleInt = PromClient.Gauge.set
|
|
137
|
+
let handleFloat = PromClient.Gauge.setFloat
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
module BenchmarkSummaryData = {
|
|
141
|
+
type labels = {
|
|
142
|
+
group: string,
|
|
143
|
+
stat: string,
|
|
144
|
+
label: string,
|
|
145
|
+
}
|
|
146
|
+
let labelSchema = S.schema(s => {
|
|
147
|
+
group: s.matches(S.string),
|
|
148
|
+
stat: s.matches(S.string),
|
|
149
|
+
label: s.matches(S.string),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
let gauge = SafeGauge.makeOrThrow(
|
|
153
|
+
~name="benchmark_summary_data",
|
|
154
|
+
~help="All data points collected during indexer benchmark",
|
|
155
|
+
~labelSchema,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
let set = (
|
|
159
|
+
~group: string,
|
|
160
|
+
~label: string,
|
|
161
|
+
~n: float,
|
|
162
|
+
~mean: float,
|
|
163
|
+
~stdDev: option<float>,
|
|
164
|
+
~min: float,
|
|
165
|
+
~max: float,
|
|
166
|
+
~sum: float,
|
|
167
|
+
) => {
|
|
168
|
+
let mk = stat => {
|
|
169
|
+
group,
|
|
170
|
+
stat,
|
|
171
|
+
label,
|
|
172
|
+
}
|
|
173
|
+
gauge->SafeGauge.handleFloat(~labels=mk("n"), ~value=n)
|
|
174
|
+
gauge->SafeGauge.handleFloat(~labels=mk("mean"), ~value=mean)
|
|
175
|
+
gauge->SafeGauge.handleFloat(~labels=mk("min"), ~value=min)
|
|
176
|
+
gauge->SafeGauge.handleFloat(~labels=mk("max"), ~value=max)
|
|
177
|
+
gauge->SafeGauge.handleFloat(~labels=mk("sum"), ~value=sum)
|
|
178
|
+
switch stdDev {
|
|
179
|
+
| Some(stdDev) => gauge->SafeGauge.handleFloat(~labels=mk("stdDev"), ~value=stdDev)
|
|
180
|
+
| None => ()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let processedUntilHeight = PromClient.Gauge.makeGauge({
|
|
186
|
+
"name": "chain_block_height_processed",
|
|
187
|
+
"help": "Block height processed by indexer",
|
|
188
|
+
"labelNames": ["chainId"],
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
let fetchedUntilHeight = PromClient.Gauge.makeGauge({
|
|
192
|
+
"name": "chain_block_height_fully_fetched",
|
|
193
|
+
"help": "Block height fully fetched by indexer",
|
|
194
|
+
"labelNames": ["chainId"],
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
let incrementLoadEntityDurationCounter = (~duration) => {
|
|
198
|
+
loadEntitiesDurationCounter->PromClient.Counter.incMany(duration)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let incrementEventRouterDurationCounter = (~duration) => {
|
|
202
|
+
eventRouterDurationCounter->PromClient.Counter.incMany(duration)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let incrementExecuteBatchDurationCounter = (~duration) => {
|
|
206
|
+
executeBatchDurationCounter->PromClient.Counter.incMany(duration)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let setEventsProcessedGuage = (~number, ~chainId) => {
|
|
210
|
+
eventsProcessedCounter
|
|
211
|
+
->PromClient.Gauge.labels({"chainId": chainId})
|
|
212
|
+
->PromClient.Gauge.set(number)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let incrementReorgsDetected = (~chain) => {
|
|
216
|
+
reorgsDetectedCounter->PromClient.Counter.incLabels({"chainId": chain->ChainMap.Chain.toString})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let setSourceChainHeight = (~blockNumber, ~chain) => {
|
|
220
|
+
sourceChainHeight
|
|
221
|
+
->PromClient.Gauge.labels({"chainId": chain->ChainMap.Chain.toString})
|
|
222
|
+
->PromClient.Gauge.set(blockNumber)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let setAllChainsSyncedToHead = () => {
|
|
226
|
+
allChainsSyncedToHead->PromClient.Gauge.set(1)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let setProcessedUntilHeight = (~blockNumber, ~chain) => {
|
|
230
|
+
processedUntilHeight
|
|
231
|
+
->PromClient.Gauge.labels({"chainId": chain->ChainMap.Chain.toString})
|
|
232
|
+
->PromClient.Gauge.set(blockNumber)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let setFetchedUntilHeight = (~blockNumber, ~chain) => {
|
|
236
|
+
fetchedUntilHeight
|
|
237
|
+
->PromClient.Gauge.labels({"chainId": chain->ChainMap.Chain.toString})
|
|
238
|
+
->PromClient.Gauge.set(blockNumber)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module BenchmarkCounters = {
|
|
242
|
+
type labels = {label: string}
|
|
243
|
+
let labelSchema = S.schema(s => {
|
|
244
|
+
label: s.matches(S.string),
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
let gauge = SafeGauge.makeOrThrow(
|
|
248
|
+
~name="benchmark_counters",
|
|
249
|
+
~help="All counters collected during indexer benchmark",
|
|
250
|
+
~labelSchema,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
let set = (~label, ~millis, ~totalRuntimeMillis) => {
|
|
254
|
+
gauge->SafeGauge.handleFloat(~labels={label: label}, ~value=millis)
|
|
255
|
+
gauge->SafeGauge.handleFloat(~labels={label: "Total Run Time (ms)"}, ~value=totalRuntimeMillis)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module PartitionBlockFetched = {
|
|
260
|
+
type labels = {chainId: int, partitionId: string}
|
|
261
|
+
|
|
262
|
+
let labelSchema = S.schema(s => {
|
|
263
|
+
chainId: s.matches(S.string->S.coerce(S.int)),
|
|
264
|
+
partitionId: s.matches(S.string),
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
let counter = SafeGauge.makeOrThrow(
|
|
268
|
+
~name="partition_block_fetched",
|
|
269
|
+
~help="The latest fetched block number for each partition",
|
|
270
|
+
~labelSchema,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
let set = (~blockNumber, ~partitionId, ~chainId) => {
|
|
274
|
+
counter->SafeGauge.handleInt(~labels={chainId, partitionId}, ~value=blockNumber)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let chainIdLabelsSchema = S.object(s => {
|
|
279
|
+
s.field("chainId", S.string->S.coerce(S.int))
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
module IndexingAddresses = {
|
|
283
|
+
let gauge = SafeGauge.makeOrThrow(
|
|
284
|
+
~name="envio_indexing_addresses",
|
|
285
|
+
~help="The number of addresses indexed on chain. Includes both static and dynamic addresses.",
|
|
286
|
+
~labelSchema=chainIdLabelsSchema,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
let set = (~addressesCount, ~chainId) => {
|
|
290
|
+
gauge->SafeGauge.handleInt(~labels=chainId, ~value=addressesCount)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module IndexingEndBlock = {
|
|
295
|
+
let gauge = SafeGauge.makeOrThrow(
|
|
296
|
+
~name="envio_indexing_end_block",
|
|
297
|
+
~help="The block number to stop indexing at. (inclusive)",
|
|
298
|
+
~labelSchema=chainIdLabelsSchema,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
let set = (~endBlock, ~chainId) => {
|
|
302
|
+
gauge->SafeGauge.handleInt(~labels=chainId, ~value=endBlock)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module ProgressBlockNumber = {
|
|
307
|
+
let gauge = SafeGauge.makeOrThrow(
|
|
308
|
+
~name="envio_progress_block_number",
|
|
309
|
+
~help="The block number to track the progress of indexing at. Currently uses the fully fetched block number. In the future will be changed to block number processed and stored in the database.",
|
|
310
|
+
~labelSchema=chainIdLabelsSchema,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
let set = (~endBlock, ~chainId) => {
|
|
314
|
+
gauge->SafeGauge.handleInt(~labels=chainId, ~value=endBlock)
|
|
315
|
+
}
|
|
316
|
+
}
|
package/src/Types.ts
CHANGED
|
@@ -20,3 +20,28 @@ export type Logger = {
|
|
|
20
20
|
params?: Record<string, unknown> | Error
|
|
21
21
|
) => void;
|
|
22
22
|
};
|
|
23
|
+
|
|
24
|
+
export abstract class Effect<I, O> {
|
|
25
|
+
protected opaque!: I | O;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type EffectCaller = <I, O>(
|
|
29
|
+
effect: Effect<I, O>,
|
|
30
|
+
// This is a hack to make the call complain on undefined
|
|
31
|
+
// when it's not needed, instead of extending the input type.
|
|
32
|
+
// Might be not needed if I misunderstand something in TS.
|
|
33
|
+
input: I extends undefined ? undefined : I
|
|
34
|
+
) => Promise<O>;
|
|
35
|
+
|
|
36
|
+
export type EffectContext = {
|
|
37
|
+
/**
|
|
38
|
+
* Access the logger instance with event as a context. The logs will be displayed in the console and Envio Hosted Service.
|
|
39
|
+
*/
|
|
40
|
+
readonly log: Logger;
|
|
41
|
+
/**
|
|
42
|
+
* Call the provided Effect with the given input.
|
|
43
|
+
* Effects are the best for external calls with automatic deduplication, error handling and caching.
|
|
44
|
+
* Define a new Effect using createEffect outside of the handler.
|
|
45
|
+
*/
|
|
46
|
+
readonly effect: EffectCaller;
|
|
47
|
+
};
|
package/src/Utils.res
CHANGED
|
@@ -70,6 +70,8 @@ module Dict = {
|
|
|
70
70
|
*/
|
|
71
71
|
external dangerouslyGetNonOption: (dict<'a>, string) => option<'a> = ""
|
|
72
72
|
|
|
73
|
+
let has: (dict<'a>, string) => bool = %raw(`(dict, key) => key in dict`)
|
|
74
|
+
|
|
73
75
|
let push = (dict, key, value) => {
|
|
74
76
|
switch dict->dangerouslyGetNonOption(key) {
|
|
75
77
|
| Some(arr) => arr->Js.Array2.push(value)->ignore
|
|
@@ -86,6 +88,9 @@ module Dict = {
|
|
|
86
88
|
|
|
87
89
|
let merge: (dict<'a>, dict<'a>) => dict<'a> = %raw(`(dictA, dictB) => ({...dictA, ...dictB})`)
|
|
88
90
|
|
|
91
|
+
@val
|
|
92
|
+
external mergeInPlace: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign"
|
|
93
|
+
|
|
89
94
|
let map = (dict, fn) => {
|
|
90
95
|
let newDict = Js.Dict.empty()
|
|
91
96
|
let keys = dict->Js.Dict.keys
|
|
@@ -103,6 +108,14 @@ module Dict = {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
let forEachWithKey = (dict, fn) => {
|
|
112
|
+
let keys = dict->Js.Dict.keys
|
|
113
|
+
for idx in 0 to keys->Js.Array2.length - 1 {
|
|
114
|
+
let key = keys->Js.Array2.unsafe_get(idx)
|
|
115
|
+
fn(key, dict->Js.Dict.unsafeGet(key))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
let deleteInPlace: (dict<'a>, string) => unit = %raw(`(dict, key) => {
|
|
107
120
|
delete dict[key];
|
|
108
121
|
}
|
|
@@ -115,6 +128,8 @@ module Dict = {
|
|
|
115
128
|
) => dict<'a> = %raw(`(dict, key, value) => ({...dict, [key]: value})`)
|
|
116
129
|
|
|
117
130
|
let shallowCopy: dict<'a> => dict<'a> = %raw(`(dict) => ({...dict})`)
|
|
131
|
+
|
|
132
|
+
let size = dict => dict->Js.Dict.keys->Js.Array2.length
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
module Math = {
|
|
@@ -205,6 +220,12 @@ Helper to check if a value exists in an array
|
|
|
205
220
|
| _ => false
|
|
206
221
|
}
|
|
207
222
|
|
|
223
|
+
let notEmpty = (arr: array<_>) =>
|
|
224
|
+
switch arr {
|
|
225
|
+
| [] => false
|
|
226
|
+
| _ => true
|
|
227
|
+
}
|
|
228
|
+
|
|
208
229
|
let awaitEach = async (arr: array<'a>, fn: 'a => promise<unit>) => {
|
|
209
230
|
for i in 0 to arr->Array.length - 1 {
|
|
210
231
|
let item = arr[i]
|
|
@@ -270,6 +291,9 @@ Helper to check if a value exists in an array
|
|
|
270
291
|
|
|
271
292
|
@send
|
|
272
293
|
external flatten: (array<array<'a>>, @as(1) _) => array<'a> = "flat"
|
|
294
|
+
|
|
295
|
+
@send
|
|
296
|
+
external copy: array<'a> => array<'a> = "slice"
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
module String = {
|
|
@@ -467,3 +491,49 @@ module Proxy = {
|
|
|
467
491
|
@new
|
|
468
492
|
external make: ('a, traps<'a>) => 'a = "Proxy"
|
|
469
493
|
}
|
|
494
|
+
|
|
495
|
+
module Hash = {
|
|
496
|
+
let rec makeOrThrow = (any: 'a): string => {
|
|
497
|
+
switch any->Js.typeof {
|
|
498
|
+
| "string" => any->magic
|
|
499
|
+
| "number" => any->magic->Js.Int.toString
|
|
500
|
+
| "bigint" => any->magic->BigInt.toString
|
|
501
|
+
| "boolean" => any->magic ? "true" : "false"
|
|
502
|
+
| "undefined" => "undefined"
|
|
503
|
+
| "object" =>
|
|
504
|
+
if any === %raw(`null`) {
|
|
505
|
+
"null"
|
|
506
|
+
} else if any->Js.Array2.isArray {
|
|
507
|
+
let any: array<'a> = any->magic
|
|
508
|
+
let hash = ref("[")
|
|
509
|
+
for i in 0 to any->Js.Array2.length - 1 {
|
|
510
|
+
hash := hash.contents ++ any->Js.Array2.unsafe_get(i)->makeOrThrow ++ ","
|
|
511
|
+
}
|
|
512
|
+
hash.contents ++ "]"
|
|
513
|
+
} else {
|
|
514
|
+
let any: dict<'a> = any->magic
|
|
515
|
+
let constructor = any->Js.Dict.unsafeGet("constructor")->magic
|
|
516
|
+
if constructor === %raw(`Object`) {
|
|
517
|
+
let hash = ref("{")
|
|
518
|
+
let keys = any->Js.Dict.keys->Js.Array2.sortInPlace
|
|
519
|
+
for i in 0 to keys->Js.Array2.length - 1 {
|
|
520
|
+
let key = keys->Js.Array2.unsafe_get(i)
|
|
521
|
+
// Ideally should escape and wrap the key in double quotes
|
|
522
|
+
// but since we don't need to decode the hash,
|
|
523
|
+
// it's fine to keep it super simple
|
|
524
|
+
hash := hash.contents ++ key ++ ":" ++ any->Js.Dict.unsafeGet(key)->makeOrThrow ++ ","
|
|
525
|
+
}
|
|
526
|
+
hash.contents ++ "}"
|
|
527
|
+
} else if constructor["name"] === "BigNumber" {
|
|
528
|
+
(any->magic)["toString"]()
|
|
529
|
+
} else {
|
|
530
|
+
Js.Exn.raiseError(`Don't know how to serialize ${(constructor->magic)["name"]}`)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
| "symbol"
|
|
534
|
+
| "function" =>
|
|
535
|
+
(any->magic)["toString"]()
|
|
536
|
+
| typeof => Js.Exn.raiseError(`Don't know how to serialize ${typeof}`)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* TypeScript file generated from BigDecimal.res by genType. */
|
|
2
|
+
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
/* tslint:disable */
|
|
5
|
+
|
|
6
|
+
const BigDecimalJS = require('./BigDecimal.bs.js');
|
|
7
|
+
|
|
8
|
+
import type {S_t as RescriptSchema_S_t} from './RescriptSchema.gen';
|
|
9
|
+
|
|
10
|
+
import type {default as $$t} from 'bignumber.js';
|
|
11
|
+
|
|
12
|
+
export type t = $$t;
|
|
13
|
+
|
|
14
|
+
export const schema: RescriptSchema_S_t<t> = BigDecimalJS.schema as any;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
@genType.import(("bignumber.js", "default"))
|
|
2
|
+
type rec t = {
|
|
3
|
+
toString: unit => string,
|
|
4
|
+
toFixed: int => string,
|
|
5
|
+
plus: t => t,
|
|
6
|
+
minus: t => t,
|
|
7
|
+
times: t => t,
|
|
8
|
+
div: t => t,
|
|
9
|
+
isEqualTo: t => bool,
|
|
10
|
+
gt: t => bool,
|
|
11
|
+
gte: t => bool,
|
|
12
|
+
lt: t => bool,
|
|
13
|
+
lte: t => bool,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Constructors
|
|
17
|
+
@new @module external fromBigInt: bigint => t = "bignumber.js"
|
|
18
|
+
@new @module external fromFloat: float => t = "bignumber.js"
|
|
19
|
+
@new @module external fromInt: int => t = "bignumber.js"
|
|
20
|
+
@new @module external fromStringUnsafe: string => t = "bignumber.js"
|
|
21
|
+
@new @module external fromString: string => option<t> = "bignumber.js"
|
|
22
|
+
|
|
23
|
+
// Methods
|
|
24
|
+
@send external toString: t => string = "toString"
|
|
25
|
+
@send external toFixed: t => string = "toFixed"
|
|
26
|
+
let toInt = (b: t): option<int> => b->toString->Belt.Int.fromString
|
|
27
|
+
@send external toNumber: t => float = "toNumber"
|
|
28
|
+
|
|
29
|
+
// Arithmetic Operations
|
|
30
|
+
@send external plus: (t, t) => t = "plus"
|
|
31
|
+
@send external minus: (t, t) => t = "minus"
|
|
32
|
+
@send external times: (t, t) => t = "multipliedBy"
|
|
33
|
+
@send external div: (t, t) => t = "dividedBy"
|
|
34
|
+
@send external sqrt: t => t = "sqrt"
|
|
35
|
+
|
|
36
|
+
// Comparison
|
|
37
|
+
@send external equals: (t, t) => bool = "isEqualTo"
|
|
38
|
+
let notEquals: (t, t) => bool = (a, b) => !equals(a, b)
|
|
39
|
+
@send external gt: (t, t) => bool = "isGreaterThan"
|
|
40
|
+
@send external gte: (t, t) => bool = "isGreaterThanOrEqualTo"
|
|
41
|
+
@send external lt: (t, t) => bool = "isLessThan"
|
|
42
|
+
@send external lte: (t, t) => bool = "isLessThanOrEqualTo"
|
|
43
|
+
|
|
44
|
+
// Utilities
|
|
45
|
+
let zero = fromInt(0)
|
|
46
|
+
let one = fromInt(1)
|
|
47
|
+
@send external decimalPlaces: (t, int) => t = "decimalPlaces"
|
|
48
|
+
|
|
49
|
+
// Serialization
|
|
50
|
+
@genType
|
|
51
|
+
let schema =
|
|
52
|
+
S.string
|
|
53
|
+
->S.setName("BigDecimal")
|
|
54
|
+
->S.transform(s => {
|
|
55
|
+
parser: string =>
|
|
56
|
+
switch string->fromString {
|
|
57
|
+
| Some(bigDecimal) => bigDecimal
|
|
58
|
+
| None => s.fail("The string is not valid BigDecimal")
|
|
59
|
+
},
|
|
60
|
+
serializer: bigDecimal => bigDecimal.toString(),
|
|
61
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** All metric types have two mandatory parameters: name and help. Refer to https://prometheus.io/docs/practices/naming/ for guidance on naming metrics. */
|
|
2
|
+
type customMetric<'a> = {.."name": string, "help": string} as 'a
|
|
3
|
+
|
|
4
|
+
@module("prom-client") external collectDefaultMetrics: 'a => unit = "collectDefaultMetrics"
|
|
5
|
+
|
|
6
|
+
type registry
|
|
7
|
+
@new @module("prom-client") external makeRegistry: unit => registry = "Registry"
|
|
8
|
+
|
|
9
|
+
@module("prom-client") external defaultRegister: registry = "register"
|
|
10
|
+
|
|
11
|
+
@send external metrics: registry => Promise.t<string> = "metrics"
|
|
12
|
+
@get external getContentType: registry => string = "contentType"
|
|
13
|
+
|
|
14
|
+
module Counter = {
|
|
15
|
+
type counter
|
|
16
|
+
@new @module("prom-client") external makeCounter: customMetric<'a> => counter = "Counter"
|
|
17
|
+
|
|
18
|
+
@send external inc: counter => unit = "inc"
|
|
19
|
+
@send external incMany: (counter, int) => unit = "inc"
|
|
20
|
+
|
|
21
|
+
@send external incLabels: (counter, 'labelsObject) => unit = "labels"
|
|
22
|
+
@send external labels: (counter, 'labelsObject) => counter = "labels"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module Gauge = {
|
|
26
|
+
type gauge
|
|
27
|
+
@new @module("prom-client") external makeGauge: customMetric<'a> => gauge = "Gauge"
|
|
28
|
+
|
|
29
|
+
@send external inc: gauge => unit = "inc"
|
|
30
|
+
@send external incMany: (gauge, int) => unit = "inc"
|
|
31
|
+
|
|
32
|
+
@send external dec: gauge => unit = "dec"
|
|
33
|
+
@send external decMany: (gauge, int) => unit = "dec"
|
|
34
|
+
|
|
35
|
+
@send external set: (gauge, int) => unit = "set"
|
|
36
|
+
|
|
37
|
+
@send external setFloat: (gauge, float) => unit = "set"
|
|
38
|
+
|
|
39
|
+
@send external labels: (gauge, 'labelsObject) => gauge = "labels"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module Histogram = {
|
|
43
|
+
type histogram
|
|
44
|
+
@new @module("prom-client") external makeHistogram: customMetric<'a> => histogram = "Histogram"
|
|
45
|
+
|
|
46
|
+
@send external observe: (histogram, float) => unit = "observe"
|
|
47
|
+
@send external startTimer: (histogram, unit) => float = "startTimer"
|
|
48
|
+
|
|
49
|
+
@send external labels: (histogram, 'labelsObject) => histogram = "labels"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module Summary = {
|
|
53
|
+
type summary
|
|
54
|
+
@new @module("prom-client") external makeSummary: customMetric<'a> => summary = "Summary"
|
|
55
|
+
|
|
56
|
+
@send external observe: (summary, float) => unit = "observe"
|
|
57
|
+
@send external startTimer: (summary, unit) => float = "startTimer"
|
|
58
|
+
}
|
package/src/db/Table.res
CHANGED
|
@@ -214,7 +214,10 @@ let toSqlParams = (table: table, ~schema) => {
|
|
|
214
214
|
}
|
|
215
215
|
| Bool =>
|
|
216
216
|
// Workaround for https://github.com/porsager/postgres/issues/471
|
|
217
|
-
S.union([
|
|
217
|
+
S.union([
|
|
218
|
+
S.literal(1)->S.shape(_ => true),
|
|
219
|
+
S.literal(0)->S.shape(_ => false),
|
|
220
|
+
])->S.toUnknown
|
|
218
221
|
| _ => schema
|
|
219
222
|
}
|
|
220
223
|
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
type contractName = string
|
|
2
|
-
|
|
3
|
-
// Currently this mapping append only, so we don't need to worry about
|
|
4
|
-
// protecting static addresses from de-registration.
|
|
5
|
-
|
|
6
|
-
type mapping = {
|
|
7
|
-
nameByAddress: dict<contractName>,
|
|
8
|
-
addressesByName: dict<Belt.Set.String.t>,
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
exception AddressRegisteredForMultipleContracts({address: Address.t, names: array<contractName>})
|
|
12
|
-
|
|
13
|
-
let addAddress = (map: mapping, ~name: string, ~address: Address.t) => {
|
|
14
|
-
switch map.nameByAddress->Utils.Dict.dangerouslyGetNonOption(address->Address.toString) {
|
|
15
|
-
| Some(currentName) if currentName != name =>
|
|
16
|
-
let logger = Logging.createChild(
|
|
17
|
-
~params={
|
|
18
|
-
"address": address->Address.toString,
|
|
19
|
-
"existingContract": currentName,
|
|
20
|
-
"newContract": name,
|
|
21
|
-
},
|
|
22
|
-
)
|
|
23
|
-
AddressRegisteredForMultipleContracts({
|
|
24
|
-
address,
|
|
25
|
-
names: [currentName, name],
|
|
26
|
-
})->ErrorHandling.mkLogAndRaise(~msg="Address registered for multiple contracts", ~logger)
|
|
27
|
-
| _ => ()
|
|
28
|
-
}
|
|
29
|
-
map.nameByAddress->Js.Dict.set(address->Address.toString, name)
|
|
30
|
-
|
|
31
|
-
let oldAddresses =
|
|
32
|
-
map.addressesByName
|
|
33
|
-
->Utils.Dict.dangerouslyGetNonOption(name)
|
|
34
|
-
->Belt.Option.getWithDefault(Belt.Set.String.empty)
|
|
35
|
-
let newAddresses = oldAddresses->Belt.Set.String.add(address->Address.toString)
|
|
36
|
-
map.addressesByName->Js.Dict.set(name, newAddresses)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let getAddresses = (map: mapping, name: string) => {
|
|
40
|
-
map.addressesByName->Utils.Dict.dangerouslyGetNonOption(name)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let getName = (map: mapping, address: string) => {
|
|
44
|
-
map.nameByAddress->Utils.Dict.dangerouslyGetNonOption(address)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let make = () => {
|
|
48
|
-
nameByAddress: Js.Dict.empty(),
|
|
49
|
-
addressesByName: Js.Dict.empty(),
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let getContractNameFromAddress = (mapping, ~contractAddress: Address.t): option<contractName> => {
|
|
53
|
-
mapping->getName(contractAddress->Address.toString)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let stringsToAddresses: array<string> => array<Address.t> = Utils.magic
|
|
57
|
-
let keyValStringToAddress: array<(string, string)> => array<(Address.t, string)> = Utils.magic
|
|
58
|
-
|
|
59
|
-
let getAddressesFromContractName = (mapping, ~contractName) => {
|
|
60
|
-
switch mapping->getAddresses(contractName) {
|
|
61
|
-
| Some(addresses) => addresses
|
|
62
|
-
| None => Belt.Set.String.empty
|
|
63
|
-
}
|
|
64
|
-
->Belt.Set.String.toArray
|
|
65
|
-
->stringsToAddresses
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let getAllAddresses = (mapping: mapping) => {
|
|
69
|
-
mapping.nameByAddress->Js.Dict.keys->stringsToAddresses
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let copy = (mapping: mapping) => {
|
|
73
|
-
{
|
|
74
|
-
nameByAddress: mapping.nameByAddress->Utils.Dict.shallowCopy,
|
|
75
|
-
// Since Belt.Set.String.t is immutable, we can simply do shallow copy here
|
|
76
|
-
addressesByName: mapping.addressesByName->Utils.Dict.shallowCopy,
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let mergeInPlace = (map, ~target) => {
|
|
81
|
-
map.nameByAddress
|
|
82
|
-
->Js.Dict.keys
|
|
83
|
-
->Belt.Array.forEach(addr => {
|
|
84
|
-
let name = map.nameByAddress->Js.Dict.unsafeGet(addr)
|
|
85
|
-
target->addAddress(~address=addr->Address.unsafeFromString, ~name)
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let fromArray = (nameAddrTuples: array<(Address.t, string)>) => {
|
|
90
|
-
let m = make()
|
|
91
|
-
nameAddrTuples->Belt.Array.forEach(((address, name)) => m->addAddress(~name, ~address))
|
|
92
|
-
m
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
Creates a new mapping from the previous without the addresses passed in as "addressesToRemove"
|
|
97
|
-
*/
|
|
98
|
-
let removeAddresses = (mapping: mapping, ~addressesToRemove: array<Address.t>) => {
|
|
99
|
-
switch addressesToRemove {
|
|
100
|
-
| [] => mapping
|
|
101
|
-
| _ =>
|
|
102
|
-
mapping.nameByAddress
|
|
103
|
-
->Js.Dict.entries
|
|
104
|
-
->Belt.Array.keep(((addr, _name)) => {
|
|
105
|
-
let shouldRemove = addressesToRemove->Utils.Array.includes(addr->Utils.magic)
|
|
106
|
-
!shouldRemove
|
|
107
|
-
})
|
|
108
|
-
->keyValStringToAddress
|
|
109
|
-
->fromArray
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let addressCount = (mapping: mapping) => mapping.nameByAddress->Js.Dict.keys->Belt.Array.length
|
|
114
|
-
|
|
115
|
-
let isEmpty = (mapping: mapping) => mapping->addressCount == 0
|