@typesol/cli 0.23.0 → 0.24.1
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 +77 -2
- package/README.md +18 -15
- package/SECURITY.md +1 -1
- package/dist/clientgen.js +222 -61
- package/dist/compiler/analyze.js +132 -20
- package/dist/compiler/emit/base.d.ts +27 -4
- package/dist/compiler/emit/base.js +68 -15
- package/dist/compiler/emit/calls.d.ts +7 -0
- package/dist/compiler/emit/calls.js +99 -9
- package/dist/compiler/emit/cpi.d.ts +33 -1
- package/dist/compiler/emit/cpi.js +353 -110
- package/dist/compiler/emit/expressions.d.ts +9 -1
- package/dist/compiler/emit/expressions.js +16 -12
- package/dist/compiler/emit/function.js +3 -1
- package/dist/compiler/emit/shared.d.ts +11 -1
- package/dist/compiler/emit/statements.d.ts +4 -2
- package/dist/compiler/emit/statements.js +128 -27
- package/dist/compiler/emit/storage.d.ts +6 -2
- package/dist/compiler/emit/storage.js +36 -32
- package/dist/compiler/model.d.ts +8 -0
- package/dist/idlpublish.d.ts +6 -1
- package/dist/idlpublish.js +47 -5
- package/dist/portgen.js +1 -1
- package/dist/svmrpc.d.ts +7 -1
- package/dist/svmrpc.js +133 -19
- package/dist/testing.js +23 -3
- package/docs/architecture.md +26 -2
- package/docs/cli.md +2 -2
- package/docs/client.md +13 -10
- package/docs/errors.md +9 -3
- package/docs/language.md +18 -17
- package/docs/mainnet.md +1 -1
- package/docs/migrate-from-anchor.md +8 -9
- package/docs/runtime.md +59 -0
- package/docs/test-your-program.md +6 -2
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +3 -2
- package/lib/typesol.d.ts +120 -21
- package/package.json +1 -1
- package/runtime/typesol_rt.h +106 -28
|
@@ -65,21 +65,22 @@ and error codes. Its notes (on stderr) list what an existing Anchor client would
|
|
|
65
65
|
| `u8` … `u128`, `i8` … `i128`, `bool`, `Pubkey` | `u8` … `u128`, `i8` … `i128`, `boolean`, `Pubkey` |
|
|
66
66
|
| `[T; N]` | `FixedArray<T, N>` |
|
|
67
67
|
| `String` with `#[max_len(n)]` | `FixedString<N>`: a u32 length and N bytes, always N bytes long |
|
|
68
|
-
| `Vec<T>` with `#[max_len(n)]` | `Vec<T>`
|
|
68
|
+
| `Vec<T>` with `#[max_len(n)]` | `Vec<T>` (any number, declared anywhere, stored after the fixed-size fields; it grows with `push(v, payer)`), or `FixedVec<T, n>`: a u32 length and room for n, always 4 + n × size bytes |
|
|
69
69
|
| `Option<T>` | `Option<T>`: fixed size (1 + size of T) |
|
|
70
70
|
| `#[derive(AnchorSerialize…)] enum` | `export enum` (unit variants) or a data enum `type A = { kind: "X"; … } \| …` |
|
|
71
71
|
|
|
72
72
|
TypeSOL keeps account data at a fixed layout by default, so three Anchor layouts differ: a Borsh `String` is
|
|
73
73
|
variable-length (TypeSOL uses `FixedString<N>`), a Borsh `Option` takes 1 byte when empty (TypeSOL always reserves the
|
|
74
|
-
value), and
|
|
75
|
-
always takes its full capacity, where Anchor packs the elements and moves the
|
|
74
|
+
value), and an Anchor `Vec` stays where it is declared (TypeSOL stores growable Vecs after the fixed-size fields; a
|
|
75
|
+
`FixedVec<T, N>` keeps its place but always takes its full capacity, where Anchor packs the elements and moves the
|
|
76
|
+
fields after them).
|
|
76
77
|
|
|
77
78
|
To keep an Anchor program's existing accounts, mark the struct `/** @layout borsh */`: it is then stored exactly as
|
|
78
79
|
Anchor stores it (`FixedString<N>` / `FixedVec<T, N>` / `Option<T>` become Borsh String / Vec / Option, N being the
|
|
79
80
|
`#[max_len]`), its IDL type is Anchor's, and the upgraded program reads and writes the accounts in place. `typesol port`
|
|
80
81
|
adds it for you. It costs some compute (the account is unpacked and packed in every instruction that uses it) but stays
|
|
81
|
-
several times cheaper than Anchor.
|
|
82
|
-
than an in-place upgrade
|
|
82
|
+
several times cheaper than Anchor. Without it, accounts that use these types need a migration (or a new program) rather
|
|
83
|
+
than an in-place upgrade. Everything else, including instruction arguments (`String`, `Option`, `Vec` are Borsh there),
|
|
83
84
|
matches byte for byte. A `@layout borsh` struct passed as a whole argument, returned or emitted is Borsh too, so an
|
|
84
85
|
Anchor instruction that takes or returns a struct with `String` / `Vec` / `Option` fields keeps its client bytes.
|
|
85
86
|
|
|
@@ -91,9 +92,8 @@ Anchor instruction that takes or returns a struct with `String` / `Vec` / `Optio
|
|
|
91
92
|
| `-> Result<u64>` / a struct | `: u64` / a struct: sent as return data, listed in the IDL |
|
|
92
93
|
| `#[error_code] enum MyError { #[msg("…")] X }` | `export enum MyError { X = "…" }` (codes from 6000, in order) |
|
|
93
94
|
| `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)
|
|
95
|
+
| `require_keys_eq!(a, b)`, `err!(ErrorCode::AccountDidNotDeserialize)` (Anchor's own codes) | `require(a === b, AnchorError.RequireKeysEqViolated)` (or your own error), `require(false, AnchorError.AccountDidNotDeserialize)` |
|
|
95
96
|
| `init_if_needed` + `extensions::…` on a mint | `mint.createIfNeeded(…, { metadataPointer: {…}, … })`: an existing mint is checked as Anchor checks it (2024–2039) |
|
|
96
|
-
| `require_keys_eq!(a, b)` | `require(a === b, …)` |
|
|
97
97
|
| `a.checked_add(b).ok_or(…)?` | `a + b` (every operation is checked; overflow aborts with 5100) |
|
|
98
98
|
| `emit!(Ev { … })` / `emit_cpi!` | `emit<Ev>({ … })` / `emitCpi<Ev>({ … }, eventAuthority, self)` |
|
|
99
99
|
| `msg!("…")` | `log("…")` |
|
|
@@ -105,10 +105,9 @@ Anchor instruction that takes or returns a struct with `String` / `Vec` / `Optio
|
|
|
105
105
|
| `declare_program!(other)` / CPI to another Anchor program | `typesol cpi other.json` generates typed helpers |
|
|
106
106
|
| `for x in v.iter()` | `for (const x of v)` |
|
|
107
107
|
| `v.iter().any(\|x\| …)`, `.all`, `.position`, `.find`, `.fold(init, …)`, `.contains(&x)` | `v.some((x) => …)`, `every`, `findIndex`, `find`, `reduce((acc, x) => …, init)`, `includes(x)` |
|
|
108
|
-
| a closure `\|x\| x + offset` | `const f = (x: u64): u64 => x + offset` (it may read and write the function's variables) |
|
|
108
|
+
| a closure `\|x\| x + offset`, `let fee = \|x: u64\| x / 100;` | `const f = (x: u64): u64 => x + offset` (it may read and write the function's variables), `const fee = (x: u64): u64 => x / 100n;` |
|
|
109
109
|
| `x.unwrap_or(d)` | `x ?? d` |
|
|
110
110
|
| `fn max<T: PartialOrd>(a: T, b: T) -> T` | `function max<T extends bigint>(a: T, b: T): T` (one copy per type used) |
|
|
111
|
-
| `let fee = \|x: u64\| x / 100;` (no captures) | `const fee = (x: u64): u64 => x / 100n;` |
|
|
112
111
|
|
|
113
112
|
## Tooling
|
|
114
113
|
|
package/docs/runtime.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# The C runtime
|
|
2
|
+
|
|
3
|
+
`runtime/typesol_rt.h` is included by every generated program. It holds what the emitted C calls: error codes, the
|
|
4
|
+
entry point's input parser, account checks, checked arithmetic, Borsh helpers, CPIs, sysvars, strings and the heap.
|
|
5
|
+
This page is for people changing it; programs never call it directly.
|
|
6
|
+
|
|
7
|
+
## Conventions
|
|
8
|
+
|
|
9
|
+
- **Header-only, `static inline`.** Each program compiles the header with its own code, and `--gc-sections` drops
|
|
10
|
+
what it does not use. `always_inline` marks the few helpers that must disappear into their caller (word loads and
|
|
11
|
+
stores, `ts_memcpy`, checked add/subtract); `noinline` marks the ones that should exist once per program because
|
|
12
|
+
many call sites would otherwise each grow the binary (`ts_fail`, the rent fallback `ts_rent_slow`, PDA search).
|
|
13
|
+
- **Errors are return values.** A function that can fail returns `uint64_t`: `SUCCESS`, or an error code. Fail with
|
|
14
|
+
`TS_FAIL(code, "TypeSOL: …")` (logs the message, then returns the code) and propagate with `TS_TRY(call)`.
|
|
15
|
+
Nothing aborts: the instruction returns the code, and the runtime reports it as `Custom(code)`.
|
|
16
|
+
- **Error codes follow Anchor.** Where Anchor has a framework error with the same meaning, its number is used (2006
|
|
17
|
+
ConstraintSeeds, 3003 AccountDidNotDeserialize, …), so Anchor and Codama clients name failures correctly.
|
|
18
|
+
TypeSOL-only conditions are 5100–5199; user errors start at 6000. Every code is listed in [errors.md](errors.md).
|
|
19
|
+
- **No byte loops from the SDK.** The SDK's `sol_memcpy`, `sol_memcmp` and `SolPubkey_same` move one byte per
|
|
20
|
+
iteration. The runtime uses `ts_ld64` / `ts_st64` (unaligned 8-byte loads and stores, which SBF allows) in
|
|
21
|
+
`ts_memcpy`, `ts_memzero`, `ts_mem_eq`, `ts_key_eq` and `ts_memmove`.
|
|
22
|
+
- **Same results as the reference.** Fast paths must give bit-identical results to what they replace: `ts_add_overflow_u64`
|
|
23
|
+
matches `__builtin_add_overflow`, the rent fast path and `ts_rent_slow` match the double computation, PDA search
|
|
24
|
+
matches `sol_try_find_program_address`. A change here needs a test against the reference, not only against itself.
|
|
25
|
+
|
|
26
|
+
## Sections
|
|
27
|
+
|
|
28
|
+
In file order:
|
|
29
|
+
|
|
30
|
+
| Section | What it provides |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Error codes, `TS_FAIL`, `TS_TRY` | The codes above and the two macros |
|
|
33
|
+
| Word-wise memory helpers | `ts_ld64`, `ts_st64`, `ts_memcpy`, `ts_memmove`, `ts_memzero`, `ts_mem_eq`; checked u64 add/subtract/multiply |
|
|
34
|
+
| Well-known program ids | System, Token, Token-2022, ATA, Metaplex and the other programs the language names |
|
|
35
|
+
| Strings | `TS_Str` views (pointer, length), UTF-8 validation, comparison, `FixedString<N>` storage |
|
|
36
|
+
| Entry point input | `TsAccount` (the loader's serialized account, read in place), `TsParams`, `ts_deserialize` |
|
|
37
|
+
| Account checks | owner, discriminator, signer, writable, executable, address, size, uniqueness (`ts_check_*`), growable Vec lengths |
|
|
38
|
+
| Little-endian IO | `ts_read_le` and friends for argument decoding and Borsh |
|
|
39
|
+
| Sysvars | clock, rent (`ts_rent`, `ts_minimum_balance_of`, `ts_minimum_balance`), program data |
|
|
40
|
+
| PDA | `ts_pda_hash`, `ts_create_pda_address`, `ts_find_pda` (canonical bump), `ts_require_pda` |
|
|
41
|
+
| System program CPIs | create, assign, allocate, transfer, `ts_create_pda`, `ts_resize` / `ts_realloc`, close |
|
|
42
|
+
| Token-2022 extensions | TLV walking for the extensions the language reads |
|
|
43
|
+
| Metaplex Core and Bubblegum | account readers (`coreAssetOwner`, …) |
|
|
44
|
+
| SPL Token / Token-2022 CPIs | transfer, mint, burn, approve, close, freeze and their checked forms |
|
|
45
|
+
| Return data | `sol_set_return_data` wrappers |
|
|
46
|
+
| Crypto | sha256, keccak256, ed25519 / secp256k1 verification helpers, Merkle proofs |
|
|
47
|
+
| Heap | `ts_alloc`, a bump allocator (32 KiB by default, `HEAP_SIZE_KIB` up to 256), string building (`ts_str_join`, `ts_str_append`, number and base58 text) |
|
|
48
|
+
| Oracles | Pyth price updates, Switchboard |
|
|
49
|
+
| Associated token accounts, token metadata, transfer hooks | ATA derivation and creation, Token Metadata CPIs, transfer-hook extra accounts |
|
|
50
|
+
|
|
51
|
+
## Changing it
|
|
52
|
+
|
|
53
|
+
- Every runtime change can move compute units or binary size. Measure it before and after with
|
|
54
|
+
[typesol/benchmarks](https://github.com/typesol/benchmarks) pointed at your checkout, and put the difference in
|
|
55
|
+
the pull request (see CONTRIBUTING).
|
|
56
|
+
- A change that alters behavior (a result, an error code, a log line, account bytes) is a language change and goes
|
|
57
|
+
in the CHANGELOG as one. One that only changes cost says so: "same results".
|
|
58
|
+
- New error codes: add the `#define` here, the row in [errors.md](errors.md) and the name in the compiler's runtime
|
|
59
|
+
error list (`TYPESOL_ERRORS` in `src/clientgen.ts`), so clients name it (a test checks every code has a name).
|
|
@@ -23,6 +23,9 @@ it("counts", async () => {
|
|
|
23
23
|
|
|
24
24
|
## Instructions
|
|
25
25
|
|
|
26
|
+
`t.pda(...seeds)` derives this program's PDA for the seeds; `t.pdaWithBump(...seeds)` gives `[address, bump]`, for an
|
|
27
|
+
instruction that takes the bump as an argument.
|
|
28
|
+
|
|
26
29
|
`t.ix(name, args, accounts, remaining?)` builds one instruction.
|
|
27
30
|
|
|
28
31
|
- **Accounts by name**, in camelCase or snake_case. Their signer and writable flags come from the IDL, and accounts
|
|
@@ -31,7 +34,8 @@ it("counts", async () => {
|
|
|
31
34
|
signs, an address otherwise, and `null` to leave an optional account out.
|
|
32
35
|
- **Arguments as plain values:** bigints or numbers for integers, address strings for Pubkeys, `null` for an empty
|
|
33
36
|
Option, objects for structs (camelCase or snake_case fields), arrays for Vecs.
|
|
34
|
-
- **Remaining accounts:** `[{ address, writable?, signer? }]
|
|
37
|
+
- **Remaining accounts:** `[{ address, writable?, signer? }]`, where `signer` is the account's `KeyPairSigner` (it then
|
|
38
|
+
signs as a signer account; `writable` defaults to false).
|
|
35
39
|
|
|
36
40
|
`t.send(payer, ...ixs)` sends them in one transaction; keypairs given as accounts sign too.
|
|
37
41
|
|
|
@@ -44,7 +48,7 @@ it("counts", async () => {
|
|
|
44
48
|
| `t.account(type, address)` | the account decoded by its IDL type (bigints, address strings), or `null` once closed. TypeSOL fixed layouts come back as their IDL structs: a `FixedString<N>` as `{ len, bytes }`, a `FixedVec<T, N>` as `{ len, items }` (the generated client gives strings and arrays) |
|
|
45
49
|
| `t.returned(instruction, res)` | the instruction's return data: an integer, boolean, Pubkey or struct |
|
|
46
50
|
| `t.balance(address)` | lamports |
|
|
47
|
-
| `t.connection` | `{ rpc, rpcSubscriptions }` answered by this LiteSVM, for the generated client: `counter.sendInstructions(t.connection, alice, [ix])`, `counter.accounts.Counter.fetch(t.connection.rpc, address)`, views, `resolve`, `watch` and `watchEvents` |
|
|
51
|
+
| `t.connection` | `{ rpc, rpcSubscriptions }` answered by this LiteSVM, for the generated client: `counter.sendInstructions(t.connection, alice, [ix])`, `counter.accounts.Counter.fetch(t.connection.rpc, address)`, views, `resolve`, `watch` and `watchEvents` (as on a cluster: `watch` yields when a later transaction changes the account, not at once; `watchEvents` gets transactions sent after it subscribed, failed ones included in the stream and skipped by the client) |
|
|
48
52
|
|
|
49
53
|
`t.svm`, `t.programId`, `t.idl` and `t.coder` are there for anything else, e.g. writing a mint straight into the VM
|
|
50
54
|
with `t.svm.setAccount` (see the escrow template). `programHarness(svm, programId, idl)` gives the same helpers for a
|
package/docs/testing.md
CHANGED
|
@@ -8,7 +8,7 @@ run prints its seed; reproduce with `FUZZ_SEED=<n>`, widen with `FUZZ_RUNS` / `F
|
|
|
8
8
|
| `test/fuzz-arith.test.ts` | BigInt | `+ - * / % **`, `& \| ^ << >> ~`, unary minus, comparisons and casts at every width u8…u128 / i8…i128, plus `mulDiv`, `mulDivCeil`, `isqrt`, `min`, `max`, return the same value or abort with the same code (5100/5101/4102), for edge and random inputs |
|
|
9
9
|
| `test/fuzz-state.test.ts` | TypeScript state model | random honest + hostile actions on vault and escrow: every outcome as predicted, balances equal the model, SOL conserved up to fees, token supply exactly conserved; random filings/decisions/resets on ledgerbook: Vec contents, account size and rent exemption equal the model |
|
|
10
10
|
| `test/differential.test.ts` | Anchor twins in `bench/anchor` | identical transactions to TypeSOL and Anchor programs: same outcomes, same account contents, same wallet and token balances after every step |
|
|
11
|
-
| `test/ports-*.test.ts` | the Anchor programs of solana-developers/program-examples and their tests | TypeSOL rewrites under `test/ports/` run the originals' flows and failure paths with the same outcomes, error codes and balances
|
|
11
|
+
| `test/ports-*.test.ts` | the Anchor programs of solana-developers/program-examples and their tests | TypeSOL rewrites under `test/ports/` run the originals' flows and failure paths with the same outcomes, error codes and balances ([test/ports/README.md](../test/ports/README.md)) |
|
|
12
12
|
|
|
13
13
|
Compute-unit and binary-size comparisons with Anchor, native Rust and Pinocchio are in the README
|
|
14
14
|
([Numbers](../README.md#numbers)).
|
package/docs/troubleshooting.md
CHANGED
|
@@ -27,8 +27,9 @@ or declare `const HEAP_SIZE_KIB = 64n` and add a ComputeBudget `requestHeapFrame
|
|
|
27
27
|
`solana-test-validator` (Agave 3.1 included) rejects a System instruction that the current Program Metadata program
|
|
28
28
|
uses. Publishing works on mainnet-beta. The error shows the failing transaction's last log lines.
|
|
29
29
|
|
|
30
|
-
**"instruction X needs N bytes of stack;
|
|
31
|
-
stack frame, and going past it corrupts memory, so the build
|
|
30
|
+
**"instruction X (with the helpers inlined into it) needs N bytes of stack; SBPF v0 gives each function 4096"** — under
|
|
31
|
+
SBPF v0 (the default) every function gets a fixed 4 KiB stack frame, and going past it corrupts memory, so the build
|
|
32
|
+
refuses it. SBPF v1 and v2 (`--sbpf v1` / `v2`) grow frames dynamically, so this limit does not apply there. Large structs and arrays copied into typed
|
|
32
33
|
locals (`const q: Big = acct.big`) are the usual cause: bind them without a type (`const q = acct.big`, a reference
|
|
33
34
|
into the account data) or move the work into helpers. Helpers are often inlined into their instruction, so their
|
|
34
35
|
locals count toward its frame.
|
package/lib/typesol.d.ts
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* Integer types are all `bigint` to TypeScript. The compiler tracks the declared
|
|
9
9
|
* width itself, rejects `number`, and checks every arithmetic operation for
|
|
10
10
|
* overflow at runtime.
|
|
11
|
+
*
|
|
12
|
+
* Integer operators: + - * / % are checked (overflow and division by zero abort). & | ^ work on equal types;
|
|
13
|
+
* << aborts when bits would be lost or the amount is not below the width; >> is arithmetic on signed types;
|
|
14
|
+
* ~ flips the bits within the type's width.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
export type u8 = bigint;
|
|
@@ -27,10 +31,6 @@ export interface Pubkey {
|
|
|
27
31
|
equals(other: Pubkey): boolean;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
/**
|
|
31
|
-
* A fixed-length array stored inline in account data, e.g. `scores: FixedArray<u64, 8>`.
|
|
32
|
-
* Indexes are bounds-checked; out of range aborts the instruction.
|
|
33
|
-
*/
|
|
34
34
|
/**
|
|
35
35
|
* Callback methods of Vec, FixedArray and FixedVec. Each expands into a loop over the elements (as `list.get(i)` gives
|
|
36
36
|
* them: structs by reference) with the arrow written at the call; it may read and write the function's variables.
|
|
@@ -53,13 +53,19 @@ export interface ListMethods<T> {
|
|
|
53
53
|
/**
|
|
54
54
|
* The elements `fn` maps them to (integers, booleans, Pubkeys or enums). Use it right away (another list method,
|
|
55
55
|
* `.length`, `for … of`: one loop, nothing stored) or store it (`const ys = xs.map(…)`: a FixedVec on the stack, as
|
|
56
|
-
* big as `xs`; from a `Vec`, annotate it: `const ys: FixedVec<u64, 16> = …`).
|
|
56
|
+
* big as `xs`; from a `Vec`, annotate it: `const ys: FixedVec<u64, 16> = …`). Stored with a `FixedVec<Struct, N>` or
|
|
57
|
+
* `FixedVec<FixedString<N>, M>` annotation it gives structs (`(o) => ({ qty: o.qty })`) or text. A stored list holds
|
|
58
|
+
* at most 2 KiB; one element more than it holds aborts (5104).
|
|
57
59
|
*/
|
|
58
60
|
map<U>(fn: (x: T, index: u64) => U): FixedVec<U, never>;
|
|
59
61
|
/** The elements `fn` holds for, used or stored like `map`'s; callbacks after it get the index among those. */
|
|
60
62
|
filter(fn: (x: T, index: u64) => boolean): FixedVec<T, never>;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* A fixed-length array stored inline in account data, e.g. `scores: FixedArray<u64, 8>`.
|
|
67
|
+
* Indexes are bounds-checked; out of range aborts the instruction.
|
|
68
|
+
*/
|
|
63
69
|
export interface FixedArray<T, N extends number> extends ListMethods<T> {
|
|
64
70
|
readonly length: u64;
|
|
65
71
|
get(index: bigint): T;
|
|
@@ -82,15 +88,17 @@ export type FixedString<N extends number> = string & { readonly __capacity?: N }
|
|
|
82
88
|
export declare function byteLength(value: string): u64;
|
|
83
89
|
|
|
84
90
|
/**
|
|
85
|
-
* An optional
|
|
91
|
+
* An optional value: `best: Option<u64>` (same as `u64 | null`), also of a struct (`home: Option<Point>`). Compare with `=== null` / `!== null`;
|
|
86
92
|
* TypeScript narrows it, and using an empty option where a value is needed aborts (5108).
|
|
87
93
|
* In account data it takes a fixed 1 + size(T) bytes.
|
|
88
94
|
*/
|
|
89
95
|
export type Option<T> = T | null;
|
|
90
96
|
|
|
91
97
|
/**
|
|
92
|
-
* A growable list. As
|
|
93
|
-
*
|
|
98
|
+
* A growable list. As an account struct field it is stored Borsh-style (u32 length + elements) and the account is
|
|
99
|
+
* resized as it grows. An account may have any number of them, declared anywhere: they are stored after the
|
|
100
|
+
* fixed-size fields, in the order declared (in a `@layout borsh` struct, a Vec must be the last field). As an
|
|
101
|
+
* instruction argument it is a read-only view.
|
|
94
102
|
*/
|
|
95
103
|
export interface Vec<T> extends ListMethods<T> {
|
|
96
104
|
readonly length: u64;
|
|
@@ -108,8 +116,8 @@ export interface Vec<T> extends ListMethods<T> {
|
|
|
108
116
|
|
|
109
117
|
/**
|
|
110
118
|
* A list stored inline with room for N elements: a u32 length, then N slots (Anchor `Vec<T>` with `#[max_len(N)]`, but
|
|
111
|
-
* always 4 + N × size(T) bytes). Unlike `Vec<T>`
|
|
112
|
-
* `push` fails (5104) when it is full. Write a whole one with `fixedVec([a, b])`.
|
|
119
|
+
* always 4 + N × size(T) bytes). Unlike `Vec<T>` its size is fixed: it keeps its place among the fields, can be in
|
|
120
|
+
* nested structs, and `push` takes no payer; `push` fails (5104) when it is full. Write a whole one with `fixedVec([a, b])`.
|
|
113
121
|
*/
|
|
114
122
|
export interface FixedVec<T, N extends number> extends ListMethods<T> {
|
|
115
123
|
readonly length: u64;
|
|
@@ -611,11 +619,6 @@ export interface SelfProgram extends AccountMeta {
|
|
|
611
619
|
*/
|
|
612
620
|
export declare function emitCpi<T>(event: T, eventAuthority: EventAuthority, self: SelfProgram): void;
|
|
613
621
|
|
|
614
|
-
/**
|
|
615
|
-
* Metaplex Core (CoREEN…), address checked. Assets and collections without plugins; `null` leaves an optional
|
|
616
|
-
* account out. Accounts that sign (the new asset/collection, authorities) are keypair Signers, or PDAs of this
|
|
617
|
-
* program covered by `signerSeeds`.
|
|
618
|
-
*/
|
|
619
622
|
/**
|
|
620
623
|
* Who manages a Metaplex Core plugin: `"owner"`, `"updateAuthority"`, `"none"` (nobody: fixed), or an address. Left out,
|
|
621
624
|
* the plugin type's default (the owner for owner-managed plugins, the update authority for the others).
|
|
@@ -650,7 +653,49 @@ export type CorePlugin = (
|
|
|
650
653
|
| { kind: "PermanentFreezeExecute"; frozen: boolean }
|
|
651
654
|
) & { authority?: CorePluginAuthority };
|
|
652
655
|
export type CorePluginKind = CorePlugin["kind"] | "Groups";
|
|
656
|
+
/** When a Core lifecycle event reaches an external plugin, and what the plugin may do about it. */
|
|
657
|
+
export interface CoreLifecycleCheck {
|
|
658
|
+
event: "create" | "transfer" | "burn" | "update" | "execute";
|
|
659
|
+
canListen?: boolean;
|
|
660
|
+
canApprove?: boolean;
|
|
661
|
+
canReject?: boolean;
|
|
662
|
+
}
|
|
663
|
+
/** An account Core passes to an external plugin: one it already has, or an address. */
|
|
664
|
+
export type CoreExtraAccount =
|
|
665
|
+
| { kind: "program" | "collection" | "owner" | "recipient" | "asset"; isSigner?: boolean; isWritable?: boolean }
|
|
666
|
+
| { kind: "address"; address: Pubkey; isSigner?: boolean; isWritable?: boolean };
|
|
667
|
+
/**
|
|
668
|
+
* A Metaplex Core external plugin adapter, written as an object literal: data a third party writes (`AppData`), an
|
|
669
|
+
* account that approves or rejects lifecycle events (`Oracle`), or a program called on them (`LifecycleHook`); the
|
|
670
|
+
* `Linked` forms go on a collection, for each of its assets. `authority` manages the adapter itself.
|
|
671
|
+
*/
|
|
672
|
+
export type CoreExternalPlugin = (
|
|
673
|
+
| { kind: "AppData" | "LinkedAppData"; dataAuthority: CorePluginAuthority; schema?: "binary" | "json" | "msgpack" }
|
|
674
|
+
| {
|
|
675
|
+
kind: "Oracle";
|
|
676
|
+
baseAddress: Pubkey;
|
|
677
|
+
checks: CoreLifecycleCheck[];
|
|
678
|
+
baseAddressConfig?: CoreExtraAccount;
|
|
679
|
+
resultsOffset?: "none" | "anchor" | u64;
|
|
680
|
+
}
|
|
681
|
+
| {
|
|
682
|
+
kind: "LifecycleHook" | "LinkedLifecycleHook";
|
|
683
|
+
hookedProgram: Pubkey;
|
|
684
|
+
checks: CoreLifecycleCheck[];
|
|
685
|
+
extraAccounts?: CoreExtraAccount[];
|
|
686
|
+
dataAuthority?: CorePluginAuthority;
|
|
687
|
+
schema?: "binary" | "json" | "msgpack";
|
|
688
|
+
}
|
|
689
|
+
) & { authority?: CorePluginAuthority };
|
|
690
|
+
/** Which external plugin: an AppData by its data authority, an Oracle by its base address, a LifecycleHook by its program. */
|
|
691
|
+
export type CoreExternalPluginKey =
|
|
692
|
+
{ kind: "AppData" | "LinkedAppData"; dataAuthority: CorePluginAuthority } | { kind: "Oracle" | "LifecycleHook" | "LinkedLifecycleHook"; address: Pubkey };
|
|
653
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Metaplex Core (CoREEN…), address checked. Assets and collections, with plugins and external plugin adapters; `null`
|
|
696
|
+
* leaves an optional account out. Accounts that sign (the new asset/collection, authorities) are keypair Signers, or
|
|
697
|
+
* PDAs of this program covered by `signerSeeds`.
|
|
698
|
+
*/
|
|
654
699
|
export interface MplCoreProgram extends AccountMeta {
|
|
655
700
|
readonly __mpl_core: void;
|
|
656
701
|
/**
|
|
@@ -753,6 +798,61 @@ export interface MplCoreProgram extends AccountMeta {
|
|
|
753
798
|
kind: CorePluginKind,
|
|
754
799
|
signerSeeds?: Seed[] | SignerSeeds,
|
|
755
800
|
): void;
|
|
801
|
+
/** AddExternalPluginAdapterV1, signed by the update authority as `authority` (`null`: the payer). */
|
|
802
|
+
addExternalPlugin(
|
|
803
|
+
asset: AccountMeta,
|
|
804
|
+
collection: AccountMeta | null,
|
|
805
|
+
payer: Mut<Signer>,
|
|
806
|
+
authority: AccountMeta | null,
|
|
807
|
+
systemProgram: SystemProgram,
|
|
808
|
+
plugin: CoreExternalPlugin,
|
|
809
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
810
|
+
): void;
|
|
811
|
+
removeExternalPlugin(
|
|
812
|
+
asset: AccountMeta,
|
|
813
|
+
collection: AccountMeta | null,
|
|
814
|
+
payer: Mut<Signer>,
|
|
815
|
+
authority: AccountMeta | null,
|
|
816
|
+
systemProgram: SystemProgram,
|
|
817
|
+
key: CoreExternalPluginKey,
|
|
818
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
819
|
+
): void;
|
|
820
|
+
/** WriteExternalPluginAdapterDataV1, signed by the plugin's data authority: `data` replaces what it holds (`null` clears it). */
|
|
821
|
+
writeExternalPluginData(
|
|
822
|
+
asset: AccountMeta,
|
|
823
|
+
collection: AccountMeta | null,
|
|
824
|
+
payer: Mut<Signer>,
|
|
825
|
+
authority: AccountMeta | null,
|
|
826
|
+
systemProgram: SystemProgram,
|
|
827
|
+
key: CoreExternalPluginKey,
|
|
828
|
+
data: string | Vec<u8> | null,
|
|
829
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
830
|
+
): void;
|
|
831
|
+
addCollectionExternalPlugin(
|
|
832
|
+
collection: AccountMeta,
|
|
833
|
+
payer: Mut<Signer>,
|
|
834
|
+
authority: AccountMeta | null,
|
|
835
|
+
systemProgram: SystemProgram,
|
|
836
|
+
plugin: CoreExternalPlugin,
|
|
837
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
838
|
+
): void;
|
|
839
|
+
removeCollectionExternalPlugin(
|
|
840
|
+
collection: AccountMeta,
|
|
841
|
+
payer: Mut<Signer>,
|
|
842
|
+
authority: AccountMeta | null,
|
|
843
|
+
systemProgram: SystemProgram,
|
|
844
|
+
key: CoreExternalPluginKey,
|
|
845
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
846
|
+
): void;
|
|
847
|
+
writeCollectionExternalPluginData(
|
|
848
|
+
collection: AccountMeta,
|
|
849
|
+
payer: Mut<Signer>,
|
|
850
|
+
authority: AccountMeta | null,
|
|
851
|
+
systemProgram: SystemProgram,
|
|
852
|
+
key: CoreExternalPluginKey,
|
|
853
|
+
data: string | Vec<u8> | null,
|
|
854
|
+
signerSeeds?: Seed[] | SignerSeeds,
|
|
855
|
+
): void;
|
|
756
856
|
/** TransferV1, signed by the owner (or a delegate) as `authority` (`null`: the payer). */
|
|
757
857
|
transferAsset(
|
|
758
858
|
asset: AccountMeta,
|
|
@@ -1437,7 +1537,6 @@ export declare function invoke(
|
|
|
1437
1537
|
signerSeeds?: Seed[] | SignerSeeds,
|
|
1438
1538
|
): void;
|
|
1439
1539
|
|
|
1440
|
-
/** Abort with the given error unless `condition` holds. Use a string enum member or a string literal. */
|
|
1441
1540
|
/**
|
|
1442
1541
|
* Anchor's own error codes (anchor-lang `ErrorCode`): `require(condition, AnchorError.AccountDidNotDeserialize)` fails
|
|
1443
1542
|
* with that code, as `require!(condition, ErrorCode::AccountDidNotDeserialize)` does. Not listed in the IDL's errors.
|
|
@@ -1521,6 +1620,11 @@ export declare enum AnchorError {
|
|
|
1521
1620
|
InvalidNumericConversion = 4102,
|
|
1522
1621
|
Deprecated = 5000,
|
|
1523
1622
|
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Abort with the given error unless `condition` holds. The error is a member of an exported string enum, any
|
|
1625
|
+
* `AnchorError` member, or a string literal: each distinct literal becomes an error named `RequireFailed<N>` with code
|
|
1626
|
+
* 6000 + N, numbered after the program's error enums and listed in the IDL.
|
|
1627
|
+
*/
|
|
1524
1628
|
export declare function require(condition: boolean, error: string | AnchorError): asserts condition;
|
|
1525
1629
|
|
|
1526
1630
|
/** Write a string, integer or pubkey to the program log. */
|
|
@@ -1562,11 +1666,6 @@ export declare function i16(value: bigint): i16;
|
|
|
1562
1666
|
export declare function i32(value: bigint): i32;
|
|
1563
1667
|
export declare function i128(value: bigint): i128;
|
|
1564
1668
|
|
|
1565
|
-
/**
|
|
1566
|
-
* Integer operators: + - * / % are checked (overflow and division by zero abort). & | ^ work on equal types;
|
|
1567
|
-
* << aborts when bits would be lost or the amount is not below the width; >> is arithmetic on signed types;
|
|
1568
|
-
* ~ flips the bits within the type's width.
|
|
1569
|
-
*/
|
|
1570
1669
|
/** floor(a * b / c) with a double-width intermediate (all u64, or all u128); aborts if c is 0 or the result does not fit. */
|
|
1571
1670
|
export declare function mulDiv(a: u64, b: u64, c: u64): u64;
|
|
1572
1671
|
/** ceil(a * b / c), like mulDiv. */
|
package/package.json
CHANGED
package/runtime/typesol_rt.h
CHANGED
|
@@ -62,11 +62,12 @@
|
|
|
62
62
|
#define TS_ERR_NO_RETURN 5114
|
|
63
63
|
#define TS_ERR_ACCOUNT_MISSING 5115
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
/* Every failure logs through this one sol_log_ call: each syscall call site costs a 16-byte relocation in the binary. */
|
|
66
|
+
static __attribute__((noinline)) uint64_t ts_fail(uint64_t code, const char *msg, uint64_t len) {
|
|
67
|
+
sol_log_(msg, len);
|
|
68
|
+
return code;
|
|
69
|
+
}
|
|
70
|
+
#define TS_FAIL(code, msg) return ts_fail((code), (msg), sol_strlen(msg))
|
|
70
71
|
|
|
71
72
|
#define TS_TRY(expr) \
|
|
72
73
|
do { \
|
|
@@ -369,9 +370,19 @@ static inline uint64_t ts_create_pda_address(const SolSignerSeed *seeds, int n,
|
|
|
369
370
|
/* sol_try_find_program_address: the canonical (highest) bump. */
|
|
370
371
|
static inline uint64_t ts_find_pda(const SolSignerSeed *seeds, int n, const SolPubkey *program, SolPubkey *out, uint8_t *bump_out) {
|
|
371
372
|
if (!ts_pda_seeds_ok(seeds, n, TS_MAX_PDA_SEEDS - 1)) return 1; /* not SUCCESS: the PDA syscalls fail the same inputs */
|
|
373
|
+
/* The seed list is built once; each attempt only changes the bump byte it points at. */
|
|
374
|
+
SolBytes parts[TS_MAX_PDA_SEEDS + 2];
|
|
375
|
+
int k = 0;
|
|
376
|
+
for (int i = 0; i < n; i++) parts[k++] = (SolBytes){seeds[i].addr, seeds[i].len};
|
|
377
|
+
uint8_t bump = 255;
|
|
378
|
+
parts[k++] = (SolBytes){&bump, 1};
|
|
379
|
+
parts[k++] = (SolBytes){program->x, 32};
|
|
380
|
+
parts[k++] = (SolBytes){(const uint8_t *)"ProgramDerivedAddress", 21};
|
|
372
381
|
for (int b = 255; b >= 0; b--) {
|
|
373
|
-
|
|
374
|
-
|
|
382
|
+
bump = (uint8_t)b;
|
|
383
|
+
sol_sha256(parts, k, out->x);
|
|
384
|
+
uint8_t ignored;
|
|
385
|
+
if (sol_curve_validate_point(0 /* CURVE25519_EDWARDS */, out->x, &ignored) != 0) {
|
|
375
386
|
*bump_out = bump;
|
|
376
387
|
return SUCCESS;
|
|
377
388
|
}
|
|
@@ -493,10 +504,52 @@ static inline void ts_write_le(uint8_t *p, uint64_t v, int n) { ts_memcpy(p, &v,
|
|
|
493
504
|
uint64_t sol_get_rent_sysvar(void *ret);
|
|
494
505
|
uint64_t sol_get_clock_sysvar(void *ret);
|
|
495
506
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
507
|
+
/* The general case, in integers: exactly (uint64_t)((double)per_year * threshold) as soft-float computes it (the
|
|
508
|
+
conversion and the product rounded to nearest, ties to even; NaN, negative and < 1 give 0), or UINT64_MAX when that
|
|
509
|
+
product is >= 2^64 (no double below 2^64 converts to UINT64_MAX: the largest is 2^64 - 2048). No soft-float routine
|
|
510
|
+
is linked, which keeps about 1.7 KB out of every program that creates accounts. */
|
|
511
|
+
static __attribute__((noinline, minsize)) uint64_t ts_rent_slow(uint64_t per_year, uint64_t tbits) {
|
|
512
|
+
uint64_t e = (tbits >> 52) & 0x7ff, f = tbits & 0xfffffffffffffULL;
|
|
513
|
+
if (e == 0x7ff) return !f && per_year && !(tbits >> 63) ? UINT64_MAX : 0; /* +inf overflows; NaN, inf * 0, -inf: 0 */
|
|
514
|
+
if ((tbits >> 63) || !per_year || (!e && !f)) return 0; /* a negative or zero product */
|
|
515
|
+
/* Round v * 2^ex (with a sticky bit for bits already dropped) to 53 bits, nearest-even, twice: per_year to a double,
|
|
516
|
+
then the exact product of the two significands. */
|
|
517
|
+
uint64_t v = per_year, sticky = 0, mt = e ? f | (1ULL << 52) : f;
|
|
518
|
+
int64_t ex = (e ? (int64_t)e : 1) - 1075;
|
|
519
|
+
for (int pass = 0; pass < 2; pass++) {
|
|
520
|
+
int top = 63;
|
|
521
|
+
while (!(v >> top)) top--;
|
|
522
|
+
if (top > 52) {
|
|
523
|
+
int sh = top - 52;
|
|
524
|
+
uint64_t rem = v & ((1ULL << sh) - 1), half = 1ULL << (sh - 1);
|
|
525
|
+
v >>= sh;
|
|
526
|
+
ex += sh;
|
|
527
|
+
if (rem > half || (rem == half && (sticky || (v & 1)))) v++;
|
|
528
|
+
if (v >> 53) v >>= 1, ex++;
|
|
529
|
+
}
|
|
530
|
+
if (pass) break;
|
|
531
|
+
/* v (< 2^53) * mt (< 2^53) as hi:lo, folded to 64 bits with the dropped low bits as the sticky bit. */
|
|
532
|
+
uint64_t a0 = v & 0xffffffff, a1 = v >> 32, b0 = mt & 0xffffffff, b1 = mt >> 32;
|
|
533
|
+
uint64_t lo = a0 * b0, mid = a0 * b1 + a1 * b0, hi = a1 * b1;
|
|
534
|
+
uint64_t lo2 = lo + (mid << 32);
|
|
535
|
+
hi += (mid >> 32) + (lo2 < lo);
|
|
536
|
+
lo = lo2;
|
|
537
|
+
int s = 0;
|
|
538
|
+
while (hi >> s) s++;
|
|
539
|
+
v = s ? (hi << (64 - s)) | (lo >> s) : lo;
|
|
540
|
+
sticky = s ? (lo & ((1ULL << s) - 1)) != 0 : 0;
|
|
541
|
+
ex += s;
|
|
542
|
+
}
|
|
543
|
+
int top = 63;
|
|
544
|
+
while (!(v >> top)) top--;
|
|
545
|
+
if (top + ex >= 64) return UINT64_MAX;
|
|
546
|
+
if (ex >= 0) return v << ex;
|
|
547
|
+
return ex > -64 ? v >> -ex : 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* The minimum balance of `data_len` bytes under `rent`, the Rent sysvar
|
|
551
|
+
{ lamports_per_byte_year: u64, exemption_threshold: f64, burn_percent: u8 }. */
|
|
552
|
+
static inline uint64_t ts_minimum_balance_of(const uint64_t rent[3], uint64_t data_len, uint64_t *out) {
|
|
500
553
|
uint64_t bytes, per_year;
|
|
501
554
|
if (ts_add_overflow_u64(data_len, 128, &bytes) || ts_mul_overflow_u64(bytes, rent[0], &per_year))
|
|
502
555
|
TS_FAIL(TS_ERR_OVERFLOW, "TypeSOL: arithmetic overflow");
|
|
@@ -511,14 +564,24 @@ static inline uint64_t ts_minimum_balance(uint64_t data_len, uint64_t *out) {
|
|
|
511
564
|
return SUCCESS;
|
|
512
565
|
}
|
|
513
566
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
567
|
+
uint64_t lamports = ts_rent_slow(per_year, rent[1]);
|
|
568
|
+
if (lamports == UINT64_MAX) TS_FAIL(TS_ERR_OVERFLOW, "TypeSOL: arithmetic overflow");
|
|
569
|
+
*out = lamports;
|
|
570
|
+
return SUCCESS;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
static inline uint64_t ts_rent(uint64_t rent[3]) {
|
|
574
|
+
rent[0] = rent[1] = rent[2] = 0;
|
|
575
|
+
if (sol_get_rent_sysvar(rent) != SUCCESS) TS_FAIL(TS_ERR_SYSVAR, "TypeSOL: rent sysvar unavailable");
|
|
519
576
|
return SUCCESS;
|
|
520
577
|
}
|
|
521
578
|
|
|
579
|
+
static inline uint64_t ts_minimum_balance(uint64_t data_len, uint64_t *out) {
|
|
580
|
+
uint64_t rent[3];
|
|
581
|
+
TS_TRY(ts_rent(rent));
|
|
582
|
+
return ts_minimum_balance_of(rent, data_len, out);
|
|
583
|
+
}
|
|
584
|
+
|
|
522
585
|
/* Clock sysvar word `i`: 0 slot, 2 epoch (u64). */
|
|
523
586
|
static inline uint64_t ts_clock_u64(int i, uint64_t *out) {
|
|
524
587
|
uint64_t clock[5] = {0};
|
|
@@ -791,9 +854,10 @@ static inline uint64_t ts_resize(TsParams *params, TsAccount *payer, TsAccount *
|
|
|
791
854
|
if (new_len > old_len) {
|
|
792
855
|
/* The payer always funds the extra rent, even when the account already holds enough lamports: those may be
|
|
793
856
|
deposits, which must not become rent (and shrinking never pays anything back, see ts_realloc). */
|
|
794
|
-
uint64_t old_rent, new_rent;
|
|
795
|
-
TS_TRY(
|
|
796
|
-
TS_TRY(
|
|
857
|
+
uint64_t rent[3], old_rent, new_rent;
|
|
858
|
+
TS_TRY(ts_rent(rent)); /* read once: the sysvar cannot change within the instruction */
|
|
859
|
+
TS_TRY(ts_minimum_balance_of(rent, old_len, &old_rent));
|
|
860
|
+
TS_TRY(ts_minimum_balance_of(rent, new_len, &new_rent));
|
|
797
861
|
if (new_rent > old_rent) {
|
|
798
862
|
if (payer == NULL) TS_FAIL(TS_ERR_REALLOC, "TypeSOL: growing an account needs a payer");
|
|
799
863
|
TS_TRY(ts_system_transfer(params, payer, acct, new_rent - old_rent));
|
|
@@ -1340,18 +1404,32 @@ static inline uint64_t ts_str_i128(__int128 v, TS_Str *out) {
|
|
|
1340
1404
|
return ts_str_u128(mag, v < 0, out);
|
|
1341
1405
|
}
|
|
1342
1406
|
|
|
1407
|
+
/* "00" … "99": two digits per division. */
|
|
1408
|
+
static const char TS_DIGIT_PAIRS[201] =
|
|
1409
|
+
"00010203040506070809101112131415161718192021222324252627282930313233343536373839"
|
|
1410
|
+
"40414243444546474849505152535455565758596061626364656667686970717273747576777879"
|
|
1411
|
+
"8081828384858687888990919293949596979899";
|
|
1343
1412
|
static inline uint64_t ts_str_u64(uint64_t v, bool negative, TS_Str *out) {
|
|
1413
|
+
/* Written from the end, two digits at a time, then copied out in one go. */
|
|
1344
1414
|
uint8_t tmp[21];
|
|
1345
|
-
int
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1415
|
+
int i = 21;
|
|
1416
|
+
while (v >= 100) {
|
|
1417
|
+
uint64_t q = v / 100, r = v - q * 100;
|
|
1418
|
+
tmp[--i] = (uint8_t)TS_DIGIT_PAIRS[2 * r + 1];
|
|
1419
|
+
tmp[--i] = (uint8_t)TS_DIGIT_PAIRS[2 * r];
|
|
1420
|
+
v = q;
|
|
1421
|
+
}
|
|
1422
|
+
if (v >= 10) {
|
|
1423
|
+
tmp[--i] = (uint8_t)TS_DIGIT_PAIRS[2 * v + 1];
|
|
1424
|
+
tmp[--i] = (uint8_t)TS_DIGIT_PAIRS[2 * v];
|
|
1425
|
+
} else
|
|
1426
|
+
tmp[--i] = (uint8_t)('0' + v);
|
|
1427
|
+
if (negative) tmp[--i] = '-';
|
|
1428
|
+
uint64_t n = (uint64_t)(21 - i);
|
|
1351
1429
|
uint8_t *p;
|
|
1352
|
-
TS_TRY(ts_alloc(
|
|
1353
|
-
|
|
1354
|
-
*out = ts_str(p,
|
|
1430
|
+
TS_TRY(ts_alloc(n, &p));
|
|
1431
|
+
ts_memcpy(p, tmp + i, n);
|
|
1432
|
+
*out = ts_str(p, n);
|
|
1355
1433
|
return SUCCESS;
|
|
1356
1434
|
}
|
|
1357
1435
|
static inline uint64_t ts_str_i64(int64_t v, TS_Str *out) {
|