@xo-cash/utils 0.0.6 → 0.0.7
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/dist/index.d.mts +171 -193
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +623 -155
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bigIntToVmNumber, binToHex, binToUtf8, createCompilerBch, hexToBin, sha256, utf8ToBin, vmNumberToBigInt } from "@bitauth/libauth";
|
|
1
|
+
import { IdentifierResolutionType, OpcodesBchSpec, bigIntToVmNumber, binToHex, binToUtf8, createCompilerBch, describeExpectedInput, generateBytecodeMap, hexToBin, parseScript, sha256, utf8ToBin, vmNumberToBigInt } from "@bitauth/libauth";
|
|
2
2
|
import { BchVmVersions, XOTemplateLockingTypes, XOTemplateNftCapabilities, XOTemplatePrimitiveTypes } from "@xo-cash/types";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { FungibleTokenAmount, NFTCommitment, PublicKey, Satoshis, SchnorrSignature, TemplateIdentifier, Timestamp, TokenCategory, TransactionHash } from "@xo-cash/primitives";
|
|
@@ -1636,10 +1636,15 @@ const parseTemplate = (inputTemplate) => {
|
|
|
1636
1636
|
* Error thrown when a required variable is missing.
|
|
1637
1637
|
*/
|
|
1638
1638
|
var CashAssemblyRequiredVariableMissingError = class extends Error {
|
|
1639
|
-
|
|
1639
|
+
/**
|
|
1640
|
+
* Variable names that were required but absent from the variables map.
|
|
1641
|
+
*/
|
|
1642
|
+
variableNames;
|
|
1643
|
+
constructor(variableNames = []) {
|
|
1640
1644
|
const defaultMessage = "Missing required variable";
|
|
1641
|
-
if (variableNames
|
|
1645
|
+
if (variableNames.length > 0) super(`${defaultMessage}: variableNames [${variableNames.join(", ")}]`);
|
|
1642
1646
|
else super(defaultMessage);
|
|
1647
|
+
this.variableNames = variableNames;
|
|
1643
1648
|
}
|
|
1644
1649
|
};
|
|
1645
1650
|
/**
|
|
@@ -1652,6 +1657,87 @@ var CashAssemblyCompilationFailedError = class extends Error {
|
|
|
1652
1657
|
}
|
|
1653
1658
|
};
|
|
1654
1659
|
/**
|
|
1660
|
+
* Error thrown when a quoted string inside `$()` never closes.
|
|
1661
|
+
*/
|
|
1662
|
+
var CashAssemblyQuotedLiteralUnclosedError = class extends Error {
|
|
1663
|
+
/**
|
|
1664
|
+
* Index of the opening quote that never found a closer.
|
|
1665
|
+
*/
|
|
1666
|
+
openingQuoteIndex;
|
|
1667
|
+
/**
|
|
1668
|
+
* Quote character that opened the literal.
|
|
1669
|
+
*/
|
|
1670
|
+
quoteCharacter;
|
|
1671
|
+
/**
|
|
1672
|
+
* Text from the opening quote through the end of the scanned string.
|
|
1673
|
+
*/
|
|
1674
|
+
unclosedText;
|
|
1675
|
+
constructor(openingQuoteIndex, quoteCharacter, cashAssemblyText) {
|
|
1676
|
+
const unclosedText = cashAssemblyText.slice(openingQuoteIndex);
|
|
1677
|
+
const defaultMessage = "Quoted literal in a CashAssembly evaluation is unclosed";
|
|
1678
|
+
const details = [
|
|
1679
|
+
`quoteCharacter ${JSON.stringify(quoteCharacter)}`,
|
|
1680
|
+
`openingQuoteIndex ${String(openingQuoteIndex)}`,
|
|
1681
|
+
`unclosedText ${JSON.stringify(unclosedText)}`
|
|
1682
|
+
].join(", ");
|
|
1683
|
+
super(`${defaultMessage}: ${details}`);
|
|
1684
|
+
this.openingQuoteIndex = openingQuoteIndex;
|
|
1685
|
+
this.quoteCharacter = quoteCharacter;
|
|
1686
|
+
this.unclosedText = unclosedText;
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
/**
|
|
1690
|
+
* Error thrown when a block comment inside `$()` never closes.
|
|
1691
|
+
*/
|
|
1692
|
+
var CashAssemblyBlockCommentUnclosedError = class extends Error {
|
|
1693
|
+
/**
|
|
1694
|
+
* Index of the first slash of the block comment opener.
|
|
1695
|
+
*/
|
|
1696
|
+
commentStartIndex;
|
|
1697
|
+
/**
|
|
1698
|
+
* Text from the block comment opener through the end of the scanned string.
|
|
1699
|
+
*/
|
|
1700
|
+
unclosedText;
|
|
1701
|
+
constructor(commentStartIndex, cashAssemblyText) {
|
|
1702
|
+
const unclosedText = cashAssemblyText.slice(commentStartIndex);
|
|
1703
|
+
const defaultMessage = "Block comment in a CashAssembly evaluation is unclosed";
|
|
1704
|
+
const details = [`commentStartIndex ${String(commentStartIndex)}`, `unclosedText ${JSON.stringify(unclosedText)}`].join(", ");
|
|
1705
|
+
super(`${defaultMessage}: ${details}`);
|
|
1706
|
+
this.commentStartIndex = commentStartIndex;
|
|
1707
|
+
this.unclosedText = unclosedText;
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
/**
|
|
1711
|
+
* Error thrown when a `$()` evaluation never finds its matching closer.
|
|
1712
|
+
*/
|
|
1713
|
+
var CashAssemblyEvaluationUnclosedError = class extends Error {
|
|
1714
|
+
/**
|
|
1715
|
+
* Index of the `$` that opened the evaluation.
|
|
1716
|
+
*/
|
|
1717
|
+
evaluationStartIndex;
|
|
1718
|
+
/**
|
|
1719
|
+
* How many `$()` remain open at the end of the string, including nested evaluations.
|
|
1720
|
+
*/
|
|
1721
|
+
remainingOpenEvaluations;
|
|
1722
|
+
/**
|
|
1723
|
+
* Text from the opening `$` through the end of the scanned string.
|
|
1724
|
+
*/
|
|
1725
|
+
unclosedText;
|
|
1726
|
+
constructor(evaluationStartIndex, remainingOpenEvaluations, cashAssemblyText) {
|
|
1727
|
+
const unclosedText = cashAssemblyText.slice(evaluationStartIndex);
|
|
1728
|
+
const defaultMessage = "CashAssembly evaluation is unclosed";
|
|
1729
|
+
const details = [
|
|
1730
|
+
`evaluationStartIndex ${String(evaluationStartIndex)}`,
|
|
1731
|
+
`remainingOpenEvaluations ${String(remainingOpenEvaluations)}`,
|
|
1732
|
+
`unclosedText ${JSON.stringify(unclosedText)}`
|
|
1733
|
+
].join(", ");
|
|
1734
|
+
super(`${defaultMessage}: ${details}`);
|
|
1735
|
+
this.evaluationStartIndex = evaluationStartIndex;
|
|
1736
|
+
this.remainingOpenEvaluations = remainingOpenEvaluations;
|
|
1737
|
+
this.unclosedText = unclosedText;
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
/**
|
|
1655
1741
|
* Error thrown when a variable's runtime type does not match the type required for compilation.
|
|
1656
1742
|
*/
|
|
1657
1743
|
var CashAssemblyVariableTypeMismatchError = class extends Error {
|
|
@@ -1692,6 +1778,24 @@ var CashAssemblyPrimitiveVariableMissingError = class extends Error {
|
|
|
1692
1778
|
}
|
|
1693
1779
|
};
|
|
1694
1780
|
/**
|
|
1781
|
+
* Error thrown when one identifier would resolve as more than one {@link IdentifierResolutionType}.
|
|
1782
|
+
*/
|
|
1783
|
+
var CashAssemblyIdentifierCollisionError = class extends Error {
|
|
1784
|
+
/**
|
|
1785
|
+
* Identifier that more than one resolution type would match.
|
|
1786
|
+
*/
|
|
1787
|
+
identifier;
|
|
1788
|
+
/**
|
|
1789
|
+
* The list of resolution types that the identifier matches.
|
|
1790
|
+
*/
|
|
1791
|
+
resolutionTypes;
|
|
1792
|
+
constructor(identifier, resolutionTypes) {
|
|
1793
|
+
super(`CashAssembly identifier exists for more than one resolution type: identifier "${identifier}", resolutionTypes [${resolutionTypes.join(", ")}]`);
|
|
1794
|
+
this.identifier = identifier;
|
|
1795
|
+
this.resolutionTypes = resolutionTypes;
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
/**
|
|
1695
1799
|
* Error thrown when compiled evaluation bytes cannot be decoded as a VM number.
|
|
1696
1800
|
*/
|
|
1697
1801
|
var CashAssemblyVmNumberDecodeError = class extends Error {
|
|
@@ -1701,64 +1805,135 @@ var CashAssemblyVmNumberDecodeError = class extends Error {
|
|
|
1701
1805
|
};
|
|
1702
1806
|
|
|
1703
1807
|
//#endregion
|
|
1704
|
-
//#region source/cash-assembly/
|
|
1808
|
+
//#region source/cash-assembly/collect-evaluations.ts
|
|
1705
1809
|
/**
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1708
|
-
* CashAssembly expressions look like `$(<variable>)` or `$(<a> <b>)`. This pattern checks
|
|
1709
|
-
* that the entire string is one such expression and nothing else. It will not match if
|
|
1710
|
-
* there is other text surrounding the expression.
|
|
1810
|
+
* Enqueues a template script id so its source can be visited when it exists in the scripts map.
|
|
1711
1811
|
*
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1715
|
-
*
|
|
1812
|
+
* @param {string} scriptIdentifier - Script id from an evaluation or nested script source.
|
|
1813
|
+
* @param {ReadonlySet<string>} knownScriptIdentifiers - Script ids present in the template scripts map.
|
|
1814
|
+
* @param {ReadonlySet<string>} visitedScriptIdentifiers - Script ids whose sources were already visited.
|
|
1815
|
+
* @param {string[]} scriptIdentifiersToVisit - Pending script ids queued to visit.
|
|
1716
1816
|
*/
|
|
1717
|
-
const
|
|
1817
|
+
const enqueueReachableTemplateScriptIdentifier = (scriptIdentifier, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit) => {
|
|
1818
|
+
if (knownScriptIdentifiers.has(scriptIdentifier) === false) return;
|
|
1819
|
+
if (visitedScriptIdentifiers.has(scriptIdentifier) === true) return;
|
|
1820
|
+
if (scriptIdentifiersToVisit.includes(scriptIdentifier) === true) return;
|
|
1821
|
+
scriptIdentifiersToVisit.push(scriptIdentifier);
|
|
1822
|
+
};
|
|
1718
1823
|
/**
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1721
|
-
* An evaluation looks like `$(...)`, for example `$(<fee>)` or `$(<a> <b>)`. This pattern
|
|
1722
|
-
* locates every occurrence in the input and returns them all (global flag `g`).
|
|
1723
|
-
* Empty evaluations `$()` are intentionally excluded because they reference no variables.
|
|
1824
|
+
* Parses CashAssembly source with parseScript and throws when the parse fails.
|
|
1724
1825
|
*
|
|
1725
|
-
*
|
|
1726
|
-
*
|
|
1826
|
+
* @param {string} cashAssemblyText - Evaluation or script source to parse.
|
|
1827
|
+
* @returns {CashAssemblyScriptSegment} - Parsed Script root.
|
|
1828
|
+
* @throws {@link CashAssemblyCompilationFailedError} - When parseScript rejects the source.
|
|
1727
1829
|
*/
|
|
1728
|
-
const
|
|
1830
|
+
const parseCashAssemblySource = (cashAssemblyText) => {
|
|
1831
|
+
const parseResult = parseScript(cashAssemblyText);
|
|
1832
|
+
if (parseResult.status === false) throw new CashAssemblyCompilationFailedError(`${describeExpectedInput(parseResult.expected)} Line ${String(parseResult.index.line)}, column ${String(parseResult.index.column)}.`);
|
|
1833
|
+
return parseResult.value;
|
|
1834
|
+
};
|
|
1729
1835
|
/**
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1732
|
-
* Inside an evaluation like `$(<pubkeyHash> <fee>)`, variables are referenced as `<name>`.
|
|
1733
|
-
* This pattern captures the name between the brackets. The global flag `g` allows iterating
|
|
1734
|
-
* over every variable reference in a single evaluation string.
|
|
1836
|
+
* Identifier visitor for the parseScript tree.
|
|
1837
|
+
* Records WalletData or queues a nested script from one Identifier node.
|
|
1735
1838
|
*
|
|
1736
|
-
*
|
|
1737
|
-
* `["pubkeyHash", "fee"]`.
|
|
1839
|
+
* @param {CollectFromParsedIdentifierParameters} parameters - Identifier text and the parse tree context.
|
|
1738
1840
|
*/
|
|
1739
|
-
const
|
|
1841
|
+
const collectFromParsedIdentifier = (parameters) => {
|
|
1842
|
+
const { identifier, isDirectPushContent, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit, variableNames } = parameters;
|
|
1843
|
+
if (knownScriptIdentifiers.has(identifier) === true) {
|
|
1844
|
+
enqueueReachableTemplateScriptIdentifier(identifier, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit);
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (isDirectPushContent === true) variableNames.add(identifier);
|
|
1848
|
+
};
|
|
1740
1849
|
/**
|
|
1741
|
-
*
|
|
1742
|
-
*
|
|
1743
|
-
*
|
|
1744
|
-
*
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
const
|
|
1850
|
+
* Visitor for one Script node in the parseScript tree.
|
|
1851
|
+
* Recurses into Push and Evaluation children and dispatches Identifier nodes.
|
|
1852
|
+
*
|
|
1853
|
+
* @param {CollectFromParsedScriptParameters} parameters - Parsed Script, Push depth flag, and parse tree context.
|
|
1854
|
+
*/
|
|
1855
|
+
const collectFromParsedScript = (parameters) => {
|
|
1856
|
+
const { scriptSegment, isDirectPushContent, knownScriptIdentifiers, visitedScriptIdentifiers, scriptIdentifiersToVisit, variableNames } = parameters;
|
|
1857
|
+
for (const child of scriptSegment.value) {
|
|
1858
|
+
if (child.name === "Push") {
|
|
1859
|
+
collectFromParsedScript({
|
|
1860
|
+
scriptSegment: child.value,
|
|
1861
|
+
isDirectPushContent: true,
|
|
1862
|
+
knownScriptIdentifiers,
|
|
1863
|
+
visitedScriptIdentifiers,
|
|
1864
|
+
scriptIdentifiersToVisit,
|
|
1865
|
+
variableNames
|
|
1866
|
+
});
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if (child.name === "Evaluation") {
|
|
1870
|
+
collectFromParsedScript({
|
|
1871
|
+
scriptSegment: child.value,
|
|
1872
|
+
isDirectPushContent: false,
|
|
1873
|
+
knownScriptIdentifiers,
|
|
1874
|
+
visitedScriptIdentifiers,
|
|
1875
|
+
scriptIdentifiersToVisit,
|
|
1876
|
+
variableNames
|
|
1877
|
+
});
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
if (child.name === "Identifier") collectFromParsedIdentifier({
|
|
1881
|
+
identifier: child.value,
|
|
1882
|
+
isDirectPushContent,
|
|
1883
|
+
knownScriptIdentifiers,
|
|
1884
|
+
visitedScriptIdentifiers,
|
|
1885
|
+
scriptIdentifiersToVisit,
|
|
1886
|
+
variableNames
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1749
1890
|
/**
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
|
|
1761
|
-
|
|
1891
|
+
* Collects WalletData names from the parseScript tree using the visitor pattern.
|
|
1892
|
+
*
|
|
1893
|
+
* parseScript returns a tree of Script, Push, Evaluation, and Identifier nodes. Nested Push and
|
|
1894
|
+
* Evaluation nodes are entered immediately. A template script id is queued whether it appears inside a Push or next to
|
|
1895
|
+
* opcodes. Queued script sources are visited after the current tree finishes, in the order they were
|
|
1896
|
+
* first seen.
|
|
1897
|
+
* A visited id is skipped on later encounters. Opcodes and literals are ignored. Unrelated scripts
|
|
1898
|
+
* in the map are not visited. An Identifier inside a nested Push is collected from that inner Push,
|
|
1899
|
+
* for example `<$(<ownerKey.public_key> OP_HASH160)>` collects `ownerKey.public_key`.
|
|
1900
|
+
*
|
|
1901
|
+
* @param {string} evaluation - CashAssembly evaluation or script id text to scan.
|
|
1902
|
+
* @param {CashAssemblyTemplateScripts} [templateScripts] - Optional template scripts map used to visit nested script ids.
|
|
1903
|
+
* @returns {string[]} - WalletData names from reachable Pushes. Script ids are omitted when
|
|
1904
|
+
* `templateScripts` is provided.
|
|
1905
|
+
* @throws {@link CashAssemblyCompilationFailedError} - When parseScript rejects the starting text or a visited script source.
|
|
1906
|
+
*/
|
|
1907
|
+
const collectVariablesUsingParseScript = (evaluation, templateScripts = {}) => {
|
|
1908
|
+
const knownScriptIdentifiers = new Set(Object.keys(templateScripts));
|
|
1909
|
+
const visitedScriptIdentifiers = /* @__PURE__ */ new Set();
|
|
1910
|
+
const scriptIdentifiersToVisit = [];
|
|
1911
|
+
const variableNames = /* @__PURE__ */ new Set();
|
|
1912
|
+
const parseTreeContext = {
|
|
1913
|
+
knownScriptIdentifiers,
|
|
1914
|
+
visitedScriptIdentifiers,
|
|
1915
|
+
scriptIdentifiersToVisit,
|
|
1916
|
+
variableNames
|
|
1917
|
+
};
|
|
1918
|
+
const startingScriptSegment = parseCashAssemblySource(evaluation);
|
|
1919
|
+
collectFromParsedScript({
|
|
1920
|
+
...parseTreeContext,
|
|
1921
|
+
scriptSegment: startingScriptSegment,
|
|
1922
|
+
isDirectPushContent: false
|
|
1923
|
+
});
|
|
1924
|
+
for (const scriptIdentifier of scriptIdentifiersToVisit) {
|
|
1925
|
+
if (visitedScriptIdentifiers.has(scriptIdentifier) === true) continue;
|
|
1926
|
+
visitedScriptIdentifiers.add(scriptIdentifier);
|
|
1927
|
+
const scriptDefinition = templateScripts[scriptIdentifier];
|
|
1928
|
+
const nestedScriptSegment = parseCashAssemblySource(scriptDefinition);
|
|
1929
|
+
collectFromParsedScript({
|
|
1930
|
+
...parseTreeContext,
|
|
1931
|
+
scriptSegment: nestedScriptSegment,
|
|
1932
|
+
isDirectPushContent: false
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
return [...variableNames];
|
|
1936
|
+
};
|
|
1762
1937
|
|
|
1763
1938
|
//#endregion
|
|
1764
1939
|
//#region source/cash-assembly/bytes.ts
|
|
@@ -1767,9 +1942,9 @@ const CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN = /^([^.]+)\.([^.]+)$/;
|
|
|
1767
1942
|
*
|
|
1768
1943
|
* @param {unknown} value - Value to encode, should be one of: Uint8Array, bigint, boolean, string, or a safe integer number.
|
|
1769
1944
|
* @param {string} valueIdentifier - Identifier used in error messages.
|
|
1770
|
-
* @returns {Uint8Array} Bytes representation of the value.
|
|
1771
|
-
* @throws {@link CashAssemblyNumberNotSafeIntegerError} When a number is not a safe integer.
|
|
1772
|
-
* @throws {@link CashAssemblyUnsupportedValueTypeError} When the value type cannot be resolved.
|
|
1945
|
+
* @returns {Uint8Array} - Bytes representation of the value.
|
|
1946
|
+
* @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number is not a safe integer.
|
|
1947
|
+
* @throws {@link CashAssemblyUnsupportedValueTypeError} - When the value type cannot be resolved.
|
|
1773
1948
|
*/
|
|
1774
1949
|
const convertValueToBytes = (value, valueIdentifier) => {
|
|
1775
1950
|
if (value instanceof Uint8Array) return value;
|
|
@@ -1783,6 +1958,46 @@ const convertValueToBytes = (value, valueIdentifier) => {
|
|
|
1783
1958
|
throw new CashAssemblyUnsupportedValueTypeError(valueIdentifier, typeof value);
|
|
1784
1959
|
};
|
|
1785
1960
|
|
|
1961
|
+
//#endregion
|
|
1962
|
+
//#region source/cash-assembly/defaults.ts
|
|
1963
|
+
/**
|
|
1964
|
+
* Matches a single dot variable method reference inside an angle bracket identifier.
|
|
1965
|
+
*
|
|
1966
|
+
* Used to detect primitive method references such as `expiry.toIso8601`.
|
|
1967
|
+
*
|
|
1968
|
+
* For example `expiry.toIso8601` matches with base expiry and method toIso8601.
|
|
1969
|
+
* `requestedSatoshis` does not match because it has no method.
|
|
1970
|
+
* `key.schnorr_signature.all_outputs` does not match because it has more than one dot.
|
|
1971
|
+
*/
|
|
1972
|
+
const CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN = /^([^.]+)\.([^.]+)$/;
|
|
1973
|
+
/**
|
|
1974
|
+
* Character count of the `$(` evaluation opener, which is two characters long.
|
|
1975
|
+
*
|
|
1976
|
+
* Adding this length moves the read position to the first character after that sequence so scanning
|
|
1977
|
+
* starts on the evaluation contents rather than on the opening parenthesis.
|
|
1978
|
+
*/
|
|
1979
|
+
const CASHASSEMBLY_EVALUATION_START_LENGTH = 2;
|
|
1980
|
+
/**
|
|
1981
|
+
* Character count of the `//` and `/*` comment openers, which are both two characters long.
|
|
1982
|
+
*
|
|
1983
|
+
* Adding this length moves the read position to the first character after that sequence so the '*'
|
|
1984
|
+
* of a block comment opener cannot be reused as the '*' of its closer.
|
|
1985
|
+
*/
|
|
1986
|
+
const CASHASSEMBLY_COMMENT_START_LENGTH = 2;
|
|
1987
|
+
/**
|
|
1988
|
+
* Character count of the block comment closer, which is two characters long.
|
|
1989
|
+
*
|
|
1990
|
+
* Adding this length moves the read position to the first character after that sequence so the
|
|
1991
|
+
* closing slash cannot pair with the next character and open another comment.
|
|
1992
|
+
*/
|
|
1993
|
+
const CASHASSEMBLY_BLOCK_COMMENT_END_LENGTH = 2;
|
|
1994
|
+
/**
|
|
1995
|
+
* `$()` with nothing between the parentheses.
|
|
1996
|
+
*
|
|
1997
|
+
* Callers use this to detect an evaluation that has no contents to compile.
|
|
1998
|
+
*/
|
|
1999
|
+
const EMPTY_CASHASSEMBLY_EVALUATION = "$()";
|
|
2000
|
+
|
|
1786
2001
|
//#endregion
|
|
1787
2002
|
//#region source/cash-assembly/primitive-evaluations.ts
|
|
1788
2003
|
/**
|
|
@@ -1814,7 +2029,7 @@ const isResolvablePrimitiveType = (type) => {
|
|
|
1814
2029
|
*
|
|
1815
2030
|
* @param {ResolvablePrimitiveType} type - Method resolvable template type.
|
|
1816
2031
|
* @param {string} methodName - Method name from the evaluation text, for example `toSatoshis`.
|
|
1817
|
-
* @returns {boolean} True when that class exposes the named method.
|
|
2032
|
+
* @returns {boolean} - True when that class exposes the named method.
|
|
1818
2033
|
*/
|
|
1819
2034
|
const canResolvePrimitiveMethod = (type, methodName) => {
|
|
1820
2035
|
const PrimitiveClass = RESOLVABLE_PRIMITIVE_CLASS_BY_TYPE[type];
|
|
@@ -1855,61 +2070,232 @@ const callPrimitiveMethod = (parameters) => {
|
|
|
1855
2070
|
* @throws {@link CashAssemblyNumberNotSafeIntegerError} When a method return value is a number that is not a safe integer.
|
|
1856
2071
|
*/
|
|
1857
2072
|
const resolvePrimitiveMethodBytes = (parameters) => {
|
|
1858
|
-
const {
|
|
2073
|
+
const { variableNames, templateVariables, variables } = parameters;
|
|
1859
2074
|
if (templateVariables === void 0) return {};
|
|
1860
2075
|
const resolvedBytes = {};
|
|
1861
|
-
for (const
|
|
1862
|
-
if (Object.hasOwn(resolvedBytes,
|
|
1863
|
-
const methodReferenceMatch =
|
|
2076
|
+
for (const variableName of variableNames) {
|
|
2077
|
+
if (Object.hasOwn(resolvedBytes, variableName) === true) continue;
|
|
2078
|
+
const methodReferenceMatch = variableName.match(CASHASSEMBLY_VARIABLE_METHOD_REFERENCE_PATTERN);
|
|
1864
2079
|
if (methodReferenceMatch === null) continue;
|
|
1865
2080
|
const [, baseName, methodName] = methodReferenceMatch;
|
|
1866
2081
|
if (Object.hasOwn(templateVariables, baseName) === false) continue;
|
|
1867
2082
|
const type = templateVariables[baseName].type;
|
|
1868
2083
|
if (isResolvablePrimitiveType(type) === false) continue;
|
|
1869
|
-
if (canResolvePrimitiveMethod(type, methodName) === false) throw new CashAssemblyPrimitiveMethodMissingError(
|
|
1870
|
-
if (Object.hasOwn(variables, baseName) === false) throw new CashAssemblyPrimitiveVariableMissingError(
|
|
1871
|
-
resolvedBytes[
|
|
1872
|
-
identifier,
|
|
2084
|
+
if (canResolvePrimitiveMethod(type, methodName) === false) throw new CashAssemblyPrimitiveMethodMissingError(variableName, methodName, type);
|
|
2085
|
+
if (Object.hasOwn(variables, baseName) === false) throw new CashAssemblyPrimitiveVariableMissingError(variableName, baseName);
|
|
2086
|
+
resolvedBytes[variableName] = convertValueToBytes(callPrimitiveMethod({
|
|
2087
|
+
identifier: variableName,
|
|
1873
2088
|
methodName,
|
|
1874
2089
|
value: variables[baseName],
|
|
1875
2090
|
type
|
|
1876
|
-
}),
|
|
2091
|
+
}), variableName);
|
|
1877
2092
|
}
|
|
1878
2093
|
return resolvedBytes;
|
|
1879
2094
|
};
|
|
1880
2095
|
|
|
1881
2096
|
//#endregion
|
|
1882
|
-
//#region source/cash-assembly/
|
|
2097
|
+
//#region source/cash-assembly/identifier-collisions.ts
|
|
2098
|
+
/**
|
|
2099
|
+
* Set of opcode names that the compiler recognizes.
|
|
2100
|
+
*/
|
|
2101
|
+
const COMPILER_OPCODE_NAMES = new Set(Object.keys(generateBytecodeMap(OpcodesBchSpec)));
|
|
1883
2102
|
/**
|
|
1884
|
-
*
|
|
2103
|
+
* Libauth resolves opcodes, then variables, then scripts, and the first match silently hides the
|
|
2104
|
+
* rest, leading to unexpected compilation results and no errors. This function throws when one identifier matches more than one resolution type.
|
|
1885
2105
|
*
|
|
1886
|
-
* @param {
|
|
1887
|
-
* @
|
|
2106
|
+
* @param {AssertNoIdentifierCollisionsParameters} parameters - Provided variables and optional template scripts.
|
|
2107
|
+
* @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches more than one resolution type.
|
|
1888
2108
|
*/
|
|
1889
|
-
const
|
|
1890
|
-
|
|
2109
|
+
const assertNoIdentifierCollisions = (parameters) => {
|
|
2110
|
+
const { variables, templateScripts } = parameters;
|
|
2111
|
+
const scriptIdentifiers = new Set(Object.keys(templateScripts ?? {}));
|
|
2112
|
+
const variableNames = new Set(Object.keys(variables));
|
|
2113
|
+
const declaredIdentifiers = new Set([...variableNames, ...scriptIdentifiers]);
|
|
2114
|
+
for (const identifier of declaredIdentifiers) {
|
|
2115
|
+
const resolutionTypes = [];
|
|
2116
|
+
if (COMPILER_OPCODE_NAMES.has(identifier) === true) resolutionTypes.push(IdentifierResolutionType.opcode);
|
|
2117
|
+
if (variableNames.has(identifier) === true) resolutionTypes.push(IdentifierResolutionType.variable);
|
|
2118
|
+
if (scriptIdentifiers.has(identifier) === true) resolutionTypes.push(IdentifierResolutionType.script);
|
|
2119
|
+
if (resolutionTypes.length > 1) throw new CashAssemblyIdentifierCollisionError(identifier, resolutionTypes);
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
|
|
2123
|
+
//#endregion
|
|
2124
|
+
//#region source/cash-assembly/scan-evaluations.ts
|
|
2125
|
+
/**
|
|
2126
|
+
* Skips a quoted CashAssembly string so a `)` inside it cannot end `$()`.
|
|
2127
|
+
*
|
|
2128
|
+
* CashAssembly UTF8 literals use `"..."` or `'...'`. Everything between the quotes is payload,
|
|
2129
|
+
* including parentheses.
|
|
2130
|
+
* For example `$(<"hello)world">)` must close after the literal, not at the `)` inside the quotes.
|
|
2131
|
+
*
|
|
2132
|
+
* @param {string} cashAssemblyText - Text that contains the quoted literal.
|
|
2133
|
+
* @param {number} openingQuoteIndex - Index of the opening `"` or `'`.
|
|
2134
|
+
* @returns {number} - Index of the character after the closing quote.
|
|
2135
|
+
* @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When the quoted literal never closes.
|
|
2136
|
+
*/
|
|
2137
|
+
const skipQuotedCashAssemblyLiteral = (cashAssemblyText, openingQuoteIndex) => {
|
|
2138
|
+
const quoteCharacter = cashAssemblyText[openingQuoteIndex];
|
|
2139
|
+
let currentIndex = openingQuoteIndex + 1;
|
|
2140
|
+
while (currentIndex < cashAssemblyText.length) {
|
|
2141
|
+
if (cashAssemblyText[currentIndex] === quoteCharacter) return currentIndex + 1;
|
|
2142
|
+
currentIndex += 1;
|
|
2143
|
+
}
|
|
2144
|
+
throw new CashAssemblyQuotedLiteralUnclosedError(openingQuoteIndex, quoteCharacter, cashAssemblyText);
|
|
2145
|
+
};
|
|
2146
|
+
/**
|
|
2147
|
+
* This function skips a `//` comment so a `)` written in the comment cannot end `$()`.
|
|
2148
|
+
* A line comment runs from `//` to the newline, or to the end of the string when there is no newline.
|
|
2149
|
+
* A `)` in that span is comment text, not the closer of `$()`.
|
|
2150
|
+
*
|
|
2151
|
+
* @param {string} cashAssemblyText - Text that contains the line comment.
|
|
2152
|
+
* @param {number} commentStartIndex - Index of the first `/` of `//`.
|
|
2153
|
+
* @returns {number} - Index after the newline that ends the comment, or the end of the string if there is no newline.
|
|
2154
|
+
*/
|
|
2155
|
+
const skipSingleLineCashAssemblyComment = (cashAssemblyText, commentStartIndex) => {
|
|
2156
|
+
let currentIndex = commentStartIndex + CASHASSEMBLY_COMMENT_START_LENGTH;
|
|
2157
|
+
while (currentIndex < cashAssemblyText.length) {
|
|
2158
|
+
if (cashAssemblyText[currentIndex] === "\n") return currentIndex + 1;
|
|
2159
|
+
currentIndex += 1;
|
|
2160
|
+
}
|
|
2161
|
+
return currentIndex;
|
|
1891
2162
|
};
|
|
1892
2163
|
/**
|
|
1893
|
-
*
|
|
2164
|
+
* This function skips a block comment so a `)` written in the comment cannot end `$()`.
|
|
2165
|
+
* A `)` inside a block comment is comment text, not the closer of `$()`.
|
|
1894
2166
|
*
|
|
1895
|
-
* @param {string}
|
|
1896
|
-
* @
|
|
2167
|
+
* @param {string} cashAssemblyText - Text that contains the block comment.
|
|
2168
|
+
* @param {number} commentStartIndex - Index of the first slash of the block comment opener.
|
|
2169
|
+
* @returns {number} - Index of the character after the comment closer.
|
|
2170
|
+
* @throws {@link CashAssemblyBlockCommentUnclosedError} - When the block comment never closes.
|
|
2171
|
+
*/
|
|
2172
|
+
const skipBlockCashAssemblyComment = (cashAssemblyText, commentStartIndex) => {
|
|
2173
|
+
let currentIndex = commentStartIndex + CASHASSEMBLY_COMMENT_START_LENGTH;
|
|
2174
|
+
while (currentIndex + 1 < cashAssemblyText.length) {
|
|
2175
|
+
if (cashAssemblyText[currentIndex] === "*" && cashAssemblyText[currentIndex + 1] === "/") return currentIndex + CASHASSEMBLY_BLOCK_COMMENT_END_LENGTH;
|
|
2176
|
+
currentIndex += 1;
|
|
2177
|
+
}
|
|
2178
|
+
throw new CashAssemblyBlockCommentUnclosedError(commentStartIndex, cashAssemblyText);
|
|
2179
|
+
};
|
|
2180
|
+
/**
|
|
2181
|
+
* Parenthesis matching with a depth count.
|
|
2182
|
+
*
|
|
2183
|
+
* Returns the index of the `)` that closes the `$()` starting at `evaluationStartIndex`.
|
|
2184
|
+
* Nested `$()` raise the depth so an inner closer cannot finish the outer evaluation.
|
|
2185
|
+
*
|
|
2186
|
+
* @param {string} cashAssemblyText - Text that contains the evaluation.
|
|
2187
|
+
* @param {number} evaluationStartIndex - Index of the `$` that opens this evaluation.
|
|
2188
|
+
* @returns {number} - Index of the matching closing parenthesis.
|
|
2189
|
+
* @throws {@link CashAssemblyEvaluationUnclosedError} - When the evaluation never closes.
|
|
2190
|
+
* @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal inside the evaluation never closes.
|
|
2191
|
+
* @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment inside the evaluation never closes.
|
|
2192
|
+
*/
|
|
2193
|
+
const findCashAssemblyEvaluationCloseIndex = (cashAssemblyText, evaluationStartIndex) => {
|
|
2194
|
+
let currentIndex = evaluationStartIndex + CASHASSEMBLY_EVALUATION_START_LENGTH;
|
|
2195
|
+
let remainingOpenEvaluations = 1;
|
|
2196
|
+
while (currentIndex < cashAssemblyText.length) {
|
|
2197
|
+
const currentCharacter = cashAssemblyText[currentIndex];
|
|
2198
|
+
const nextCharacter = cashAssemblyText[currentIndex + 1];
|
|
2199
|
+
if (currentCharacter === "\"" || currentCharacter === "'") {
|
|
2200
|
+
currentIndex = skipQuotedCashAssemblyLiteral(cashAssemblyText, currentIndex);
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (currentCharacter === "/" && nextCharacter === "/") {
|
|
2204
|
+
currentIndex = skipSingleLineCashAssemblyComment(cashAssemblyText, currentIndex);
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
if (currentCharacter === "/" && nextCharacter === "*") {
|
|
2208
|
+
currentIndex = skipBlockCashAssemblyComment(cashAssemblyText, currentIndex);
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
if (currentCharacter === "$" && nextCharacter === "(") {
|
|
2212
|
+
remainingOpenEvaluations += 1;
|
|
2213
|
+
currentIndex += CASHASSEMBLY_EVALUATION_START_LENGTH;
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
if (currentCharacter === ")") {
|
|
2217
|
+
remainingOpenEvaluations -= 1;
|
|
2218
|
+
if (remainingOpenEvaluations === 0) return currentIndex;
|
|
2219
|
+
}
|
|
2220
|
+
currentIndex += 1;
|
|
2221
|
+
}
|
|
2222
|
+
throw new CashAssemblyEvaluationUnclosedError(evaluationStartIndex, remainingOpenEvaluations, cashAssemblyText);
|
|
2223
|
+
};
|
|
2224
|
+
/**
|
|
2225
|
+
* Scans text and returns every `$()` that starts in this string, including empty `$()`.
|
|
2226
|
+
*
|
|
2227
|
+
* Nested `$()` stay inside the outer evaluation.
|
|
2228
|
+
*
|
|
2229
|
+
* @param {string} cashAssemblyText - Text that may contain `$()`.
|
|
2230
|
+
* @returns {CashAssemblyEvaluationScanMatch[]} - Evaluations in left to right order.
|
|
2231
|
+
* @throws {@link CashAssemblyEvaluationUnclosedError} - When an evaluation never closes.
|
|
2232
|
+
* @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal never closes.
|
|
2233
|
+
* @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment never closes.
|
|
2234
|
+
*/
|
|
2235
|
+
const scanCashAssemblyEvaluations = (cashAssemblyText) => {
|
|
2236
|
+
const scannedEvaluations = [];
|
|
2237
|
+
let currentIndex = 0;
|
|
2238
|
+
while (currentIndex < cashAssemblyText.length) {
|
|
2239
|
+
const currentCharacter = cashAssemblyText[currentIndex];
|
|
2240
|
+
const nextCharacter = cashAssemblyText[currentIndex + 1];
|
|
2241
|
+
if (currentCharacter === "$" && nextCharacter === "(") {
|
|
2242
|
+
const closeIndex = findCashAssemblyEvaluationCloseIndex(cashAssemblyText, currentIndex);
|
|
2243
|
+
const evaluationText = cashAssemblyText.slice(currentIndex, closeIndex + 1);
|
|
2244
|
+
scannedEvaluations.push({
|
|
2245
|
+
closeIndex,
|
|
2246
|
+
evaluationText,
|
|
2247
|
+
startIndex: currentIndex
|
|
2248
|
+
});
|
|
2249
|
+
currentIndex = closeIndex + 1;
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
currentIndex += 1;
|
|
2253
|
+
}
|
|
2254
|
+
return scannedEvaluations;
|
|
2255
|
+
};
|
|
2256
|
+
/**
|
|
2257
|
+
* This function returns each `$()` that starts in this string.
|
|
2258
|
+
* Empty `$()` is omitted.
|
|
1897
2259
|
*
|
|
1898
|
-
* @
|
|
1899
|
-
*
|
|
1900
|
-
*
|
|
2260
|
+
* @param {string} cashAssemblyText - Text that may contain `$()`.
|
|
2261
|
+
* @returns {string[]} - Complete non empty evaluation strings in left to right order.
|
|
2262
|
+
* @throws {@link CashAssemblyEvaluationUnclosedError} - When an evaluation never closes.
|
|
2263
|
+
* @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal never closes.
|
|
2264
|
+
* @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment never closes.
|
|
1901
2265
|
*/
|
|
1902
|
-
const extractCashAssemblyEvaluations = (
|
|
1903
|
-
|
|
2266
|
+
const extractCashAssemblyEvaluations = (cashAssemblyText) => {
|
|
2267
|
+
const scannedEvaluations = scanCashAssemblyEvaluations(cashAssemblyText);
|
|
2268
|
+
const evaluations = [];
|
|
2269
|
+
for (const scannedEvaluation of scannedEvaluations) if (scannedEvaluation.evaluationText !== EMPTY_CASHASSEMBLY_EVALUATION) evaluations.push(scannedEvaluation.evaluationText);
|
|
2270
|
+
return evaluations;
|
|
1904
2271
|
};
|
|
1905
2272
|
/**
|
|
2273
|
+
* This function returns true when the value is exactly one CashAssembly `$()`.
|
|
2274
|
+
* Text with characters outside `$()`, empty `$()`, or a failed scan returns false.
|
|
2275
|
+
*
|
|
2276
|
+
* @param {unknown} expression - Value to test.
|
|
2277
|
+
* @returns {boolean} - True when the value is a string that is one complete evaluation and nothing else.
|
|
2278
|
+
*/
|
|
2279
|
+
const isCashAssemblyExpression = (expression) => {
|
|
2280
|
+
if (typeof expression !== "string") return false;
|
|
2281
|
+
try {
|
|
2282
|
+
const evaluations = extractCashAssemblyEvaluations(expression);
|
|
2283
|
+
return evaluations.length === 1 && evaluations[0] === expression;
|
|
2284
|
+
} catch {
|
|
2285
|
+
return false;
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
|
|
2289
|
+
//#endregion
|
|
2290
|
+
//#region source/cash-assembly/evaluations.ts
|
|
2291
|
+
/**
|
|
1906
2292
|
* Returns the segment of `identifier` before the first `.`.
|
|
1907
2293
|
*
|
|
1908
2294
|
* When there is no `.`, returns `identifier` unchanged.
|
|
1909
2295
|
* Multi segment identifiers such as `foo.bar.baz` resolve to `foo`.
|
|
1910
2296
|
*
|
|
1911
2297
|
* @param {string} identifier - Identifier that may contain a dot.
|
|
1912
|
-
* @returns {string} The base name before the first `.`.
|
|
2298
|
+
* @returns {string} - The base name before the first `.`.
|
|
1913
2299
|
*/
|
|
1914
2300
|
const resolveIdentifierBaseName = (identifier) => {
|
|
1915
2301
|
const firstDotIndex = identifier.indexOf(".");
|
|
@@ -1917,23 +2303,6 @@ const resolveIdentifierBaseName = (identifier) => {
|
|
|
1917
2303
|
return identifier.slice(0, firstDotIndex);
|
|
1918
2304
|
};
|
|
1919
2305
|
/**
|
|
1920
|
-
* Extracts unique variable identifiers enclosed in angle brackets from each evaluation string.
|
|
1921
|
-
*
|
|
1922
|
-
* CashAssembly literal tokens such as hex bytes, numbers, and quoted strings are excluded via
|
|
1923
|
-
* {@link CASHASSEMBLY_LITERAL_TOKEN_PATTERN}. For example, `<0x02>` and `<"minting">` are not returned.
|
|
1924
|
-
*
|
|
1925
|
-
* @param {string[]} evaluations - An array of evaluation strings from which to extract variable names.
|
|
1926
|
-
* @returns {string[]} An array of variable names.
|
|
1927
|
-
*/
|
|
1928
|
-
const extractVariablesFromEvaluations = (evaluations) => {
|
|
1929
|
-
const uniqueVariables = /* @__PURE__ */ new Set();
|
|
1930
|
-
for (const evaluation of evaluations) for (const [, extractedIdentifier] of evaluation.matchAll(CASHASSEMBLY_VARIABLE_PATTERN)) {
|
|
1931
|
-
if (CASHASSEMBLY_LITERAL_TOKEN_PATTERN.test(extractedIdentifier)) continue;
|
|
1932
|
-
uniqueVariables.add(extractedIdentifier);
|
|
1933
|
-
}
|
|
1934
|
-
return [...uniqueVariables];
|
|
1935
|
-
};
|
|
1936
|
-
/**
|
|
1937
2306
|
* Decodes compiled CashAssembly evaluation bytes into a string representation.
|
|
1938
2307
|
*
|
|
1939
2308
|
* 'evaluationDecodeMode' determines how the evaluation bytes are interpreted and presented.
|
|
@@ -1941,10 +2310,9 @@ const extractVariablesFromEvaluations = (evaluations) => {
|
|
|
1941
2310
|
* such as hashes, and `boolean` to represent boolean values.
|
|
1942
2311
|
*
|
|
1943
2312
|
* @param {Uint8Array} compiledResult - The compiled evaluation bytecode.
|
|
1944
|
-
* @param {CompiledCashAssemblyDecodeMode}
|
|
1945
|
-
*
|
|
1946
|
-
* @
|
|
1947
|
-
* @throws {@link CashAssemblyVmNumberDecodeError} When `evaluationDecodeMode` is `bigint` and the bytes are not a VM number.
|
|
2313
|
+
* @param {CompiledCashAssemblyDecodeMode} evaluationDecodeMode - The decode mode used to convert bytes to text.
|
|
2314
|
+
* @returns {string} - The decoded value as a string suitable for inline replacement.
|
|
2315
|
+
* @throws {@link CashAssemblyVmNumberDecodeError} - When `evaluationDecodeMode` is `bigint` and the bytes are not a VM number.
|
|
1948
2316
|
*/
|
|
1949
2317
|
const decodeCompiledCashAssemblyEvaluation = (compiledResult, evaluationDecodeMode = "utf8") => {
|
|
1950
2318
|
if (evaluationDecodeMode === "uint8array") return String(compiledResult);
|
|
@@ -1963,26 +2331,26 @@ const decodeCompiledCashAssemblyEvaluation = (compiledResult, evaluationDecodeMo
|
|
|
1963
2331
|
* @param {CompilerBch} compiler - The libauth compiler from {@link compileCashAssemblyEvaluations}.
|
|
1964
2332
|
* @param {string} evaluation - The specific evaluation string to compile.
|
|
1965
2333
|
* @param {Record<string, Uint8Array>} variables - A record mapping variable names to their values.
|
|
1966
|
-
* @
|
|
1967
|
-
* @
|
|
1968
|
-
* @throws {@link
|
|
1969
|
-
* @throws {@link
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
const missingVariables =
|
|
2334
|
+
* @param {string[]} requiredVariableNames - WalletData names from {@link collectVariablesUsingParseScript} that must be present.
|
|
2335
|
+
* @returns {Uint8Array} - The compiled bytecode.
|
|
2336
|
+
* @throws {@link CashAssemblyRequiredVariableMissingError} - If a required variable is not present.
|
|
2337
|
+
* @throws {@link CashAssemblyVariableTypeMismatchError} - If a variable value is not a Uint8Array.
|
|
2338
|
+
* @throws {@link CashAssemblyCompilationFailedError} - If libauth compilation fails.
|
|
2339
|
+
*/
|
|
2340
|
+
const generateCashAssemblyBytecode = (compiler, evaluation, variables, requiredVariableNames) => {
|
|
2341
|
+
const missingVariables = requiredVariableNames.filter((name) => Object.hasOwn(variables, name) === false);
|
|
1974
2342
|
if (missingVariables.length > 0) throw new CashAssemblyRequiredVariableMissingError(missingVariables);
|
|
1975
2343
|
const bytecode = {};
|
|
1976
|
-
for (const variableName of
|
|
2344
|
+
for (const variableName of requiredVariableNames) {
|
|
1977
2345
|
const value = variables[variableName];
|
|
1978
|
-
if (
|
|
2346
|
+
if (value instanceof Uint8Array === false) throw new CashAssemblyVariableTypeMismatchError(variableName, "Uint8Array", typeof value);
|
|
1979
2347
|
bytecode[variableName] = value;
|
|
1980
2348
|
}
|
|
1981
2349
|
const compiledBytecode = compiler.generateBytecode({
|
|
1982
2350
|
data: { bytecode },
|
|
1983
2351
|
scriptId: evaluation
|
|
1984
2352
|
});
|
|
1985
|
-
if (
|
|
2353
|
+
if (compiledBytecode.success === false) {
|
|
1986
2354
|
let compilationFailureMessage = "unknown compilation failure";
|
|
1987
2355
|
if ("errors" in compiledBytecode && compiledBytecode.errors.length > 0) compilationFailureMessage = compiledBytecode.errors.map((compilationError) => compilationError.error).join("; ");
|
|
1988
2356
|
throw new CashAssemblyCompilationFailedError(compilationFailureMessage);
|
|
@@ -1990,68 +2358,168 @@ const generateCashAssemblyBytecode = (compiler, evaluation, variables) => {
|
|
|
1990
2358
|
return compiledBytecode.bytecode;
|
|
1991
2359
|
};
|
|
1992
2360
|
/**
|
|
1993
|
-
* Prepares a compiler for the provided CashAssembly
|
|
2361
|
+
* Prepares a compiler for the provided CashAssembly source strings, setting required variables as 'WalletData'.
|
|
2362
|
+
*
|
|
2363
|
+
* Each evaluation string is registered as a script whose id and source are that string. Template
|
|
2364
|
+
* scripts are then copied onto the same table. If a source is a template id such as `scriptA`, that
|
|
2365
|
+
* id must compile the template source. Leaving the id as its own source would compile the text
|
|
2366
|
+
* `scriptA` and include itself.
|
|
1994
2367
|
*
|
|
1995
|
-
* @param {
|
|
1996
|
-
* @returns {CompilerBch} A Libauth compiler instance for use with these
|
|
2368
|
+
* @param {CompileCashAssemblyEvaluationsParameters} parameters - Source strings, optional template scripts, and already collected WalletData names.
|
|
2369
|
+
* @returns {CompilerBch} - A Libauth compiler instance for use with these source strings.
|
|
1997
2370
|
*/
|
|
1998
|
-
const compileCashAssemblyEvaluations = (
|
|
2371
|
+
const compileCashAssemblyEvaluations = (parameters) => {
|
|
2372
|
+
const { evaluations, templateScripts, variableNames } = parameters;
|
|
1999
2373
|
const scripts = {};
|
|
2000
2374
|
for (const evaluation of evaluations) scripts[evaluation] = evaluation;
|
|
2001
|
-
const
|
|
2375
|
+
const knownScriptIdentifiers = /* @__PURE__ */ new Set();
|
|
2376
|
+
if (templateScripts !== void 0) for (const [scriptIdentifier, scriptDefinition] of Object.entries(templateScripts)) {
|
|
2377
|
+
scripts[scriptIdentifier] = scriptDefinition;
|
|
2378
|
+
knownScriptIdentifiers.add(scriptIdentifier);
|
|
2379
|
+
}
|
|
2002
2380
|
const variables = {};
|
|
2003
|
-
for (const variableName of variableNames)
|
|
2381
|
+
for (const variableName of variableNames) {
|
|
2382
|
+
if (knownScriptIdentifiers.has(variableName) === true) continue;
|
|
2383
|
+
variables[resolveIdentifierBaseName(variableName)] = { type: "WalletData" };
|
|
2384
|
+
}
|
|
2004
2385
|
return createCompilerBch({
|
|
2005
2386
|
scripts,
|
|
2006
2387
|
variables
|
|
2007
2388
|
});
|
|
2008
2389
|
};
|
|
2009
2390
|
/**
|
|
2010
|
-
* Compiles
|
|
2011
|
-
*
|
|
2391
|
+
* Compiles one CashAssembly source string to bytecode.
|
|
2392
|
+
*
|
|
2393
|
+
* The source may be an evaluation such as `$(scriptA)` or CashAssembly such as
|
|
2394
|
+
* `scriptA`. WalletData names come from {@link collectVariablesUsingParseScript}.
|
|
2395
|
+
*
|
|
2396
|
+
* @param {CompileCashAssemblySourceToBytesParameters} parameters - Source text and compilation context.
|
|
2397
|
+
* @returns {Uint8Array} - Compiled bytecode for that source.
|
|
2398
|
+
* @throws {@link CashAssemblyRequiredVariableMissingError} - When a required variable is not present in the variables map.
|
|
2399
|
+
* @throws {@link CashAssemblyPrimitiveMethodMissingError} - When a supported primitive hint has an unknown method.
|
|
2400
|
+
* @throws {@link CashAssemblyPrimitiveVariableMissingError} - When a supported primitive method is missing its runtime value.
|
|
2401
|
+
* @throws {@link CashAssemblyUnsupportedValueTypeError} - When a primitive method return type cannot be embedded as bytes.
|
|
2402
|
+
* @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number variable is not a safe integer.
|
|
2403
|
+
* @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches two resolution types.
|
|
2404
|
+
* @throws {@link CashAssemblyCompilationFailedError} - When Libauth compilation fails.
|
|
2405
|
+
*/
|
|
2406
|
+
const compileCashAssemblySourceToBytes = (parameters) => {
|
|
2407
|
+
const { cashAssemblySource, variables, templateVariables, templateScripts } = parameters;
|
|
2408
|
+
assertNoIdentifierCollisions({
|
|
2409
|
+
variables,
|
|
2410
|
+
templateScripts
|
|
2411
|
+
});
|
|
2412
|
+
const variableNames = collectVariablesUsingParseScript(cashAssemblySource, templateScripts);
|
|
2413
|
+
const primitiveMethodBytes = resolvePrimitiveMethodBytes({
|
|
2414
|
+
variableNames,
|
|
2415
|
+
templateVariables,
|
|
2416
|
+
variables
|
|
2417
|
+
});
|
|
2418
|
+
const missingVariables = [];
|
|
2419
|
+
for (const variableName of variableNames) {
|
|
2420
|
+
if (Object.hasOwn(primitiveMethodBytes, variableName) === true) continue;
|
|
2421
|
+
if (Object.hasOwn(variables, variableName) === true) continue;
|
|
2422
|
+
missingVariables.push(variableName);
|
|
2423
|
+
}
|
|
2424
|
+
if (missingVariables.length > 0) throw new CashAssemblyRequiredVariableMissingError(missingVariables);
|
|
2425
|
+
const variableBytes = {};
|
|
2426
|
+
for (const variableName of variableNames) {
|
|
2427
|
+
if (Object.hasOwn(primitiveMethodBytes, variableName) === true) {
|
|
2428
|
+
variableBytes[variableName] = primitiveMethodBytes[variableName];
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
variableBytes[variableName] = convertValueToBytes(variables[variableName], variableName);
|
|
2432
|
+
}
|
|
2433
|
+
return generateCashAssemblyBytecode(compileCashAssemblyEvaluations({
|
|
2434
|
+
evaluations: [cashAssemblySource],
|
|
2435
|
+
templateScripts,
|
|
2436
|
+
variableNames
|
|
2437
|
+
}), cashAssemblySource, variableBytes, variableNames);
|
|
2438
|
+
};
|
|
2439
|
+
/**
|
|
2440
|
+
* Compiles one CashAssembly source to bytes and decodes those bytes to text.
|
|
2441
|
+
*
|
|
2442
|
+
* @param {CompileCashAssemblySourceToDecodedTextParameters} parameters - Source text, WalletData, optional template context, and decode mode.
|
|
2443
|
+
* @returns {string} - Decoded compilation result for that source.
|
|
2444
|
+
* @throws {@link CashAssemblyRequiredVariableMissingError} - When a required variable is not present in the variables map.
|
|
2445
|
+
* @throws {@link CashAssemblyPrimitiveMethodMissingError} - When a supported primitive hint has an unknown method.
|
|
2446
|
+
* @throws {@link CashAssemblyPrimitiveVariableMissingError} - When a supported primitive method is missing its runtime value.
|
|
2447
|
+
* @throws {@link CashAssemblyUnsupportedValueTypeError} - When a primitive method return type cannot be embedded as bytes.
|
|
2448
|
+
* @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number variable is not a safe integer.
|
|
2449
|
+
* @throws {@link CashAssemblyVmNumberDecodeError} - When `evaluationDecodeMode` is `bigint` and compiled bytes are not a VM number.
|
|
2450
|
+
* @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches two resolution types.
|
|
2451
|
+
* @throws {@link CashAssemblyCompilationFailedError} - When Libauth compilation fails.
|
|
2452
|
+
*/
|
|
2453
|
+
const compileCashAssemblySourceToDecodedText = (parameters) => {
|
|
2454
|
+
const { cashAssemblySource, variables, templateVariables, templateScripts, evaluationDecodeMode } = parameters;
|
|
2455
|
+
return decodeCompiledCashAssemblyEvaluation(compileCashAssemblySourceToBytes({
|
|
2456
|
+
cashAssemblySource,
|
|
2457
|
+
variables,
|
|
2458
|
+
templateVariables,
|
|
2459
|
+
templateScripts
|
|
2460
|
+
}), evaluationDecodeMode);
|
|
2461
|
+
};
|
|
2462
|
+
/**
|
|
2463
|
+
* Compiles CashAssembly text. WalletData names come from parseScript. `$()` evaluations in text
|
|
2464
|
+
* are found by scanning the string and matching parentheses.
|
|
2465
|
+
*
|
|
2466
|
+
* When `cashAssemblyText` contains `$()`, each non empty evaluation is compiled and
|
|
2467
|
+
* replaced in place. Empty `$()` is copied through. Surrounding text is kept. Evaluation drops
|
|
2468
|
+
* push opcodes and keeps the stack payload.
|
|
2469
|
+
*
|
|
2470
|
+
* When `cashAssemblyText` contains no `$()` at all, the entire string is compiled as CashAssembly.
|
|
2471
|
+
* Push opcodes from `<...>` remain. Pass a template script id such as `scriptA` or
|
|
2472
|
+
* concatenated ids such as `scriptA scriptB`.
|
|
2473
|
+
*
|
|
2474
|
+
* Required WalletData names come from {@link collectVariablesUsingParseScript}.
|
|
2012
2475
|
*
|
|
2013
2476
|
* @param {CompileCashAssemblyStringParameters} parameters - Parameters for compiling the CashAssembly string.
|
|
2014
|
-
* @
|
|
2015
|
-
* @
|
|
2016
|
-
*
|
|
2017
|
-
* @
|
|
2018
|
-
*
|
|
2019
|
-
* @
|
|
2020
|
-
*
|
|
2021
|
-
* @
|
|
2022
|
-
* @throws {@link
|
|
2023
|
-
* @throws {@link
|
|
2024
|
-
* @throws {@link
|
|
2025
|
-
* @throws {@link
|
|
2026
|
-
* @throws {@link CashAssemblyNumberNotSafeIntegerError} When a number variable is not a safe integer.
|
|
2027
|
-
* @throws {@link CashAssemblyVmNumberDecodeError} When `evaluationDecodeMode` is `bigint` and an evaluation is not a VM number.
|
|
2477
|
+
* @returns {string} - Compiled text with evaluations replaced, or decoded bytecode when the whole string is compiled as CashAssembly.
|
|
2478
|
+
* @throws {@link CashAssemblyRequiredVariableMissingError} - When a required variable is not present in the variables map.
|
|
2479
|
+
* @throws {@link CashAssemblyPrimitiveMethodMissingError} - When a supported primitive hint has an unknown method.
|
|
2480
|
+
* @throws {@link CashAssemblyPrimitiveVariableMissingError} - When a supported primitive method is missing its runtime value.
|
|
2481
|
+
* @throws {@link CashAssemblyUnsupportedValueTypeError} - When a primitive method return type cannot be embedded as bytes.
|
|
2482
|
+
* @throws {@link CashAssemblyNumberNotSafeIntegerError} - When a number variable is not a safe integer.
|
|
2483
|
+
* @throws {@link CashAssemblyVmNumberDecodeError} - When `evaluationDecodeMode` is `bigint` and compiled bytes are not a VM number.
|
|
2484
|
+
* @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches two resolution types.
|
|
2485
|
+
* @throws {@link CashAssemblyCompilationFailedError} - When Libauth compilation fails.
|
|
2486
|
+
* @throws {@link CashAssemblyEvaluationUnclosedError} - When an evaluation never closes.
|
|
2487
|
+
* @throws {@link CashAssemblyQuotedLiteralUnclosedError} - When a quoted literal never closes.
|
|
2488
|
+
* @throws {@link CashAssemblyBlockCommentUnclosedError} - When a block comment never closes.
|
|
2028
2489
|
*/
|
|
2029
2490
|
const compileCashAssemblyString = (parameters) => {
|
|
2030
|
-
const { cashAssemblyText, variables, evaluationDecodeMode = "utf8", templateVariables } = parameters;
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
const missingVariables = variableNames.filter((variableName) => {
|
|
2039
|
-
if (Object.hasOwn(primitiveMethodBytes, variableName) === true) return false;
|
|
2040
|
-
return Object.hasOwn(variables, variableName) === false;
|
|
2041
|
-
});
|
|
2042
|
-
if (missingVariables.length > 0) throw new CashAssemblyRequiredVariableMissingError(missingVariables);
|
|
2043
|
-
const variableBytes = {};
|
|
2044
|
-
for (const variableName of variableNames) {
|
|
2045
|
-
if (Object.hasOwn(primitiveMethodBytes, variableName) === true) {
|
|
2046
|
-
variableBytes[variableName] = primitiveMethodBytes[variableName];
|
|
2047
|
-
continue;
|
|
2048
|
-
}
|
|
2049
|
-
variableBytes[variableName] = convertValueToBytes(variables[variableName], variableName);
|
|
2050
|
-
}
|
|
2051
|
-
return decodeCompiledCashAssemblyEvaluation(generateCashAssemblyBytecode(compileCashAssemblyEvaluations([evaluation]), evaluation, variableBytes), evaluationDecodeMode);
|
|
2491
|
+
const { cashAssemblyText, variables, evaluationDecodeMode = "utf8", templateVariables, templateScripts } = parameters;
|
|
2492
|
+
const scannedEvaluations = scanCashAssemblyEvaluations(cashAssemblyText);
|
|
2493
|
+
if (scannedEvaluations.length === 0) return compileCashAssemblySourceToDecodedText({
|
|
2494
|
+
cashAssemblySource: cashAssemblyText,
|
|
2495
|
+
variables,
|
|
2496
|
+
templateVariables,
|
|
2497
|
+
templateScripts,
|
|
2498
|
+
evaluationDecodeMode
|
|
2052
2499
|
});
|
|
2500
|
+
let textWithCompiledEvaluations = "";
|
|
2501
|
+
let nextTextStartIndex = 0;
|
|
2502
|
+
let hasCompiledNonEmptyEvaluation = false;
|
|
2503
|
+
for (const scannedEvaluation of scannedEvaluations) {
|
|
2504
|
+
textWithCompiledEvaluations += cashAssemblyText.slice(nextTextStartIndex, scannedEvaluation.startIndex);
|
|
2505
|
+
if (scannedEvaluation.evaluationText === EMPTY_CASHASSEMBLY_EVALUATION) textWithCompiledEvaluations += scannedEvaluation.evaluationText;
|
|
2506
|
+
else {
|
|
2507
|
+
hasCompiledNonEmptyEvaluation = true;
|
|
2508
|
+
textWithCompiledEvaluations += compileCashAssemblySourceToDecodedText({
|
|
2509
|
+
cashAssemblySource: scannedEvaluation.evaluationText,
|
|
2510
|
+
variables,
|
|
2511
|
+
templateVariables,
|
|
2512
|
+
templateScripts,
|
|
2513
|
+
evaluationDecodeMode
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
nextTextStartIndex = scannedEvaluation.closeIndex + 1;
|
|
2517
|
+
}
|
|
2518
|
+
textWithCompiledEvaluations += cashAssemblyText.slice(nextTextStartIndex);
|
|
2519
|
+
if (hasCompiledNonEmptyEvaluation === false) return cashAssemblyText;
|
|
2520
|
+
return textWithCompiledEvaluations;
|
|
2053
2521
|
};
|
|
2054
2522
|
|
|
2055
2523
|
//#endregion
|
|
2056
|
-
export { AsyncPushIterator,
|
|
2524
|
+
export { AsyncPushIterator, CashAssemblyBlockCommentUnclosedError, CashAssemblyCompilationFailedError, CashAssemblyEvaluationUnclosedError, CashAssemblyIdentifierCollisionError, CashAssemblyNumberNotSafeIntegerError, CashAssemblyPrimitiveMethodMissingError, CashAssemblyPrimitiveVariableMissingError, CashAssemblyQuotedLiteralUnclosedError, CashAssemblyRequiredVariableMissingError, CashAssemblyUnsupportedValueTypeError, CashAssemblyVariableTypeMismatchError, CashAssemblyVmNumberDecodeError, EventEmitter, ExponentialBackoff, ExponentialBackoffExternallyAbortable, ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNonIntegerError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffNumberOutOfBoundsError, ExponentialBackoffNumberTooSmallError, ExponentialBackoffStoppedRetriesError, ExternallyAbortedExponentialBackoffExternalSignalAbortedError, ExternallyAbortedExponentialBackoffInternalSignalAbortedError, SSEEventParser, TemplateInvalidError, TemplateJsonMalformedError, TemplateSerializationFailedError, VIEW_PROPERTIES_DESCRIPTION_MAX_LENGTH, VIEW_PROPERTIES_ICON_MAX_LENGTH, VIEW_PROPERTIES_NAME_MAX_LENGTH, WaitForTimeoutError, bchVmVersionSchema, buildErrorDescription, collectVariablesUsingParseScript, compileCashAssemblyString, extendedJsonReplacer, extendedJsonReviver, extractCashAssemblyEvaluations, fromExtendedJson, generateTemplateIdentifier, isCashAssemblyExpression, parseTemplate, satoshisSchema, scriptToScriptHash, serializeTemplate, toExtendedJson, uint8ArraySchema, xoTemplateActionIntentSchema, xoTemplateActionRequirementsSchema, xoTemplateActionRoleRequirementsSchema, xoTemplateActionRoleSchema, xoTemplateActionSchema, xoTemplateAssetAmountsSchema, xoTemplateConstantSchema, xoTemplateDataSchema, xoTemplateDefaultsSchema, xoTemplateIconSchema, xoTemplateImportDefaultValueSchema, xoTemplateInputSchema, xoTemplateIntentSchema, xoTemplateLockingScriptIntentSchema, xoTemplateLockingScriptRoleSchema, xoTemplateLockingScriptSchema, xoTemplateLockingTypeSchema, xoTemplateNftCapabilitySchema, xoTemplateNonFungibleTokenDetailsSchema, xoTemplateOutputIntentSchema, xoTemplateOutputSchema, xoTemplatePrimitiveTypeSchema, xoTemplateResourceSchema, xoTemplateRoleSlotSchema, xoTemplateRoleSlotsRequirementsSchema, xoTemplateSchema, xoTemplateStateSchema, xoTemplateTokenSchema, xoTemplateTransactionInputSchema, xoTemplateTransactionOutputSchema, xoTemplateTransactionRoleDataSchema, xoTemplateTransactionSchema, xoTemplateVariableSchema, xoTemplateViewPropertiesSchema };
|
|
2057
2525
|
//# sourceMappingURL=index.mjs.map
|