@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
|
@@ -207,6 +207,7 @@ export class CpiEmitter extends CallEmitter {
|
|
|
207
207
|
ts.isPostfixUnaryExpression(n) ||
|
|
208
208
|
ts.forEachChild(n, mutates) === true;
|
|
209
209
|
const inPlace = !node.elements.some(mutates);
|
|
210
|
+
const mark = this.lines.length;
|
|
210
211
|
for (const el of node.elements) {
|
|
211
212
|
const desc = this.describeSeed(el);
|
|
212
213
|
if (!desc)
|
|
@@ -252,9 +253,19 @@ export class CpiEmitter extends CallEmitter {
|
|
|
252
253
|
else
|
|
253
254
|
this.fail(el, "seeds can be strings, Pubkeys, integers or unit enum values");
|
|
254
255
|
}
|
|
256
|
+
// The same list again (escrow: the vault transfer, then its close) reuses the array declared for the first, when
|
|
257
|
+
// nothing was copied into temporaries (every entry points at a literal or at the place itself, so the array reads
|
|
258
|
+
// what a new one would), it has no spare slot (callers never write into those arrays) and it is still in scope.
|
|
259
|
+
const key = parts.join(", ");
|
|
260
|
+
const reusable = extra === 0 && parts.length > 0 && this.lines.length === mark;
|
|
261
|
+
const known = reusable ? this.seedArrays.get(key) : undefined;
|
|
262
|
+
if (known && this.scopes.includes(known.scope) && this.lines[known.at]?.trim() === known.text)
|
|
263
|
+
return { name: known.name, count: parts.length, idl };
|
|
255
264
|
const name = this.tmp("seeds");
|
|
256
265
|
const slots = parts.length + extra;
|
|
257
266
|
this.line(`SolSignerSeed ${name}[${Math.max(slots, 1)}] = {${parts.join(", ")}};`);
|
|
267
|
+
if (reusable)
|
|
268
|
+
this.seedArrays.set(key, { name, scope: this.scopes[this.scopes.length - 1], at: this.lines.length - 1, text: this.lines.at(-1).trim() });
|
|
258
269
|
return { name, count: parts.length, idl };
|
|
259
270
|
}
|
|
260
271
|
describeSeed(el) {
|
|
@@ -1526,6 +1537,8 @@ export class CpiEmitter extends CallEmitter {
|
|
|
1526
1537
|
this.line(`static const uint8_t ${name}[${f.bytes.length}] = ${cBytes(f.bytes)};`);
|
|
1527
1538
|
put(name, String(f.bytes.length), f.bytes.length);
|
|
1528
1539
|
}
|
|
1540
|
+
else if ("c" in f)
|
|
1541
|
+
put(f.c, f.n);
|
|
1529
1542
|
else
|
|
1530
1543
|
this.serialize(this.expect(this.expr(f.value, f.type), f.type, f.value), f.value, put);
|
|
1531
1544
|
}
|
|
@@ -1713,124 +1726,354 @@ export class CpiEmitter extends CallEmitter {
|
|
|
1713
1726
|
});
|
|
1714
1727
|
return [{ bytes: [1, els.length & 0xff, 0, 0, 0] }, ...els.flatMap((e) => this.corePlugin(e, true))];
|
|
1715
1728
|
}
|
|
1716
|
-
/**
|
|
1729
|
+
/**
|
|
1730
|
+
* Metaplex Core: assets and collections (with plugins), plugins and external plugin adapters added, updated, written
|
|
1731
|
+
* and removed, transfer and burn. Each method is a row of CORE_OPS below: its accounts in order, its data.
|
|
1732
|
+
*/
|
|
1717
1733
|
coreOp(node, core, method) {
|
|
1718
1734
|
const a = node.arguments;
|
|
1719
1735
|
const p = `a_${core.name}`;
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1736
|
+
const op = this.coreOps()[method];
|
|
1737
|
+
if (!op)
|
|
1738
|
+
this.fail(node.expression, `Metaplex Core supports createAsset, createCollection, addPlugin, updatePlugin, removePlugin, addExternalPlugin, removeExternalPlugin, writeExternalPluginData (and the Collection forms), transferAsset and burnAsset, not ${method}`);
|
|
1739
|
+
// An array literal of object literals right after the fixed arguments is the plugin list; the signer seeds follow.
|
|
1723
1740
|
const isPlugins = (n) => !!n && ts.isArrayLiteralExpression(n) && (n.elements.length === 0 || n.elements.every((e) => ts.isObjectLiteralExpression(e)));
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1741
|
+
const list = op.plugins && isPlugins(a[op.args]) ? a[op.args] : undefined;
|
|
1742
|
+
const seedsAt = op.args + (list ? 1 : 0);
|
|
1743
|
+
if (op.plugins ? a.length < op.args || a.length > seedsAt + 1 : a.length !== op.args && a.length !== op.args + 1)
|
|
1744
|
+
this.fail(node, op.usage(method));
|
|
1745
|
+
const metas = op.metas.map((m) => (m === "self" ? [p, false, false] : this.mplMeta(a[m[0]], core, m[1], m[2], m[3])));
|
|
1746
|
+
const data = this.mplData(node, op.max, op.data(a, list));
|
|
1747
|
+
this.cpi(p, metas, data.buf, data.len, a[seedsAt] ? this.signingSeeds(a[seedsAt]) : { name: "NULL", count: 0 });
|
|
1748
|
+
return undefined;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Core instructions: `args` fixed arguments (then an optional plugin list when `plugins`, then optional signer
|
|
1752
|
+
* seeds), accounts in order (`[argument, label, writable, signer]`, or "self": the Core program, Core's "none" for an
|
|
1753
|
+
* optional account), at most `max` bytes of data.
|
|
1754
|
+
*/
|
|
1755
|
+
coreOps() {
|
|
1756
|
+
const str = (n) => ({ value: n, type: STRING });
|
|
1757
|
+
const asset = [0, "the asset", true, false];
|
|
1758
|
+
const assetPlugin = {
|
|
1759
|
+
usage: (m) => `${m}(asset, collection | null, payer, authority | null, systemProgram, ${m === "removePlugin" ? "kind" : "plugin"}, signerSeeds?)`,
|
|
1760
|
+
args: 6,
|
|
1761
|
+
// Add/Remove/UpdatePluginV1: asset, collection, payer, authority, systemProgram, logWrapper.
|
|
1762
|
+
metas: [
|
|
1763
|
+
asset,
|
|
1764
|
+
[1, "the collection", true, false],
|
|
1765
|
+
[2, "the payer", true, true],
|
|
1766
|
+
[3, "the authority", false, true],
|
|
1767
|
+
[4, "the system program", false, false],
|
|
1768
|
+
"self",
|
|
1769
|
+
],
|
|
1770
|
+
max: 2048,
|
|
1771
|
+
};
|
|
1772
|
+
const collectionPlugin = {
|
|
1773
|
+
usage: (m) => `${m}(collection, payer, authority | null, systemProgram, ${m === "removeCollectionPlugin" ? "kind" : "plugin"}, signerSeeds?)`,
|
|
1774
|
+
args: 5,
|
|
1775
|
+
// Add/Remove/UpdateCollectionPluginV1: collection, payer, authority, systemProgram, logWrapper.
|
|
1776
|
+
metas: [
|
|
1777
|
+
[0, "the collection", true, false],
|
|
1778
|
+
[1, "the payer", true, true],
|
|
1779
|
+
[2, "the authority", false, true],
|
|
1780
|
+
[3, "the system program", false, false],
|
|
1781
|
+
"self",
|
|
1782
|
+
],
|
|
1783
|
+
max: 2048,
|
|
1784
|
+
};
|
|
1785
|
+
const assetPluginOp = (method, disc) => ({
|
|
1786
|
+
...assetPlugin,
|
|
1787
|
+
data: (a) => [
|
|
1788
|
+
{ bytes: [disc] },
|
|
1789
|
+
...(method === "removePlugin" ? [this.corePluginType(a[5])] : this.corePlugin(a[5], method === "addPlugin")),
|
|
1790
|
+
],
|
|
1791
|
+
});
|
|
1792
|
+
const collectionPluginOp = (method, disc) => ({
|
|
1793
|
+
...collectionPlugin,
|
|
1794
|
+
data: (a) => [
|
|
1795
|
+
{ bytes: [disc] },
|
|
1796
|
+
...(method === "removeCollectionPlugin" ? [this.corePluginType(a[4])] : this.corePlugin(a[4], method === "addCollectionPlugin")),
|
|
1797
|
+
],
|
|
1798
|
+
});
|
|
1799
|
+
// Add/RemoveExternalPluginAdapterV1: asset, collection, payer, authority, systemProgram, logWrapper; the write has a
|
|
1800
|
+
// buffer account (left out: the data is in the instruction) before systemProgram. The Collection forms drop the asset.
|
|
1801
|
+
const external = (method, disc, collection) => {
|
|
1802
|
+
const write = method.startsWith("write");
|
|
1803
|
+
const add = method.startsWith("add");
|
|
1804
|
+
const n = (collection ? 5 : 6) + (write ? 1 : 0);
|
|
1805
|
+
const at = collection ? 4 : 5;
|
|
1806
|
+
const head = collection ? "collection, payer, authority | null, systemProgram" : "asset, collection | null, payer, authority | null, systemProgram";
|
|
1807
|
+
const metas = collection
|
|
1808
|
+
? [
|
|
1809
|
+
[0, "the collection", true, false],
|
|
1810
|
+
[1, "the payer", true, true],
|
|
1811
|
+
[2, "the authority", false, true],
|
|
1812
|
+
]
|
|
1813
|
+
: [asset, [1, "the collection", true, false], [2, "the payer", true, true], [3, "the authority", false, true]];
|
|
1814
|
+
if (write)
|
|
1815
|
+
metas.push("self");
|
|
1816
|
+
metas.push([collection ? 3 : 4, "the system program", false, false], "self");
|
|
1817
|
+
return {
|
|
1818
|
+
usage: (m) => `${m}(${head}, ${add ? "plugin" : write ? "key, data" : "key"}, signerSeeds?)`,
|
|
1819
|
+
args: n,
|
|
1820
|
+
metas,
|
|
1821
|
+
max: 1024,
|
|
1822
|
+
data: (a) => [
|
|
1823
|
+
{ bytes: [disc] },
|
|
1824
|
+
...(add
|
|
1825
|
+
? this.coreExternalPlugin(a[at])
|
|
1826
|
+
: write
|
|
1827
|
+
? [...this.coreExternalKey(a[at]), ...this.coreExternalData(a[at + 1])]
|
|
1828
|
+
: this.coreExternalKey(a[at])),
|
|
1829
|
+
],
|
|
1830
|
+
};
|
|
1831
|
+
};
|
|
1832
|
+
return {
|
|
1833
|
+
createAsset: {
|
|
1834
|
+
usage: () => "createAsset(asset, collection | null, authority | null, payer, owner | null, systemProgram, name, uri, plugins?, signerSeeds?)",
|
|
1835
|
+
args: 8,
|
|
1836
|
+
plugins: true,
|
|
1837
|
+
// CreateV1: asset, collection, authority, payer, owner, updateAuthority (the collection's, or the payer by
|
|
1838
|
+
// default), systemProgram, logWrapper.
|
|
1839
|
+
metas: [
|
|
1840
|
+
[0, "the asset", true, true],
|
|
1841
|
+
[1, "the collection", true, false],
|
|
1842
|
+
[2, "the collection authority", false, true],
|
|
1843
|
+
[3, "the payer", true, true],
|
|
1844
|
+
[4, "the owner", false, false],
|
|
1845
|
+
"self",
|
|
1846
|
+
[5, "the system program", false, false],
|
|
1847
|
+
"self",
|
|
1848
|
+
],
|
|
1849
|
+
max: 2048,
|
|
1850
|
+
data: (a, plugins) => [{ bytes: [0, 0] }, str(a[6]), str(a[7]), ...this.corePluginList(plugins)],
|
|
1851
|
+
},
|
|
1852
|
+
createCollection: {
|
|
1853
|
+
usage: () => "createCollection(collection, updateAuthority | null, payer, systemProgram, name, uri, plugins?, signerSeeds?)",
|
|
1854
|
+
args: 6,
|
|
1855
|
+
plugins: true,
|
|
1746
1856
|
// CreateCollectionV1: collection, updateAuthority, payer, systemProgram.
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
if (a.length !== 6 && a.length !== 7)
|
|
1766
|
-
this.fail(node, `${method}(asset, collection | null, payer, authority | null, systemProgram, ${last}, signerSeeds?)`);
|
|
1767
|
-
const metas = [
|
|
1768
|
-
this.mplMeta(a[0], core, "the asset", true, false),
|
|
1769
|
-
this.mplMeta(a[1], core, "the collection", true, false),
|
|
1770
|
-
this.mplMeta(a[2], core, "the payer", true, true),
|
|
1771
|
-
this.mplMeta(a[3], core, "the authority", false, true),
|
|
1772
|
-
this.mplMeta(a[4], core, "the system program", false, false),
|
|
1773
|
-
self,
|
|
1774
|
-
];
|
|
1775
|
-
const body = method === "removePlugin" ? [this.corePluginType(a[5])] : this.corePlugin(a[5], method === "addPlugin");
|
|
1776
|
-
const data = this.mplData(node, 2048, [{ bytes: [{ addPlugin: 2, removePlugin: 4, updatePlugin: 6 }[method]] }, ...body]);
|
|
1777
|
-
this.cpi(p, metas, data.buf, data.len, seedsAt(6));
|
|
1778
|
-
return undefined;
|
|
1779
|
-
}
|
|
1780
|
-
case "addCollectionPlugin":
|
|
1781
|
-
case "updateCollectionPlugin":
|
|
1782
|
-
case "removeCollectionPlugin": {
|
|
1783
|
-
// Add/Remove/UpdateCollectionPluginV1: collection, payer, authority, systemProgram, logWrapper.
|
|
1784
|
-
const last = method === "removeCollectionPlugin" ? "kind" : "plugin";
|
|
1785
|
-
if (a.length !== 5 && a.length !== 6)
|
|
1786
|
-
this.fail(node, `${method}(collection, payer, authority | null, systemProgram, ${last}, signerSeeds?)`);
|
|
1787
|
-
const metas = [
|
|
1788
|
-
this.mplMeta(a[0], core, "the collection", true, false),
|
|
1789
|
-
this.mplMeta(a[1], core, "the payer", true, true),
|
|
1790
|
-
this.mplMeta(a[2], core, "the authority", false, true),
|
|
1791
|
-
this.mplMeta(a[3], core, "the system program", false, false),
|
|
1792
|
-
self,
|
|
1793
|
-
];
|
|
1794
|
-
const body = method === "removeCollectionPlugin" ? [this.corePluginType(a[4])] : this.corePlugin(a[4], method === "addCollectionPlugin");
|
|
1795
|
-
const data = this.mplData(node, 2048, [{ bytes: [{ addCollectionPlugin: 3, removeCollectionPlugin: 5, updateCollectionPlugin: 7 }[method]] }, ...body]);
|
|
1796
|
-
this.cpi(p, metas, data.buf, data.len, seedsAt(5));
|
|
1797
|
-
return undefined;
|
|
1798
|
-
}
|
|
1799
|
-
case "transferAsset": {
|
|
1857
|
+
metas: [
|
|
1858
|
+
[0, "the collection", true, true],
|
|
1859
|
+
[1, "the update authority", false, false],
|
|
1860
|
+
[2, "the payer", true, true],
|
|
1861
|
+
[3, "the system program", false, false],
|
|
1862
|
+
],
|
|
1863
|
+
max: 2048,
|
|
1864
|
+
data: (a, plugins) => [{ bytes: [1] }, str(a[4]), str(a[5]), ...this.corePluginList(plugins)],
|
|
1865
|
+
},
|
|
1866
|
+
addPlugin: assetPluginOp("addPlugin", 2),
|
|
1867
|
+
removePlugin: assetPluginOp("removePlugin", 4),
|
|
1868
|
+
updatePlugin: assetPluginOp("updatePlugin", 6),
|
|
1869
|
+
addCollectionPlugin: collectionPluginOp("addCollectionPlugin", 3),
|
|
1870
|
+
removeCollectionPlugin: collectionPluginOp("removeCollectionPlugin", 5),
|
|
1871
|
+
updateCollectionPlugin: collectionPluginOp("updateCollectionPlugin", 7),
|
|
1872
|
+
transferAsset: {
|
|
1873
|
+
usage: () => "transferAsset(asset, collection | null, payer, authority | null, newOwner, signerSeeds?)",
|
|
1874
|
+
args: 5,
|
|
1800
1875
|
// TransferV1: asset, collection, payer, authority, newOwner, systemProgram, logWrapper.
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
]
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
case "burnAsset": {
|
|
1876
|
+
metas: [
|
|
1877
|
+
asset,
|
|
1878
|
+
[1, "the collection", false, false],
|
|
1879
|
+
[2, "the payer", true, true],
|
|
1880
|
+
[3, "the authority", false, true],
|
|
1881
|
+
[4, "the new owner", false, false],
|
|
1882
|
+
"self",
|
|
1883
|
+
"self",
|
|
1884
|
+
],
|
|
1885
|
+
max: 8,
|
|
1886
|
+
data: () => [{ bytes: [14, 0] }],
|
|
1887
|
+
},
|
|
1888
|
+
burnAsset: {
|
|
1889
|
+
usage: () => "burnAsset(asset, collection | null, payer, authority | null, signerSeeds?)",
|
|
1890
|
+
args: 4,
|
|
1817
1891
|
// BurnV1: asset, collection, payer, authority, systemProgram, logWrapper.
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1892
|
+
metas: [asset, [1, "the collection", true, false], [2, "the payer", true, true], [3, "the authority", false, true], "self", "self"],
|
|
1893
|
+
max: 8,
|
|
1894
|
+
data: () => [{ bytes: [12, 0] }],
|
|
1895
|
+
},
|
|
1896
|
+
addExternalPlugin: external("addExternalPlugin", 22, false),
|
|
1897
|
+
removeExternalPlugin: external("removeExternalPlugin", 24, false),
|
|
1898
|
+
writeExternalPluginData: external("writeExternalPluginData", 28, false),
|
|
1899
|
+
addCollectionExternalPlugin: external("addCollectionExternalPlugin", 23, true),
|
|
1900
|
+
removeCollectionExternalPlugin: external("removeCollectionExternalPlugin", 25, true),
|
|
1901
|
+
writeCollectionExternalPluginData: external("writeCollectionExternalPluginData", 29, true),
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
/** The fields of a Core object literal (`{ kind: "AppData", … }`) by name. */
|
|
1905
|
+
coreProps(node, what) {
|
|
1906
|
+
let n = node;
|
|
1907
|
+
while (ts.isParenthesizedExpression(n) || ts.isAsExpression(n) || ts.isSatisfiesExpression(n))
|
|
1908
|
+
n = n.expression;
|
|
1909
|
+
if (!ts.isObjectLiteralExpression(n))
|
|
1910
|
+
this.fail(n, `${what} is an object literal: { kind: "…", … }`);
|
|
1911
|
+
const props = new Map();
|
|
1912
|
+
for (const q of n.properties) {
|
|
1913
|
+
if (ts.isPropertyAssignment(q) && ts.isIdentifier(q.name))
|
|
1914
|
+
props.set(q.name.text, q.initializer);
|
|
1915
|
+
else if (ts.isShorthandPropertyAssignment(q))
|
|
1916
|
+
props.set(q.name.text, q.name);
|
|
1917
|
+
else
|
|
1918
|
+
this.fail(q, "write the fields as name: value");
|
|
1919
|
+
}
|
|
1920
|
+
return props;
|
|
1921
|
+
}
|
|
1922
|
+
/** PluginAuthority: "none" | "owner" | "updateAuthority" (literals), or an address. */
|
|
1923
|
+
coreAuthority(n) {
|
|
1924
|
+
if (ts.isStringLiteral(n)) {
|
|
1925
|
+
const i = ["none", "owner", "updateAuthority"].indexOf(n.text);
|
|
1926
|
+
if (i < 0)
|
|
1927
|
+
this.fail(n, 'an authority is "none", "owner", "updateAuthority" or a Pubkey');
|
|
1928
|
+
return [{ bytes: [i] }];
|
|
1929
|
+
}
|
|
1930
|
+
return [{ bytes: [3] }, { value: n, type: PUBKEY }];
|
|
1931
|
+
}
|
|
1932
|
+
coreOption(n, inner) {
|
|
1933
|
+
return n ? [{ bytes: [1] }, ...inner(n)] : [{ bytes: [0] }];
|
|
1934
|
+
}
|
|
1935
|
+
coreSchema(n) {
|
|
1936
|
+
const i = ts.isStringLiteral(n) ? ["binary", "json", "msgpack"].indexOf(n.text) : -1;
|
|
1937
|
+
if (i < 0)
|
|
1938
|
+
this.fail(n, 'schema is "binary", "json" or "msgpack"');
|
|
1939
|
+
return [{ bytes: [i] }];
|
|
1940
|
+
}
|
|
1941
|
+
/** Vec<(HookableLifecycleEvent, ExternalCheckResult)>: `[{ event: "transfer", canReject: true }, …]`. */
|
|
1942
|
+
coreChecks(n, at) {
|
|
1943
|
+
if (!n || !ts.isArrayLiteralExpression(n))
|
|
1944
|
+
this.fail(n ?? at, 'checks is a list: [{ event: "transfer", canApprove: true, canReject: true }]');
|
|
1945
|
+
const out = [{ bytes: [n.elements.length & 0xff, 0, 0, 0] }];
|
|
1946
|
+
for (const e of n.elements) {
|
|
1947
|
+
const p = this.coreProps(e, "a lifecycle check");
|
|
1948
|
+
const ev = p.get("event");
|
|
1949
|
+
const i = ev && ts.isStringLiteral(ev) ? ["create", "transfer", "burn", "update", "execute"].indexOf(ev.text) : -1;
|
|
1950
|
+
if (i < 0)
|
|
1951
|
+
this.fail(ev ?? e, 'event is "create", "transfer", "burn", "update" or "execute"');
|
|
1952
|
+
let flags = 0;
|
|
1953
|
+
for (const [k, bit] of [
|
|
1954
|
+
["canListen", 1],
|
|
1955
|
+
["canApprove", 2],
|
|
1956
|
+
["canReject", 4],
|
|
1957
|
+
]) {
|
|
1958
|
+
const v = p.get(k);
|
|
1959
|
+
if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
|
|
1960
|
+
this.fail(v, `${k} is true or false`);
|
|
1961
|
+
if (v?.kind === ts.SyntaxKind.TrueKeyword)
|
|
1962
|
+
flags |= bit;
|
|
1963
|
+
}
|
|
1964
|
+
for (const k of p.keys())
|
|
1965
|
+
if (!["event", "canListen", "canApprove", "canReject"].includes(k))
|
|
1966
|
+
this.fail(e, `a lifecycle check has event, canListen, canApprove and canReject, not ${k}`);
|
|
1967
|
+
out.push({ bytes: [i, flags, 0, 0, 0] });
|
|
1968
|
+
}
|
|
1969
|
+
return out;
|
|
1970
|
+
}
|
|
1971
|
+
/** ExtraAccount: `{ kind: "address", address, isSigner?, isWritable? }` or a preconfigured one (`{ kind: "owner" }`, …). */
|
|
1972
|
+
coreExtraAccount(n) {
|
|
1973
|
+
const p = this.coreProps(n, "an extra account");
|
|
1974
|
+
const kind = p.get("kind");
|
|
1975
|
+
const kinds = ["program", "collection", "owner", "recipient", "asset", "customPda", "address"];
|
|
1976
|
+
const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
|
|
1977
|
+
if (i < 0 || i === 5)
|
|
1978
|
+
this.fail(kind ?? n, 'an extra account is { kind: "address", address } or "program", "collection", "owner", "recipient", "asset"');
|
|
1979
|
+
const flag = (k) => {
|
|
1980
|
+
const v = p.get(k);
|
|
1981
|
+
if (v && v.kind !== ts.SyntaxKind.TrueKeyword && v.kind !== ts.SyntaxKind.FalseKeyword)
|
|
1982
|
+
this.fail(v, `${k} is true or false`);
|
|
1983
|
+
return v?.kind === ts.SyntaxKind.TrueKeyword ? 1 : 0;
|
|
1984
|
+
};
|
|
1985
|
+
if (i === 6) {
|
|
1986
|
+
const addr = p.get("address");
|
|
1987
|
+
if (!addr)
|
|
1988
|
+
this.fail(n, "an address extra account needs address");
|
|
1989
|
+
return [{ bytes: [6] }, { value: addr, type: PUBKEY }, { bytes: [flag("isSigner"), flag("isWritable")] }];
|
|
1990
|
+
}
|
|
1991
|
+
return [{ bytes: [i, flag("isSigner"), flag("isWritable")] }];
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* ExternalPluginAdapterInitInfo from a literal: `{ kind: "AppData", dataAuthority, schema? }`, `{ kind: "Oracle",
|
|
1995
|
+
* baseAddress, checks, resultsOffset?, baseAddressConfig? }`, `{ kind: "LifecycleHook", hookedProgram, checks,
|
|
1996
|
+
* extraAccounts?, dataAuthority?, schema? }` and the Linked forms (collections); `authority?` is the plugin's own.
|
|
1997
|
+
*/
|
|
1998
|
+
coreExternalPlugin(n) {
|
|
1999
|
+
const p = this.coreProps(n, "a Core external plugin");
|
|
2000
|
+
const kind = p.get("kind");
|
|
2001
|
+
const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
|
|
2002
|
+
const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
|
|
2003
|
+
if (i < 0)
|
|
2004
|
+
this.fail(kind ?? n, `a Core external plugin is ${kinds.join(", ")}`);
|
|
2005
|
+
const allowed = {
|
|
2006
|
+
0: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
|
|
2007
|
+
1: ["baseAddress", "checks", "baseAddressConfig", "resultsOffset"],
|
|
2008
|
+
2: ["dataAuthority", "schema"],
|
|
2009
|
+
3: ["hookedProgram", "checks", "extraAccounts", "dataAuthority", "schema"],
|
|
2010
|
+
4: ["dataAuthority", "schema"],
|
|
2011
|
+
};
|
|
2012
|
+
for (const k of p.keys())
|
|
2013
|
+
if (k !== "kind" && k !== "authority" && !allowed[i].includes(k))
|
|
2014
|
+
this.fail(n, `${kinds[i]} takes ${allowed[i].join(", ")} and authority, not ${k}`);
|
|
2015
|
+
const need = (k) => p.get(k) ?? this.fail(n, `${kinds[i]} needs ${k}`);
|
|
2016
|
+
const auth = this.coreOption(p.get("authority"), (x) => this.coreAuthority(x));
|
|
2017
|
+
const schema = this.coreOption(p.get("schema"), (x) => this.coreSchema(x));
|
|
2018
|
+
if (i === 2 || i === 4)
|
|
2019
|
+
return [{ bytes: [i] }, ...this.coreAuthority(need("dataAuthority")), ...auth, ...schema];
|
|
2020
|
+
if (i === 1) {
|
|
2021
|
+
const offset = p.get("resultsOffset");
|
|
2022
|
+
const off = !offset
|
|
2023
|
+
? [{ bytes: [0] }]
|
|
2024
|
+
: ts.isStringLiteral(offset) && (offset.text === "none" || offset.text === "anchor")
|
|
2025
|
+
? [{ bytes: [1, offset.text === "none" ? 0 : 1] }]
|
|
2026
|
+
: [{ bytes: [1, 2] }, { value: offset, type: U64 }];
|
|
2027
|
+
return [
|
|
2028
|
+
{ bytes: [1] },
|
|
2029
|
+
{ value: need("baseAddress"), type: PUBKEY },
|
|
2030
|
+
...auth,
|
|
2031
|
+
...this.coreChecks(need("checks"), n),
|
|
2032
|
+
...this.coreOption(p.get("baseAddressConfig"), (x) => this.coreExtraAccount(x)),
|
|
2033
|
+
...off,
|
|
2034
|
+
];
|
|
2035
|
+
}
|
|
2036
|
+
const extras = p.get("extraAccounts");
|
|
2037
|
+
if (extras && !ts.isArrayLiteralExpression(extras))
|
|
2038
|
+
this.fail(extras, "extraAccounts is a list of extra accounts");
|
|
2039
|
+
return [
|
|
2040
|
+
{ bytes: [i] },
|
|
2041
|
+
{ value: need("hookedProgram"), type: PUBKEY },
|
|
2042
|
+
...auth,
|
|
2043
|
+
...this.coreChecks(need("checks"), n),
|
|
2044
|
+
...(extras ? [{ bytes: [1, extras.elements.length & 0xff, 0, 0, 0] }, ...extras.elements.flatMap((e) => this.coreExtraAccount(e))] : [{ bytes: [0] }]),
|
|
2045
|
+
...this.coreOption(p.get("dataAuthority"), (x) => this.coreAuthority(x)),
|
|
2046
|
+
...schema,
|
|
2047
|
+
];
|
|
2048
|
+
}
|
|
2049
|
+
/** ExternalPluginAdapterKey: `{ kind: "AppData", dataAuthority }`, `{ kind: "Oracle", address }`, `{ kind: "LifecycleHook", address }`, … */
|
|
2050
|
+
coreExternalKey(n) {
|
|
2051
|
+
const p = this.coreProps(n, "a Core external plugin key");
|
|
2052
|
+
const kind = p.get("kind");
|
|
2053
|
+
const kinds = ["LifecycleHook", "Oracle", "AppData", "LinkedLifecycleHook", "LinkedAppData"];
|
|
2054
|
+
const i = kind && ts.isStringLiteral(kind) ? kinds.indexOf(kind.text) : -1;
|
|
2055
|
+
if (i < 0)
|
|
2056
|
+
this.fail(kind ?? n, `a Core external plugin key is ${kinds.join(", ")}`);
|
|
2057
|
+
if (i === 2 || i === 4)
|
|
2058
|
+
return [{ bytes: [i] }, ...this.coreAuthority(p.get("dataAuthority") ?? this.fail(n, `${kinds[i]}'s key is its dataAuthority`))];
|
|
2059
|
+
return [
|
|
2060
|
+
{ bytes: [i] },
|
|
2061
|
+
{ value: p.get("address") ?? this.fail(n, `${kinds[i]}'s key is its address (the oracle's base address or the hooked program)`), type: PUBKEY },
|
|
2062
|
+
];
|
|
2063
|
+
}
|
|
2064
|
+
/** The data written: Option<Vec<u8>> from a string, a Vec<u8> argument, or null (clears it). */
|
|
2065
|
+
coreExternalData(n) {
|
|
2066
|
+
if (n.kind === ts.SyntaxKind.NullKeyword)
|
|
2067
|
+
return [{ bytes: [0] }];
|
|
2068
|
+
const l = ts.isIdentifier(n) ? this.lookup(n.text) : undefined;
|
|
2069
|
+
if (l?.type.kind === "vecArg") {
|
|
2070
|
+
if (l.type.elem.kind !== "int" || l.type.elem.name !== "u8")
|
|
2071
|
+
this.fail(n, "data is a string, a Vec<u8> argument or null");
|
|
2072
|
+
const t = this.tmp("dl");
|
|
2073
|
+
this.line(`uint32_t ${t} = (uint32_t)${l.c}.len;`);
|
|
2074
|
+
return [{ bytes: [1] }, { c: `&${t}`, n: "4" }, { c: `${l.c}.ptr`, n: `(uint64_t)${t}` }];
|
|
1832
2075
|
}
|
|
1833
|
-
|
|
2076
|
+
return [{ bytes: [1] }, { value: n, type: STRING }];
|
|
1834
2077
|
}
|
|
1835
2078
|
/** PluginType (u8) from a kind string literal: removePlugin(…, "FreezeDelegate"). */
|
|
1836
2079
|
corePluginType(n) {
|
|
@@ -32,17 +32,25 @@ export declare abstract class ExpressionEmitter extends StorageEmitter {
|
|
|
32
32
|
text: string;
|
|
33
33
|
arr?: undefined;
|
|
34
34
|
n?: undefined;
|
|
35
|
+
only?: undefined;
|
|
35
36
|
} | {
|
|
36
37
|
text?: undefined;
|
|
37
38
|
arr: string;
|
|
38
39
|
n: number;
|
|
40
|
+
only?: undefined;
|
|
41
|
+
} | {
|
|
42
|
+
only: string;
|
|
43
|
+
text?: undefined;
|
|
39
44
|
};
|
|
40
45
|
/**
|
|
41
46
|
* The text of `${e}` when it is known at compile time: a string literal or constant, a non-negative integer
|
|
42
47
|
* literal, a boolean literal, or a Pubkey constant (its base58). Undefined otherwise.
|
|
43
48
|
*/
|
|
44
49
|
protected constantText(e: ts.Expression): string | undefined;
|
|
45
|
-
|
|
50
|
+
/** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
|
|
51
|
+
protected toText(node: ts.Expression, converted?: {
|
|
52
|
+
yes: boolean;
|
|
53
|
+
}): string;
|
|
46
54
|
/** C statement writing the decimal text of an integer into the TS_Str `out`. */
|
|
47
55
|
protected textOfInt(name: IntName, c: string, out: string): string;
|
|
48
56
|
/** `c ? a : b` on values: only the chosen branch runs (its checks and side effects included). */
|
|
@@ -128,7 +128,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
128
128
|
if (ts.isNonNullExpression(node)) {
|
|
129
129
|
const v = this.expr(node.expression, hint && hint.kind !== "option" ? { kind: "option", inner: hint } : hint);
|
|
130
130
|
if (v.type.kind !== "option")
|
|
131
|
-
this.fail(node, "! works on Option values
|
|
131
|
+
this.fail(node, "! works on Option values and optional accounts (acct!.field); check other values with `x !== null`");
|
|
132
132
|
return this.unwrap(v, node);
|
|
133
133
|
}
|
|
134
134
|
if (ts.isPropertyAccessExpression(node) && node.questionDotToken)
|
|
@@ -154,6 +154,8 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
154
154
|
if (ts.isTemplateExpression(node)) {
|
|
155
155
|
// `a${x}b${y}c`: one join on the heap (or a literal when every piece is known at compile time).
|
|
156
156
|
const pieces = this.templatePieces(node);
|
|
157
|
+
if (pieces.only !== undefined)
|
|
158
|
+
return { c: pieces.only, type: STRING };
|
|
157
159
|
if (pieces.text !== undefined)
|
|
158
160
|
return { c: `ts_str((const uint8_t *)${cString(pieces.text)}, ${Buffer.byteLength(pieces.text, "utf8")})`, type: STRING, stringLiteral: pieces.text };
|
|
159
161
|
const t = this.tmp("tpl");
|
|
@@ -171,7 +173,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
171
173
|
const l = this.lookup(node.text);
|
|
172
174
|
if (l) {
|
|
173
175
|
if (l.closure)
|
|
174
|
-
this.fail(node, `"${node.text}" is a function:
|
|
176
|
+
this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
|
|
175
177
|
if (l.type.kind === "array")
|
|
176
178
|
this.fail(node, `"${node.text}" is an array; use .get(i)`);
|
|
177
179
|
if (l.type.kind === "vec")
|
|
@@ -204,7 +206,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
204
206
|
if (this.accounts.has(node.text))
|
|
205
207
|
this.fail(node, `"${node.text}" is an account; use ${node.text}.key for its address`);
|
|
206
208
|
if (calleeName(node, this.model.helperDecls, this.checker) || this.model.helpers.has(node.text))
|
|
207
|
-
this.fail(node, `"${node.text}" is a function:
|
|
209
|
+
this.fail(node, `"${node.text}" is a function: call it (${node.text}(…)), bind it with const, or pass it to a helper or list method; it cannot be stored or returned as a value`);
|
|
208
210
|
this.fail(node, `unknown name "${node.text}"`);
|
|
209
211
|
}
|
|
210
212
|
if (ts.isPropertyAccessExpression(node))
|
|
@@ -455,16 +457,20 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
455
457
|
lit(known);
|
|
456
458
|
else {
|
|
457
459
|
const mark = this.lines.length;
|
|
458
|
-
const
|
|
460
|
+
const converted = { yes: false };
|
|
461
|
+
const c = this.toText(span.expression, converted);
|
|
459
462
|
for (const p of parts)
|
|
460
463
|
if (p.c !== undefined)
|
|
461
464
|
p.c = this.settle({ c: p.c, type: STRING }, mark).c;
|
|
462
|
-
parts.push({ c });
|
|
465
|
+
parts.push({ c, converted: converted.yes });
|
|
463
466
|
}
|
|
464
467
|
lit(span.literal.text);
|
|
465
468
|
}
|
|
466
469
|
if (parts.every((p) => p.text !== undefined))
|
|
467
470
|
return { text: parts.map((p) => p.text).join("") };
|
|
471
|
+
// `${n}` alone: the text a conversion just made (or a constant one) is already a string of its own; no join.
|
|
472
|
+
if (parts.length === 1 && parts[0].converted)
|
|
473
|
+
return { only: parts[0].c };
|
|
468
474
|
const strOf = (text) => `ts_str((const uint8_t *)${cString(text)}, ${Buffer.byteLength(text, "utf8")})`;
|
|
469
475
|
const arr = this.tmp("parts");
|
|
470
476
|
this.line(`TS_Str ${arr}[${parts.length}] = {${parts.map((p) => (p.text !== undefined ? strOf(p.text) : p.c)).join(", ")}};`);
|
|
@@ -494,10 +500,13 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
494
500
|
}
|
|
495
501
|
return undefined;
|
|
496
502
|
}
|
|
497
|
-
|
|
503
|
+
/** `${node}`'s text; `converted.yes` when it was made from a non-string value (new text, not a view of other text). */
|
|
504
|
+
toText(node, converted) {
|
|
498
505
|
const v = this.expr(node);
|
|
499
506
|
if (v.type.kind === "string")
|
|
500
507
|
return v.c;
|
|
508
|
+
if (converted)
|
|
509
|
+
converted.yes = true;
|
|
501
510
|
if (v.type.kind === "option" && (v.type.inner.kind === "int" || v.type.inner.kind === "bool")) {
|
|
502
511
|
// `${opt}`: the value, or "null" like JS.
|
|
503
512
|
const inner = v.type.inner;
|
|
@@ -557,12 +566,7 @@ export class ExpressionEmitter extends StorageEmitter {
|
|
|
557
566
|
(ts.isBigIntLiteral(node.whenFalse) || ts.isPrefixUnaryExpression(node.whenFalse));
|
|
558
567
|
if (!type && !bothLiteral && (ts.isBigIntLiteral(node.whenTrue) || node.whenTrue.kind === ts.SyntaxKind.NullKeyword)) {
|
|
559
568
|
// Type the non-literal branch first, without emitting it, to give the literal its type.
|
|
560
|
-
const
|
|
561
|
-
const probe = this.reporter.guard(() => this.expr(node.whenFalse));
|
|
562
|
-
this.lines.length = saved.lines;
|
|
563
|
-
this.hoisted.length = saved.hoisted; // a probed helper call may hoist declarations
|
|
564
|
-
this.temp = saved.temp;
|
|
565
|
-
this.reporter.diagnostics.length = saved.diagnostics;
|
|
569
|
+
const probe = this.probe(() => this.expr(node.whenFalse));
|
|
566
570
|
if (probe && probe.literal === undefined && !probe.isNull)
|
|
567
571
|
type = probe.type;
|
|
568
572
|
}
|