@typesol/cli 0.16.0 → 0.17.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 +46 -0
- package/README.md +3 -3
- package/dist/clientgen.js +37 -0
- package/dist/compiler/analyze.d.ts +7 -1
- package/dist/compiler/analyze.js +258 -30
- package/dist/compiler/emit/base.d.ts +7 -0
- package/dist/compiler/emit/base.js +61 -1
- package/dist/compiler/emit/borsh.d.ts +13 -0
- package/dist/compiler/emit/borsh.js +232 -0
- package/dist/compiler/emit/calls.js +43 -15
- package/dist/compiler/emit/cpi.d.ts +32 -8
- package/dist/compiler/emit/cpi.js +522 -20
- package/dist/compiler/emit/expressions.js +19 -2
- package/dist/compiler/emit/function.js +34 -11
- package/dist/compiler/emit/program.js +24 -6
- package/dist/compiler/emit/shared.d.ts +12 -0
- package/dist/compiler/emit/shared.js +19 -2
- package/dist/compiler/emit/statements.js +11 -3
- package/dist/compiler/emit/storage.d.ts +4 -0
- package/dist/compiler/emit/storage.js +60 -12
- package/dist/compiler/idl.js +33 -4
- package/dist/compiler/model.d.ts +41 -2
- package/dist/compiler/model.js +8 -2
- package/dist/portgen.js +60 -4
- package/docs/errors.md +3 -2
- package/docs/language.md +10 -6
- package/docs/migrate-from-anchor.md +14 -5
- package/docs/test-your-program.md +1 -1
- package/lib/typesol.d.ts +259 -3
- package/package.json +1 -1
- package/runtime/typesol_rt.h +32 -7
package/dist/compiler/model.d.ts
CHANGED
|
@@ -110,9 +110,11 @@ export type FieldType = ScalarType | {
|
|
|
110
110
|
name: string;
|
|
111
111
|
}
|
|
112
112
|
/** `Vec<T>`: Borsh u32 length + elements. Only as the last field of an account struct. */
|
|
113
|
+
/** `Vec<T>` (last field, grows the account) or, with `cap`, `FixedVec<T, N>`: a u32 length and room for N elements. */
|
|
113
114
|
| {
|
|
114
115
|
kind: "vec";
|
|
115
116
|
elem: FieldType;
|
|
117
|
+
cap?: number;
|
|
116
118
|
};
|
|
117
119
|
export interface StructField {
|
|
118
120
|
name: string;
|
|
@@ -136,6 +138,11 @@ export interface StructDef {
|
|
|
136
138
|
accountDiscriminator: number[];
|
|
137
139
|
eventDiscriminator: number[];
|
|
138
140
|
node: ts.InterfaceDeclaration;
|
|
141
|
+
/**
|
|
142
|
+
* How account data stores it: "fixed" (every field at a fixed offset, the default) or "borsh" (`@layout borsh`:
|
|
143
|
+
* Anchor's bytes, where strings, Vecs and Options take only what they hold).
|
|
144
|
+
*/
|
|
145
|
+
layout: "fixed" | "borsh";
|
|
139
146
|
}
|
|
140
147
|
export type TokenProgramKind = "token" | "token2022" | "tokenInterface";
|
|
141
148
|
export type ParamKind = {
|
|
@@ -291,6 +298,14 @@ export interface Instruction {
|
|
|
291
298
|
/** The declaring file's `typesol` imports (local name -> export name). */
|
|
292
299
|
imports: Map<string, string>;
|
|
293
300
|
}
|
|
301
|
+
/** A helper with type parameters, before it is instantiated. */
|
|
302
|
+
export interface GenericHelper {
|
|
303
|
+
name: string;
|
|
304
|
+
display: string;
|
|
305
|
+
node: HelperNode;
|
|
306
|
+
typeParams: string[];
|
|
307
|
+
imports: Map<string, string>;
|
|
308
|
+
}
|
|
294
309
|
export interface HelperParam {
|
|
295
310
|
name: string;
|
|
296
311
|
/** `{ kind: "account" }` for account parameters (then `account` says which kind). */
|
|
@@ -298,16 +313,28 @@ export interface HelperParam {
|
|
|
298
313
|
account?: ParamKind;
|
|
299
314
|
node: ts.ParameterDeclaration;
|
|
300
315
|
}
|
|
301
|
-
/** A
|
|
316
|
+
/** A function written as `function f(…)`, `const f = (…) => …` or `const f = function (…) {…}`. */
|
|
317
|
+
export type HelperNode = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;
|
|
318
|
+
/**
|
|
319
|
+
* A non-exported function (or any function of an imported file), or an arrow function declared with const at the top
|
|
320
|
+
* level or inside a function (capturing nothing): values and accounts in; void, a scalar or a struct out.
|
|
321
|
+
*/
|
|
302
322
|
export interface Helper {
|
|
323
|
+
/** The C name (fn_<name>): the function's name, `outer__local` for a local arrow, `name__u64` for an instance. */
|
|
303
324
|
name: string;
|
|
325
|
+
/** The name in messages: `local`, `max<u64>`. */
|
|
326
|
+
display: string;
|
|
304
327
|
params: HelperParam[];
|
|
305
328
|
/** A struct result is written through an out pointer (`TSS_<name> *ret`) that the caller provides. */
|
|
306
329
|
returns: ScalarType | {
|
|
307
330
|
kind: "struct";
|
|
308
331
|
name: string;
|
|
309
332
|
} | undefined;
|
|
310
|
-
node:
|
|
333
|
+
node: HelperNode;
|
|
334
|
+
/** The body; an arrow's expression body is one return (or expression) statement. */
|
|
335
|
+
statements: readonly ts.Statement[];
|
|
336
|
+
/** A generic helper's instance: type parameter -> the type it stands for. */
|
|
337
|
+
typeArgs?: Map<string, ScalarType>;
|
|
311
338
|
/** The declaring file's `typesol` imports (local name -> export name). */
|
|
312
339
|
imports: Map<string, string>;
|
|
313
340
|
}
|
|
@@ -352,6 +379,10 @@ export interface ProgramModel {
|
|
|
352
379
|
structs: Map<string, StructDef>;
|
|
353
380
|
instructions: Instruction[];
|
|
354
381
|
helpers: Map<string, Helper>;
|
|
382
|
+
/** Generic helpers by name: instantiated (into `helpers`) for the types each call uses. */
|
|
383
|
+
generics: Map<string, GenericHelper>;
|
|
384
|
+
/** Declaration (function, or the const of an arrow) -> helper or generic name, for calls to local arrows. */
|
|
385
|
+
helperDecls: Map<ts.Node, string>;
|
|
355
386
|
errors: ErrorDef[];
|
|
356
387
|
/** Enum name -> member name -> error */
|
|
357
388
|
errorEnums: Map<string, Map<string, ErrorDef>>;
|
|
@@ -374,6 +405,10 @@ export interface ProgramModel {
|
|
|
374
405
|
argStructs: Set<string>;
|
|
375
406
|
/** FixedString capacities in use, for C typedefs and IDL types. */
|
|
376
407
|
fstrings: Set<number>;
|
|
408
|
+
/** FixedVec<T, N> types in use (IDL name -> type), for IDL types. */
|
|
409
|
+
fixedVecs: Map<string, FieldType & {
|
|
410
|
+
kind: "vec";
|
|
411
|
+
}>;
|
|
377
412
|
}
|
|
378
413
|
export declare function isWritable(k: ParamKind): boolean;
|
|
379
414
|
export declare function typeName(t: ValueType | FieldType): string;
|
|
@@ -386,3 +421,7 @@ export declare function optionKey(t: ScalarType | {
|
|
|
386
421
|
name: string;
|
|
387
422
|
}): string;
|
|
388
423
|
export declare function scalarKey(t: ScalarType): string;
|
|
424
|
+
/** The IDL type name of a FixedVec<T, N>: `FixedVec<N>_<T>`. */
|
|
425
|
+
export declare function fixedVecName(t: FieldType & {
|
|
426
|
+
kind: "vec";
|
|
427
|
+
}): string;
|
package/dist/compiler/model.js
CHANGED
|
@@ -56,7 +56,7 @@ export function typeName(t) {
|
|
|
56
56
|
case "enumRef":
|
|
57
57
|
return t.name;
|
|
58
58
|
case "vec":
|
|
59
|
-
return `Vec<${typeName(t.elem)}>`;
|
|
59
|
+
return t.cap === undefined ? `Vec<${typeName(t.elem)}>` : `FixedVec<${typeName(t.elem)}, ${t.cap}>`;
|
|
60
60
|
case "vecArg":
|
|
61
61
|
return `Vec<${typeName(t.elem)}>`;
|
|
62
62
|
case "arrayArg":
|
|
@@ -92,7 +92,7 @@ export function sameType(a, b) {
|
|
|
92
92
|
case "option":
|
|
93
93
|
return sameType(a.inner, b.inner);
|
|
94
94
|
case "vec":
|
|
95
|
-
return sameType(a.elem, b.elem);
|
|
95
|
+
return a.cap === b.cap && sameType(a.elem, b.elem);
|
|
96
96
|
case "vecArg":
|
|
97
97
|
return sameType(a.elem, b.elem);
|
|
98
98
|
case "arrayArg":
|
|
@@ -120,3 +120,9 @@ export function optionKey(t) {
|
|
|
120
120
|
export function scalarKey(t) {
|
|
121
121
|
return t.kind === "int" ? t.name : t.kind === "enum" ? `e_${t.name}` : t.kind === "pubkey" && t.hash ? "hash" : t.kind;
|
|
122
122
|
}
|
|
123
|
+
/** The IDL type name of a FixedVec<T, N>: `FixedVec<N>_<T>`. */
|
|
124
|
+
export function fixedVecName(t) {
|
|
125
|
+
return `FixedVec${t.cap}_${typeName(t.elem)
|
|
126
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
127
|
+
.replace(/_+$/, "")}`;
|
|
128
|
+
}
|
package/dist/portgen.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* It writes the account structs, enums, errors, events and one function per instruction with its accounts and
|
|
5
5
|
* arguments, bodies left to fill in. Instructions keep their discriminators (an explicit `@discriminator` when the
|
|
6
6
|
* name would derive another), account structs keep their names (so their discriminators), errors keep their order —
|
|
7
|
-
* so existing clients keep working
|
|
8
|
-
*
|
|
7
|
+
* so existing clients keep working. Account structs holding a String, Vec or Option get `@layout borsh`, so they keep
|
|
8
|
+
* Anchor's bytes (existing accounts stay readable); where a layout still differs (a String or Vec nested in a struct, a
|
|
9
|
+
* data enum, a COption) it says so in a note and a comment.
|
|
9
10
|
*/
|
|
10
11
|
import { createHash } from "node:crypto";
|
|
11
12
|
import { cNameProblem } from "./compiler/analyze.js";
|
|
@@ -152,6 +153,57 @@ export function generatePort(idl) {
|
|
|
152
153
|
notes.push(`${at}: unsupported IDL type ${JSON.stringify(t)}; left as u64`);
|
|
153
154
|
return `${use("u64")} /* TODO */`;
|
|
154
155
|
};
|
|
156
|
+
/** A String, Vec or Option at a struct's top level: Borsh-sized in Anchor. */
|
|
157
|
+
const borshOnly = (t) => t === "string" || t === "bytes" || (typeof t === "object" && (!!t.vec || !!t.option));
|
|
158
|
+
/** A field's type in a `@layout borsh` struct, or undefined when that layout cannot hold it. Notes the capacities. */
|
|
159
|
+
const borshField = (t, at) => {
|
|
160
|
+
const quiet = at === "";
|
|
161
|
+
const fixedOk = (u) => {
|
|
162
|
+
if (typeof u === "string")
|
|
163
|
+
return INTS.has(u) || u === "bool" || u === "pubkey" || u === "publicKey";
|
|
164
|
+
if (u.array)
|
|
165
|
+
return Number.isInteger(Number(u.array[1])) && fixedOk(u.array[0]);
|
|
166
|
+
if (u.defined) {
|
|
167
|
+
const name = definedName(u.defined);
|
|
168
|
+
const d = defs.get(name);
|
|
169
|
+
if (!d || skipped.has(name) || helperDefs.has(name))
|
|
170
|
+
return false;
|
|
171
|
+
if (d.type.kind === "enum")
|
|
172
|
+
return (d.type.variants ?? []).every((v) => !v.fields?.length);
|
|
173
|
+
return (d.type.fields ?? []).every((f) => fixedOk(f.type));
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
};
|
|
177
|
+
if (t === "string") {
|
|
178
|
+
if (!quiet)
|
|
179
|
+
notes.push(`${at}: set the String's capacity to Anchor's #[max_len] (Borsh bytes are kept: @layout borsh)`);
|
|
180
|
+
return `${use("FixedString")}<32> /* TODO: #[max_len] */`;
|
|
181
|
+
}
|
|
182
|
+
if (t === "bytes")
|
|
183
|
+
return borshField({ vec: "u8" }, at);
|
|
184
|
+
if (typeof t === "object" && t.vec) {
|
|
185
|
+
const elem = t.vec === "string"
|
|
186
|
+
? `${use("FixedString")}<32> /* TODO: #[max_len] */`
|
|
187
|
+
: fixedOk(t.vec) && !quiet
|
|
188
|
+
? tsType(t.vec, "account", at)
|
|
189
|
+
: fixedOk(t.vec)
|
|
190
|
+
? "x"
|
|
191
|
+
: undefined;
|
|
192
|
+
if (elem === undefined)
|
|
193
|
+
return undefined;
|
|
194
|
+
if (!quiet)
|
|
195
|
+
notes.push(`${at}: set the Vec's capacity to Anchor's #[max_len] (Borsh bytes are kept: @layout borsh)`);
|
|
196
|
+
return `${use("FixedVec")}<${elem}, 8> /* TODO: #[max_len] */`;
|
|
197
|
+
}
|
|
198
|
+
if (typeof t === "object" && t.option) {
|
|
199
|
+
if (!fixedOk(t.option))
|
|
200
|
+
return undefined;
|
|
201
|
+
return quiet ? "x" : `${use("Option")}<${tsType(t.option, "account", at)}>`;
|
|
202
|
+
}
|
|
203
|
+
if (!fixedOk(t))
|
|
204
|
+
return undefined;
|
|
205
|
+
return quiet ? "x" : tsType(t, "account", at);
|
|
206
|
+
};
|
|
155
207
|
const esc = (s) => s.replace(/\*\//g, "* /");
|
|
156
208
|
const docs = (lines, pad = "") => (lines?.length ? `${pad}/** ${esc(lines.join(" "))} */\n` : "");
|
|
157
209
|
const out = [];
|
|
@@ -180,8 +232,11 @@ export function generatePort(idl) {
|
|
|
180
232
|
const where = used.has("account") ? "account" : used.has("arg") ? "struct" : used.has("event") ? "event" : "account";
|
|
181
233
|
if (def.type.kind === "struct") {
|
|
182
234
|
const fields = (def.type.fields ?? []);
|
|
235
|
+
// An account struct with a String, Vec or Option of its own: `@layout borsh` keeps Anchor's bytes.
|
|
236
|
+
const borsh = accountNames.has(def.name) && !isTuple(fields) && fields.some((f) => borshOnly(f.type)) && fields.every((f) => borshField(f.type, "") !== undefined);
|
|
183
237
|
const tag = accountNames.has(def.name) ? " (account)" : eventNames.has(def.name) ? ` (event: emit<${typeName(def.name)}>({ … }))` : "";
|
|
184
|
-
|
|
238
|
+
const doc = def.docs?.length ? esc(def.docs.join(" ")) : `${def.name}${tag}`;
|
|
239
|
+
out.push(borsh ? `/**\n * ${doc}\n * @layout borsh\n */` : `/** ${doc} */`);
|
|
185
240
|
out.push(`export interface ${typeName(def.name)} {`);
|
|
186
241
|
const seen = new Set();
|
|
187
242
|
fields.forEach((f, i) => {
|
|
@@ -189,7 +244,8 @@ export function generatePort(idl) {
|
|
|
189
244
|
while (seen.has(n))
|
|
190
245
|
n = `${n}2`;
|
|
191
246
|
seen.add(n);
|
|
192
|
-
|
|
247
|
+
const ty = borsh ? borshField(f.type, `${def.name}.${f.name}`) : tsType(f.type, where, `${def.name}.${f.name}`, i === fields.length - 1);
|
|
248
|
+
out.push(`${docs(f.docs, " ")} ${n}: ${ty};`);
|
|
193
249
|
});
|
|
194
250
|
out.push("}", "");
|
|
195
251
|
}
|
package/docs/errors.md
CHANGED
|
@@ -15,7 +15,8 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
|
|
|
15
15
|
| 2040 | the same account passed in two writable slots (ConstraintDuplicateMutableAccount) |
|
|
16
16
|
| 3000 | account already exists / initialized (AccountDiscriminatorAlreadySet) |
|
|
17
17
|
| 3002 | discriminator mismatch (AccountDiscriminatorMismatch) |
|
|
18
|
-
| 3003 | data too small, not an initialized token account / mint,
|
|
18
|
+
| 3003 | data too small, not an initialized token account / mint, a read past the end, or a `@layout borsh` account whose bytes are not a valid Borsh value (AccountDidNotDeserialize) |
|
|
19
|
+
| 3004 | a `@layout borsh` account too small for the value it would now hold (AccountDidNotSerialize) |
|
|
19
20
|
| 3005 | not enough accounts (AccountNotEnoughKeys) |
|
|
20
21
|
| 3006 | account must be writable (AccountNotMutable) |
|
|
21
22
|
| 3007 | wrong owner program (AccountOwnedByWrongProgram) |
|
|
@@ -26,7 +27,7 @@ Framework errors use Anchor's numbers where Anchor has the same check, so client
|
|
|
26
27
|
| 4102 | cast overflow (InvalidNumericConversion) |
|
|
27
28
|
| 5100 / 5101 | arithmetic overflow (including shifts) / division by zero |
|
|
28
29
|
| 5103 | sysvar unavailable |
|
|
29
|
-
| 5104 | index out of bounds (arrays, Vecs, remaining accounts, `pop()` on an empty Vec) |
|
|
30
|
+
| 5104 | index out of bounds (arrays, Vecs, remaining accounts, `pop()` on an empty Vec, `push` on a full FixedVec) |
|
|
30
31
|
| 5105 | string longer than the FixedString capacity |
|
|
31
32
|
| 5106 | missing or mismatched return data |
|
|
32
33
|
| 5107 | event larger than 1024 bytes |
|
package/docs/language.md
CHANGED
|
@@ -25,6 +25,8 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
25
25
|
| Enums | `export enum Status { Pending, Approved }` — unit variants stored as the Borsh u8 index; `Status.Approved`, `===` |
|
|
26
26
|
| Options | `best: Option<u64>` (or `u64 \| null`) — `null`, `=== null`, TypeScript narrowing; `===` between options or with a value follows JS (null equals null, empty never equals a value); empty use aborts (5108); `a ?? b` takes b only when a is empty (b runs only then; the result is an Option when b can be null), `x!` is the value or aborts with 5108; `${opt}` prints the value or `null`. Borsh `option` in arguments/events, fixed `{ is_some, value }` in account data |
|
|
27
27
|
| Vecs | `entries: Vec<Entry>` as the last account field — `get`, `set`, `push(v, payer)`, `pop()`, `clear()`, `.length`; the account is resized (realloc) and rent topped up by `payer`; `Vec<u64>` arguments are read-only views; `pop()` of a Vec of structs works as a statement |
|
|
28
|
+
| Anchor's account bytes | `/** @layout borsh */` on an account struct stores it as Anchor does (Borsh: a `FixedString<N>` is a String, a `FixedVec<T, N>` a Vec, an `Option` one byte when empty, and the fields after them move), so a TypeSOL program can take over accounts an Anchor program wrote and the IDL lists Anchor's own types. The instruction unpacks it into the usual fixed layout when it starts (bad bytes fail with 3003) and packs it back when it returns (3004 when the account is too small for the new value), so everything else works on it unchanged. Programs invoked from the body (CPIs) see the account's bytes as they were when the instruction started. Costs more compute than the fixed layout (the whole account is read and written each time; see the benchmarks); nested structs and list elements must be fixed-size (FixedString elements allowed); not for arguments, events, `Zeroed<T>`, `loadAccount` or a growable `Vec<T>` |
|
|
29
|
+
| Fixed-capacity lists | `owners: FixedVec<Pubkey, 8>` — any field, any number of them, in nested structs too: a u32 length and room for N elements (always 4 + N × size bytes), the same `get`, `set`, `push(v)`, `pop()`, `clear()`, `.length` and `for … of`; `push` on a full one fails (5104); `acct.owners = fixedVec([a, b])` writes a whole one (unused slots zeroed). Account data and returned structs only: arguments use `Vec<T>` |
|
|
28
30
|
| Struct arguments | `order: Order`, `ids: FixedArray<u8, 4>`, `orders: Vec<Order>` — fixed layouts, Borsh-identical, validated on entry (bools, enums, option tags, string lengths, enum tags; 102) and read-only |
|
|
29
31
|
| Remaining accounts | `remainingAccounts()` — `.length`, `.get(i)` → `key`, `owner`, `lamports` (writes check writability), `isSigner`, `isWritable`; `loadAccount<T>` / `loadAccountMut<T>` give checked struct views; pass them to `invoke`, one at a time or spread: `...remainingAccounts()` (each with the flags it was passed with), `...readonly(rest)` / `...writable(rest)` (all with that role), `rest.slice(a, b)` for part of them; a CPI lists at most 32 accounts (5113) |
|
|
30
32
|
| Struct literals | `book.entries.push({ who: who.key, amount, status: Status.Pending, note }, who)` — every field required |
|
|
@@ -41,6 +43,8 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
41
43
|
| Multiple files | `import { Side, min } from "./math"` — structs, enums, constants and helpers can live in other files; only the entry file's exported functions are instructions; names are program-wide, so imports from TypeSOL files cannot be renamed; error codes number imported files first |
|
|
42
44
|
| Loops | `for (let i = 0n; i < n; i++)`, `while`, `for (const x of list)` over a `Vec` or `FixedArray` (each `x` is what `list.get(i)` gives: a value, or a reference into account data for structs; the length is read again every step, as in JS), `break`, `continue` — bounded by the compute budget. Remaining accounts are iterated by index |
|
|
43
45
|
| Helpers | non-exported functions (and every function of an imported file): take values, structs, Vec arguments, `SignerSeeds` and accounts the instruction already checked (`Signer`, `AccountInfo`/`AccountMeta`, `Mut<T>`, token accounts, programs); return `void`, a scalar or a fixed-size struct (`function at(x: u64, y: u64): Point { return { x, y }; }`: the caller gets its own copy, to bind, store with `acct.home = at(…)` / `arr.set(i, at(…))` or read as `at(…).x`); no recursion. Helpers cannot grow Vecs (only instructions know the entry size) |
|
|
46
|
+
| Arrow functions | `const fee = (x: u64): u64 => x / 100n` (or `const f = function (…) {…}`) at the top level or inside a function is a helper (parameter and return types written out). A local one capturing nothing: it reads its parameters, top-level names and other functions, not the enclosing function's variables (pass them in). Functions are called, not stored or passed |
|
|
47
|
+
| Generic helpers | `function max<T extends bigint>(a: T, b: T): T` — compiled once per set of types it is called with (`max(a, b)` with u64 arguments, or `max<u128>(x, 1n)` when only literals tell); a type parameter stands for an integer, boolean, Pubkey or enum. Each instance is checked like any helper; one that is never called is not compiled (TypeScript still type-checks it). Arithmetic on `T` is a `bigint` to TypeScript: `return (a + b) as T` (`as` names a type the value already has; it never converts) |
|
|
44
48
|
| Evaluation order | operands and arguments are read left to right, as in JS, even when a later one changes account data (`v.length + v.pop()`) |
|
|
45
49
|
| `++` `--` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | all checked |
|
|
46
50
|
| Sysvars | `now()`, `currentSlot()`, `currentEpoch()` (clock), `minimumBalance(account)`, `rentExempt(space)` (rent) |
|
|
@@ -49,8 +53,8 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
49
53
|
| Events through CPI | `emitCpi<Paid>({ … }, eventAuthority, self)` with `eventAuthority: EventAuthority, self: SelfProgram` parameters — Anchor `emit_cpi!`: the event rides in a self-CPI (inner instruction), not in logs; the program accepts Anchor's event instruction (2002 / 2006 otherwise) |
|
|
50
54
|
| Heap | 32 KiB by default; `const HEAP_SIZE_KIB = 64n` (32–256) lets the program use more, when clients add ComputeBudget `requestHeapFrame` |
|
|
51
55
|
| Docs | JSDoc on instructions, `@param`, structs, fields and data enums becomes `docs` in the IDL |
|
|
52
|
-
| Metaplex Core | `core: MplCoreProgram` — `createCollection`, `createAsset` (in a collection, PDA authorities via signerSeeds), `transferAsset`, `burnAsset` (
|
|
53
|
-
| Compressed NFTs | `bubblegum: BubblegumProgram` — `createTree` (client-allocated tree on SPL Account Compression), `mint` (MintV1, no collection/creators) |
|
|
56
|
+
| Metaplex Core | `core: MplCoreProgram` — `createCollection`, `createAsset` (in a collection, PDA authorities via signerSeeds), `transferAsset`, `burnAsset` (`null` leaves an optional account out); plugins as a literal list after the uri (`[{ kind: "Royalties", basisPoints: 500n, creators: [{ address, percentage: 100n }] }, { kind: "PermanentFreezeDelegate", frozen: false, authority: studio.key }]`), and `addPlugin` / `updatePlugin` / `removePlugin(…, "FreezeDelegate")` with their `…CollectionPlugin` forms. Plugins: Royalties (no rule set), FreezeDelegate, BurnDelegate, TransferDelegate, UpdateDelegate, PermanentFreezeDelegate, Attributes, PermanentTransferDelegate, PermanentBurnDelegate, Edition, AddBlocker, ImmutableMetadata; `authority` is `"owner"`, `"updateAuthority"`, `"none"` or a Pubkey; `coreAssetOwner(asset)`, `coreAssetCollection(asset)` |
|
|
57
|
+
| Compressed NFTs | `bubblegum: BubblegumProgram` — V1: `createTree` (client-allocated tree on SPL Account Compression), `mint` (MintV1, no collection/creators). V2 (trees on mpl-account-compression with mpl-noop): `createTreeV2`, `mintV2(…, { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable? }, { collection, authority, cpiSigner }?)` (into a Core collection that has the `BubblegumV2` plugin), `transferV2` and `burnV2` with the leaf `{ root, dataHash, creatorHash, assetDataHash?, flags?, nonce, index }` and its proof as `remainingAccounts()` / `rest.slice(a)` |
|
|
54
58
|
|
|
55
59
|
## Account parameters and the checks they emit
|
|
56
60
|
|
|
@@ -76,7 +80,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
76
80
|
| `SystemProgram`, `TokenProgram`, `Token2022Program` | exact program address |
|
|
77
81
|
| `TokenInterface` | either token program |
|
|
78
82
|
| `AssociatedTokenProgram` | `create` / `createIdempotent` CPIs; `associatedTokenAddress(owner, mint, tokenProgram)` |
|
|
79
|
-
| `TokenMetadataProgram` | Metaplex `createV3(...)` (CreateMetadataAccountV3), optionally with `{ creators: [{ address, verified, share }], collection: mint }` after `isMutable` (1–5 creators, shares adding up to 100; only the update authority, signing, can be `verified: true`; the collection is written unverified); `metadataAddress(mint)` |
|
|
83
|
+
| `TokenMetadataProgram` | Metaplex `createV3(...)` (CreateMetadataAccountV3), optionally with `{ creators: [{ address, verified, share }], collection: mint }` after `isMutable` (1–5 creators, shares adding up to 100; only the update authority, signing, can be `verified: true`; the collection is written unverified; `isCollection: true` makes the NFT a sized collection); `createMasterEditionV3(edition, mint, updateAuthority, mintAuthority, payer, metadata, tokenProgram, systemProgram, maxSupply \| null)`; `verifyCollection(authority, metadata, collectionMint, collectionMetadata, collectionMasterEdition, systemProgram, instructionsSysvar)` (Verify with CollectionV1); `metadataAddress(mint)`, `masterEditionAddress(mint)` |
|
|
80
84
|
| `InstructionsSysvar`, `PythPriceUpdate`, `SwitchboardFeed` | exact address / owner program + discriminator |
|
|
81
85
|
| `Program<"Address…">` | exact program address + executable |
|
|
82
86
|
| `UncheckedProgram` | executable only — check `.key` yourself |
|
|
@@ -145,10 +149,10 @@ an integer literal, an argument, `acct.key` or a scalar field of an account, the
|
|
|
145
149
|
|
|
146
150
|
## Not supported yet
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
Growable `Vec`s anywhere but the last account field (use `FixedVec<T, N>`), FixedVec in arguments and events, growing an account by more than 10 KiB in one instruction, growing a Vec
|
|
149
153
|
inside a helper, data-enum arguments carrying structs, Pubkeys in templates, `?.`, `**=`, `!` on anything but an Option, `switch` on
|
|
150
|
-
128-bit integers, `mulDiv` beyond u64,
|
|
151
|
-
|
|
154
|
+
128-bit integers, `mulDiv` beyond u64, Core's MasterEdition / VerifiedCreators / Autograph plugins, royalty rule sets and external plugin adapters, Bubblegum V2 delegates / freezes / metadata updates / V1 transfers, closures (local functions reading the enclosing function's variables), functions as values (callbacks,
|
|
155
|
+
`.map`), type parameters standing for structs, floating point (by design), `do … while`.
|
|
152
156
|
They are rejected at compile time rather than half-supported.
|
|
153
157
|
|
|
154
158
|
`Init.create` handles a PDA address someone pre-funded: it tops up rent, then allocates and assigns instead of
|
|
@@ -60,14 +60,21 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
|
|
|
60
60
|
| `u8` … `u128`, `i8` … `i128`, `bool`, `Pubkey` | `u8` … `u128`, `i8` … `i128`, `boolean`, `Pubkey` |
|
|
61
61
|
| `[T; N]` | `FixedArray<T, N>` |
|
|
62
62
|
| `String` with `#[max_len(n)]` | `FixedString<N>`: a u32 length and N bytes, always N bytes long |
|
|
63
|
-
| `Vec<T>` with `#[max_len(n)]` | `Vec<T>` as the account's last field
|
|
63
|
+
| `Vec<T>` with `#[max_len(n)]` | `Vec<T>` as the account's last field (it grows with `push(v, payer)`), or `FixedVec<T, n>` anywhere: a u32 length and room for n, always 4 + n × size bytes |
|
|
64
64
|
| `Option<T>` | `Option<T>`: fixed size (1 + size of T) |
|
|
65
65
|
| `#[derive(AnchorSerialize…)] enum` | `export enum` (unit variants) or a data enum `type A = { kind: "X"; … } \| …` |
|
|
66
66
|
|
|
67
|
-
TypeSOL keeps account data at a fixed layout, so three Anchor layouts differ: a Borsh `String` is
|
|
68
|
-
uses `FixedString<N>`), a Borsh `Option` takes 1 byte when empty (TypeSOL always reserves the
|
|
69
|
-
anywhere in an Anchor struct (TypeSOL keeps
|
|
70
|
-
|
|
67
|
+
TypeSOL keeps account data at a fixed layout by default, so three Anchor layouts differ: a Borsh `String` is
|
|
68
|
+
variable-length (TypeSOL uses `FixedString<N>`), a Borsh `Option` takes 1 byte when empty (TypeSOL always reserves the
|
|
69
|
+
value), and a `Vec` can be anywhere in an Anchor struct (TypeSOL keeps a growable one last; a `FixedVec<T, N>` elsewhere
|
|
70
|
+
always takes its full capacity, where Anchor packs the elements and moves the fields after them).
|
|
71
|
+
|
|
72
|
+
To keep an Anchor program's existing accounts, mark the struct `/** @layout borsh */`: it is then stored exactly as
|
|
73
|
+
Anchor stores it (`FixedString<N>` / `FixedVec<T, N>` / `Option<T>` become Borsh String / Vec / Option, N being the
|
|
74
|
+
`#[max_len]`), its IDL type is Anchor's, and the upgraded program reads and writes the accounts in place. `typesol port`
|
|
75
|
+
adds it for you. It costs some compute (the account is unpacked and packed in every instruction that uses it) but stays
|
|
76
|
+
several times cheaper than Anchor. Accounts using them need a migration (or a new program) rather
|
|
77
|
+
than an in-place upgrade, unless they use `@layout borsh`. Everything else, including instruction arguments (`String`, `Option`, `Vec` are Borsh there),
|
|
71
78
|
matches byte for byte.
|
|
72
79
|
|
|
73
80
|
## Code
|
|
@@ -90,6 +97,8 @@ matches byte for byte.
|
|
|
90
97
|
| `declare_program!(other)` / CPI to another Anchor program | `typesol cpi other.json` generates typed helpers |
|
|
91
98
|
| `for x in v.iter()` | `for (const x of v)` |
|
|
92
99
|
| `x.unwrap_or(d)` | `x ?? d` |
|
|
100
|
+
| `fn max<T: PartialOrd>(a: T, b: T) -> T` | `function max<T extends bigint>(a: T, b: T): T` (one copy per type used) |
|
|
101
|
+
| `let fee = \|x: u64\| x / 100;` (no captures) | `const fee = (x: u64): u64 => x / 100n;` |
|
|
93
102
|
|
|
94
103
|
## Tooling
|
|
95
104
|
|
|
@@ -41,7 +41,7 @@ it("counts", async () => {
|
|
|
41
41
|
| --- | --- |
|
|
42
42
|
| `t.ok(res)` | the successful result, or throws with the program's logs |
|
|
43
43
|
| `t.errorCode(res)` | the custom error code of a failed transaction: 6000 + the member's index in your error enum, or a framework code ([errors.md](errors.md)) |
|
|
44
|
-
| `t.account(type, address)` | the account decoded by its IDL type (bigints, address strings), or `null` once closed |
|
|
44
|
+
| `t.account(type, address)` | the account decoded by its IDL type (bigints, address strings), or `null` once closed. TypeSOL fixed layouts come back as their IDL structs: a `FixedString<N>` as `{ len, bytes }`, a `FixedVec<T, N>` as `{ len, items }` (the generated client gives strings and arrays) |
|
|
45
45
|
| `t.returned(instruction, res)` | the instruction's return data: an integer, boolean, Pubkey or struct |
|
|
46
46
|
| `t.balance(address)` | lamports |
|
|
47
47
|
|