@cashscript/utils 0.11.0-next.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.d.ts +1 -3
- package/dist/artifact.js +3 -10
- package/dist/optimisations.d.ts +1 -0
- package/dist/optimisations.js +124 -0
- package/dist/script.d.ts +11 -1
- package/dist/script.js +157 -3
- package/package.json +7 -3
package/dist/artifact.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { PathLike } from 'fs';
|
|
2
1
|
export interface AbiInput {
|
|
3
2
|
name: string;
|
|
4
3
|
type: string;
|
|
@@ -22,6 +21,7 @@ export interface StackItem {
|
|
|
22
21
|
type: string;
|
|
23
22
|
stackIndex: number;
|
|
24
23
|
ip: number;
|
|
24
|
+
transformations?: string;
|
|
25
25
|
}
|
|
26
26
|
export type LogData = StackItem | string;
|
|
27
27
|
export interface RequireStatement {
|
|
@@ -42,6 +42,4 @@ export interface Artifact {
|
|
|
42
42
|
};
|
|
43
43
|
updatedAt: string;
|
|
44
44
|
}
|
|
45
|
-
export declare function importArtifact(artifactFile: PathLike): Artifact;
|
|
46
|
-
export declare function exportArtifact(artifact: Artifact, targetFile: string, format: 'json' | 'ts'): void;
|
|
47
45
|
export declare function formatArtifact(artifact: Artifact, format: 'json' | 'ts'): string;
|
package/dist/artifact.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
export function importArtifact(artifactFile) {
|
|
3
|
-
return JSON.parse(fs.readFileSync(artifactFile, { encoding: 'utf-8' }));
|
|
4
|
-
}
|
|
5
|
-
export function exportArtifact(artifact, targetFile, format) {
|
|
6
|
-
const jsonString = formatArtifact(artifact, format);
|
|
7
|
-
fs.writeFileSync(targetFile, jsonString);
|
|
8
|
-
}
|
|
9
1
|
export function formatArtifact(artifact, format) {
|
|
10
2
|
if (format === 'ts') {
|
|
11
3
|
// We remove any undefined values to make the artifact serializable using stringifyAsTs
|
|
@@ -16,9 +8,10 @@ export function formatArtifact(artifact, format) {
|
|
|
16
8
|
}
|
|
17
9
|
const indent = (level) => ' '.repeat(level);
|
|
18
10
|
function stringifyAsTs(obj, indentationLevel = 1) {
|
|
19
|
-
// For strings
|
|
11
|
+
// For strings we use JSON.stringify to handle escaping, but we want to use single quotes instead of double quotes
|
|
12
|
+
// around string values inside objects, to match regular TS style
|
|
20
13
|
if (typeof obj === 'string') {
|
|
21
|
-
return JSON.stringify(obj).replace(/'/g, "\\'").replace(
|
|
14
|
+
return `'${JSON.stringify(obj).replace(/'/g, "\\'").replace(/\\"/g, '"').slice(1, -1)}'`;
|
|
22
15
|
}
|
|
23
16
|
// Numbers and booleans are just converted to strings
|
|
24
17
|
if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const optimisationReplacements: [string, string][];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const provableOptimisations = [
|
|
2
|
+
// Hardcoded arithmetic
|
|
3
|
+
['OP_1 OP_ADD', 'OP_1ADD'],
|
|
4
|
+
['OP_1 OP_SUB', 'OP_1SUB'],
|
|
5
|
+
['OP_1 OP_NEGATE', 'OP_1NEGATE'],
|
|
6
|
+
['OP_0 OP_NUMEQUAL OP_NOT', 'OP_0NOTEQUAL'],
|
|
7
|
+
['OP_NUMEQUAL OP_NOT', 'OP_NUMNOTEQUAL'],
|
|
8
|
+
['OP_SHA256 OP_SHA256', 'OP_HASH256'],
|
|
9
|
+
['OP_SHA256 OP_RIPEMD160', 'OP_HASH160'],
|
|
10
|
+
// Hardcoded stack ops
|
|
11
|
+
['OP_2 OP_PICK OP_1 OP_PICK OP_3 OP_PICK', 'OP_3DUP OP_SWAP'],
|
|
12
|
+
['OP_2 OP_PICK OP_2 OP_PICK OP_2 OP_PICK', 'OP_3DUP'],
|
|
13
|
+
['OP_0 OP_PICK OP_2 OP_PICK', 'OP_2DUP OP_SWAP'],
|
|
14
|
+
['OP_2 OP_PICK OP_4 OP_PICK', 'OP_2OVER OP_SWAP'],
|
|
15
|
+
['OP_3 OP_PICK OP_3 OP_PICK', 'OP_2OVER'],
|
|
16
|
+
['OP_2 OP_ROLL OP_3 OP_ROLL', 'OP_2SWAP OP_SWAP'],
|
|
17
|
+
['OP_3 OP_ROLL OP_3 OP_ROLL', 'OP_2SWAP'],
|
|
18
|
+
['OP_4 OP_ROLL OP_5 OP_ROLL', 'OP_2ROT OP_SWAP'],
|
|
19
|
+
['OP_5 OP_ROLL OP_5 OP_ROLL', 'OP_2ROT'],
|
|
20
|
+
['OP_0 OP_PICK', 'OP_DUP'],
|
|
21
|
+
['OP_1 OP_PICK', 'OP_OVER'],
|
|
22
|
+
['OP_0 OP_ROLL', ''],
|
|
23
|
+
['OP_1 OP_ROLL', 'OP_SWAP'],
|
|
24
|
+
['OP_2 OP_ROLL', 'OP_ROT'],
|
|
25
|
+
['OP_DROP OP_DROP', 'OP_2DROP'],
|
|
26
|
+
// Secondary effects
|
|
27
|
+
['OP_DUP OP_SWAP', 'OP_DUP'],
|
|
28
|
+
['OP_SWAP OP_SWAP', ''],
|
|
29
|
+
['OP_2SWAP OP_2SWAP', ''],
|
|
30
|
+
['OP_ROT OP_ROT OP_ROT', ''],
|
|
31
|
+
['OP_2ROT OP_2ROT OP_2ROT', ''],
|
|
32
|
+
['OP_OVER OP_OVER', 'OP_2DUP'],
|
|
33
|
+
['OP_DUP OP_DROP', ''],
|
|
34
|
+
['OP_DUP OP_NIP', ''],
|
|
35
|
+
// Enabling secondary effects
|
|
36
|
+
['OP_DUP OP_OVER', 'OP_DUP OP_DUP'],
|
|
37
|
+
// Merge OP_VERIFY
|
|
38
|
+
['OP_EQUAL OP_VERIFY', 'OP_EQUALVERIFY'],
|
|
39
|
+
['OP_NUMEQUAL OP_VERIFY', 'OP_NUMEQUALVERIFY'],
|
|
40
|
+
['OP_CHECKSIG OP_VERIFY', 'OP_CHECKSIGVERIFY'],
|
|
41
|
+
['OP_CHECKDATASIG OP_VERIFY', 'OP_CHECKDATASIGVERIFY'],
|
|
42
|
+
// Remove/replace extraneous OP_SWAP
|
|
43
|
+
['OP_SWAP OP_ADD', 'OP_ADD'],
|
|
44
|
+
// This was added to keep the old behaviour while explicitly disallowing partial matches in the optimisation regex
|
|
45
|
+
['OP_SWAP OP_EQUALVERIFY', 'OP_EQUALVERIFY'],
|
|
46
|
+
['OP_SWAP OP_EQUAL', 'OP_EQUAL'],
|
|
47
|
+
// This was added to keep the old behaviour while explicitly disallowing partial matches in the optimisation regex
|
|
48
|
+
['OP_SWAP OP_NUMEQUALVERIFY', 'OP_NUMEQUALVERIFY'],
|
|
49
|
+
['OP_SWAP OP_NUMEQUAL', 'OP_NUMEQUAL'],
|
|
50
|
+
['OP_SWAP OP_NUMNOTEQUAL', 'OP_NUMNOTEQUAL'],
|
|
51
|
+
['OP_SWAP OP_GREATERTHANOREQUAL', 'OP_LESSTHANOREQUAL'],
|
|
52
|
+
['OP_SWAP OP_LESSTHANOREQUAL', 'OP_GREATERTHANOREQUAL'],
|
|
53
|
+
['OP_SWAP OP_GREATERTHAN', 'OP_LESSTHAN'],
|
|
54
|
+
['OP_SWAP OP_LESSTHAN', 'OP_GREATERTHAN'],
|
|
55
|
+
['OP_SWAP OP_DROP', 'OP_NIP'],
|
|
56
|
+
['OP_SWAP OP_NIP', 'OP_DROP'],
|
|
57
|
+
// Remove/replace extraneous OP_DUP
|
|
58
|
+
['OP_DUP OP_DROP', ''],
|
|
59
|
+
['OP_DUP OP_NIP', ''],
|
|
60
|
+
// Random optimisations (don't know what I'm targeting with this)
|
|
61
|
+
['OP_2DUP OP_DROP', 'OP_OVER'],
|
|
62
|
+
['OP_2DUP OP_NIP', 'OP_DUP'],
|
|
63
|
+
['OP_CAT OP_DROP', 'OP_2DROP'],
|
|
64
|
+
['OP_NIP OP_DROP', 'OP_2DROP'],
|
|
65
|
+
// Far-fetched stuff
|
|
66
|
+
['OP_DUP OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
67
|
+
['OP_OVER OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
68
|
+
['OP_2 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
69
|
+
['OP_3 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
70
|
+
['OP_4 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
71
|
+
['OP_5 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
72
|
+
['OP_6 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
73
|
+
['OP_7 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
74
|
+
['OP_8 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
75
|
+
['OP_9 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
76
|
+
['OP_10 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
77
|
+
['OP_11 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
78
|
+
['OP_12 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
79
|
+
['OP_13 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
80
|
+
['OP_14 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
81
|
+
['OP_16 OP_PICK OP_ROT OP_SWAP OP_DROP', 'OP_SWAP'],
|
|
82
|
+
['OP_DUP OP_ROT OP_DROP', 'OP_NIP OP_DUP'],
|
|
83
|
+
['OP_OVER OP_ROT OP_DROP', 'OP_SWAP'],
|
|
84
|
+
['OP_2 OP_PICK OP_ROT OP_DROP', 'OP_NIP OP_OVER'],
|
|
85
|
+
['OP_0 OP_NIP', 'OP_DROP OP_0'],
|
|
86
|
+
['OP_1 OP_NIP', 'OP_DROP OP_1'],
|
|
87
|
+
['OP_2 OP_NIP', 'OP_DROP OP_2'],
|
|
88
|
+
['OP_3 OP_NIP', 'OP_DROP OP_3'],
|
|
89
|
+
['OP_4 OP_NIP', 'OP_DROP OP_4'],
|
|
90
|
+
['OP_5 OP_NIP', 'OP_DROP OP_5'],
|
|
91
|
+
['OP_6 OP_NIP', 'OP_DROP OP_6'],
|
|
92
|
+
['OP_7 OP_NIP', 'OP_DROP OP_7'],
|
|
93
|
+
['OP_8 OP_NIP', 'OP_DROP OP_8'],
|
|
94
|
+
['OP_9 OP_NIP', 'OP_DROP OP_9'],
|
|
95
|
+
['OP_10 OP_NIP', 'OP_DROP OP_10'],
|
|
96
|
+
['OP_11 OP_NIP', 'OP_DROP OP_11'],
|
|
97
|
+
['OP_12 OP_NIP', 'OP_DROP OP_12'],
|
|
98
|
+
['OP_13 OP_NIP', 'OP_DROP OP_13'],
|
|
99
|
+
['OP_14 OP_NIP', 'OP_DROP OP_14'],
|
|
100
|
+
['OP_15 OP_NIP', 'OP_DROP OP_15'],
|
|
101
|
+
['OP_16 OP_NIP', 'OP_DROP OP_16'],
|
|
102
|
+
['OP_2 OP_PICK OP_SWAP OP_2 OP_PICK OP_NIP', 'OP_DROP OP_2DUP'],
|
|
103
|
+
];
|
|
104
|
+
const unprovableOptimisations = [
|
|
105
|
+
// Hardcoded arithmetic
|
|
106
|
+
// CashProof can't prove OP_IF without parameters
|
|
107
|
+
['OP_NOT OP_IF', 'OP_NOTIF'],
|
|
108
|
+
// Merge OP_VERIFY
|
|
109
|
+
// CashProof can't prove OP_CHECKMULTISIG without specifying N
|
|
110
|
+
['OP_CHECKMULTISIG OP_VERIFY', 'OP_CHECKMULTISIGVERIFY'],
|
|
111
|
+
// Remove/replace extraneous OP_SWAP
|
|
112
|
+
// CashProof can't prove bitwise operators
|
|
113
|
+
['OP_SWAP OP_AND', 'OP_AND'],
|
|
114
|
+
['OP_SWAP OP_OR', 'OP_OR'],
|
|
115
|
+
['OP_SWAP OP_XOR', 'OP_XOR'],
|
|
116
|
+
// Remove/replace extraneous OP_DUP
|
|
117
|
+
// CashProof can't prove bitwise operators
|
|
118
|
+
['OP_DUP OP_AND', ''],
|
|
119
|
+
['OP_DUP OP_OR', ''],
|
|
120
|
+
];
|
|
121
|
+
// Note: we moved these optimisations into a single file, but kept the exact same order as before,
|
|
122
|
+
// because the order in which optimisations are applied can impact the output.
|
|
123
|
+
export const optimisationReplacements = [...provableOptimisations, ...unprovableOptimisations];
|
|
124
|
+
//# sourceMappingURL=optimisations.js.map
|
package/dist/script.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OpcodesBch2023 } from '@bitauth/libauth';
|
|
2
|
+
import { FullLocationData } from './types.js';
|
|
3
|
+
import { LogEntry, RequireStatement } from './artifact.js';
|
|
2
4
|
export declare const Op: typeof OpcodesBch2023;
|
|
3
5
|
export type Op = number;
|
|
4
6
|
export type OpOrData = Op | Uint8Array;
|
|
@@ -16,4 +18,12 @@ export declare function countOpcodes(script: Script): number;
|
|
|
16
18
|
export declare function calculateBytesize(script: Script): number;
|
|
17
19
|
export declare function encodeNullDataScript(chunks: OpOrData[]): Uint8Array;
|
|
18
20
|
export declare function generateRedeemScript(baseScript: Script, encodedConstructorArgs: Script): Script;
|
|
19
|
-
|
|
21
|
+
interface OptimiseBytecodeResult {
|
|
22
|
+
script: Script;
|
|
23
|
+
locationData: FullLocationData;
|
|
24
|
+
logs: LogEntry[];
|
|
25
|
+
requires: RequireStatement[];
|
|
26
|
+
}
|
|
27
|
+
export declare function optimiseBytecode(script: Script, locationData: FullLocationData, logs: LogEntry[], requires: RequireStatement[], constructorParamLength: number, runs?: number): OptimiseBytecodeResult;
|
|
28
|
+
export declare function optimiseBytecodeOld(script: Script, runs?: number): Script;
|
|
29
|
+
export {};
|
package/dist/script.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { OpcodesBch2023, encodeDataPush, hexToBin, disassembleBytecodeBch, flattenBinArray, encodeAuthenticationInstructions, decodeAuthenticationInstructions, } from '@bitauth/libauth';
|
|
2
2
|
import OptimisationsEquivFile from './cashproof-optimisations.js';
|
|
3
|
+
import { optimisationReplacements } from './optimisations.js';
|
|
4
|
+
import { PositionHint } from './types.js';
|
|
3
5
|
export const Op = OpcodesBch2023;
|
|
4
6
|
export function scriptToAsm(script) {
|
|
5
7
|
return bytecodeToAsm(scriptToBytecode(script));
|
|
@@ -101,7 +103,21 @@ function getPushDataOpcode(data) {
|
|
|
101
103
|
export function generateRedeemScript(baseScript, encodedConstructorArgs) {
|
|
102
104
|
return [...encodedConstructorArgs.slice().reverse(), ...baseScript];
|
|
103
105
|
}
|
|
104
|
-
export function optimiseBytecode(script, runs = 1000) {
|
|
106
|
+
export function optimiseBytecode(script, locationData, logs, requires, constructorParamLength, runs = 1000) {
|
|
107
|
+
for (let i = 0; i < runs; i += 1) {
|
|
108
|
+
const oldScript = script;
|
|
109
|
+
const { script: newScript, locationData: newLocationData, logs: newLogs, requires: newRequires, } = replaceOps(script, locationData, logs, requires, constructorParamLength, optimisationReplacements);
|
|
110
|
+
// Break on fixed point
|
|
111
|
+
if (scriptToAsm(oldScript) === scriptToAsm(newScript))
|
|
112
|
+
break;
|
|
113
|
+
script = newScript;
|
|
114
|
+
locationData = newLocationData;
|
|
115
|
+
logs = newLogs;
|
|
116
|
+
requires = newRequires;
|
|
117
|
+
}
|
|
118
|
+
return { script, locationData, logs, requires };
|
|
119
|
+
}
|
|
120
|
+
export function optimiseBytecodeOld(script, runs = 1000) {
|
|
105
121
|
const optimisations = OptimisationsEquivFile
|
|
106
122
|
// Split by line and filter all line comments (#)
|
|
107
123
|
.split('\n')
|
|
@@ -116,14 +132,14 @@ export function optimiseBytecode(script, runs = 1000) {
|
|
|
116
132
|
.filter((equiv) => equiv.length === 2);
|
|
117
133
|
for (let i = 0; i < runs; i += 1) {
|
|
118
134
|
const oldScript = script;
|
|
119
|
-
script =
|
|
135
|
+
script = replaceOpsOld(script, optimisations);
|
|
120
136
|
// Break on fixed point
|
|
121
137
|
if (scriptToAsm(oldScript) === scriptToAsm(script))
|
|
122
138
|
break;
|
|
123
139
|
}
|
|
124
140
|
return script;
|
|
125
141
|
}
|
|
126
|
-
function
|
|
142
|
+
function replaceOpsOld(script, optimisations) {
|
|
127
143
|
let asm = scriptToAsm(script);
|
|
128
144
|
// Apply all optimisations in the cashproof file
|
|
129
145
|
optimisations.forEach(([pattern, replacement]) => {
|
|
@@ -144,4 +160,142 @@ function replaceOps(script, optimisations) {
|
|
|
144
160
|
asm = asm.replace(/\s+/g, ' ').trim();
|
|
145
161
|
return asmToScript(asm);
|
|
146
162
|
}
|
|
163
|
+
function replaceOps(script, locationData, logs, requires, constructorParamLength, optimisations) {
|
|
164
|
+
let asm = scriptToAsm(script);
|
|
165
|
+
let newLocationData = [...locationData];
|
|
166
|
+
let newLogs = [...logs];
|
|
167
|
+
let newRequires = [...requires];
|
|
168
|
+
optimisations.forEach(([pattern, replacement]) => {
|
|
169
|
+
let processedAsm = '';
|
|
170
|
+
let asmToSearch = asm;
|
|
171
|
+
// We add a space or end of string to the end of the pattern to ensure that we match the whole pattern
|
|
172
|
+
// (no partial matches)
|
|
173
|
+
const regex = new RegExp(`${pattern}(\\s|$)`, 'g');
|
|
174
|
+
let matchIndex = asmToSearch.search(regex);
|
|
175
|
+
while (matchIndex !== -1) {
|
|
176
|
+
// We add the part before the match to the processed asm
|
|
177
|
+
processedAsm = mergeAsm(processedAsm, asmToSearch.slice(0, matchIndex));
|
|
178
|
+
// We count the number of spaces in the processed asm + 1, which is equal to the script index
|
|
179
|
+
// We do the same thing to calculate the number of opcodes in the pattern and replacement
|
|
180
|
+
const scriptIndex = processedAsm === '' ? 0 : [...processedAsm.matchAll(/\s+/g)].length + 1;
|
|
181
|
+
const patternLength = [...pattern.matchAll(/\s+/g)].length + 1;
|
|
182
|
+
const replacementLength = replacement === '' ? 0 : [...replacement.matchAll(/\s+/g)].length + 1;
|
|
183
|
+
// We get the locationData entries for every opcode in the pattern
|
|
184
|
+
const patternLocations = newLocationData.slice(scriptIndex, scriptIndex + patternLength);
|
|
185
|
+
// We get the lowest start location and highest end location of the pattern
|
|
186
|
+
const lowestStart = getLowestStartLocation(patternLocations);
|
|
187
|
+
const highestEnd = getHighestEndLocation(patternLocations);
|
|
188
|
+
// Initially we set the position hint to END if any of the pattern locations have a position hint of END
|
|
189
|
+
// It turned out that this was not the correct approach in the case of OP_NOT OP_IF => OP_NOTIF,
|
|
190
|
+
// because OP_IF and OP_NOTIF are START opcodes, and OP_NOT is an END opcode.
|
|
191
|
+
// After reviewing the entire list of optimisations, we set the position hint to the last location's position hint
|
|
192
|
+
// which we believe to be the correct approach, but it is hard to reason about.
|
|
193
|
+
// We've also consulted with AI (o3-max) to help us reason about this, and it seems to be the correct approach.
|
|
194
|
+
const positionHint = patternLocations.at(-1)?.positionHint ?? PositionHint.START;
|
|
195
|
+
// We merge the lowest start and highest end locations into a single location data entry
|
|
196
|
+
const mergedLocation = {
|
|
197
|
+
location: {
|
|
198
|
+
start: lowestStart.location.start,
|
|
199
|
+
end: highestEnd.location.end,
|
|
200
|
+
},
|
|
201
|
+
positionHint,
|
|
202
|
+
};
|
|
203
|
+
// We replace the pattern locations with the merged location
|
|
204
|
+
// (note that every opcode in the replacement has the same location)
|
|
205
|
+
const replacementLocations = new Array(replacementLength).fill(mergedLocation);
|
|
206
|
+
newLocationData.splice(scriptIndex, patternLength, ...replacementLocations);
|
|
207
|
+
const lengthDiff = patternLength - replacementLength; // 2 or 1
|
|
208
|
+
// The IP of an opcode in the script is its index within the script + the constructor parameters, because
|
|
209
|
+
// the constructor parameters still have to get added to the front of the script when a new Contract is created.
|
|
210
|
+
const scriptIp = scriptIndex + constructorParamLength;
|
|
211
|
+
newRequires = newRequires.map((require) => {
|
|
212
|
+
// We calculate the new ip of the require by subtracting the length diff between the matched pattern and replacement
|
|
213
|
+
const newCalculatedRequireIp = require.ip - lengthDiff;
|
|
214
|
+
return {
|
|
215
|
+
...require,
|
|
216
|
+
// If the require is within the pattern, we want to make sure that the new ip is at least the scriptIp
|
|
217
|
+
// Note that this is impossible for the current set of optimisations, but future proofs the code
|
|
218
|
+
ip: require.ip >= scriptIp ? Math.max(scriptIp, newCalculatedRequireIp) : require.ip,
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
newLogs = newLogs.map((log) => {
|
|
222
|
+
// We calculate the new ip of the log by subtracting the length diff between the matched pattern and replacement
|
|
223
|
+
const newCalculatedLogIp = log.ip - lengthDiff;
|
|
224
|
+
return {
|
|
225
|
+
// If the log is within the pattern, we want to make sure that the new ip is at least the scriptIp
|
|
226
|
+
ip: log.ip >= scriptIp ? Math.max(scriptIp, newCalculatedLogIp) : log.ip,
|
|
227
|
+
line: log.line,
|
|
228
|
+
data: log.data.map((data) => {
|
|
229
|
+
if (typeof data === 'string')
|
|
230
|
+
return data;
|
|
231
|
+
// If the log is completely before the pattern, we don't need to change anything
|
|
232
|
+
if (data.ip <= scriptIp)
|
|
233
|
+
return data;
|
|
234
|
+
// If the log is completely after the pattern, we just need to offset the ip by the length diff
|
|
235
|
+
if (data.ip >= scriptIp + patternLength) {
|
|
236
|
+
const newCalculatedDataIp = data.ip - lengthDiff;
|
|
237
|
+
return { ...data, ip: newCalculatedDataIp };
|
|
238
|
+
}
|
|
239
|
+
const addedTransformationsCount = data.ip - scriptIp;
|
|
240
|
+
const addedTransformations = [...pattern.split(/\s+/g)].slice(0, addedTransformationsCount).join(' ');
|
|
241
|
+
const newTransformations = data.transformations ? `${addedTransformations} ${data.transformations}` : addedTransformations;
|
|
242
|
+
return {
|
|
243
|
+
...data,
|
|
244
|
+
ip: scriptIp,
|
|
245
|
+
transformations: newTransformations,
|
|
246
|
+
};
|
|
247
|
+
}),
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
// We add the replacement to the processed asm
|
|
251
|
+
processedAsm = mergeAsm(processedAsm, replacement);
|
|
252
|
+
// We do not add the matched pattern anywhere since it gets replaced
|
|
253
|
+
// We set the asmToSearch to the part after the match
|
|
254
|
+
asmToSearch = asmToSearch.slice(matchIndex + pattern.length).trim();
|
|
255
|
+
// Find the next match
|
|
256
|
+
matchIndex = asmToSearch.search(regex);
|
|
257
|
+
}
|
|
258
|
+
// We add the remaining asm to the processed asm
|
|
259
|
+
processedAsm = mergeAsm(processedAsm, asmToSearch);
|
|
260
|
+
// We replace the original asm with the processed asm so that the next optimisation can use the updated asm
|
|
261
|
+
asm = processedAsm;
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
script: asmToScript(asm),
|
|
265
|
+
locationData: newLocationData,
|
|
266
|
+
logs: newLogs,
|
|
267
|
+
requires: newRequires,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const getHighestEndLocation = (locations) => {
|
|
271
|
+
return locations.reduce((highest, current) => {
|
|
272
|
+
if (current.location.end.line > highest.location.end.line) {
|
|
273
|
+
return current;
|
|
274
|
+
}
|
|
275
|
+
if (highest.location.end.line === current.location.end.line) {
|
|
276
|
+
if (current.location.end.column > highest.location.end.column) {
|
|
277
|
+
return current;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return highest;
|
|
281
|
+
}, locations[0]);
|
|
282
|
+
};
|
|
283
|
+
const getLowestStartLocation = (locations) => {
|
|
284
|
+
return locations.reduce((lowest, current) => {
|
|
285
|
+
if (current.location.start.line < lowest.location.start.line) {
|
|
286
|
+
return current;
|
|
287
|
+
}
|
|
288
|
+
if (lowest.location.start.line === current.location.start.line) {
|
|
289
|
+
if (current.location.start.column < lowest.location.start.column) {
|
|
290
|
+
return current;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return lowest;
|
|
294
|
+
}, locations[0]);
|
|
295
|
+
};
|
|
296
|
+
const mergeAsm = (asm1, asm2) => {
|
|
297
|
+
// We merge two ASM strings by adding a space between them, and removing any duplicate spaces
|
|
298
|
+
// or trailing/leading spaces, which might have been introduced due to regex matching / replacements / empty asm strings
|
|
299
|
+
return `${asm1} ${asm2}`.replace(/\s+/g, ' ').trim();
|
|
300
|
+
};
|
|
147
301
|
//# sourceMappingURL=script.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cashscript/utils",
|
|
3
|
-
"version": "0.11.0
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "CashScript utilities and types",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin cash",
|
|
7
7
|
"cashscript",
|
|
8
8
|
"sdk",
|
|
9
|
-
"smart contracts"
|
|
9
|
+
"smart contracts",
|
|
10
|
+
"cashtokens"
|
|
10
11
|
],
|
|
11
12
|
"homepage": "https://cashscript.org",
|
|
12
13
|
"bugs": {
|
|
@@ -18,6 +19,9 @@
|
|
|
18
19
|
},
|
|
19
20
|
"license": "MIT",
|
|
20
21
|
"author": "Rosco Kalis <roscokalis@gmail.com>",
|
|
22
|
+
"contributors": [
|
|
23
|
+
"Mathieu Geukens <mr-zwets@protonmail.com>"
|
|
24
|
+
],
|
|
21
25
|
"main": "dist/index.js",
|
|
22
26
|
"types": "dist/index.d.ts",
|
|
23
27
|
"type": "module",
|
|
@@ -48,5 +52,5 @@
|
|
|
48
52
|
"jest": "^29.7.0",
|
|
49
53
|
"typescript": "^5.7.3"
|
|
50
54
|
},
|
|
51
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "47a9dbc781e009eea4704735a256ba05ae03f719"
|
|
52
56
|
}
|