@typesol/cli 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +3 -3
- package/dist/clientgen.js +37 -0
- package/dist/compiler/analyze.d.ts +7 -1
- package/dist/compiler/analyze.js +258 -30
- package/dist/compiler/emit/base.d.ts +7 -0
- package/dist/compiler/emit/base.js +61 -1
- package/dist/compiler/emit/borsh.d.ts +13 -0
- package/dist/compiler/emit/borsh.js +232 -0
- package/dist/compiler/emit/calls.js +43 -15
- package/dist/compiler/emit/cpi.d.ts +32 -8
- package/dist/compiler/emit/cpi.js +522 -20
- package/dist/compiler/emit/expressions.js +19 -2
- package/dist/compiler/emit/function.js +34 -11
- package/dist/compiler/emit/program.js +24 -6
- package/dist/compiler/emit/shared.d.ts +12 -0
- package/dist/compiler/emit/shared.js +19 -2
- package/dist/compiler/emit/statements.js +11 -3
- package/dist/compiler/emit/storage.d.ts +4 -0
- package/dist/compiler/emit/storage.js +60 -12
- package/dist/compiler/idl.js +33 -4
- package/dist/compiler/model.d.ts +41 -2
- package/dist/compiler/model.js +8 -2
- package/dist/portgen.js +60 -4
- package/docs/errors.md +3 -2
- package/docs/language.md +10 -6
- package/docs/migrate-from-anchor.md +14 -5
- package/docs/test-your-program.md +1 -1
- package/lib/typesol.d.ts +259 -3
- package/package.json +1 -1
- package/runtime/typesol_rt.h +32 -7
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accounts declared `@layout borsh` are stored as Anchor stores them (Borsh: a String or Vec takes 4 + its length, an
|
|
3
|
+
* empty Option one byte, and the fields after them move). An instruction unpacks such an account into the usual
|
|
4
|
+
* fixed-layout struct on the heap (`ts_unpack_<S>`, checking it as Borsh deserialization would), runs on that, and
|
|
5
|
+
* packs it back when it returns (`ts_pack_<S>`), so every language feature works on it unchanged.
|
|
6
|
+
*/
|
|
7
|
+
/** A field type whose Borsh bytes are its fixed-layout bytes: scalars, Pubkeys, unit enums, arrays and structs of them. */
|
|
8
|
+
export function borshIdentical(t, model) {
|
|
9
|
+
switch (t.kind) {
|
|
10
|
+
case "int":
|
|
11
|
+
case "bool":
|
|
12
|
+
case "pubkey":
|
|
13
|
+
case "enum":
|
|
14
|
+
return true;
|
|
15
|
+
case "array":
|
|
16
|
+
return borshIdentical(t.elem, model);
|
|
17
|
+
case "struct": {
|
|
18
|
+
const s = model.structs.get(t.name);
|
|
19
|
+
return !!s && s.layout !== "borsh" && s.fields.every((f) => borshIdentical(f.type, model));
|
|
20
|
+
}
|
|
21
|
+
default:
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Bytes of a Borsh-identical type. */
|
|
26
|
+
function fixedBytes(t, model) {
|
|
27
|
+
switch (t.kind) {
|
|
28
|
+
case "int":
|
|
29
|
+
return Number(t.name.slice(1)) / 8;
|
|
30
|
+
case "bool":
|
|
31
|
+
case "enum":
|
|
32
|
+
return 1;
|
|
33
|
+
case "pubkey":
|
|
34
|
+
return 32;
|
|
35
|
+
case "array":
|
|
36
|
+
return fixedBytes(t.elem, model) * t.len;
|
|
37
|
+
case "struct":
|
|
38
|
+
return model.structs.get(t.name).size;
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`unreachable: ${t.kind} has no fixed Borsh size`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** The fewest bytes a Borsh `S` can take (every String and Vec empty, every Option None). */
|
|
44
|
+
export function borshMinSize(s, model) {
|
|
45
|
+
let n = 0;
|
|
46
|
+
for (const f of s.fields) {
|
|
47
|
+
const t = f.type;
|
|
48
|
+
if (t.kind === "fstring" || t.kind === "vec")
|
|
49
|
+
n += 4;
|
|
50
|
+
else if (t.kind === "option")
|
|
51
|
+
n += 1;
|
|
52
|
+
else
|
|
53
|
+
n += fixedBytes(t, model);
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
const DESER = `TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data is not a valid Borsh value")`;
|
|
58
|
+
const SER = `TS_FAIL(TS_ERR_DID_NOT_SERIALIZE, "TypeSOL: account data too small for the value")`;
|
|
59
|
+
/** C for `ts_unpack_<S>` and `ts_pack_<S>` of every `@layout borsh` struct. */
|
|
60
|
+
export function emitBorshCodecs(model) {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const s of model.structs.values()) {
|
|
63
|
+
if (s.layout !== "borsh" || s.size === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
let k = 0;
|
|
66
|
+
const tmp = (p) => `${p}${k++}`;
|
|
67
|
+
// Bools and enum tags inside a Borsh-identical value: invalid ones fail, as Borsh deserialization does.
|
|
68
|
+
const validate = (t, lv, pad) => {
|
|
69
|
+
switch (t.kind) {
|
|
70
|
+
case "bool":
|
|
71
|
+
return [`${pad}if (${lv} > 1) ${DESER};`];
|
|
72
|
+
case "enum":
|
|
73
|
+
return [`${pad}if (${lv} >= ${model.enums.get(t.name).variants.length}) ${DESER};`];
|
|
74
|
+
case "array": {
|
|
75
|
+
const i = tmp("i");
|
|
76
|
+
const inner = validate(t.elem, `${lv}[${i}]`, `${pad} `);
|
|
77
|
+
return inner.length ? [`${pad}for (uint64_t ${i} = 0; ${i} < ${t.len}; ${i}++) {`, ...inner, `${pad}}`] : [];
|
|
78
|
+
}
|
|
79
|
+
case "struct":
|
|
80
|
+
return model.structs.get(t.name).fields.flatMap((f) => validate(f.type, `${lv}.${f.name}`, pad));
|
|
81
|
+
default:
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
// Bounds: the caller checked `len >= MIN` (the smallest Borsh form). Fixed-size pieces then always fit; only a
|
|
86
|
+
// String / Vec / Option payload is checked, against what is left after the fields that follow it still need.
|
|
87
|
+
const minOf = (t) => (t.kind === "fstring" || t.kind === "vec" ? 4 : t.kind === "option" ? 1 : fixedBytes(t, model));
|
|
88
|
+
const minAfter = s.fields.map((_, i) => s.fields.slice(i + 1).reduce((n, f) => n + minOf(f.type), 0));
|
|
89
|
+
const need = (n, rest, pad) => `${pad}if (${n} > len - at - ${rest}) ${DESER};`;
|
|
90
|
+
const room = (n, rest, pad) => `${pad}if (${n} > cap - at - ${rest}) ${SER};`;
|
|
91
|
+
// A String / Vec / Option field, `rest` bytes of later fields still to come.
|
|
92
|
+
const unpackVar = (t, lv, rest, pad) => {
|
|
93
|
+
switch (t.kind) {
|
|
94
|
+
case "fstring": {
|
|
95
|
+
const l = tmp("n");
|
|
96
|
+
// Strings were checked as UTF-8 when they came in (arguments) and are only stored whole: not checked again.
|
|
97
|
+
return [
|
|
98
|
+
`${pad}uint32_t ${l} = (uint32_t)ts_read_le(src + at, 4);`,
|
|
99
|
+
`${pad}at += 4;`,
|
|
100
|
+
`${pad}if (${l} > ${t.cap}) ${DESER};`,
|
|
101
|
+
need(l, rest, pad),
|
|
102
|
+
`${pad}${lv}.len = ${l};`,
|
|
103
|
+
`${pad}ts_memcpy(${lv}.bytes, src + at, ${l});`,
|
|
104
|
+
`${pad}at += ${l};`,
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
case "vec": {
|
|
108
|
+
const l = tmp("n");
|
|
109
|
+
const head = [
|
|
110
|
+
`${pad}uint32_t ${l} = (uint32_t)ts_read_le(src + at, 4);`,
|
|
111
|
+
`${pad}at += 4;`,
|
|
112
|
+
`${pad}if (${l} > ${t.cap}) ${DESER};`,
|
|
113
|
+
`${pad}${lv}.len = ${l};`,
|
|
114
|
+
];
|
|
115
|
+
if (borshIdentical(t.elem, model)) {
|
|
116
|
+
const n = fixedBytes(t.elem, model);
|
|
117
|
+
const i = tmp("i");
|
|
118
|
+
const check = validate(t.elem, `${lv}.items[${i}]`, `${pad} `);
|
|
119
|
+
return [
|
|
120
|
+
...head,
|
|
121
|
+
need(`(uint64_t)${l} * ${n}`, rest, pad),
|
|
122
|
+
`${pad}ts_memcpy(${lv}.items, src + at, (uint64_t)${l} * ${n});`,
|
|
123
|
+
`${pad}at += (uint64_t)${l} * ${n};`,
|
|
124
|
+
...(check.length ? [`${pad}for (uint64_t ${i} = 0; ${i} < ${l}; ${i}++) {`, ...check, `${pad}}`] : []),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
// Strings: each element's length prefix is checked as it is reached.
|
|
128
|
+
const i = tmp("i");
|
|
129
|
+
return [
|
|
130
|
+
...head,
|
|
131
|
+
`${pad}for (uint64_t ${i} = 0; ${i} < ${l}; ${i}++) {`,
|
|
132
|
+
need("4", rest, `${pad} `),
|
|
133
|
+
...unpackVar(t.elem, `${lv}.items[${i}]`, rest, `${pad} `),
|
|
134
|
+
`${pad}}`,
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
case "option": {
|
|
138
|
+
const tag = tmp("tag");
|
|
139
|
+
const n = fixedBytes(t.inner, model);
|
|
140
|
+
return [
|
|
141
|
+
`${pad}uint8_t ${tag} = src[at++];`,
|
|
142
|
+
`${pad}if (${tag} > 1) ${DESER};`,
|
|
143
|
+
`${pad}${lv}.some = ${tag};`,
|
|
144
|
+
`${pad}if (${tag}) {`,
|
|
145
|
+
need(String(n), rest, `${pad} `),
|
|
146
|
+
`${pad} ts_memcpy(&${lv}.v, src + at, ${n});`,
|
|
147
|
+
`${pad} at += ${n};`,
|
|
148
|
+
...validate(t.inner, `${lv}.v`, `${pad} `),
|
|
149
|
+
`${pad}}`,
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
default:
|
|
153
|
+
throw new Error(`unreachable: ${t.kind} in a Borsh account`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const packVar = (t, lv, rest, pad) => {
|
|
157
|
+
switch (t.kind) {
|
|
158
|
+
case "fstring": {
|
|
159
|
+
const l = tmp("n");
|
|
160
|
+
return [
|
|
161
|
+
`${pad}uint32_t ${l} = ${lv}.len > ${t.cap} ? ${t.cap} : ${lv}.len;`,
|
|
162
|
+
room(`4 + (uint64_t)${l}`, rest, pad),
|
|
163
|
+
`${pad}ts_write_le(dst + at, ${l}, 4);`,
|
|
164
|
+
`${pad}ts_memcpy(dst + at + 4, ${lv}.bytes, ${l});`,
|
|
165
|
+
`${pad}at += 4 + (uint64_t)${l};`,
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
case "vec": {
|
|
169
|
+
const l = tmp("n");
|
|
170
|
+
if (borshIdentical(t.elem, model)) {
|
|
171
|
+
const n = fixedBytes(t.elem, model);
|
|
172
|
+
return [
|
|
173
|
+
`${pad}uint32_t ${l} = ${lv}.len > ${t.cap} ? ${t.cap} : ${lv}.len;`,
|
|
174
|
+
room(`4 + (uint64_t)${l} * ${n}`, rest, pad),
|
|
175
|
+
`${pad}ts_write_le(dst + at, ${l}, 4);`,
|
|
176
|
+
`${pad}ts_memcpy(dst + at + 4, ${lv}.items, (uint64_t)${l} * ${n});`,
|
|
177
|
+
`${pad}at += 4 + (uint64_t)${l} * ${n};`,
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
const i = tmp("i");
|
|
181
|
+
return [
|
|
182
|
+
`${pad}uint32_t ${l} = ${lv}.len > ${t.cap} ? ${t.cap} : ${lv}.len;`,
|
|
183
|
+
room("4", rest, pad),
|
|
184
|
+
`${pad}ts_write_le(dst + at, ${l}, 4);`,
|
|
185
|
+
`${pad}at += 4;`,
|
|
186
|
+
`${pad}for (uint64_t ${i} = 0; ${i} < ${l}; ${i}++) {`,
|
|
187
|
+
...packVar(t.elem, `${lv}.items[${i}]`, rest, `${pad} `),
|
|
188
|
+
`${pad}}`,
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
case "option": {
|
|
192
|
+
const n = fixedBytes(t.inner, model);
|
|
193
|
+
return [
|
|
194
|
+
room(`(uint64_t)(${lv}.some ? ${1 + n} : 1)`, rest, pad),
|
|
195
|
+
`${pad}dst[at++] = ${lv}.some ? 1 : 0;`,
|
|
196
|
+
`${pad}if (${lv}.some) {`,
|
|
197
|
+
`${pad} ts_memcpy(dst + at, &${lv}.v, ${n});`,
|
|
198
|
+
`${pad} at += ${n};`,
|
|
199
|
+
`${pad}}`,
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`unreachable: ${t.kind} in a Borsh account`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
// Runs of Borsh-identical fields are contiguous in both layouts: one copy each.
|
|
207
|
+
const unpackLines = [];
|
|
208
|
+
const packLines = [];
|
|
209
|
+
for (let i = 0; i < s.fields.length;) {
|
|
210
|
+
const f = s.fields[i];
|
|
211
|
+
if (borshIdentical(f.type, model)) {
|
|
212
|
+
let j = i;
|
|
213
|
+
let bytes = 0;
|
|
214
|
+
while (j < s.fields.length && borshIdentical(s.fields[j].type, model))
|
|
215
|
+
bytes += fixedBytes(s.fields[j++].type, model);
|
|
216
|
+
unpackLines.push(` ts_memcpy(&out->${f.name}, src + at, ${bytes});`, ` at += ${bytes};`);
|
|
217
|
+
for (const g of s.fields.slice(i, j))
|
|
218
|
+
unpackLines.push(...validate(g.type, `out->${g.name}`, " "));
|
|
219
|
+
// A pack can be short of room only after a String / Vec / Option grew; the start always fits (cap >= MIN).
|
|
220
|
+
packLines.push(...(i > 0 ? [room(String(bytes), minAfter[j - 1], " ")] : []), ` ts_memcpy(dst + at, &in->${f.name}, ${bytes});`, ` at += ${bytes};`);
|
|
221
|
+
i = j;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
unpackLines.push(...unpackVar(f.type, `out->${f.name}`, minAfter[i], " "));
|
|
225
|
+
packLines.push(...packVar(f.type, `in->${f.name}`, minAfter[i], " "));
|
|
226
|
+
i++;
|
|
227
|
+
}
|
|
228
|
+
const min = borshMinSize(s, model);
|
|
229
|
+
out.push(`/* ${s.name} as Borsh (Anchor's account layout) <-> its fixed-layout struct. */`, `__attribute__((noinline)) static uint64_t ts_unpack_${s.name}(const uint8_t *src, uint64_t len, TSS_${s.name} *out) {`, ` if (len < ${min}) ${DESER};`, ` uint64_t at = 0;`, ...unpackLines, ` return SUCCESS;`, `}`, `__attribute__((noinline)) static uint64_t ts_pack_${s.name}(const TSS_${s.name} *in, uint8_t *dst, uint64_t cap) {`, ` if (cap < ${min}) ${SER};`, ` uint64_t at = 0;`, ...packLines, ` return SUCCESS;`, `}`, "");
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
import { resolveScalarType } from "../analyze.js";
|
|
3
3
|
import { decodeAddress } from "../../keys.js";
|
|
4
4
|
import { INT_INFO, INT_NAMES, isScalar, isWritable, sameType, scalarSize, typeName, } from "../model.js";
|
|
5
|
-
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, } from "./shared.js";
|
|
5
|
+
import { U64, U8, I64, BOOL, PUBKEY, STRING, HASH, cBytes, cString, scalarC, scalarStorageC, optC, valueC, fieldC, storageDecl, accountMismatch, vecCount, } from "./shared.js";
|
|
6
6
|
import { ExpressionEmitter } from "./expressions.js";
|
|
7
7
|
/**
|
|
8
8
|
* Calls: built-in functions, Vec methods, helpers, remaining accounts, foreign account reads and return data.
|
|
@@ -364,13 +364,14 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
364
364
|
this.line(`if (ts_find_pda(${seeds.name}, ${seeds.count}, ${program.c}, &${t}, &${b}) != SUCCESS) TS_FAIL(TS_ERR_SEEDS, "TypeSOL: no program address for these seeds");`);
|
|
365
365
|
return { c: `(&${t})`, type: PUBKEY };
|
|
366
366
|
}
|
|
367
|
-
case "metadataAddress":
|
|
367
|
+
case "metadataAddress":
|
|
368
|
+
case "masterEditionAddress": {
|
|
368
369
|
if (args.length !== 1)
|
|
369
|
-
this.fail(node,
|
|
370
|
+
this.fail(node, `${builtin}(mint) takes the mint`);
|
|
370
371
|
const m = this.expect(this.expr(args[0], PUBKEY), PUBKEY, args[0]);
|
|
371
372
|
const t = this.tmp("md");
|
|
372
373
|
this.line(`SolPubkey ${t};`);
|
|
373
|
-
this.line(`TS_TRY(ts_metadata_address(${m.c}, &${t}));`);
|
|
374
|
+
this.line(`TS_TRY(${builtin === "metadataAddress" ? "ts_metadata_address" : "ts_master_edition_address"}(${m.c}, &${t}));`);
|
|
374
375
|
return { c: `(&${t})`, type: PUBKEY };
|
|
375
376
|
}
|
|
376
377
|
case "initExtraAccountMetas":
|
|
@@ -433,8 +434,9 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
433
434
|
}
|
|
434
435
|
if (builtin)
|
|
435
436
|
this.fail(callee, `${builtin} cannot be called here`);
|
|
436
|
-
|
|
437
|
-
|
|
437
|
+
const helper = this.helperOf(node);
|
|
438
|
+
if (helper)
|
|
439
|
+
return this.callHelper(node, helper);
|
|
438
440
|
if (ts.isPropertyAccessExpression(callee)) {
|
|
439
441
|
const method = callee.name.text;
|
|
440
442
|
// Vec<T> instruction arguments are read-only views.
|
|
@@ -520,10 +522,14 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
520
522
|
return this.ataOp(node, target, method);
|
|
521
523
|
if (target?.kind.kind === "metadataProgram")
|
|
522
524
|
return this.metadataOp(node, target, method);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
525
|
+
// Each Metaplex CPI in its own C block: its data buffer's stack slot is reused by the next one.
|
|
526
|
+
if (target?.kind.kind === "coreProgram" || target?.kind.kind === "bubblegumProgram") {
|
|
527
|
+
const t = target;
|
|
528
|
+
this.line("{");
|
|
529
|
+
this.block(() => (t.kind.kind === "coreProgram" ? this.coreOp(node, t, method) : this.bubblegumOp(node, t, method)));
|
|
530
|
+
this.line("}");
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
527
533
|
}
|
|
528
534
|
}
|
|
529
535
|
if (ts.isPropertyAccessExpression(callee) && ts.isCallExpression(callee.expression) && this.exportName(callee.expression.expression) === "fixedArray")
|
|
@@ -536,7 +542,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
536
542
|
const args = node.arguments;
|
|
537
543
|
const vt = v.type;
|
|
538
544
|
const info = v.vec;
|
|
539
|
-
const elemRef = (idx) => ({ ...v, lv: `${
|
|
545
|
+
const elemRef = (idx) => this.withFixedVec({ ...v, lv: `${info.items}[${idx}]`, type: vt.elem, vec: undefined, path: undefined });
|
|
546
|
+
const count = vecCount(info);
|
|
540
547
|
/**
|
|
541
548
|
* Evaluate the value into a temporary first (JS evaluates arguments before the call runs), then return a
|
|
542
549
|
* writer for the element: `push(v.length)` sees the old length, `set(i, pop())` checks i after the pop.
|
|
@@ -564,6 +571,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
564
571
|
// Elements removed or replaced here must not be referenced afterwards.
|
|
565
572
|
const elemPaths = () => (ts.isPropertyAccessExpression(node.expression) ? this.pathsOf(node.expression.expression)?.map((p) => `${p}[]`) : undefined);
|
|
566
573
|
const resize = (newLen, payer) => {
|
|
574
|
+
if (info.cap !== undefined)
|
|
575
|
+
return; // a FixedVec has its room already
|
|
567
576
|
if (!info.resizable || !v.account)
|
|
568
577
|
this.fail(node, "only Vecs of declared Mut<...> / Init<...> accounts can grow or shrink");
|
|
569
578
|
this.line(`TS_TRY(ts_resize(params, ${payer}, a_${v.account.name}, 8 + ${info.fixed} + (${newLen}) * ${info.elemSize}, entry_${v.account.name}));`);
|
|
@@ -577,11 +586,23 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
577
586
|
this.assertNotAliased(elemPaths(), node);
|
|
578
587
|
const i = this.indexValue(args[0]);
|
|
579
588
|
const write = stageElem(args[1]);
|
|
580
|
-
this.checkIndex(i,
|
|
589
|
+
this.checkIndex(i, count);
|
|
581
590
|
write(elemRef(i));
|
|
582
591
|
return undefined;
|
|
583
592
|
}
|
|
584
593
|
case "push": {
|
|
594
|
+
if (info.cap !== undefined) {
|
|
595
|
+
if (args.length !== 1)
|
|
596
|
+
this.fail(node, "push(value) on a FixedVec takes the value (it has room for its capacity already)");
|
|
597
|
+
needWritable();
|
|
598
|
+
const write = stageElem(args[0]);
|
|
599
|
+
const n = this.tmp("n");
|
|
600
|
+
this.line(`uint64_t ${n} = ${count};`);
|
|
601
|
+
this.line(`if (${n} >= ${info.cap}) TS_FAIL(TS_ERR_INDEX, "TypeSOL: push() on a full FixedVec");`);
|
|
602
|
+
this.line(`${info.len} = (uint32_t)(${n} + 1);`);
|
|
603
|
+
write(elemRef(n));
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
585
606
|
if (args.length !== 2)
|
|
586
607
|
this.fail(node, "push(value, payer) takes the value and a Mut<Signer> that pays for the extra rent");
|
|
587
608
|
needWritable();
|
|
@@ -604,7 +625,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
604
625
|
this.assertNotAliased(elemPaths(), node);
|
|
605
626
|
const st = this.storageValueType(vt.elem);
|
|
606
627
|
const n = this.tmp("n");
|
|
607
|
-
this.line(`uint64_t ${n} =
|
|
628
|
+
this.line(`uint64_t ${n} = ${count};`);
|
|
608
629
|
this.line(`if (${n} == 0) TS_FAIL(TS_ERR_INDEX, "TypeSOL: pop() on an empty Vec");`);
|
|
609
630
|
// Structs and arrays are dropped (pop() as a statement); read .get(length - 1) first to keep one.
|
|
610
631
|
let result;
|
|
@@ -836,6 +857,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
836
857
|
const s = targ && ts.isTypeReferenceNode(targ) && ts.isIdentifier(targ.typeName) ? this.model.structs.get(targ.typeName.text) : undefined;
|
|
837
858
|
if (!s || s.size === undefined || s.vec)
|
|
838
859
|
this.fail(node, "loadForeign needs a fixed-size struct type: loadForeign<Pool>(account, owner)");
|
|
860
|
+
if (s.layout === "borsh")
|
|
861
|
+
this.fail(node, `${s.name} is @layout borsh: loadForeign reads fixed layouts only`);
|
|
839
862
|
if (node.arguments.length !== 2)
|
|
840
863
|
this.fail(node, "loadForeign<T>(account, owner) takes the account and its owning program's address");
|
|
841
864
|
const h = this.anyAccount(node.arguments[0], "loadForeign's account");
|
|
@@ -857,6 +880,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
857
880
|
const s = targ && ts.isTypeReferenceNode(targ) && ts.isIdentifier(targ.typeName) ? this.model.structs.get(targ.typeName.text) : undefined;
|
|
858
881
|
if (!s || s.size === undefined)
|
|
859
882
|
this.fail(node, "loadAccount needs an account struct type: loadAccount<MyStruct>(account)");
|
|
883
|
+
if (s.layout === "borsh")
|
|
884
|
+
this.fail(node, `${s.name} is @layout borsh: declare it as an instruction parameter (remaining accounts are read in place)`);
|
|
860
885
|
if (node.arguments.length !== 1)
|
|
861
886
|
this.fail(node, "loadAccount<T>(account) takes one remaining account");
|
|
862
887
|
const h = this.handle(node.arguments[0]);
|
|
@@ -876,7 +901,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
876
901
|
}
|
|
877
902
|
callHelper(node, h) {
|
|
878
903
|
if (node.arguments.length !== h.params.length)
|
|
879
|
-
this.fail(node, `${h.
|
|
904
|
+
this.fail(node, `${h.display}() takes ${h.params.length} argument(s)`);
|
|
880
905
|
// Arguments are evaluated left to right: settle earlier ones when a later one runs statements.
|
|
881
906
|
// A helper that takes accounts may change account data, so account text it gets is copied first.
|
|
882
907
|
const takesAccounts = h.params.some((p) => p.account);
|
|
@@ -898,7 +923,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
898
923
|
// A helper that takes a writable struct account may replace anything stored in it (see assertNotAliased).
|
|
899
924
|
for (const [i, p] of h.params.entries())
|
|
900
925
|
if (p.account && p.account.kind === "account" && p.account.mut && ts.isIdentifier(node.arguments[i]))
|
|
901
|
-
this.assertNotAliased([node.arguments[i].text], node, h.
|
|
926
|
+
this.assertNotAliased([node.arguments[i].text], node, h.display);
|
|
902
927
|
const args = ["params", ...pieces.map((x) => (typeof x === "number" ? (values[x].type.kind === "pubkey" ? `*(${values[x].c})` : values[x].c) : x))];
|
|
903
928
|
if (!h.returns) {
|
|
904
929
|
this.line(`TS_TRY(fn_${h.name}(${args.join(", ")}));`);
|
|
@@ -923,6 +948,9 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
923
948
|
const why = accountMismatch(acct.kind, p.account);
|
|
924
949
|
if (why)
|
|
925
950
|
this.fail(n, `${h.name}() takes ${p.name} as ${why}`);
|
|
951
|
+
// A @layout borsh account goes with the struct it is being worked on as.
|
|
952
|
+
if (p.account.kind === "account" && p.account.struct.layout === "borsh")
|
|
953
|
+
return `a_${acct.name}, d_${acct.name}`;
|
|
926
954
|
return `a_${acct.name}`;
|
|
927
955
|
}
|
|
928
956
|
switch (p.type.kind) {
|
|
@@ -2,6 +2,13 @@ import ts from "typescript";
|
|
|
2
2
|
import { type AccountParam, type IdlSeed, type ValueType } from "../model.js";
|
|
3
3
|
import { type Value } from "./shared.js";
|
|
4
4
|
import { CallEmitter } from "./calls.js";
|
|
5
|
+
/** One piece of a Metaplex instruction's Borsh data: fixed bytes, or a value lowered and serialized in source order. */
|
|
6
|
+
type MplField = {
|
|
7
|
+
bytes: number[];
|
|
8
|
+
} | {
|
|
9
|
+
value: ts.Expression;
|
|
10
|
+
type: ValueType;
|
|
11
|
+
};
|
|
5
12
|
/**
|
|
6
13
|
* Other programs: PDAs and seeds, account creation, System/Token/ATA/Metaplex CPIs, generic invoke, Borsh
|
|
7
14
|
* serialization and events.
|
|
@@ -30,10 +37,16 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
30
37
|
protected systemTransfer(node: ts.CallExpression): undefined;
|
|
31
38
|
/** systemProgram.createAccount(payer, account, lamports, space, owner, signerSeeds?) */
|
|
32
39
|
protected createAccount(node: ts.CallExpression, system: AccountParam): undefined;
|
|
33
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Emit a CPI to the program account `program` with these metas (account, writable, signer), and then (`tail`) a range
|
|
42
|
+
* of the remaining accounts, read-only (a Merkle proof).
|
|
43
|
+
*/
|
|
34
44
|
protected cpi(program: string, metas: [string, boolean, boolean][], buf: string, len: string, seeds: {
|
|
35
45
|
name: string;
|
|
36
46
|
count: number | string;
|
|
47
|
+
}, tail?: {
|
|
48
|
+
from: string;
|
|
49
|
+
to: string;
|
|
37
50
|
}): void;
|
|
38
51
|
protected signerSeeds(node: ts.Expression | undefined, authority: AccountParam, at: ts.Node): {
|
|
39
52
|
name: string;
|
|
@@ -47,25 +60,35 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
47
60
|
protected tokenOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
48
61
|
protected ataOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
49
62
|
protected metadataOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
63
|
+
/** CreateMasterEditionV3 (17) and Verify (52) with VerificationArgs::CollectionV1. */
|
|
64
|
+
protected metadataCollectionOp(node: ts.CallExpression, program: AccountParam, method: string): undefined;
|
|
50
65
|
/**
|
|
51
66
|
* One account of a Metaplex CPI: an account parameter, or `null` for an optional account left out
|
|
52
67
|
* (Metaplex programs take their own id in its place).
|
|
53
68
|
*/
|
|
54
69
|
protected mplMeta(n: ts.Expression, program: AccountParam, what: string, writable: boolean, signer: boolean): [string, boolean, boolean];
|
|
55
70
|
/** Borsh fields into a fresh buffer: raw bytes ("u8:N" style) or lowered values. */
|
|
56
|
-
protected mplData(at: ts.Node, max: number, fields:
|
|
57
|
-
bytes: number[];
|
|
58
|
-
} | {
|
|
59
|
-
value: ts.Expression;
|
|
60
|
-
type: ValueType;
|
|
61
|
-
})[]): {
|
|
71
|
+
protected mplData(at: ts.Node, max: number, fields: MplField[]): {
|
|
62
72
|
buf: string;
|
|
63
73
|
len: string;
|
|
64
74
|
};
|
|
65
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Borsh of one Metaplex Core plugin literal (`{ kind: "FreezeDelegate", frozen: true, authority?: … }`): the Plugin
|
|
77
|
+
* enum, then (`withAuthority`) the Option<PluginAuthority> that CreateV1's PluginAuthorityPair and AddPluginV1 take.
|
|
78
|
+
*/
|
|
79
|
+
protected corePlugin(n: ts.Expression, withAuthority: boolean): MplField[];
|
|
80
|
+
/** CreateV1 / CreateCollectionV1 plugins: Option<Vec<PluginAuthorityPair>>. */
|
|
81
|
+
protected corePluginList(n: ts.Expression | undefined): MplField[];
|
|
82
|
+
/** Metaplex Core: assets and collections (with plugins), plugins added, updated and removed, transfer and burn. */
|
|
66
83
|
protected coreOp(node: ts.CallExpression, core: AccountParam, method: string): undefined;
|
|
84
|
+
/** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
|
|
85
|
+
protected corePluginType(n: ts.Expression): MplField;
|
|
67
86
|
/** Bubblegum (compressed NFTs on SPL account compression): createTree, mint (MintV1, no collection, no creators). */
|
|
68
87
|
protected bubblegumOp(node: ts.CallExpression, bg: AccountParam, method: string): undefined;
|
|
88
|
+
/** Bubblegum V2 (trees on mpl-account-compression with mpl-noop; optional Metaplex Core collections). */
|
|
89
|
+
protected bubblegumV2(node: ts.CallExpression, bg: AccountParam, method: string): undefined;
|
|
90
|
+
/** `acct.key` of an account parameter, as an expression to serialize. */
|
|
91
|
+
protected keyOf(n: ts.Expression): ts.Expression;
|
|
69
92
|
/**
|
|
70
93
|
* `remainingAccounts()` (or a const bound to it): every remaining account; `rest.slice(a, b?)`: indexes a..b of them
|
|
71
94
|
* (both checked against the count, 5104). C expressions for the range, relative to the first remaining account.
|
|
@@ -99,3 +122,4 @@ export declare abstract class CpiEmitter extends CallEmitter {
|
|
|
99
122
|
/** Structs whose IDL description is the fixed layout: account data, arguments, and everything nested in them. */
|
|
100
123
|
protected fixedLayoutStructs(): Set<string>;
|
|
101
124
|
}
|
|
125
|
+
export {};
|