@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.
- package/CHANGELOG.md +99 -0
- package/README.md +4 -4
- package/dist/clientgen.js +2 -0
- package/dist/compiler/analyze.d.ts +10 -1
- package/dist/compiler/analyze.js +165 -49
- package/dist/compiler/anchor-errors.d.ts +1 -0
- package/dist/compiler/anchor-errors.js +80 -0
- package/dist/compiler/emit/base.d.ts +29 -1
- package/dist/compiler/emit/base.js +79 -8
- package/dist/compiler/emit/borsh.js +71 -5
- package/dist/compiler/emit/calls.d.ts +2 -3
- package/dist/compiler/emit/calls.js +101 -15
- package/dist/compiler/emit/cpi.d.ts +31 -0
- package/dist/compiler/emit/cpi.js +1061 -39
- package/dist/compiler/emit/expressions.d.ts +24 -0
- package/dist/compiler/emit/expressions.js +137 -19
- package/dist/compiler/emit/function.js +57 -10
- package/dist/compiler/emit/shared.d.ts +8 -0
- package/dist/compiler/emit/shared.js +4 -1
- package/dist/compiler/emit/statements.d.ts +21 -0
- package/dist/compiler/emit/statements.js +386 -16
- package/dist/compiler/emit/storage.d.ts +2 -0
- package/dist/compiler/emit/storage.js +28 -4
- package/dist/compiler/idl.js +4 -1
- package/dist/compiler/model.d.ts +29 -2
- package/dist/portgen.js +32 -2
- package/docs/errors.md +3 -2
- package/docs/language.md +31 -24
- package/docs/migrate-from-anchor.md +13 -3
- package/lib/typesol.d.ts +528 -22
- package/package.json +1 -1
- package/runtime/typesol_rt.h +279 -5
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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) =>
|
|
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;
|