@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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,105 @@ until 1.0, a minor version may change the language, the IDL layout or error code
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.19.0 — 2026-10-28
|
|
9
|
+
|
|
10
|
+
Anchor's error codes where TypeSOL raised its own: `TokenOf` / `MintOf` constraints that name the parameters as
|
|
11
|
+
`token::mint = …` does, every Anchor `ErrorCode` for `require`, and `init_if_needed` mints with extensions checked as
|
|
12
|
+
Anchor checks them. Strings and logs cost up to a third fewer compute units. 30 of the 46 official examples are now
|
|
13
|
+
full ports. Existing programs keep their layouts and error codes. Not externally audited (see SECURITY.md).
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `TokenOf<TokenAccount, "mintParam", "authorityParam">` and `MintOf<Mint, "authorityParam", "freezeParam">`: Anchor's
|
|
17
|
+
`token::mint = …` / `mint::authority = …` naming the parameters (2014 / 2015 / 2016 / 2017).
|
|
18
|
+
- `AnchorError`: Anchor's own error codes for `require(condition, AnchorError.X)` (`RequireKeysEqViolated`,
|
|
19
|
+
`AccountDidNotDeserialize`, the extension constraints …), as `require!` / `err!` with `ErrorCode` raise them.
|
|
20
|
+
- Extensions on `InitIfNeeded<Mint>`: an existing mint is checked as Anchor's `extensions::…` constraints check it
|
|
21
|
+
(2024–2039).
|
|
22
|
+
- Bubblegum `mintToCollection` (MintToCollectionV1 on V1 trees, Token Metadata collections).
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Strings are built with less work and heap: a template literal is joined in one allocation (it was copied piece by
|
|
26
|
+
piece), text known at compile time (literals, string and Pubkey constants) is folded in, `s += …` and `a + b` grow
|
|
27
|
+
the newest heap string in place, and base58 of a Pubkey is about 20% cheaper. Results are the same; official
|
|
28
|
+
examples that log addresses use 11–36% fewer compute units (set_favorites 10,244 → 6,601, transfer_tokens
|
|
29
|
+
9,760 → 8,467, create_system_account 4,664 → 4,075, go_to_park 435 → 372); binaries of programs that build
|
|
30
|
+
strings grow by up to 2.4 KB.
|
|
31
|
+
|
|
32
|
+
## 0.18.0 — 2026-10-28
|
|
33
|
+
|
|
34
|
+
The remaining Anchor differences, closed: Anchor's argument structs (Strings, Vecs, Options and enums in Borsh),
|
|
35
|
+
keypair `init` with `space`, `init` of mints with Token-2022 extensions and of token accounts, `Vec<String>`
|
|
36
|
+
arguments, `SystemAccount`, string return values, and the Token-2022 and Token Metadata instructions programs call.
|
|
37
|
+
Closures, list callbacks and more everyday TypeScript; the rest of Bubblegum and Core. 26 of the 46 official
|
|
38
|
+
examples are now full ports. Existing programs keep their layouts and error codes (2018 and 3011 are new, raised only
|
|
39
|
+
by the new `Init<Mint>` and `SystemAccount` checks). Not externally audited (see SECURITY.md).
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
Anchor accounts and arguments:
|
|
43
|
+
- `@layout borsh` structs as whole instruction arguments, return values and events, in Anchor's Borsh bytes (a
|
|
44
|
+
FixedString is a String, a FixedVec a Vec, an Option one byte when empty). Arguments are checked as Borsh
|
|
45
|
+
deserialization does, UTF-8 included (102 otherwise). `return vault` returns a whole struct account.
|
|
46
|
+
- `@layout borsh` structs used as arguments, return values or events may hold `string` fields and data enums (with or
|
|
47
|
+
without strings), as Anchor argument structs do; Borsh accounts may hold data enums without strings.
|
|
48
|
+
- `acct.create(payer, null)` on `Init<T>`: Anchor's `init` of a keypair account (it must sign; 3010 otherwise), and an
|
|
49
|
+
optional third argument for Anchor's `space = …` on `create` / `createIfNeeded`.
|
|
50
|
+
- Mint extensions on `Init<Mint>`: `mint.create(payer, seeds | null, decimals, authority, freeze, { … })` with
|
|
51
|
+
transferFee, closeAuthority, defaultAccountState, nonTransferable, interestBearing, permanentDelegate, transferHook,
|
|
52
|
+
metadataPointer, groupPointer and groupMemberPointer (Anchor's `extensions::…`).
|
|
53
|
+
- `Init<Mint>` / `InitIfNeeded<Mint>` with `mint.create(payer, seeds | null, decimals, mintAuthority, freezeAuthority)`
|
|
54
|
+
and `Init<TokenAccount>` / `InitIfNeeded<TokenAccount>` (without `Ata<…>`) with `vault.create(payer, seeds | null,
|
|
55
|
+
mint, authority)`: Anchor's `init` with `mint::` / `token::` constraints, at a PDA of this program or (with `null`) a
|
|
56
|
+
new keypair account that signs, under SPL Token or Token-2022 (token accounts sized with GetAccountDataSize). The
|
|
57
|
+
`createIfNeeded` forms check an existing account like Anchor's `init_if_needed` (2014 / 2015 / 2016 / 2017 and the
|
|
58
|
+
new 2018 ConstraintMintDecimals). The IDL marks keypair-created accounts as signers.
|
|
59
|
+
- `SystemAccount` / `Mut<SystemAccount>`: Anchor's `SystemAccount`, refused with 3011 (AccountNotSystemOwned) when
|
|
60
|
+
another program owns it.
|
|
61
|
+
- `Vec<string>` instruction arguments (Anchor's `Vec<String>`): `.get(i)`, `.length`, `for … of`, list callbacks,
|
|
62
|
+
helper parameters, storing into `FixedVec<FixedString<N>, M>`, and re-encoding through `invoke`; each string is
|
|
63
|
+
checked as UTF-8 on entry (102).
|
|
64
|
+
- Helpers and instructions can return `string` (an instruction's is Borsh String return data).
|
|
65
|
+
- `acct.key` of an `Init<…>` account before it is created (the address is known; Anchor seeds use it that way).
|
|
66
|
+
- `mint.create(…, tokenProgram)` / `vault.create(…, tokenProgram)`: an explicit token program, for instructions that
|
|
67
|
+
take more than one.
|
|
68
|
+
|
|
69
|
+
Language:
|
|
70
|
+
- Closures: a local function that reads or writes the enclosing function's variables is expanded where it is called,
|
|
71
|
+
with the names it was declared with (a block at the call that declares the same name does not change it), struct
|
|
72
|
+
parameters by reference and `return` leaving the closure. Recursion and storing / returning closures are refused.
|
|
73
|
+
- List callbacks on Vec, FixedArray, FixedVec and Vec arguments: `forEach`, `some`, `every`, `find`, `findIndex`,
|
|
74
|
+
`reduce`, `includes`, `indexOf`, expanded into loops with the arrow written at the call.
|
|
75
|
+
- Helpers can take function parameters (`f: (x: u64) => u64`): compiled once per named function passed.
|
|
76
|
+
- `do … while` loops (`continue` goes to the test).
|
|
77
|
+
- `x **= n` (the target must not call a function: it is read and written).
|
|
78
|
+
- `switch` on 128-bit integers (the case is picked with comparisons, then switched on as usual, fallthrough included).
|
|
79
|
+
- `?.` on optional accounts (`acct?.field`) and on `Option<struct>` fields (`x.home?.field`): an Option, empty when the
|
|
80
|
+
account was left out or the option is empty; use it with `??`.
|
|
81
|
+
- `acct!` on optional accounts (fails with 5115 when it was left out), Pubkeys / Hashes in template strings (base58),
|
|
82
|
+
and data-enum arguments whose variants carry fixed-size structs or arrays.
|
|
83
|
+
- Type parameters standing for fixed-size structs (`pick<Point>(…)`, or taken from struct arguments), and `cond ? a : b`
|
|
84
|
+
for struct values (places, literals or calls).
|
|
85
|
+
- Growable `Vec<T>` `push` / `pop` / `clear` inside helpers that take the account as `Mut<T>` (with its payer and
|
|
86
|
+
SystemProgram, as in instructions).
|
|
87
|
+
- `mulDiv` / `mulDivCeil` on u128 through a 256-bit product (all three u128; literals take that type).
|
|
88
|
+
|
|
89
|
+
CPI:
|
|
90
|
+
- Token-2022 extension instructions: `updateDefaultAccountState`, `updateTransferHook`, `enableRequiredMemoTransfers` /
|
|
91
|
+
`disableRequiredMemoTransfers`, `updateInterestRate`, `transferCheckedWithFee`, `harvestWithheldTokensToMint`,
|
|
92
|
+
`withdrawWithheldTokensFromMint`, `setTransferFee`; the Token Metadata interface: `updateMetadataField`,
|
|
93
|
+
`removeMetadataKey`, `updateMetadataAuthority`, `emitMetadata`; `closeAccount` of a mint; `mintTo` /
|
|
94
|
+
`initializeMetadata` on a mint created earlier in the instruction; `isTransferring(tokenAccount)` and
|
|
95
|
+
`transferHookAuthority(mint)`.
|
|
96
|
+
- Metaplex Core plugin values: MasterEdition, VerifiedCreators, Autograph, FreezeExecute, PermanentFreezeExecute, and
|
|
97
|
+
royalty rule sets (`ruleSet: { programAllowList | programDenyList }`).
|
|
98
|
+
- Bubblegum: `delegateV2`, `freezeV2`, `thawV2`, `setNonTransferableV2`, `updateMetadataV2`, `verifyCreatorV2`,
|
|
99
|
+
`unverifyCreatorV2`, `setCollectionV2`, and the V1 `transfer`, `burn`, `delegate`. Checked on the real programs with
|
|
100
|
+
every leaf recomputed: verify → delegate → freeze (a frozen leaf is refused) → thaw → rename, unverify, moving a leaf
|
|
101
|
+
into a collection, soulbinding a collection's leaf, and a V1 transfer signed by a PDA owner, delegate and burn.
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
- Bubblegum leaf fields that may be null at run time (`assetDataHash: Hash | null`, `flags`) are sent as Borsh Options;
|
|
105
|
+
a null aborted the instruction before.
|
|
106
|
+
|
|
8
107
|
## 0.17.0 — 2026-10-28
|
|
9
108
|
|
|
10
109
|
Anchor's account bytes on request (`@layout borsh`, so a TypeSOL program can take over an Anchor program's accounts
|
package/README.md
CHANGED
|
@@ -189,10 +189,10 @@ Runtime failures point back to your source: `Error 6000 Unauthorized (counter.ts
|
|
|
189
189
|
|
|
190
190
|
| | |
|
|
191
191
|
| --- | --- |
|
|
192
|
-
| **Accounts** | `Signer`, `Account<T>`, `Mut<T>`, `Init<T>`, `InitIfNeeded<T>` (PDAs and associated token accounts), `Zeroed<T>` (up to 10 MiB), optional accounts (`A \| null`), token accounts and mints, programs, sysvars |
|
|
192
|
+
| **Accounts** | `Signer`, `Account<T>`, `Mut<T>`, `Init<T>`, `InitIfNeeded<T>` (PDAs or keypair accounts, with Anchor's `space`; mints, token accounts and associated token accounts), `Zeroed<T>` (up to 10 MiB), optional accounts (`A \| null`), token accounts and mints, `SystemAccount`, programs, sysvars |
|
|
193
193
|
| **Constraints** | `HasOne`, `Seeds` (also from account fields and under another program; `pdaBump` like `ctx.bumps`), `CloseTo`, `Ata`, `Address`, `Owner`, `Executable`. They are checked before the body runs and recorded in the IDL |
|
|
194
|
-
| **Data** | `u8`…`u128`, `i8`…`i128`, `boolean`, `Pubkey`, `Hash`, nested structs, `FixedArray`, `FixedString`, `Option`, unit enums, data enums (discriminated unions), `Vec` that resizes its account, `FixedVec` lists anywhere, and Anchor's own byte layout (`@layout borsh`) to take over existing Anchor accounts |
|
|
195
|
-
| **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too, arrow functions, generics) across multiple files |
|
|
194
|
+
| **Data** | `u8`…`u128`, `i8`…`i128`, `boolean`, `Pubkey`, `Hash`, nested structs, `FixedArray`, `FixedString`, `Option`, unit enums, data enums (discriminated unions), `Vec` that resizes its account, `FixedVec` lists anywhere, and Anchor's own byte layout (`@layout borsh`) to take over existing Anchor accounts and to pass, return and emit structs with String / Vec / Option fields as Anchor does |
|
|
195
|
+
| **Control flow** | `if`, `for`, `for … of`, `while`, `switch` (with narrowing on data enums), ternaries, `??`, destructuring, helper functions (returning structs too, arrow functions, closures, function arguments, generics), list callbacks (`some`, `find`, `reduce`, …) across multiple files |
|
|
196
196
|
| **CPIs** | System, SPL Token, Token-2022 (including extension reads and metadata), ATA, Metaplex Token Metadata, Core, Bubblegum. Generic `invoke` with PDA signing, return data, and typed bindings generated from any Anchor IDL (`typesol cpi`) |
|
|
197
197
|
| **Events** | `emit` (log-based) and `emitCpi` (Anchor `emit_cpi!`, through a self-CPI) |
|
|
198
198
|
| **Crypto & oracles** | `sha256`, `keccak256`, Merkle proofs, Ed25519 and secp256k1 signature checks, Pyth pull oracle, Switchboard |
|
|
@@ -306,7 +306,7 @@ Details are in [docs/testing.md](docs/testing.md).
|
|
|
306
306
|
|
|
307
307
|
## Status
|
|
308
308
|
|
|
309
|
-
**TypeSOL is pre-1.0 (0.
|
|
309
|
+
**TypeSOL is pre-1.0 (0.19).** Releases follow the organization's
|
|
310
310
|
[versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
|
|
311
311
|
change.
|
|
312
312
|
- The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
|
package/dist/clientgen.js
CHANGED
|
@@ -39,6 +39,7 @@ export const TYPESOL_ERRORS = {
|
|
|
39
39
|
2015: "ConstraintTokenOwner",
|
|
40
40
|
2016: "ConstraintMintMintAuthority",
|
|
41
41
|
2017: "ConstraintMintFreezeAuthority",
|
|
42
|
+
2018: "ConstraintMintDecimals",
|
|
42
43
|
2040: "ConstraintDuplicateMutableAccount",
|
|
43
44
|
3000: "AccountDiscriminatorAlreadySet",
|
|
44
45
|
3002: "AccountDiscriminatorMismatch",
|
|
@@ -50,6 +51,7 @@ export const TYPESOL_ERRORS = {
|
|
|
50
51
|
3008: "InvalidProgramId",
|
|
51
52
|
3009: "InvalidProgramExecutable",
|
|
52
53
|
3010: "AccountNotSigner",
|
|
54
|
+
3011: "AccountNotSystemOwned",
|
|
53
55
|
3012: "AccountNotInitialized",
|
|
54
56
|
3016: "AccountReallocExceedsLimit",
|
|
55
57
|
4102: "InvalidNumericConversion",
|
|
@@ -12,7 +12,7 @@ export declare const MAX_CPI_ALLOCATION: number;
|
|
|
12
12
|
export interface Analysis {
|
|
13
13
|
model: ProgramModel;
|
|
14
14
|
/** A generic helper's instance for these type arguments (analyzed once, then kept in `model.helpers`). */
|
|
15
|
-
instantiate(g: GenericHelper, typeArgs:
|
|
15
|
+
instantiate(g: GenericHelper, typeArgs: TypeArg[], fnArgs?: string[]): Helper;
|
|
16
16
|
/** TypeScript's checker: used for narrowing (which variant a data-enum value is in a branch). */
|
|
17
17
|
checker: ts.TypeChecker;
|
|
18
18
|
sourceFile: ts.SourceFile;
|
|
@@ -22,11 +22,20 @@ export interface Analysis {
|
|
|
22
22
|
imports: Map<string, string>;
|
|
23
23
|
reporter: Reporter;
|
|
24
24
|
}
|
|
25
|
+
/** What a generic helper's type parameter stands for: a scalar, or a fixed-size struct. */
|
|
26
|
+
export type TypeArg = ScalarType | {
|
|
27
|
+
kind: "struct";
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
/** A struct named by a type reference, through the type parameters of a generic helper's instance. */
|
|
31
|
+
export declare function structNameOf(node: ts.TypeNode, scope: Pick<TypeScope, "structArgs">): string | undefined;
|
|
25
32
|
/** What type resolution needs: imports, unit enums, and where to record Option<T> uses. */
|
|
26
33
|
export interface TypeScope {
|
|
27
34
|
imports: Map<string, string>;
|
|
28
35
|
/** Inside a generic helper's instance: type parameter -> the type it stands for. */
|
|
29
36
|
typeArgs?: Map<string, ScalarType>;
|
|
37
|
+
/** Inside a generic helper's instance: type parameter -> the struct it stands for. */
|
|
38
|
+
structArgs?: Map<string, string>;
|
|
30
39
|
enums: Map<string, EnumDef>;
|
|
31
40
|
dataEnums?: Map<string, DataEnumDef>;
|
|
32
41
|
options: Map<string, ScalarType | {
|
package/dist/compiler/analyze.js
CHANGED
|
@@ -77,6 +77,8 @@ const TYPE_EXPORTS = new Set([
|
|
|
77
77
|
"TokenMetadataProgram",
|
|
78
78
|
"SwitchboardFeed",
|
|
79
79
|
"HasOne",
|
|
80
|
+
"TokenOf",
|
|
81
|
+
"MintOf",
|
|
80
82
|
"Seeds",
|
|
81
83
|
"CloseTo",
|
|
82
84
|
"Address",
|
|
@@ -90,6 +92,7 @@ const TYPE_EXPORTS = new Set([
|
|
|
90
92
|
"SelfProgram",
|
|
91
93
|
"ProgramData",
|
|
92
94
|
"SignerSeeds",
|
|
95
|
+
"SystemAccount",
|
|
93
96
|
]);
|
|
94
97
|
/** Largest fixed array a struct may declare, and largest FixedString. */
|
|
95
98
|
const MAX_ARRAY_LEN = 1_000_000;
|
|
@@ -99,6 +102,12 @@ export const MAX_ACCOUNT_SIZE = 10 * 1024 * 1024;
|
|
|
99
102
|
/** sol_set_return_data's limit. */
|
|
100
103
|
export const MAX_RETURN_DATA = 1024;
|
|
101
104
|
export const MAX_CPI_ALLOCATION = 10 * 1024;
|
|
105
|
+
/** A struct named by a type reference, through the type parameters of a generic helper's instance. */
|
|
106
|
+
export function structNameOf(node, scope) {
|
|
107
|
+
if (!ts.isTypeReferenceNode(node) || !ts.isIdentifier(node.typeName) || node.typeArguments?.length)
|
|
108
|
+
return undefined;
|
|
109
|
+
return scope.structArgs?.get(node.typeName.text) ?? node.typeName.text;
|
|
110
|
+
}
|
|
102
111
|
export function snakeCase(name) {
|
|
103
112
|
return name
|
|
104
113
|
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
@@ -424,23 +433,35 @@ export function analyze(entry) {
|
|
|
424
433
|
if (h.node.body)
|
|
425
434
|
collectLocals(h.node.body, h.name, h.imports, h.node);
|
|
426
435
|
const localDecls = new Set(locals.map((l) => l.decl));
|
|
427
|
-
|
|
428
|
-
|
|
436
|
+
// A local function that reads the enclosing function's variables (or calls one that does) is a closure: it is
|
|
437
|
+
// expanded where it is called, in the enclosing function. The others become helpers of their own.
|
|
438
|
+
const inline = new Set(locals.filter((l) => captures(l, localDecls, checker)).map((l) => l.decl));
|
|
439
|
+
for (let changed = true; changed;) {
|
|
440
|
+
changed = false;
|
|
441
|
+
for (const l of locals)
|
|
442
|
+
if (!inline.has(l.decl) && references(l, inline, checker)) {
|
|
443
|
+
inline.add(l.decl);
|
|
444
|
+
changed = true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const closureDecls = new Map(locals.filter((l) => inline.has(l.decl)).map((l) => [l.decl, l.display]));
|
|
429
448
|
const helpers = new Map();
|
|
430
449
|
const generics = new Map();
|
|
431
450
|
const helperDecls = new Map();
|
|
432
|
-
for (const fn of [...helperNodes, ...locals]) {
|
|
451
|
+
for (const fn of [...helperNodes, ...locals.filter((l) => !inline.has(l.decl))]) {
|
|
433
452
|
helperDecls.set(fn.decl, fn.name);
|
|
434
|
-
|
|
453
|
+
const fnParams = fn.node.parameters.map((p, i) => (p.type && ts.isFunctionTypeNode(p.type) ? i : -1)).filter((i) => i >= 0);
|
|
454
|
+
if (fn.node.typeParameters?.length || fnParams.length) {
|
|
435
455
|
reporter.guard(() => {
|
|
436
|
-
for (const tp of fn.node.typeParameters)
|
|
456
|
+
for (const tp of fn.node.typeParameters ?? [])
|
|
437
457
|
if (tp.default)
|
|
438
458
|
reporter.fail(tp, "type parameters of helpers take no default");
|
|
439
459
|
generics.set(fn.name, {
|
|
440
460
|
name: fn.name,
|
|
441
461
|
display: fn.display,
|
|
442
462
|
node: fn.node,
|
|
443
|
-
typeParams: fn.node.typeParameters.map((t) => t.name.text),
|
|
463
|
+
typeParams: (fn.node.typeParameters ?? []).map((t) => t.name.text),
|
|
464
|
+
fnParams,
|
|
444
465
|
imports: fn.imports,
|
|
445
466
|
});
|
|
446
467
|
});
|
|
@@ -451,20 +472,34 @@ export function analyze(entry) {
|
|
|
451
472
|
helpers.set(h.name, h);
|
|
452
473
|
}
|
|
453
474
|
const instances = new Map();
|
|
454
|
-
const instantiate = (g, typeArgs) => {
|
|
455
|
-
const
|
|
475
|
+
const instantiate = (g, typeArgs, fnArgs = []) => {
|
|
476
|
+
const argKey = (t) => (t.kind === "struct" ? `S${t.name}` : scalarKey(t));
|
|
477
|
+
const key = [g.name, ...typeArgs.map(argKey), ...fnArgs.map((f) => `fn_${f}`)].join("__");
|
|
456
478
|
const done = instances.get(key);
|
|
457
479
|
if (done)
|
|
458
480
|
return done;
|
|
459
|
-
const bound = new Map(
|
|
460
|
-
const
|
|
461
|
-
|
|
481
|
+
const bound = new Map();
|
|
482
|
+
const boundStructs = new Map();
|
|
483
|
+
g.typeParams.forEach((n, i) => {
|
|
484
|
+
const t = typeArgs[i];
|
|
485
|
+
if (t.kind === "struct")
|
|
486
|
+
boundStructs.set(n, t.name);
|
|
487
|
+
else
|
|
488
|
+
bound.set(n, t);
|
|
489
|
+
});
|
|
490
|
+
const display = `${g.display}<${[...typeArgs.map((t) => (t.kind === "struct" ? t.name : typeName(t))), ...fnArgs].join(", ")}>`;
|
|
491
|
+
const h = analyzeHelper(g.node, key, display, reporter, { ...scopeFor(g.imports), typeArgs: bound, structArgs: boundStructs }, structs, new Set(g.fnParams));
|
|
462
492
|
h.typeArgs = bound;
|
|
493
|
+
h.structArgs = boundStructs;
|
|
494
|
+
if (g.fnParams.length) {
|
|
495
|
+
h.fnArgs = new Map(g.fnParams.map((i, k) => [g.node.parameters[i].name.text, fnArgs[k]]));
|
|
496
|
+
h.fnArgIndexes = g.fnParams;
|
|
497
|
+
}
|
|
463
498
|
instances.set(key, h);
|
|
464
499
|
helpers.set(key, h);
|
|
465
500
|
return h;
|
|
466
501
|
};
|
|
467
|
-
reporter.guard(() => checkRecursion([...helperNodes, ...locals], helperDecls, checker, reporter));
|
|
502
|
+
reporter.guard(() => checkRecursion([...helperNodes, ...locals.filter((l) => !inline.has(l.decl))], helperDecls, checker, reporter));
|
|
468
503
|
const accountStructs = new Set();
|
|
469
504
|
const argStructs = new Set();
|
|
470
505
|
const instructions = [];
|
|
@@ -478,26 +513,29 @@ export function analyze(entry) {
|
|
|
478
513
|
for (const a of ix.args) {
|
|
479
514
|
const seen = new Set();
|
|
480
515
|
collectStructs(a.type, structs, dataEnums, seen);
|
|
516
|
+
// A @layout borsh struct is decoded as a whole argument; inside lists or enums it would need a fixed size.
|
|
517
|
+
const whole = a.type.kind === "structRef" ? a.type.struct : undefined;
|
|
481
518
|
for (const s of seen)
|
|
482
|
-
if (structs.get(s).layout === "borsh")
|
|
483
|
-
reporter.error(fn.node.name ?? fn.node, `argument ${a.name}: ${s} is @layout borsh,
|
|
519
|
+
if (structs.get(s).layout === "borsh" && s !== whole)
|
|
520
|
+
reporter.error(fn.node.name ?? fn.node, `argument ${a.name}: ${s} is @layout borsh; pass it as a whole argument, not inside a list or enum`);
|
|
484
521
|
}
|
|
485
|
-
|
|
486
|
-
reporter.error(fn.node.type ?? fn.node, `${ix.returns.name} is @layout borsh, an account layout only; return another struct`);
|
|
487
|
-
// Arguments are Borsh: a FixedVec's padded layout is account data only.
|
|
522
|
+
// Arguments are Borsh: a FixedVec's padded layout is account data only (a @layout borsh struct's FixedVec is a Borsh Vec).
|
|
488
523
|
for (const a of ix.args) {
|
|
489
524
|
const seen = new Set();
|
|
490
525
|
collectStructs(a.type, structs, dataEnums, seen);
|
|
491
526
|
for (const s of seen)
|
|
492
|
-
if (structs.get(s).fields.some((f) => f.type.kind === "vec" && f.type.cap !== undefined))
|
|
527
|
+
if (structs.get(s).layout !== "borsh" && structs.get(s).fields.some((f) => f.type.kind === "vec" && f.type.cap !== undefined))
|
|
493
528
|
reporter.error(fn.node.name ?? fn.node, `argument ${a.name}: ${s} holds a FixedVec, which only account data can; pass a Vec<T> argument instead`);
|
|
494
529
|
}
|
|
495
530
|
// A returned struct goes out in its fixed layout, like an argument comes in: it cannot also be a Borsh event.
|
|
496
531
|
if (ix.returns?.kind === "struct")
|
|
497
532
|
collectStructs(ix.returns, structs, dataEnums, argStructs);
|
|
498
533
|
for (const a of ix.accounts)
|
|
499
|
-
if (a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "zeroed" || a.kind.kind === "initIfNeeded")
|
|
534
|
+
if (a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "zeroed" || a.kind.kind === "initIfNeeded") {
|
|
500
535
|
accountStructs.add(a.kind.struct.name);
|
|
536
|
+
if (a.kind.struct.viewsOnly)
|
|
537
|
+
reporter.error(a.node, `${a.kind.struct.name} holds a string (or an enum carrying one), so it is an argument, return value or event; account data uses FixedString<N>`);
|
|
538
|
+
}
|
|
501
539
|
}
|
|
502
540
|
checkSnakeDuplicates(instructions.map((ix) => ({ name: ix.node.name.text, node: ix.node.name })), "instructions", reporter);
|
|
503
541
|
// emitCpi's event instruction (Anchor's EVENT_IX_TAG) is dispatched before every other instruction.
|
|
@@ -530,6 +568,7 @@ export function analyze(entry) {
|
|
|
530
568
|
helpers,
|
|
531
569
|
generics,
|
|
532
570
|
helperDecls,
|
|
571
|
+
closureDecls,
|
|
533
572
|
errors,
|
|
534
573
|
errorEnums,
|
|
535
574
|
enums,
|
|
@@ -585,7 +624,7 @@ export function resolveArgAggregate(node, scope, structs) {
|
|
|
585
624
|
if (scalar)
|
|
586
625
|
return scalar;
|
|
587
626
|
if (ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName) && !t.typeArguments?.length) {
|
|
588
|
-
const s = structs.get(t
|
|
627
|
+
const s = structs.get(structNameOf(t, scope));
|
|
589
628
|
if (s && s.size !== undefined && !s.vec)
|
|
590
629
|
return { kind: "struct", name: s.name };
|
|
591
630
|
}
|
|
@@ -668,6 +707,9 @@ export function resolveValueType(node, scope, allowVec = false) {
|
|
|
668
707
|
return { kind: "dataEnumArg", name: node.typeName.text };
|
|
669
708
|
if (allowVec && typesolName(node, scope.imports) === "Vec") {
|
|
670
709
|
const elemNode = node.typeArguments?.[0];
|
|
710
|
+
// Vec<string>: Borsh Vec<String>, read as views of the instruction data.
|
|
711
|
+
if (elemNode?.kind === ts.SyntaxKind.StringKeyword)
|
|
712
|
+
return { kind: "vecArg", elem: { kind: "string" } };
|
|
671
713
|
const elem = elemNode && resolveScalarType(elemNode, scope);
|
|
672
714
|
if (elem)
|
|
673
715
|
return { kind: "vecArg", elem };
|
|
@@ -935,7 +977,11 @@ function analyzeStructs(items, aliases, dataEnums, reporter, scopeFor, fstrings,
|
|
|
935
977
|
state.set(s.name, "visiting");
|
|
936
978
|
let total = 0;
|
|
937
979
|
for (const f of s.fields) {
|
|
938
|
-
|
|
980
|
+
// In a @layout borsh struct a string (or a data enum carrying one) is held as a TS_Str view: a C size, no layout.
|
|
981
|
+
const view = s.layout === "borsh" && (f.type.kind === "string" || (f.type.kind === "dataEnum" && sizeOf(f.type, s.node) === undefined));
|
|
982
|
+
if (view)
|
|
983
|
+
s.viewsOnly = true;
|
|
984
|
+
const fsz = view ? (f.type.kind === "string" ? 16 : 1 + dataEnums.get(f.type.name).payload) : sizeOf(f.type, s.node);
|
|
939
985
|
total = total === undefined || fsz === undefined ? undefined : total + fsz;
|
|
940
986
|
}
|
|
941
987
|
s.size = total;
|
|
@@ -954,19 +1000,19 @@ function analyzeStructs(items, aliases, dataEnums, reporter, scopeFor, fstrings,
|
|
|
954
1000
|
reporter.guard(() => visit(s));
|
|
955
1001
|
for (const s of structs.values())
|
|
956
1002
|
if (s.layout === "borsh")
|
|
957
|
-
reporter.guard(() => checkBorshLayout(s, structs, reporter));
|
|
1003
|
+
reporter.guard(() => checkBorshLayout(s, structs, dataEnums, reporter));
|
|
958
1004
|
for (const e of dataEnums.values())
|
|
959
1005
|
reporter.guard(() => sizeOf({ kind: "dataEnum", name: e.name }, e.node));
|
|
960
1006
|
return structs;
|
|
961
1007
|
}
|
|
962
1008
|
const isFunctionNode = (n) => ts.isFunctionDeclaration(n) || ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isMethodDeclaration(n);
|
|
963
|
-
/**
|
|
964
|
-
|
|
965
|
-
* other local functions: reading a variable of the enclosing function (a closure) is refused.
|
|
966
|
-
*/
|
|
967
|
-
function checkCaptures(l, localDecls, checker, reporter) {
|
|
1009
|
+
/** Whether a local function reads a variable or parameter of a function around it (a closure). */
|
|
1010
|
+
function captures(l, localDecls, checker) {
|
|
968
1011
|
const fn = l.node;
|
|
1012
|
+
let found = false;
|
|
969
1013
|
const visit = (n) => {
|
|
1014
|
+
if (found)
|
|
1015
|
+
return;
|
|
970
1016
|
if (ts.isIdentifier(n)) {
|
|
971
1017
|
const sym = ts.isShorthandPropertyAssignment(n.parent) ? checker.getShorthandAssignmentValueSymbol(n.parent) : checker.getSymbolAtLocation(n);
|
|
972
1018
|
for (const d of sym?.declarations ?? []) {
|
|
@@ -974,30 +1020,53 @@ function checkCaptures(l, localDecls, checker, reporter) {
|
|
|
974
1020
|
continue;
|
|
975
1021
|
if (d.getSourceFile() !== fn.getSourceFile() || (d.pos >= fn.pos && d.end <= fn.end))
|
|
976
1022
|
continue;
|
|
977
|
-
// Declared in some function around this one: a captured variable.
|
|
978
1023
|
for (let p = d.parent; p; p = p.parent)
|
|
979
|
-
if (isFunctionNode(p))
|
|
980
|
-
|
|
981
|
-
}
|
|
1024
|
+
if (isFunctionNode(p))
|
|
1025
|
+
found = true;
|
|
982
1026
|
}
|
|
983
1027
|
}
|
|
984
1028
|
ts.forEachChild(n, visit);
|
|
985
1029
|
};
|
|
986
1030
|
if (fn.body)
|
|
987
1031
|
visit(fn.body);
|
|
1032
|
+
return found;
|
|
1033
|
+
}
|
|
1034
|
+
/** Whether a function's body names one of `decls` (a closure it calls). */
|
|
1035
|
+
function references(l, decls, checker) {
|
|
1036
|
+
let found = false;
|
|
1037
|
+
const visit = (n) => {
|
|
1038
|
+
if (found)
|
|
1039
|
+
return;
|
|
1040
|
+
if (ts.isIdentifier(n))
|
|
1041
|
+
for (const d of checker.getSymbolAtLocation(n)?.declarations ?? [])
|
|
1042
|
+
if (decls.has(d))
|
|
1043
|
+
found = true;
|
|
1044
|
+
ts.forEachChild(n, visit);
|
|
1045
|
+
};
|
|
1046
|
+
if (l.node.body)
|
|
1047
|
+
visit(l.node.body);
|
|
1048
|
+
return found;
|
|
988
1049
|
}
|
|
989
1050
|
/**
|
|
990
1051
|
* A `@layout borsh` struct: its own fields may be FixedString (Borsh String), FixedVec (Borsh Vec) and Option; anything
|
|
991
1052
|
* nested must already be Borsh-identical (fixed-size scalars, Pubkeys, enums, arrays and structs of them).
|
|
992
1053
|
*/
|
|
993
|
-
function checkBorshLayout(s, structs, reporter) {
|
|
1054
|
+
function checkBorshLayout(s, structs, dataEnums, reporter) {
|
|
994
1055
|
const model = { structs };
|
|
995
1056
|
const fixed = (t) => borshIdentical(t, model);
|
|
996
1057
|
for (const f of s.fields) {
|
|
997
1058
|
const t = f.type;
|
|
998
1059
|
const at = s.node.members.find((m) => m.name && ts.isIdentifier(m.name) && m.name.text === f.name) ?? s.node;
|
|
999
|
-
if (fixed(t) || t.kind === "fstring")
|
|
1060
|
+
if (fixed(t) || t.kind === "fstring" || t.kind === "string")
|
|
1000
1061
|
continue;
|
|
1062
|
+
if (t.kind === "dataEnum") {
|
|
1063
|
+
// Borsh enum: tag, then the variant's fields; each a fixed-size value or a string.
|
|
1064
|
+
for (const v of dataEnums.get(t.name)?.variants ?? [])
|
|
1065
|
+
for (const vf of v.fields)
|
|
1066
|
+
if (!fixed(vf.type) && vf.type.kind !== "string")
|
|
1067
|
+
reporter.fail(at, `${t.name}.${v.name}.${vf.name} in a @layout borsh struct: enum variants hold fixed-size values and strings`);
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1001
1070
|
if (t.kind === "vec" && t.cap === undefined)
|
|
1002
1071
|
reporter.fail(at, "a @layout borsh struct cannot end in a growable Vec<T>; use FixedVec<T, N> (Anchor's #[max_len(N)] Vec)");
|
|
1003
1072
|
if (t.kind === "vec" && (fixed(t.elem) || t.elem.kind === "fstring"))
|
|
@@ -1015,7 +1084,7 @@ function checkBorshLayout(s, structs, reporter) {
|
|
|
1015
1084
|
reporter.error(s.node.name, `${s.name} is @layout borsh, so it is account data only; ${other.name}.${f.name} cannot hold one`);
|
|
1016
1085
|
}
|
|
1017
1086
|
}
|
|
1018
|
-
function analyzeHelper(fn, name, display, reporter, scope, structs) {
|
|
1087
|
+
function analyzeHelper(fn, name, display, reporter, scope, structs, skip = new Set()) {
|
|
1019
1088
|
if (!fn.body)
|
|
1020
1089
|
reporter.fail(fn, "helper functions need a body");
|
|
1021
1090
|
if (fn.asteriskToken)
|
|
@@ -1024,16 +1093,23 @@ function analyzeHelper(fn, name, display, reporter, scope, structs) {
|
|
|
1024
1093
|
reporter.fail(fn, "helper functions cannot be async");
|
|
1025
1094
|
if (!fn.type)
|
|
1026
1095
|
reporter.fail(fn.name ?? fn, "helper functions need an explicit return type (void, an integer, boolean, Pubkey or a struct)");
|
|
1027
|
-
let returns = fn.type.kind === ts.SyntaxKind.VoidKeyword
|
|
1096
|
+
let returns = fn.type.kind === ts.SyntaxKind.VoidKeyword
|
|
1097
|
+
? undefined
|
|
1098
|
+
: fn.type.kind === ts.SyntaxKind.StringKeyword
|
|
1099
|
+
? { kind: "string" }
|
|
1100
|
+
: resolveScalarType(fn.type, scope);
|
|
1028
1101
|
if (fn.type.kind !== ts.SyntaxKind.VoidKeyword && !returns) {
|
|
1029
|
-
const
|
|
1102
|
+
const sn = structNameOf(fn.type, scope);
|
|
1103
|
+
const s = sn ? structs.get(sn) : undefined;
|
|
1030
1104
|
if (s && (s.size === undefined || s.vec))
|
|
1031
1105
|
reporter.fail(fn.type, `${s.name} has variable-size fields and cannot be returned; return a struct of fixed size`);
|
|
1032
1106
|
if (!s)
|
|
1033
|
-
reporter.fail(fn.type, "helpers return void, an integer, boolean, Pubkey, a struct or a type parameter");
|
|
1107
|
+
reporter.fail(fn.type, "helpers return void, an integer, boolean, Pubkey, string, a struct or a type parameter");
|
|
1034
1108
|
returns = { kind: "struct", name: s.name };
|
|
1035
1109
|
}
|
|
1036
|
-
const params = fn.parameters
|
|
1110
|
+
const params = fn.parameters
|
|
1111
|
+
.filter((_, i) => !skip.has(i))
|
|
1112
|
+
.map((p) => {
|
|
1037
1113
|
if (!ts.isIdentifier(p.name) || !p.type || p.initializer || p.questionToken || p.dotDotDotToken)
|
|
1038
1114
|
reporter.fail(p, "parameters need a plain name and an explicit type");
|
|
1039
1115
|
// `p_<name>__n` carries a SignerSeeds count: "__" in a name could collide with it.
|
|
@@ -1046,7 +1122,13 @@ function analyzeHelper(fn, name, display, reporter, scope, structs) {
|
|
|
1046
1122
|
return { name: p.name.text, type: { kind: "signerSeeds" }, node: p };
|
|
1047
1123
|
// Accounts the caller already checked: Signer, AccountInfo, Account<T>/Mut<T>, token accounts, programs.
|
|
1048
1124
|
const wrapped = typesolName(p.type, scope.imports);
|
|
1049
|
-
if (wrapped === "HasOne" ||
|
|
1125
|
+
if (wrapped === "HasOne" ||
|
|
1126
|
+
wrapped === "TokenOf" ||
|
|
1127
|
+
wrapped === "MintOf" ||
|
|
1128
|
+
wrapped === "Seeds" ||
|
|
1129
|
+
wrapped === "CloseTo" ||
|
|
1130
|
+
wrapped === "Address" ||
|
|
1131
|
+
wrapped === "Ata")
|
|
1050
1132
|
reporter.fail(p.type, "constraints belong on instruction parameters; helpers take the plain account type");
|
|
1051
1133
|
const account = resolveAccountKind(p.type, reporter, scope.imports, structs);
|
|
1052
1134
|
if (account.kind === "init" || account.kind === "initIfNeeded" || account.kind === "zeroed")
|
|
@@ -1120,7 +1202,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1120
1202
|
reporter.fail(fn, "instructions cannot be async or generators");
|
|
1121
1203
|
let returns;
|
|
1122
1204
|
if (fn.type && fn.type.kind !== ts.SyntaxKind.VoidKeyword) {
|
|
1123
|
-
returns = resolveScalarType(fn.type, scope);
|
|
1205
|
+
returns = fn.type.kind === ts.SyntaxKind.StringKeyword ? { kind: "string" } : resolveScalarType(fn.type, scope);
|
|
1124
1206
|
const s = !returns && ts.isTypeReferenceNode(fn.type) && ts.isIdentifier(fn.type.typeName) && !fn.type.typeArguments
|
|
1125
1207
|
? structs.get(fn.type.typeName.text)
|
|
1126
1208
|
: undefined;
|
|
@@ -1133,7 +1215,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1133
1215
|
returns = { kind: "struct", name: s.name };
|
|
1134
1216
|
}
|
|
1135
1217
|
if (!returns)
|
|
1136
|
-
reporter.fail(fn.type, "instructions return void, one integer, boolean or
|
|
1218
|
+
reporter.fail(fn.type, "instructions return void, one integer, boolean, Pubkey or string, or a fixed-size struct (sent as return data)");
|
|
1137
1219
|
}
|
|
1138
1220
|
const accounts = [];
|
|
1139
1221
|
const args = [];
|
|
@@ -1173,7 +1255,8 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1173
1255
|
const inner = unwrapConstraints(typeNode, reporter, imports, constraints);
|
|
1174
1256
|
const kind = resolveAccountKind(inner, reporter, imports, structs);
|
|
1175
1257
|
if (optional &&
|
|
1176
|
-
(["init", "initIfNeeded", "zeroed", "pyth", "eventAuthority", "selfProgram", "programData"].includes(kind.kind) ||
|
|
1258
|
+
(["init", "initIfNeeded", "zeroed", "pyth", "eventAuthority", "selfProgram", "programData"].includes(kind.kind) ||
|
|
1259
|
+
((kind.kind === "token" || kind.kind === "mint") && kind.init)))
|
|
1177
1260
|
reporter.fail(p, "Init, InitIfNeeded, Zeroed, PythPriceUpdate, EventAuthority, SelfProgram and ProgramData accounts cannot be optional");
|
|
1178
1261
|
accounts.push({ name: p.name.text, index: accounts.length, kind, node: p, constraints, ...(optional ? { optional } : {}) });
|
|
1179
1262
|
});
|
|
@@ -1186,15 +1269,26 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1186
1269
|
// Constraints refer to other parameters by name: check them now that all are known.
|
|
1187
1270
|
for (const a of accounts) {
|
|
1188
1271
|
const c = a.constraints;
|
|
1189
|
-
if (a.kind.kind === "token" && a.kind.init &&
|
|
1190
|
-
reporter.fail(a.node, 'Init<TokenAccount> / InitIfNeeded<TokenAccount> create associated token accounts: wrap them in Ata<…, "owner", "mint">');
|
|
1191
|
-
if (a.kind.kind === "token" && a.kind.init && (c.hasOne.length || c.seeds || c.address || c.owner || c.executable))
|
|
1272
|
+
if (a.kind.kind === "token" && a.kind.init && c.ata && (c.hasOne.length || c.seeds || c.address || c.owner || c.executable))
|
|
1192
1273
|
reporter.fail(a.node, "an associated token account created here is checked against Ata<…>'s owner and mint; drop HasOne/Seeds/Address/Owner/Executable");
|
|
1274
|
+
// Token accounts and mints created in the body are checked there (seeds, mint, authorities).
|
|
1275
|
+
if ((a.kind.kind === "token" || a.kind.kind === "mint") && a.kind.init && !c.ata && (c.hasOne.length || c.seeds || c.address || c.owner || c.executable))
|
|
1276
|
+
reporter.fail(a.node, `${a.name}.create(...) takes the seeds, mint and authorities; drop HasOne/Seeds/Address/Owner/Executable`);
|
|
1193
1277
|
if (!c.hasOne.length && !c.seeds && !c.closeTo && !c.ata)
|
|
1194
1278
|
continue;
|
|
1195
1279
|
const struct = a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "initIfNeeded" || a.kind.kind === "zeroed" ? a.kind.struct : undefined;
|
|
1196
1280
|
reporter.guard(() => {
|
|
1197
|
-
for (const
|
|
1281
|
+
for (const entry of c.hasOne) {
|
|
1282
|
+
const [f, param = f] = entry.split("=");
|
|
1283
|
+
if (entry.includes("=")) {
|
|
1284
|
+
if (!accounts.some((o) => o.name === param))
|
|
1285
|
+
reporter.fail(a.node, `add an account parameter named "${param}"`);
|
|
1286
|
+
if (a.kind.kind === "token" ? f !== "mint" && f !== "owner" : a.kind.kind === "mint" ? false : true)
|
|
1287
|
+
reporter.fail(a.node, a.kind.kind === "token" || a.kind.kind === "mint" ? "TokenOf<…> applies to a TokenAccount" : "TokenOf / MintOf apply to a TokenAccount / Mint");
|
|
1288
|
+
if (a.kind.kind === "mint" && f !== "mintAuthority" && f !== "freezeAuthority")
|
|
1289
|
+
reporter.fail(a.node, "MintOf<…> applies to a Mint");
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1198
1292
|
if (!accounts.some((o) => o.name === f))
|
|
1199
1293
|
reporter.fail(a.node, `HasOne<…, "${f}">: add an account parameter named "${f}"`);
|
|
1200
1294
|
if (a.kind.kind === "token") {
|
|
@@ -1245,7 +1339,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1245
1339
|
for (const a of accounts) {
|
|
1246
1340
|
const c = a.constraints;
|
|
1247
1341
|
const refs = [
|
|
1248
|
-
...c.hasOne,
|
|
1342
|
+
...c.hasOne.map((e) => e.split("=").at(-1)),
|
|
1249
1343
|
...(c.seeds?.names ?? []).filter((n) => n.startsWith("@")).map((n) => n.slice(1).split(".")[0]),
|
|
1250
1344
|
...(c.seeds?.program?.startsWith("@") ? [c.seeds.program.slice(1)] : []),
|
|
1251
1345
|
...(c.closeTo ? [c.closeTo] : []),
|
|
@@ -1305,7 +1399,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1305
1399
|
/** Peel HasOne / Seeds / CloseTo wrappers off a parameter type, recording what they ask for. */
|
|
1306
1400
|
function unwrapConstraints(node, reporter, imports, out) {
|
|
1307
1401
|
const name = typesolName(node, imports);
|
|
1308
|
-
if (!["HasOne", "Seeds", "CloseTo", "Address", "Ata", "Owner", "Executable"].includes(name ?? ""))
|
|
1402
|
+
if (!["HasOne", "TokenOf", "MintOf", "Seeds", "CloseTo", "Address", "Ata", "Owner", "Executable"].includes(name ?? ""))
|
|
1309
1403
|
return node;
|
|
1310
1404
|
const args = node.typeArguments ?? [];
|
|
1311
1405
|
const lit = (t) => {
|
|
@@ -1319,6 +1413,20 @@ function unwrapConstraints(node, reporter, imports, out) {
|
|
|
1319
1413
|
};
|
|
1320
1414
|
if (name === "HasOne")
|
|
1321
1415
|
out.hasOne.push(...lit(args[1]));
|
|
1416
|
+
// Anchor's `token::mint = m, token::authority = a` / `mint::authority = a, mint::freeze_authority = f` naming the
|
|
1417
|
+
// parameters: recorded as "field=param" (`never` leaves one out).
|
|
1418
|
+
if (name === "TokenOf" || name === "MintOf") {
|
|
1419
|
+
const [first, second] = name === "TokenOf" ? ["mint", "owner"] : ["mintAuthority", "freezeAuthority"];
|
|
1420
|
+
const one = (t) => (t && t.kind !== ts.SyntaxKind.NeverKeyword ? lit(t)[0] : undefined);
|
|
1421
|
+
const p1 = one(args[1]);
|
|
1422
|
+
const p2 = one(args[2]);
|
|
1423
|
+
if (!p1 && !p2)
|
|
1424
|
+
reporter.fail(node, `${name}<…> names at least one parameter`);
|
|
1425
|
+
if (p1)
|
|
1426
|
+
out.hasOne.push(`${first}=${p1}`);
|
|
1427
|
+
if (p2)
|
|
1428
|
+
out.hasOne.push(`${second}=${p2}`);
|
|
1429
|
+
}
|
|
1322
1430
|
if (name === "CloseTo")
|
|
1323
1431
|
out.closeTo = lit(args[1])[0];
|
|
1324
1432
|
if (name === "Owner") {
|
|
@@ -1381,6 +1489,8 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1381
1489
|
// Any account (helpers that accept every kind, e.g. generated CPI helpers).
|
|
1382
1490
|
case "AccountMeta":
|
|
1383
1491
|
return { kind: "info", mut: false };
|
|
1492
|
+
case "SystemAccount":
|
|
1493
|
+
return { kind: "info", mut: false, systemOwned: true };
|
|
1384
1494
|
case "TokenAccount":
|
|
1385
1495
|
return { kind: "token", mut: false };
|
|
1386
1496
|
case "Mint":
|
|
@@ -1438,9 +1548,11 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1438
1548
|
case "Init": {
|
|
1439
1549
|
if (typesolName(inner(), imports) === "TokenAccount")
|
|
1440
1550
|
return { kind: "token", mut: true, init: "create" };
|
|
1551
|
+
if (typesolName(inner(), imports) === "Mint")
|
|
1552
|
+
return { kind: "mint", mut: true, init: "create" };
|
|
1441
1553
|
const s = structOf(inner());
|
|
1442
1554
|
if (!s)
|
|
1443
|
-
reporter.fail(node, "Init<T> needs a struct declared in this program");
|
|
1555
|
+
reporter.fail(node, "Init<T> needs a struct declared in this program, TokenAccount or Mint");
|
|
1444
1556
|
if (s.size + 8 > MAX_CPI_ALLOCATION)
|
|
1445
1557
|
reporter.fail(node, `${s.name} needs ${s.size + 8} bytes but a program can create at most ${MAX_CPI_ALLOCATION} through Init<T>; have the client allocate it and take it as Zeroed<${s.name}>`);
|
|
1446
1558
|
return { kind: "init", struct: s };
|
|
@@ -1448,6 +1560,8 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1448
1560
|
case "InitIfNeeded": {
|
|
1449
1561
|
if (typesolName(inner(), imports) === "TokenAccount")
|
|
1450
1562
|
return { kind: "token", mut: true, init: "ifNeeded" };
|
|
1563
|
+
if (typesolName(inner(), imports) === "Mint")
|
|
1564
|
+
return { kind: "mint", mut: true, init: "ifNeeded" };
|
|
1451
1565
|
const s = structOf(inner());
|
|
1452
1566
|
if (!s)
|
|
1453
1567
|
reporter.fail(node, "InitIfNeeded<T> needs a struct declared in this program");
|
|
@@ -1475,6 +1589,8 @@ function resolveAccountKind(node, reporter, imports, structs) {
|
|
|
1475
1589
|
return { kind: "signer", mut: true };
|
|
1476
1590
|
if (innerName === "AccountInfo" || innerName === "AccountMeta")
|
|
1477
1591
|
return { kind: "info", mut: true };
|
|
1592
|
+
if (innerName === "SystemAccount")
|
|
1593
|
+
return { kind: "info", mut: true, systemOwned: true };
|
|
1478
1594
|
if (innerName === "TokenAccount")
|
|
1479
1595
|
return { kind: "token", mut: true };
|
|
1480
1596
|
if (innerName === "Mint")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ANCHOR_ERRORS: Record<string, number>;
|