@typesol/cli 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -0
- package/README.md +4 -4
- package/dist/clientgen.js +39 -0
- package/dist/compiler/analyze.d.ts +16 -1
- package/dist/compiler/analyze.js +355 -43
- package/dist/compiler/emit/base.d.ts +36 -1
- package/dist/compiler/emit/base.js +135 -4
- package/dist/compiler/emit/borsh.d.ts +13 -0
- package/dist/compiler/emit/borsh.js +298 -0
- package/dist/compiler/emit/calls.d.ts +2 -3
- package/dist/compiler/emit/calls.js +135 -29
- package/dist/compiler/emit/cpi.d.ts +62 -8
- package/dist/compiler/emit/cpi.js +1458 -40
- package/dist/compiler/emit/expressions.d.ts +5 -0
- package/dist/compiler/emit/expressions.js +85 -5
- package/dist/compiler/emit/function.js +82 -14
- package/dist/compiler/emit/program.js +24 -6
- package/dist/compiler/emit/shared.d.ts +20 -0
- package/dist/compiler/emit/shared.js +23 -3
- package/dist/compiler/emit/statements.d.ts +21 -0
- package/dist/compiler/emit/statements.js +380 -19
- package/dist/compiler/emit/storage.d.ts +6 -0
- package/dist/compiler/emit/storage.js +88 -16
- package/dist/compiler/idl.js +37 -5
- package/dist/compiler/model.d.ts +70 -4
- package/dist/compiler/model.js +8 -2
- package/dist/portgen.js +90 -4
- package/docs/errors.md +6 -4
- package/docs/language.md +32 -20
- package/docs/migrate-from-anchor.md +24 -7
- package/docs/test-your-program.md +1 -1
- package/lib/typesol.d.ts +652 -21
- package/package.json +1 -1
- package/runtime/typesol_rt.h +230 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveScalarType } from "../analyze.js";
|
|
3
3
|
import { INT_INFO, isScalar, isWritable, sameType, scalarSize, typeName } from "../model.js";
|
|
4
|
-
import { U64, BOOL, PUBKEY, STRING, optC, fieldC, storageDecl } from "./shared.js";
|
|
4
|
+
import { U64, BOOL, PUBKEY, STRING, optC, fieldC, fixedVecC, storageDecl, vecCount } from "./shared.js";
|
|
5
5
|
import { EmitterBase } from "./base.js";
|
|
6
6
|
/**
|
|
7
7
|
* Places in account data and locals: references, reads and writes, Vec/array indexing, aliasing rules and
|
|
@@ -242,11 +242,14 @@ export class StorageEmitter extends EmitterBase {
|
|
|
242
242
|
ref(node) {
|
|
243
243
|
if (ts.isParenthesizedExpression(node))
|
|
244
244
|
return this.ref(node.expression);
|
|
245
|
+
const unbanged = this.withoutBang(node);
|
|
246
|
+
if (unbanged)
|
|
247
|
+
return this.ref(unbanged);
|
|
245
248
|
this.noOptionalChain(node);
|
|
246
249
|
// A helper's struct result: a local of the caller's, read and copied like any struct.
|
|
247
250
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
248
|
-
const h = this.
|
|
249
|
-
if (h?.returns?.kind === "struct"
|
|
251
|
+
const h = this.helperOf(node);
|
|
252
|
+
if (h?.returns?.kind === "struct") {
|
|
250
253
|
const v = this.callHelper(node, h);
|
|
251
254
|
return { lv: `(*${v.c})`, type: { kind: "struct", name: h.returns.name }, writable: true };
|
|
252
255
|
}
|
|
@@ -273,13 +276,16 @@ export class StorageEmitter extends EmitterBase {
|
|
|
273
276
|
return undefined;
|
|
274
277
|
this.assertUsable(a, node);
|
|
275
278
|
const r = { lv: `d_${a.name}->${f.name}`, type: f.type, writable: isWritable(a.kind), account: a, path: [f.name] };
|
|
276
|
-
if (f.type.kind === "vec")
|
|
277
|
-
|
|
279
|
+
if (f.type.kind === "vec" && f.type.cap !== undefined)
|
|
280
|
+
r.vec = this.fixedVec(r.lv, f.type.elem, f.type.cap);
|
|
281
|
+
else if (f.type.kind === "vec")
|
|
282
|
+
// Growing needs the account's length at instruction entry: instructions have it, helpers get it passed.
|
|
278
283
|
r.vec = {
|
|
279
284
|
len: `d_${a.name}->${f.name}__len`,
|
|
285
|
+
items: r.lv,
|
|
280
286
|
fixed: `sizeof(TSS_${a.kind.struct.name})`,
|
|
281
287
|
elemSize: a.kind.struct.vec.elemSize,
|
|
282
|
-
resizable: isWritable(a.kind)
|
|
288
|
+
resizable: isWritable(a.kind),
|
|
283
289
|
};
|
|
284
290
|
return r;
|
|
285
291
|
}
|
|
@@ -307,7 +313,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
307
313
|
// TypeScript proved the variant set; this runtime check is the backstop.
|
|
308
314
|
const cond = located.map((l) => `${base.lv}.tag != ${l.vi}`).join(" && ");
|
|
309
315
|
this.line(`if (${cond}) TS_FAIL(TS_ERR_ENUM_VARIANT, "TypeSOL: ${e.name} is not ${names.join("/")}");`);
|
|
310
|
-
return { ...base, lv: `${base.lv}.v.${first.name}.${first.f.name}`, type: first.f.type, path: undefined, vec: undefined };
|
|
316
|
+
return this.withFixedVec({ ...base, lv: `${base.lv}.v.${first.name}.${first.f.name}`, type: first.f.type, path: undefined, vec: undefined });
|
|
311
317
|
}
|
|
312
318
|
if (base && base.type.kind === "option" && base.type.inner.kind === "struct") {
|
|
313
319
|
const s = this.model.structs.get(base.type.inner.name);
|
|
@@ -315,7 +321,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
315
321
|
if (!f)
|
|
316
322
|
this.fail(node.name, `${s.name} has no field "${field}"`);
|
|
317
323
|
this.line(`if (!${base.lv}.some) TS_FAIL(TS_ERR_UNWRAP, "TypeSOL: option is empty");`);
|
|
318
|
-
return { ...base, lv: `${base.lv}.v.${f.name}`, type: f.type, path: undefined, vec: undefined };
|
|
324
|
+
return this.withFixedVec({ ...base, lv: `${base.lv}.v.${f.name}`, type: f.type, path: undefined, vec: undefined });
|
|
319
325
|
}
|
|
320
326
|
if (base && base.type.kind === "struct") {
|
|
321
327
|
const s = this.model.structs.get(base.type.name);
|
|
@@ -324,9 +330,9 @@ export class StorageEmitter extends EmitterBase {
|
|
|
324
330
|
this.fail(node.name, `${s.name} has no field "${field}"`);
|
|
325
331
|
const r = { ...base, lv: `${base.lv}.${f.name}`, type: f.type, path: base.path && [...base.path, f.name], vec: undefined };
|
|
326
332
|
// Vecs of loaded accounts can be read and updated in place but not resized (no entry length known).
|
|
327
|
-
if (f.type.kind === "vec")
|
|
328
|
-
r.vec = { len: `${base.lv}.${f.name}__len`, fixed: `sizeof(TSS_${s.name})`, elemSize: s.vec.elemSize, resizable: false };
|
|
329
|
-
return r;
|
|
333
|
+
if (f.type.kind === "vec" && f.type.cap === undefined)
|
|
334
|
+
r.vec = { len: `${base.lv}.${f.name}__len`, items: r.lv, fixed: `sizeof(TSS_${s.name})`, elemSize: s.vec.elemSize, resizable: false };
|
|
335
|
+
return this.withFixedVec(r);
|
|
330
336
|
}
|
|
331
337
|
return undefined;
|
|
332
338
|
}
|
|
@@ -334,7 +340,7 @@ export class StorageEmitter extends EmitterBase {
|
|
|
334
340
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "get") {
|
|
335
341
|
const owner = node.expression.expression;
|
|
336
342
|
const l = ts.isIdentifier(owner) ? this.lookup(owner.text) : undefined;
|
|
337
|
-
if (l?.type.kind === "vecArg" && !isScalar(l.type.elem)) {
|
|
343
|
+
if (l?.type.kind === "vecArg" && !isScalar(l.type.elem) && l.type.elem.kind !== "string") {
|
|
338
344
|
// A Vec<Struct> argument: elements are read in place (validated when the instruction started).
|
|
339
345
|
if (node.arguments.length !== 1)
|
|
340
346
|
this.fail(node, "get(index) takes one argument");
|
|
@@ -345,8 +351,9 @@ export class StorageEmitter extends EmitterBase {
|
|
|
345
351
|
if (base && (base.type.kind === "array" || base.type.kind === "vec")) {
|
|
346
352
|
if (node.arguments.length !== 1)
|
|
347
353
|
this.fail(node, "get(index) takes one argument");
|
|
348
|
-
const idx = this.index(node.arguments[0], base.type.kind === "array" ? `${base.type.len}ULL` :
|
|
349
|
-
|
|
354
|
+
const idx = this.index(node.arguments[0], base.type.kind === "array" ? `${base.type.len}ULL` : vecCount(base.vec));
|
|
355
|
+
const lv = base.type.kind === "array" ? `${base.lv}[${idx}]` : `${base.vec.items}[${idx}]`;
|
|
356
|
+
return this.withFixedVec({ ...base, lv, type: base.type.elem, path: undefined, vec: undefined });
|
|
350
357
|
}
|
|
351
358
|
}
|
|
352
359
|
return undefined;
|
|
@@ -474,7 +481,19 @@ export class StorageEmitter extends EmitterBase {
|
|
|
474
481
|
* Write a struct or array into storage: from an object literal (field by field, every field
|
|
475
482
|
* required) or by copying another location of the same type.
|
|
476
483
|
*/
|
|
484
|
+
/** A whole struct account copied as a value (`return vault`, `info = vault`): fixed-size structs only. */
|
|
485
|
+
wholeAccount(node) {
|
|
486
|
+
if (!ts.isIdentifier(node) || this.lookup(node.text))
|
|
487
|
+
return undefined;
|
|
488
|
+
const a = this.accounts.get(node.text);
|
|
489
|
+
if (!a || !(a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "initIfNeeded") || a.kind.struct.size === undefined || a.kind.struct.vec)
|
|
490
|
+
return undefined;
|
|
491
|
+
this.assertUsable(a, node);
|
|
492
|
+
return { lv: `(*d_${a.name})`, type: { kind: "struct", name: a.kind.struct.name }, writable: false };
|
|
493
|
+
}
|
|
477
494
|
writeAggregate(target, valueNode) {
|
|
495
|
+
if (ts.isParenthesizedExpression(valueNode))
|
|
496
|
+
return this.writeAggregate(target, valueNode.expression);
|
|
478
497
|
if (target.type.kind === "option" && target.type.inner.kind === "struct") {
|
|
479
498
|
if (valueNode.kind === ts.SyntaxKind.NullKeyword) {
|
|
480
499
|
this.line(`${target.lv}.some = 0;`);
|
|
@@ -534,6 +553,35 @@ export class StorageEmitter extends EmitterBase {
|
|
|
534
553
|
this.line(`ts_memcpy(&${target.lv}, &${tmp}, sizeof(${tmp}));`);
|
|
535
554
|
return;
|
|
536
555
|
}
|
|
556
|
+
if (target.type.kind === "vec" && target.type.cap !== undefined && ts.isCallExpression(valueNode) && this.exportName(valueNode.expression) === "fixedVec") {
|
|
557
|
+
const list = valueNode.arguments[0];
|
|
558
|
+
if (valueNode.arguments.length !== 1 || !ts.isArrayLiteralExpression(list))
|
|
559
|
+
this.fail(valueNode, "fixedVec([…]) takes an array literal");
|
|
560
|
+
return this.writeAggregate(target, list);
|
|
561
|
+
}
|
|
562
|
+
if (target.type.kind === "vec" && target.type.cap !== undefined && ts.isArrayLiteralExpression(valueNode)) {
|
|
563
|
+
// `list = [a, b]`: built in a temporary (the elements may read the old list), the unused slots zeroed.
|
|
564
|
+
const t = target.type;
|
|
565
|
+
const els = valueNode.elements;
|
|
566
|
+
if (els.length > t.cap)
|
|
567
|
+
this.fail(valueNode, `${els.length} elements do not fit in ${typeName(t)}`);
|
|
568
|
+
const tmp = this.tmp("fv");
|
|
569
|
+
this.line(`${fixedVecC(t)} ${tmp};`);
|
|
570
|
+
this.line(`ts_memzero(&${tmp}, sizeof(${tmp}));`);
|
|
571
|
+
this.line(`${tmp}.len = ${els.length};`);
|
|
572
|
+
els.forEach((e, i) => {
|
|
573
|
+
if (ts.isSpreadElement(e))
|
|
574
|
+
this.fail(e, "list the elements one by one");
|
|
575
|
+
const sub = { lv: `${tmp}.items[${i}]`, type: t.elem, writable: true };
|
|
576
|
+
const vt = this.storageValueType(t.elem);
|
|
577
|
+
if (vt)
|
|
578
|
+
this.line(this.writeStorage(sub, this.ownedString(this.expect(this.expr(e, vt), vt, e)).c) + ";");
|
|
579
|
+
else
|
|
580
|
+
this.writeAggregate(sub, e);
|
|
581
|
+
});
|
|
582
|
+
this.line(`ts_memcpy(&${target.lv}, &${tmp}, sizeof(${tmp}));`);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
537
585
|
if (ts.isObjectLiteralExpression(valueNode)) {
|
|
538
586
|
if (target.type.kind !== "struct")
|
|
539
587
|
this.fail(valueNode, `expected a ${typeName(target.type)}, not an object literal`);
|
|
@@ -569,12 +617,33 @@ export class StorageEmitter extends EmitterBase {
|
|
|
569
617
|
this.line(`ts_memcpy(&${target.lv}, &${tmp}, sizeof(${tmp}));`);
|
|
570
618
|
return;
|
|
571
619
|
}
|
|
572
|
-
|
|
620
|
+
if (ts.isConditionalExpression(valueNode)) {
|
|
621
|
+
// `cond ? a : b` (places, literals or calls): each branch writes the target. A branch that reads the target
|
|
622
|
+
// copies it onto itself, which the copies below allow.
|
|
623
|
+
const cond = this.expect(this.expr(valueNode.condition, BOOL), BOOL, valueNode.condition);
|
|
624
|
+
this.line(`if (${cond.c}) {`);
|
|
625
|
+
this.block(() => this.writeAggregate(target, valueNode.whenTrue));
|
|
626
|
+
this.line(`} else {`);
|
|
627
|
+
this.block(() => this.writeAggregate(target, valueNode.whenFalse));
|
|
628
|
+
this.line(`}`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const src = this.ref(valueNode) ?? this.wholeAccount(valueNode);
|
|
573
632
|
if (!src || !sameType(src.type, target.type))
|
|
574
633
|
this.fail(valueNode, `expected a ${typeName(target.type)}: an object literal or another ${typeName(target.type)}`);
|
|
575
634
|
// Same-typed places are either distinct or identical (`a.p = a.p`); ts_copy skips the latter.
|
|
576
635
|
this.line(`ts_copy(&${target.lv}, &${src.lv}, sizeof(${target.lv}));`);
|
|
577
636
|
}
|
|
637
|
+
/** The Vec view of a FixedVec stored at `lv` (`{ len, items[cap] }`). */
|
|
638
|
+
fixedVec(lv, elem, cap) {
|
|
639
|
+
return { len: `${lv}.len`, items: `${lv}.items`, fixed: "", elemSize: this.fieldBytes(elem), resizable: false, cap };
|
|
640
|
+
}
|
|
641
|
+
/** A place whose type is a FixedVec gets its Vec view. */
|
|
642
|
+
withFixedVec(r) {
|
|
643
|
+
if (r.type.kind === "vec" && r.type.cap !== undefined)
|
|
644
|
+
r.vec = this.fixedVec(r.lv, r.type.elem, r.type.cap);
|
|
645
|
+
return r;
|
|
646
|
+
}
|
|
578
647
|
/** `arr.get(i)` reads storage rather than calling anything. */
|
|
579
648
|
isStorageCall(node) {
|
|
580
649
|
return ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "get" && !!this.peekArray(node.expression.expression);
|
|
@@ -658,8 +727,11 @@ export class StorageEmitter extends EmitterBase {
|
|
|
658
727
|
return this.fieldBytes(t.elem) * t.len;
|
|
659
728
|
case "struct":
|
|
660
729
|
return this.model.structs.get(t.name).size;
|
|
661
|
-
case "string":
|
|
662
730
|
case "vec":
|
|
731
|
+
if (t.cap !== undefined)
|
|
732
|
+
return 4 + this.fieldBytes(t.elem) * t.cap;
|
|
733
|
+
throw new Error("unreachable: variable-size field has no fixed size");
|
|
734
|
+
case "string":
|
|
663
735
|
throw new Error("unreachable: variable-size field has no fixed size");
|
|
664
736
|
}
|
|
665
737
|
}
|
package/dist/compiler/idl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { snakeCase } from "./analyze.js";
|
|
3
3
|
import { decodeAddress } from "../keys.js";
|
|
4
|
-
import { isWritable, optionKey } from "./model.js";
|
|
4
|
+
import { fixedVecName, isWritable, optionKey, typeName, } from "./model.js";
|
|
5
5
|
const SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
6
6
|
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
7
7
|
const TOKEN_2022_PROGRAM = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
@@ -37,6 +37,19 @@ const fixedEnumName = (name) => `FixedEnum_${name}`;
|
|
|
37
37
|
* IDL type of a field or value. `borshOption` picks the encoding of Option: Borsh `option` for
|
|
38
38
|
* instruction arguments and events, the fixed { is_some, value } struct for account data.
|
|
39
39
|
*/
|
|
40
|
+
/** IDL type of a @layout borsh account's field: Anchor's own (`string`, `{ vec }`, `{ option }`). */
|
|
41
|
+
function borshIdlType(t) {
|
|
42
|
+
if (t.kind === "fstring")
|
|
43
|
+
return "string";
|
|
44
|
+
if (t.kind === "vec")
|
|
45
|
+
return { vec: borshIdlType(t.elem) };
|
|
46
|
+
if (t.kind === "option")
|
|
47
|
+
return { option: idlType(t.inner) };
|
|
48
|
+
// A data enum in a Borsh struct is the Borsh enum itself (tag, then the variant's fields).
|
|
49
|
+
if (t.kind === "dataEnum")
|
|
50
|
+
return { defined: { name: t.name } };
|
|
51
|
+
return idlType(t);
|
|
52
|
+
}
|
|
40
53
|
function idlType(t, borshOption = false) {
|
|
41
54
|
switch (t.kind) {
|
|
42
55
|
case "int":
|
|
@@ -62,7 +75,7 @@ function idlType(t, borshOption = false) {
|
|
|
62
75
|
// Borsh enum in arguments and events; fixed tag + payload in account data.
|
|
63
76
|
return borshOption || t.kind === "dataEnumArg" ? { defined: { name: t.name } } : { defined: { name: fixedEnumName(t.name) } };
|
|
64
77
|
case "vec":
|
|
65
|
-
return { vec: idlType(t.elem) };
|
|
78
|
+
return t.cap === undefined ? { vec: idlType(t.elem) } : { defined: { name: fixedVecName(t) } };
|
|
66
79
|
case "vecArg":
|
|
67
80
|
return { vec: idlType(t.elem) };
|
|
68
81
|
case "arrayArg":
|
|
@@ -80,7 +93,7 @@ function accountEntry(ix, name, k, programAddress) {
|
|
|
80
93
|
entry.writable = true;
|
|
81
94
|
if (param?.optional)
|
|
82
95
|
entry.optional = true;
|
|
83
|
-
if (k.kind === "signer")
|
|
96
|
+
if (k.kind === "signer" || param?.signsInit)
|
|
84
97
|
entry.signer = true;
|
|
85
98
|
if (k.kind === "system")
|
|
86
99
|
entry.address = SYSTEM_PROGRAM;
|
|
@@ -125,7 +138,7 @@ function accountEntry(ix, name, k, programAddress) {
|
|
|
125
138
|
export function buildIdl(model, address, version = "0.1.0") {
|
|
126
139
|
const structs = [...model.structs.values()];
|
|
127
140
|
const usedStructs = structs.filter((s) => model.accountStructs.has(s.name) || model.events.has(s.name) || s.size !== undefined);
|
|
128
|
-
|
|
141
|
+
const idl = {
|
|
129
142
|
address,
|
|
130
143
|
metadata: { name: model.name, version, spec: "0.1.0", description: "Built with TypeSOL" },
|
|
131
144
|
instructions: model.instructions.map((ix) => ({
|
|
@@ -148,7 +161,7 @@ export function buildIdl(model, address, version = "0.1.0") {
|
|
|
148
161
|
fields: s.fields.map((f) => ({
|
|
149
162
|
name: snakeCase(f.name),
|
|
150
163
|
...docsOf(s.node.members.find((m) => m.name && ts.isIdentifier(m.name) && m.name.text === f.name)),
|
|
151
|
-
type: idlType(f.type, model.events.has(s.name) && !model.accountStructs.has(s.name)),
|
|
164
|
+
type: s.layout === "borsh" ? borshIdlType(f.type) : idlType(f.type, model.events.has(s.name) && !model.accountStructs.has(s.name)),
|
|
152
165
|
})),
|
|
153
166
|
},
|
|
154
167
|
})),
|
|
@@ -201,6 +214,17 @@ export function buildIdl(model, address, version = "0.1.0") {
|
|
|
201
214
|
],
|
|
202
215
|
},
|
|
203
216
|
})),
|
|
217
|
+
...[...model.fixedVecs].map(([name, t]) => ({
|
|
218
|
+
name,
|
|
219
|
+
docs: [`TypeSOL FixedVec<${typeName(t.elem)}, ${t.cap}>: \`len\` elements in use, room for ${t.cap} (the rest zero or stale).`],
|
|
220
|
+
type: {
|
|
221
|
+
kind: "struct",
|
|
222
|
+
fields: [
|
|
223
|
+
{ name: "len", type: "u32" },
|
|
224
|
+
{ name: "items", type: { array: [idlType(t.elem), t.cap] } },
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
})),
|
|
204
228
|
...[...model.fstrings]
|
|
205
229
|
.sort((a, b) => a - b)
|
|
206
230
|
.map((cap) => ({
|
|
@@ -216,4 +240,12 @@ export function buildIdl(model, address, version = "0.1.0") {
|
|
|
216
240
|
})),
|
|
217
241
|
],
|
|
218
242
|
};
|
|
243
|
+
// FixedString / FixedVec / FixedOption types only @layout borsh accounts declared (they list theirs as string / vec /
|
|
244
|
+
// option) are left out.
|
|
245
|
+
// FixedVecs first: a kept one may name a FixedString.
|
|
246
|
+
for (const prefix of [/^FixedVec\d/, /^FixedOption_/, /^FixedString\d/]) {
|
|
247
|
+
const text = JSON.stringify([idl.instructions, idl.types.filter((t) => !prefix.test(t.name))]);
|
|
248
|
+
idl.types = idl.types.filter((t) => !prefix.test(t.name) || text.includes(`"name":"${t.name}"`));
|
|
249
|
+
}
|
|
250
|
+
return idl;
|
|
219
251
|
}
|
package/dist/compiler/model.d.ts
CHANGED
|
@@ -110,9 +110,11 @@ export type FieldType = ScalarType | {
|
|
|
110
110
|
name: string;
|
|
111
111
|
}
|
|
112
112
|
/** `Vec<T>`: Borsh u32 length + elements. Only as the last field of an account struct. */
|
|
113
|
+
/** `Vec<T>` (last field, grows the account) or, with `cap`, `FixedVec<T, N>`: a u32 length and room for N elements. */
|
|
113
114
|
| {
|
|
114
115
|
kind: "vec";
|
|
115
116
|
elem: FieldType;
|
|
117
|
+
cap?: number;
|
|
116
118
|
};
|
|
117
119
|
export interface StructField {
|
|
118
120
|
name: string;
|
|
@@ -136,14 +138,27 @@ export interface StructDef {
|
|
|
136
138
|
accountDiscriminator: number[];
|
|
137
139
|
eventDiscriminator: number[];
|
|
138
140
|
node: ts.InterfaceDeclaration;
|
|
141
|
+
/**
|
|
142
|
+
* How account data stores it: "fixed" (every field at a fixed offset, the default) or "borsh" (`@layout borsh`:
|
|
143
|
+
* Anchor's bytes, where strings, Vecs and Options take only what they hold).
|
|
144
|
+
*/
|
|
145
|
+
layout: "fixed" | "borsh";
|
|
146
|
+
/**
|
|
147
|
+
* A @layout borsh struct with a `string` field or a data enum carrying strings: they are views of the bytes it was
|
|
148
|
+
* read from, so it is an argument, return value or event, never account data.
|
|
149
|
+
*/
|
|
150
|
+
viewsOnly?: boolean;
|
|
139
151
|
}
|
|
140
152
|
export type TokenProgramKind = "token" | "token2022" | "tokenInterface";
|
|
141
153
|
export type ParamKind = {
|
|
142
154
|
kind: "signer";
|
|
143
155
|
mut: boolean;
|
|
144
|
-
}
|
|
156
|
+
}
|
|
157
|
+
/** `systemOwned`: a SystemAccount (Anchor's): owned by the System Program (3011). */
|
|
158
|
+
| {
|
|
145
159
|
kind: "info";
|
|
146
160
|
mut: boolean;
|
|
161
|
+
systemOwned?: boolean;
|
|
147
162
|
} | {
|
|
148
163
|
kind: "account";
|
|
149
164
|
struct: StructDef;
|
|
@@ -168,9 +183,12 @@ export type ParamKind = {
|
|
|
168
183
|
kind: "token";
|
|
169
184
|
mut: boolean;
|
|
170
185
|
init?: "create" | "ifNeeded";
|
|
171
|
-
}
|
|
186
|
+
}
|
|
187
|
+
/** `init`: created in the body (`mint.create(payer, seeds | null, decimals, authority, freeze)`). */
|
|
188
|
+
| {
|
|
172
189
|
kind: "mint";
|
|
173
190
|
mut: boolean;
|
|
191
|
+
init?: "create" | "ifNeeded";
|
|
174
192
|
} | {
|
|
175
193
|
kind: "system";
|
|
176
194
|
} | {
|
|
@@ -253,6 +271,8 @@ export interface AccountParam {
|
|
|
253
271
|
optional?: boolean;
|
|
254
272
|
node: ts.ParameterDeclaration;
|
|
255
273
|
constraints: Constraints;
|
|
274
|
+
/** Created in the body as a new keypair account (`create(payer, null, …)`), so it signs: the IDL says so. */
|
|
275
|
+
signsInit?: boolean;
|
|
256
276
|
}
|
|
257
277
|
export interface ArgParam {
|
|
258
278
|
name: string;
|
|
@@ -280,6 +300,8 @@ export interface Instruction {
|
|
|
280
300
|
minArgsSize: number;
|
|
281
301
|
/** Sent as return data: a scalar in Borsh, or a fixed-size struct in its fixed layout (as the IDL describes it). */
|
|
282
302
|
returns: ScalarType | {
|
|
303
|
+
kind: "string";
|
|
304
|
+
} | {
|
|
283
305
|
kind: "struct";
|
|
284
306
|
name: string;
|
|
285
307
|
} | undefined;
|
|
@@ -291,6 +313,16 @@ export interface Instruction {
|
|
|
291
313
|
/** The declaring file's `typesol` imports (local name -> export name). */
|
|
292
314
|
imports: Map<string, string>;
|
|
293
315
|
}
|
|
316
|
+
/** A helper with type parameters, before it is instantiated. */
|
|
317
|
+
export interface GenericHelper {
|
|
318
|
+
name: string;
|
|
319
|
+
display: string;
|
|
320
|
+
/** Indexes of parameters with a function type (`f: (x: u64) => u64`): bound per call to a named function. */
|
|
321
|
+
fnParams: number[];
|
|
322
|
+
node: HelperNode;
|
|
323
|
+
typeParams: string[];
|
|
324
|
+
imports: Map<string, string>;
|
|
325
|
+
}
|
|
294
326
|
export interface HelperParam {
|
|
295
327
|
name: string;
|
|
296
328
|
/** `{ kind: "account" }` for account parameters (then `account` says which kind). */
|
|
@@ -298,16 +330,36 @@ export interface HelperParam {
|
|
|
298
330
|
account?: ParamKind;
|
|
299
331
|
node: ts.ParameterDeclaration;
|
|
300
332
|
}
|
|
301
|
-
/** A
|
|
333
|
+
/** A function written as `function f(…)`, `const f = (…) => …` or `const f = function (…) {…}`. */
|
|
334
|
+
export type HelperNode = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;
|
|
335
|
+
/**
|
|
336
|
+
* A non-exported function (or any function of an imported file), or an arrow function declared with const at the top
|
|
337
|
+
* level or inside a function (capturing nothing): values and accounts in; void, a scalar or a struct out.
|
|
338
|
+
*/
|
|
302
339
|
export interface Helper {
|
|
340
|
+
/** The C name (fn_<name>): the function's name, `outer__local` for a local arrow, `name__u64` for an instance. */
|
|
303
341
|
name: string;
|
|
342
|
+
/** The name in messages: `local`, `max<u64>`. */
|
|
343
|
+
display: string;
|
|
304
344
|
params: HelperParam[];
|
|
305
345
|
/** A struct result is written through an out pointer (`TSS_<name> *ret`) that the caller provides. */
|
|
306
346
|
returns: ScalarType | {
|
|
347
|
+
kind: "string";
|
|
348
|
+
} | {
|
|
307
349
|
kind: "struct";
|
|
308
350
|
name: string;
|
|
309
351
|
} | undefined;
|
|
310
|
-
node:
|
|
352
|
+
node: HelperNode;
|
|
353
|
+
/** The body; an arrow's expression body is one return (or expression) statement. */
|
|
354
|
+
statements: readonly ts.Statement[];
|
|
355
|
+
/** A generic helper's instance: type parameter -> the type it stands for. */
|
|
356
|
+
typeArgs?: Map<string, ScalarType>;
|
|
357
|
+
/** A generic helper's instance: type parameter -> the struct it stands for. */
|
|
358
|
+
structArgs?: Map<string, string>;
|
|
359
|
+
/** An instance's function parameters: name -> the helper passed for it (its calls go there). */
|
|
360
|
+
fnArgs?: Map<string, string>;
|
|
361
|
+
/** Their argument positions, which have no C parameter. */
|
|
362
|
+
fnArgIndexes?: number[];
|
|
311
363
|
/** The declaring file's `typesol` imports (local name -> export name). */
|
|
312
364
|
imports: Map<string, string>;
|
|
313
365
|
}
|
|
@@ -352,6 +404,12 @@ export interface ProgramModel {
|
|
|
352
404
|
structs: Map<string, StructDef>;
|
|
353
405
|
instructions: Instruction[];
|
|
354
406
|
helpers: Map<string, Helper>;
|
|
407
|
+
/** Generic helpers by name: instantiated (into `helpers`) for the types each call uses. */
|
|
408
|
+
generics: Map<string, GenericHelper>;
|
|
409
|
+
/** Declaration (function, or the const of an arrow) -> helper or generic name, for calls to local arrows. */
|
|
410
|
+
helperDecls: Map<ts.Node, string>;
|
|
411
|
+
/** Local closures (they read the enclosing function's variables): their declaration -> name; expanded where called. */
|
|
412
|
+
closureDecls: Map<ts.Node, string>;
|
|
355
413
|
errors: ErrorDef[];
|
|
356
414
|
/** Enum name -> member name -> error */
|
|
357
415
|
errorEnums: Map<string, Map<string, ErrorDef>>;
|
|
@@ -374,6 +432,10 @@ export interface ProgramModel {
|
|
|
374
432
|
argStructs: Set<string>;
|
|
375
433
|
/** FixedString capacities in use, for C typedefs and IDL types. */
|
|
376
434
|
fstrings: Set<number>;
|
|
435
|
+
/** FixedVec<T, N> types in use (IDL name -> type), for IDL types. */
|
|
436
|
+
fixedVecs: Map<string, FieldType & {
|
|
437
|
+
kind: "vec";
|
|
438
|
+
}>;
|
|
377
439
|
}
|
|
378
440
|
export declare function isWritable(k: ParamKind): boolean;
|
|
379
441
|
export declare function typeName(t: ValueType | FieldType): string;
|
|
@@ -386,3 +448,7 @@ export declare function optionKey(t: ScalarType | {
|
|
|
386
448
|
name: string;
|
|
387
449
|
}): string;
|
|
388
450
|
export declare function scalarKey(t: ScalarType): string;
|
|
451
|
+
/** The IDL type name of a FixedVec<T, N>: `FixedVec<N>_<T>`. */
|
|
452
|
+
export declare function fixedVecName(t: FieldType & {
|
|
453
|
+
kind: "vec";
|
|
454
|
+
}): string;
|
package/dist/compiler/model.js
CHANGED
|
@@ -56,7 +56,7 @@ export function typeName(t) {
|
|
|
56
56
|
case "enumRef":
|
|
57
57
|
return t.name;
|
|
58
58
|
case "vec":
|
|
59
|
-
return `Vec<${typeName(t.elem)}>`;
|
|
59
|
+
return t.cap === undefined ? `Vec<${typeName(t.elem)}>` : `FixedVec<${typeName(t.elem)}, ${t.cap}>`;
|
|
60
60
|
case "vecArg":
|
|
61
61
|
return `Vec<${typeName(t.elem)}>`;
|
|
62
62
|
case "arrayArg":
|
|
@@ -92,7 +92,7 @@ export function sameType(a, b) {
|
|
|
92
92
|
case "option":
|
|
93
93
|
return sameType(a.inner, b.inner);
|
|
94
94
|
case "vec":
|
|
95
|
-
return sameType(a.elem, b.elem);
|
|
95
|
+
return a.cap === b.cap && sameType(a.elem, b.elem);
|
|
96
96
|
case "vecArg":
|
|
97
97
|
return sameType(a.elem, b.elem);
|
|
98
98
|
case "arrayArg":
|
|
@@ -120,3 +120,9 @@ export function optionKey(t) {
|
|
|
120
120
|
export function scalarKey(t) {
|
|
121
121
|
return t.kind === "int" ? t.name : t.kind === "enum" ? `e_${t.name}` : t.kind === "pubkey" && t.hash ? "hash" : t.kind;
|
|
122
122
|
}
|
|
123
|
+
/** The IDL type name of a FixedVec<T, N>: `FixedVec<N>_<T>`. */
|
|
124
|
+
export function fixedVecName(t) {
|
|
125
|
+
return `FixedVec${t.cap}_${typeName(t.elem)
|
|
126
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
127
|
+
.replace(/_+$/, "")}`;
|
|
128
|
+
}
|
package/dist/portgen.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* It writes the account structs, enums, errors, events and one function per instruction with its accounts and
|
|
5
5
|
* arguments, bodies left to fill in. Instructions keep their discriminators (an explicit `@discriminator` when the
|
|
6
6
|
* name would derive another), account structs keep their names (so their discriminators), errors keep their order —
|
|
7
|
-
* so existing clients keep working
|
|
8
|
-
*
|
|
7
|
+
* so existing clients keep working. Account structs holding a String, Vec or Option get `@layout borsh`, so they keep
|
|
8
|
+
* Anchor's bytes (existing accounts stay readable); where a layout still differs (a String or Vec nested in a struct, a
|
|
9
|
+
* data enum, a COption) it says so in a note and a comment.
|
|
9
10
|
*/
|
|
10
11
|
import { createHash } from "node:crypto";
|
|
11
12
|
import { cNameProblem } from "./compiler/analyze.js";
|
|
@@ -83,6 +84,34 @@ export function generatePort(idl) {
|
|
|
83
84
|
for (const ix of idl.instructions)
|
|
84
85
|
for (const g of ix.args)
|
|
85
86
|
mark(g.type, "arg");
|
|
87
|
+
// Structs passed or returned whole (not inside another type): these can keep Anchor's Borsh bytes with @layout borsh.
|
|
88
|
+
const whole = new Set();
|
|
89
|
+
const nested = new Set();
|
|
90
|
+
const inside = (t) => {
|
|
91
|
+
if (!t || typeof t === "string")
|
|
92
|
+
return;
|
|
93
|
+
const inner = t.vec ?? t.option ?? t.coption ?? t.array?.[0];
|
|
94
|
+
if (inner)
|
|
95
|
+
return inside(inner);
|
|
96
|
+
if (t.defined)
|
|
97
|
+
nested.add(definedName(t.defined));
|
|
98
|
+
};
|
|
99
|
+
for (const ix of idl.instructions) {
|
|
100
|
+
for (const t of [...ix.args.map((g) => g.type), ix.returns]) {
|
|
101
|
+
if (t && typeof t === "object" && t.defined)
|
|
102
|
+
whole.add(definedName(t.defined));
|
|
103
|
+
else if (t && typeof t === "object")
|
|
104
|
+
inside(t.vec ?? t.option ?? t.coption ?? t.array?.[0]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const def of idl.types ?? []) {
|
|
108
|
+
for (const f of (def.type.fields ?? []))
|
|
109
|
+
if (typeof f === "object" && f && "type" in f)
|
|
110
|
+
inside(f.type);
|
|
111
|
+
for (const v of def.type.variants ?? [])
|
|
112
|
+
for (const f of v.fields ?? [])
|
|
113
|
+
inside(typeof f === "object" && f && "type" in f ? f.type : f);
|
|
114
|
+
}
|
|
86
115
|
const fixed = (w) => w === "account" || w === "struct" || w === "variant";
|
|
87
116
|
const describe = (w) => (w === "account" ? "account data" : w === "struct" ? "a struct argument" : "a data-enum variant");
|
|
88
117
|
const tsType = (t, where, at, last = false) => {
|
|
@@ -152,6 +181,57 @@ export function generatePort(idl) {
|
|
|
152
181
|
notes.push(`${at}: unsupported IDL type ${JSON.stringify(t)}; left as u64`);
|
|
153
182
|
return `${use("u64")} /* TODO */`;
|
|
154
183
|
};
|
|
184
|
+
/** A String, Vec or Option at a struct's top level: Borsh-sized in Anchor. */
|
|
185
|
+
const borshOnly = (t) => t === "string" || t === "bytes" || (typeof t === "object" && (!!t.vec || !!t.option));
|
|
186
|
+
/** A field's type in a `@layout borsh` struct, or undefined when that layout cannot hold it. Notes the capacities. */
|
|
187
|
+
const borshField = (t, at) => {
|
|
188
|
+
const quiet = at === "";
|
|
189
|
+
const fixedOk = (u) => {
|
|
190
|
+
if (typeof u === "string")
|
|
191
|
+
return INTS.has(u) || u === "bool" || u === "pubkey" || u === "publicKey";
|
|
192
|
+
if (u.array)
|
|
193
|
+
return Number.isInteger(Number(u.array[1])) && fixedOk(u.array[0]);
|
|
194
|
+
if (u.defined) {
|
|
195
|
+
const name = definedName(u.defined);
|
|
196
|
+
const d = defs.get(name);
|
|
197
|
+
if (!d || skipped.has(name) || helperDefs.has(name))
|
|
198
|
+
return false;
|
|
199
|
+
if (d.type.kind === "enum")
|
|
200
|
+
return (d.type.variants ?? []).every((v) => !v.fields?.length);
|
|
201
|
+
return (d.type.fields ?? []).every((f) => fixedOk(f.type));
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
};
|
|
205
|
+
if (t === "string") {
|
|
206
|
+
if (!quiet)
|
|
207
|
+
notes.push(`${at}: set the String's capacity to Anchor's #[max_len] (Borsh bytes are kept: @layout borsh)`);
|
|
208
|
+
return `${use("FixedString")}<32> /* TODO: #[max_len] */`;
|
|
209
|
+
}
|
|
210
|
+
if (t === "bytes")
|
|
211
|
+
return borshField({ vec: "u8" }, at);
|
|
212
|
+
if (typeof t === "object" && t.vec) {
|
|
213
|
+
const elem = t.vec === "string"
|
|
214
|
+
? `${use("FixedString")}<32> /* TODO: #[max_len] */`
|
|
215
|
+
: fixedOk(t.vec) && !quiet
|
|
216
|
+
? tsType(t.vec, "account", at)
|
|
217
|
+
: fixedOk(t.vec)
|
|
218
|
+
? "x"
|
|
219
|
+
: undefined;
|
|
220
|
+
if (elem === undefined)
|
|
221
|
+
return undefined;
|
|
222
|
+
if (!quiet)
|
|
223
|
+
notes.push(`${at}: set the Vec's capacity to Anchor's #[max_len] (Borsh bytes are kept: @layout borsh)`);
|
|
224
|
+
return `${use("FixedVec")}<${elem}, 8> /* TODO: #[max_len] */`;
|
|
225
|
+
}
|
|
226
|
+
if (typeof t === "object" && t.option) {
|
|
227
|
+
if (!fixedOk(t.option))
|
|
228
|
+
return undefined;
|
|
229
|
+
return quiet ? "x" : `${use("Option")}<${tsType(t.option, "account", at)}>`;
|
|
230
|
+
}
|
|
231
|
+
if (!fixedOk(t))
|
|
232
|
+
return undefined;
|
|
233
|
+
return quiet ? "x" : tsType(t, "account", at);
|
|
234
|
+
};
|
|
155
235
|
const esc = (s) => s.replace(/\*\//g, "* /");
|
|
156
236
|
const docs = (lines, pad = "") => (lines?.length ? `${pad}/** ${esc(lines.join(" "))} */\n` : "");
|
|
157
237
|
const out = [];
|
|
@@ -180,8 +260,13 @@ export function generatePort(idl) {
|
|
|
180
260
|
const where = used.has("account") ? "account" : used.has("arg") ? "struct" : used.has("event") ? "event" : "account";
|
|
181
261
|
if (def.type.kind === "struct") {
|
|
182
262
|
const fields = (def.type.fields ?? []);
|
|
263
|
+
// An account struct, or one passed / returned whole, with a String, Vec or Option of its own: `@layout borsh`
|
|
264
|
+
// keeps Anchor's bytes.
|
|
265
|
+
const borshable = accountNames.has(def.name) || (whole.has(def.name) && !nested.has(def.name) && !eventNames.has(def.name));
|
|
266
|
+
const borsh = borshable && !isTuple(fields) && fields.some((f) => borshOnly(f.type)) && fields.every((f) => borshField(f.type, "") !== undefined);
|
|
183
267
|
const tag = accountNames.has(def.name) ? " (account)" : eventNames.has(def.name) ? ` (event: emit<${typeName(def.name)}>({ … }))` : "";
|
|
184
|
-
|
|
268
|
+
const doc = def.docs?.length ? esc(def.docs.join(" ")) : `${def.name}${tag}`;
|
|
269
|
+
out.push(borsh ? `/**\n * ${doc}\n * @layout borsh\n */` : `/** ${doc} */`);
|
|
185
270
|
out.push(`export interface ${typeName(def.name)} {`);
|
|
186
271
|
const seen = new Set();
|
|
187
272
|
fields.forEach((f, i) => {
|
|
@@ -189,7 +274,8 @@ export function generatePort(idl) {
|
|
|
189
274
|
while (seen.has(n))
|
|
190
275
|
n = `${n}2`;
|
|
191
276
|
seen.add(n);
|
|
192
|
-
|
|
277
|
+
const ty = borsh ? borshField(f.type, `${def.name}.${f.name}`) : tsType(f.type, where, `${def.name}.${f.name}`, i === fields.length - 1);
|
|
278
|
+
out.push(`${docs(f.docs, " ")} ${n}: ${ty};`);
|
|
193
279
|
});
|
|
194
280
|
out.push("}", "");
|
|
195
281
|
}
|
package/docs/errors.md
CHANGED
|
@@ -11,22 +11,24 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
|
|
|
11
11
|
| 2009 | not the associated token account (ConstraintAssociated) |
|
|
12
12
|
| 2012 | not the expected address (ConstraintAddress) |
|
|
13
13
|
| 2014 / 2015 | token account mint / owner mismatch (ConstraintTokenMint / ConstraintTokenOwner) |
|
|
14
|
-
| 2016 / 2017 | mint authority / freeze authority mismatch |
|
|
14
|
+
| 2016 / 2017 / 2018 | mint authority / freeze authority / decimals mismatch (2018: an existing mint `createIfNeeded` found) |
|
|
15
15
|
| 2040 | the same account passed in two writable slots (ConstraintDuplicateMutableAccount) |
|
|
16
16
|
| 3000 | account already exists / initialized (AccountDiscriminatorAlreadySet) |
|
|
17
17
|
| 3002 | discriminator mismatch (AccountDiscriminatorMismatch) |
|
|
18
|
-
| 3003 | data too small, not an initialized token account / mint,
|
|
18
|
+
| 3003 | data too small, not an initialized token account / mint, a read past the end, or a `@layout borsh` account whose bytes are not a valid Borsh value (AccountDidNotDeserialize) |
|
|
19
|
+
| 3004 | a `@layout borsh` account too small for the value it would now hold (AccountDidNotSerialize) |
|
|
19
20
|
| 3005 | not enough accounts (AccountNotEnoughKeys) |
|
|
20
21
|
| 3006 | account must be writable (AccountNotMutable) |
|
|
21
22
|
| 3007 | wrong owner program (AccountOwnedByWrongProgram) |
|
|
22
23
|
| 3008 / 3009 | wrong program address / not executable (InvalidProgramId / InvalidProgramExecutable) |
|
|
23
|
-
| 3010 | account must sign (AccountNotSigner) |
|
|
24
|
+
| 3010 | account must sign (AccountNotSigner; also a new keypair account a `create(payer, null, …)` makes) |
|
|
25
|
+
| 3011 | a `SystemAccount` owned by another program (AccountNotSystemOwned) |
|
|
24
26
|
| 3012 | account not initialized: empty system account where a program account was expected (AccountNotInitialized) |
|
|
25
27
|
| 3016 | account would grow more than 10 KiB in one instruction, or grow without a payer (AccountReallocExceedsLimit) |
|
|
26
28
|
| 4102 | cast overflow (InvalidNumericConversion) |
|
|
27
29
|
| 5100 / 5101 | arithmetic overflow (including shifts) / division by zero |
|
|
28
30
|
| 5103 | sysvar unavailable |
|
|
29
|
-
| 5104 | index out of bounds (arrays, Vecs, remaining accounts, `pop()` on an empty Vec) |
|
|
31
|
+
| 5104 | index out of bounds (arrays, Vecs, remaining accounts, `pop()` on an empty Vec, `push` on a full FixedVec) |
|
|
30
32
|
| 5105 | string longer than the FixedString capacity |
|
|
31
33
|
| 5106 | missing or mismatched return data |
|
|
32
34
|
| 5107 | event larger than 1024 bytes |
|