gen-typescript-from-tolk-dev 0.1.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.
@@ -0,0 +1,430 @@
1
+ import * as c from '@ton/core'; // todo is it peer dependency?
2
+ import type { Ty } from './abi-types';
3
+ import { CantPackDynamic, CantUnpackDynamic, InvalidDynamicInput, NonStandardDictKey } from './unsupported-errors';
4
+ import { DynamicCtx } from './dynamic-ctx';
5
+ import {
6
+ checkIsArray,
7
+ checkIsBoolean,
8
+ checkIsNumber,
9
+ checkIsObject,
10
+ checkIsObjectWithProperty,
11
+ checkIsString,
12
+ throwInvalidInput,
13
+ } from './dynamic-validation';
14
+ import { createLabelsForUnion, instantiateGenerics, renderTy } from './types-kernel'
15
+
16
+ /*
17
+ Dynamic serialization works without generating TypeScript wrappers.
18
+ Just given an ABI, serialize any JS input to a cell, and unpack back.
19
+ Its purpose is orthogonal to wrappers:
20
+ - wrappers — for manual development
21
+ (write tests, deployment, and other manual interactions with your contract)
22
+ - dynamic serialization — for tolk-js, explorers, etc.
23
+ (pass some data from auto-generated UI to some contract just given its ABI)
24
+ */
25
+
26
+
27
+ function lookupPrefix(s: c.Slice, expected: number, prefixLen: number): boolean {
28
+ return s.remainingBits >= prefixLen && s.preloadUint(prefixLen) === expected;
29
+ }
30
+
31
+ export function createTonCoreDictionaryKey(fieldPath: string, tyK: Ty): c.DictionaryKey<any> {
32
+ if (tyK.kind === 'intN') return c.Dictionary.Keys.BigInt(tyK.n);
33
+ if (tyK.kind === 'uintN') return c.Dictionary.Keys.BigUint(tyK.n);
34
+ if (tyK.kind === 'address') return c.Dictionary.Keys.Address();
35
+
36
+ throw new NonStandardDictKey(`'${fieldPath}' is 'map<${renderTy(tyK)}, ...>': such a non-standard map key can not be handled by @ton/core library`);
37
+ }
38
+
39
+ export function createTonCoreDictionaryValue(ctx: DynamicCtx, fieldPath: string, tyV: Ty): c.DictionaryValue<any> {
40
+ return {
41
+ serialize(self: any, b: c.Builder) {
42
+ dynamicPack(ctx, fieldPath, tyV, self, b);
43
+ },
44
+ parse(s: c.Slice): any {
45
+ return dynamicUnpack(ctx, fieldPath, tyV, s);
46
+ }
47
+ }
48
+ }
49
+
50
+ // Parameter `fieldPath` is used for error messages only, e.g. when provided invalid input.
51
+ export function dynamicPack(ctx: DynamicCtx, fieldPath: string, ty: Ty, v: any, b: c.Builder): void {
52
+ switch (ty.kind) {
53
+ case 'intN': {
54
+ checkIsNumber(fieldPath, ty, v);
55
+ b.storeInt(v, ty.n);
56
+ break;
57
+ }
58
+ case 'uintN': {
59
+ checkIsNumber(fieldPath, ty, v);
60
+ b.storeUint(v, ty.n);
61
+ break;
62
+ }
63
+ case 'varintN': {
64
+ checkIsNumber(fieldPath, ty, v);
65
+ b.storeVarInt(v, Math.log2(ty.n));
66
+ break;
67
+ }
68
+ case 'varuintN': {
69
+ checkIsNumber(fieldPath, ty, v);
70
+ b.storeVarUint(v, Math.log2(ty.n));
71
+ break;
72
+ }
73
+ case 'coins': {
74
+ checkIsNumber(fieldPath, ty, v);
75
+ b.storeCoins(v);
76
+ break;
77
+ }
78
+ case 'bool': {
79
+ checkIsBoolean(fieldPath, ty, v);
80
+ b.storeBit(v);
81
+ break;
82
+ }
83
+ case 'cell': {
84
+ checkIsObject(fieldPath, ty, v);
85
+ b.storeRef(v);
86
+ break;
87
+ }
88
+ case 'builder': {
89
+ checkIsObject(fieldPath, ty, v);
90
+ b.storeBuilder(v);
91
+ break;
92
+ }
93
+ case 'slice': {
94
+ checkIsObject(fieldPath, ty, v);
95
+ b.storeSlice(v);
96
+ break;
97
+ }
98
+ case 'string': {
99
+ checkIsString(fieldPath, ty, v);
100
+ b.storeStringRefTail(v);
101
+ break;
102
+ }
103
+ case 'remaining': {
104
+ checkIsObject(fieldPath, ty, v);
105
+ b.storeSlice(v);
106
+ break;
107
+ }
108
+ case 'address': {
109
+ checkIsObject(fieldPath, ty, v);
110
+ b.storeAddress(v);
111
+ break;
112
+ }
113
+ case 'addressOpt': {
114
+ checkIsObject(fieldPath, ty, v, true);
115
+ b.storeAddress(v);
116
+ break;
117
+ }
118
+ case 'addressExt': {
119
+ checkIsObject(fieldPath, ty, v);
120
+ b.storeAddress(v);
121
+ break;
122
+ }
123
+ case 'addressAny': {
124
+ if (v === 'none') {
125
+ b.storeAddress(null);
126
+ } else {
127
+ checkIsObject(fieldPath, ty, v);
128
+ b.storeAddress(v);
129
+ }
130
+ break;
131
+ }
132
+ case 'bitsN': {
133
+ checkIsObject(fieldPath, ty, v);
134
+ if (v.remainingBits !== ty.n || v.remainingRefs !== 0) {
135
+ throwInvalidInput(fieldPath, ty, `expected ${ty.n} bits and 0 refs, got ${v.remainingBits} bits and ${v.remainingRefs} refs`);
136
+ }
137
+ b.storeSlice(v);
138
+ break;
139
+ }
140
+ case 'nullable': {
141
+ if (v === null) {
142
+ b.storeUint(0, 1);
143
+ } else {
144
+ b.storeUint(1, 1);
145
+ dynamicPack(ctx, fieldPath, ty.inner, v, b);
146
+ }
147
+ break;
148
+ }
149
+ case 'cellOf': {
150
+ checkIsObjectWithProperty(fieldPath, ty, v, 'ref');
151
+ let b_ref = c.beginCell();
152
+ dynamicPack(ctx, fieldPath, ty.inner, v.ref, b_ref);
153
+ b.storeRef(b_ref.endCell());
154
+ break;
155
+ }
156
+ case 'arrayOf': {
157
+ checkIsArray(fieldPath, ty, v);
158
+ // the compiler stores array<T> in chunks; in TypeScript, for simplicity, store "1 elem = 1 ref"
159
+ let tail = null as c.Cell | null;
160
+ for (let i = 0; i < v.length; ++i) {
161
+ let chunkB = c.beginCell().storeMaybeRef(tail);
162
+ dynamicPack(ctx, fieldPath + '[ith]', ty.inner, v[v.length - 1 - i], chunkB);
163
+ tail = chunkB.endCell();
164
+ }
165
+ b.storeUint(v.length, 8);
166
+ b.storeMaybeRef(tail);
167
+ break;
168
+ }
169
+ case 'lispListOf': {
170
+ checkIsArray(fieldPath, ty, v);
171
+ let tail = c.Cell.EMPTY;
172
+ for (let i = 0; i < v.length; ++i) {
173
+ let itemB = c.beginCell();
174
+ dynamicPack(ctx, `${fieldPath}[${i}]`, ty.inner, v[i], itemB);
175
+ tail = itemB.storeRef(tail).endCell();
176
+ }
177
+ b.storeRef(tail);
178
+ break;
179
+ }
180
+ case 'tensor': // `(T1, T2)` and `[T1, T2]` are both TypeScript arrays
181
+ case 'shapedTuple': { // (but in Tolk, they are different: a tensor and a TVM shaped tuple)
182
+ checkIsArray(fieldPath, ty, v, ty.items.length);
183
+ for (let i = 0; i < v.length; ++i) {
184
+ dynamicPack(ctx, `${fieldPath}[${i}]`, ty.items[i], v[i], b);
185
+ }
186
+ break;
187
+ }
188
+ case 'mapKV': {
189
+ checkIsObject(fieldPath, ty, v);
190
+ let dictKey = createTonCoreDictionaryKey(fieldPath, ty.k);
191
+ let dictValue = createTonCoreDictionaryValue(ctx, fieldPath, ty.v);
192
+ b.storeDict(v, dictKey, dictValue);
193
+ break;
194
+ }
195
+ case 'EnumRef': {
196
+ checkIsNumber(fieldPath, ty, v);
197
+ let enumRef = ctx.symbols.getEnum(ty.enumName);
198
+ if (enumRef.customPackUnpack?.packToBuilder) {
199
+ ctx.getCustomPackFnOrThrow(ty.enumName, fieldPath)(v, b);
200
+ break;
201
+ }
202
+ dynamicPack(ctx, fieldPath, enumRef.encodedAs, v, b);
203
+ break;
204
+ }
205
+ case 'StructRef': {
206
+ checkIsObject(fieldPath, ty, v);
207
+ let structRef = ctx.symbols.getStruct(ty.structName);
208
+ if (structRef.customPackUnpack?.packToBuilder) {
209
+ ctx.getCustomPackFnOrThrow(ty.structName, fieldPath)(v, b);
210
+ break;
211
+ }
212
+ if (structRef.prefix) {
213
+ b.storeUint(+structRef.prefix.prefixStr, structRef.prefix.prefixLen);
214
+ }
215
+ for (let f of structRef.fields) {
216
+ const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
217
+ try {
218
+ dynamicPack(ctx, `${fieldPath}.${f.name}`, fTy, v[f.name], b);
219
+ } catch (ex: any) {
220
+ if (ex instanceof InvalidDynamicInput) {
221
+ throw ex;
222
+ }
223
+ // serialization errors from @ton/core, like "value is out of range"
224
+ throwInvalidInput(`${fieldPath}.${f.name}`, fTy, ex.message ?? ex.toString());
225
+ }
226
+ }
227
+ break;
228
+ }
229
+ case 'AliasRef': {
230
+ let aliasRef = ctx.symbols.getAlias(ty.aliasName);
231
+ if (aliasRef.customPackUnpack?.packToBuilder) {
232
+ ctx.getCustomPackFnOrThrow(ty.aliasName, fieldPath)(v, b);
233
+ break;
234
+ }
235
+ const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
236
+ dynamicPack(ctx, fieldPath, targetTy, v, b);
237
+ break;
238
+ }
239
+ case 'union': {
240
+ const variants = createLabelsForUnion(ctx.symbols, ty.variants);
241
+ const hasNull = variants.find(v => v.variantTy.kind === 'nullLiteral');
242
+ if (hasNull && v === null) {
243
+ b.storeUint(+hasNull.prefixStr, hasNull.prefixLen);
244
+ } else {
245
+ checkIsObjectWithProperty(fieldPath, ty, v, '$');
246
+
247
+ const activeVariant = variants.find(variant => v.$ === variant.labelStr);
248
+ if (!activeVariant) {
249
+ throwInvalidInput(fieldPath, ty, `non-existing union variant for $ = '${v.$}'`);
250
+ }
251
+ if (activeVariant.hasValueField && !v.hasOwnProperty('value')) {
252
+ throwInvalidInput(fieldPath, ty, `expected {$,value} but field 'value' not provided`);
253
+ }
254
+ if (activeVariant.isPrefixImplicit) {
255
+ b.storeUint(+activeVariant.prefixStr, activeVariant.prefixLen);
256
+ }
257
+ let actualValue = activeVariant.hasValueField ? v.value : v;
258
+ dynamicPack(ctx, `${fieldPath}#${activeVariant.labelStr}`, activeVariant.variantTy, actualValue, b);
259
+ }
260
+ break;
261
+ }
262
+ case 'void':
263
+ break;
264
+
265
+ default:
266
+ throw new CantPackDynamic(fieldPath, `type '${renderTy(ty)}' is not serializable`);
267
+ }
268
+ }
269
+
270
+ // Parameter `fieldPath` is used for error messages only, e.g. when can't deserialize exact field (bad slice).
271
+ export function dynamicUnpack(ctx: DynamicCtx, fieldPath: string, ty: Ty, s: c.Slice): any {
272
+ switch (ty.kind) {
273
+ case 'intN': return s.loadIntBig(ty.n);
274
+ case 'uintN': return s.loadUintBig(ty.n);
275
+ case 'varintN': return s.loadVarIntBig(Math.log2(ty.n));
276
+ case 'varuintN': return s.loadVarUintBig(Math.log2(ty.n));
277
+ case 'coins': return s.loadCoins();
278
+ case 'bool': return s.loadBoolean();
279
+ case 'cell': return s.loadRef();
280
+ case 'string': return s.loadStringRefTail();
281
+ case 'remaining': {
282
+ let rest = s.clone();
283
+ s.loadBits(s.remainingBits);
284
+ while (s.remainingRefs) {
285
+ s.loadRef();
286
+ }
287
+ return rest;
288
+ }
289
+ case 'address': return s.loadAddress();
290
+ case 'addressOpt': return s.loadMaybeAddress();
291
+ case 'addressExt': return s.loadExternalAddress();
292
+ case 'addressAny': {
293
+ let maybe_addr = s.loadAddressAny();
294
+ return maybe_addr === null ? 'none' : maybe_addr;
295
+ }
296
+ case 'bitsN': return new c.Slice(new c.BitReader(s.loadBits(ty.n)), []);
297
+ case 'nullLiteral': return null;
298
+ case 'nullable': return s.loadBoolean() ? dynamicUnpack(ctx, fieldPath, ty.inner, s) : null;
299
+ case 'cellOf': {
300
+ let s_ref = s.loadRef().beginParse();
301
+ return { ref: dynamicUnpack(ctx, fieldPath, ty.inner, s_ref) };
302
+ }
303
+ case 'arrayOf': {
304
+ let len = s.loadUint(8);
305
+ let head = s.loadMaybeRef();
306
+ let outArr = [];
307
+ while (head != null) {
308
+ let s = head.beginParse();
309
+ head = s.loadMaybeRef();
310
+ while (s.remainingBits || s.remainingRefs) {
311
+ outArr.push(dynamicUnpack(ctx, fieldPath + '[ith]', ty.inner, s));
312
+ }
313
+ }
314
+ if (len !== outArr.length) {
315
+ throw new CantUnpackDynamic(fieldPath, `mismatch array binary data: expected ${len} elements, got ${outArr.length}`);
316
+ }
317
+ return outArr;
318
+ }
319
+ case 'lispListOf': {
320
+ let outArr = [];
321
+ let head = s.loadRef().beginParse();
322
+ while (head.remainingRefs) {
323
+ let tailSnaked = head.loadRef();
324
+ let headValue = dynamicUnpack(ctx, fieldPath + '[ith]', ty.inner, head);
325
+ head.endParse(); // ensure no data is present besides T
326
+ outArr.unshift(headValue);
327
+ head = tailSnaked.beginParse();
328
+ }
329
+ return outArr;
330
+ }
331
+ case 'tensor':
332
+ case 'shapedTuple': {
333
+ return ty.items.map((item, idx) =>
334
+ dynamicUnpack(ctx, `${fieldPath}[${idx}]`, item, s)
335
+ );
336
+ }
337
+ case 'mapKV': {
338
+ let dictKey = createTonCoreDictionaryKey(fieldPath, ty.k);
339
+ let dictValue = createTonCoreDictionaryValue(ctx, fieldPath, ty.v);
340
+ return s.loadDict(dictKey, dictValue);
341
+ }
342
+ case 'EnumRef': {
343
+ let enumRef = ctx.symbols.getEnum(ty.enumName);
344
+ if (enumRef.customPackUnpack?.unpackFromSlice) {
345
+ return ctx.getCustomUnpackFnOrThrow(ty.enumName, fieldPath)(s);
346
+ }
347
+ return dynamicUnpack(ctx, fieldPath, enumRef.encodedAs, s);
348
+ }
349
+ case 'StructRef': {
350
+ let structRef = ctx.symbols.getStruct(ty.structName);
351
+ if (structRef.customPackUnpack?.unpackFromSlice) {
352
+ return ctx.getCustomUnpackFnOrThrow(ty.structName, fieldPath)(s);
353
+ }
354
+ let result = { $: ty.structName } as Record<string, any>;
355
+ if (structRef.prefix) {
356
+ let prefix = s.loadUint(structRef.prefix.prefixLen);
357
+ if (prefix !== +structRef.prefix.prefixStr) {
358
+ throw new CantUnpackDynamic(fieldPath, `incorrect prefix for '${ty.structName}', expected ${structRef.prefix.prefixStr}`);
359
+ }
360
+ }
361
+ for (let f of structRef.fields) {
362
+ const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
363
+ try {
364
+ result[f.name] = dynamicUnpack(ctx, `${fieldPath}.${f.name}`, fTy, s);
365
+ } catch (ex: any) {
366
+ if (ex instanceof CantUnpackDynamic) {
367
+ throw ex;
368
+ }
369
+ // deserialization errors due to invalid slice, show field name
370
+ throw new CantUnpackDynamic(`${fieldPath}.${f.name}`, ex.message ?? ex.toString());
371
+ }
372
+ }
373
+ return result;
374
+ }
375
+ case 'AliasRef': {
376
+ let aliasRef = ctx.symbols.getAlias(ty.aliasName);
377
+ if (aliasRef.customPackUnpack?.unpackFromSlice) {
378
+ return ctx.getCustomUnpackFnOrThrow(ty.aliasName, fieldPath)(s);
379
+ }
380
+ const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
381
+ return dynamicUnpack(ctx, fieldPath, targetTy, s);
382
+ }
383
+ case 'genericT': throw new CantUnpackDynamic(fieldPath, 'unexpected genericT');
384
+ case 'union': {
385
+ for (const variant of createLabelsForUnion(ctx.symbols, ty.variants)) {
386
+ if (!lookupPrefix(s, +variant.prefixStr, variant.prefixLen)) {
387
+ continue;
388
+ }
389
+ if (variant.isPrefixImplicit) {
390
+ s.skip(variant.prefixLen);
391
+ }
392
+
393
+ let actualValue = dynamicUnpack(ctx, `${fieldPath}#${variant.labelStr}`, variant.variantTy, s);
394
+ if (!variant.hasValueField) {
395
+ return actualValue;
396
+ }
397
+ return { $: variant.labelStr, value: actualValue };
398
+ }
399
+ throw new CantUnpackDynamic(fieldPath, 'none of union prefixes match');
400
+ }
401
+ case 'void':
402
+ return void 0;
403
+
404
+ default:
405
+ throw new CantUnpackDynamic(fieldPath, `type '${renderTy(ty)}' is not serializable`);
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Pack any input to a builder according to an ABI schema.
411
+ * Example: `struct Point { x: int8, y: int8 }`.
412
+ * After being converted, it's stored as ABI.
413
+ * Call: `packToBuilderDynamic(ctx, structTy, { x: 10, y: 20 }, b)` appends 0x0A14.
414
+ * When input is incorrect (e.g. missing property in a JS object), throws InvalidDynamicInput.
415
+ */
416
+ export function packToBuilderDynamic(ctx: DynamicCtx, ty: Ty, value: any, b: c.Builder): void {
417
+ let fieldPath = ty.kind === 'StructRef' ? ty.structName : 'self';
418
+ dynamicPack(ctx, fieldPath, ty, value, b);
419
+ }
420
+
421
+ /**
422
+ * Unpack any slice to a JS variable according to an ABI schema.
423
+ * Example: `struct Point { x: int8, y: int8 }`.
424
+ * Call: `unpackFromSliceDynamic(ctx, structTy, hex"0A14")` returns `{ x: 10, y: 20 }`.
425
+ * When ty can't be deserialized, throws CantUnpackDynamic.
426
+ */
427
+ export function unpackFromSliceDynamic(ctx: DynamicCtx, ty: Ty, s: c.Slice): any {
428
+ let fieldPath = ty.kind === 'StructRef' ? ty.structName : 'self';
429
+ return dynamicUnpack(ctx, fieldPath, ty, s);
430
+ }
@@ -0,0 +1,55 @@
1
+ import type { Ty } from './abi-types'
2
+ import { InvalidDynamicInput } from './unsupported-errors'
3
+ import { renderTy } from './types-kernel'
4
+
5
+ export function throwInvalidInput(fieldPath: string, expectedTy: Ty, msgWhyInvalid: string): never {
6
+ throw new InvalidDynamicInput(`invalid value passed for '${fieldPath}' of type '${renderTy(expectedTy)}': ${msgWhyInvalid}`);
7
+ }
8
+
9
+ export function checkIsNumber(fieldPath: string, expectedTy: Ty, passedV: any): void {
10
+ let isNumber = typeof passedV === 'bigint' || typeof passedV === 'number';
11
+ if (!isNumber) {
12
+ throwInvalidInput(fieldPath, expectedTy, 'not a number');
13
+ }
14
+ }
15
+
16
+ export function checkIsBoolean(fieldPath: string, expectedTy: Ty, passedV: any): void {
17
+ let isBoolean = typeof passedV === 'boolean';
18
+ if (!isBoolean) {
19
+ throwInvalidInput(fieldPath, expectedTy, 'not a boolean');
20
+ }
21
+ }
22
+
23
+ export function checkIsString(fieldPath: string, expectedTy: Ty, passedV: any): void {
24
+ let isString = typeof passedV === 'string';
25
+ if (!isString) {
26
+ throwInvalidInput(fieldPath, expectedTy, 'not a string');
27
+ }
28
+ }
29
+
30
+ export function checkIsArray(fieldPath: string, expectedTy: Ty, passedV: any, expectedLen?: number): void {
31
+ let isArray = Array.isArray(passedV);
32
+ if (!isArray) {
33
+ throwInvalidInput(fieldPath, expectedTy, 'not an array');
34
+ }
35
+ if (expectedLen !== undefined && passedV.length !== expectedLen) {
36
+ throwInvalidInput(fieldPath, expectedTy, `expected ${expectedLen} elements, got ${passedV.length}`);
37
+ }
38
+ }
39
+
40
+ export function checkIsObject(fieldPath: string, expectedTy: Ty, passedV: any, allowNull = false): void {
41
+ let isObject = typeof passedV === 'object' && !Array.isArray(passedV);
42
+ if (passedV === null && !allowNull) {
43
+ isObject = false;
44
+ }
45
+ if (!isObject) {
46
+ throwInvalidInput(fieldPath, expectedTy, 'not an object');
47
+ }
48
+ }
49
+
50
+ export function checkIsObjectWithProperty(fieldPath: string, expectedTy: Ty, passedV: any, propertyName: string): void {
51
+ let isObject = typeof passedV === 'object' && !Array.isArray(passedV) && passedV !== null;
52
+ if (!isObject || !passedV.hasOwnProperty(propertyName)) {
53
+ throwInvalidInput(fieldPath, expectedTy, `not an object with property ${propertyName}`);
54
+ }
55
+ }
@@ -0,0 +1,60 @@
1
+ import { ABIConstExpression, Ty } from './abi-types'
2
+ import { CodegenCtx } from './codegen-ctx'
3
+ import { safeFieldDecl } from './formatting'
4
+ import { renderTy } from './types-kernel'
5
+
6
+ // Default values of struct fields are also exported to TypeScript unless they are hard to be represented.
7
+ // In practice, all simple values are okay: `f: int = 5`, `f: coins = ton("1")`, etc.
8
+ // But in theory, a field can be a union, for example. It will require a large amount of work to support
9
+ // cases like `f: int8 | slice = 5` or `f: int32 | int64 = 5 as int64` (to generate correct `$` labels).
10
+ // So, if such a field exists, then we just assume it has no default.
11
+ export function isDefaultValueSupported(fieldTy: Ty): boolean {
12
+ if (fieldTy.kind === 'arrayOf') return isDefaultValueSupported(fieldTy.inner);
13
+ if (fieldTy.kind === 'lispListOf') return isDefaultValueSupported(fieldTy.inner);
14
+ if (fieldTy.kind === 'tensor') return fieldTy.items.every(isDefaultValueSupported);
15
+ if (fieldTy.kind === 'shapedTuple') return fieldTy.items.every(isDefaultValueSupported);
16
+
17
+ return fieldTy.kind !== 'union' && fieldTy.kind !== 'mapKV';
18
+ }
19
+
20
+ // Default values of fields (particularly, in storage) are precalculated by the compiler
21
+ // and stored as "const expression" intermediate representation in ABI.
22
+ // Here we output them to a valid TypeScript expression.
23
+ // Default parameters for get methods follow the same logic, they are also "const expressions".
24
+ export function emitFieldDefault(ctx: CodegenCtx, expr: ABIConstExpression): string {
25
+ switch (expr.kind) {
26
+ case 'int': return expr.v + 'n';
27
+ case 'bool': return expr.v ? 'true' : 'false';
28
+ case 'slice': return `new c.Slice(new c.BitReader(new c.BitString(Buffer.from('${expr.hex}', 'hex'), 0, ${expr.hex.length * 4})), [])`;
29
+ case 'string': return JSON.stringify(expr.str);
30
+ case 'address': return `c.Address.parse('${expr.addr}')`;
31
+ case 'tensor': return `[${expr.items.map(i => emitFieldDefault(ctx, i)).join(', ')}]`;
32
+ case 'shapedTuple': return `[${expr.items.map(i => emitFieldDefault(ctx, i)).join(', ')}]`;
33
+ case 'object': {
34
+ const structRef = ctx.symbols.getStruct(expr.structName);
35
+ return `{ $: '${structRef.name}', ${[...structRef.fields.map((f, i) =>
36
+ `${safeFieldDecl(f.name)}: ${emitFieldDefault(ctx, expr.fields[i])}`
37
+ )].join(', ')} }`;
38
+ }
39
+ case 'castTo': return emitFieldDefault(ctx, expr.inner);
40
+ case 'null': return `null`;
41
+ }
42
+ }
43
+
44
+ // Output a default value not as a TypeScript expression, but as a human-readable one.
45
+ // For instance, "123" instead of bigint "123n".
46
+ // It's inserted inside comments next to fields having defaults.
47
+ export function emitFieldDefaultInComment(expr: ABIConstExpression): string {
48
+ switch (expr.kind) {
49
+ case 'int': return expr.v;
50
+ case 'bool': return expr.v ? 'true' : 'false';
51
+ case 'slice': return `hex('${expr.hex}')`;
52
+ case 'string': return JSON.stringify(expr.str);
53
+ case 'address': return `address('${expr.addr}')`;
54
+ case 'tensor': return `[${expr.items.map(emitFieldDefaultInComment).join(', ')}]`;
55
+ case 'shapedTuple': return `[${expr.items.map(emitFieldDefaultInComment).join(', ')}]`;
56
+ case 'object': return `${expr.structName} { ${expr.fields.map(emitFieldDefaultInComment).join(', ')} }`;
57
+ case 'castTo': return `${emitFieldDefaultInComment(expr.inner)} as ${renderTy(expr.castTo)}`;
58
+ case 'null': return `null`;
59
+ }
60
+ }