@typesol/cli 0.22.0 → 0.24.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +13 -12
  3. package/SECURITY.md +1 -1
  4. package/dist/clientgen.js +190 -41
  5. package/dist/compiler/analyze.js +195 -25
  6. package/dist/compiler/emit/base.d.ts +28 -5
  7. package/dist/compiler/emit/base.js +81 -15
  8. package/dist/compiler/emit/calls.d.ts +18 -1
  9. package/dist/compiler/emit/calls.js +159 -7
  10. package/dist/compiler/emit/cpi.d.ts +23 -0
  11. package/dist/compiler/emit/cpi.js +274 -5
  12. package/dist/compiler/emit/expressions.d.ts +9 -1
  13. package/dist/compiler/emit/expressions.js +25 -12
  14. package/dist/compiler/emit/function.js +8 -4
  15. package/dist/compiler/emit/program.js +9 -2
  16. package/dist/compiler/emit/shared.d.ts +43 -3
  17. package/dist/compiler/emit/shared.js +43 -0
  18. package/dist/compiler/emit/statements.d.ts +39 -2
  19. package/dist/compiler/emit/statements.js +346 -87
  20. package/dist/compiler/emit/storage.d.ts +6 -2
  21. package/dist/compiler/emit/storage.js +75 -45
  22. package/dist/compiler/model.d.ts +26 -1
  23. package/dist/idlpublish.d.ts +6 -1
  24. package/dist/idlpublish.js +47 -5
  25. package/dist/portgen.js +1 -1
  26. package/dist/svmrpc.d.ts +7 -1
  27. package/dist/svmrpc.js +133 -19
  28. package/dist/testing.js +23 -3
  29. package/docs/cli.md +2 -2
  30. package/docs/client.md +13 -10
  31. package/docs/errors.md +9 -3
  32. package/docs/language.md +21 -20
  33. package/docs/mainnet.md +1 -1
  34. package/docs/migrate-from-anchor.md +8 -9
  35. package/docs/test-your-program.md +6 -2
  36. package/docs/testing.md +1 -1
  37. package/docs/troubleshooting.md +3 -2
  38. package/lib/typesol.d.ts +127 -20
  39. package/package.json +1 -1
  40. package/runtime/typesol_rt.h +73 -18
@@ -1,9 +1,9 @@
1
1
  import ts from "typescript";
2
- import { calleeName, resolveScalarType } from "../analyze.js";
2
+ import { calleeName, resolveScalarType, resolveValueType } from "../analyze.js";
3
3
  import { ANCHOR_ERRORS } from "../anchor-errors.js";
4
4
  import { decodeAddress } from "../../keys.js";
5
5
  import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
6
- import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, } from "./shared.js";
6
+ import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, emptySizeC, vecLenCheck, } from "./shared.js";
7
7
  import { ExpressionEmitter } from "./expressions.js";
8
8
  /**
9
9
  * Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
@@ -195,7 +195,7 @@ export class CallEmitter extends ExpressionEmitter {
195
195
  if (!isWritable(acct.kind) || !struct)
196
196
  this.fail(args[0], "realloc needs a Mut<Struct> account of this program");
197
197
  if (struct?.vec)
198
- this.fail(args[0], `${struct.name} ends in a Vec, which sizes the account itself (push/pop/clear)`);
198
+ this.fail(args[0], `${struct.name} has a growable Vec, which sizes the account itself (push/pop/clear)`);
199
199
  const size = this.expect(this.expr(args[1], U64), U64, args[1]);
200
200
  const payer = this.accountParam(args[2], "the payer");
201
201
  if (payer.kind.kind !== "signer" || !payer.kind.mut)
@@ -460,11 +460,25 @@ export class CallEmitter extends ExpressionEmitter {
460
460
  }
461
461
  if (builtin)
462
462
  this.fail(callee, `${builtin} cannot be called here`);
463
+ // `adder(5n)(1n)`: the function a function returns, called right away.
464
+ if (ts.isCallExpression(callee)) {
465
+ const made = this.madeFunction(callee);
466
+ if (made)
467
+ return this.expandClosure(made, node.arguments, node);
468
+ }
463
469
  if (ts.isIdentifier(callee)) {
464
470
  const local = this.lookup(callee.text);
471
+ if (local?.closure && this.returnedFunction(local.closure.node))
472
+ this.fail(node, `${callee.text} returns a function: bind it (const f = ${callee.text}(…)) or call it right away (${callee.text}(…)(…))`);
465
473
  if (local?.closure)
466
474
  return this.expandClosure(local.closure, node.arguments, node);
475
+ const name = calleeName(callee, this.model.helperDecls, this.checker) ?? callee.text;
476
+ if (!local && this.model.factories.has(name))
477
+ this.fail(node, `${callee.text} returns a function: bind it (const f = ${callee.text}(…)) or call it right away (${callee.text}(…)(…))`);
467
478
  }
479
+ const inlined = this.helperWithClosure(node);
480
+ if (inlined)
481
+ return inlined.value;
468
482
  const helper = this.helperOf(node);
469
483
  if (helper)
470
484
  return this.callHelper(node, helper);
@@ -473,9 +487,12 @@ export class CallEmitter extends ExpressionEmitter {
473
487
  // xs.some(…) and friends on a Vec, FixedArray, FixedVec or Vec argument: a loop with the callback expanded in it.
474
488
  if (LIST_METHODS.has(method)) {
475
489
  const l = ts.isIdentifier(callee.expression) ? this.lookup(callee.expression.text) : undefined;
476
- if (l?.type.kind === "vecArg" || l?.type.kind === "array" || this.peekArray(callee.expression))
490
+ if (l?.type.kind === "vecArg" || l?.type.kind === "array" || this.isPipeline(callee.expression) || this.peekArray(callee.expression))
477
491
  return this.listMethod(node, method);
478
492
  }
493
+ // A map / filter chain is consumed by a list method, `.length` or for…of, or stored: const ys = xs.map(…).
494
+ if (this.isPipeline(node))
495
+ this.fail(node, `${method} builds a new list: store it (const ys = xs.${method}(…)), or use it right away with a list method, .length or for…of`);
479
496
  // Vec<T> instruction arguments are read-only views.
480
497
  if (ts.isIdentifier(callee.expression)) {
481
498
  const l = this.lookup(callee.expression.text);
@@ -620,6 +637,21 @@ export class CallEmitter extends ExpressionEmitter {
620
637
  this.fail(node, "only Vecs of Mut<...> / Init<...> accounts can grow or shrink");
621
638
  this.line(`TS_TRY(ts_resize(params, ${payer}, a_${v.account.name}, 8 + ${info.fixed} + (${newLen}) * ${info.elemSize}, entry_${v.account.name}));`);
622
639
  };
640
+ /**
641
+ * The growable Vecs stored after this one move by `by` bytes (+ grows, - shrinks): where they start and how many
642
+ * bytes they take, read before the resize; references into them cannot outlive this call.
643
+ */
644
+ const tail = info.tail;
645
+ const tailStart = () => {
646
+ if (!tail)
647
+ return undefined;
648
+ this.assertNotAliased(tail.paths, node);
649
+ const at = this.tmp("tail");
650
+ const n = this.tmp("tail_n");
651
+ this.line(`uint8_t *${at} = ${tail.end};`);
652
+ this.line(`uint64_t ${n} = (uint64_t)(${tail.dataEnd} - ${at});`);
653
+ return { at, n };
654
+ };
623
655
  switch (method) {
624
656
  case "set": {
625
657
  if (args.length !== 2)
@@ -655,8 +687,11 @@ export class CallEmitter extends ExpressionEmitter {
655
687
  const write = stageElem(args[0]);
656
688
  const n = this.tmp("n");
657
689
  this.line(`uint64_t ${n} = (uint64_t)${info.len};`);
658
- // Resizing does not move the data, so element pointers stay valid.
690
+ const t = tailStart();
691
+ // Resizing does not move the data, so element pointers stay valid; the Vecs after this one shift up.
659
692
  resize(`${n} + 1`, `a_${payer.name}`);
693
+ if (t)
694
+ this.line(`ts_memmove(${t.at} + ${info.elemSize}, ${t.at}, ${t.n});`);
660
695
  this.line(`${info.len} = (uint32_t)(${n} + 1);`);
661
696
  write(elemRef(n));
662
697
  return undefined;
@@ -686,6 +721,9 @@ export class CallEmitter extends ExpressionEmitter {
686
721
  this.line(`${valueC(st)} ${out} = ${val.c};`);
687
722
  result = st.kind === "pubkey" ? { c: `(&${out})`, type: st } : { c: out, type: st };
688
723
  }
724
+ const t = info.cap === undefined ? tailStart() : undefined;
725
+ if (t)
726
+ this.line(`ts_memmove(${t.at} - ${info.elemSize}, ${t.at}, ${t.n});`);
689
727
  this.line(`${info.len} = (uint32_t)(${n} - 1);`);
690
728
  resize(`${n} - 1`, "NULL");
691
729
  return result;
@@ -695,6 +733,9 @@ export class CallEmitter extends ExpressionEmitter {
695
733
  this.fail(node, "clear() takes no arguments");
696
734
  needWritable();
697
735
  this.assertNotAliased(elemPaths(), node);
736
+ const t = info.cap === undefined ? tailStart() : undefined;
737
+ if (t)
738
+ this.line(`ts_memmove((uint8_t *)${info.items}, ${t.at}, ${t.n});`);
698
739
  this.line(`${info.len} = 0;`);
699
740
  resize("0", "NULL");
700
741
  return undefined;
@@ -933,9 +974,10 @@ export class CallEmitter extends ExpressionEmitter {
933
974
  if (this.model.events.has(s.name) && s.fields.some((f) => f.type.kind === "option" || f.type.kind === "dataEnum"))
934
975
  this.fail(node, `${s.name} is emitted as an event and has Option or enum fields; declare a separate event struct`);
935
976
  this.model.accountStructs.add(s.name);
936
- this.line(`TS_TRY(ts_check_program_account(${h}, params->program_id, TS_DISC_${s.name}, sizeof(TSS_${s.name})));`);
977
+ this.line(`TS_TRY(ts_check_program_account(${h}, params->program_id, TS_DISC_${s.name}, ${emptySizeC(s)}));`);
937
978
  if (s.vec)
938
- this.line(`TS_TRY(ts_check_vec_len(${h}, sizeof(TSS_${s.name}), ((TSS_${s.name} *)(${h}->data + 8))->${s.vec.name}__len, TS_ELEM_${s.name}));`);
979
+ for (const l of vecLenCheck(s, h, `((TSS_${s.name} *)(${h}->data + 8))`, ""))
980
+ this.line(l);
939
981
  if (mut) {
940
982
  this.line(`TS_TRY(ts_check_writable(${h}));`);
941
983
  this.line(`TS_TRY(ts_check_unaliased(params, ${h}));`);
@@ -967,6 +1009,116 @@ export class CallEmitter extends ExpressionEmitter {
967
1009
  visit(body);
968
1010
  return found;
969
1011
  }
1012
+ /**
1013
+ * A helper given a closure (a function that reads the caller's variables) for a function parameter: a closure only
1014
+ * runs where it is declared, so the helper is expanded here, like a closure, with the closure bound to the
1015
+ * parameter. Helpers expanded so take values (not accounts) and have no type parameters.
1016
+ */
1017
+ helperWithClosure(node) {
1018
+ const g = this.givenClosure(node);
1019
+ if (!g)
1020
+ return undefined;
1021
+ // Type parameters: bound from the call (as an instance would be) while the body is expanded.
1022
+ const saved = this.scope;
1023
+ if (g.typeParams.length) {
1024
+ const typeArgs = this.typeArgsOf(node, g);
1025
+ const bound = new Map();
1026
+ const structs = new Map();
1027
+ g.typeParams.forEach((n, i) => {
1028
+ const t = typeArgs[i];
1029
+ if (t.kind === "struct")
1030
+ structs.set(n, t.name);
1031
+ else
1032
+ bound.set(n, t);
1033
+ });
1034
+ this.scope = { ...this.scope, typeArgs: bound, structArgs: structs };
1035
+ }
1036
+ try {
1037
+ return { value: this.expandClosure({ node: g.node, name: g.display, scopes: [], env: this.noEnv(), imports: g.imports }, node.arguments, node) };
1038
+ }
1039
+ finally {
1040
+ this.scope = saved;
1041
+ }
1042
+ }
1043
+ /** The function a function returns (`return (x) => x + n`, or an arrow body `(n) => (x) => x + n`), if it returns one. */
1044
+ returnedFunction(fn) {
1045
+ const strip = (e) => (e && ts.isParenthesizedExpression(e) ? strip(e.expression) : e);
1046
+ const body = fn.body;
1047
+ let ret;
1048
+ if (body && !ts.isBlock(body))
1049
+ ret = strip(body);
1050
+ else if (body && body.statements.length === 1 && ts.isReturnStatement(body.statements[0]))
1051
+ ret = strip(body.statements[0].expression);
1052
+ if (ret && (ts.isArrowFunction(ret) || ts.isFunctionExpression(ret)))
1053
+ return ret;
1054
+ if (fn.type && ts.isFunctionTypeNode(fn.type))
1055
+ this.fail(fn, "a function that returns a function has one statement: return (x) => … (the function it makes)");
1056
+ return undefined;
1057
+ }
1058
+ /**
1059
+ * `adder(5n)`, where `adder` returns a function: the arguments are evaluated here into locals of the current block,
1060
+ * and the function it returns is bound to them as a closure (expanded where it is called, like any closure).
1061
+ */
1062
+ madeFunction(call) {
1063
+ if (!ts.isIdentifier(call.expression))
1064
+ return undefined;
1065
+ const local = this.lookup(call.expression.text);
1066
+ let maker = local?.closure;
1067
+ if (!maker && !local) {
1068
+ const name = calleeName(call.expression, this.model.helperDecls, this.checker) ?? call.expression.text;
1069
+ const f = this.model.factories.get(name);
1070
+ if (f)
1071
+ maker = { node: f.node, name: call.expression.text, scopes: [], env: this.noEnv(), imports: f.imports };
1072
+ }
1073
+ if (!maker)
1074
+ return undefined;
1075
+ const made = this.returnedFunction(maker.node);
1076
+ if (!made)
1077
+ return undefined;
1078
+ const fn = maker.node;
1079
+ if (call.arguments.length !== fn.parameters.length)
1080
+ this.fail(call, `${maker.name}() takes ${fn.parameters.length} argument(s)`);
1081
+ const params = new Map();
1082
+ fn.parameters.forEach((p, i) => {
1083
+ if (!ts.isIdentifier(p.name) || p.initializer || p.dotDotDotToken)
1084
+ this.fail(p, "parameters are plain names");
1085
+ const arg = call.arguments[i];
1086
+ if (p.type && ts.isFunctionTypeNode(p.type)) {
1087
+ params.set(p.name.text, { c: "", type: BOOL, mutable: false, closure: this.closureOf(arg) });
1088
+ return;
1089
+ }
1090
+ const annotated = p.type ? resolveValueType(p.type, this.scope) : undefined;
1091
+ let v = this.expr(arg, annotated);
1092
+ if (v.literal !== undefined && !annotated)
1093
+ v = this.literal(v.literal, U64, arg);
1094
+ const type = annotated ?? v.type;
1095
+ if (!["int", "bool", "pubkey", "string", "enum", "option"].includes(type.kind))
1096
+ this.fail(arg, `the function made keeps integers, booleans, Pubkeys, strings, enums or Options, not ${typeName(type)}`);
1097
+ v = this.expect(v, type, arg);
1098
+ const c = `l_${p.name.text}_${this.temp++}`;
1099
+ if (type.kind === "pubkey")
1100
+ this.line(`SolPubkey ${c} = *(${v.c});`);
1101
+ else
1102
+ this.line(`${valueC(type)} ${c} = ${this.ownedString(v).c};`);
1103
+ params.set(p.name.text, { c, type, mutable: true });
1104
+ });
1105
+ return { node: made, name: `the function ${maker.name}() made`, scopes: [...maker.scopes, params], env: maker.env, imports: maker.imports };
1106
+ }
1107
+ /** A function given for a function parameter of an expanded helper or closure, as something to expand. */
1108
+ closureOf(arg) {
1109
+ if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg))
1110
+ return { node: arg, name: "the function passed", scopes: [...this.scopes], env: this.envNow(), imports: this.scope.imports };
1111
+ if (ts.isIdentifier(arg)) {
1112
+ const l = this.lookup(arg.text);
1113
+ if (l?.closure)
1114
+ return l.closure;
1115
+ const name = calleeName(arg, this.model.helperDecls, this.checker) ?? arg.text;
1116
+ const h = this.model.helpers.get(name) ?? this.model.generics.get(name);
1117
+ if (h)
1118
+ return { node: h.node, name: h.display, scopes: [], env: this.noEnv(), imports: h.imports };
1119
+ }
1120
+ this.fail(arg, "pass a function: its name, or an arrow written here");
1121
+ }
970
1122
  callHelper(node, h) {
971
1123
  if (this.ix && !this.ix.accounts.some((a) => a.kind.kind === "system" && !a.optional) && h.params.some((p) => p.account) && this.helperGrows(h.node))
972
1124
  this.fail(node, `${h.display}() grows a Vec (push): the instruction needs a SystemProgram parameter to top up rent`);
@@ -8,6 +8,9 @@ type MplField = {
8
8
  } | {
9
9
  value: ts.Expression;
10
10
  type: ValueType;
11
+ } | {
12
+ c: string;
13
+ n: string;
11
14
  };
12
15
  /**
13
16
  * Other programs: PDAs and seeds, account creation, System/Token/ATA/Metaplex CPIs, generic invoke, Borsh
@@ -102,6 +105,26 @@ export declare abstract class CpiEmitter extends CallEmitter {
102
105
  protected corePluginList(n: ts.Expression | undefined): MplField[];
103
106
  /** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
104
107
  protected coreOp(node: ts.CallExpression, core: AccountParam, method: string): undefined;
108
+ /** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
109
+ private coreProps;
110
+ /** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */
111
+ private coreAuthority;
112
+ private coreOption;
113
+ private coreSchema;
114
+ /** Vec<(HookableLifecycleEvent, ExternalCheckResult)>: `[{ event: "transfer", canReject: true }, …]`. */
115
+ private coreChecks;
116
+ /** ExtraAccount: `{ kind: "address", address, isSigner?, isWritable? }` or a preconfigured one (`{ kind: "owner" }`, …). */
117
+ private coreExtraAccount;
118
+ /**
119
+ * ExternalPluginAdapterInitInfo from a literal: `{ kind: "AppData", dataAuthority, schema? }`, `{ kind: "Oracle",
120
+ * baseAddress, checks, resultsOffset?, baseAddressConfig? }`, `{ kind: "LifecycleHook", hookedProgram, checks,
121
+ * extraAccounts?, dataAuthority?, schema? }` and the Linked forms (collections); `authority?` is the plugin's own.
122
+ */
123
+ protected coreExternalPlugin(n: ts.Expression): MplField[];
124
+ /** ExternalPluginAdapterKey: `{ kind: "AppData", dataAuthority }`, `{ kind: "Oracle", address }`, `{ kind: "LifecycleHook", address }`, … */
125
+ protected coreExternalKey(n: ts.Expression): MplField[];
126
+ /** The data written: Option<Vec<u8>> from a string, a Vec<u8> argument, or null (clears it). */
127
+ protected coreExternalData(n: ts.Expression): MplField[];
105
128
  /** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
106
129
  protected corePluginType(n: ts.Expression): MplField;
107
130
  /** Bubblegum (compressed NFTs on SPL account compression): createTree, mint (MintV1, no collection, no creators). */
@@ -2,7 +2,7 @@ import ts from "typescript";
2
2
  import { discriminator, snakeCase } from "../analyze.js";
3
3
  import { borshMinSize } from "./borsh.js";
4
4
  import { INT_INFO, isScalar, isWritable, optionKey, sameType, scalarSize, typeName, } from "../model.js";
5
- import { MAX_ACCOUNTS, MAX_SEEDS, MAX_CPI_DATA, MAX_EVENT, U64, U8, BOOL, PUBKEY, STRING, cBytes, isStable, cString, scalarStorageC, optC, valueC, } from "./shared.js";
5
+ import { MAX_ACCOUNTS, MAX_SEEDS, MAX_CPI_DATA, MAX_EVENT, U64, U8, BOOL, PUBKEY, STRING, cBytes, isStable, cString, scalarStorageC, optC, valueC, emptySizeC, fixedVecC, vecCount, } from "./shared.js";
6
6
  import { CallEmitter } from "./calls.js";
7
7
  /**
8
8
  * Bubblegum instructions that replace a leaf: discriminator, accounts in order ([label, writable, signer, optional,
@@ -321,7 +321,7 @@ export class CpiEmitter extends CallEmitter {
321
321
  const b = this.tmp("bump");
322
322
  // The account's size: the struct's (8 + its fixed size, Anchor's InitSpace for @layout borsh), or Anchor's
323
323
  // `space = …` when given, which must hold the struct.
324
- const need = `8 + sizeof(TSS_${s.name})`;
324
+ const need = s.layout === "borsh" ? `8 + sizeof(TSS_${s.name})` : `8 + ${emptySizeC(s)}`;
325
325
  let space = need;
326
326
  const spaceArg = node.arguments[2];
327
327
  if (spaceArg) {
@@ -1526,6 +1526,8 @@ export class CpiEmitter extends CallEmitter {
1526
1526
  this.line(`static const uint8_t ${name}[${f.bytes.length}] = ${cBytes(f.bytes)};`);
1527
1527
  put(name, String(f.bytes.length), f.bytes.length);
1528
1528
  }
1529
+ else if ("c" in f)
1530
+ put(f.c, f.n);
1529
1531
  else
1530
1532
  this.serialize(this.expect(this.expr(f.value, f.type), f.type, f.value), f.value, put);
1531
1533
  }
@@ -1829,8 +1831,237 @@ export class CpiEmitter extends CallEmitter {
1829
1831
  this.cpi(p, metas, data.buf, data.len, seedsAt(4));
1830
1832
  return undefined;
1831
1833
  }
1834
+ case "addExternalPlugin":
1835
+ case "removeExternalPlugin":
1836
+ case "writeExternalPluginData": {
1837
+ // Add/RemoveExternalPluginAdapterV1: asset, collection, payer, authority, systemProgram, logWrapper;
1838
+ // WriteExternalPluginAdapterDataV1 has a buffer account (left out: the data is in the instruction) before systemProgram.
1839
+ const write = method === "writeExternalPluginData";
1840
+ const n = write ? 7 : 6;
1841
+ if (a.length !== n && a.length !== n + 1)
1842
+ this.fail(node, `${method}(asset, collection | null, payer, authority | null, systemProgram, ${method === "addExternalPlugin" ? "plugin" : write ? "key, data" : "key"}, signerSeeds?)`);
1843
+ const metas = [
1844
+ this.mplMeta(a[0], core, "the asset", true, false),
1845
+ this.mplMeta(a[1], core, "the collection", true, false),
1846
+ this.mplMeta(a[2], core, "the payer", true, true),
1847
+ this.mplMeta(a[3], core, "the authority", false, true),
1848
+ ...(write ? [self] : []),
1849
+ this.mplMeta(a[4], core, "the system program", false, false),
1850
+ self,
1851
+ ];
1852
+ const body = method === "addExternalPlugin"
1853
+ ? this.coreExternalPlugin(a[5])
1854
+ : write
1855
+ ? [...this.coreExternalKey(a[5]), ...this.coreExternalData(a[6])]
1856
+ : this.coreExternalKey(a[5]);
1857
+ const data = this.mplData(node, 1024, [{ bytes: [{ addExternalPlugin: 22, removeExternalPlugin: 24, writeExternalPluginData: 28 }[method]] }, ...body]);
1858
+ this.cpi(p, metas, data.buf, data.len, seedsAt(n));
1859
+ return undefined;
1860
+ }
1861
+ case "addCollectionExternalPlugin":
1862
+ case "removeCollectionExternalPlugin":
1863
+ case "writeCollectionExternalPluginData": {
1864
+ // The same on a collection: collection, payer, authority, (buffer,) systemProgram, logWrapper.
1865
+ const write = method === "writeCollectionExternalPluginData";
1866
+ const n = write ? 6 : 5;
1867
+ if (a.length !== n && a.length !== n + 1)
1868
+ this.fail(node, `${method}(collection, payer, authority | null, systemProgram, ${method === "addCollectionExternalPlugin" ? "plugin" : write ? "key, data" : "key"}, signerSeeds?)`);
1869
+ const metas = [
1870
+ this.mplMeta(a[0], core, "the collection", true, false),
1871
+ this.mplMeta(a[1], core, "the payer", true, true),
1872
+ this.mplMeta(a[2], core, "the authority", false, true),
1873
+ ...(write ? [self] : []),
1874
+ this.mplMeta(a[3], core, "the system program", false, false),
1875
+ self,
1876
+ ];
1877
+ const body = method === "addCollectionExternalPlugin"
1878
+ ? this.coreExternalPlugin(a[4])
1879
+ : write
1880
+ ? [...this.coreExternalKey(a[4]), ...this.coreExternalData(a[5])]
1881
+ : this.coreExternalKey(a[4]);
1882
+ const data = this.mplData(node, 1024, [
1883
+ { bytes: [{ addCollectionExternalPlugin: 23, removeCollectionExternalPlugin: 25, writeCollectionExternalPluginData: 29 }[method]] },
1884
+ ...body,
1885
+ ]);
1886
+ this.cpi(p, metas, data.buf, data.len, seedsAt(n));
1887
+ return undefined;
1888
+ }
1889
+ }
1890
+ this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin, addExternalPlugin, removeExternalPlugin, writeExternalPluginData (and the Collection forms), transferAsset and burnAsset, not ${method}`);
1891
+ }
1892
+ /** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
1893
+ coreProps(node, what) {
1894
+ let n = node;
1895
+ while (ts.isParenthesizedExpression(n) || ts.isAsExpression(n) || ts.isSatisfiesExpression(n))
1896
+ n = n.expression;
1897
+ if (!ts.isObjectLiteralExpression(n))
1898
+ this.fail(n, `${what} is an object literal: { kind: "…", … }`);
1899
+ const props = new Map();
1900
+ for (const q of n.properties) {
1901
+ if (ts.isPropertyAssignment(q) && ts.isIdentifier(q.name))
1902
+ props.set(q.name.text, q.initializer);
1903
+ else if (ts.isShorthandPropertyAssignment(q))
1904
+ props.set(q.name.text, q.name);
1905
+ else
1906
+ this.fail(q, "write the fields as name: value");
1907
+ }
1908
+ return props;
1909
+ }
1910
+ /** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */
1911
+ coreAuthority(n) {
1912
+ if (ts.isStringLiteral(n)) {
1913
+ const i = ["none", "owner", "updateAuthority"].indexOf(n.text);
1914
+ if (i < 0)
1915
+ this.fail(n, 'an authority is "none", "owner", "updateAuthority" or a Pubkey');
1916
+ return [{ bytes: [i] }];
1917
+ }
1918
+ return [{ bytes: [3] }, { value: n, type: PUBKEY }];
1919
+ }
1920
+ coreOption(n, inner) {
1921
+ return n ? [{ bytes: [1] }, ...inner(n)] : [{ bytes: [0] }];
1922
+ }
1923
+ coreSchema(n) {
1924
+ const i = ts.isStringLiteral(n) ? ["binary", "json", "msgpack"].indexOf(n.text) : -1;
1925
+ if (i < 0)
1926
+ this.fail(n, 'schema is "binary", "json" or "msgpack"');
1927
+ return [{ bytes: [i] }];
1928
+ }
1929
+ /** Vec<(HookableLifecycleEvent, ExternalCheckResult)>: `[{ event: "transfer", canReject: true }, …]`. */
1930
+ coreChecks(n, at) {
1931
+ if (!n || !ts.isArrayLiteralExpression(n))
1932
+ this.fail(n ?? at, 'checks is a list: [{ event: "transfer", canApprove: true, canReject: true }]');
1933
+ const out = [{ bytes: [n.elements.length & 0xff, 0, 0, 0] }];
1934
+ for (const e of n.elements) {
1935
+ const p = this.coreProps(e, "a lifecycle check");
1936
+ const ev = p.get("event");
1937
+ const i = ev && ts.isStringLiteral(ev) ? ["create", "transfer", "burn", "update", "execute"].indexOf(ev.text) : -1;
1938
+ if (i < 0)
1939
+ this.fail(ev ?? e, 'event is "create", "transfer", "burn", "update" or "execute"');
1940
+ let flags = 0;
1941
+ for (const [k, bit] of [
1942
+ ["canListen", 1],
1943
+ ["canApprove", 2],
1944
+ ["canReject", 4],
1945
+ ]) {
1946
+ const v = p.get(k);
1947
+ if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
1948
+ this.fail(v, `${k} is true or false`);
1949
+ if (v?.kind === ts.SyntaxKind.TrueKeyword)
1950
+ flags |= bit;
1951
+ }
1952
+ for (const k of p.keys())
1953
+ if (!["event", "canListen", "canApprove", "canReject"].includes(k))
1954
+ this.fail(e, `a lifecycle check has event, canListen, canApprove and canReject, not ${k}`);
1955
+ out.push({ bytes: [i, flags, 0, 0, 0] });
1956
+ }
1957
+ return out;
1958
+ }
1959
+ /** ExtraAccount: `{ kind: "address", address, isSigner?, isWritable? }` or a preconfigured one (`{ kind: "owner" }`, …). */
1960
+ coreExtraAccount(n) {
1961
+ const p = this.coreProps(n, "an extra account");
1962
+ const kind = p.get("kind");
1963
+ const kinds = ["program", "collection", "owner", "recipient", "asset", "customPda", "address"];
1964
+ const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
1965
+ if (i < 0 || i === 5)
1966
+ this.fail(kind ?? n, 'an extra account is { kind: "address", address } or "program", "collection", "owner", "recipient", "asset"');
1967
+ const flag = (k) => {
1968
+ const v = p.get(k);
1969
+ if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
1970
+ this.fail(v, `${k} is true or false`);
1971
+ return v?.kind === ts.SyntaxKind.TrueKeyword ? 1 : 0;
1972
+ };
1973
+ if (i === 6) {
1974
+ const addr = p.get("address");
1975
+ if (!addr)
1976
+ this.fail(n, "an address extra account needs address");
1977
+ return [{ bytes: [6] }, { value: addr, type: PUBKEY }, { bytes: [flag("isSigner"), flag("isWritable")] }];
1978
+ }
1979
+ return [{ bytes: [i, flag("isSigner"), flag("isWritable")] }];
1980
+ }
1981
+ /**
1982
+ * ExternalPluginAdapterInitInfo from a literal: `{ kind: "AppData", dataAuthority, schema? }`, `{ kind: "Oracle",
1983
+ * baseAddress, checks, resultsOffset?, baseAddressConfig? }`, `{ kind: "LifecycleHook", hookedProgram, checks,
1984
+ * extraAccounts?, dataAuthority?, schema? }` and the Linked forms (collections); `authority?` is the plugin's own.
1985
+ */
1986
+ coreExternalPlugin(n) {
1987
+ const p = this.coreProps(n, "a Core external plugin");
1988
+ const kind = p.get("kind");
1989
+ const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
1990
+ const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
1991
+ if (i < 0)
1992
+ this.fail(kind ?? n, `a Core external plugin is ${kinds.join(", ")}`);
1993
+ const allowed = {
1994
+ 0: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
1995
+ 1: ["baseAddress", "checks", "baseAddressConfig", "resultsOffset"],
1996
+ 2: ["dataAuthority", "schema"],
1997
+ 3: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
1998
+ 4: ["dataAuthority", "schema"],
1999
+ };
2000
+ for (const k of p.keys())
2001
+ if (k !== "kind" && k !== "authority" && !allowed[i].includes(k))
2002
+ this.fail(n, `${kinds[i]} takes ${allowed[i].join(", ")} and authority, not ${k}`);
2003
+ const need = (k) => p.get(k) ?? this.fail(n, `${kinds[i]} needs ${k}`);
2004
+ const auth = this.coreOption(p.get("authority"), (x) => this.coreAuthority(x));
2005
+ const schema = this.coreOption(p.get("schema"), (x) => this.coreSchema(x));
2006
+ if (i === 2 || i === 4)
2007
+ return [{ bytes: [i] }, ...this.coreAuthority(need("dataAuthority")), ...auth, ...schema];
2008
+ if (i === 1) {
2009
+ const offset = p.get("resultsOffset");
2010
+ const off = !offset
2011
+ ? [{ bytes: [0] }]
2012
+ : ts.isStringLiteral(offset) && (offset.text === "none" || offset.text === "anchor")
2013
+ ? [{ bytes: [1, offset.text === "none" ? 0 : 1] }]
2014
+ : [{ bytes: [1, 2] }, { value: offset, type: U64 }];
2015
+ return [
2016
+ { bytes: [1] },
2017
+ { value: need("baseAddress"), type: PUBKEY },
2018
+ ...auth,
2019
+ ...this.coreChecks(need("checks"), n),
2020
+ ...this.coreOption(p.get("baseAddressConfig"), (x) => this.coreExtraAccount(x)),
2021
+ ...off,
2022
+ ];
2023
+ }
2024
+ const extras = p.get("extraAccounts");
2025
+ if (extras && !ts.isArrayLiteralExpression(extras))
2026
+ this.fail(extras, "extraAccounts is a list of extra accounts");
2027
+ return [
2028
+ { bytes: [i] },
2029
+ { value: need("hookedProgram"), type: PUBKEY },
2030
+ ...auth,
2031
+ ...this.coreChecks(need("checks"), n),
2032
+ ...(extras ? [{ bytes: [1, extras.elements.length & 0xff, 0, 0, 0] }, ...extras.elements.flatMap((e) => this.coreExtraAccount(e))] : [{ bytes: [0] }]),
2033
+ ...this.coreOption(p.get("dataAuthority"), (x) => this.coreAuthority(x)),
2034
+ ...schema,
2035
+ ];
2036
+ }
2037
+ /** ExternalPluginAdapterKey: `{ kind: "AppData", dataAuthority }`, `{ kind: "Oracle", address }`, `{ kind: "LifecycleHook", address }`, … */
2038
+ coreExternalKey(n) {
2039
+ const p = this.coreProps(n, "a Core external plugin key");
2040
+ const kind = p.get("kind");
2041
+ const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
2042
+ const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
2043
+ if (i < 0)
2044
+ this.fail(kind ?? n, `a Core external plugin key is ${kinds.join(", ")}`);
2045
+ if (i === 2 || i === 4)
2046
+ return [{ bytes: [i] }, ...this.coreAuthority(p.get("dataAuthority") ?? this.fail(n, `${kinds[i]}'s key is its dataAuthority`))];
2047
+ return [
2048
+ { bytes: [i] },
2049
+ { value: p.get("address") ?? this.fail(n, `${kinds[i]}'s key is its address (the oracle's base address or the hooked program)`), type: PUBKEY },
2050
+ ];
2051
+ }
2052
+ /** The data written: Option<Vec<u8>> from a string, a Vec<u8> argument, or null (clears it). */
2053
+ coreExternalData(n) {
2054
+ if (n.kind === ts.SyntaxKind.NullKeyword)
2055
+ return [{ bytes: [0] }];
2056
+ const l = ts.isIdentifier(n) ? this.lookup(n.text) : undefined;
2057
+ if (l?.type.kind === "vecArg") {
2058
+ if (l.type.elem.kind !== "int" || l.type.elem.name !== "u8")
2059
+ this.fail(n, "data is a string, a Vec<u8> argument or null");
2060
+ const t = this.tmp("dl");
2061
+ this.line(`uint32_t ${t} = (uint32_t)${l.c}.len;`);
2062
+ return [{ bytes: [1] }, { c: `&${t}`, n: "4" }, { c: `${l.c}.ptr`, n: `(uint64_t)${t}` }];
1832
2063
  }
1833
- this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin (and the Collection forms), transferAsset and burnAsset, not ${method}`);
2064
+ return [{ bytes: [1] }, { value: n, type: STRING }];
1834
2065
  }
1835
2066
  /** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
1836
2067
  corePluginType(n) {
@@ -2642,8 +2873,46 @@ export class CpiEmitter extends CallEmitter {
2642
2873
  this.fail(obj, `missing field "${f.name}" of ${s.name}`);
2643
2874
  for (const [name, e] of given) {
2644
2875
  const f = s.fields.find((x) => x.name === name);
2645
- if (f.type.kind === "vec")
2646
- this.fail(obj, "Vec fields are not supported in events yet");
2876
+ if (f.type.kind === "vec" && f.type.cap !== undefined) {
2877
+ // A FixedVec keeps its fixed layout, as in account data: u32 length, then room for N elements.
2878
+ const ft = f.type;
2879
+ const t = this.tmp("evv");
2880
+ this.line(`${fixedVecC(ft)} ${t};`);
2881
+ this.line(`ts_memzero(&${t}, sizeof(${t}));`);
2882
+ this.writeAggregate({ lv: t, type: ft, writable: true }, e);
2883
+ const n = this.fieldBytes(ft);
2884
+ staged.set(name, (put) => put(`&${t}`, String(n), n));
2885
+ continue;
2886
+ }
2887
+ if (f.type.kind === "vec") {
2888
+ // A Vec is Borsh: u32 length, then the elements, read from a Vec argument or account data when emitted.
2889
+ const l = ts.isIdentifier(e) ? this.lookup(e.text) : undefined;
2890
+ const r = l?.type.kind === "vecArg" ? undefined : this.ref(e);
2891
+ const elemSize = this.fieldBytes(f.type.elem);
2892
+ if (l?.type.kind === "vecArg") {
2893
+ if (!sameType(l.type.elem, f.type.elem))
2894
+ this.fail(e, `expected a ${typeName(f.type)}`);
2895
+ const c = l.c;
2896
+ staged.set(name, (put) => {
2897
+ const n = this.tmp("vl");
2898
+ this.line(`uint32_t ${n} = (uint32_t)${c}.len;`);
2899
+ put(`&${n}`, "4");
2900
+ put(`(const uint8_t *)${c}.ptr`, `(uint64_t)${n} * ${elemSize}`);
2901
+ });
2902
+ }
2903
+ else if (r?.vec && r.type.kind === "vec" && sameType(r.type.elem, f.type.elem)) {
2904
+ const info = r.vec;
2905
+ staged.set(name, (put) => {
2906
+ const n = this.tmp("vl");
2907
+ this.line(`uint32_t ${n} = (uint32_t)${vecCount(info)};`);
2908
+ put(`&${n}`, "4");
2909
+ put(`(const uint8_t *)${info.items}`, `(uint64_t)${n} * ${elemSize}`);
2910
+ });
2911
+ }
2912
+ else
2913
+ this.fail(e, `expected a ${typeName(f.type)}: a Vec argument or a list in account data`);
2914
+ continue;
2915
+ }
2647
2916
  if (f.type.kind === "option" && f.type.inner.kind === "struct")
2648
2917
  this.fail(obj, "Option<struct> fields are not supported in events yet");
2649
2918
  const vt = f.type.kind === "string" ? STRING : f.type.kind === "option" || isScalar(f.type) ? f.type : undefined;
@@ -32,17 +32,25 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
32
32
  text: string;
33
33
  arr?: undefined;
34
34
  n?: undefined;
35
+ only?: undefined;
35
36
  } | {
36
37
  text?: undefined;
37
38
  arr: string;
38
39
  n: number;
40
+ only?: undefined;
41
+ } | {
42
+ only: string;
43
+ text?: undefined;
39
44
  };
40
45
  /**
41
46
  * The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
42
47
  * literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
43
48
  */
44
49
  protected constantText(e: ts.Expression): string | undefined;
45
- protected toText(node: ts.Expression): string;
50
+ /** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
51
+ protected toText(node: ts.Expression, converted?: {
52
+ yes: boolean;
53
+ }): string;
46
54
  /** C statement writing the decimal text of an integer into the TS_Str `out`. */
47
55
  protected textOfInt(name: IntName, c: string, out: string): string;
48
56
  /** `c ? a : b` on values: only the chosen branch runs (its checks and side effects included). */