envio 3.6.0 → 3.6.1-subgraph
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/ChainState.res +10 -4
- package/src/ChainState.res.mjs +5 -1
- package/src/Config.res +12 -0
- package/src/Config.res.mjs +8 -2
- package/src/Core.res +15 -0
- package/src/Core.res.mjs +11 -0
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +8 -6
- package/src/HandlerLoader.res +20 -8
- package/src/HandlerLoader.res.mjs +11 -2
- package/src/Metrics.res +5 -5
- package/src/Metrics.res.mjs +5 -1
- package/src/UserContext.res +358 -51
- package/src/UserContext.res.mjs +271 -35
- package/src/sources/SourceManager.res +6 -12
- package/src/sources/SourceManager.res.mjs +8 -5
- package/src/subgraph/blocks.ts +176 -0
- package/src/subgraph/calls.ts +213 -0
- package/src/subgraph/conformance.ts +99 -0
- package/src/subgraph/division.ts +100 -0
- package/src/subgraph/errors.ts +90 -0
- package/src/subgraph/graph-ts-types/VERSION +2 -0
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
- package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
- package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
- package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
- package/src/subgraph/graph-ts-types/index.d.ts +102 -0
- package/src/subgraph/graph-ts.ts +1695 -0
- package/src/subgraph/hosts.ts +148 -0
- package/src/subgraph/runtime.ts +827 -0
- package/src/subgraph/scope.ts +63 -0
|
@@ -0,0 +1,1695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `@graphprotocol/graph-ts` resolves to at runtime inside a subgraph
|
|
3
|
+
* project. Types still resolve to the real package, so the editor and `tsc`
|
|
4
|
+
* see exactly what a subgraph developer sees today; only module resolution is
|
|
5
|
+
* swapped, and only for the mapping graph.
|
|
6
|
+
*
|
|
7
|
+
* Everything `graph codegen` emits sits on this surface — entity classes over
|
|
8
|
+
* `Entity`/`TypedMap`/`Value` and `store`, contract bindings over
|
|
9
|
+
* `ethereum.SmartContract`, template classes over `DataSourceTemplate` — so
|
|
10
|
+
* the project's real `generated/` runs on top of it unchanged.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import BigNumber from "bignumber.js";
|
|
14
|
+
import { keccak256 as viemKeccak256, toHex, hexToBytes, decodeAbiParameters } from "viem";
|
|
15
|
+
import { currentScope } from "./scope.ts";
|
|
16
|
+
import {
|
|
17
|
+
PROTOTYPE_PASSTHROUGH,
|
|
18
|
+
refusedGetter,
|
|
19
|
+
strictNamespace,
|
|
20
|
+
unsupported,
|
|
21
|
+
unknown,
|
|
22
|
+
} from "./errors.ts";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Byte values
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* graph-ts stores every integer as a little-endian byte array, signed values in
|
|
30
|
+
* two's complement — so the byte order and the sign both have to be spelled out
|
|
31
|
+
* here rather than borrowed from a hex string, which reads big-endian.
|
|
32
|
+
*/
|
|
33
|
+
/** An AssemblyScript i64 literal reaches the shim as a plain JS number. */
|
|
34
|
+
function asBigInt(value: bigint | number): bigint {
|
|
35
|
+
return typeof value === "bigint" ? value : BigInt(Math.trunc(value));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toLittleEndian(value: bigint, size: number): Uint8Array {
|
|
39
|
+
const out = new Uint8Array(size);
|
|
40
|
+
let rest = BigInt.asUintN(size * 8, value);
|
|
41
|
+
for (let index = 0; index < size; index++) {
|
|
42
|
+
out[index] = Number(rest & 0xffn);
|
|
43
|
+
rest >>= 8n;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fromLittleEndian(bytes: Uint8Array, signed: boolean): bigint {
|
|
49
|
+
let magnitude = 0n;
|
|
50
|
+
for (let index = bytes.length - 1; index >= 0; index--) {
|
|
51
|
+
magnitude = (magnitude << 8n) | BigInt(bytes[index]);
|
|
52
|
+
}
|
|
53
|
+
return signed ? BigInt.asIntN(bytes.length * 8, magnitude) : magnitude;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The smallest byte count that still holds `value` in two's complement. */
|
|
57
|
+
function byteWidth(value: bigint): number {
|
|
58
|
+
let size = 1;
|
|
59
|
+
while (BigInt.asIntN(size * 8, value) !== value) size++;
|
|
60
|
+
return size;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fitted(value: bigint, min: bigint, max: bigint, target: string): bigint {
|
|
64
|
+
if (value < min || value > max) {
|
|
65
|
+
throw new Error(`Envio Subgraph: ${value} does not fit in an ${target}.`);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class ByteArray extends Uint8Array {
|
|
71
|
+
static fromHexString(hex: string): ByteArray {
|
|
72
|
+
const normalized = hex.startsWith("0x") ? hex : "0x" + hex;
|
|
73
|
+
return new ByteArray(hexToBytes(normalized as `0x${string}`));
|
|
74
|
+
}
|
|
75
|
+
static fromUTF8(input: string): ByteArray {
|
|
76
|
+
return new ByteArray(new TextEncoder().encode(input));
|
|
77
|
+
}
|
|
78
|
+
static fromI32(value: number): ByteArray {
|
|
79
|
+
return new ByteArray(toLittleEndian(BigInt(Math.trunc(value)), 4));
|
|
80
|
+
}
|
|
81
|
+
static fromBigInt(value: BigInt_): ByteArray {
|
|
82
|
+
return new ByteArray(toLittleEndian(value.valueOf() as bigint, byteWidth(value.valueOf() as bigint)));
|
|
83
|
+
}
|
|
84
|
+
static fromUint8Array(bytes: Uint8Array): ByteArray {
|
|
85
|
+
return new ByteArray(bytes);
|
|
86
|
+
}
|
|
87
|
+
static fromU32(value: number): ByteArray {
|
|
88
|
+
return ByteArray.fromI32(value);
|
|
89
|
+
}
|
|
90
|
+
static fromI64(value: bigint | number): ByteArray {
|
|
91
|
+
return new ByteArray(toLittleEndian(asBigInt(value), 8));
|
|
92
|
+
}
|
|
93
|
+
static fromU64(value: bigint): ByteArray {
|
|
94
|
+
return ByteArray.fromI64(value);
|
|
95
|
+
}
|
|
96
|
+
static empty(): ByteArray {
|
|
97
|
+
return new ByteArray(0);
|
|
98
|
+
}
|
|
99
|
+
toHex(): string {
|
|
100
|
+
return this.toHexString();
|
|
101
|
+
}
|
|
102
|
+
toHexString(): string {
|
|
103
|
+
return toHex(this as Uint8Array);
|
|
104
|
+
}
|
|
105
|
+
toString(): string {
|
|
106
|
+
return this.toHexString();
|
|
107
|
+
}
|
|
108
|
+
toBase58(): string {
|
|
109
|
+
throw unsupported("ByteArray.toBase58", "a mapping handler");
|
|
110
|
+
}
|
|
111
|
+
toU32(): number {
|
|
112
|
+
return Number(fitted(fromLittleEndian(this, false), 0n, 4294967295n, "u32"));
|
|
113
|
+
}
|
|
114
|
+
toI32(): number {
|
|
115
|
+
return Number(fitted(fromLittleEndian(this, true), -2147483648n, 2147483647n, "i32"));
|
|
116
|
+
}
|
|
117
|
+
toBigInt(): BigInt_ {
|
|
118
|
+
return new BigInt_(fromLittleEndian(this, true));
|
|
119
|
+
}
|
|
120
|
+
concat(other: ByteArray): ByteArray {
|
|
121
|
+
const out = new ByteArray(this.length + other.length);
|
|
122
|
+
out.set(this, 0);
|
|
123
|
+
out.set(other, this.length);
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
equals(other: ByteArray): boolean {
|
|
127
|
+
return this.toHexString() === other.toHexString();
|
|
128
|
+
}
|
|
129
|
+
notEqual(other: ByteArray): boolean {
|
|
130
|
+
return !this.equals(other);
|
|
131
|
+
}
|
|
132
|
+
concatI32(other: number): ByteArray {
|
|
133
|
+
return this.concat(ByteArray.fromI32(other));
|
|
134
|
+
}
|
|
135
|
+
toI64(): bigint {
|
|
136
|
+
return fitted(fromLittleEndian(this, true), -(2n ** 63n), 2n ** 63n - 1n, "i64");
|
|
137
|
+
}
|
|
138
|
+
toU64(): bigint {
|
|
139
|
+
return fitted(fromLittleEndian(this, false), 0n, 2n ** 64n - 1n, "u64");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export class Bytes extends ByteArray {
|
|
144
|
+
static fromHexString(hex: string): Bytes {
|
|
145
|
+
return new Bytes(ByteArray.fromHexString(hex));
|
|
146
|
+
}
|
|
147
|
+
static fromUTF8(input: string): Bytes {
|
|
148
|
+
return new Bytes(ByteArray.fromUTF8(input));
|
|
149
|
+
}
|
|
150
|
+
static fromByteArray(byteArray: ByteArray): Bytes {
|
|
151
|
+
return new Bytes(byteArray);
|
|
152
|
+
}
|
|
153
|
+
static fromI32(value: number): Bytes {
|
|
154
|
+
return new Bytes(ByteArray.fromI32(value));
|
|
155
|
+
}
|
|
156
|
+
static fromUint8Array(bytes: Uint8Array): Bytes {
|
|
157
|
+
return new Bytes(bytes);
|
|
158
|
+
}
|
|
159
|
+
static fromU32(value: number): Bytes {
|
|
160
|
+
return new Bytes(ByteArray.fromU32(value));
|
|
161
|
+
}
|
|
162
|
+
static fromI64(value: bigint): Bytes {
|
|
163
|
+
return new Bytes(ByteArray.fromI64(value));
|
|
164
|
+
}
|
|
165
|
+
static fromU64(value: bigint): Bytes {
|
|
166
|
+
return new Bytes(ByteArray.fromU64(value));
|
|
167
|
+
}
|
|
168
|
+
static fromBigInt(value: BigInt_): Bytes {
|
|
169
|
+
return new Bytes(ByteArray.fromBigInt(value));
|
|
170
|
+
}
|
|
171
|
+
static empty(): Bytes {
|
|
172
|
+
return new Bytes(0);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class Address extends Bytes {
|
|
177
|
+
static fromString(address: string): Address {
|
|
178
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
179
|
+
throw new Error(`Address.fromString: ${address} is not a valid 20-byte hex address`);
|
|
180
|
+
}
|
|
181
|
+
return new Address(ByteArray.fromHexString(address.toLowerCase()));
|
|
182
|
+
}
|
|
183
|
+
static fromBytes(bytes: Bytes): Address {
|
|
184
|
+
if (bytes.length !== 20) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Envio Subgraph: Address.fromBytes needs 20 bytes, got ${bytes.length}.`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return new Address(bytes);
|
|
190
|
+
}
|
|
191
|
+
static zero(): Address {
|
|
192
|
+
return Address.fromString("0x0000000000000000000000000000000000000000");
|
|
193
|
+
}
|
|
194
|
+
// graph-ts renders addresses lowercase, and id/derived-key parity across the
|
|
195
|
+
// two indexers depends on it.
|
|
196
|
+
toHexString(): string {
|
|
197
|
+
return super.toHexString().toLowerCase();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Numbers
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
class BigInt_ extends Uint8Array {
|
|
206
|
+
readonly value: bigint;
|
|
207
|
+
|
|
208
|
+
constructor(value: bigint) {
|
|
209
|
+
// The bytes are deliberately left empty. Arithmetic, comparison and every
|
|
210
|
+
// `to*` run off `value`; materialising the two's-complement representation
|
|
211
|
+
// on construction cost ~7% of indexing CPU, and nothing in a mapping reads
|
|
212
|
+
// a BigInt as bytes — graph codegen never emits it, and the conversions
|
|
213
|
+
// that would (`toHexString`, `toI32`) are overridden here.
|
|
214
|
+
super(0);
|
|
215
|
+
this.value = value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Inherited TypedArray operations (`map`, `slice`, `subarray`) would
|
|
219
|
+
// otherwise call this constructor with a length.
|
|
220
|
+
static get [Symbol.species](): Uint8ArrayConstructor {
|
|
221
|
+
return Uint8Array;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// graph codegen types a small uint as `i32`, so a mapping hands one straight
|
|
225
|
+
// to `fromI32` — but envio decodes every integer ABI type as a bigint, so what
|
|
226
|
+
// arrives is already a BigInt.
|
|
227
|
+
static fromI32(value: number | bigint | BigInt_): BigInt_ {
|
|
228
|
+
const raw = typeof value === "object" ? (value as BigInt_).valueOf() : value;
|
|
229
|
+
return new BigInt_(typeof raw === "bigint" ? raw : BigInt(Math.trunc(raw as number)));
|
|
230
|
+
}
|
|
231
|
+
static fromU32(value: number): BigInt_ {
|
|
232
|
+
return BigInt_.fromI32(value);
|
|
233
|
+
}
|
|
234
|
+
static fromI64(value: bigint | number): BigInt_ {
|
|
235
|
+
return new BigInt_(BigInt(value));
|
|
236
|
+
}
|
|
237
|
+
static fromU64(value: bigint | number): BigInt_ {
|
|
238
|
+
return new BigInt_(BigInt(value));
|
|
239
|
+
}
|
|
240
|
+
static fromString(value: string): BigInt_ {
|
|
241
|
+
return new BigInt_(BigInt(value));
|
|
242
|
+
}
|
|
243
|
+
static fromByteArray(bytes: ByteArray): BigInt_ {
|
|
244
|
+
return BigInt_.fromSignedBytes(bytes as Bytes);
|
|
245
|
+
}
|
|
246
|
+
static fromUnsignedBytes(bytes: ByteArray): BigInt_ {
|
|
247
|
+
return new BigInt_(fromLittleEndian(bytes, false));
|
|
248
|
+
}
|
|
249
|
+
static fromSignedBytes(bytes: Bytes): BigInt_ {
|
|
250
|
+
return new BigInt_(fromLittleEndian(bytes, true));
|
|
251
|
+
}
|
|
252
|
+
static zero(): BigInt_ {
|
|
253
|
+
return new BigInt_(0n);
|
|
254
|
+
}
|
|
255
|
+
static compare(a: BigInt_, b: BigInt_): number {
|
|
256
|
+
return a.value < b.value ? -1 : a.value > b.value ? 1 : 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Uint8Array.valueOf returns the array itself; graph-ts BigInt has no
|
|
260
|
+
// valueOf at all, so widening keeps both callers honest.
|
|
261
|
+
valueOf(): any {
|
|
262
|
+
return this.value;
|
|
263
|
+
}
|
|
264
|
+
toString(): string {
|
|
265
|
+
return this.value.toString();
|
|
266
|
+
}
|
|
267
|
+
toHex(): string {
|
|
268
|
+
return this.toHexString();
|
|
269
|
+
}
|
|
270
|
+
toHexString(): string {
|
|
271
|
+
return toHex(this.value);
|
|
272
|
+
}
|
|
273
|
+
toI32(): number {
|
|
274
|
+
return Number(fitted(this.value, -2147483648n, 2147483647n, "i32"));
|
|
275
|
+
}
|
|
276
|
+
toU32(): number {
|
|
277
|
+
return Number(fitted(this.value, 0n, 4294967295n, "u32"));
|
|
278
|
+
}
|
|
279
|
+
toI64(): bigint {
|
|
280
|
+
return this.value;
|
|
281
|
+
}
|
|
282
|
+
toBigDecimal(): BigDecimal {
|
|
283
|
+
return new BigDecimal(new BigNumber(this.value.toString()));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
plus(other: BigInt_): BigInt_ {
|
|
287
|
+
return new BigInt_(this.value + other.value);
|
|
288
|
+
}
|
|
289
|
+
minus(other: BigInt_): BigInt_ {
|
|
290
|
+
return new BigInt_(this.value - other.value);
|
|
291
|
+
}
|
|
292
|
+
times(other: BigInt_): BigInt_ {
|
|
293
|
+
return new BigInt_(this.value * other.value);
|
|
294
|
+
}
|
|
295
|
+
div(other: BigInt_): BigInt_ {
|
|
296
|
+
return new BigInt_(this.value / other.value);
|
|
297
|
+
}
|
|
298
|
+
mod(other: BigInt_): BigInt_ {
|
|
299
|
+
return new BigInt_(this.value % other.value);
|
|
300
|
+
}
|
|
301
|
+
pow(exponent: number): BigInt_ {
|
|
302
|
+
return new BigInt_(this.value ** BigInt(exponent));
|
|
303
|
+
}
|
|
304
|
+
neg(): BigInt_ {
|
|
305
|
+
return new BigInt_(-this.value);
|
|
306
|
+
}
|
|
307
|
+
abs(): BigInt_ {
|
|
308
|
+
return new BigInt_(this.value < 0n ? -this.value : this.value);
|
|
309
|
+
}
|
|
310
|
+
equals(other: BigInt_): boolean {
|
|
311
|
+
return this.value === other.value;
|
|
312
|
+
}
|
|
313
|
+
notEqual(other: BigInt_): boolean {
|
|
314
|
+
return this.value !== other.value;
|
|
315
|
+
}
|
|
316
|
+
lt(other: BigInt_): boolean {
|
|
317
|
+
return this.value < other.value;
|
|
318
|
+
}
|
|
319
|
+
le(other: BigInt_): boolean {
|
|
320
|
+
return this.value <= other.value;
|
|
321
|
+
}
|
|
322
|
+
gt(other: BigInt_): boolean {
|
|
323
|
+
return this.value > other.value;
|
|
324
|
+
}
|
|
325
|
+
ge(other: BigInt_): boolean {
|
|
326
|
+
return this.value >= other.value;
|
|
327
|
+
}
|
|
328
|
+
isZero(): boolean {
|
|
329
|
+
return this.value === 0n;
|
|
330
|
+
}
|
|
331
|
+
isI32(): boolean {
|
|
332
|
+
return this.value >= -2147483648n && this.value <= 2147483647n;
|
|
333
|
+
}
|
|
334
|
+
toU64(): bigint {
|
|
335
|
+
return this.value;
|
|
336
|
+
}
|
|
337
|
+
sqrt(): BigInt_ {
|
|
338
|
+
if (this.value < 0n) {
|
|
339
|
+
throw new Error("BigInt.sqrt of a negative value");
|
|
340
|
+
}
|
|
341
|
+
let guess = this.value;
|
|
342
|
+
let next = (guess + 1n) / 2n;
|
|
343
|
+
while (next < guess) {
|
|
344
|
+
guess = next;
|
|
345
|
+
next = (guess + this.value / guess) / 2n;
|
|
346
|
+
}
|
|
347
|
+
return new BigInt_(guess);
|
|
348
|
+
}
|
|
349
|
+
divDecimal(other: BigDecimal): BigDecimal {
|
|
350
|
+
return this.toBigDecimal().div(other);
|
|
351
|
+
}
|
|
352
|
+
bitAnd(other: BigInt_): BigInt_ {
|
|
353
|
+
return new BigInt_(this.value & other.value);
|
|
354
|
+
}
|
|
355
|
+
bitOr(other: BigInt_): BigInt_ {
|
|
356
|
+
return new BigInt_(this.value | other.value);
|
|
357
|
+
}
|
|
358
|
+
leftShift(bits: number): BigInt_ {
|
|
359
|
+
return new BigInt_(this.value << BigInt(bits));
|
|
360
|
+
}
|
|
361
|
+
rightShift(bits: number): BigInt_ {
|
|
362
|
+
return new BigInt_(this.value >> BigInt(bits));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export { BigInt_ as BigInt };
|
|
367
|
+
|
|
368
|
+
export class BigDecimal {
|
|
369
|
+
readonly value: BigNumber;
|
|
370
|
+
|
|
371
|
+
constructor(value: BigNumber | BigInt_ | string | number) {
|
|
372
|
+
if (value instanceof BigNumber) {
|
|
373
|
+
this.value = value;
|
|
374
|
+
} else if (value instanceof BigInt_) {
|
|
375
|
+
this.value = new BigNumber(value.toString());
|
|
376
|
+
} else {
|
|
377
|
+
this.value = new BigNumber(value as any);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
static fromString(value: string): BigDecimal {
|
|
382
|
+
return new BigDecimal(new BigNumber(value));
|
|
383
|
+
}
|
|
384
|
+
static zero(): BigDecimal {
|
|
385
|
+
return new BigDecimal(new BigNumber(0));
|
|
386
|
+
}
|
|
387
|
+
static compare(a: BigDecimal, b: BigDecimal): number {
|
|
388
|
+
return a.value.comparedTo(b.value);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
toString(): string {
|
|
392
|
+
return this.value.toFixed();
|
|
393
|
+
}
|
|
394
|
+
toBigInt(): BigInt_ {
|
|
395
|
+
return BigInt_.fromString(this.value.integerValue(BigNumber.ROUND_DOWN).toFixed());
|
|
396
|
+
}
|
|
397
|
+
plus(other: BigDecimal): BigDecimal {
|
|
398
|
+
return new BigDecimal(this.value.plus(other.value));
|
|
399
|
+
}
|
|
400
|
+
minus(other: BigDecimal): BigDecimal {
|
|
401
|
+
return new BigDecimal(this.value.minus(other.value));
|
|
402
|
+
}
|
|
403
|
+
times(other: BigDecimal): BigDecimal {
|
|
404
|
+
return new BigDecimal(this.value.times(other.value));
|
|
405
|
+
}
|
|
406
|
+
div(other: BigDecimal): BigDecimal {
|
|
407
|
+
return new BigDecimal(this.value.div(other.value));
|
|
408
|
+
}
|
|
409
|
+
equals(other: BigDecimal): boolean {
|
|
410
|
+
return this.value.isEqualTo(other.value);
|
|
411
|
+
}
|
|
412
|
+
notEqual(other: BigDecimal): boolean {
|
|
413
|
+
return !this.value.isEqualTo(other.value);
|
|
414
|
+
}
|
|
415
|
+
lt(other: BigDecimal): boolean {
|
|
416
|
+
return this.value.isLessThan(other.value);
|
|
417
|
+
}
|
|
418
|
+
le(other: BigDecimal): boolean {
|
|
419
|
+
return this.value.isLessThanOrEqualTo(other.value);
|
|
420
|
+
}
|
|
421
|
+
gt(other: BigDecimal): boolean {
|
|
422
|
+
return this.value.isGreaterThan(other.value);
|
|
423
|
+
}
|
|
424
|
+
ge(other: BigDecimal): boolean {
|
|
425
|
+
return this.value.isGreaterThanOrEqualTo(other.value);
|
|
426
|
+
}
|
|
427
|
+
neg(): BigDecimal {
|
|
428
|
+
return new BigDecimal(this.value.negated());
|
|
429
|
+
}
|
|
430
|
+
truncate(decimals: number): BigDecimal {
|
|
431
|
+
return new BigDecimal(this.value.decimalPlaces(decimals, BigNumber.ROUND_DOWN));
|
|
432
|
+
}
|
|
433
|
+
// graph-ts stores a decimal as `digits * 10 ** exp`.
|
|
434
|
+
get digits(): BigInt_ {
|
|
435
|
+
const [coefficient, exponent] = this.value.toFixed().split(".");
|
|
436
|
+
const scaled = (coefficient ?? "0") + (exponent ?? "");
|
|
437
|
+
return BigInt_.fromString(scaled === "" || scaled === "-" ? "0" : scaled);
|
|
438
|
+
}
|
|
439
|
+
get exp(): BigInt_ {
|
|
440
|
+
const fraction = this.value.toFixed().split(".")[1] ?? "";
|
|
441
|
+
return BigInt_.fromI32(-fraction.length);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
// TypedMap / Value / Entity
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
export class TypedMapEntry<K, V> {
|
|
450
|
+
constructor(
|
|
451
|
+
public key: K,
|
|
452
|
+
public value: V,
|
|
453
|
+
) {}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export class TypedMap<K, V> {
|
|
457
|
+
entries: TypedMapEntry<K, V>[] = [];
|
|
458
|
+
|
|
459
|
+
set(key: K, value: V): void {
|
|
460
|
+
const entry = this.getEntry(key);
|
|
461
|
+
if (entry) {
|
|
462
|
+
entry.value = value;
|
|
463
|
+
} else {
|
|
464
|
+
this.entries.push(new TypedMapEntry(key, value));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
getEntry(key: K): TypedMapEntry<K, V> | null {
|
|
468
|
+
return this.entries.find((entry) => entry.key === key) ?? null;
|
|
469
|
+
}
|
|
470
|
+
get(key: K): V | null {
|
|
471
|
+
const entry = this.getEntry(key);
|
|
472
|
+
return entry ? entry.value : null;
|
|
473
|
+
}
|
|
474
|
+
mustGetEntry(key: K): TypedMapEntry<K, V> {
|
|
475
|
+
const entry = this.getEntry(key);
|
|
476
|
+
if (entry === null) {
|
|
477
|
+
throw new Error(`TypedMap does not contain an entry for key ${String(key)}`);
|
|
478
|
+
}
|
|
479
|
+
return entry;
|
|
480
|
+
}
|
|
481
|
+
mustGet(key: K): V {
|
|
482
|
+
const value = this.get(key);
|
|
483
|
+
if (value === null) {
|
|
484
|
+
throw new Error(`TypedMap does not contain a value for key ${String(key)}`);
|
|
485
|
+
}
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
488
|
+
isSet(key: K): boolean {
|
|
489
|
+
return this.getEntry(key) !== null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export enum ValueKind {
|
|
494
|
+
STRING = 0,
|
|
495
|
+
INT = 1,
|
|
496
|
+
BIGDECIMAL = 2,
|
|
497
|
+
BOOL = 3,
|
|
498
|
+
ARRAY = 4,
|
|
499
|
+
NULL = 5,
|
|
500
|
+
BYTES = 6,
|
|
501
|
+
BIGINT = 7,
|
|
502
|
+
INT8 = 8,
|
|
503
|
+
TIMESTAMP = 9,
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export class Value {
|
|
507
|
+
constructor(
|
|
508
|
+
public kind: ValueKind,
|
|
509
|
+
public data: any,
|
|
510
|
+
) {}
|
|
511
|
+
|
|
512
|
+
static fromString(value: string): Value {
|
|
513
|
+
return new Value(ValueKind.STRING, value);
|
|
514
|
+
}
|
|
515
|
+
static fromI32(value: number): Value {
|
|
516
|
+
return new Value(ValueKind.INT, value);
|
|
517
|
+
}
|
|
518
|
+
static fromI64(value: bigint): Value {
|
|
519
|
+
return new Value(ValueKind.INT8, value);
|
|
520
|
+
}
|
|
521
|
+
static fromBigInt(value: BigInt_): Value {
|
|
522
|
+
return new Value(ValueKind.BIGINT, value);
|
|
523
|
+
}
|
|
524
|
+
static fromBigDecimal(value: BigDecimal): Value {
|
|
525
|
+
return new Value(ValueKind.BIGDECIMAL, value);
|
|
526
|
+
}
|
|
527
|
+
static fromBoolean(value: boolean): Value {
|
|
528
|
+
return new Value(ValueKind.BOOL, value);
|
|
529
|
+
}
|
|
530
|
+
static fromBytes(value: Bytes): Value {
|
|
531
|
+
return new Value(ValueKind.BYTES, value);
|
|
532
|
+
}
|
|
533
|
+
static fromAddress(value: Address): Value {
|
|
534
|
+
return new Value(ValueKind.BYTES, value);
|
|
535
|
+
}
|
|
536
|
+
static fromTimestamp(value: bigint): Value {
|
|
537
|
+
return new Value(ValueKind.TIMESTAMP, value);
|
|
538
|
+
}
|
|
539
|
+
static fromNull(): Value {
|
|
540
|
+
return new Value(ValueKind.NULL, null);
|
|
541
|
+
}
|
|
542
|
+
static fromArray(values: Value[]): Value {
|
|
543
|
+
return new Value(ValueKind.ARRAY, values);
|
|
544
|
+
}
|
|
545
|
+
static fromStringArray(values: string[]): Value {
|
|
546
|
+
return Value.fromArray(values.map(Value.fromString));
|
|
547
|
+
}
|
|
548
|
+
static fromBytesArray(values: Bytes[]): Value {
|
|
549
|
+
return Value.fromArray(values.map(Value.fromBytes));
|
|
550
|
+
}
|
|
551
|
+
static fromBigIntArray(values: BigInt_[]): Value {
|
|
552
|
+
return Value.fromArray(values.map(Value.fromBigInt));
|
|
553
|
+
}
|
|
554
|
+
static fromBigDecimalArray(values: BigDecimal[]): Value {
|
|
555
|
+
return Value.fromArray(values.map(Value.fromBigDecimal));
|
|
556
|
+
}
|
|
557
|
+
static fromBooleanArray(values: boolean[]): Value {
|
|
558
|
+
return Value.fromArray(values.map(Value.fromBoolean));
|
|
559
|
+
}
|
|
560
|
+
static fromI32Array(values: number[]): Value {
|
|
561
|
+
return Value.fromArray(values.map(Value.fromI32));
|
|
562
|
+
}
|
|
563
|
+
static fromI64Array(values: bigint[]): Value {
|
|
564
|
+
return Value.fromArray(values.map(Value.fromI64));
|
|
565
|
+
}
|
|
566
|
+
static fromAddressArray(values: Address[]): Value {
|
|
567
|
+
return Value.fromArray(values.map(Value.fromAddress));
|
|
568
|
+
}
|
|
569
|
+
static fromMatrix(values: Value[][]): Value {
|
|
570
|
+
return Value.fromArray(values.map(Value.fromArray));
|
|
571
|
+
}
|
|
572
|
+
static fromStringMatrix(values: string[][]): Value {
|
|
573
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromString)));
|
|
574
|
+
}
|
|
575
|
+
static fromBytesMatrix(values: Bytes[][]): Value {
|
|
576
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromBytes)));
|
|
577
|
+
}
|
|
578
|
+
static fromAddressMatrix(values: Address[][]): Value {
|
|
579
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromAddress)));
|
|
580
|
+
}
|
|
581
|
+
static fromBigIntMatrix(values: BigInt_[][]): Value {
|
|
582
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromBigInt)));
|
|
583
|
+
}
|
|
584
|
+
static fromBooleanMatrix(values: boolean[][]): Value {
|
|
585
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromBoolean)));
|
|
586
|
+
}
|
|
587
|
+
static fromI32Matrix(values: number[][]): Value {
|
|
588
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromI32)));
|
|
589
|
+
}
|
|
590
|
+
static fromI64Matrix(values: bigint[][]): Value {
|
|
591
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromI64)));
|
|
592
|
+
}
|
|
593
|
+
static fromTimestampMatrix(values: bigint[][]): Value {
|
|
594
|
+
return Value.fromMatrix(values.map((row) => row.map(Value.fromTimestamp)));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// The accessors are deliberately lenient about kind: envio stores a
|
|
598
|
+
// subgraph's `Bytes` as lowercase hex text, so a value read back from the
|
|
599
|
+
// store carries STRING where the generated getter asks for bytes.
|
|
600
|
+
toString(): string {
|
|
601
|
+
if (this.data instanceof Bytes || this.data instanceof ByteArray) {
|
|
602
|
+
return this.data.toHexString();
|
|
603
|
+
}
|
|
604
|
+
return String(this.data);
|
|
605
|
+
}
|
|
606
|
+
toStringArray(): string[] {
|
|
607
|
+
return (this.data as Value[]).map((value) => value.toString());
|
|
608
|
+
}
|
|
609
|
+
toBytes(): Bytes {
|
|
610
|
+
if (this.data instanceof Bytes) return this.data;
|
|
611
|
+
if (this.data instanceof ByteArray) return Bytes.fromByteArray(this.data);
|
|
612
|
+
return Bytes.fromHexString(String(this.data));
|
|
613
|
+
}
|
|
614
|
+
toBytesArray(): Bytes[] {
|
|
615
|
+
return (this.data as Value[]).map((value) => value.toBytes());
|
|
616
|
+
}
|
|
617
|
+
toAddress(): Address {
|
|
618
|
+
return Address.fromString(this.toBytes().toHexString());
|
|
619
|
+
}
|
|
620
|
+
toBigInt(): BigInt_ {
|
|
621
|
+
if (this.data instanceof BigInt_) return this.data;
|
|
622
|
+
if (typeof this.data === "bigint") return new BigInt_(this.data);
|
|
623
|
+
return BigInt_.fromString(String(this.data));
|
|
624
|
+
}
|
|
625
|
+
toBigIntArray(): BigInt_[] {
|
|
626
|
+
return (this.data as Value[]).map((value) => value.toBigInt());
|
|
627
|
+
}
|
|
628
|
+
toBigDecimal(): BigDecimal {
|
|
629
|
+
if (this.data instanceof BigDecimal) return this.data;
|
|
630
|
+
return BigDecimal.fromString(String(this.data));
|
|
631
|
+
}
|
|
632
|
+
toBigDecimalArray(): BigDecimal[] {
|
|
633
|
+
return (this.data as Value[]).map((value) => value.toBigDecimal());
|
|
634
|
+
}
|
|
635
|
+
toBoolean(): boolean {
|
|
636
|
+
return Boolean(this.data);
|
|
637
|
+
}
|
|
638
|
+
toBooleanArray(): boolean[] {
|
|
639
|
+
return (this.data as Value[]).map((value) => value.toBoolean());
|
|
640
|
+
}
|
|
641
|
+
toI32(): number {
|
|
642
|
+
return Number(this.data);
|
|
643
|
+
}
|
|
644
|
+
toI32Array(): number[] {
|
|
645
|
+
return (this.data as Value[]).map((value) => value.toI32());
|
|
646
|
+
}
|
|
647
|
+
toI64(): bigint {
|
|
648
|
+
return BigInt(this.data as any);
|
|
649
|
+
}
|
|
650
|
+
toTimestamp(): bigint {
|
|
651
|
+
return BigInt(this.data as any);
|
|
652
|
+
}
|
|
653
|
+
toArray(): Value[] {
|
|
654
|
+
return this.data as Value[];
|
|
655
|
+
}
|
|
656
|
+
toMatrix(): Value[][] {
|
|
657
|
+
return this.toArray().map((row) => row.toArray());
|
|
658
|
+
}
|
|
659
|
+
toI64Array(): bigint[] {
|
|
660
|
+
return this.toArray().map((value) => value.toI64());
|
|
661
|
+
}
|
|
662
|
+
toTimestampArray(): bigint[] {
|
|
663
|
+
return this.toArray().map((value) => value.toTimestamp());
|
|
664
|
+
}
|
|
665
|
+
toAddressMatrix(): Address[][] {
|
|
666
|
+
return this.toMatrix().map((row) => row.map((value) => value.toAddress()));
|
|
667
|
+
}
|
|
668
|
+
toStringMatrix(): string[][] {
|
|
669
|
+
return this.toMatrix().map((row) => row.map((value) => value.toString()));
|
|
670
|
+
}
|
|
671
|
+
toBytesMatrix(): Bytes[][] {
|
|
672
|
+
return this.toMatrix().map((row) => row.map((value) => value.toBytes()));
|
|
673
|
+
}
|
|
674
|
+
toBigIntMatrix(): BigInt_[][] {
|
|
675
|
+
return this.toMatrix().map((row) => row.map((value) => value.toBigInt()));
|
|
676
|
+
}
|
|
677
|
+
toBooleanMatrix(): boolean[][] {
|
|
678
|
+
return this.toMatrix().map((row) => row.map((value) => value.toBoolean()));
|
|
679
|
+
}
|
|
680
|
+
toI32Matrix(): number[][] {
|
|
681
|
+
return this.toMatrix().map((row) => row.map((value) => value.toI32()));
|
|
682
|
+
}
|
|
683
|
+
toI64Matrix(): bigint[][] {
|
|
684
|
+
return this.toMatrix().map((row) => row.map((value) => value.toI64()));
|
|
685
|
+
}
|
|
686
|
+
displayData(): string {
|
|
687
|
+
return String(this.data);
|
|
688
|
+
}
|
|
689
|
+
displayKind(): string {
|
|
690
|
+
return ValueKind[this.kind] ?? String(this.kind);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** A stored `Value` as the scalar a generated entity getter would return. */
|
|
695
|
+
function valueToNative(value: Value): unknown {
|
|
696
|
+
switch (value.kind) {
|
|
697
|
+
case ValueKind.NULL:
|
|
698
|
+
return null;
|
|
699
|
+
case ValueKind.BYTES:
|
|
700
|
+
return value.toBytes();
|
|
701
|
+
case ValueKind.BIGINT:
|
|
702
|
+
return value.toBigInt();
|
|
703
|
+
case ValueKind.BIGDECIMAL:
|
|
704
|
+
return value.toBigDecimal();
|
|
705
|
+
case ValueKind.INT8:
|
|
706
|
+
case ValueKind.TIMESTAMP:
|
|
707
|
+
return value.toI64();
|
|
708
|
+
case ValueKind.ARRAY:
|
|
709
|
+
return value.toArray().map(valueToNative);
|
|
710
|
+
default:
|
|
711
|
+
return value.data;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function nativeToValue(native: unknown): Value {
|
|
716
|
+
if (native === null || native === undefined) return Value.fromNull();
|
|
717
|
+
if (native instanceof Value) return native;
|
|
718
|
+
if (native instanceof Bytes || native instanceof ByteArray) {
|
|
719
|
+
return Value.fromBytes(Bytes.fromByteArray(native));
|
|
720
|
+
}
|
|
721
|
+
if (native instanceof BigInt_) return Value.fromBigInt(native);
|
|
722
|
+
if (native instanceof BigDecimal) return Value.fromBigDecimal(native);
|
|
723
|
+
if (typeof native === "string") return Value.fromString(native);
|
|
724
|
+
if (typeof native === "boolean") return Value.fromBoolean(native);
|
|
725
|
+
if (typeof native === "bigint") return Value.fromI64(native);
|
|
726
|
+
if (typeof native === "number") return Value.fromI32(native);
|
|
727
|
+
if (Array.isArray(native)) return Value.fromArray(native.map(nativeToValue));
|
|
728
|
+
return Value.fromString(String(native));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export class Entity extends TypedMap<string, Value> {
|
|
732
|
+
setString(key: string, value: string): void {
|
|
733
|
+
this.set(key, Value.fromString(value));
|
|
734
|
+
}
|
|
735
|
+
setI32(key: string, value: number): void {
|
|
736
|
+
this.set(key, Value.fromI32(value));
|
|
737
|
+
}
|
|
738
|
+
setBigInt(key: string, value: BigInt_): void {
|
|
739
|
+
this.set(key, Value.fromBigInt(value));
|
|
740
|
+
}
|
|
741
|
+
setBytes(key: string, value: Bytes): void {
|
|
742
|
+
this.set(key, Value.fromBytes(value));
|
|
743
|
+
}
|
|
744
|
+
setBoolean(key: string, value: boolean): void {
|
|
745
|
+
this.set(key, Value.fromBoolean(value));
|
|
746
|
+
}
|
|
747
|
+
setBigDecimal(key: string, value: BigDecimal): void {
|
|
748
|
+
this.set(key, Value.fromBigDecimal(value));
|
|
749
|
+
}
|
|
750
|
+
getString(key: string): string {
|
|
751
|
+
return this.mustGet(key).toString();
|
|
752
|
+
}
|
|
753
|
+
getI32(key: string): number {
|
|
754
|
+
return this.mustGet(key).toI32();
|
|
755
|
+
}
|
|
756
|
+
getBigInt(key: string): BigInt_ {
|
|
757
|
+
return this.mustGet(key).toBigInt();
|
|
758
|
+
}
|
|
759
|
+
getBytes(key: string): Bytes {
|
|
760
|
+
return this.mustGet(key).toBytes();
|
|
761
|
+
}
|
|
762
|
+
getBoolean(key: string): boolean {
|
|
763
|
+
return this.mustGet(key).toBoolean();
|
|
764
|
+
}
|
|
765
|
+
getBigDecimal(key: string): BigDecimal {
|
|
766
|
+
return this.mustGet(key).toBigDecimal();
|
|
767
|
+
}
|
|
768
|
+
unset(key: string): void {
|
|
769
|
+
this.set(key, Value.fromNull());
|
|
770
|
+
}
|
|
771
|
+
merge(sources: Entity[]): this {
|
|
772
|
+
for (const entity of sources) {
|
|
773
|
+
for (const entry of entity.entries) {
|
|
774
|
+
this.set(entry.key, entry.value);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return this;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Tail of the entity prototype chain: instance -> generated prototype ->
|
|
783
|
+
* Entity -> TypedMap -> here.
|
|
784
|
+
*
|
|
785
|
+
* `graph codegen` emits `changetype<Pair | null>(store.get(...))`, and
|
|
786
|
+
* `changetype` erases its type argument, so a loaded entity reaches the mapping
|
|
787
|
+
* without the generated prototype that carries `pair.token0`. Falling through to
|
|
788
|
+
* the stored field — typed by the `Value` kind, which is what the generated
|
|
789
|
+
* getter would have returned — is what makes that code work here. A name the
|
|
790
|
+
* entity doesn't hold is still refused.
|
|
791
|
+
*/
|
|
792
|
+
/**
|
|
793
|
+
* The entity type a loaded row came from. `changetype` erased the generated
|
|
794
|
+
* prototype, and with it the `save()` that knows which table to write back to,
|
|
795
|
+
* so the type is remembered on the instance instead.
|
|
796
|
+
*/
|
|
797
|
+
const ENTITY_TYPE = Symbol("envio.entityType");
|
|
798
|
+
|
|
799
|
+
const entityTail = new Proxy(Object.create(null), {
|
|
800
|
+
get(_target, prop, receiver) {
|
|
801
|
+
if (typeof prop === "symbol" || PROTOTYPE_PASSTHROUGH.has(prop as string)) {
|
|
802
|
+
return undefined;
|
|
803
|
+
}
|
|
804
|
+
if (prop === "save" && receiver instanceof Entity) {
|
|
805
|
+
const entityType = (receiver as any)[ENTITY_TYPE];
|
|
806
|
+
if (typeof entityType === "string") {
|
|
807
|
+
return () => {
|
|
808
|
+
const id = receiver.get("id");
|
|
809
|
+
if (id === null) {
|
|
810
|
+
throw new Error(`Cannot save ${entityType} entity without an ID`);
|
|
811
|
+
}
|
|
812
|
+
storeImpl.set(
|
|
813
|
+
entityType,
|
|
814
|
+
id.kind === ValueKind.BYTES ? id.toBytes().toHexString() : id.toString(),
|
|
815
|
+
receiver,
|
|
816
|
+
);
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const stored = receiver instanceof Entity ? receiver.get(prop as string) : null;
|
|
821
|
+
if (stored !== null) {
|
|
822
|
+
return valueToNative(stored);
|
|
823
|
+
}
|
|
824
|
+
// graph-node's store returns every column; envio's returns what the mapping
|
|
825
|
+
// wrote, so a field nothing has set is simply absent. It is still a field,
|
|
826
|
+
// and reading it is a null check — ENS opens with one.
|
|
827
|
+
const entityType = (receiver as any)?.[ENTITY_TYPE];
|
|
828
|
+
if (typeof entityType === "string") {
|
|
829
|
+
const declared = currentScope().schema.entityFields[entityType];
|
|
830
|
+
if (declared?.includes(prop as string)) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// Only an entity read is a schema field access. A plain TypedMap — what
|
|
835
|
+
// JSONValue.toObject() hands back — has to keep ordinary JS semantics, or
|
|
836
|
+
// every host probe for `toJSON` or `then` raises from unrelated code.
|
|
837
|
+
if (!(receiver instanceof Entity)) {
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
throw unknown(`the entity member ${String(prop)}`, "a mapping handler");
|
|
841
|
+
},
|
|
842
|
+
set(_target, prop, value, receiver) {
|
|
843
|
+
// `entries` is TypedMap's own storage, and its class-field initializer is a
|
|
844
|
+
// plain assignment — which walks the prototype chain and lands here before
|
|
845
|
+
// the instance has the property. Routing it into `set()` would leave the
|
|
846
|
+
// map with nowhere to store anything.
|
|
847
|
+
if (typeof prop === "symbol" || prop === "entries" || !(receiver instanceof Entity)) {
|
|
848
|
+
return Reflect.defineProperty(receiver as object, prop, {
|
|
849
|
+
value,
|
|
850
|
+
writable: true,
|
|
851
|
+
enumerable: true,
|
|
852
|
+
configurable: true,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
receiver.set(prop as string, nativeToValue(value));
|
|
856
|
+
return true;
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
Object.setPrototypeOf(TypedMap.prototype, entityTail);
|
|
861
|
+
|
|
862
|
+
// ---------------------------------------------------------------------------
|
|
863
|
+
// store
|
|
864
|
+
// ---------------------------------------------------------------------------
|
|
865
|
+
|
|
866
|
+
/** graph-ts `Value`s -> the plain scalars envio stores. */
|
|
867
|
+
function toRow(entityType: string, entity: Entity): Record<string, unknown> {
|
|
868
|
+
const { schema } = currentScope();
|
|
869
|
+
const timestampFields = new Set(schema.timestampFields[entityType] ?? []);
|
|
870
|
+
// graph-ts holds a relation as the related entity's id under the field's own
|
|
871
|
+
// name; envio's column for it is `<field>_id`.
|
|
872
|
+
const refFields = new Set(schema.entityRefFields[entityType] ?? []);
|
|
873
|
+
const row: Record<string, unknown> = {};
|
|
874
|
+
for (const entry of entity.entries) {
|
|
875
|
+
const column = refFields.has(entry.key) ? `${entry.key}_id` : entry.key;
|
|
876
|
+
row[column] = timestampFields.has(entry.key)
|
|
877
|
+
? new Date(Number(entry.value.toTimestamp() / 1000n))
|
|
878
|
+
: fromValue(entry.value);
|
|
879
|
+
}
|
|
880
|
+
return row;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function fromValue(value: Value): unknown {
|
|
884
|
+
switch (value.kind) {
|
|
885
|
+
case ValueKind.NULL:
|
|
886
|
+
return null;
|
|
887
|
+
case ValueKind.BYTES:
|
|
888
|
+
return value.toBytes().toHexString();
|
|
889
|
+
case ValueKind.BIGINT:
|
|
890
|
+
return value.toBigInt().valueOf();
|
|
891
|
+
case ValueKind.INT8:
|
|
892
|
+
case ValueKind.TIMESTAMP:
|
|
893
|
+
return BigInt(value.data as any);
|
|
894
|
+
case ValueKind.BIGDECIMAL:
|
|
895
|
+
return value.toBigDecimal().value;
|
|
896
|
+
case ValueKind.ARRAY:
|
|
897
|
+
return value.toArray().map(fromValue);
|
|
898
|
+
default:
|
|
899
|
+
return value.data;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/** envio rows -> graph-ts `Value`s. */
|
|
904
|
+
function toEntity(entityType: string, row: Record<string, unknown> | undefined | null): Entity | null {
|
|
905
|
+
if (row === undefined || row === null) {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
const { schema } = currentScope();
|
|
909
|
+
const timestampFields = new Set(schema.timestampFields[entityType] ?? []);
|
|
910
|
+
const refFields = new Set(schema.entityRefFields[entityType] ?? []);
|
|
911
|
+
const declared = schema.entityFieldTypes[entityType] ?? {};
|
|
912
|
+
const entity = new Entity();
|
|
913
|
+
Object.defineProperty(entity, ENTITY_TYPE, { value: entityType });
|
|
914
|
+
for (const [column, value] of Object.entries(row)) {
|
|
915
|
+
const key =
|
|
916
|
+
column.endsWith("_id") && refFields.has(column.slice(0, -3))
|
|
917
|
+
? column.slice(0, -3)
|
|
918
|
+
: column;
|
|
919
|
+
entity.set(
|
|
920
|
+
key,
|
|
921
|
+
timestampFields.has(key)
|
|
922
|
+
? Value.fromTimestamp(BigInt((value as Date).getTime()) * 1000n)
|
|
923
|
+
: toValue(value, declaredKind(schema, declared[key])),
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return entity;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* What a mapping expects a field to read back as. A relation carries the id of
|
|
931
|
+
* the entity it points at, so its type is that entity's own `id` type — which is
|
|
932
|
+
* how `Token.load(pool.token0)` gets `Bytes` rather than a string.
|
|
933
|
+
*/
|
|
934
|
+
function declaredKind(
|
|
935
|
+
schema: { entityFieldTypes: Record<string, Record<string, { kind: string; target?: string; list?: boolean }>> },
|
|
936
|
+
field: { kind: string; target?: string; list?: boolean } | undefined,
|
|
937
|
+
): string | undefined {
|
|
938
|
+
if (!field) return undefined;
|
|
939
|
+
// For a list, this is the element's kind — a list of entity ids carries the
|
|
940
|
+
// ids themselves, so each one is the target entity's id type.
|
|
941
|
+
if (field.kind !== "Entity") return field.kind;
|
|
942
|
+
const target = field.target ? schema.entityFieldTypes[field.target] : undefined;
|
|
943
|
+
return target?.id?.kind;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function toValue(value: unknown, kind?: string): Value {
|
|
947
|
+
if (value === null || value === undefined) return Value.fromNull();
|
|
948
|
+
if (Array.isArray(value)) return Value.fromArray(value.map((item) => toValue(item, kind)));
|
|
949
|
+
switch (kind) {
|
|
950
|
+
case "Bytes":
|
|
951
|
+
return typeof value === "string" ? Value.fromBytes(Bytes.fromHexString(value)) : Value.fromNull();
|
|
952
|
+
case "BigInt":
|
|
953
|
+
case "Int8":
|
|
954
|
+
return Value.fromBigInt(BigInt_.fromString(String(value)));
|
|
955
|
+
case "BigDecimal":
|
|
956
|
+
return Value.fromBigDecimal(
|
|
957
|
+
value instanceof BigNumber ? new BigDecimal(value) : BigDecimal.fromString(String(value)),
|
|
958
|
+
);
|
|
959
|
+
case "Int":
|
|
960
|
+
return Value.fromI32(Number(value));
|
|
961
|
+
case "Boolean":
|
|
962
|
+
return Value.fromBoolean(Boolean(value));
|
|
963
|
+
default:
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
if (typeof value === "string") return Value.fromString(value);
|
|
967
|
+
if (typeof value === "boolean") return Value.fromBoolean(value);
|
|
968
|
+
if (typeof value === "number") return Value.fromI32(value);
|
|
969
|
+
if (typeof value === "bigint") return Value.fromBigInt(new BigInt_(value));
|
|
970
|
+
if (Array.isArray(value)) return Value.fromArray(value.map((item) => toValue(item)));
|
|
971
|
+
if (value instanceof BigNumber) return Value.fromBigDecimal(new BigDecimal(value));
|
|
972
|
+
return Value.fromString(String(value));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function entityContext(entityType: string) {
|
|
976
|
+
const { context } = currentScope();
|
|
977
|
+
const table = context[entityType];
|
|
978
|
+
if (!table) {
|
|
979
|
+
throw unknown(`the entity ${entityType}`, "a mapping handler");
|
|
980
|
+
}
|
|
981
|
+
return table;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const storeImpl = {
|
|
985
|
+
get(entityType: string, id: string): Entity | null {
|
|
986
|
+
const { mode } = currentScope();
|
|
987
|
+
// Nothing has been written at fetch time, so the register pass reads null
|
|
988
|
+
// rather than a value that would differ between the two passes.
|
|
989
|
+
if (mode === "register") return null;
|
|
990
|
+
return toEntity(entityType, entityContext(entityType).getSync(id));
|
|
991
|
+
},
|
|
992
|
+
get_in_block(entityType: string, id: string): Entity | null {
|
|
993
|
+
const { mode } = currentScope();
|
|
994
|
+
if (mode === "register") return null;
|
|
995
|
+
return toEntity(entityType, entityContext(entityType).getInBlockSync(id));
|
|
996
|
+
},
|
|
997
|
+
set(entityType: string, id: string, data: Entity): void {
|
|
998
|
+
const { mode } = currentScope();
|
|
999
|
+
if (mode === "register") return;
|
|
1000
|
+
const row = toRow(entityType, data);
|
|
1001
|
+
row.id = id;
|
|
1002
|
+
entityContext(entityType).set(row);
|
|
1003
|
+
},
|
|
1004
|
+
remove(entityType: string, id: string): void {
|
|
1005
|
+
const { mode } = currentScope();
|
|
1006
|
+
if (mode === "register") return;
|
|
1007
|
+
entityContext(entityType).deleteUnsafe(id);
|
|
1008
|
+
},
|
|
1009
|
+
loadRelated(entityType: string, id: string, field: string): Entity[] {
|
|
1010
|
+
const { mode } = currentScope();
|
|
1011
|
+
if (mode === "register") return [];
|
|
1012
|
+
const rows = entityContext(entityType).getWhereSync({ [field]: { _eq: id } });
|
|
1013
|
+
return rows.map((row: any) => toEntity(entityType, row) as Entity);
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
export const store = strictNamespace("store", storeImpl);
|
|
1018
|
+
|
|
1019
|
+
// ---------------------------------------------------------------------------
|
|
1020
|
+
// ethereum
|
|
1021
|
+
// ---------------------------------------------------------------------------
|
|
1022
|
+
|
|
1023
|
+
export class EthereumValue extends Value {}
|
|
1024
|
+
|
|
1025
|
+
class SmartContractCall {
|
|
1026
|
+
constructor(
|
|
1027
|
+
public contractName: string,
|
|
1028
|
+
public contractAddress: Address,
|
|
1029
|
+
public functionName: string,
|
|
1030
|
+
public functionSignature: string,
|
|
1031
|
+
public functionParams: EthereumValue[],
|
|
1032
|
+
) {}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
export class CallResult<T> {
|
|
1036
|
+
// Generated bindings signal a revert with a bare `new ethereum.CallResult()`.
|
|
1037
|
+
constructor(
|
|
1038
|
+
public reverted: boolean = true,
|
|
1039
|
+
private _value: T | null = null,
|
|
1040
|
+
) {}
|
|
1041
|
+
get value(): T {
|
|
1042
|
+
if (this.reverted) {
|
|
1043
|
+
throw new Error("accessed value of a reverted call, please check the `reverted` field");
|
|
1044
|
+
}
|
|
1045
|
+
return this._value as T;
|
|
1046
|
+
}
|
|
1047
|
+
static fromValue<T>(value: T): CallResult<T> {
|
|
1048
|
+
return new CallResult(false, value);
|
|
1049
|
+
}
|
|
1050
|
+
static fromNullable<T>(value: T | null): CallResult<T> {
|
|
1051
|
+
return new CallResult(value === null, value);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
class SmartContract {
|
|
1056
|
+
constructor(
|
|
1057
|
+
public _name: string,
|
|
1058
|
+
public _address: Address,
|
|
1059
|
+
) {}
|
|
1060
|
+
|
|
1061
|
+
call(_returnTypes: string, functionSignature: string, params: EthereumValue[]): EthereumValue[] {
|
|
1062
|
+
return callContract(this, functionSignature, params, false) as EthereumValue[];
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
tryCall(
|
|
1066
|
+
_returnTypes: string,
|
|
1067
|
+
functionSignature: string,
|
|
1068
|
+
params: EthereumValue[],
|
|
1069
|
+
): CallResult<EthereumValue[]> {
|
|
1070
|
+
return callContract(this, functionSignature, params, true) as CallResult<EthereumValue[]>;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Contract calls go through the shim's call hook, installed by the runtime so
|
|
1076
|
+
* this module stays free of envio imports. A transport failure is not a revert:
|
|
1077
|
+
* it throws as the handler error (which envio retries) so a flaky RPC never
|
|
1078
|
+
* fabricates `{reverted: true}` data.
|
|
1079
|
+
*/
|
|
1080
|
+
let callHook:
|
|
1081
|
+
| ((call: SmartContractCall) => { reverted: boolean; value: unknown[] | null })
|
|
1082
|
+
| null = null;
|
|
1083
|
+
|
|
1084
|
+
export function installCallHook(hook: typeof callHook) {
|
|
1085
|
+
callHook = hook;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function callContract(
|
|
1089
|
+
contract: SmartContract,
|
|
1090
|
+
functionSignature: string,
|
|
1091
|
+
params: EthereumValue[],
|
|
1092
|
+
isTry: boolean,
|
|
1093
|
+
) {
|
|
1094
|
+
if (!callHook) {
|
|
1095
|
+
throw unsupported(
|
|
1096
|
+
"contract calls without a configured RPC endpoint",
|
|
1097
|
+
`${contract._name}.${functionSignature}`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const call = new SmartContractCall(
|
|
1101
|
+
contract._name,
|
|
1102
|
+
contract._address,
|
|
1103
|
+
functionSignature.split("(")[0],
|
|
1104
|
+
functionSignature,
|
|
1105
|
+
params,
|
|
1106
|
+
);
|
|
1107
|
+
// A suspend thrown by the underlying effect must escape `try_` too: it isn't
|
|
1108
|
+
// a revert, it's "not resolved yet".
|
|
1109
|
+
const result = callHook(call);
|
|
1110
|
+
if (isTry) {
|
|
1111
|
+
return result.reverted
|
|
1112
|
+
? new CallResult(true, null)
|
|
1113
|
+
: CallResult.fromValue((result.value ?? []).map(toEthereumValue));
|
|
1114
|
+
}
|
|
1115
|
+
if (result.reverted) {
|
|
1116
|
+
throw new Error(`Call to ${contract._name}.${functionSignature} reverted`);
|
|
1117
|
+
}
|
|
1118
|
+
return (result.value ?? []).map(toEthereumValue);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function toEthereumValue(value: unknown): EthereumValue {
|
|
1122
|
+
return toValue(value) as EthereumValue;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/** A graph-ts value as the plain JS an ABI encoder takes. */
|
|
1126
|
+
export function valueToJs(value: Value): unknown {
|
|
1127
|
+
switch (value.kind) {
|
|
1128
|
+
case ValueKind.BYTES:
|
|
1129
|
+
return value.toBytes().toHexString();
|
|
1130
|
+
case ValueKind.BIGINT:
|
|
1131
|
+
return value.toBigInt().valueOf();
|
|
1132
|
+
case ValueKind.ARRAY:
|
|
1133
|
+
return value.toArray().map(valueToJs);
|
|
1134
|
+
case ValueKind.NULL:
|
|
1135
|
+
return null;
|
|
1136
|
+
default:
|
|
1137
|
+
return value.data;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
class EthereumBlock {
|
|
1142
|
+
constructor(
|
|
1143
|
+
public number: BigInt_,
|
|
1144
|
+
private _timestamp: () => BigInt_,
|
|
1145
|
+
) {}
|
|
1146
|
+
get timestamp(): BigInt_ {
|
|
1147
|
+
return this._timestamp();
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
class EthereumTransaction {
|
|
1152
|
+
constructor(private _fields: Record<string, unknown>) {
|
|
1153
|
+
Object.assign(this, _fields);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/** graph-ts' own tag for an ABI value's Solidity type. */
|
|
1158
|
+
export enum EthereumValueKind {
|
|
1159
|
+
ADDRESS = 0,
|
|
1160
|
+
FIXED_BYTES = 1,
|
|
1161
|
+
BYTES = 2,
|
|
1162
|
+
INT = 3,
|
|
1163
|
+
UINT = 4,
|
|
1164
|
+
BOOL = 5,
|
|
1165
|
+
STRING = 6,
|
|
1166
|
+
FIXED_ARRAY = 7,
|
|
1167
|
+
ARRAY = 8,
|
|
1168
|
+
TUPLE = 9,
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
class EthereumEventParam {
|
|
1172
|
+
constructor(
|
|
1173
|
+
public name: string,
|
|
1174
|
+
public value: EthereumValue,
|
|
1175
|
+
) {}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Only reachable through `event.receipt.logs`, which the runtime refuses on
|
|
1180
|
+
* access — envio's receipt selection carries the scalars, not the log list.
|
|
1181
|
+
* Declared so a mapping that names the type still compiles the way it does
|
|
1182
|
+
* against graph-ts.
|
|
1183
|
+
*/
|
|
1184
|
+
class EthereumLog {
|
|
1185
|
+
constructor(
|
|
1186
|
+
public address: Address,
|
|
1187
|
+
public topics: Bytes[],
|
|
1188
|
+
public data: Bytes,
|
|
1189
|
+
public blockHash: Bytes,
|
|
1190
|
+
public blockNumber: Bytes,
|
|
1191
|
+
public transactionHash: Bytes,
|
|
1192
|
+
public transactionIndex: BigInt_,
|
|
1193
|
+
public logIndex: BigInt_,
|
|
1194
|
+
public transactionLogIndex: BigInt_,
|
|
1195
|
+
public logType: string,
|
|
1196
|
+
public removed: { inner: boolean } | null,
|
|
1197
|
+
) {}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
class EthereumTransactionReceipt {
|
|
1201
|
+
constructor(
|
|
1202
|
+
public transactionHash: Bytes,
|
|
1203
|
+
public transactionIndex: BigInt_,
|
|
1204
|
+
public blockHash: Bytes,
|
|
1205
|
+
public blockNumber: BigInt_,
|
|
1206
|
+
public cumulativeGasUsed: BigInt_,
|
|
1207
|
+
public gasUsed: BigInt_,
|
|
1208
|
+
public contractAddress: Address,
|
|
1209
|
+
public logs: EthereumLog[],
|
|
1210
|
+
public status: BigInt_,
|
|
1211
|
+
public root: Bytes,
|
|
1212
|
+
public logsBloom: Bytes,
|
|
1213
|
+
) {}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
class EthereumCall {
|
|
1217
|
+
constructor(
|
|
1218
|
+
public to: Address,
|
|
1219
|
+
public from: Address,
|
|
1220
|
+
public block: EthereumBlock,
|
|
1221
|
+
public transaction: EthereumTransaction,
|
|
1222
|
+
public inputValues: EthereumEventParam[],
|
|
1223
|
+
public outputValues: EthereumEventParam[],
|
|
1224
|
+
) {}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
class EthereumEvent {
|
|
1228
|
+
constructor(
|
|
1229
|
+
public address: Address,
|
|
1230
|
+
public logIndex: BigInt_,
|
|
1231
|
+
public transactionLogIndex: never,
|
|
1232
|
+
public block: EthereumBlock,
|
|
1233
|
+
public transaction: EthereumTransaction,
|
|
1234
|
+
public parameters: unknown[],
|
|
1235
|
+
) {}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const ethereumImpl = {
|
|
1239
|
+
Value: EthereumValue,
|
|
1240
|
+
ValueKind: EthereumValueKind,
|
|
1241
|
+
SmartContract,
|
|
1242
|
+
SmartContractCall,
|
|
1243
|
+
CallResult,
|
|
1244
|
+
Block: EthereumBlock,
|
|
1245
|
+
Transaction: EthereumTransaction,
|
|
1246
|
+
TransactionReceipt: EthereumTransactionReceipt,
|
|
1247
|
+
Log: EthereumLog,
|
|
1248
|
+
Event: EthereumEvent,
|
|
1249
|
+
EventParam: EthereumEventParam,
|
|
1250
|
+
Call: EthereumCall,
|
|
1251
|
+
Tuple: Array,
|
|
1252
|
+
call(call: SmartContractCall): Value[] | null {
|
|
1253
|
+
const contract = new SmartContract(call.contractName, call.contractAddress);
|
|
1254
|
+
const result = callContract(contract, call.functionSignature, call.functionParams, true);
|
|
1255
|
+
return (result as CallResult<EthereumValue[]>).reverted
|
|
1256
|
+
? null
|
|
1257
|
+
: (result as CallResult<EthereumValue[]>).value;
|
|
1258
|
+
},
|
|
1259
|
+
decode(types: string, data: Bytes): EthereumValue | null {
|
|
1260
|
+
try {
|
|
1261
|
+
const decoded = decodeAbiParameters(
|
|
1262
|
+
[{ type: types } as any],
|
|
1263
|
+
data.toHexString() as `0x${string}`,
|
|
1264
|
+
);
|
|
1265
|
+
return toEthereumValue(decoded[0]);
|
|
1266
|
+
} catch {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
encode(_value: EthereumValue): Bytes | null {
|
|
1271
|
+
throw unsupported("ethereum.encode", "a mapping handler");
|
|
1272
|
+
},
|
|
1273
|
+
getBalance(address: Address): BigInt_ {
|
|
1274
|
+
return BigInt_.fromString(hostsOrThrow().getBalance(address.toHexString()));
|
|
1275
|
+
},
|
|
1276
|
+
// graph-ts declares a Wrapped, which carries its value on `inner` rather than
|
|
1277
|
+
// on `value` the way a CallResult does.
|
|
1278
|
+
hasCode(address: Address): { inner: boolean } {
|
|
1279
|
+
return { inner: hostsOrThrow().hasCode(address.toHexString()) };
|
|
1280
|
+
},
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
export const ethereum = strictNamespace("ethereum", ethereumImpl);
|
|
1284
|
+
|
|
1285
|
+
// ---------------------------------------------------------------------------
|
|
1286
|
+
// dataSource + templates
|
|
1287
|
+
// ---------------------------------------------------------------------------
|
|
1288
|
+
|
|
1289
|
+
let registerHook: ((templateName: string, address: string) => void) | null = null;
|
|
1290
|
+
|
|
1291
|
+
export function installRegisterHook(hook: typeof registerHook) {
|
|
1292
|
+
registerHook = hook;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
export class DataSourceTemplate {
|
|
1296
|
+
static create(name: string, params: string[]): void {
|
|
1297
|
+
const { mode, registered } = currentScope();
|
|
1298
|
+
const address = params[0];
|
|
1299
|
+
if (!address) {
|
|
1300
|
+
throw new Error(`${name}.create() was called without an address parameter`);
|
|
1301
|
+
}
|
|
1302
|
+
const key = `${name}:${address.toLowerCase()}`;
|
|
1303
|
+
// Replays rerun the mapping from the top, so registration is deduped
|
|
1304
|
+
// rather than repeated.
|
|
1305
|
+
if (mode !== "register" || registered.has(key)) return;
|
|
1306
|
+
registered.add(key);
|
|
1307
|
+
registerHook?.(name, address);
|
|
1308
|
+
}
|
|
1309
|
+
static createWithContext(name: string, params: string[], context: DataSourceContext): void {
|
|
1310
|
+
// The created data source would have to carry the context to every event it
|
|
1311
|
+
// ever sees, and there is nowhere to keep it — `dataSource.context()` in the
|
|
1312
|
+
// template's handlers would quietly come back empty.
|
|
1313
|
+
if (context.entries.length > 0) {
|
|
1314
|
+
throw unsupported(
|
|
1315
|
+
`${name}.createWithContext() with a non-empty context`,
|
|
1316
|
+
"a mapping handler",
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
DataSourceTemplate.create(name, params);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
export class DataSourceContext extends Entity {}
|
|
1324
|
+
|
|
1325
|
+
/** A manifest `context` entry, typed the way graph-node types it. */
|
|
1326
|
+
function contextValue(kind: string, data: string, key: string): Value {
|
|
1327
|
+
switch (kind) {
|
|
1328
|
+
case "Bool":
|
|
1329
|
+
case "Boolean":
|
|
1330
|
+
return Value.fromBoolean(data === "true");
|
|
1331
|
+
case "String":
|
|
1332
|
+
return Value.fromString(data);
|
|
1333
|
+
case "Int":
|
|
1334
|
+
return Value.fromI32(Number(data));
|
|
1335
|
+
case "Int8":
|
|
1336
|
+
return Value.fromI64(BigInt(data));
|
|
1337
|
+
case "BigInt":
|
|
1338
|
+
return Value.fromBigInt(BigInt_.fromString(data));
|
|
1339
|
+
case "BigDecimal":
|
|
1340
|
+
return Value.fromBigDecimal(BigDecimal.fromString(data));
|
|
1341
|
+
case "Bytes":
|
|
1342
|
+
return Value.fromBytes(Bytes.fromHexString(data));
|
|
1343
|
+
default:
|
|
1344
|
+
throw unknown(`the context value type ${kind} on "${key}"`, "a mapping handler");
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
const dataSourceImpl = {
|
|
1349
|
+
address(): Address {
|
|
1350
|
+
return Address.fromString(currentScope().dataSource.address);
|
|
1351
|
+
},
|
|
1352
|
+
network(): string {
|
|
1353
|
+
return currentScope().dataSource.network;
|
|
1354
|
+
},
|
|
1355
|
+
context(): DataSourceContext {
|
|
1356
|
+
const context = new DataSourceContext();
|
|
1357
|
+
for (const [key, entry] of Object.entries(currentScope().dataSource.context ?? {})) {
|
|
1358
|
+
context.set(key, contextValue(entry.type, entry.data, key));
|
|
1359
|
+
}
|
|
1360
|
+
return context;
|
|
1361
|
+
},
|
|
1362
|
+
// The address a template was created with, which is the only string param
|
|
1363
|
+
// an EVM template ever carries.
|
|
1364
|
+
stringParam(): string {
|
|
1365
|
+
return currentScope().dataSource.address;
|
|
1366
|
+
},
|
|
1367
|
+
create: DataSourceTemplate.create,
|
|
1368
|
+
createWithContext: DataSourceTemplate.createWithContext,
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
export const dataSource = strictNamespace("dataSource", dataSourceImpl);
|
|
1372
|
+
|
|
1373
|
+
// ---------------------------------------------------------------------------
|
|
1374
|
+
// log / crypto / json
|
|
1375
|
+
// ---------------------------------------------------------------------------
|
|
1376
|
+
|
|
1377
|
+
function interpolate(message: string, args: string[]): string {
|
|
1378
|
+
let index = 0;
|
|
1379
|
+
return message.replace(/\{\}/g, () => args[index++] ?? "{}");
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
export enum LogLevel {
|
|
1383
|
+
CRITICAL = 0,
|
|
1384
|
+
ERROR = 1,
|
|
1385
|
+
WARNING = 2,
|
|
1386
|
+
INFO = 3,
|
|
1387
|
+
DEBUG = 4,
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
const logImpl = {
|
|
1391
|
+
Level: LogLevel,
|
|
1392
|
+
log(level: LogLevel, msg: string) {
|
|
1393
|
+
switch (level) {
|
|
1394
|
+
case LogLevel.CRITICAL:
|
|
1395
|
+
return logImpl.critical(msg);
|
|
1396
|
+
case LogLevel.ERROR:
|
|
1397
|
+
return logImpl.error(msg);
|
|
1398
|
+
case LogLevel.WARNING:
|
|
1399
|
+
return logImpl.warning(msg);
|
|
1400
|
+
case LogLevel.DEBUG:
|
|
1401
|
+
return logImpl.debug(msg);
|
|
1402
|
+
default:
|
|
1403
|
+
return logImpl.info(msg);
|
|
1404
|
+
}
|
|
1405
|
+
},
|
|
1406
|
+
debug(message: string, args: string[] = []) {
|
|
1407
|
+
currentScope().context.log?.debug(interpolate(message, args));
|
|
1408
|
+
},
|
|
1409
|
+
info(message: string, args: string[] = []) {
|
|
1410
|
+
currentScope().context.log?.info(interpolate(message, args));
|
|
1411
|
+
},
|
|
1412
|
+
warning(message: string, args: string[] = []) {
|
|
1413
|
+
currentScope().context.log?.warn(interpolate(message, args));
|
|
1414
|
+
},
|
|
1415
|
+
error(message: string, args: string[] = []) {
|
|
1416
|
+
currentScope().context.log?.error(interpolate(message, args));
|
|
1417
|
+
},
|
|
1418
|
+
// graph-node halts the subgraph on critical.
|
|
1419
|
+
critical(message: string, args: string[] = []): never {
|
|
1420
|
+
throw new Error(interpolate(message, args));
|
|
1421
|
+
},
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
export const log = strictNamespace("log", logImpl);
|
|
1425
|
+
|
|
1426
|
+
const cryptoImpl = {
|
|
1427
|
+
keccak256(input: ByteArray): ByteArray {
|
|
1428
|
+
return ByteArray.fromHexString(viemKeccak256(input as Uint8Array));
|
|
1429
|
+
},
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
export const crypto = strictNamespace("crypto", cryptoImpl);
|
|
1433
|
+
|
|
1434
|
+
export enum JSONValueKind {
|
|
1435
|
+
NULL = 0,
|
|
1436
|
+
BOOL = 1,
|
|
1437
|
+
NUMBER = 2,
|
|
1438
|
+
STRING = 3,
|
|
1439
|
+
ARRAY = 4,
|
|
1440
|
+
OBJECT = 5,
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
export class JSONValue {
|
|
1444
|
+
constructor(
|
|
1445
|
+
public kind: JSONValueKind,
|
|
1446
|
+
public data: any,
|
|
1447
|
+
) {}
|
|
1448
|
+
toString(): string {
|
|
1449
|
+
return String(this.data);
|
|
1450
|
+
}
|
|
1451
|
+
toBool(): boolean {
|
|
1452
|
+
return Boolean(this.data);
|
|
1453
|
+
}
|
|
1454
|
+
isNull(): boolean {
|
|
1455
|
+
return this.kind === JSONValueKind.NULL;
|
|
1456
|
+
}
|
|
1457
|
+
toU64(): bigint {
|
|
1458
|
+
return this.toI64();
|
|
1459
|
+
}
|
|
1460
|
+
toI64(): bigint {
|
|
1461
|
+
return BigInt(this.data);
|
|
1462
|
+
}
|
|
1463
|
+
toF64(): number {
|
|
1464
|
+
return Number(this.data);
|
|
1465
|
+
}
|
|
1466
|
+
toBigInt(): BigInt_ {
|
|
1467
|
+
return BigInt_.fromString(String(this.data));
|
|
1468
|
+
}
|
|
1469
|
+
toArray(): JSONValue[] {
|
|
1470
|
+
return (this.data as unknown[]).map(fromJson);
|
|
1471
|
+
}
|
|
1472
|
+
toObject(): TypedMap<string, JSONValue> {
|
|
1473
|
+
const map = new TypedMap<string, JSONValue>();
|
|
1474
|
+
for (const [key, value] of Object.entries(this.data as object)) {
|
|
1475
|
+
map.set(key, fromJson(value));
|
|
1476
|
+
}
|
|
1477
|
+
return map;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function fromJson(value: unknown): JSONValue {
|
|
1482
|
+
if (value === null) return new JSONValue(JSONValueKind.NULL, null);
|
|
1483
|
+
if (typeof value === "boolean") return new JSONValue(JSONValueKind.BOOL, value);
|
|
1484
|
+
if (typeof value === "number") return new JSONValue(JSONValueKind.NUMBER, value);
|
|
1485
|
+
if (typeof value === "string") return new JSONValue(JSONValueKind.STRING, value);
|
|
1486
|
+
if (Array.isArray(value)) return new JSONValue(JSONValueKind.ARRAY, value);
|
|
1487
|
+
return new JSONValue(JSONValueKind.OBJECT, value);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
const jsonImpl = {
|
|
1491
|
+
fromBytes(bytes: Bytes): JSONValue {
|
|
1492
|
+
return fromJson(JSON.parse(new TextDecoder().decode(bytes)));
|
|
1493
|
+
},
|
|
1494
|
+
fromString(input: string): JSONValue {
|
|
1495
|
+
return fromJson(JSON.parse(input));
|
|
1496
|
+
},
|
|
1497
|
+
toI64(value: JSONValue): bigint {
|
|
1498
|
+
return value.toI64();
|
|
1499
|
+
},
|
|
1500
|
+
toU64(value: JSONValue): bigint {
|
|
1501
|
+
return value.toI64();
|
|
1502
|
+
},
|
|
1503
|
+
toF64(value: JSONValue): number {
|
|
1504
|
+
return value.toF64();
|
|
1505
|
+
},
|
|
1506
|
+
toBigInt(value: JSONValue): BigInt_ {
|
|
1507
|
+
return value.toBigInt();
|
|
1508
|
+
},
|
|
1509
|
+
try_fromString(input: string) {
|
|
1510
|
+
try {
|
|
1511
|
+
return { isOk: true, isError: false, value: jsonImpl.fromString(input), error: null };
|
|
1512
|
+
} catch {
|
|
1513
|
+
return { isOk: false, isError: true, value: null, error: true };
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
try_fromBytes(bytes: Bytes) {
|
|
1517
|
+
try {
|
|
1518
|
+
return { isOk: true, isError: false, value: jsonImpl.fromBytes(bytes), error: null };
|
|
1519
|
+
} catch {
|
|
1520
|
+
return { isOk: false, isError: true, value: null, error: true };
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
export const json = strictNamespace("json", jsonImpl);
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* The host ops that reach outside the chain: each returns synchronously here
|
|
1529
|
+
* and suspends underneath, so the mapping keeps graph-ts' shape.
|
|
1530
|
+
*/
|
|
1531
|
+
export type Hosts = {
|
|
1532
|
+
ipfsCat: (hash: string) => string | null;
|
|
1533
|
+
ipfsMap: (hash: string, callback: string, userData: Value, flags: string[]) => void;
|
|
1534
|
+
arweaveData: (txId: string) => string | null;
|
|
1535
|
+
ensName: (hash: string) => string | null;
|
|
1536
|
+
getBalance: (address: string) => string;
|
|
1537
|
+
hasCode: (address: string) => boolean;
|
|
1538
|
+
blockTimestamp: (blockNumber: number) => string;
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1541
|
+
let hosts: Hosts | null = null;
|
|
1542
|
+
|
|
1543
|
+
export function installHosts(installed: Hosts) {
|
|
1544
|
+
hosts = installed;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function hostsOrThrow(): Hosts {
|
|
1548
|
+
if (!hosts) {
|
|
1549
|
+
throw new Error("Envio Subgraph host ops were used before the runtime installed them.");
|
|
1550
|
+
}
|
|
1551
|
+
return hosts;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function decodeBase64(encoded: string | null): Bytes | null {
|
|
1555
|
+
return encoded === null ? null : new Bytes(Buffer.from(encoded, "base64"));
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
const ipfsImpl = {
|
|
1559
|
+
cat(hash: string): Bytes | null {
|
|
1560
|
+
return decodeBase64(hostsOrThrow().ipfsCat(hash));
|
|
1561
|
+
},
|
|
1562
|
+
map(hash: string, callback: string, userData: Value, flags: string[]): void {
|
|
1563
|
+
hostsOrThrow().ipfsMap(hash, callback, userData, flags);
|
|
1564
|
+
},
|
|
1565
|
+
mapJSON(hash: string, callback: string, userData: Value): void {
|
|
1566
|
+
hostsOrThrow().ipfsMap(hash, callback, userData, ["json"]);
|
|
1567
|
+
},
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
export const ipfs = strictNamespace("ipfs", ipfsImpl);
|
|
1571
|
+
|
|
1572
|
+
const arweaveImpl = {
|
|
1573
|
+
transactionData(txId: string): Bytes | null {
|
|
1574
|
+
return decodeBase64(hostsOrThrow().arweaveData(txId));
|
|
1575
|
+
},
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
export const arweave = strictNamespace("arweave", arweaveImpl);
|
|
1579
|
+
|
|
1580
|
+
const ensImpl = {
|
|
1581
|
+
nameByHash(hash: string): string | null {
|
|
1582
|
+
return hostsOrThrow().ensName(hash);
|
|
1583
|
+
},
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
export const ens = strictNamespace("ens", ensImpl);
|
|
1587
|
+
|
|
1588
|
+
/** graph-ts hands a block handler an `ethereum.Block`; only `number` is free. */
|
|
1589
|
+
export function makeBlockHandlerBlock(blockNumber: number, location: string): EthereumBlock {
|
|
1590
|
+
const block = new EthereumBlock(BigInt_.fromI32(blockNumber), () =>
|
|
1591
|
+
BigInt_.fromString(hostsOrThrow().blockTimestamp(blockNumber)),
|
|
1592
|
+
);
|
|
1593
|
+
// A post-hoc fetch of the rest can't be made reorg-consistent, so the other
|
|
1594
|
+
// fields are refused rather than guessed.
|
|
1595
|
+
for (const field of [
|
|
1596
|
+
"hash",
|
|
1597
|
+
"parentHash",
|
|
1598
|
+
"unclesHash",
|
|
1599
|
+
"author",
|
|
1600
|
+
"stateRoot",
|
|
1601
|
+
"transactionsRoot",
|
|
1602
|
+
"receiptsRoot",
|
|
1603
|
+
"gasUsed",
|
|
1604
|
+
"gasLimit",
|
|
1605
|
+
"difficulty",
|
|
1606
|
+
"totalDifficulty",
|
|
1607
|
+
"size",
|
|
1608
|
+
"baseFeePerGas",
|
|
1609
|
+
]) {
|
|
1610
|
+
refusedGetter(block, field, `block.${field} in a block handler`, location);
|
|
1611
|
+
}
|
|
1612
|
+
return block;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* AssemblyScript reinterprets the pointer and the layouts match, so nothing
|
|
1617
|
+
* happens at runtime — which leaves the value with whatever prototype it was
|
|
1618
|
+
* built with. A helper that returns `changetype<ByteArray>(new Uint8Array(n))`
|
|
1619
|
+
* then reaches the mapping without any of ByteArray's methods; ENS's
|
|
1620
|
+
* `byteArrayFromHex` is written that way, and so is every subgraph that copied
|
|
1621
|
+
* it. A plain Uint8Array can only have been meant as one of graph-ts' byte
|
|
1622
|
+
* types, so it is retagged as the most derived one — `Bytes` adds no instance
|
|
1623
|
+
* members over `ByteArray`, so this satisfies both spellings.
|
|
1624
|
+
*/
|
|
1625
|
+
export function changetype<T>(value: unknown): T {
|
|
1626
|
+
if (value instanceof Uint8Array && Object.getPrototypeOf(value) === Uint8Array.prototype) {
|
|
1627
|
+
Object.setPrototypeOf(value, Bytes.prototype);
|
|
1628
|
+
}
|
|
1629
|
+
return value as T;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* AssemblyScript's primitives are namespaces as well as types: `i32.MAX_VALUE`
|
|
1634
|
+
* reads a bound, `i32(x)` truncates to one. Both are ordinary source in a
|
|
1635
|
+
* mapping and neither exists in JavaScript.
|
|
1636
|
+
*
|
|
1637
|
+
* The 64-bit pair carries values a double can't hold, so it works in bigints;
|
|
1638
|
+
* everything else stays a number, which is what the rest of the shim converts.
|
|
1639
|
+
*/
|
|
1640
|
+
function integerNamespace(name: string, bits: number, signed: boolean) {
|
|
1641
|
+
const min = signed ? -(2 ** (bits - 1)) : 0;
|
|
1642
|
+
const max = signed ? 2 ** (bits - 1) - 1 : 2 ** bits - 1;
|
|
1643
|
+
const cast = (value: number) => {
|
|
1644
|
+
const truncated = Math.trunc(Number(value)) || 0;
|
|
1645
|
+
const span = 2 ** bits;
|
|
1646
|
+
const wrapped = ((truncated % span) + span) % span;
|
|
1647
|
+
return wrapped > max ? wrapped - span : wrapped;
|
|
1648
|
+
};
|
|
1649
|
+
return strictNamespace(name, Object.assign(cast, { MIN_VALUE: min, MAX_VALUE: max }));
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function bigIntegerNamespace(name: string, signed: boolean) {
|
|
1653
|
+
const min = signed ? -(2n ** 63n) : 0n;
|
|
1654
|
+
const max = signed ? 2n ** 63n - 1n : 2n ** 64n - 1n;
|
|
1655
|
+
const cast = (value: unknown) => {
|
|
1656
|
+
const raw = typeof value === "bigint" ? value : BigInt(Math.trunc(Number(value)) || 0);
|
|
1657
|
+
// AssemblyScript truncates a cast to the target width rather than widening.
|
|
1658
|
+
return signed ? BigInt.asIntN(64, raw) : BigInt.asUintN(64, raw);
|
|
1659
|
+
};
|
|
1660
|
+
return strictNamespace(name, Object.assign(cast, { MIN_VALUE: min, MAX_VALUE: max }));
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function floatNamespace(name: string, single: boolean) {
|
|
1664
|
+
const cast = (value: unknown) => (single ? Math.fround(Number(value)) : Number(value));
|
|
1665
|
+
return strictNamespace(
|
|
1666
|
+
name,
|
|
1667
|
+
Object.assign(cast, {
|
|
1668
|
+
// AssemblyScript's MIN_VALUE is the most negative finite value, not the
|
|
1669
|
+
// smallest positive one JavaScript names.
|
|
1670
|
+
MIN_VALUE: single ? -3.4028234663852886e38 : -Number.MAX_VALUE,
|
|
1671
|
+
MAX_VALUE: single ? 3.4028234663852886e38 : Number.MAX_VALUE,
|
|
1672
|
+
EPSILON: single ? 1.1920928955078125e-7 : Number.EPSILON,
|
|
1673
|
+
MIN_SAFE_INTEGER: single ? -16777215 : Number.MIN_SAFE_INTEGER,
|
|
1674
|
+
MAX_SAFE_INTEGER: single ? 16777215 : Number.MAX_SAFE_INTEGER,
|
|
1675
|
+
NaN: Number.NaN,
|
|
1676
|
+
POSITIVE_INFINITY: Number.POSITIVE_INFINITY,
|
|
1677
|
+
NEGATIVE_INFINITY: Number.NEGATIVE_INFINITY,
|
|
1678
|
+
}),
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
export const assemblyScriptPrimitives: Record<string, unknown> = {
|
|
1683
|
+
i8: integerNamespace("i8", 8, true),
|
|
1684
|
+
u8: integerNamespace("u8", 8, false),
|
|
1685
|
+
i16: integerNamespace("i16", 16, true),
|
|
1686
|
+
u16: integerNamespace("u16", 16, false),
|
|
1687
|
+
i32: integerNamespace("i32", 32, true),
|
|
1688
|
+
u32: integerNamespace("u32", 32, false),
|
|
1689
|
+
isize: integerNamespace("isize", 32, true),
|
|
1690
|
+
usize: integerNamespace("usize", 32, false),
|
|
1691
|
+
i64: bigIntegerNamespace("i64", true),
|
|
1692
|
+
u64: bigIntegerNamespace("u64", false),
|
|
1693
|
+
f32: floatNamespace("f32", true),
|
|
1694
|
+
f64: floatNamespace("f64", false),
|
|
1695
|
+
};
|