@typesol/cli 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ // Anchor's own error codes (anchor-lang ErrorCode), for `require(condition, AnchorError.X)`.
2
+ export const ANCHOR_ERRORS = {
3
+ InstructionMissing: 100,
4
+ InstructionFallbackNotFound: 101,
5
+ InstructionDidNotDeserialize: 102,
6
+ InstructionDidNotSerialize: 103,
7
+ IdlInstructionStub: 1000,
8
+ IdlInstructionInvalidProgram: 1001,
9
+ IdlAccountNotEmpty: 1002,
10
+ EventInstructionStub: 1500,
11
+ ConstraintMut: 2000,
12
+ ConstraintHasOne: 2001,
13
+ ConstraintSigner: 2002,
14
+ ConstraintRaw: 2003,
15
+ ConstraintOwner: 2004,
16
+ ConstraintRentExempt: 2005,
17
+ ConstraintSeeds: 2006,
18
+ ConstraintExecutable: 2007,
19
+ ConstraintState: 2008,
20
+ ConstraintAssociated: 2009,
21
+ ConstraintAssociatedInit: 2010,
22
+ ConstraintClose: 2011,
23
+ ConstraintAddress: 2012,
24
+ ConstraintZero: 2013,
25
+ ConstraintTokenMint: 2014,
26
+ ConstraintTokenOwner: 2015,
27
+ ConstraintMintMintAuthority: 2016,
28
+ ConstraintMintFreezeAuthority: 2017,
29
+ ConstraintMintDecimals: 2018,
30
+ ConstraintSpace: 2019,
31
+ ConstraintAccountIsNone: 2020,
32
+ ConstraintTokenTokenProgram: 2021,
33
+ ConstraintMintTokenProgram: 2022,
34
+ ConstraintAssociatedTokenTokenProgram: 2023,
35
+ ConstraintMintGroupPointerExtension: 2024,
36
+ ConstraintMintGroupPointerExtensionAuthority: 2025,
37
+ ConstraintMintGroupPointerExtensionGroupAddress: 2026,
38
+ ConstraintMintGroupMemberPointerExtension: 2027,
39
+ ConstraintMintGroupMemberPointerExtensionAuthority: 2028,
40
+ ConstraintMintGroupMemberPointerExtensionMemberAddress: 2029,
41
+ ConstraintMintMetadataPointerExtension: 2030,
42
+ ConstraintMintMetadataPointerExtensionAuthority: 2031,
43
+ ConstraintMintMetadataPointerExtensionMetadataAddress: 2032,
44
+ ConstraintMintCloseAuthorityExtension: 2033,
45
+ ConstraintMintCloseAuthorityExtensionAuthority: 2034,
46
+ ConstraintMintPermanentDelegateExtension: 2035,
47
+ ConstraintMintPermanentDelegateExtensionDelegate: 2036,
48
+ ConstraintMintTransferHookExtension: 2037,
49
+ ConstraintMintTransferHookExtensionAuthority: 2038,
50
+ ConstraintMintTransferHookExtensionProgramId: 2039,
51
+ RequireViolated: 2500,
52
+ RequireEqViolated: 2501,
53
+ RequireKeysEqViolated: 2502,
54
+ RequireNeqViolated: 2503,
55
+ RequireKeysNeqViolated: 2504,
56
+ RequireGtViolated: 2505,
57
+ RequireGteViolated: 2506,
58
+ AccountDiscriminatorAlreadySet: 3000,
59
+ AccountDiscriminatorNotFound: 3001,
60
+ AccountDiscriminatorMismatch: 3002,
61
+ AccountDidNotDeserialize: 3003,
62
+ AccountDidNotSerialize: 3004,
63
+ AccountNotEnoughKeys: 3005,
64
+ AccountNotMutable: 3006,
65
+ AccountOwnedByWrongProgram: 3007,
66
+ InvalidProgramId: 3008,
67
+ InvalidProgramExecutable: 3009,
68
+ AccountNotSigner: 3010,
69
+ AccountNotSystemOwned: 3011,
70
+ AccountNotInitialized: 3012,
71
+ AccountNotProgramData: 3013,
72
+ AccountNotAssociatedTokenAccount: 3014,
73
+ AccountSysvarMismatch: 3015,
74
+ AccountReallocExceedsLimit: 3016,
75
+ AccountDuplicateReallocs: 3017,
76
+ DeclaredProgramIdMismatch: 4100,
77
+ TryingToInitPayerAsProgramAccount: 4101,
78
+ InvalidNumericConversion: 4102,
79
+ Deprecated: 5000,
80
+ };
@@ -2,7 +2,7 @@ import ts from "typescript";
2
2
  import { type Analysis, type TypeScope } from "../analyze.js";
3
3
  import type { Reporter } from "../diagnostics.js";
4
4
  import { type AccountParam, type Helper, type IdlSeed, type Instruction, type ProgramModel, type ValueType } from "../model.js";
5
- import { type Value, type Local, type EmitOptions } from "./shared.js";
5
+ import { type Value, type Ref, type Local, type Closure, type EmitOptions } from "./shared.js";
6
6
  /**
7
7
  * State of one function being lowered, and the primitives every layer uses: emitting lines, temporaries,
8
8
  * scopes, evaluation-order snapshots and account lookups.
@@ -24,6 +24,17 @@ export declare abstract class EmitterBase {
24
24
  protected readonly pendingInit: Set<string>;
25
25
  /** Bump temporaries from create()/createIfNeeded(), for `acct.pdaBump`. */
26
26
  protected readonly bumps: Map<string, string>;
27
+ /** Closures being expanded, innermost last: a `return` inside one assigns `result` and jumps to `label`. */
28
+ protected inlineFrames: {
29
+ label: string;
30
+ result: string;
31
+ type?: ValueType;
32
+ declAt: number;
33
+ pad: string;
34
+ node: ts.Node;
35
+ }[];
36
+ /** Closures being expanded (a closure calling itself cannot be expanded). */
37
+ protected readonly inlining: Set<ts.Node>;
27
38
  /** Enclosing loops, innermost last; each has a `continue` label. */
28
39
  protected readonly loops: string[];
29
40
  protected readonly ix: Instruction | undefined;
@@ -39,6 +50,14 @@ export declare abstract class EmitterBase {
39
50
  constructor(analysis: Analysis, target: Instruction | Helper, opts?: EmitOptions);
40
51
  /** The helper a call runs: a function, a const arrow function (top-level or local), or a generic one's instance. */
41
52
  protected helperOf(node: ts.CallExpression): Helper | undefined;
53
+ /**
54
+ * The functions passed for a helper's function parameters: named functions only (a helper, or a local function that
55
+ * captures nothing). A closure can only run where it is declared, so it cannot be passed.
56
+ */
57
+ private fnArgsOf;
58
+ /** A struct written as a type argument (`pick<Point>(…)`). */
59
+ private structArg;
60
+ private fixedStruct;
42
61
  /** A generic call's type arguments: written out (`max<u64>(a, b)`), or the types of the arguments in `T` positions. */
43
62
  private typeArgsOf;
44
63
  protected line(s: string): void;
@@ -59,11 +78,17 @@ export declare abstract class EmitterBase {
59
78
  protected block(fn: () => void): void;
60
79
  protected fail(node: ts.Node, message: string): never;
61
80
  protected exportName(node: ts.Expression): string | undefined;
81
+ /**
82
+ * `acct!.field` on an optional account: the same access without `!` (using the account checks that it was passed,
83
+ * failing with 3020 otherwise). Returns undefined for anything else.
84
+ */
85
+ protected withoutBang(node: ts.Expression): ts.PropertyAccessExpression | undefined;
62
86
  protected accountParam(node: ts.Expression, what: string): AccountParam;
63
87
  protected assertUsable(a: AccountParam, node: ts.Node): void;
64
88
  protected abstract expect(v: Value, want: ValueType | undefined, node: ts.Node): Value;
65
89
  protected abstract literal(value: bigint, type: ValueType, node: ts.Node): Value;
66
90
  protected abstract expr(node: ts.Expression, hint?: ValueType): Value;
91
+ protected abstract ref(node: ts.Expression): Ref | undefined;
67
92
  protected abstract call(node: ts.CallExpression, topLevel: boolean): Value | undefined;
68
93
  protected abstract isRemaining(node: ts.Expression): boolean;
69
94
  protected abstract handle(node: ts.Expression): string | undefined;
@@ -75,6 +100,7 @@ export declare abstract class EmitterBase {
75
100
  protected abstract recordPda(account: AccountParam, idl: IdlSeed[] | undefined): void;
76
101
  protected abstract create(node: ts.CallExpression, target: AccountParam, topLevel: boolean, ifNeeded?: boolean): Value;
77
102
  protected abstract createAta(node: ts.CallExpression, target: AccountParam, topLevel: boolean): Value | undefined;
103
+ protected abstract createToken(node: ts.CallExpression, target: AccountParam, topLevel: boolean): Value;
78
104
  protected abstract systemTransfer(node: ts.CallExpression): undefined;
79
105
  protected abstract createAccount(node: ts.CallExpression, system: AccountParam): undefined;
80
106
  protected abstract tokenOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
@@ -88,4 +114,6 @@ export declare abstract class EmitterBase {
88
114
  program: string;
89
115
  }): undefined;
90
116
  protected abstract epilogue(): string[];
117
+ protected abstract expandClosure(c: Closure, args: readonly ts.Expression[], node: ts.Node): Value | undefined;
118
+ protected abstract listMethod(node: ts.CallExpression, method: string): Value | undefined;
91
119
  }
@@ -1,5 +1,5 @@
1
1
  import ts from "typescript";
2
- import { calleeName, resolveScalarType } from "../analyze.js";
2
+ import { calleeName, resolveScalarType, structNameOf } from "../analyze.js";
3
3
  import { isScalar, } from "../model.js";
4
4
  import { isStable, cString, valueC } from "./shared.js";
5
5
  /**
@@ -23,6 +23,10 @@ export class EmitterBase {
23
23
  pendingInit = new Set();
24
24
  /** Bump temporaries from create()/createIfNeeded(), for `acct.pdaBump`. */
25
25
  bumps = new Map();
26
+ /** Closures being expanded, innermost last: a `return` inside one assigns `result` and jumps to `label`. */
27
+ inlineFrames = [];
28
+ /** Closures being expanded (a closure calling itself cannot be expanded). */
29
+ inlining = new Set();
26
30
  /** Enclosing loops, innermost last; each has a `continue` label. */
27
31
  loops = [];
28
32
  ix;
@@ -47,6 +51,7 @@ export class EmitterBase {
47
51
  options: analysis.model.options,
48
52
  dataEnums: analysis.model.dataEnums,
49
53
  typeArgs: "accounts" in target ? undefined : target.typeArgs,
54
+ structArgs: "accounts" in target ? undefined : target.structArgs,
50
55
  };
51
56
  this.checker = analysis.checker;
52
57
  this.analysis = analysis;
@@ -54,7 +59,7 @@ export class EmitterBase {
54
59
  this.ix = target;
55
60
  for (const a of target.accounts) {
56
61
  this.accounts.set(a.name, a);
57
- if (a.kind.kind === "init" || a.kind.kind === "initIfNeeded" || (a.kind.kind === "token" && a.kind.init))
62
+ if (a.kind.kind === "init" || a.kind.kind === "initIfNeeded" || ((a.kind.kind === "token" || a.kind.kind === "mint") && a.kind.init))
58
63
  this.pendingInit.add(a.name);
59
64
  }
60
65
  for (const a of target.args)
@@ -86,23 +91,59 @@ export class EmitterBase {
86
91
  if (cached !== undefined)
87
92
  return cached ?? undefined;
88
93
  const callee = node.expression;
89
- const name = calleeName(callee, this.model.helperDecls, this.checker) ?? (this.lookup(callee.text) ? undefined : callee.text);
94
+ // A function parameter of this helper instance: the function it was given.
95
+ const passed = this.helper?.fnArgs?.get(callee.text);
96
+ const name = passed ?? calleeName(callee, this.model.helperDecls, this.checker) ?? (this.lookup(callee.text) ? undefined : callee.text);
90
97
  const g = name ? this.model.generics.get(name) : undefined;
91
98
  let h = name ? this.model.helpers.get(name) : undefined;
92
99
  if (g)
93
- h = this.analysis.instantiate(g, this.typeArgsOf(node, g));
100
+ h = this.analysis.instantiate(g, g.typeParams.length ? this.typeArgsOf(node, g) : [], this.fnArgsOf(node, g));
94
101
  else if (h && node.typeArguments)
95
102
  this.fail(node, `${h.display} takes no type arguments`);
96
103
  this.calleeCache.set(node, h ?? null);
97
104
  return h;
98
105
  }
106
+ /**
107
+ * The functions passed for a helper's function parameters: named functions only (a helper, or a local function that
108
+ * captures nothing). A closure can only run where it is declared, so it cannot be passed.
109
+ */
110
+ fnArgsOf(node, g) {
111
+ return g.fnParams.map((i) => {
112
+ const arg = node.arguments[i];
113
+ const pname = g.node.parameters[i].name.text;
114
+ if (!arg || !ts.isIdentifier(arg))
115
+ this.fail(arg ?? node, `pass ${g.display}'s ${pname} a named function (declare it: const f = (…) => …)`);
116
+ const passed = this.helper?.fnArgs?.get(arg.text);
117
+ if (passed)
118
+ return passed;
119
+ if (this.lookup(arg.text)?.closure)
120
+ this.fail(arg, `${arg.text} reads the variables of the function around it, so it can only be called there, not passed to ${g.display}`);
121
+ const name = calleeName(arg, this.model.helperDecls, this.checker) ?? arg.text;
122
+ if (this.model.generics.has(name))
123
+ this.fail(arg, `${arg.text} is generic or takes functions itself: pass a plain function`);
124
+ if (!this.model.helpers.has(name))
125
+ this.fail(arg, `${arg.text} is not a function of this program`);
126
+ return name;
127
+ });
128
+ }
129
+ /** A struct written as a type argument (`pick<Point>(…)`). */
130
+ structArg(t) {
131
+ const name = structNameOf(t, this.scope);
132
+ return name && this.model.structs.has(name) ? this.fixedStruct(name, t) : undefined;
133
+ }
134
+ fixedStruct(name, at) {
135
+ const s = this.model.structs.get(name);
136
+ if (s.size === undefined || s.vec)
137
+ this.fail(at, `${name} has variable-size fields; type parameters stand for fixed-size structs`);
138
+ return { kind: "struct", name };
139
+ }
99
140
  /** A generic call's type arguments: written out (`max<u64>(a, b)`), or the types of the arguments in `T` positions. */
100
141
  typeArgsOf(node, g) {
101
142
  const usage = `write ${g.display}<u64>(…) with the types`;
102
143
  if (node.typeArguments) {
103
144
  if (node.typeArguments.length !== g.typeParams.length)
104
145
  this.fail(node, `${g.display} takes ${g.typeParams.length} type argument(s)`);
105
- return node.typeArguments.map((t) => resolveScalarType(t, this.scope) ?? this.fail(t, "type arguments are integers, boolean, Pubkey or enums"));
146
+ return node.typeArguments.map((t) => resolveScalarType(t, this.scope) ?? this.structArg(t) ?? this.fail(t, "type arguments are integers, boolean, Pubkey, enums or fixed-size structs"));
106
147
  }
107
148
  return g.typeParams.map((tp) => {
108
149
  for (const [i, p] of g.node.parameters.entries()) {
@@ -116,10 +157,22 @@ export class EmitterBase {
116
157
  this.hoisted.length = saved.hoisted;
117
158
  this.temp = saved.temp;
118
159
  this.reporter.diagnostics.length = saved.diagnostics;
160
+ if (probe?.type.kind === "structRef")
161
+ return this.fixedStruct(probe.type.struct, arg);
162
+ if (!probe) {
163
+ // A struct place (`p.a`) is not a value: type it as a reference, again without emitting anything.
164
+ const r = this.reporter.guard(() => this.ref(arg));
165
+ this.lines.length = saved.lines;
166
+ this.hoisted.length = saved.hoisted;
167
+ this.temp = saved.temp;
168
+ this.reporter.diagnostics.length = saved.diagnostics;
169
+ if (r?.type.kind === "struct")
170
+ return this.fixedStruct(r.type.name, arg);
171
+ }
119
172
  if (!probe || probe.literal !== undefined || probe.isNull)
120
173
  continue;
121
174
  if (!isScalar(probe.type))
122
- this.fail(arg, `${tp} stands for an integer, boolean, Pubkey or enum, not ${probe.type.kind}`);
175
+ this.fail(arg, `${tp} stands for an integer, boolean, Pubkey, enum or struct, not ${probe.type.kind}`);
123
176
  const t = probe.type;
124
177
  return t.kind === "pubkey" ? (t.hash ? { kind: "pubkey", hash: true } : { kind: "pubkey" }) : t;
125
178
  }
@@ -196,7 +249,7 @@ export class EmitterBase {
196
249
  if ((ts.isPropertyAccessExpression(node) || ts.isCallExpression(node) || ts.isElementAccessExpression(node)) && node.questionDotToken)
197
250
  this.fail(node, "optional chaining (?.) is not supported; check `x !== null` first");
198
251
  if (ts.isNonNullExpression(node))
199
- this.fail(node, "the ! assertion works on Option values only; check `x !== null` so TypeScript narrows it");
252
+ this.fail(node, "the ! assertion works on Option values and optional accounts only; check `x !== null` so TypeScript narrows it");
200
253
  }
201
254
  lookup(name) {
202
255
  for (let i = this.scopes.length - 1; i >= 0; i--) {
@@ -219,7 +272,25 @@ export class EmitterBase {
219
272
  exportName(node) {
220
273
  return ts.isIdentifier(node) ? this.imports.get(node.text) : undefined;
221
274
  }
275
+ /**
276
+ * `acct!.field` on an optional account: the same access without `!` (using the account checks that it was passed,
277
+ * failing with 3020 otherwise). Returns undefined for anything else.
278
+ */
279
+ withoutBang(node) {
280
+ if (!ts.isPropertyAccessExpression(node) || !ts.isNonNullExpression(node.expression))
281
+ return undefined;
282
+ const inner = node.expression.expression;
283
+ if (!ts.isIdentifier(inner) || !this.accounts.get(inner.text)?.optional)
284
+ return undefined;
285
+ const plain = ts.factory.createPropertyAccessExpression(inner, node.name);
286
+ ts.setTextRange(plain, node);
287
+ plain.parent = node.parent;
288
+ return plain;
289
+ }
222
290
  accountParam(node, what) {
291
+ // `acct!` on an optional account: the account (its use checks it was passed).
292
+ if (ts.isNonNullExpression(node) && ts.isIdentifier(node.expression) && this.accounts.get(node.expression.text)?.optional)
293
+ return this.accountParam(node.expression, what);
223
294
  if (!ts.isIdentifier(node))
224
295
  this.fail(node, `${what} must be an account parameter`);
225
296
  const a = this.accounts.get(node.text);
@@ -236,7 +307,7 @@ export class EmitterBase {
236
307
  ? "create"
237
308
  : a.kind.kind === "initIfNeeded"
238
309
  ? "createIfNeeded"
239
- : a.kind.kind === "token" && a.kind.init
310
+ : (a.kind.kind === "token" || a.kind.kind === "mint") && a.kind.init
240
311
  ? a.kind.init === "create"
241
312
  ? "create"
242
313
  : "createIfNeeded"
@@ -45,16 +45,16 @@ export function borshMinSize(s, model) {
45
45
  let n = 0;
46
46
  for (const f of s.fields) {
47
47
  const t = f.type;
48
- if (t.kind === "fstring" || t.kind === "vec")
48
+ if (t.kind === "fstring" || t.kind === "vec" || t.kind === "string")
49
49
  n += 4;
50
- else if (t.kind === "option")
50
+ else if (t.kind === "option" || t.kind === "dataEnum")
51
51
  n += 1;
52
52
  else
53
53
  n += fixedBytes(t, model);
54
54
  }
55
55
  return n;
56
56
  }
57
- const DESER = `TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data is not a valid Borsh value")`;
57
+ const DESER = `TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: bytes are not a valid Borsh value")`;
58
58
  const SER = `TS_FAIL(TS_ERR_DID_NOT_SERIALIZE, "TypeSOL: account data too small for the value")`;
59
59
  /** C for `ts_unpack_<S>` and `ts_pack_<S>` of every `@layout borsh` struct. */
60
60
  export function emitBorshCodecs(model) {
@@ -84,7 +84,7 @@ export function emitBorshCodecs(model) {
84
84
  };
85
85
  // Bounds: the caller checked `len >= MIN` (the smallest Borsh form). Fixed-size pieces then always fit; only a
86
86
  // String / Vec / Option payload is checked, against what is left after the fields that follow it still need.
87
- const minOf = (t) => (t.kind === "fstring" || t.kind === "vec" ? 4 : t.kind === "option" ? 1 : fixedBytes(t, model));
87
+ const minOf = (t) => t.kind === "fstring" || t.kind === "vec" || t.kind === "string" ? 4 : t.kind === "option" || t.kind === "dataEnum" ? 1 : fixedBytes(t, model);
88
88
  const minAfter = s.fields.map((_, i) => s.fields.slice(i + 1).reduce((n, f) => n + minOf(f.type), 0));
89
89
  const need = (n, rest, pad) => `${pad}if (${n} > len - at - ${rest}) ${DESER};`;
90
90
  const room = (n, rest, pad) => `${pad}if (${n} > cap - at - ${rest}) ${SER};`;
@@ -149,6 +149,43 @@ export function emitBorshCodecs(model) {
149
149
  `${pad}}`,
150
150
  ];
151
151
  }
152
+ case "string": {
153
+ // A view of the bytes it was read from (arguments only), checked as UTF-8 as Borsh String deserialization does.
154
+ const l = tmp("n");
155
+ return [
156
+ `${pad}uint32_t ${l} = (uint32_t)ts_read_le(src + at, 4);`,
157
+ `${pad}at += 4;`,
158
+ need(l, rest, pad),
159
+ `${pad}if (!ts_utf8_valid(src + at, ${l})) ${DESER};`,
160
+ `${pad}${lv} = ts_str(src + at, ${l});`,
161
+ `${pad}at += ${l};`,
162
+ ];
163
+ }
164
+ case "dataEnum": {
165
+ const e = model.dataEnums.get(t.name);
166
+ const tag = tmp("tag");
167
+ const lines = [
168
+ `${pad}uint8_t ${tag} = src[at++];`,
169
+ `${pad}if (${tag} >= ${e.variants.length}) ${DESER};`,
170
+ `${pad}${lv}.tag = ${tag};`,
171
+ `${pad}switch (${tag}) {`,
172
+ ];
173
+ e.variants.forEach((v, i) => {
174
+ lines.push(`${pad}case ${i}: {`);
175
+ for (const f of v.fields) {
176
+ const flv = `${lv}.v.${v.name}.${f.name}`;
177
+ if (f.type.kind === "string") {
178
+ lines.push(`${pad} if (4 > len - at - ${rest}) ${DESER};`, ...unpackVar(f.type, flv, rest, `${pad} `));
179
+ continue;
180
+ }
181
+ const n = fixedBytes(f.type, model);
182
+ lines.push(need(String(n), rest, `${pad} `), `${pad} ts_memcpy(&${flv}, src + at, ${n});`, `${pad} at += ${n};`, ...validate(f.type, flv, `${pad} `));
183
+ }
184
+ lines.push(`${pad} break;`, `${pad}}`);
185
+ });
186
+ lines.push(`${pad}}`);
187
+ return lines;
188
+ }
152
189
  default:
153
190
  throw new Error(`unreachable: ${t.kind} in a Borsh account`);
154
191
  }
@@ -199,6 +236,35 @@ export function emitBorshCodecs(model) {
199
236
  `${pad}}`,
200
237
  ];
201
238
  }
239
+ case "string": {
240
+ const l = tmp("n");
241
+ return [
242
+ `${pad}uint32_t ${l} = (uint32_t)${lv}.len;`,
243
+ room(`4 + (uint64_t)${l}`, rest, pad),
244
+ `${pad}ts_write_le(dst + at, ${l}, 4);`,
245
+ `${pad}ts_memcpy(dst + at + 4, ${lv}.ptr, ${l});`,
246
+ `${pad}at += 4 + (uint64_t)${l};`,
247
+ ];
248
+ }
249
+ case "dataEnum": {
250
+ const e = model.dataEnums.get(t.name);
251
+ const lines = [room("1", rest, pad), `${pad}dst[at++] = ${lv}.tag;`, `${pad}switch (${lv}.tag) {`];
252
+ e.variants.forEach((v, i) => {
253
+ lines.push(`${pad}case ${i}: {`);
254
+ for (const f of v.fields) {
255
+ const flv = `${lv}.v.${v.name}.${f.name}`;
256
+ if (f.type.kind === "string") {
257
+ lines.push(...packVar(f.type, flv, rest, `${pad} `));
258
+ continue;
259
+ }
260
+ const n = fixedBytes(f.type, model);
261
+ lines.push(room(String(n), rest, `${pad} `), `${pad} ts_memcpy(dst + at, &${flv}, ${n});`, `${pad} at += ${n};`);
262
+ }
263
+ lines.push(`${pad} break;`, `${pad}}`);
264
+ });
265
+ lines.push(`${pad}}`);
266
+ return lines;
267
+ }
202
268
  default:
203
269
  throw new Error(`unreachable: ${t.kind} in a Borsh account`);
204
270
  }
@@ -226,7 +292,7 @@ export function emitBorshCodecs(model) {
226
292
  i++;
227
293
  }
228
294
  const min = borshMinSize(s, model);
229
- out.push(`/* ${s.name} as Borsh (Anchor's account layout) <-> its fixed-layout struct. */`, `__attribute__((noinline)) static uint64_t ts_unpack_${s.name}(const uint8_t *src, uint64_t len, TSS_${s.name} *out) {`, ` if (len < ${min}) ${DESER};`, ` uint64_t at = 0;`, ...unpackLines, ` return SUCCESS;`, `}`, `__attribute__((noinline)) static uint64_t ts_pack_${s.name}(const TSS_${s.name} *in, uint8_t *dst, uint64_t cap) {`, ` if (cap < ${min}) ${SER};`, ` uint64_t at = 0;`, ...packLines, ` return SUCCESS;`, `}`, "");
295
+ out.push(`/* ${s.name} as Borsh (Anchor's account layout) <-> its fixed-layout struct. */`, `__attribute__((noinline)) static uint64_t ts_unpack_${s.name}(const uint8_t *src, uint64_t len, TSS_${s.name} *out, uint64_t *used) {`, ` if (len < ${min}) ${DESER};`, ` uint64_t at = 0;`, ...unpackLines, ` if (used) *used = at;`, ` return SUCCESS;`, `}`, `__attribute__((noinline)) static uint64_t ts_pack_${s.name}(const TSS_${s.name} *in, uint8_t *dst, uint64_t cap, uint64_t *written) {`, ` if (cap < ${min}) ${SER};`, ` uint64_t at = 0;`, ...packLines, ` if (written) *written = at;`, ` return SUCCESS;`, `}`, "");
230
296
  }
231
297
  return out;
232
298
  }
@@ -2,9 +2,6 @@ import ts from "typescript";
2
2
  import { type ErrorDef, type FieldType, type Helper } from "../model.js";
3
3
  import { type Value, type Ref } from "./shared.js";
4
4
  import { ExpressionEmitter } from "./expressions.js";
5
- /**
6
- * Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
7
- */
8
5
  export declare abstract class CallEmitter extends ExpressionEmitter {
9
6
  protected call(node: ts.CallExpression, topLevel: boolean): Value | undefined;
10
7
  protected vecMethod(node: ts.CallExpression, method: string, v: Ref): Value | undefined;
@@ -32,6 +29,8 @@ export declare abstract class CallEmitter extends ExpressionEmitter {
32
29
  protected loadForeign(node: ts.CallExpression): Value;
33
30
  /** `loadAccount<T>(acct)` / `loadAccountMut<T>(acct)`: checked view of a program account. */
34
31
  protected loadAccount(node: ts.CallExpression, mut: boolean): Value;
32
+ /** Whether a helper (or one it calls) grows a Vec, which needs the instruction's SystemProgram for rent. */
33
+ private helperGrows;
35
34
  protected callHelper(node: ts.CallExpression, h: Helper): Value | undefined;
36
35
  /** One helper argument: a C argument list fragment (accounts, seeds, references) or a value to settle. */
37
36
  protected helperArg(h: Helper, p: Helper["params"][number], n: ts.Expression): Value | string;