@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,12 +1,14 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { resolveScalarType } from "../analyze.js";
|
|
2
|
+
import { calleeName, resolveScalarType } from "../analyze.js";
|
|
3
3
|
import { decodeAddress } from "../../keys.js";
|
|
4
4
|
import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
|
|
5
|
-
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, } from "./shared.js";
|
|
5
|
+
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, } from "./shared.js";
|
|
6
6
|
import { ExpressionEmitter } from "./expressions.js";
|
|
7
7
|
/**
|
|
8
8
|
* Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
|
|
9
9
|
*/
|
|
10
|
+
/** List methods taking a callback (or a value) that expand into a loop. */
|
|
11
|
+
const LIST_METHODS = new Set(["forEach", "some", "every", "find", "findIndex", "reduce", "includes", "indexOf"]);
|
|
10
12
|
export class CallEmitter extends ExpressionEmitter {
|
|
11
13
|
call(node, topLevel) {
|
|
12
14
|
const callee = node.expression;
|
|
@@ -126,14 +128,26 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
126
128
|
}
|
|
127
129
|
case "mulDiv":
|
|
128
130
|
case "mulDivCeil": {
|
|
129
|
-
// a * b / c with a 128-bit
|
|
131
|
+
// a * b / c with a double-width intermediate (128-bit for u64, 256-bit for u128), rounding down or up;
|
|
132
|
+
// aborts if the result does not fit. All three are u64, or all u128 (literals take the others' type).
|
|
130
133
|
if (args.length !== 3)
|
|
131
|
-
this.fail(node, `${builtin}(a, b, c) takes three u64 values`);
|
|
132
|
-
const
|
|
134
|
+
this.fail(node, `${builtin}(a, b, c) takes three u64 or three u128 values`);
|
|
135
|
+
const vals = [];
|
|
136
|
+
for (const a of args) {
|
|
137
|
+
const mark = this.lines.length;
|
|
138
|
+
const v = this.expr(a);
|
|
139
|
+
for (let j = 0; j < vals.length; j++)
|
|
140
|
+
vals[j] = this.settle(vals[j], mark);
|
|
141
|
+
vals.push(v);
|
|
142
|
+
}
|
|
143
|
+
const typed = vals.find((v) => v.literal === undefined);
|
|
144
|
+
const wide = typed?.type.kind === "int" && typed.type.name === "u128";
|
|
145
|
+
const ty = wide ? { kind: "int", name: "u128" } : U64;
|
|
146
|
+
const [x, y, z] = vals.map((v, i) => this.expect(v.literal !== undefined ? this.literal(v.literal, ty, args[i]) : v, ty, args[i]));
|
|
133
147
|
const t = this.tmp("md");
|
|
134
|
-
this.line(
|
|
135
|
-
this.line(`TS_TRY(ts_mul_div(${x.c}, ${y.c}, ${z.c}, ${builtin === "mulDivCeil"}, &${t}));`);
|
|
136
|
-
return { c: t, type:
|
|
148
|
+
this.line(`${wide ? "unsigned __int128" : "uint64_t"} ${t};`);
|
|
149
|
+
this.line(`TS_TRY(ts_mul_div${wide ? "_u128" : ""}(${x.c}, ${y.c}, ${z.c}, ${builtin === "mulDivCeil"}, &${t}));`);
|
|
150
|
+
return { c: t, type: ty };
|
|
137
151
|
}
|
|
138
152
|
case "isqrt": {
|
|
139
153
|
if (args.length !== 1)
|
|
@@ -219,7 +233,18 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
219
233
|
this.line(`TS_TRY(ts_t22_transfer_fee(${m}, ${amt.c}, &${t}));`);
|
|
220
234
|
return { c: t, type: U64 };
|
|
221
235
|
}
|
|
236
|
+
case "isTransferring": {
|
|
237
|
+
// TransferHookAccount (15) on a Token-2022 token account: set only while Token-2022 runs a transfer of it.
|
|
238
|
+
if (args.length !== 1)
|
|
239
|
+
this.fail(node, "isTransferring(tokenAccount) takes the source token account");
|
|
240
|
+
const m = this.anyAccount(args[0], "the token account");
|
|
241
|
+
this.line(`TS_TRY(ts_t22_layout(${m}, 2));`);
|
|
242
|
+
const off = this.tmp("eo");
|
|
243
|
+
this.line(`uint64_t ${off}, ${off}l;`);
|
|
244
|
+
return { c: `(ts_t22_ext(${m}, 15, &${off}, &${off}l) && ${off}l >= 1 && ${m}->data[${off}] != 0)`, type: BOOL };
|
|
245
|
+
}
|
|
222
246
|
case "transferHookProgram":
|
|
247
|
+
case "transferHookAuthority":
|
|
223
248
|
case "permanentDelegate":
|
|
224
249
|
case "mintCloseAuthority": {
|
|
225
250
|
// Token-2022 mint extensions holding an optional Pubkey: TransferHook (14, program at +32),
|
|
@@ -228,7 +253,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
228
253
|
this.fail(node, `${builtin}(mint) takes the mint`);
|
|
229
254
|
const m = this.anyAccount(args[0], "the mint");
|
|
230
255
|
this.line(`TS_TRY(ts_t22_layout(${m}, 1));`);
|
|
231
|
-
const [type, field] = builtin === "transferHookProgram" ? [14, 32] : builtin === "permanentDelegate" ? [12, 0] : [3, 0];
|
|
256
|
+
const [type, field] = builtin === "transferHookProgram" ? [14, 32] : builtin === "transferHookAuthority" ? [14, 0] : builtin === "permanentDelegate" ? [12, 0] : [3, 0];
|
|
232
257
|
this.model.options.set("pubkey", PUBKEY);
|
|
233
258
|
const t = this.tmp("ext");
|
|
234
259
|
this.line(`${optC(PUBKEY)} ${t};`);
|
|
@@ -364,13 +389,14 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
364
389
|
this.line(`if (ts_find_pda(${seeds.name}, ${seeds.count}, ${program.c}, &${t}, &${b}) != SUCCESS) TS_FAIL(TS_ERR_SEEDS, "TypeSOL: no program address for these seeds");`);
|
|
365
390
|
return { c: `(&${t})`, type: PUBKEY };
|
|
366
391
|
}
|
|
367
|
-
case "metadataAddress":
|
|
392
|
+
case "metadataAddress":
|
|
393
|
+
case "masterEditionAddress": {
|
|
368
394
|
if (args.length !== 1)
|
|
369
|
-
this.fail(node,
|
|
395
|
+
this.fail(node, `${builtin}(mint) takes the mint`);
|
|
370
396
|
const m = this.expect(this.expr(args[0], PUBKEY), PUBKEY, args[0]);
|
|
371
397
|
const t = this.tmp("md");
|
|
372
398
|
this.line(`SolPubkey ${t};`);
|
|
373
|
-
this.line(`TS_TRY(ts_metadata_address(${m.c}, &${t}));`);
|
|
399
|
+
this.line(`TS_TRY(${builtin === "metadataAddress" ? "ts_metadata_address" : "ts_master_edition_address"}(${m.c}, &${t}));`);
|
|
374
400
|
return { c: `(&${t})`, type: PUBKEY };
|
|
375
401
|
}
|
|
376
402
|
case "initExtraAccountMetas":
|
|
@@ -433,10 +459,22 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
433
459
|
}
|
|
434
460
|
if (builtin)
|
|
435
461
|
this.fail(callee, `${builtin} cannot be called here`);
|
|
436
|
-
if (ts.isIdentifier(callee)
|
|
437
|
-
|
|
462
|
+
if (ts.isIdentifier(callee)) {
|
|
463
|
+
const local = this.lookup(callee.text);
|
|
464
|
+
if (local?.closure)
|
|
465
|
+
return this.expandClosure(local.closure, node.arguments, node);
|
|
466
|
+
}
|
|
467
|
+
const helper = this.helperOf(node);
|
|
468
|
+
if (helper)
|
|
469
|
+
return this.callHelper(node, helper);
|
|
438
470
|
if (ts.isPropertyAccessExpression(callee)) {
|
|
439
471
|
const method = callee.name.text;
|
|
472
|
+
// xs.some(…) and friends on a Vec, FixedArray, FixedVec or Vec argument: a loop with the callback expanded in it.
|
|
473
|
+
if (LIST_METHODS.has(method)) {
|
|
474
|
+
const l = ts.isIdentifier(callee.expression) ? this.lookup(callee.expression.text) : undefined;
|
|
475
|
+
if (l?.type.kind === "vecArg" || l?.type.kind === "array" || this.peekArray(callee.expression))
|
|
476
|
+
return this.listMethod(node, method);
|
|
477
|
+
}
|
|
440
478
|
// Vec<T> instruction arguments are read-only views.
|
|
441
479
|
if (ts.isIdentifier(callee.expression)) {
|
|
442
480
|
const l = this.lookup(callee.expression.text);
|
|
@@ -444,6 +482,10 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
444
482
|
if (method !== "get" || args.length !== 1)
|
|
445
483
|
this.fail(node, "Vec arguments are read-only: use .get(i) and .length");
|
|
446
484
|
const elem = l.type.elem;
|
|
485
|
+
if (elem.kind === "string") {
|
|
486
|
+
const idx = this.index(args[0], `(uint64_t)${l.c}.len`);
|
|
487
|
+
return { c: `(((const TS_Str *)${l.c}.ptr)[${idx}])`, type: STRING };
|
|
488
|
+
}
|
|
447
489
|
if (!isScalar(elem))
|
|
448
490
|
this.fail(node, `read a field of the element: ${callee.expression.text}.get(i).field`);
|
|
449
491
|
const idx = this.index(args[0], `(uint64_t)${l.c}.len`);
|
|
@@ -504,8 +546,10 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
504
546
|
// Program accounts used through their methods (systemProgram.transfer, tokenProgram.*…) may be optional.
|
|
505
547
|
if (target?.optional)
|
|
506
548
|
this.assertUsable(target, callee.expression);
|
|
507
|
-
if (target?.kind.kind === "token"
|
|
508
|
-
|
|
549
|
+
if ((target?.kind.kind === "token" || target?.kind.kind === "mint") &&
|
|
550
|
+
target.kind.init &&
|
|
551
|
+
method === (target.kind.init === "create" ? "create" : "createIfNeeded"))
|
|
552
|
+
return target.kind.kind === "token" && target.constraints.ata ? this.createAta(node, target, topLevel) : this.createToken(node, target, topLevel);
|
|
509
553
|
if (target?.kind.kind === "init" && method === "create")
|
|
510
554
|
return this.create(node, target, topLevel);
|
|
511
555
|
if (target?.kind.kind === "initIfNeeded" && method === "createIfNeeded")
|
|
@@ -520,10 +564,14 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
520
564
|
return this.ataOp(node, target, method);
|
|
521
565
|
if (target?.kind.kind === "metadataProgram")
|
|
522
566
|
return this.metadataOp(node, target, method);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
567
|
+
// Each Metaplex CPI in its own C block: its data buffer's stack slot is reused by the next one.
|
|
568
|
+
if (target?.kind.kind === "coreProgram" || target?.kind.kind === "bubblegumProgram") {
|
|
569
|
+
const t = target;
|
|
570
|
+
this.line("{");
|
|
571
|
+
this.block(() => (t.kind.kind === "coreProgram" ? this.coreOp(node, t, method) : this.bubblegumOp(node, t, method)));
|
|
572
|
+
this.line("}");
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
527
575
|
}
|
|
528
576
|
}
|
|
529
577
|
if (ts.isPropertyAccessExpression(callee) && ts.isCallExpression(callee.expression) && this.exportName(callee.expression.expression) === "fixedArray")
|
|
@@ -536,7 +584,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
536
584
|
const args = node.arguments;
|
|
537
585
|
const vt = v.type;
|
|
538
586
|
const info = v.vec;
|
|
539
|
-
const elemRef = (idx) => ({ ...v, lv: `${
|
|
587
|
+
const elemRef = (idx) => this.withFixedVec({ ...v, lv: `${info.items}[${idx}]`, type: vt.elem, vec: undefined, path: undefined });
|
|
588
|
+
const count = vecCount(info);
|
|
540
589
|
/**
|
|
541
590
|
* Evaluate the value into a temporary first (JS evaluates arguments before the call runs), then return a
|
|
542
591
|
* writer for the element: `push(v.length)` sees the old length, `set(i, pop())` checks i after the pop.
|
|
@@ -564,8 +613,10 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
564
613
|
// Elements removed or replaced here must not be referenced afterwards.
|
|
565
614
|
const elemPaths = () => (ts.isPropertyAccessExpression(node.expression) ? this.pathsOf(node.expression.expression)?.map((p) => `${p}[]`) : undefined);
|
|
566
615
|
const resize = (newLen, payer) => {
|
|
616
|
+
if (info.cap !== undefined)
|
|
617
|
+
return; // a FixedVec has its room already
|
|
567
618
|
if (!info.resizable || !v.account)
|
|
568
|
-
this.fail(node, "only Vecs of
|
|
619
|
+
this.fail(node, "only Vecs of Mut<...> / Init<...> accounts can grow or shrink");
|
|
569
620
|
this.line(`TS_TRY(ts_resize(params, ${payer}, a_${v.account.name}, 8 + ${info.fixed} + (${newLen}) * ${info.elemSize}, entry_${v.account.name}));`);
|
|
570
621
|
};
|
|
571
622
|
switch (method) {
|
|
@@ -577,11 +628,23 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
577
628
|
this.assertNotAliased(elemPaths(), node);
|
|
578
629
|
const i = this.indexValue(args[0]);
|
|
579
630
|
const write = stageElem(args[1]);
|
|
580
|
-
this.checkIndex(i,
|
|
631
|
+
this.checkIndex(i, count);
|
|
581
632
|
write(elemRef(i));
|
|
582
633
|
return undefined;
|
|
583
634
|
}
|
|
584
635
|
case "push": {
|
|
636
|
+
if (info.cap !== undefined) {
|
|
637
|
+
if (args.length !== 1)
|
|
638
|
+
this.fail(node, "push(value) on a FixedVec takes the value (it has room for its capacity already)");
|
|
639
|
+
needWritable();
|
|
640
|
+
const write = stageElem(args[0]);
|
|
641
|
+
const n = this.tmp("n");
|
|
642
|
+
this.line(`uint64_t ${n} = ${count};`);
|
|
643
|
+
this.line(`if (${n} >= ${info.cap}) TS_FAIL(TS_ERR_INDEX, "TypeSOL: push() on a full FixedVec");`);
|
|
644
|
+
this.line(`${info.len} = (uint32_t)(${n} + 1);`);
|
|
645
|
+
write(elemRef(n));
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
585
648
|
if (args.length !== 2)
|
|
586
649
|
this.fail(node, "push(value, payer) takes the value and a Mut<Signer> that pays for the extra rent");
|
|
587
650
|
needWritable();
|
|
@@ -604,7 +667,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
604
667
|
this.assertNotAliased(elemPaths(), node);
|
|
605
668
|
const st = this.storageValueType(vt.elem);
|
|
606
669
|
const n = this.tmp("n");
|
|
607
|
-
this.line(`uint64_t ${n} =
|
|
670
|
+
this.line(`uint64_t ${n} = ${count};`);
|
|
608
671
|
this.line(`if (${n} == 0) TS_FAIL(TS_ERR_INDEX, "TypeSOL: pop() on an empty Vec");`);
|
|
609
672
|
// Structs and arrays are dropped (pop() as a statement); read .get(length - 1) first to keep one.
|
|
610
673
|
let result;
|
|
@@ -836,6 +899,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
836
899
|
const s = targ && ts.isTypeReferenceNode(targ) && ts.isIdentifier(targ.typeName) ? this.model.structs.get(targ.typeName.text) : undefined;
|
|
837
900
|
if (!s || s.size === undefined || s.vec)
|
|
838
901
|
this.fail(node, "loadForeign needs a fixed-size struct type: loadForeign<Pool>(account, owner)");
|
|
902
|
+
if (s.layout === "borsh")
|
|
903
|
+
this.fail(node, `${s.name} is @layout borsh: loadForeign reads fixed layouts only`);
|
|
839
904
|
if (node.arguments.length !== 2)
|
|
840
905
|
this.fail(node, "loadForeign<T>(account, owner) takes the account and its owning program's address");
|
|
841
906
|
const h = this.anyAccount(node.arguments[0], "loadForeign's account");
|
|
@@ -857,6 +922,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
857
922
|
const s = targ && ts.isTypeReferenceNode(targ) && ts.isIdentifier(targ.typeName) ? this.model.structs.get(targ.typeName.text) : undefined;
|
|
858
923
|
if (!s || s.size === undefined)
|
|
859
924
|
this.fail(node, "loadAccount needs an account struct type: loadAccount<MyStruct>(account)");
|
|
925
|
+
if (s.layout === "borsh")
|
|
926
|
+
this.fail(node, `${s.name} is @layout borsh: declare it as an instruction parameter (remaining accounts are read in place)`);
|
|
860
927
|
if (node.arguments.length !== 1)
|
|
861
928
|
this.fail(node, "loadAccount<T>(account) takes one remaining account");
|
|
862
929
|
const h = this.handle(node.arguments[0]);
|
|
@@ -874,16 +941,45 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
874
941
|
}
|
|
875
942
|
return { c: `((TSS_${s.name} *)(${h}->data + 8))`, type: { kind: "structRef", struct: s.name, writable: mut } };
|
|
876
943
|
}
|
|
944
|
+
/** Whether a helper (or one it calls) grows a Vec, which needs the instruction's SystemProgram for rent. */
|
|
945
|
+
helperGrows(body, seen = new Set()) {
|
|
946
|
+
if (seen.has(body))
|
|
947
|
+
return false;
|
|
948
|
+
seen.add(body);
|
|
949
|
+
let found = false;
|
|
950
|
+
const visit = (n) => {
|
|
951
|
+
if (found)
|
|
952
|
+
return;
|
|
953
|
+
if (ts.isCallExpression(n)) {
|
|
954
|
+
// Vec.push(value, payer) grows the account; FixedVec.push(value) does not.
|
|
955
|
+
if (ts.isPropertyAccessExpression(n.expression) && n.expression.name.text === "push" && n.arguments.length === 2)
|
|
956
|
+
found = true;
|
|
957
|
+
else if (ts.isIdentifier(n.expression)) {
|
|
958
|
+
const name = calleeName(n.expression, this.model.helperDecls, this.checker);
|
|
959
|
+
const callee = name ? (this.model.generics.get(name)?.node ?? this.model.helpers.get(name)?.node) : undefined;
|
|
960
|
+
if (callee && this.helperGrows(callee, seen))
|
|
961
|
+
found = true;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
ts.forEachChild(n, visit);
|
|
965
|
+
};
|
|
966
|
+
visit(body);
|
|
967
|
+
return found;
|
|
968
|
+
}
|
|
877
969
|
callHelper(node, h) {
|
|
878
|
-
if (
|
|
879
|
-
this.fail(node, `${h.
|
|
970
|
+
if (this.ix && !this.ix.accounts.some((a) => a.kind.kind === "system" && !a.optional) && h.params.some((p) => p.account) && this.helperGrows(h.node))
|
|
971
|
+
this.fail(node, `${h.display}() grows a Vec (push): the instruction needs a SystemProgram parameter to top up rent`);
|
|
972
|
+
// Function arguments were bound when the helper was instantiated: they have no C parameter.
|
|
973
|
+
const argNodes = h.fnArgIndexes ? node.arguments.filter((_, i) => !h.fnArgIndexes.includes(i)) : node.arguments;
|
|
974
|
+
if (argNodes.length !== h.params.length)
|
|
975
|
+
this.fail(node, `${h.display}() takes ${h.params.length} argument(s)`);
|
|
880
976
|
// Arguments are evaluated left to right: settle earlier ones when a later one runs statements.
|
|
881
977
|
// A helper that takes accounts may change account data, so account text it gets is copied first.
|
|
882
978
|
const takesAccounts = h.params.some((p) => p.account);
|
|
883
979
|
const values = [];
|
|
884
980
|
const pieces = [];
|
|
885
981
|
h.params.forEach((p, i) => {
|
|
886
|
-
const n =
|
|
982
|
+
const n = argNodes[i];
|
|
887
983
|
const mark = this.lines.length;
|
|
888
984
|
const raw = this.helperArg(h, p, n);
|
|
889
985
|
for (let j = 0; j < values.length; j++)
|
|
@@ -897,8 +993,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
897
993
|
});
|
|
898
994
|
// A helper that takes a writable struct account may replace anything stored in it (see assertNotAliased).
|
|
899
995
|
for (const [i, p] of h.params.entries())
|
|
900
|
-
if (p.account && p.account.kind === "account" && p.account.mut && ts.isIdentifier(
|
|
901
|
-
this.assertNotAliased([
|
|
996
|
+
if (p.account && p.account.kind === "account" && p.account.mut && ts.isIdentifier(argNodes[i]))
|
|
997
|
+
this.assertNotAliased([argNodes[i].text], node, h.display);
|
|
902
998
|
const args = ["params", ...pieces.map((x) => (typeof x === "number" ? (values[x].type.kind === "pubkey" ? `*(${values[x].c})` : values[x].c) : x))];
|
|
903
999
|
if (!h.returns) {
|
|
904
1000
|
this.line(`TS_TRY(fn_${h.name}(${args.join(", ")}));`);
|
|
@@ -912,6 +1008,11 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
912
1008
|
this.line(`TS_TRY(fn_${h.name}(${[...args, `&${t}`].join(", ")}));`);
|
|
913
1009
|
return { c: `(&${t})`, type: { kind: "structRef", struct: h.returns.name, writable: true } };
|
|
914
1010
|
}
|
|
1011
|
+
if (h.returns.kind === "string") {
|
|
1012
|
+
this.line(`TS_Str ${t};`);
|
|
1013
|
+
this.line(`TS_TRY(fn_${h.name}(${[...args, `&${t}`].join(", ")}));`);
|
|
1014
|
+
return { c: t, type: STRING };
|
|
1015
|
+
}
|
|
915
1016
|
this.line(`${scalarC(h.returns)} ${t};`);
|
|
916
1017
|
this.line(`TS_TRY(fn_${h.name}(${[...args, `&${t}`].join(", ")}));`);
|
|
917
1018
|
return h.returns.kind === "pubkey" ? { c: `(&${t})`, type: h.returns } : { c: t, type: h.returns };
|
|
@@ -923,6 +1024,11 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
923
1024
|
const why = accountMismatch(acct.kind, p.account);
|
|
924
1025
|
if (why)
|
|
925
1026
|
this.fail(n, `${h.name}() takes ${p.name} as ${why}`);
|
|
1027
|
+
// A @layout borsh account goes with the struct it is being worked on as.
|
|
1028
|
+
if (p.account.kind === "account" && p.account.struct.layout === "borsh")
|
|
1029
|
+
return `a_${acct.name}, d_${acct.name}`;
|
|
1030
|
+
if (p.account.kind === "account" && p.account.mut && p.account.struct.vec)
|
|
1031
|
+
return `a_${acct.name}, entry_${acct.name}`;
|
|
926
1032
|
return `a_${acct.name}`;
|
|
927
1033
|
}
|
|
928
1034
|
switch (p.type.kind) {
|
|
@@ -2,6 +2,13 @@ import ts from "typescript";
|
|
|
2
2
|
import { type AccountParam, type IdlSeed, type ValueType } from "../model.js";
|
|
3
3
|
import { type Value } from "./shared.js";
|
|
4
4
|
import { CallEmitter } from "./calls.js";
|
|
5
|
+
/** One piece of a Metaplex instruction's Borsh data: fixed bytes, or a value lowered and serialized in source order. */
|
|
6
|
+
type MplField = {
|
|
7
|
+
bytes: number[];
|
|
8
|
+
} | {
|
|
9
|
+
value: ts.Expression;
|
|
10
|
+
type: ValueType;
|
|
11
|
+
};
|
|
5
12
|
/**
|
|
6
13
|
* Other programs: PDAs and seeds, account creation, System/Token/ATA/Metaplex CPIs, generic invoke, Borsh
|
|
7
14
|
* serialization and events.
|
|
@@ -19,6 +26,25 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
19
26
|
protected describeSeed(el: ts.Expression): IdlSeed | undefined;
|
|
20
27
|
protected recordPda(account: AccountParam, idl: IdlSeed[] | undefined): void;
|
|
21
28
|
protected create(node: ts.CallExpression, target: AccountParam, topLevel: boolean, ifNeeded?: boolean): Value;
|
|
29
|
+
/**
|
|
30
|
+
* `vault.create(payer, seeds | null, mint, authority)` on `Init<TokenAccount>` and `mint.create(payer, seeds | null,
|
|
31
|
+
* decimals, authority, freezeAuthority | null)` on `Init<Mint>` (Anchor's `init` with `token::` / `mint::`
|
|
32
|
+
* constraints): a PDA of this program (or, with `null`, a new keypair account that signs), created owned by the
|
|
33
|
+
* instruction's token program and initialized. `createIfNeeded` on `InitIfNeeded<…>` does that unless the account
|
|
34
|
+
* exists, and then checks it is that PDA, owned by the token program, and has that mint and owner / those
|
|
35
|
+
* authorities and decimals (Anchor's init_if_needed checks). create returns the bump (0 for a keypair),
|
|
36
|
+
* createIfNeeded whether it created the account.
|
|
37
|
+
*/
|
|
38
|
+
protected createToken(node: ts.CallExpression, target: AccountParam, topLevel: boolean): Value;
|
|
39
|
+
/**
|
|
40
|
+
* The extensions in `mint.create(…, { … })` (Anchor's `extensions::…` constraints): their values are evaluated now,
|
|
41
|
+
* in source order; the returned lines (each extension's Token-2022 initializer, mint writable, before
|
|
42
|
+
* InitializeMint2) run after the account is created, sized for exactly these extensions.
|
|
43
|
+
*/
|
|
44
|
+
protected mintExtensions(lit: ts.ObjectLiteralExpression, tp: string, acct: string): {
|
|
45
|
+
space: number;
|
|
46
|
+
lines: string[];
|
|
47
|
+
};
|
|
22
48
|
/**
|
|
23
49
|
* `ata.create(payer)` / `ata.createIfNeeded(payer)` on `Ata<Init|InitIfNeeded<TokenAccount>, "owner", "mint">`
|
|
24
50
|
* (Anchor's `init` / `init_if_needed` with `associated_token::…`). create always calls the ATA program's Create, which
|
|
@@ -30,10 +56,17 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
30
56
|
protected systemTransfer(node: ts.CallExpression): undefined;
|
|
31
57
|
/** systemProgram.createAccount(payer, account, lamports, space, owner, signerSeeds?) */
|
|
32
58
|
protected createAccount(node: ts.CallExpression, system: AccountParam): undefined;
|
|
33
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Emit a CPI to the program account `program` with these metas (account, writable, signer), and then (`tail`) a range
|
|
61
|
+
* of the remaining accounts, read-only (a Merkle proof).
|
|
62
|
+
*/
|
|
34
63
|
protected cpi(program: string, metas: [string, boolean, boolean][], buf: string, len: string, seeds: {
|
|
35
64
|
name: string;
|
|
36
65
|
count: number | string;
|
|
66
|
+
}, tail?: {
|
|
67
|
+
from: string;
|
|
68
|
+
to: string;
|
|
69
|
+
asPassed?: boolean;
|
|
37
70
|
}): void;
|
|
38
71
|
protected signerSeeds(node: ts.Expression | undefined, authority: AccountParam, at: ts.Node): {
|
|
39
72
|
name: string;
|
|
@@ -47,25 +80,41 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
47
80
|
protected tokenOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
48
81
|
protected ataOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
49
82
|
protected metadataOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
83
|
+
/** CreateMasterEditionV3 (17) and Verify (52) with VerificationArgs::CollectionV1. */
|
|
84
|
+
protected metadataCollectionOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
50
85
|
/**
|
|
51
86
|
* One account of a Metaplex CPI: an account parameter, or `null` for an optional account left out
|
|
52
87
|
* (Metaplex programs take their own id in its place).
|
|
53
88
|
*/
|
|
54
89
|
protected mplMeta(n: ts.Expression, program: AccountParam, what: string, writable: boolean, signer: boolean): [string, boolean, boolean];
|
|
55
90
|
/** Borsh fields into a fresh buffer: raw bytes ("u8:N" style) or lowered values. */
|
|
56
|
-
protected mplData(at: ts.Node, max: number, fields:
|
|
57
|
-
bytes: number[];
|
|
58
|
-
} | {
|
|
59
|
-
value: ts.Expression;
|
|
60
|
-
type: ValueType;
|
|
61
|
-
})[]): {
|
|
91
|
+
protected mplData(at: ts.Node, max: number, fields: MplField[]): {
|
|
62
92
|
buf: string;
|
|
63
93
|
len: string;
|
|
64
94
|
};
|
|
65
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Borsh of one Metaplex Core plugin literal (`{ kind: "FreezeDelegate", frozen: true, authority?: … }`): the Plugin
|
|
97
|
+
* enum, then (`withAuthority`) the Option<PluginAuthority> that CreateV1's PluginAuthorityPair and AddPluginV1 take.
|
|
98
|
+
*/
|
|
99
|
+
protected corePlugin(n: ts.Expression, withAuthority: boolean): MplField[];
|
|
100
|
+
/** CreateV1 / CreateCollectionV1 plugins: Option<Vec<PluginAuthorityPair>>. */
|
|
101
|
+
protected corePluginList(n: ts.Expression | undefined): MplField[];
|
|
102
|
+
/** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
|
|
66
103
|
protected coreOp(node: ts.CallExpression, core: AccountParam, method: string): undefined;
|
|
104
|
+
/** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
|
|
105
|
+
protected corePluginType(n: ts.Expression): MplField;
|
|
67
106
|
/** Bubblegum (compressed NFTs on SPL account compression): createTree, mint (MintV1, no collection, no creators). */
|
|
68
107
|
protected bubblegumOp(node: ts.CallExpression, bg: AccountParam, method: string): undefined;
|
|
108
|
+
/** Bubblegum V2 (trees on mpl-account-compression with mpl-noop; optional Metaplex Core collections). */
|
|
109
|
+
protected bubblegumV2(node: ts.CallExpression, bg: AccountParam, method: string): undefined;
|
|
110
|
+
/** Object literal properties by name, refusing others. */
|
|
111
|
+
protected literalProps(o: ts.ObjectLiteralExpression, allowed: string[], what: string): Map<string, ts.Expression>;
|
|
112
|
+
/** Vec<Creator> (address, verified, share) from a literal list; shares add up to 100 when literal. */
|
|
113
|
+
protected bubblegumCreators(creators: ts.Expression | undefined): MplField[];
|
|
114
|
+
/** UpdateArgs: every field optional (left out or null: unchanged). */
|
|
115
|
+
protected bubblegumUpdateArgs(n: ts.Expression): MplField[];
|
|
116
|
+
/** `acct.key` of an account parameter, as an expression to serialize. */
|
|
117
|
+
protected keyOf(n: ts.Expression): ts.Expression;
|
|
69
118
|
/**
|
|
70
119
|
* `remainingAccounts()` (or a const bound to it): every remaining account; `rest.slice(a, b?)`: indexes a..b of them
|
|
71
120
|
* (both checked against the count, 5104). C expressions for the range, relative to the first remaining account.
|
|
@@ -94,8 +143,13 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
94
143
|
authority: string;
|
|
95
144
|
program: string;
|
|
96
145
|
}): undefined;
|
|
146
|
+
/** emit<T> of a @layout borsh struct: built like account data, then packed as Anchor serializes it. */
|
|
147
|
+
private emitBorshEvent;
|
|
148
|
+
/** Discriminator + `body`, logged (emit) or self-invoked (emitCpi). */
|
|
149
|
+
private sendEvent;
|
|
97
150
|
/** Structs reachable through the fields of struct `name` (not including itself unless it recurses). */
|
|
98
151
|
protected nestedStructs(name: string): Set<string>;
|
|
99
152
|
/** Structs whose IDL description is the fixed layout: account data, arguments, and everything nested in them. */
|
|
100
153
|
protected fixedLayoutStructs(): Set<string>;
|
|
101
154
|
}
|
|
155
|
+
export {};
|