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,191 @@
1
+ import * as c from '@ton/core';
2
+ import { DynamicCtx } from './dynamic-ctx';
3
+ import { Ty } from './abi-types';
4
+ import { makeTvmTupleDynamic, StackReader } from './dynamic-get-methods';
5
+ import { createTonCoreDictionaryKey, createTonCoreDictionaryValue, dynamicUnpack } from './dynamic-serialization';
6
+ import { calcWidthOnStack, createLabelsForUnion, instantiateGenerics, renderTy } from './types-kernel';
7
+
8
+ /*
9
+ Debug printing from the TVM stack.
10
+ Given TupleItem[] of a type Ty, and an ABI (via DynamicCtx),
11
+ produce a human-readable string representation.
12
+ This is used for debugging / println / explorers —
13
+ to show the value of a variable or a get method result in a readable format.
14
+ */
15
+
16
+ function printRawCellContents(c: c.Cell): string {
17
+ return c.toString();
18
+ }
19
+
20
+ // Read `ty` from a stack and return formatted human-readable representation.
21
+ // Throws if a stack is too small or failed to deserialize a value (inside `Cell<T>` or a map, for example).
22
+ function debugFormat(ctx: DynamicCtx, r: StackReader, fieldPath: string, ty: Ty, unTupleIfW = false): string {
23
+ if (unTupleIfW) {
24
+ let wOnStack = calcWidthOnStack(ctx.symbols, ty);
25
+ if (wOnStack !== 1) {
26
+ return r.readTuple(wOnStack, (r) => debugFormat(ctx, r, fieldPath, ty, false));
27
+ }
28
+ }
29
+
30
+ switch (ty.kind) {
31
+ case 'int':
32
+ case 'intN':
33
+ case 'uintN':
34
+ case 'varintN':
35
+ case 'varuintN':
36
+ case 'coins': return r.readBigInt().toString();
37
+ case 'bool': return r.readBoolean() ? 'true' : 'false';
38
+ case 'cell': return `cell{${printRawCellContents(r.readCell())}}`;
39
+ case 'builder': return `builder{${printRawCellContents(r.readBuilder().endCell())}}`;
40
+ case 'slice':
41
+ case 'bitsN':
42
+ case 'remaining': return `slice{${printRawCellContents(r.readSlice().asCell())}}`;
43
+ case 'string': return `"${r.readSnakeString()}"`;
44
+ case 'address': return r.readSlice().loadAddress().toString();
45
+ case 'addressOpt': return debugFormat(ctx, r, fieldPath, { kind: 'nullable', inner: { kind: 'address' } });
46
+ case 'addressExt': return r.readSlice().loadExternalAddress().toString();
47
+ case 'addressAny': {
48
+ let addr = dynamicUnpack(ctx, fieldPath, ty, r.readSlice());
49
+ return addr === 'none' ? 'addr_none' : addr.toString();
50
+ }
51
+ case 'nullLiteral': return r.readNullLiteral() ?? 'null';
52
+ case 'void': return '(void)';
53
+ case 'unknown': return debugPrintUnknown(r.readUnknown());
54
+ case 'nullable': {
55
+ if (ty.stackTypeId) {
56
+ return r.readWideNullable(ty.stackWidth!, (r) => debugFormat(ctx, r, fieldPath, ty.inner)) ?? 'null';
57
+ }
58
+ return r.readNullable((r) => debugFormat(ctx, r, fieldPath, ty.inner)) ?? 'null';
59
+ }
60
+ case 'cellOf': {
61
+ let innerStr = r.readCellRef((s) => {
62
+ const deserialized = dynamicUnpack(ctx, fieldPath, ty.inner, s);
63
+ return debugFormatValue(ctx, fieldPath, ty.inner, deserialized);
64
+ });
65
+ return `ref{${innerStr.ref}}`;
66
+ }
67
+ case 'arrayOf': {
68
+ let items = r.readArrayOf((r) => debugFormat(ctx, r, fieldPath, ty.inner, true));
69
+ return `[${items.join(', ')}]`;
70
+ }
71
+ case 'lispListOf': {
72
+ let items = r.readLispListOf((r) => debugFormat(ctx, r, fieldPath, ty.inner, true));
73
+ return `[${items.join(', ')}]`;
74
+ }
75
+ case 'tensor': {
76
+ let parts: string[] = [];
77
+ for (let i = 0; i < ty.items.length; ++i) {
78
+ parts.push(debugFormat(ctx, r, `${fieldPath}[${i}]`, ty.items[i]));
79
+ }
80
+ return `(${parts.join(', ')})`;
81
+ }
82
+ case 'shapedTuple': {
83
+ return r.readTuple(ty.items.length, (r) => {
84
+ let parts: string[] = [];
85
+ for (let i = 0; i < ty.items.length; ++i) {
86
+ parts.push(debugFormat(ctx, r, `${fieldPath}[${i}]`, ty.items[i], true));
87
+ }
88
+ return `[${parts.join(', ')}]`;
89
+ });
90
+ }
91
+ case 'mapKV': {
92
+ let dictKey = createTonCoreDictionaryKey(fieldPath, ty.k);
93
+ let dictValue = createTonCoreDictionaryValue(ctx, fieldPath, ty.v);
94
+ let entries: string[] = [];
95
+ for (let [k, v] of r.readDictionary(dictKey, dictValue)) {
96
+ let kStr = debugFormatValue(ctx, fieldPath, ty.k, k);
97
+ let vStr = debugFormatValue(ctx, `${fieldPath}[${kStr}]`, ty.v, v);
98
+ entries.push(`${kStr}: ${vStr}`);
99
+ }
100
+ if (entries.length === 0) {
101
+ return 'map{}';
102
+ }
103
+ return `map{${entries.join(', ')}}`;
104
+ }
105
+ case 'EnumRef': {
106
+ let value = r.readBigInt();
107
+ let enumRef = ctx.symbols.getEnum(ty.enumName);
108
+ let member = enumRef.members.find(m => BigInt(m.value) === value);
109
+ if (member) {
110
+ return `${ty.enumName}.${member.name}`;
111
+ }
112
+ return `${ty.enumName}(${value})`;
113
+ }
114
+ case 'StructRef': {
115
+ const structRef = ctx.symbols.getStruct(ty.structName);
116
+ if (structRef.fields.length === 0) {
117
+ return `${ty.structName} {}`;
118
+ }
119
+ const fieldStrs: string[] = [];
120
+ for (const f of structRef.fields) {
121
+ const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
122
+ fieldStrs.push(`${f.name}: ${debugFormat(ctx, r, `${fieldPath}.${f.name}`, fTy)}`);
123
+ }
124
+ return `${ty.structName} { ${fieldStrs.join(', ')} }`;
125
+ }
126
+ case 'AliasRef': {
127
+ const aliasRef = ctx.symbols.getAlias(ty.aliasName);
128
+ const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
129
+ return debugFormat(ctx, r, fieldPath, targetTy);
130
+ }
131
+ case 'union': {
132
+ const variants = createLabelsForUnion(ctx.symbols, ty.variants);
133
+ const infoForTypeId = {} as Record<number, [number, string | null, (r: StackReader) => string]>;
134
+ for (let v of variants) {
135
+ infoForTypeId[v.stackTypeId] = [v.stackWidth, v.hasValueField ? v.labelStr : null,
136
+ (r: StackReader) => debugFormat(ctx, r, `${fieldPath}#${v.labelStr}`, v.variantTy)
137
+ ];
138
+ }
139
+ // readUnionType returns `valueT` (if no label) or `{ $: "label", value: valueT }`
140
+ // In our case, valueT is already a string.
141
+ const result = r.readUnionType<string | { $: string, value: string }>(ty.stackWidth, infoForTypeId);
142
+ if (typeof result === 'string') {
143
+ // label is null, then it's a struct with its own $, like "Point { ... }", or it's "null"
144
+ return result;
145
+ }
146
+ // render "#label value": notation "#label" means "active variant of a union"
147
+ return `#${result.$} ${result.value}`;
148
+ }
149
+ case 'callable': {
150
+ r.readUnknown();
151
+ return 'continuation';
152
+ }
153
+ case 'genericT': throw new Error(`unexpected genericT=${ty.nameT}`);
154
+ }
155
+ }
156
+
157
+ // Format a value that was already parsed by dynamicUnpack into a JS representation.
158
+ // This is used for inner values of `Cell<T>` and map entries, where we parse via `dynamicUnpack`
159
+ // and then need to show the result as a string.
160
+ function debugFormatValue(ctx: DynamicCtx, fieldPath: string, ty: Ty, value: any): string {
161
+ // unlike `debugFormat()`, here we don't read `ty` from a stack: we already have `value` of type `ty`;
162
+ // to visualize it, instead of making a giant switch-case for all types, do the following trick:
163
+ // pack value back to a tuple, and render this tuple as if it's a stack value
164
+ const backToTuple = makeTvmTupleDynamic(ctx, ty, value);
165
+ return debugFormat(ctx, new StackReader(backToTuple), fieldPath, ty);
166
+ }
167
+
168
+ // `unknown` is a raw TVM tuple item; `tuple`, by definition, is `array<unknown>`,
169
+ // it has no compile-time type information, so visualize it anyhow
170
+ function debugPrintUnknown(item: c.TupleItem): string {
171
+ switch (item.type) {
172
+ case 'int': return `${item.value}`;
173
+ case 'cell': return `cell{${printRawCellContents(item.cell)}}`;
174
+ case 'slice': return `slice{${printRawCellContents(item.cell)}}`;
175
+ case 'builder': return `builder{${printRawCellContents(item.cell)}}`;
176
+ case 'null': return 'null';
177
+ case 'tuple': return `(${item.items.map(debugPrintUnknown).join(', ')})`;
178
+ case 'nan': return 'NaN';
179
+ default: return `<unknown>`;
180
+ }
181
+ }
182
+
183
+
184
+ /**
185
+ * Read a value of type `ty` from the TVM stack `tuple` and return a human-readable string.
186
+ * Consumes `calcWidthOnStack(ty)` elements from the beginning of the array.
187
+ * Example: given `Point { x: int, y: int }` and stack `[10n, 20n]`, returns "Point { x: 10, y: 20 }".
188
+ */
189
+ export function debugPrintFromStack(ctx: DynamicCtx, r: StackReader, ty: Ty): string {
190
+ return debugFormat(ctx, r, 'self', ty);
191
+ }
@@ -0,0 +1,454 @@
1
+ import * as c from '@ton/core'; // todo is it peer dependency?
2
+ import { DynamicCtx } from './dynamic-ctx';
3
+ import { CantCallGetMethodDynamic, NotSupportedTypeOnStack } from './unsupported-errors'
4
+ import { Ty } from './abi-types'
5
+ import { createTonCoreDictionaryKey, createTonCoreDictionaryValue, dynamicPack, dynamicUnpack } from './dynamic-serialization'
6
+ import { calcWidthOnStack, createLabelsForUnion, instantiateGenerics } from './types-kernel'
7
+ import { checkIsArray, checkIsBoolean, checkIsNumber, checkIsObject, checkIsObjectWithProperty, throwInvalidInput } from './dynamic-validation'
8
+
9
+ /*
10
+ Dynamic calling GET methods works without generating TypeScript wrappers.
11
+ Just given an ABI, pack any JS input to TVM stack (TupleItem[]), and unpack back.
12
+ Its purpose is orthogonal to wrappers:
13
+ - wrappers — for manual development
14
+ (write tests, deployment, and other manual interactions with your contract)
15
+ - dynamic contract getters — for tolk-js, explorers, etc.
16
+ (pass some data from auto-generated UI and invoke getters given its ABI)
17
+ */
18
+
19
+ type LoadCallback<T> = (s: c.Slice) => T
20
+
21
+ export class StackReader {
22
+ constructor(private tuple: c.TupleItem[]) {
23
+ }
24
+
25
+ static fromGetMethod(expectedN: number, getMethodResult: { stack: c.TupleReader }): StackReader {
26
+ let tuple = [] as c.TupleItem[];
27
+ while (getMethodResult.stack.remaining) {
28
+ tuple.push(getMethodResult.stack.pop());
29
+ }
30
+ if (tuple.length !== expectedN) {
31
+ throw new Error(`expected ${expectedN} stack width, got ${tuple.length}`);
32
+ }
33
+ return new StackReader(tuple);
34
+ }
35
+
36
+ private popExpecting<ItemT>(itemType: string): ItemT {
37
+ const item = this.tuple.shift();
38
+ if (item?.type !== itemType) {
39
+ throw new Error(`not '${itemType}' on a stack`);
40
+ }
41
+ return item as ItemT;
42
+ }
43
+
44
+ readBigInt(): bigint {
45
+ return this.popExpecting<c.TupleItemInt>('int').value;
46
+ }
47
+
48
+ readBoolean(): boolean {
49
+ return this.popExpecting<c.TupleItemInt>('int').value !== 0n;
50
+ }
51
+
52
+ readCell(): c.Cell {
53
+ return this.popExpecting<c.TupleItemCell>('cell').cell;
54
+ }
55
+
56
+ readSlice(): c.Slice {
57
+ return this.popExpecting<c.TupleItemSlice>('slice').cell.beginParse();
58
+ }
59
+
60
+ readBuilder(): c.Builder {
61
+ return c.beginCell().storeSlice(this.popExpecting<c.TupleItemBuilder>('builder').cell.beginParse());
62
+ }
63
+
64
+ readUnknown(): c.TupleItem {
65
+ // `unknown` from Tolk is left as a raw tuple item
66
+ return this.tuple.shift()!;
67
+ }
68
+
69
+ readArrayOf<T>(readFn_T: (nestedReader: StackReader) => T): T[] {
70
+ const subItems = this.popExpecting<c.Tuple>('tuple').items;
71
+ const subReader = new StackReader(subItems);
72
+ // array len N => N subItems => N calls to readFn_T
73
+ return [...subItems].map(_ => readFn_T(subReader))
74
+ }
75
+
76
+ readLispListOf<T>(readFn_T: (nestedReader: StackReader) => T): T[] {
77
+ // read `[1 [2 [3 null]]]` to `[1 2 3]`
78
+ let pairReader: StackReader = this;
79
+ let outArr = [] as T[];
80
+ while (true) {
81
+ if (pairReader.tuple[0].type === 'null') {
82
+ pairReader.tuple.shift();
83
+ break;
84
+ }
85
+ let headAndTail = pairReader.popExpecting<c.Tuple>('tuple').items;
86
+ if (headAndTail.length !== 2) {
87
+ throw new Error(`malformed lisp_list, expected 2 stack width, got ${headAndTail.length}`);
88
+ }
89
+ pairReader = new StackReader(headAndTail);
90
+ outArr.push(readFn_T(pairReader));
91
+ }
92
+ return outArr;
93
+ }
94
+
95
+ readSnakeString(): string {
96
+ return this.readCell().beginParse().loadStringTail();
97
+ }
98
+
99
+ readTuple<T>(expectedN: number, readFn_T: (nestedReader: StackReader) => T): T {
100
+ const subItems = this.popExpecting<c.Tuple>('tuple').items;
101
+ if (subItems.length !== expectedN) {
102
+ throw new Error(`expected ${expectedN} items in a tuple, got ${subItems.length}`);
103
+ }
104
+ return readFn_T(new StackReader(subItems));
105
+ }
106
+
107
+ readNullLiteral(): null {
108
+ this.popExpecting<c.TupleItemNull>('null');
109
+ return null;
110
+ }
111
+
112
+ readNullable<T>(readFn_T: (r: StackReader) => T): T | null {
113
+ if (this.tuple[0].type === 'null') {
114
+ this.tuple.shift();
115
+ return null;
116
+ }
117
+ return readFn_T(this);
118
+ }
119
+
120
+ readWideNullable<T>(stackW: number, readFn_T: (r: StackReader) => T): T | null {
121
+ const slotTypeId = this.tuple[stackW - 1];
122
+ if (slotTypeId?.type !== 'int') {
123
+ throw new Error(`not 'int' on a stack`);
124
+ }
125
+ if (slotTypeId.value === 0n) {
126
+ this.tuple = this.tuple.slice(stackW);
127
+ return null;
128
+ }
129
+ const valueT = readFn_T(this);
130
+ this.tuple.shift();
131
+ return valueT;
132
+ }
133
+
134
+ readUnionType<T>(stackW: number, infoForTypeId: Record<number, [number, string | null, (r: StackReader) => any]>): T {
135
+ const slotTypeId = this.tuple[stackW - 1];
136
+ if (slotTypeId?.type !== 'int') {
137
+ throw new Error(`not 'int' on a stack`);
138
+ }
139
+ const info = infoForTypeId[Number(slotTypeId.value)]; // [stackWidth, label, readFn_T{i}]
140
+ if (info == null) {
141
+ throw new Error(`unexpected UTag=${slotTypeId.value}`);
142
+ }
143
+ const label = info[1];
144
+ this.tuple = this.tuple.slice(stackW - 1 - info[0]);
145
+ const valueT = info[2](this);
146
+ this.tuple.shift();
147
+ return label == null ? valueT : { $: label, value: valueT } as T;
148
+ }
149
+
150
+ readCellRef<T>(loadFn_T: LoadCallback<T>): { ref: T } {
151
+ return { ref: loadFn_T(this.readCell().beginParse()) };
152
+ }
153
+
154
+ readDictionary<K extends c.DictionaryKeyTypes, V>(keySerializer: c.DictionaryKey<K>, valueSerializer: c.DictionaryValue<V>): c.Dictionary<K, V> {
155
+ if (this.tuple[0].type === 'null') {
156
+ this.tuple.shift();
157
+ return c.Dictionary.empty<K, V>(keySerializer, valueSerializer);
158
+ }
159
+ return c.Dictionary.loadDirect<K, V>(keySerializer, valueSerializer, this.readCell());
160
+ }
161
+ }
162
+
163
+ // When constructing a TVM tuple (when calling a get method), we often need to make cells.
164
+ // For example, given an address, we should result in { type: 'slice', cell: (serialized address) }
165
+ function makeCell(ctx: DynamicCtx, fieldPath: string, ty: Ty, v: any): c.Cell {
166
+ let b = c.beginCell();
167
+ dynamicPack(ctx, fieldPath, ty, v, b); // does validation, throws if invalid, e.g. number instead of address
168
+ return b.endCell();
169
+ }
170
+
171
+ // Pack value `v` of type `ty` to a TVM tuple, as an argument of a get method.
172
+ // Parameter `fieldPath` is used for error messages only, e.g. when provided invalid input.
173
+ function dynamicConstruct(ctx: DynamicCtx, fieldPath: string, ty: Ty, v: any, tupleIfW: boolean = false): c.TupleItem[] {
174
+ if (tupleIfW) { // inside `array<T>` or `[T, ...]`, if T is non-primitive, it's a sub-tuple
175
+ if (calcWidthOnStack(ctx.symbols, ty) === 1) {
176
+ return dynamicConstruct(ctx, fieldPath, ty, v, false);
177
+ }
178
+ return [{ type: 'tuple', items: dynamicConstruct(ctx, fieldPath, ty, v, false) }];
179
+ }
180
+
181
+ switch (ty.kind) {
182
+ case 'int':
183
+ case 'intN':
184
+ case 'uintN':
185
+ case 'varintN':
186
+ case 'varuintN':
187
+ case 'coins': {
188
+ checkIsNumber(fieldPath, ty, v);
189
+ return [{ type: 'int', value: v }];
190
+ }
191
+ case 'bool': {
192
+ checkIsBoolean(fieldPath, ty, v);
193
+ return [{ type: 'int', value: (v ? -1n : 0n) }];
194
+ }
195
+ case 'cell': {
196
+ checkIsObject(fieldPath, ty, v);
197
+ return [{ type: 'cell', cell: v }];
198
+ }
199
+ case 'builder': {
200
+ // the call to `makeCell()`, automatically does validation of input (of v)
201
+ return [{ type: 'builder', cell: makeCell(ctx, fieldPath, ty, v) }];
202
+ }
203
+ case 'slice':
204
+ case 'remaining':
205
+ case 'address':
206
+ case 'addressExt':
207
+ case 'addressAny':
208
+ case 'bitsN': {
209
+ return [{ type: 'slice', cell: makeCell(ctx, fieldPath, ty, v) }];
210
+ }
211
+ case 'string': {
212
+ return [{ type: 'cell', cell: makeCell(ctx, fieldPath, ty, v) }];
213
+ }
214
+ case 'addressOpt': {
215
+ if (v === null) {
216
+ return [{ type: 'null' }];
217
+ }
218
+ return [{ type: 'slice', cell: makeCell(ctx, fieldPath, ty, v) }];
219
+ }
220
+ case 'nullLiteral': {
221
+ return [{ type: 'null' }];
222
+ }
223
+ case 'callable': {
224
+ throw new NotSupportedTypeOnStack(ty, fieldPath);
225
+ }
226
+ case 'void': {
227
+ return []
228
+ }
229
+ case 'unknown': {
230
+ checkIsObjectWithProperty(fieldPath, ty, v, 'type'); // c.TupleItem has 'type'
231
+ return [v as c.TupleItem];
232
+ }
233
+ case 'nullable': {
234
+ if (ty.stackTypeId) {
235
+ let result = [] as c.TupleItem[];
236
+ if (v === null) {
237
+ for (let i = 0; i < ty.stackWidth! - 1; ++i) {
238
+ result.push({ type: 'null' });
239
+ }
240
+ result.push({ type: 'int', value: 0n });
241
+ } else {
242
+ result = dynamicConstruct(ctx, fieldPath, ty.inner, v);
243
+ result.push({ type: 'int', value: BigInt(ty.stackTypeId) });
244
+ }
245
+ return result;
246
+ }
247
+ if (v === null) {
248
+ return [{ type: 'null' }];
249
+ }
250
+ return dynamicConstruct(ctx, fieldPath, ty.inner, v);
251
+ }
252
+ case 'cellOf': {
253
+ checkIsObjectWithProperty(fieldPath, ty, v, 'ref');
254
+ return [{ type: 'cell', cell: makeCell(ctx, fieldPath, ty.inner, v.ref) }];
255
+ }
256
+ case 'arrayOf': {
257
+ checkIsArray(fieldPath, ty, v);
258
+ return [{ type: 'tuple', items: v.map((ith: any) => dynamicConstruct(ctx, 'ith', ty.inner, ith, true)[0]) }];
259
+ }
260
+ case 'lispListOf': {
261
+ checkIsArray(fieldPath, ty, v);
262
+ return [v.reduceRight((tail: c.TupleItem, head: any) => (
263
+ { type: 'tuple', items: [dynamicConstruct(ctx, 'head', ty.inner, head, true)[0], tail] }
264
+ ), { type: 'null' })];
265
+ }
266
+ case 'tensor': {
267
+ checkIsArray(fieldPath, ty, v, ty.items.length);
268
+ return v.flatMap((item: any, idx: number) => dynamicConstruct(ctx, `${fieldPath}[${idx}]`, ty.items[idx], item));
269
+ }
270
+ case 'shapedTuple': {
271
+ checkIsArray(fieldPath, ty, v, ty.items.length);
272
+ return [{ type: 'tuple', items: v.map((ith: any, idx: number) => dynamicConstruct(ctx, `${fieldPath}[${idx}]`, ty.items[idx], ith, true)[0]) }];
273
+ }
274
+ case 'mapKV': {
275
+ checkIsObject(fieldPath, ty, v);
276
+ if (v.size === 0) {
277
+ return [{ type: 'null' }];
278
+ }
279
+ let dictKey = createTonCoreDictionaryKey(fieldPath, ty.k);
280
+ let dictValue = createTonCoreDictionaryValue(ctx, fieldPath, ty.v);
281
+ return [{ type: 'cell', cell: c.beginCell().storeDictDirect(v, dictKey, dictValue).endCell() }];
282
+ }
283
+ case 'EnumRef': {
284
+ checkIsNumber(fieldPath, ty, v);
285
+ return [{ type: 'int', value: v }];
286
+ }
287
+ case 'StructRef': {
288
+ checkIsObject(fieldPath, ty, v);
289
+ let structRef = ctx.symbols.getStruct(ty.structName);
290
+ return structRef.fields.flatMap(f => {
291
+ const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
292
+ return dynamicConstruct(ctx, `${fieldPath}.${f.name}`, fTy, v[f.name]);
293
+ });
294
+ }
295
+ case 'AliasRef': {
296
+ let aliasRef = ctx.symbols.getAlias(ty.aliasName);
297
+ const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
298
+ return dynamicConstruct(ctx, fieldPath, targetTy, v);
299
+ }
300
+ case 'union': {
301
+ const variants = createLabelsForUnion(ctx.symbols, ty.variants);
302
+ const hasNull = variants.find(v => v.variantTy.kind === 'nullLiteral');
303
+ let result = [] as c.TupleItem[];
304
+ if (hasNull && v === null) {
305
+ for (let i = 0; i < ty.stackWidth - 1; ++i) {
306
+ result.push({ type: 'null' });
307
+ }
308
+ result.push({ type: 'int', value: 0n });
309
+ } else {
310
+ checkIsObjectWithProperty(fieldPath, ty, v, '$');
311
+
312
+ const activeVariant = variants.find(variant => v.$ === variant.labelStr);
313
+ if (!activeVariant) {
314
+ throwInvalidInput(fieldPath, ty, `non-existing union variant for $ = '${v.$}'`);
315
+ }
316
+ if (activeVariant.hasValueField && !v.hasOwnProperty('value')) {
317
+ throwInvalidInput(fieldPath, ty, `expected {$,value} but field 'value' not provided`);
318
+ }
319
+ for (let i = 0; i < ty.stackWidth - 1 - activeVariant.stackWidth; ++i) {
320
+ result.push({ type: 'null' });
321
+ }
322
+ let actualValue = activeVariant.hasValueField ? v.value : v;
323
+ result.push(...dynamicConstruct(ctx, `${fieldPath}#${activeVariant.labelStr}`, activeVariant.variantTy, actualValue));
324
+ result.push({ type: 'int', value: BigInt(activeVariant.stackTypeId) });
325
+ }
326
+ return result;
327
+ }
328
+ case 'genericT': throw new Error(`unexpected genericT=${ty.nameT} at ${fieldPath}`);
329
+ }
330
+ }
331
+
332
+ // Parse `ty` from a TVM tuple on a stack, returned as a result of a get method invocation.
333
+ // Parameter `fieldPath` is used for error messages only, e.g. when can't deserialize CellRef<T>.
334
+ function dynamicParse(ctx: DynamicCtx, r: StackReader, fieldPath: string, ty: Ty, unTupleIfW = false): any {
335
+ if (unTupleIfW) { // inside `array<T>` or `[T, ...]`, if T is non-primitive, it's a sub-tuple
336
+ let wOnStack = calcWidthOnStack(ctx.symbols, ty);
337
+ if (wOnStack !== 1) {
338
+ return r.readTuple(wOnStack, (r) => dynamicParse(ctx, r, fieldPath, ty, false));
339
+ }
340
+ }
341
+
342
+ switch (ty.kind) {
343
+ case 'int':
344
+ case 'intN':
345
+ case 'uintN':
346
+ case 'varintN':
347
+ case 'varuintN':
348
+ case 'coins': return r.readBigInt();
349
+ case 'bool': return r.readBoolean();
350
+ case 'cell': return r.readCell();
351
+ case 'builder': return r.readBuilder();
352
+ case 'slice': return r.readSlice();
353
+ case 'string': return r.readSnakeString();
354
+ case 'remaining': return r.readSlice();
355
+ case 'address': return r.readSlice().loadAddress();
356
+ case 'addressOpt': return dynamicParse(ctx, r, fieldPath, { kind: 'nullable', inner: { kind: 'address' } });
357
+ case 'addressExt': return r.readSlice().loadExternalAddress();
358
+ case 'addressAny': return dynamicUnpack(ctx, fieldPath, ty, r.readSlice());
359
+ case 'bitsN': return r.readSlice();
360
+ case 'nullLiteral': return r.readNullLiteral();
361
+ case 'callable': throw new NotSupportedTypeOnStack(ty, fieldPath);
362
+ case 'void': return void 0;
363
+ case 'unknown': return r.readUnknown();
364
+ case 'nullable': {
365
+ if (ty.stackTypeId) {
366
+ return r.readWideNullable(ty.stackWidth!, (r) => dynamicParse(ctx, r, fieldPath, ty.inner));
367
+ }
368
+ return r.readNullable((r) => dynamicParse(ctx, r, fieldPath, ty.inner));
369
+ }
370
+ case 'cellOf': return r.readCellRef((s) => dynamicUnpack(ctx, fieldPath, ty.inner, s));
371
+ case 'arrayOf': return r.readArrayOf((r) => dynamicParse(ctx, r, fieldPath, ty.inner, true));
372
+ case 'lispListOf': return r.readLispListOf((r) => dynamicParse(ctx, r, fieldPath, ty.inner, true));
373
+ case 'tensor': {
374
+ let result = [];
375
+ for (let i = 0; i < ty.items.length; ++i) {
376
+ result.push(dynamicParse(ctx, r, `${fieldPath}[${i}]`, ty.items[i]));
377
+ }
378
+ return result;
379
+ }
380
+ case 'shapedTuple': {
381
+ return r.readTuple(ty.items.length, (r) => {
382
+ let result = [];
383
+ for (let i = 0; i < ty.items.length; ++i) {
384
+ result.push(dynamicParse(ctx, r, `${fieldPath}[${i}]`, ty.items[i], true));
385
+ }
386
+ return result;
387
+ });
388
+ }
389
+ case 'mapKV': {
390
+ let dictKey = createTonCoreDictionaryKey(fieldPath, ty.k);
391
+ let dictValue = createTonCoreDictionaryValue(ctx, fieldPath, ty.v);
392
+ return r.readDictionary(dictKey, dictValue);
393
+ }
394
+ case 'EnumRef': return r.readBigInt();
395
+ case 'StructRef': {
396
+ const structRef = ctx.symbols.getStruct(ty.structName);
397
+ let result = { $: ty.structName } as Record<string, any>;
398
+ for (let f of structRef.fields) {
399
+ const fTy = ty.typeArgs ? instantiateGenerics(f.ty, structRef.typeParams, ty.typeArgs) : f.ty;
400
+ result[f.name] = dynamicParse(ctx, r, `${fieldPath}.${f.name}`, fTy);
401
+ }
402
+ return result;
403
+ }
404
+ case 'AliasRef': {
405
+ const aliasRef = ctx.symbols.getAlias(ty.aliasName);
406
+ const targetTy = ty.typeArgs ? instantiateGenerics(aliasRef.targetTy, aliasRef.typeParams, ty.typeArgs) : aliasRef.targetTy;
407
+ return dynamicParse(ctx, r, fieldPath, targetTy);
408
+ }
409
+ case 'genericT': throw new Error(`unexpected genericT=${ty.nameT} at ${fieldPath}`);
410
+ case 'union': {
411
+ const variants = createLabelsForUnion(ctx.symbols, ty.variants);
412
+ const infoForTypeId = {} as Record<number, [number, string | null, (r: StackReader) => any]>;
413
+ for (let v of variants) {
414
+ infoForTypeId[v.stackTypeId] = [v.stackWidth, v.hasValueField ? v.labelStr : null,
415
+ (r: StackReader) => dynamicParse(ctx, r, `${fieldPath}#${v.labelStr}`, v.variantTy)
416
+ ];
417
+ }
418
+ return r.readUnionType(ty.stackWidth, infoForTypeId);
419
+ }
420
+ }
421
+ }
422
+
423
+
424
+ /**
425
+ * Pack any `value` of type `ty` to a TVM tuple.
426
+ * This tuple can be used as a get method argument or debug-printed as human-readable.
427
+ */
428
+ export function makeTvmTupleDynamic(ctx: DynamicCtx, ty: Ty, value: any): c.TupleItem[] {
429
+ return dynamicConstruct(ctx, 'value', ty, value);
430
+ }
431
+
432
+ /**
433
+ * Invoke any get method taking plain JS variables as arguments and interpreting the resulting TVM tuple according to ABI.
434
+ * Example: `get fun intAndPoint(i: int, p: Point): Point`.
435
+ * Call: `callGetMethodDynamic(provider, ctx, 'intAndPoint', [num, {x: num, y: num}])` returns `{x: num, y: num}`.
436
+ * When input is incorrect (e.g. missing property in a JS object), throws InvalidDynamicInput.
437
+ */
438
+ export async function callGetMethodDynamic(provider: c.ContractProvider, ctx: DynamicCtx, getMethodName: string, args: any[]): Promise<any> {
439
+ const getM = ctx.findGetMethod(getMethodName);
440
+ if (getM == null) {
441
+ throw new CantCallGetMethodDynamic(getMethodName, `method not found in contract ${ctx.contractName}`);
442
+ }
443
+ if (getM.parameters.length !== args.length) { // default parameters not supported, dynamic invocation should provide all
444
+ throw new CantCallGetMethodDynamic(getMethodName, `expected ${getM.parameters.length} arguments, got ${args.length}`);
445
+ }
446
+
447
+ const stackIn: c.TupleItem[] = [];
448
+ for (let i = 0; i < args.length; ++i) {
449
+ stackIn.push(...dynamicConstruct(ctx, getM.parameters[i].name, getM.parameters[i].ty, args[i]));
450
+ }
451
+ const expectedSlotsOut = calcWidthOnStack(ctx.symbols, getM.returnTy);
452
+ const r = StackReader.fromGetMethod(expectedSlotsOut, await provider.get(getMethodName, stackIn));
453
+ return dynamicParse(ctx, r, 'result', getM.returnTy);
454
+ }