@cashscript/utils 0.13.0-next.3 → 0.13.0-next.5
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/artifact.d.ts +5 -0
- package/dist/bitauth-script.d.ts +4 -3
- package/dist/bitauth-script.js +84 -19
- package/dist/data.d.ts +1 -0
- package/dist/data.js +3 -0
- package/dist/script.d.ts +4 -3
- package/dist/script.js +14 -5
- package/dist/source-map.d.ts +3 -1
- package/dist/source-map.js +22 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js +8 -2
- package/package.json +2 -2
package/dist/artifact.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export interface CompilerOptions {
|
|
2
|
+
enforceFunctionParameterTypes?: boolean;
|
|
3
|
+
}
|
|
1
4
|
export interface AbiInput {
|
|
2
5
|
name: string;
|
|
3
6
|
type: string;
|
|
@@ -11,6 +14,7 @@ export interface DebugInformation {
|
|
|
11
14
|
sourceMap: string;
|
|
12
15
|
logs: readonly LogEntry[];
|
|
13
16
|
requires: readonly RequireStatement[];
|
|
17
|
+
sourceTags?: string;
|
|
14
18
|
}
|
|
15
19
|
export interface LogEntry {
|
|
16
20
|
ip: number;
|
|
@@ -39,6 +43,7 @@ export interface Artifact {
|
|
|
39
43
|
compiler: {
|
|
40
44
|
name: string;
|
|
41
45
|
version: string;
|
|
46
|
+
options?: CompilerOptions;
|
|
42
47
|
};
|
|
43
48
|
updatedAt: string;
|
|
44
49
|
}
|
package/dist/bitauth-script.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Script } from './script.js';
|
|
2
|
+
import { FullLocationData } from './types.js';
|
|
2
3
|
export type LineToOpcodesMap = Record<string, Script>;
|
|
3
4
|
export type LineToAsmMap = Record<string, string>;
|
|
4
|
-
export declare function buildLineToOpcodesMap(bytecode: Script,
|
|
5
|
-
export declare function buildLineToAsmMap(bytecode: Script,
|
|
6
|
-
export declare function formatBitAuthScript(bytecode: Script, sourceMap: string, sourceCode: string): string;
|
|
5
|
+
export declare function buildLineToOpcodesMap(bytecode: Script, sourceMapOrLocationData: string | FullLocationData): LineToOpcodesMap;
|
|
6
|
+
export declare function buildLineToAsmMap(bytecode: Script, sourceMapOrLocationData: string | FullLocationData): LineToAsmMap;
|
|
7
|
+
export declare function formatBitAuthScript(bytecode: Script, sourceMap: string, sourceCode: string, sourceTags?: string): string;
|
package/dist/bitauth-script.js
CHANGED
|
@@ -1,33 +1,98 @@
|
|
|
1
|
+
import { range } from './data.js';
|
|
1
2
|
import { scriptToBitAuthAsm } from './script.js';
|
|
2
|
-
import { sourceMapToLocationData } from './source-map.js';
|
|
3
|
+
import { parseSourceTags, sourceMapToLocationData } from './source-map.js';
|
|
3
4
|
import { PositionHint } from './types.js';
|
|
4
|
-
export function buildLineToOpcodesMap(bytecode,
|
|
5
|
-
const locationData = sourceMapToLocationData(
|
|
6
|
-
return locationData.reduce((lineToOpcodeMap,
|
|
5
|
+
export function buildLineToOpcodesMap(bytecode, sourceMapOrLocationData) {
|
|
6
|
+
const locationData = typeof sourceMapOrLocationData === 'string' ? sourceMapToLocationData(sourceMapOrLocationData) : sourceMapOrLocationData;
|
|
7
|
+
return locationData.reduce((lineToOpcodeMap, singleLocation, index) => {
|
|
7
8
|
const opcode = bytecode[index];
|
|
8
|
-
const line =
|
|
9
|
+
const line = getDisplayLine(singleLocation);
|
|
9
10
|
return {
|
|
10
11
|
...lineToOpcodeMap,
|
|
11
12
|
[line]: [...(lineToOpcodeMap[line] || []), opcode],
|
|
12
13
|
};
|
|
13
14
|
}, {});
|
|
14
15
|
}
|
|
15
|
-
export function buildLineToAsmMap(bytecode,
|
|
16
|
-
const lineToOpcodesMap = buildLineToOpcodesMap(bytecode,
|
|
16
|
+
export function buildLineToAsmMap(bytecode, sourceMapOrLocationData) {
|
|
17
|
+
const lineToOpcodesMap = buildLineToOpcodesMap(bytecode, sourceMapOrLocationData);
|
|
17
18
|
return Object.fromEntries(Object.entries(lineToOpcodesMap).map(([lineNumber, opcodeList]) => [lineNumber, scriptToBitAuthAsm(opcodeList)]));
|
|
18
19
|
}
|
|
19
|
-
export function formatBitAuthScript(bytecode, sourceMap, sourceCode) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
export function formatBitAuthScript(bytecode, sourceMap, sourceCode, sourceTags) {
|
|
21
|
+
const locationData = sourceMapToLocationData(sourceMap);
|
|
22
|
+
const sourceLines = sourceCode.split('\n');
|
|
23
|
+
// Splice synthetic annotation lines (e.g. for-loop updates) into source and remap opcode lines
|
|
24
|
+
const insertions = buildInsertions(locationData, sourceLines, sourceTags);
|
|
25
|
+
const splicedSourceLines = spliceSyntheticSourceLines(sourceLines, insertions);
|
|
26
|
+
const splicedLocationData = updateLocationData(locationData, insertions);
|
|
27
|
+
// Group opcodes by display line and convert to ASM
|
|
28
|
+
const lineToAsm = buildLineToAsmMap(bytecode, splicedLocationData);
|
|
29
|
+
// Format output
|
|
30
|
+
const escapedLines = splicedSourceLines.map(escapeCommentChars);
|
|
31
|
+
const maxAsmLen = Math.max(...escapedLines.map((_, i) => (lineToAsm[i + 1] || '').length));
|
|
32
|
+
const maxSrcLen = Math.max(...escapedLines.map((l) => l.length));
|
|
33
|
+
return escapedLines.map((src, i) => {
|
|
34
|
+
const asm = lineToAsm[i + 1] || '';
|
|
35
|
+
return `${asm.padEnd(maxAsmLen)} /* ${src.padEnd(maxSrcLen)} */`;
|
|
36
|
+
}).join('\n');
|
|
37
|
+
}
|
|
38
|
+
// --- Helpers ---
|
|
39
|
+
function getDisplayLine(singleLocation) {
|
|
40
|
+
const { location, positionHint } = singleLocation;
|
|
41
|
+
return positionHint === PositionHint.END ? location.end.line : location.start.line;
|
|
42
|
+
}
|
|
43
|
+
function escapeCommentChars(text) {
|
|
44
|
+
return text.replaceAll('/*', '\\/*').replaceAll('*/', '*\\/');
|
|
45
|
+
}
|
|
46
|
+
function buildInsertions(locationData, sourceLines, sourceTags) {
|
|
47
|
+
const tags = (sourceTags ? parseSourceTags(sourceTags) : []);
|
|
48
|
+
return tags.map((tag) => {
|
|
49
|
+
const annotation = deriveTagLabel(tag, locationData, sourceLines);
|
|
50
|
+
const insertAfterLine = getDisplayLine(locationData[Math.max(tag.startIndex - 1, 0)]);
|
|
51
|
+
return { insertAfterLine, annotation, startIndex: tag.startIndex, endIndex: tag.endIndex };
|
|
30
52
|
});
|
|
31
|
-
|
|
53
|
+
}
|
|
54
|
+
function spliceSyntheticSourceLines(sourceLines, insertions) {
|
|
55
|
+
return insertions.reduceRight((lines, ins) => [...lines.slice(0, ins.insertAfterLine), ins.annotation, ...lines.slice(ins.insertAfterLine)], sourceLines);
|
|
56
|
+
}
|
|
57
|
+
function updateLocationData(locationData, insertions) {
|
|
58
|
+
return insertions.reduceRight((location, insertion) => {
|
|
59
|
+
return location.map((entry, opcodeIndex) => {
|
|
60
|
+
const currentLineNumber = getDisplayLine(location[opcodeIndex]);
|
|
61
|
+
const updatedLineNumber = getUpdatedLineNumber(currentLineNumber, insertion, opcodeIndex);
|
|
62
|
+
if (updatedLineNumber === currentLineNumber)
|
|
63
|
+
return entry;
|
|
64
|
+
return {
|
|
65
|
+
location: {
|
|
66
|
+
start: { line: updatedLineNumber, column: 0 },
|
|
67
|
+
end: { line: updatedLineNumber, column: 0 },
|
|
68
|
+
},
|
|
69
|
+
positionHint: PositionHint.START,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}, locationData);
|
|
73
|
+
}
|
|
74
|
+
const getUpdatedLineNumber = (currentLineNumber, insertion, opcodeIndex) => {
|
|
75
|
+
const newLineNumber = insertion.insertAfterLine + 1;
|
|
76
|
+
const inTagRange = opcodeIndex >= insertion.startIndex && opcodeIndex <= insertion.endIndex;
|
|
77
|
+
if (inTagRange)
|
|
78
|
+
return newLineNumber;
|
|
79
|
+
if (currentLineNumber > insertion.insertAfterLine)
|
|
80
|
+
return currentLineNumber + 1;
|
|
81
|
+
return currentLineNumber;
|
|
82
|
+
};
|
|
83
|
+
/** Derive the annotation text (e.g. ">>> for-loop update (i = i + 1)") for a source tag. */
|
|
84
|
+
function deriveTagLabel(tag, locationData, sourceLines) {
|
|
85
|
+
const headerLine = getDisplayLine(locationData[tag.startIndex]);
|
|
86
|
+
// Find the column span of the update expression using single-line locations in the tag range
|
|
87
|
+
const singleLineLocations = range(tag.startIndex, tag.endIndex)
|
|
88
|
+
.map((idx) => locationData[idx].location)
|
|
89
|
+
.filter((loc) => loc.start.line === loc.end.line);
|
|
90
|
+
const startCol = Math.min(...singleLineLocations.map((loc) => loc.start.column));
|
|
91
|
+
const endCol = Math.max(...singleLineLocations.map((loc) => loc.end.column));
|
|
92
|
+
const expression = sourceLines[headerLine - 1].substring(startCol, endCol);
|
|
93
|
+
// Indentation: use first non-empty line after header
|
|
94
|
+
const bodyLine = sourceLines.slice(headerLine).find((l) => l.trim().length > 0);
|
|
95
|
+
const indent = bodyLine?.match(/^(\s*)/)?.[1] ?? '';
|
|
96
|
+
return `${indent}>>> for-loop update (${expression})`;
|
|
32
97
|
}
|
|
33
98
|
//# sourceMappingURL=bitauth-script.js.map
|
package/dist/data.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export declare function decodeInt(encodedInt: Uint8Array, maxLength?: number): b
|
|
|
5
5
|
export declare function encodeString(str: string): Uint8Array;
|
|
6
6
|
export declare function decodeString(encodedString: Uint8Array): string;
|
|
7
7
|
export declare function placeholder(size: number): Uint8Array;
|
|
8
|
+
export declare function range(start: number, end: number): number[];
|
package/dist/data.js
CHANGED
|
@@ -34,4 +34,7 @@ export function decodeString(encodedString) {
|
|
|
34
34
|
export function placeholder(size) {
|
|
35
35
|
return new Uint8Array(size).fill(0);
|
|
36
36
|
}
|
|
37
|
+
export function range(start, end) {
|
|
38
|
+
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
39
|
+
}
|
|
37
40
|
//# sourceMappingURL=data.js.map
|
package/dist/script.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FullLocationData } from './types.js';
|
|
1
|
+
import { FullLocationData, SourceTagEntry } from './types.js';
|
|
2
2
|
import { LogEntry, RequireStatement } from './artifact.js';
|
|
3
3
|
export declare const Op: {
|
|
4
4
|
[x: number]: string;
|
|
@@ -284,13 +284,14 @@ export declare function bytecodeToBitAuthAsm(bytecode: Uint8Array): string;
|
|
|
284
284
|
export declare function countOpcodes(script: Script): number;
|
|
285
285
|
export declare function calculateBytesize(script: Script): number;
|
|
286
286
|
export declare function encodeNullDataScript(chunks: OpOrData[]): Uint8Array;
|
|
287
|
-
export declare function
|
|
287
|
+
export declare function generateContractBytecodeScript(baseScript: Script, encodedConstructorArgs: Script): Script;
|
|
288
288
|
interface OptimiseBytecodeResult {
|
|
289
289
|
script: Script;
|
|
290
290
|
locationData: FullLocationData;
|
|
291
291
|
logs: LogEntry[];
|
|
292
292
|
requires: RequireStatement[];
|
|
293
|
+
sourceTags: SourceTagEntry[];
|
|
293
294
|
}
|
|
294
|
-
export declare function optimiseBytecode(script: Script, locationData: FullLocationData, logs: LogEntry[], requires: RequireStatement[], constructorParamLength: number, runs?: number): OptimiseBytecodeResult;
|
|
295
|
+
export declare function optimiseBytecode(script: Script, locationData: FullLocationData, logs: LogEntry[], requires: RequireStatement[], sourceTags: SourceTagEntry[], constructorParamLength: number, runs?: number): OptimiseBytecodeResult;
|
|
295
296
|
export declare function optimiseBytecodeOld(script: Script, runs?: number): Script;
|
|
296
297
|
export {};
|
package/dist/script.js
CHANGED
|
@@ -102,13 +102,13 @@ function getPushDataOpcode(data) {
|
|
|
102
102
|
return Uint8Array.from([0x4c, byteLength]);
|
|
103
103
|
throw Error('Pushdata too large');
|
|
104
104
|
}
|
|
105
|
-
export function
|
|
105
|
+
export function generateContractBytecodeScript(baseScript, encodedConstructorArgs) {
|
|
106
106
|
return [...encodedConstructorArgs.slice().reverse(), ...baseScript];
|
|
107
107
|
}
|
|
108
|
-
export function optimiseBytecode(script, locationData, logs, requires, constructorParamLength, runs = 1000) {
|
|
108
|
+
export function optimiseBytecode(script, locationData, logs, requires, sourceTags, constructorParamLength, runs = 1000) {
|
|
109
109
|
for (let i = 0; i < runs; i += 1) {
|
|
110
110
|
const oldScript = script;
|
|
111
|
-
const { script: newScript, locationData: newLocationData, logs: newLogs, requires: newRequires, } = replaceOps(script, locationData, logs, requires, constructorParamLength, optimisationReplacements);
|
|
111
|
+
const { script: newScript, locationData: newLocationData, logs: newLogs, requires: newRequires, sourceTags: newSourceTags, } = replaceOps(script, locationData, logs, requires, sourceTags, constructorParamLength, optimisationReplacements);
|
|
112
112
|
// Break on fixed point
|
|
113
113
|
if (scriptToAsm(oldScript) === scriptToAsm(newScript))
|
|
114
114
|
break;
|
|
@@ -116,8 +116,9 @@ export function optimiseBytecode(script, locationData, logs, requires, construct
|
|
|
116
116
|
locationData = newLocationData;
|
|
117
117
|
logs = newLogs;
|
|
118
118
|
requires = newRequires;
|
|
119
|
+
sourceTags = newSourceTags;
|
|
119
120
|
}
|
|
120
|
-
return { script, locationData, logs, requires };
|
|
121
|
+
return { script, locationData, logs, requires, sourceTags };
|
|
121
122
|
}
|
|
122
123
|
export function optimiseBytecodeOld(script, runs = 1000) {
|
|
123
124
|
const optimisations = OptimisationsEquivFile
|
|
@@ -162,11 +163,12 @@ function replaceOpsOld(script, optimisations) {
|
|
|
162
163
|
asm = asm.replace(/\s+/g, ' ').trim();
|
|
163
164
|
return asmToScript(asm);
|
|
164
165
|
}
|
|
165
|
-
function replaceOps(script, locationData, logs, requires, constructorParamLength, optimisations) {
|
|
166
|
+
function replaceOps(script, locationData, logs, requires, sourceTags, constructorParamLength, optimisations) {
|
|
166
167
|
let asm = scriptToAsm(script);
|
|
167
168
|
let newLocationData = [...locationData];
|
|
168
169
|
let newLogs = [...logs];
|
|
169
170
|
let newRequires = [...requires];
|
|
171
|
+
let newSourceTags = [...sourceTags];
|
|
170
172
|
optimisations.forEach(([pattern, replacement]) => {
|
|
171
173
|
let processedAsm = '';
|
|
172
174
|
let asmToSearch = asm;
|
|
@@ -249,6 +251,12 @@ function replaceOps(script, locationData, logs, requires, constructorParamLength
|
|
|
249
251
|
}),
|
|
250
252
|
};
|
|
251
253
|
});
|
|
254
|
+
// Source tags use raw script indices (no constructor offset), so we adjust using scriptIndex directly
|
|
255
|
+
newSourceTags = newSourceTags.map((tag) => ({
|
|
256
|
+
...tag,
|
|
257
|
+
startIndex: tag.startIndex >= scriptIndex ? Math.max(scriptIndex, tag.startIndex - lengthDiff) : tag.startIndex,
|
|
258
|
+
endIndex: tag.endIndex >= scriptIndex ? Math.max(scriptIndex, tag.endIndex - lengthDiff) : tag.endIndex,
|
|
259
|
+
}));
|
|
252
260
|
// We add the replacement to the processed asm
|
|
253
261
|
processedAsm = mergeAsm(processedAsm, replacement);
|
|
254
262
|
// We do not add the matched pattern anywhere since it gets replaced
|
|
@@ -267,6 +275,7 @@ function replaceOps(script, locationData, logs, requires, constructorParamLength
|
|
|
267
275
|
locationData: newLocationData,
|
|
268
276
|
logs: newLogs,
|
|
269
277
|
requires: newRequires,
|
|
278
|
+
sourceTags: newSourceTags,
|
|
270
279
|
};
|
|
271
280
|
}
|
|
272
281
|
const getHighestEndLocation = (locations) => {
|
package/dist/source-map.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import { FullLocationData } from './types.js';
|
|
1
|
+
import { FullLocationData, SourceTagEntry } from './types.js';
|
|
2
2
|
export declare function generateSourceMap(locationData: FullLocationData): string;
|
|
3
3
|
export declare const sourceMapToLocationData: (sourceMap: string) => FullLocationData;
|
|
4
|
+
export declare function parseSourceTags(sourceTags: string): SourceTagEntry[];
|
|
5
|
+
export declare function generateSourceTags(entries: SourceTagEntry[]): string;
|
package/dist/source-map.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PositionHint } from './types.js';
|
|
1
|
+
import { PositionHint, SourceTagKind } from './types.js';
|
|
2
2
|
/*
|
|
3
3
|
* The source mappings for the bytecode use the following notation (similar to Solidity):
|
|
4
4
|
*
|
|
@@ -100,4 +100,25 @@ const parsePositionHint = (hint) => {
|
|
|
100
100
|
return PositionHint.START;
|
|
101
101
|
return undefined;
|
|
102
102
|
};
|
|
103
|
+
const SOURCE_TAG_KIND_VALUES = new Set(Object.values(SourceTagKind));
|
|
104
|
+
/*
|
|
105
|
+
* Format: "startIndex:endIndex:kind;..." e.g. "14:16:fu;38:42:fu"
|
|
106
|
+
* (currently only `fu` (For-Update) is supported as source tag kind)
|
|
107
|
+
*/
|
|
108
|
+
export function parseSourceTags(sourceTags) {
|
|
109
|
+
if (!sourceTags)
|
|
110
|
+
return [];
|
|
111
|
+
return sourceTags.split(';').map((segment) => {
|
|
112
|
+
const [startStr, endStr, kindStr] = segment.split(':');
|
|
113
|
+
if (!SOURCE_TAG_KIND_VALUES.has(kindStr)) {
|
|
114
|
+
throw new Error(`Unknown source tag kind: ${kindStr}`);
|
|
115
|
+
}
|
|
116
|
+
return { startIndex: Number(startStr), endIndex: Number(endStr), kind: kindStr };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function generateSourceTags(entries) {
|
|
120
|
+
if (entries.length === 0)
|
|
121
|
+
return '';
|
|
122
|
+
return entries.map((entry) => `${entry.startIndex}:${entry.endIndex}:${entry.kind}`).join(';');
|
|
123
|
+
}
|
|
103
124
|
//# sourceMappingURL=source-map.js.map
|
package/dist/types.d.ts
CHANGED
package/dist/types.js
CHANGED
|
@@ -82,9 +82,9 @@ export function explicitlyCastable(from, to) {
|
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
84
|
if (from instanceof BytesType) {
|
|
85
|
-
// Can cast
|
|
85
|
+
// Can cast any bytes to int
|
|
86
86
|
if (to === PrimitiveType.INT)
|
|
87
|
-
return
|
|
87
|
+
return true;
|
|
88
88
|
// Can't cast bytes to bool or string
|
|
89
89
|
if (to === PrimitiveType.BOOL)
|
|
90
90
|
return false;
|
|
@@ -191,4 +191,10 @@ export var PositionHint;
|
|
|
191
191
|
PositionHint[PositionHint["START"] = 0] = "START";
|
|
192
192
|
PositionHint[PositionHint["END"] = 1] = "END";
|
|
193
193
|
})(PositionHint || (PositionHint = {}));
|
|
194
|
+
// Semantic tags for opcodes that need special treatment in debugging output (e.g. synthetic labels).
|
|
195
|
+
// Currently used for loop constructs where opcode order diverges from source line order.
|
|
196
|
+
export var SourceTagKind;
|
|
197
|
+
(function (SourceTagKind) {
|
|
198
|
+
SourceTagKind["FOR_UPDATE"] = "fu";
|
|
199
|
+
})(SourceTagKind || (SourceTagKind = {}));
|
|
194
200
|
//# sourceMappingURL=types.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cashscript/utils",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.5",
|
|
4
4
|
"description": "CashScript utilities and types",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin cash",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"typescript": "^5.9.2",
|
|
49
49
|
"vitest": "^4.0.15"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "672ddcaf8d8bd464e518c4902c4ee8062407512f"
|
|
52
52
|
}
|