@typesol/cli 0.18.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 +24 -0
- package/README.md +1 -1
- package/dist/compiler/analyze.js +36 -4
- package/dist/compiler/anchor-errors.d.ts +1 -0
- package/dist/compiler/anchor-errors.js +80 -0
- package/dist/compiler/emit/calls.js +8 -0
- package/dist/compiler/emit/cpi.d.ts +2 -1
- package/dist/compiler/emit/cpi.js +112 -6
- package/dist/compiler/emit/expressions.d.ts +19 -0
- package/dist/compiler/emit/expressions.js +71 -16
- package/dist/compiler/emit/function.js +6 -4
- package/dist/compiler/emit/statements.js +17 -0
- package/docs/language.md +5 -6
- package/docs/migrate-from-anchor.md +3 -1
- package/lib/typesol.d.ts +132 -1
- package/package.json +1 -1
- package/runtime/typesol_rt.h +98 -22
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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
|
+
|
|
8
32
|
## 0.18.0 — 2026-10-28
|
|
9
33
|
|
|
10
34
|
The remaining Anchor differences, closed: Anchor's argument structs (Strings, Vecs, Options and enums in Borsh),
|
package/README.md
CHANGED
|
@@ -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/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",
|
|
@@ -1120,7 +1122,13 @@ function analyzeHelper(fn, name, display, reporter, scope, structs, skip = new S
|
|
|
1120
1122
|
return { name: p.name.text, type: { kind: "signerSeeds" }, node: p };
|
|
1121
1123
|
// Accounts the caller already checked: Signer, AccountInfo, Account<T>/Mut<T>, token accounts, programs.
|
|
1122
1124
|
const wrapped = typesolName(p.type, scope.imports);
|
|
1123
|
-
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")
|
|
1124
1132
|
reporter.fail(p.type, "constraints belong on instruction parameters; helpers take the plain account type");
|
|
1125
1133
|
const account = resolveAccountKind(p.type, reporter, scope.imports, structs);
|
|
1126
1134
|
if (account.kind === "init" || account.kind === "initIfNeeded" || account.kind === "zeroed")
|
|
@@ -1270,7 +1278,17 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1270
1278
|
continue;
|
|
1271
1279
|
const struct = a.kind.kind === "account" || a.kind.kind === "init" || a.kind.kind === "initIfNeeded" || a.kind.kind === "zeroed" ? a.kind.struct : undefined;
|
|
1272
1280
|
reporter.guard(() => {
|
|
1273
|
-
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
|
+
}
|
|
1274
1292
|
if (!accounts.some((o) => o.name === f))
|
|
1275
1293
|
reporter.fail(a.node, `HasOne<…, "${f}">: add an account parameter named "${f}"`);
|
|
1276
1294
|
if (a.kind.kind === "token") {
|
|
@@ -1321,7 +1339,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1321
1339
|
for (const a of accounts) {
|
|
1322
1340
|
const c = a.constraints;
|
|
1323
1341
|
const refs = [
|
|
1324
|
-
...c.hasOne,
|
|
1342
|
+
...c.hasOne.map((e) => e.split("=").at(-1)),
|
|
1325
1343
|
...(c.seeds?.names ?? []).filter((n) => n.startsWith("@")).map((n) => n.slice(1).split(".")[0]),
|
|
1326
1344
|
...(c.seeds?.program?.startsWith("@") ? [c.seeds.program.slice(1)] : []),
|
|
1327
1345
|
...(c.closeTo ? [c.closeTo] : []),
|
|
@@ -1381,7 +1399,7 @@ function analyzeInstruction(fn, reporter, scope, structs) {
|
|
|
1381
1399
|
/** Peel HasOne / Seeds / CloseTo wrappers off a parameter type, recording what they ask for. */
|
|
1382
1400
|
function unwrapConstraints(node, reporter, imports, out) {
|
|
1383
1401
|
const name = typesolName(node, imports);
|
|
1384
|
-
if (!["HasOne", "Seeds", "CloseTo", "Address", "Ata", "Owner", "Executable"].includes(name ?? ""))
|
|
1402
|
+
if (!["HasOne", "TokenOf", "MintOf", "Seeds", "CloseTo", "Address", "Ata", "Owner", "Executable"].includes(name ?? ""))
|
|
1385
1403
|
return node;
|
|
1386
1404
|
const args = node.typeArguments ?? [];
|
|
1387
1405
|
const lit = (t) => {
|
|
@@ -1395,6 +1413,20 @@ function unwrapConstraints(node, reporter, imports, out) {
|
|
|
1395
1413
|
};
|
|
1396
1414
|
if (name === "HasOne")
|
|
1397
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
|
+
}
|
|
1398
1430
|
if (name === "CloseTo")
|
|
1399
1431
|
out.closeTo = lit(args[1])[0];
|
|
1400
1432
|
if (name === "Owner") {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ANCHOR_ERRORS: Record<string, number>;
|
|
@@ -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
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { calleeName, resolveScalarType } from "../analyze.js";
|
|
3
|
+
import { ANCHOR_ERRORS } from "../anchor-errors.js";
|
|
3
4
|
import { decodeAddress } from "../../keys.js";
|
|
4
5
|
import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
|
|
5
6
|
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, } from "./shared.js";
|
|
@@ -1091,6 +1092,13 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
1091
1092
|
this.fail(n, "pass signer([...seeds with the bump]) or a SignerSeeds parameter");
|
|
1092
1093
|
}
|
|
1093
1094
|
errorRef(node) {
|
|
1095
|
+
// `AnchorError.X`: one of Anchor's own codes (not one of this program's errors, so not in the IDL).
|
|
1096
|
+
if (ts.isPropertyAccessExpression(node) && this.exportName(node.expression) === "AnchorError") {
|
|
1097
|
+
const code = ANCHOR_ERRORS[node.name.text];
|
|
1098
|
+
if (code === undefined)
|
|
1099
|
+
this.fail(node.name, `AnchorError has no ${node.name.text}`);
|
|
1100
|
+
return { code, name: node.name.text, msg: node.name.text };
|
|
1101
|
+
}
|
|
1094
1102
|
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
|
1095
1103
|
const def = this.model.errorEnums.get(node.expression.text)?.get(node.name.text);
|
|
1096
1104
|
if (def)
|
|
@@ -41,9 +41,10 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
41
41
|
* in source order; the returned lines (each extension's Token-2022 initializer, mint writable, before
|
|
42
42
|
* InitializeMint2) run after the account is created, sized for exactly these extensions.
|
|
43
43
|
*/
|
|
44
|
-
protected mintExtensions(lit: ts.ObjectLiteralExpression, tp: string, acct: string): {
|
|
44
|
+
protected mintExtensions(lit: ts.ObjectLiteralExpression, tp: string, acct: string, ifNeeded?: boolean): {
|
|
45
45
|
space: number;
|
|
46
46
|
lines: string[];
|
|
47
|
+
checks: string[];
|
|
47
48
|
};
|
|
48
49
|
/**
|
|
49
50
|
* `ata.create(payer)` / `ata.createIfNeeded(payer)` on `Ata<Init|InitIfNeeded<TokenAccount>, "owner", "mint">`
|
|
@@ -396,8 +396,6 @@ export class CpiEmitter extends CallEmitter {
|
|
|
396
396
|
const fixedArgs = isMint ? (extArg ? 6 : 5) : 4;
|
|
397
397
|
if (a.length !== fixedArgs && a.length !== fixedArgs + 1)
|
|
398
398
|
this.fail(node, usage);
|
|
399
|
-
if (extArg && ifNeeded)
|
|
400
|
-
this.fail(extArg, "createIfNeeded does not take extensions yet: create the mint with create(…)");
|
|
401
399
|
const payer = this.accountParam(a[0], "the payer");
|
|
402
400
|
if (payer.kind.kind !== "signer" || !payer.kind.mut)
|
|
403
401
|
this.fail(a[0], "the payer must be a Mut<Signer>");
|
|
@@ -438,6 +436,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
438
436
|
};
|
|
439
437
|
let mint;
|
|
440
438
|
const extLines = [];
|
|
439
|
+
const extChecks = [];
|
|
441
440
|
let mintSpace = "TS_MINT_LEN";
|
|
442
441
|
if (isMint) {
|
|
443
442
|
take(a[2], U8);
|
|
@@ -447,9 +446,10 @@ export class CpiEmitter extends CallEmitter {
|
|
|
447
446
|
if (extArg) {
|
|
448
447
|
if (programs[0].kind.kind === "tokenProgram" && programs[0].kind.flavor === "token")
|
|
449
448
|
this.fail(extArg, "mint extensions are Token-2022's; use Token2022Program or TokenInterface");
|
|
450
|
-
const ext = this.mintExtensions(extArg, tp, acct);
|
|
449
|
+
const ext = this.mintExtensions(extArg, tp, acct, ifNeeded);
|
|
451
450
|
mintSpace = String(ext.space);
|
|
452
451
|
extLines.push(...ext.lines);
|
|
452
|
+
extChecks.push(...ext.checks);
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
else {
|
|
@@ -474,6 +474,8 @@ export class CpiEmitter extends CallEmitter {
|
|
|
474
474
|
this.line(` TS_TRY(ts_token_existing(params, ${acct}, ${tp}, ${seeds.name}, ${seeds.count}, &${b}));`);
|
|
475
475
|
this.line(` TS_TRY(ts_check_mint(${acct}));`);
|
|
476
476
|
this.line(` TS_TRY(ts_check_mint_is(${acct}, (uint8_t)(${decimals.c}), ${authority.c}, ${freeze ? freeze.c : "NULL"}));`);
|
|
477
|
+
for (const l of extChecks)
|
|
478
|
+
this.line(` ${l}`);
|
|
477
479
|
this.line(`}`);
|
|
478
480
|
}
|
|
479
481
|
else
|
|
@@ -510,7 +512,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
510
512
|
* in source order; the returned lines (each extension's Token-2022 initializer, mint writable, before
|
|
511
513
|
* InitializeMint2) run after the account is created, sized for exactly these extensions.
|
|
512
514
|
*/
|
|
513
|
-
mintExtensions(lit, tp, acct) {
|
|
515
|
+
mintExtensions(lit, tp, acct, ifNeeded = false) {
|
|
514
516
|
const optKey = { kind: "option", inner: PUBKEY };
|
|
515
517
|
this.model.options.set("pubkey", PUBKEY);
|
|
516
518
|
// name: [extension type length, initializer tag, the fields an object value takes ([] for a plain value)]
|
|
@@ -676,8 +678,60 @@ export class CpiEmitter extends CallEmitter {
|
|
|
676
678
|
}
|
|
677
679
|
}
|
|
678
680
|
}
|
|
681
|
+
// An existing mint (createIfNeeded) is checked as Anchor's `extensions::…` constraints check it: the extension must
|
|
682
|
+
// be there (its Anchor code) and each key must match (the next codes). Anchor has constraints for these six only.
|
|
683
|
+
const CHECKS = {
|
|
684
|
+
groupPointer: {
|
|
685
|
+
type: 20,
|
|
686
|
+
code: 2024,
|
|
687
|
+
fields: [
|
|
688
|
+
["authority", 0],
|
|
689
|
+
["groupAddress", 32],
|
|
690
|
+
],
|
|
691
|
+
},
|
|
692
|
+
groupMemberPointer: {
|
|
693
|
+
type: 21,
|
|
694
|
+
code: 2027,
|
|
695
|
+
fields: [
|
|
696
|
+
["authority", 0],
|
|
697
|
+
["memberAddress", 32],
|
|
698
|
+
],
|
|
699
|
+
},
|
|
700
|
+
metadataPointer: {
|
|
701
|
+
type: 18,
|
|
702
|
+
code: 2030,
|
|
703
|
+
fields: [
|
|
704
|
+
["authority", 0],
|
|
705
|
+
["metadataAddress", 32],
|
|
706
|
+
],
|
|
707
|
+
},
|
|
708
|
+
closeAuthority: { type: 3, code: 2033, fields: [["v", 0]] },
|
|
709
|
+
permanentDelegate: { type: 12, code: 2035, fields: [["v", 0]] },
|
|
710
|
+
transferHook: {
|
|
711
|
+
type: 14,
|
|
712
|
+
code: 2037,
|
|
713
|
+
fields: [
|
|
714
|
+
["authority", 0],
|
|
715
|
+
["programId", 32],
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
const checks = [];
|
|
720
|
+
if (ifNeeded)
|
|
721
|
+
for (const [name, v] of held) {
|
|
722
|
+
const c = CHECKS[name];
|
|
723
|
+
if (!c)
|
|
724
|
+
this.fail(lit, `${name} cannot be checked on an existing mint (Anchor has no constraint for it): create the mint with create(…)`);
|
|
725
|
+
checks.push(`{ uint64_t eo, el; if (!ts_t22_ext(${acct}, ${c.type}, &eo, &el)) TS_FAIL(${c.code}, "TypeSOL: the mint has no ${name} extension"); }`);
|
|
726
|
+
c.fields.forEach(([f, at], i) => {
|
|
727
|
+
const held = v.get(f);
|
|
728
|
+
// The value given: a nonzero-or-zero key, a COption (close authority) or a plain key (permanent delegate).
|
|
729
|
+
const want = name === "closeAuthority" ? `(${held}.some ? ${held}.v : (SolPubkey){0})` : held;
|
|
730
|
+
checks.push(`{ SolPubkey k, w = ${want}; if (!ts_t22_key(${acct}, ${c.type}, ${at}, &k)) ts_memzero(&k, 32); if (!ts_key_eq(&k, &w)) TS_FAIL(${c.code + 1 + i}, "TypeSOL: the mint's ${name} ${f === "v" ? "key" : f} differs"); }`);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
679
733
|
// Base account (165, the mint padded to an account's size), the account type byte, then each TLV entry.
|
|
680
|
-
return { space: 165 + 1 + space, lines };
|
|
734
|
+
return { space: 165 + 1 + space, lines, checks };
|
|
681
735
|
}
|
|
682
736
|
/**
|
|
683
737
|
* `ata.create(payer)` / `ata.createIfNeeded(payer)` on `Ata<Init|InitIfNeeded<TokenAccount>, "owner", "mint">`
|
|
@@ -1847,6 +1901,58 @@ export class CpiEmitter extends CallEmitter {
|
|
|
1847
1901
|
this.cpi(p, metas, data.buf, data.len, seedsAt(13));
|
|
1848
1902
|
return undefined;
|
|
1849
1903
|
}
|
|
1904
|
+
case "mintToCollection": {
|
|
1905
|
+
// MintToCollectionV1: MintV1's accounts plus the collection authority (signing), its record PDA (or null),
|
|
1906
|
+
// the collection mint, metadata (writable) and master edition, Bubblegum's signer PDA and Token Metadata;
|
|
1907
|
+
// MetadataArgs with Some({ verified: false, key: collection mint }) (Bubblegum verifies it).
|
|
1908
|
+
if (a.length !== 17 && a.length !== 18)
|
|
1909
|
+
this.fail(node, "mintToCollection(treeConfig, leafOwner, leafDelegate, merkleTree, payer, treeDelegate, collectionAuthority, collectionAuthorityRecordPda | null, collectionMint, collectionMetadata, collectionEdition, bubblegumSigner, logWrapper, compressionProgram, tokenMetadataProgram, systemProgram, { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable?, editionNonce? }, signerSeeds?)");
|
|
1910
|
+
const metas = [
|
|
1911
|
+
acct(0, "the tree config", true, false),
|
|
1912
|
+
acct(1, "the leaf owner", false, false),
|
|
1913
|
+
acct(2, "the leaf delegate", false, false),
|
|
1914
|
+
acct(3, "the merkle tree", true, false),
|
|
1915
|
+
acct(4, "the payer", false, true),
|
|
1916
|
+
acct(5, "the tree delegate", false, true),
|
|
1917
|
+
acct(6, "the collection authority", false, true),
|
|
1918
|
+
acct(7, "the collection authority record", false, false),
|
|
1919
|
+
acct(8, "the collection mint", false, false),
|
|
1920
|
+
acct(9, "the collection metadata", true, false),
|
|
1921
|
+
acct(10, "the collection edition", false, false),
|
|
1922
|
+
acct(11, "Bubblegum's signer", false, false),
|
|
1923
|
+
acct(12, "the log wrapper", false, false),
|
|
1924
|
+
acct(13, "the compression program", false, false),
|
|
1925
|
+
acct(14, "Token Metadata", false, false),
|
|
1926
|
+
acct(15, "the system program", false, false),
|
|
1927
|
+
];
|
|
1928
|
+
const md = a[16];
|
|
1929
|
+
if (!ts.isObjectLiteralExpression(md))
|
|
1930
|
+
this.fail(md, "the metadata is { name, symbol, uri, sellerFeeBasisPoints, creators?, isMutable?, editionNonce? }");
|
|
1931
|
+
const props = this.literalProps(md, ["name", "symbol", "uri", "sellerFeeBasisPoints", "creators", "isMutable", "editionNonce"], "the metadata");
|
|
1932
|
+
const need = (k) => props.get(k) ?? this.fail(md, `the metadata needs ${k}`);
|
|
1933
|
+
// `collectionMint.key`, read where the call is.
|
|
1934
|
+
const key = ts.factory.createPropertyAccessExpression(a[8], "key");
|
|
1935
|
+
ts.setTextRange(key, a[8]);
|
|
1936
|
+
key.parent = node;
|
|
1937
|
+
const nonce = props.get("editionNonce");
|
|
1938
|
+
const data = this.mplData(node, 1024, [
|
|
1939
|
+
{ bytes: [153, 18, 178, 47, 197, 158, 86, 15] },
|
|
1940
|
+
{ value: need("name"), type: STRING },
|
|
1941
|
+
{ value: need("symbol"), type: STRING },
|
|
1942
|
+
{ value: need("uri"), type: STRING },
|
|
1943
|
+
{ value: need("sellerFeeBasisPoints"), type: { kind: "int", name: "u16" } },
|
|
1944
|
+
{ bytes: [0] }, // primarySaleHappened
|
|
1945
|
+
props.get("isMutable") ? { value: props.get("isMutable"), type: BOOL } : { bytes: [1] },
|
|
1946
|
+
...(nonce ? [{ bytes: [1] }, { value: nonce, type: U8 }] : [{ bytes: [0] }]),
|
|
1947
|
+
{ bytes: [1, 0] }, // tokenStandard Some(NonFungible)
|
|
1948
|
+
{ bytes: [1, 0] }, // collection Some({ verified: false, … })
|
|
1949
|
+
{ value: key, type: PUBKEY },
|
|
1950
|
+
{ bytes: [0, 0] }, // uses None, tokenProgramVersion Original
|
|
1951
|
+
...this.bubblegumCreators(props.get("creators")),
|
|
1952
|
+
]);
|
|
1953
|
+
this.cpi(p, metas, data.buf, data.len, seedsAt(17));
|
|
1954
|
+
return undefined;
|
|
1955
|
+
}
|
|
1850
1956
|
}
|
|
1851
1957
|
return this.bubblegumV2(node, bg, method);
|
|
1852
1958
|
}
|
|
@@ -2128,7 +2234,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
2128
2234
|
this.cpi(p, metas, data.buf, data.len, seedsAt(proofAt + 1), proofRange(a[proofAt]));
|
|
2129
2235
|
return undefined;
|
|
2130
2236
|
}
|
|
2131
|
-
this.fail(node.expression, `Bubblegum supports createTree, mint, createTreeV2, mintV2, transferV2, burnV2, ${Object.keys(BUBBLEGUM_OPS).join(", ")}, not ${method}`);
|
|
2237
|
+
this.fail(node.expression, `Bubblegum supports createTree, mint, mintToCollection, createTreeV2, mintV2, transferV2, burnV2, ${Object.keys(BUBBLEGUM_OPS).join(", ")}, not ${method}`);
|
|
2132
2238
|
}
|
|
2133
2239
|
/** Object literal properties by name, refusing others. */
|
|
2134
2240
|
literalProps(o, allowed, what) {
|
|
@@ -23,6 +23,25 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
|
|
|
23
23
|
/** Whether TypeScript sees this expression as a string (decides `+` between concatenation and arithmetic). */
|
|
24
24
|
protected looksLikeString(node: ts.Expression): boolean;
|
|
25
25
|
/** A string view of a string, integer or boolean (for concatenation and templates). */
|
|
26
|
+
/**
|
|
27
|
+
* A template literal's pieces in order, text known at compile time folded in: all text (`text`), or a C array of
|
|
28
|
+
* TS_Str (`arr`, `n` entries). When a later piece runs statements, earlier pieces that view account text are copied
|
|
29
|
+
* first, as JS reads them then.
|
|
30
|
+
*/
|
|
31
|
+
protected templatePieces(node: ts.TemplateExpression): {
|
|
32
|
+
text: string;
|
|
33
|
+
arr?: undefined;
|
|
34
|
+
n?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
text?: undefined;
|
|
37
|
+
arr: string;
|
|
38
|
+
n: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
|
|
42
|
+
* literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
|
|
43
|
+
*/
|
|
44
|
+
protected constantText(e: ts.Expression): string | undefined;
|
|
26
45
|
protected toText(node: ts.Expression): string;
|
|
27
46
|
/** C statement writing the decimal text of an integer into the TS_Str `out`. */
|
|
28
47
|
protected textOfInt(name: IntName, c: string, out: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { base58 } from "../../keys.js";
|
|
2
3
|
import { calleeName, resolveValueType } from "../analyze.js";
|
|
3
4
|
import { INT_INFO, unsignedC, optionKey, sameType, typeName } from "../model.js";
|
|
4
5
|
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cString, intLiteral, scalarStorageC, optC, valueC, vecCount } from "./shared.js";
|
|
@@ -151,22 +152,14 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
151
152
|
return { c: `ts_str((const uint8_t *)${cString(node.text)}, ${len})`, type: STRING, stringLiteral: node.text };
|
|
152
153
|
}
|
|
153
154
|
if (ts.isTemplateExpression(node)) {
|
|
154
|
-
// `a${x}b${y}c
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]) {
|
|
163
|
-
const next = this.tmp("tpl");
|
|
164
|
-
this.line(`TS_Str ${next};`);
|
|
165
|
-
this.line(`TS_TRY(ts_str_concat(${acc}, ${piece}, &${next}));`);
|
|
166
|
-
acc = next;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return { c: acc, type: STRING };
|
|
155
|
+
// `a${x}b${y}c`: one join on the heap (or a literal when every piece is known at compile time).
|
|
156
|
+
const pieces = this.templatePieces(node);
|
|
157
|
+
if (pieces.text !== undefined)
|
|
158
|
+
return { c: `ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})`, type: STRING, stringLiteral: pieces.text };
|
|
159
|
+
const t = this.tmp("tpl");
|
|
160
|
+
this.line(`TS_Str ${t};`);
|
|
161
|
+
this.line(`TS_TRY(ts_str_join(${pieces.arr}, ${pieces.n}, &${t}));`);
|
|
162
|
+
return { c: t, type: STRING };
|
|
170
163
|
}
|
|
171
164
|
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
172
165
|
return { c: "", type: BOOL, isNull: true };
|
|
@@ -430,6 +423,68 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
430
423
|
return isText(this.checker.getTypeAtLocation(node));
|
|
431
424
|
}
|
|
432
425
|
/** A string view of a string, integer or boolean (for concatenation and templates). */
|
|
426
|
+
/**
|
|
427
|
+
* A template literal's pieces in order, text known at compile time folded in: all text (`text`), or a C array of
|
|
428
|
+
* TS_Str (`arr`, `n` entries). When a later piece runs statements, earlier pieces that view account text are copied
|
|
429
|
+
* first, as JS reads them then.
|
|
430
|
+
*/
|
|
431
|
+
templatePieces(node) {
|
|
432
|
+
const parts = [];
|
|
433
|
+
const lit = (text) => {
|
|
434
|
+
if (!text)
|
|
435
|
+
return;
|
|
436
|
+
const last = parts.at(-1);
|
|
437
|
+
if (last?.text !== undefined)
|
|
438
|
+
last.text += text;
|
|
439
|
+
else
|
|
440
|
+
parts.push({ text });
|
|
441
|
+
};
|
|
442
|
+
lit(node.head.text);
|
|
443
|
+
for (const span of node.templateSpans) {
|
|
444
|
+
const known = this.constantText(span.expression);
|
|
445
|
+
if (known !== undefined)
|
|
446
|
+
lit(known);
|
|
447
|
+
else {
|
|
448
|
+
const mark = this.lines.length;
|
|
449
|
+
const c = this.toText(span.expression);
|
|
450
|
+
for (const p of parts)
|
|
451
|
+
if (p.c !== undefined)
|
|
452
|
+
p.c = this.settle({ c: p.c, type: STRING }, mark).c;
|
|
453
|
+
parts.push({ c });
|
|
454
|
+
}
|
|
455
|
+
lit(span.literal.text);
|
|
456
|
+
}
|
|
457
|
+
if (parts.every((p) => p.text !== undefined))
|
|
458
|
+
return { text: parts.map((p) => p.text).join("") };
|
|
459
|
+
const strOf = (text) => `ts_str((const uint8_t *)${cString(text)}, ${Buffer.byteLength(text, "utf8")})`;
|
|
460
|
+
const arr = this.tmp("parts");
|
|
461
|
+
this.line(`TS_Str ${arr}[${parts.length}] = {${parts.map((p) => (p.text !== undefined ? strOf(p.text) : p.c)).join(", ")}};`);
|
|
462
|
+
return { arr, n: parts.length };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
|
|
466
|
+
* literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
|
|
467
|
+
*/
|
|
468
|
+
constantText(e) {
|
|
469
|
+
if (ts.isParenthesizedExpression(e))
|
|
470
|
+
return this.constantText(e.expression);
|
|
471
|
+
if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e))
|
|
472
|
+
return e.text;
|
|
473
|
+
if (e.kind === ts.SyntaxKind.TrueKeyword)
|
|
474
|
+
return "true";
|
|
475
|
+
if (e.kind === ts.SyntaxKind.FalseKeyword)
|
|
476
|
+
return "false";
|
|
477
|
+
if (ts.isBigIntLiteral(e))
|
|
478
|
+
return BigInt(e.text.slice(0, -1).replace(/_/g, "")).toString();
|
|
479
|
+
if (ts.isIdentifier(e) && !this.lookup(e.text)) {
|
|
480
|
+
const k = this.model.constants.get(e.text);
|
|
481
|
+
if (k && typeof k.value === "string")
|
|
482
|
+
return k.value;
|
|
483
|
+
if (k && typeof k.value === "object")
|
|
484
|
+
return base58(Uint8Array.from(k.value.pubkey));
|
|
485
|
+
}
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
433
488
|
toText(node) {
|
|
434
489
|
const v = this.expr(node);
|
|
435
490
|
if (v.type.kind === "string")
|
|
@@ -371,15 +371,17 @@ export class FunctionEmitter extends StatementEmitter {
|
|
|
371
371
|
const k = a.kind.kind;
|
|
372
372
|
if (c.hasOne.length && k !== "account" && k !== "token" && k !== "mint")
|
|
373
373
|
this.reporter.fail(a.node, "HasOne applies to existing accounts (Account<T> / Mut<T>), TokenAccount and Mint");
|
|
374
|
-
for (const
|
|
374
|
+
for (const entry of c.hasOne) {
|
|
375
|
+
// "field=param" (TokenOf / MintOf) or a field checked against the parameter of the same name (HasOne).
|
|
376
|
+
const [f, p = f] = entry.split("=");
|
|
375
377
|
if (k === "token")
|
|
376
378
|
out.push(f === "mint"
|
|
377
|
-
? ` if (!ts_key_eq(ts_token_mint(a_${a.name}), &a_${
|
|
378
|
-
: ` if (!ts_key_eq(ts_token_owner(a_${a.name}), &a_${
|
|
379
|
+
? ` if (!ts_key_eq(ts_token_mint(a_${a.name}), &a_${p}->key)) TS_FAIL(TS_ERR_TOKEN_MINT, "TypeSOL: ${a.name} is not a token account of ${p}");`
|
|
380
|
+
: ` if (!ts_key_eq(ts_token_owner(a_${a.name}), &a_${p}->key)) TS_FAIL(TS_ERR_TOKEN_OWNER, "TypeSOL: ${a.name} is not owned by ${p}");`);
|
|
379
381
|
else if (k === "mint") {
|
|
380
382
|
const at = f === "mintAuthority" ? 0 : 46;
|
|
381
383
|
const code = f === "mintAuthority" ? "TS_ERR_MINT_AUTHORITY" : "TS_ERR_FREEZE_AUTHORITY";
|
|
382
|
-
out.push(` if (ts_read_le(a_${a.name}->data + ${at}, 4) != 1 || !ts_key_eq((const SolPubkey *)(a_${a.name}->data + ${at + 4}), &a_${
|
|
384
|
+
out.push(` if (ts_read_le(a_${a.name}->data + ${at}, 4) != 1 || !ts_key_eq((const SolPubkey *)(a_${a.name}->data + ${at + 4}), &a_${p}->key)) TS_FAIL(${code}, "TypeSOL: ${p} is not the ${f} of ${a.name}");`);
|
|
383
385
|
}
|
|
384
386
|
else
|
|
385
387
|
out.push(` if (!ts_key_eq(&d_${a.name}->${f}, &a_${f}->key)) TS_FAIL(TS_ERR_CONSTRAINT, "TypeSOL: ${a.name}.${f} must be the ${f} account");`);
|
|
@@ -842,7 +842,24 @@ export class StatementEmitter extends CpiEmitter {
|
|
|
842
842
|
this.line(`TS_Str ${cur};`);
|
|
843
843
|
this.line(`TS_TRY(ts_str_copy(${current.c}, &${cur}));`);
|
|
844
844
|
}
|
|
845
|
+
// A local grows in place when it is the newest heap string (a loop of `s += …` copies each piece once); a
|
|
846
|
+
// template on the right is appended piece by piece.
|
|
847
|
+
const local = cur === current.c && place.write("X") === `${cur} = X`;
|
|
848
|
+
const bare = (e) => (ts.isParenthesizedExpression(e) ? bare(e.expression) : e);
|
|
849
|
+
const rhsNode = bare(valueNode);
|
|
850
|
+
if (local && ts.isTemplateExpression(rhsNode)) {
|
|
851
|
+
const pieces = this.templatePieces(rhsNode);
|
|
852
|
+
if (pieces.text !== undefined)
|
|
853
|
+
this.line(`TS_TRY(ts_str_append(&${cur}, ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})));`);
|
|
854
|
+
else
|
|
855
|
+
this.line(`TS_TRY(ts_str_append_parts(&${cur}, ${pieces.arr}, ${pieces.n}));`);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
845
858
|
const rhs = this.toText(valueNode);
|
|
859
|
+
if (local) {
|
|
860
|
+
this.line(`TS_TRY(ts_str_append(&${cur}, ${rhs}));`);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
846
863
|
const t = this.tmp("cat");
|
|
847
864
|
this.line(`TS_Str ${t};`);
|
|
848
865
|
this.line(`TS_TRY(ts_str_concat(${cur}, ${rhs}, &${t}));`);
|
package/docs/language.md
CHANGED
|
@@ -11,7 +11,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
11
11
|
| Integers | `u8` `u16` `u32` `u64` `u128` `i8` `i16` `i32` `i64` `i128` — bigint literals (`1n`), every `+ - * / % **` (and `+=` … `**=`) checked (`**` aborts on a negative exponent, as JS throws); `& \| ^` on equal types, `~` within the width, `<<` / `>>` checked (amount below the width, `<<` may not lose bits) |
|
|
12
12
|
| Integer helpers | `mulDiv(a, b, c)` / `mulDivCeil` (u64 through a 128-bit product, u128 through a 256-bit one), `isqrt(x)` (up to u128), `min(a, b)`, `max(a, b)` |
|
|
13
13
|
| Other scalars | `boolean`, `Pubkey` (compare with `===` or `.equals`) |
|
|
14
|
-
| Errors | `export enum MyError { Name = "message" }` + `require(cond, MyError.Name)` → codes 6000
|
|
14
|
+
| Errors | `export enum MyError { Name = "message" }` + `require(cond, MyError.Name)` → codes 6000+; `require(cond, AnchorError.RequireKeysEqViolated)` fails with one of Anchor's own codes (2502 here; every anchor-lang `ErrorCode`) |
|
|
15
15
|
| Casts | `u8(x)`, `u64(x)`, … abort if the value does not fit |
|
|
16
16
|
| Logging | `log("text")`, `log(value)`, `log(pubkey)` |
|
|
17
17
|
| Arrays | `scores: FixedArray<u64, 8>` in account data — `.get(i)`, `.set(i, v)`, `.length`, bounds-checked; elements can be structs or strings |
|
|
@@ -36,7 +36,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
36
36
|
| Constants | top-level `const SEED = "vault"`, `const FEE: u16 = 30n`, `const ON = true` — inlined, usable as seeds (and described in the IDL) |
|
|
37
37
|
| Data enums | `export type Action = { kind: "Deposit"; amount: u64 } \| { kind: "Pause" }` — `a.kind === "Deposit"`, `switch (a.kind)`, TypeScript narrowing picks the variant (checked again at runtime, 5112); Borsh enums in arguments/events, fixed `{ tag, payload }` in account data. A variant may hold a `string` (`{ kind: "Say"; text: string }`): such an enum works in arguments, events and locals (Borsh, as Anchor encodes it) but not in account data |
|
|
38
38
|
| Option of structs | `home: Option<Point>` in account data — `=== null`, `p.home.x` after narrowing, `p.home = null` / `{ x, y }` |
|
|
39
|
-
| Strings at runtime | `"hello, " + name`, `` `#${n}` ``, `` `owner ${acct.owner}` `` (a Pubkey or Hash as base58) — built on the 32 KiB heap (5110 when full) |
|
|
39
|
+
| Strings at runtime | `"hello, " + name`, `` `#${n}` ``, `` `owner ${acct.owner}` `` (a Pubkey or Hash as base58) — built on the 32 KiB heap (5110 when full): a template is joined in one allocation, text known at compile time (literals, string and Pubkey constants) is folded in, and `s += …` grows the newest heap string in place |
|
|
40
40
|
| `switch` | on integers (128-bit ones too), enums and data-enum `.kind`, with JS `break` / fallthrough; case labels are literals, constants or enum members |
|
|
41
41
|
| Hashing & signatures | `sha256(...)`, `keccak256(...)` → `Hash`; `findProgramAddress(seeds, program)` → the canonical PDA of any program (Rust `Pubkey::find_program_address(..).0`, e.g. a Bubblegum asset id); `merkleVerify(leaf, proof: Vec<Hash>, root)` (sorted-pair keccak, OpenZeppelin convention); `verifyEd25519(instructionsSysvar, signer, message)` checks an Ed25519 precompile instruction in the same transaction; `verifySecp256k1(keccak256(...message) | ethMessageHash(message), recoveryId, r, s, ethAddress)` (the hash must be computed in the program; a passed-in hash could be forged) |
|
|
42
42
|
| Oracles | `pythPrice(update: PythPriceUpdate, feedId, maxAgeSeconds)` (fully verified, feed-matched, fresh); raw `update.price / conf / exponent / publishTime`; `switchboardValue(feed: SwitchboardFeed, expectedFeed, maxStaleSlots, decimals)` (the feed must be the expected one: anyone can create feeds) (mainnet program unless `SwitchboardFeed<"devnet">`) |
|
|
@@ -67,7 +67,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
67
67
|
| `Init<T>` | writable; `create(payer, seeds)` derives the PDA, verifies it, funds rent, writes the discriminator (up to 10 KiB); `create(payer, null)` creates a new keypair account that signs the transaction instead (3010 if it did not; a signer in the IDL); a third argument is Anchor's `space = …` (discriminator included, at least 8 + the struct's size) |
|
|
68
68
|
| `InitIfNeeded<T>` | `const created = acct.createIfNeeded(payer, seeds)` — creates once; afterwards re-derives the PDA and checks owner, discriminator and size (Anchor `init_if_needed`); `acct.pdaBump` after create |
|
|
69
69
|
| `HasOne<A, "owner">`, `Seeds<A, ["vault", "@owner"], "bump">`, `CloseTo<A, "owner">` | declarative constraints like Anchor's `has_one`, `seeds`/`bump` and `close`: checked before the body (2001 / 2006), close runs when the instruction succeeds; `"@name"` = account key or argument bytes, `"@acct.field"` = a Pubkey, integer or unit enum field of a struct account parameter (also the account's own: `["offer", "@maker", "@offer.id"]`), other strings are literals; recorded as `relations` / `pda` in the IDL. `acct.pdaBump` is the bump (Anchor `ctx.bumps.acct`): the stored field when one is named, else the canonical bump the check found — use it to sign as the PDA |
|
|
70
|
-
| `HasOne<TokenAccount, "mint" \| "owner">`, `HasOne<Mint, "mintAuthority" \| "freezeAuthority">` | token constraints (2014 / 2015 / 2016 / 2017) against the parameter with that name |
|
|
70
|
+
| `HasOne<TokenAccount, "mint" \| "owner">`, `HasOne<Mint, "mintAuthority" \| "freezeAuthority">` | token constraints (2014 / 2015 / 2016 / 2017) against the parameter with that name; `TokenOf<TokenAccount, "mintParam", "authorityParam">` / `MintOf<Mint, "authorityParam", "freezeParam">` name the parameters, as Anchor's `token::mint = …` / `mint::authority = …` do (`never` leaves one out) |
|
|
71
71
|
| `Ata<TokenAccount, "owner", "mint">` | the account is the associated token account of those parameters (2009; ATA `pda` in the IDL) |
|
|
72
72
|
| `Ata<Init<TokenAccount>, "owner", "mint">`, `Ata<InitIfNeeded<TokenAccount>, "owner", "mint">` | Anchor `init` / `init_if_needed` with `associated_token::…`: `acct.create(payer)` creates the ATA (fails if it exists); `const created = acct.createIfNeeded(payer)` creates it only when missing, otherwise checks it is that ATA (2009). Then it is a `TokenAccount` of that mint and owner (2014 / 2015). Needs `AssociatedTokenProgram`, `SystemProgram` and a token program parameter; call it at the top level before using the account |
|
|
73
73
|
| `Address<A, "4Nd1…">` | exact address (2012; `address` in the IDL) |
|
|
@@ -77,7 +77,7 @@ The full reference. For a tour, start with the [README](../README.md).
|
|
|
77
77
|
| `ProgramData` | this program's ProgramData (address derived from the program id, upgradeable-loader owner and state checked); `upgradeAuthority`, `deploySlot` — gate admin setup on `programData.upgradeAuthority === admin.key` |
|
|
78
78
|
| `EventAuthority`, `SelfProgram` | Anchor's `["__event_authority"]` PDA, and this program's own address, for `emitCpi` |
|
|
79
79
|
| `Zeroed<T>` | large accounts (up to 10 MiB) the client allocated with System `CreateAccount`: owned by this program, big enough, rent-exempt, discriminator still zero — then initialized; like Anchor's zero-copy `zero` |
|
|
80
|
-
| `Init<Mint>`, `InitIfNeeded<Mint>` | Anchor `init` / `init_if_needed` with `mint::decimals` / `mint::authority` / `mint::freeze_authority`: `const bump = mint.create(payer, ["mint", payer.key], 6n, authority, freezeAuthority \| null)` creates the mint at that PDA of this program, or with `null` seeds as a new keypair account that signs (3010 if it did not), under the instruction's token program (exactly one token program parameter, plus `SystemProgram`). Token-2022 extensions (Anchor's `extensions::…`) go after the freeze authority: `mint.create(payer, null, 6n, authority, null, { metadataPointer: { authority, metadataAddress: mint.key }, transferHook: { authority, programId }, closeAuthority, permanentDelegate, nonTransferable: true, defaultAccountState: true, interestBearing: { rateAuthority, rate }, transferFee: { configAuthority, withdrawAuthority, basisPoints, maximumFee }, groupPointer, groupMemberPointer })` sizes the mint for them and initializes each before InitializeMint2. `createIfNeeded(…)` returns whether it created it; an existing one must be that PDA and have those decimals and authorities (2018 / 2016 / 2017) |
|
|
80
|
+
| `Init<Mint>`, `InitIfNeeded<Mint>` | Anchor `init` / `init_if_needed` with `mint::decimals` / `mint::authority` / `mint::freeze_authority`: `const bump = mint.create(payer, ["mint", payer.key], 6n, authority, freezeAuthority \| null)` creates the mint at that PDA of this program, or with `null` seeds as a new keypair account that signs (3010 if it did not), under the instruction's token program (exactly one token program parameter, plus `SystemProgram`). Token-2022 extensions (Anchor's `extensions::…`) go after the freeze authority: `mint.create(payer, null, 6n, authority, null, { metadataPointer: { authority, metadataAddress: mint.key }, transferHook: { authority, programId }, closeAuthority, permanentDelegate, nonTransferable: true, defaultAccountState: true, interestBearing: { rateAuthority, rate }, transferFee: { configAuthority, withdrawAuthority, basisPoints, maximumFee }, groupPointer, groupMemberPointer })` sizes the mint for them and initializes each before InitializeMint2. With `createIfNeeded` an existing mint must carry groupPointer / groupMemberPointer / metadataPointer / closeAuthority / permanentDelegate / transferHook with those keys (Anchor's 2024–2039). `createIfNeeded(…)` returns whether it created it; an existing one must be that PDA and have those decimals and authorities (2018 / 2016 / 2017) |
|
|
81
81
|
| `Init<TokenAccount>`, `InitIfNeeded<TokenAccount>` (no `Ata<…>`) | Anchor `init` / `init_if_needed` with `token::mint` / `token::authority`: `vault.create(payer, ["vault", mint.key], mint, authority)` (the authority may be `vault.key` itself) creates a token account at that PDA (or a keypair account with `null`), sized for the mint's extensions under Token-2022. `createIfNeeded` checks an existing one has that mint and owner (2014 / 2015) |
|
|
82
82
|
| `TokenAccount` / `Mut<TokenAccount>` | owned by SPL Token or Token-2022 (extensions allowed, multisig accounts refused), initialized; `mint`, `owner`, `amount`, `delegate`, `delegatedAmount`, `isFrozen`, `isNative`, `closeAuthority`, `tokenProgram` |
|
|
83
83
|
| `Mint` / `Mut<Mint>` | owned by SPL Token or Token-2022, initialized; `decimals`, `supply`, `mintAuthority`, `freezeAuthority`, `tokenProgram` |
|
|
@@ -159,8 +159,7 @@ Growable `Vec`s anywhere but the last account field (use `FixedVec<T, N>`), Fixe
|
|
|
159
159
|
the next instruction), `?.` on anything but optional accounts and Option<struct> fields, `!` on anything but an Option
|
|
160
160
|
or an optional account, Core's external plugin adapters (oracles, app data, lifecycle hooks: use `invoke`), closures
|
|
161
161
|
stored or returned (they are called or passed to list methods), arrow literals and closures as helper arguments (pass a
|
|
162
|
-
named function), `map` / `filter` (they would build a new list),
|
|
163
|
-
mint with `create(…, extensions)`), floating point (by design).
|
|
162
|
+
named function), `map` / `filter` (they would build a new list), floating point (by design).
|
|
164
163
|
They are rejected at compile time rather than half-supported.
|
|
165
164
|
|
|
166
165
|
`Init.create` handles a PDA address someone pre-funded: it tops up rent, then allocates and assigns instead of
|
|
@@ -49,7 +49,7 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
|
|
|
49
49
|
| `Program<'info, System>` | `SystemProgram` |
|
|
50
50
|
| `Program<'info, Token>` / `Token2022` / `Interface<'info, TokenInterface>` | `TokenProgram` / `Token2022Program` / `TokenInterface` |
|
|
51
51
|
| `Account<'info, TokenAccount>` / `Account<'info, Mint>` (`InterfaceAccount`) | `TokenAccount` / `Mint` (`Mut<…>` when written) |
|
|
52
|
-
| `token::mint = m, token::authority = a` | `HasOne<TokenAccount, "mint" \| "owner">`
|
|
52
|
+
| `token::mint = m, token::authority = a` | `TokenOf<TokenAccount, "m", "a">` (2014 / 2015), or `HasOne<TokenAccount, "mint" \| "owner">` when the parameters have those names; `mint::authority = a, mint::freeze_authority = f` on an existing mint: `MintOf<Mint, "a", "f">` (2016 / 2017) |
|
|
53
53
|
| `extensions::metadata_pointer::authority = a, …` (with `mint::…`) | `mint.create(p, …, { metadataPointer: { authority: a.key, metadataAddress: mint.key }, … })`: transferHook, closeAuthority, permanentDelegate, nonTransferable, groupPointer, groupMemberPointer and more |
|
|
54
54
|
| `#[account(init, payer = p, seeds = […], bump, mint::decimals = 6, mint::authority = a, mint::freeze_authority = f)]` | `Init<Mint>`, then `const bump = mint.create(p, […], 6n, a.key, f.key)` (`null` seeds: a keypair mint; `InitIfNeeded<Mint>` + `createIfNeeded`) |
|
|
55
55
|
| `#[account(init, payer = p, seeds = […], bump, token::mint = m, token::authority = a)]` | `Init<TokenAccount>`, then `vault.create(p, […], m, a.key)` (`InitIfNeeded<TokenAccount>` + `createIfNeeded`) |
|
|
@@ -91,6 +91,8 @@ Anchor instruction that takes or returns a struct with `String` / `Vec` / `Optio
|
|
|
91
91
|
| `-> Result<u64>` / a struct | `: u64` / a struct: sent as return data, listed in the IDL |
|
|
92
92
|
| `#[error_code] enum MyError { #[msg("…")] X }` | `export enum MyError { X = "…" }` (codes from 6000, in order) |
|
|
93
93
|
| `require!(c, MyError::X)`, `err!(MyError::X)` | `require(c, MyError.X)`, `require(false, MyError.X)` |
|
|
94
|
+
| `require_keys_eq!(a, b)`, `err!(ErrorCode::AccountDidNotDeserialize)` (Anchor's own codes) | `require(a === b, AnchorError.RequireKeysEqViolated)`, `require(false, AnchorError.AccountDidNotDeserialize)` |
|
|
95
|
+
| `init_if_needed` + `extensions::…` on a mint | `mint.createIfNeeded(…, { metadataPointer: {…}, … })`: an existing mint is checked as Anchor checks it (2024–2039) |
|
|
94
96
|
| `require_keys_eq!(a, b)` | `require(a === b, …)` |
|
|
95
97
|
| `a.checked_add(b).ok_or(…)?` | `a + b` (every operation is checked; overflow aborts with 5100) |
|
|
96
98
|
| `emit!(Ev { … })` / `emit_cpi!` | `emit<Ev>({ … })` / `emitCpi<Ev>({ … }, eventAuthority, self)` |
|
package/lib/typesol.d.ts
CHANGED
|
@@ -517,6 +517,20 @@ export type InitIfNeeded<T> = T extends TokenAccount
|
|
|
517
517
|
freezeAuthority: Pubkey | null,
|
|
518
518
|
tokenProgram?: TokenProgram | Token2022Program | TokenInterface,
|
|
519
519
|
): boolean;
|
|
520
|
+
/**
|
|
521
|
+
* With extensions: created with them, or an existing mint must carry them with these keys, as Anchor's
|
|
522
|
+
* `extensions::…` constraints check (2024–2039). groupPointer, groupMemberPointer, metadataPointer,
|
|
523
|
+
* closeAuthority, permanentDelegate and transferHook only.
|
|
524
|
+
*/
|
|
525
|
+
createIfNeeded(
|
|
526
|
+
payer: Mut<Signer>,
|
|
527
|
+
seeds: Seed[] | null,
|
|
528
|
+
decimals: u8,
|
|
529
|
+
mintAuthority: Pubkey,
|
|
530
|
+
freezeAuthority: Pubkey | null,
|
|
531
|
+
extensions: MintExtensions,
|
|
532
|
+
tokenProgram?: Token2022Program | TokenInterface,
|
|
533
|
+
): boolean;
|
|
520
534
|
readonly pdaBump: u8;
|
|
521
535
|
}
|
|
522
536
|
: T &
|
|
@@ -539,6 +553,15 @@ export type InitIfNeeded<T> = T extends TokenAccount
|
|
|
539
553
|
* They nest: `CloseTo<HasOne<Mut<Vault>, "owner">, "owner">`.
|
|
540
554
|
*/
|
|
541
555
|
export type HasOne<A, Field extends string> = A & { readonly __hasOne?: Field };
|
|
556
|
+
/**
|
|
557
|
+
* Anchor's `token::mint = mintParam, token::authority = authorityParam` on an existing token account, naming the
|
|
558
|
+
* parameters (2014 / 2015); `never` leaves one out.
|
|
559
|
+
*/
|
|
560
|
+
export type TokenOf<A, MintParam extends string, AuthorityParam extends string = never> = A & { readonly __tokenOf?: [MintParam, AuthorityParam] };
|
|
561
|
+
/** Anchor's `mint::authority = a, mint::freeze_authority = f` on an existing mint, naming the parameters (2016 / 2017). */
|
|
562
|
+
export type MintOf<A, AuthorityParam extends string, FreezeAuthorityParam extends string = never> = A & {
|
|
563
|
+
readonly __mintOf?: [AuthorityParam, FreezeAuthorityParam];
|
|
564
|
+
};
|
|
542
565
|
export type Seeds<A, Names extends readonly string[], Bump extends string = never, ProgramId extends string = never> = A & {
|
|
543
566
|
readonly __seeds?: [Names, Bump, ProgramId];
|
|
544
567
|
/** The PDA's bump (Anchor `ctx.bumps.<account>`): the stored bump field when one is named, else the canonical bump found. */
|
|
@@ -774,6 +797,31 @@ export interface BubblegumProgram extends AccountMeta {
|
|
|
774
797
|
sellerFeeBasisPoints: u16,
|
|
775
798
|
signerSeeds?: Seed[] | SignerSeeds,
|
|
776
799
|
): void;
|
|
800
|
+
/**
|
|
801
|
+
* MintToCollectionV1 on a V1 tree: the leaf joins a Token Metadata collection (Bubblegum verifies it; the collection
|
|
802
|
+
* authority signs, its record PDA is `null` when it is the update authority itself). Creators default to none,
|
|
803
|
+
* `isMutable` to true, `editionNonce` to None.
|
|
804
|
+
*/
|
|
805
|
+
mintToCollection(
|
|
806
|
+
treeConfig: AccountMeta,
|
|
807
|
+
leafOwner: AccountMeta,
|
|
808
|
+
leafDelegate: AccountMeta,
|
|
809
|
+
merkleTree: AccountMeta,
|
|
810
|
+
payer: Signer,
|
|
811
|
+
treeDelegate: AccountMeta,
|
|
812
|
+
collectionAuthority: AccountMeta,
|
|
813
|
+
collectionAuthorityRecordPda: AccountMeta | null,
|
|
814
|
+
collectionMint: AccountMeta,
|
|
815
|
+
collectionMetadata: AccountMeta,
|
|
816
|
+
collectionEdition: AccountMeta,
|
|
817
|
+
bubblegumSigner: AccountMeta,
|
|
818
|
+
logWrapper: AccountMeta,
|
|
819
|
+
compressionProgram: AccountMeta,
|
|
820
|
+
tokenMetadataProgram: AccountMeta,
|
|
821
|
+
systemProgram: SystemProgram,
|
|
822
|
+
metadata: CompressedMetadata & { editionNonce?: u8 },
|
|
823
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
824
|
+
): void;
|
|
777
825
|
/**
|
|
778
826
|
* CreateTreeConfigV2: a tree on mpl-account-compression (mcmt6…, allocated by the client and owned by it) with the
|
|
779
827
|
* mpl-noop log wrapper (mnoop…). `treeCreator` `null`: the payer.
|
|
@@ -1382,7 +1430,90 @@ export declare function invoke(
|
|
|
1382
1430
|
): void;
|
|
1383
1431
|
|
|
1384
1432
|
/** Abort with the given error unless `condition` holds. Use a string enum member or a string literal. */
|
|
1385
|
-
|
|
1433
|
+
/**
|
|
1434
|
+
* Anchor's own error codes (anchor-lang `ErrorCode`): `require(condition, AnchorError.AccountDidNotDeserialize)` fails
|
|
1435
|
+
* with that code, as `require!(condition, ErrorCode::AccountDidNotDeserialize)` does. Not listed in the IDL's errors.
|
|
1436
|
+
*/
|
|
1437
|
+
export declare enum AnchorError {
|
|
1438
|
+
InstructionMissing = 100,
|
|
1439
|
+
InstructionFallbackNotFound = 101,
|
|
1440
|
+
InstructionDidNotDeserialize = 102,
|
|
1441
|
+
InstructionDidNotSerialize = 103,
|
|
1442
|
+
IdlInstructionStub = 1000,
|
|
1443
|
+
IdlInstructionInvalidProgram = 1001,
|
|
1444
|
+
IdlAccountNotEmpty = 1002,
|
|
1445
|
+
EventInstructionStub = 1500,
|
|
1446
|
+
ConstraintMut = 2000,
|
|
1447
|
+
ConstraintHasOne = 2001,
|
|
1448
|
+
ConstraintSigner = 2002,
|
|
1449
|
+
ConstraintRaw = 2003,
|
|
1450
|
+
ConstraintOwner = 2004,
|
|
1451
|
+
ConstraintRentExempt = 2005,
|
|
1452
|
+
ConstraintSeeds = 2006,
|
|
1453
|
+
ConstraintExecutable = 2007,
|
|
1454
|
+
ConstraintState = 2008,
|
|
1455
|
+
ConstraintAssociated = 2009,
|
|
1456
|
+
ConstraintAssociatedInit = 2010,
|
|
1457
|
+
ConstraintClose = 2011,
|
|
1458
|
+
ConstraintAddress = 2012,
|
|
1459
|
+
ConstraintZero = 2013,
|
|
1460
|
+
ConstraintTokenMint = 2014,
|
|
1461
|
+
ConstraintTokenOwner = 2015,
|
|
1462
|
+
ConstraintMintMintAuthority = 2016,
|
|
1463
|
+
ConstraintMintFreezeAuthority = 2017,
|
|
1464
|
+
ConstraintMintDecimals = 2018,
|
|
1465
|
+
ConstraintSpace = 2019,
|
|
1466
|
+
ConstraintAccountIsNone = 2020,
|
|
1467
|
+
ConstraintTokenTokenProgram = 2021,
|
|
1468
|
+
ConstraintMintTokenProgram = 2022,
|
|
1469
|
+
ConstraintAssociatedTokenTokenProgram = 2023,
|
|
1470
|
+
ConstraintMintGroupPointerExtension = 2024,
|
|
1471
|
+
ConstraintMintGroupPointerExtensionAuthority = 2025,
|
|
1472
|
+
ConstraintMintGroupPointerExtensionGroupAddress = 2026,
|
|
1473
|
+
ConstraintMintGroupMemberPointerExtension = 2027,
|
|
1474
|
+
ConstraintMintGroupMemberPointerExtensionAuthority = 2028,
|
|
1475
|
+
ConstraintMintGroupMemberPointerExtensionMemberAddress = 2029,
|
|
1476
|
+
ConstraintMintMetadataPointerExtension = 2030,
|
|
1477
|
+
ConstraintMintMetadataPointerExtensionAuthority = 2031,
|
|
1478
|
+
ConstraintMintMetadataPointerExtensionMetadataAddress = 2032,
|
|
1479
|
+
ConstraintMintCloseAuthorityExtension = 2033,
|
|
1480
|
+
ConstraintMintCloseAuthorityExtensionAuthority = 2034,
|
|
1481
|
+
ConstraintMintPermanentDelegateExtension = 2035,
|
|
1482
|
+
ConstraintMintPermanentDelegateExtensionDelegate = 2036,
|
|
1483
|
+
ConstraintMintTransferHookExtension = 2037,
|
|
1484
|
+
ConstraintMintTransferHookExtensionAuthority = 2038,
|
|
1485
|
+
ConstraintMintTransferHookExtensionProgramId = 2039,
|
|
1486
|
+
RequireViolated = 2500,
|
|
1487
|
+
RequireEqViolated = 2501,
|
|
1488
|
+
RequireKeysEqViolated = 2502,
|
|
1489
|
+
RequireNeqViolated = 2503,
|
|
1490
|
+
RequireKeysNeqViolated = 2504,
|
|
1491
|
+
RequireGtViolated = 2505,
|
|
1492
|
+
RequireGteViolated = 2506,
|
|
1493
|
+
AccountDiscriminatorAlreadySet = 3000,
|
|
1494
|
+
AccountDiscriminatorNotFound = 3001,
|
|
1495
|
+
AccountDiscriminatorMismatch = 3002,
|
|
1496
|
+
AccountDidNotDeserialize = 3003,
|
|
1497
|
+
AccountDidNotSerialize = 3004,
|
|
1498
|
+
AccountNotEnoughKeys = 3005,
|
|
1499
|
+
AccountNotMutable = 3006,
|
|
1500
|
+
AccountOwnedByWrongProgram = 3007,
|
|
1501
|
+
InvalidProgramId = 3008,
|
|
1502
|
+
InvalidProgramExecutable = 3009,
|
|
1503
|
+
AccountNotSigner = 3010,
|
|
1504
|
+
AccountNotSystemOwned = 3011,
|
|
1505
|
+
AccountNotInitialized = 3012,
|
|
1506
|
+
AccountNotProgramData = 3013,
|
|
1507
|
+
AccountNotAssociatedTokenAccount = 3014,
|
|
1508
|
+
AccountSysvarMismatch = 3015,
|
|
1509
|
+
AccountReallocExceedsLimit = 3016,
|
|
1510
|
+
AccountDuplicateReallocs = 3017,
|
|
1511
|
+
DeclaredProgramIdMismatch = 4100,
|
|
1512
|
+
TryingToInitPayerAsProgramAccount = 4101,
|
|
1513
|
+
InvalidNumericConversion = 4102,
|
|
1514
|
+
Deprecated = 5000,
|
|
1515
|
+
}
|
|
1516
|
+
export declare function require(condition: boolean, error: string | AnchorError): asserts condition;
|
|
1386
1517
|
|
|
1387
1518
|
/** Write a string, integer or pubkey to the program log. */
|
|
1388
1519
|
export declare function log(value: string | bigint | Pubkey | Hash): void;
|
package/package.json
CHANGED
package/runtime/typesol_rt.h
CHANGED
|
@@ -1105,48 +1105,124 @@ static inline uint64_t ts_alloc(uint64_t n, uint8_t **out) {
|
|
|
1105
1105
|
static uint64_t ts_base58_32(const uint8_t *in, TS_Str *out) {
|
|
1106
1106
|
static const char digits[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1107
1107
|
const uint64_t D = 656356768ULL; /* 58^5 */
|
|
1108
|
-
|
|
1109
|
-
for (int i = 0; i < 8; i++)
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
uint64_t
|
|
1108
|
+
uint64_t limb[8];
|
|
1109
|
+
for (int i = 0; i < 8; i++) limb[i] = __builtin_bswap32(*(const ts_u32_unaligned *)(in + 4 * i)); /* big-endian words */
|
|
1110
|
+
/* Digits are written as characters from the end of `buf`, five per pass; 32 bytes need at most 44. */
|
|
1111
|
+
char buf[50];
|
|
1112
|
+
uint64_t pos = sizeof(buf);
|
|
1113
1113
|
int start = 0;
|
|
1114
1114
|
while (start < 8 && limb[start] == 0) start++;
|
|
1115
1115
|
while (start < 8) {
|
|
1116
1116
|
uint64_t rem = 0;
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1117
|
+
/* One long-division pass from the first nonzero limb, unrolled. */
|
|
1118
|
+
#define TS_B58_STEP(i) \
|
|
1119
|
+
{ \
|
|
1120
|
+
uint64_t cur = (rem << 32) | limb[i]; \
|
|
1121
|
+
uint64_t q = cur / D; \
|
|
1122
|
+
rem = cur - q * D; \
|
|
1123
|
+
limb[i] = q; \
|
|
1124
|
+
}
|
|
1125
|
+
switch (start) {
|
|
1126
|
+
case 0: TS_B58_STEP(0) /* fallthrough */
|
|
1127
|
+
case 1: TS_B58_STEP(1) /* fallthrough */
|
|
1128
|
+
case 2: TS_B58_STEP(2) /* fallthrough */
|
|
1129
|
+
case 3: TS_B58_STEP(3) /* fallthrough */
|
|
1130
|
+
case 4: TS_B58_STEP(4) /* fallthrough */
|
|
1131
|
+
case 5: TS_B58_STEP(5) /* fallthrough */
|
|
1132
|
+
case 6: TS_B58_STEP(6) /* fallthrough */
|
|
1133
|
+
default: TS_B58_STEP(7)
|
|
1122
1134
|
}
|
|
1135
|
+
#undef TS_B58_STEP
|
|
1123
1136
|
while (start < 8 && limb[start] == 0) start++;
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1137
|
+
uint64_t q;
|
|
1138
|
+
q = rem / 58; buf[--pos] = digits[rem - q * 58]; rem = q;
|
|
1139
|
+
q = rem / 58; buf[--pos] = digits[rem - q * 58]; rem = q;
|
|
1140
|
+
q = rem / 58; buf[--pos] = digits[rem - q * 58]; rem = q;
|
|
1141
|
+
q = rem / 58; buf[--pos] = digits[rem - q * 58]; rem = q;
|
|
1142
|
+
buf[--pos] = digits[rem];
|
|
1129
1143
|
}
|
|
1130
|
-
|
|
1144
|
+
/* Leading zero digits of the number are not printed; each leading zero byte is a '1'. */
|
|
1145
|
+
while (pos < sizeof(buf) && buf[pos] == '1') pos++;
|
|
1131
1146
|
uint64_t zeros = 0;
|
|
1132
1147
|
while (zeros < 32 && in[zeros] == 0) zeros++;
|
|
1148
|
+
uint64_t len = sizeof(buf) - pos;
|
|
1133
1149
|
uint8_t *p;
|
|
1134
1150
|
TS_TRY(ts_alloc(zeros + len, &p));
|
|
1135
1151
|
for (uint64_t i = 0; i < zeros; i++) p[i] = '1';
|
|
1136
|
-
|
|
1152
|
+
ts_memcpy(p + zeros, buf + pos, len);
|
|
1137
1153
|
*out = ts_str(p, zeros + len);
|
|
1138
1154
|
return SUCCESS;
|
|
1139
1155
|
}
|
|
1140
1156
|
|
|
1141
|
-
|
|
1157
|
+
/*
|
|
1158
|
+
* `s += …` on a local string: the pieces are copied right after `s` when it ends where the heap's free space starts
|
|
1159
|
+
* (it was the last thing allocated), and `s` grows in place; otherwise `s` and the pieces are joined into a new string.
|
|
1160
|
+
* Other views of the old `s` keep their length, so they still read the same bytes. The bytes written are past the
|
|
1161
|
+
* heap's top, where no view can point (ts_alloc is the only source of heap pointers and never hands out memory past
|
|
1162
|
+
* the top), so no piece is overwritten while it is copied.
|
|
1163
|
+
*/
|
|
1164
|
+
static uint64_t ts_str_append_parts(TS_Str *s, const TS_Str *parts, uint64_t n) {
|
|
1165
|
+
uint64_t *next = (uint64_t *)HEAP_START_ADDRESS;
|
|
1166
|
+
uint64_t add = 0;
|
|
1167
|
+
for (uint64_t i = 0; i < n; i++) add += parts[i].len;
|
|
1168
|
+
if (*next != 0 && s->len > 0 && (uint64_t)(s->ptr + s->len) == HEAP_START_ADDRESS + *next && *next + add <= TS_HEAP_LENGTH) {
|
|
1169
|
+
for (uint64_t i = 0; i < n; i++) {
|
|
1170
|
+
ts_memcpy((uint8_t *)s->ptr + s->len, parts[i].ptr, parts[i].len);
|
|
1171
|
+
s->len += parts[i].len;
|
|
1172
|
+
}
|
|
1173
|
+
*next += add;
|
|
1174
|
+
return SUCCESS;
|
|
1175
|
+
}
|
|
1142
1176
|
uint8_t *p;
|
|
1143
|
-
TS_TRY(ts_alloc(
|
|
1144
|
-
ts_memcpy(p,
|
|
1145
|
-
|
|
1146
|
-
|
|
1177
|
+
TS_TRY(ts_alloc(s->len + add, &p));
|
|
1178
|
+
ts_memcpy(p, s->ptr, s->len);
|
|
1179
|
+
uint64_t at = s->len;
|
|
1180
|
+
for (uint64_t i = 0; i < n; i++) {
|
|
1181
|
+
ts_memcpy(p + at, parts[i].ptr, parts[i].len);
|
|
1182
|
+
at += parts[i].len;
|
|
1183
|
+
}
|
|
1184
|
+
*s = ts_str(p, at);
|
|
1147
1185
|
return SUCCESS;
|
|
1148
1186
|
}
|
|
1149
1187
|
|
|
1188
|
+
/* `s += b` (and `a + b`, with `s` a copy of `a`): the one-piece case of ts_str_append_parts. */
|
|
1189
|
+
static uint64_t ts_str_append(TS_Str *s, TS_Str b) {
|
|
1190
|
+
uint64_t *next = (uint64_t *)HEAP_START_ADDRESS;
|
|
1191
|
+
if (*next != 0 && s->len > 0 && (uint64_t)(s->ptr + s->len) == HEAP_START_ADDRESS + *next && *next + b.len <= TS_HEAP_LENGTH) {
|
|
1192
|
+
ts_memcpy((uint8_t *)s->ptr + s->len, b.ptr, b.len);
|
|
1193
|
+
*next += b.len;
|
|
1194
|
+
s->len += b.len;
|
|
1195
|
+
return SUCCESS;
|
|
1196
|
+
}
|
|
1197
|
+
uint8_t *p;
|
|
1198
|
+
TS_TRY(ts_alloc(s->len + b.len, &p));
|
|
1199
|
+
ts_memcpy(p, s->ptr, s->len);
|
|
1200
|
+
ts_memcpy(p + s->len, b.ptr, b.len);
|
|
1201
|
+
*s = ts_str(p, s->len + b.len);
|
|
1202
|
+
return SUCCESS;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/* The pieces of a template literal, joined with one heap allocation and one copy each. */
|
|
1206
|
+
static uint64_t ts_str_join(const TS_Str *parts, uint64_t n, TS_Str *out) {
|
|
1207
|
+
uint64_t total = 0;
|
|
1208
|
+
for (uint64_t i = 0; i < n; i++) total += parts[i].len;
|
|
1209
|
+
uint8_t *p;
|
|
1210
|
+
TS_TRY(ts_alloc(total, &p));
|
|
1211
|
+
uint64_t at = 0;
|
|
1212
|
+
for (uint64_t i = 0; i < n; i++) {
|
|
1213
|
+
ts_memcpy(p + at, parts[i].ptr, parts[i].len);
|
|
1214
|
+
at += parts[i].len;
|
|
1215
|
+
}
|
|
1216
|
+
*out = ts_str(p, total);
|
|
1217
|
+
return SUCCESS;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/* `a + b`: a new string (`a` grows in place when it is the newest heap string; its own view keeps its length). */
|
|
1221
|
+
static inline uint64_t ts_str_concat(TS_Str a, TS_Str b, TS_Str *out) {
|
|
1222
|
+
*out = a;
|
|
1223
|
+
return ts_str_append(out, b);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1150
1226
|
/* Decimal text of an integer, on the heap. */
|
|
1151
1227
|
/* Copy a string's bytes to the heap (a stable value, independent of where the view pointed). */
|
|
1152
1228
|
static inline uint64_t ts_str_copy(TS_Str a, TS_Str *out) {
|