@ton/sandbox 0.30.0 → 0.31.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.
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeGasReport = exports.prepareDelta = exports.aggregatedCompareMetric = exports.toFlatDeltaResult = exports.undefinedDeltaMetric = void 0;
4
+ const collectMetric_1 = require("./collectMetric");
5
+ const undefinedDeltaMetric = () => ({
6
+ kind: 'undefined',
7
+ value: '~',
8
+ });
9
+ exports.undefinedDeltaMetric = undefinedDeltaMetric;
10
+ function toFlatDeltaResult(deltas) {
11
+ const out = {
12
+ header: ['Contract', 'Method'],
13
+ rows: [],
14
+ };
15
+ const contractSet = new Set();
16
+ const methodMap = new Map();
17
+ const metricSet = new Set();
18
+ for (const delta of deltas) {
19
+ for (const [contract, methods] of Object.entries(delta.result)) {
20
+ contractSet.add(contract);
21
+ if (!methodMap.has(contract)) {
22
+ methodMap.set(contract, new Set());
23
+ }
24
+ for (const [method, metrics] of Object.entries(methods)) {
25
+ methodMap.get(contract).add(method);
26
+ for (const metric of Object.keys(metrics)) {
27
+ metricSet.add(metric);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ const metricNames = Array.from(metricSet.values());
33
+ out.header.push(...Array.from({ length: deltas.length }, () => metricNames).flat());
34
+ for (const contract of contractSet) {
35
+ const methods = methodMap.get(contract);
36
+ for (const method of methods) {
37
+ const metrics = [];
38
+ for (const delta of deltas) {
39
+ for (const metric of metricNames) {
40
+ metrics.push(delta.result?.[contract]?.[method]?.[metric] ?? (0, exports.undefinedDeltaMetric)());
41
+ }
42
+ }
43
+ out.rows.push([contract, method, ...metrics]);
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+ exports.toFlatDeltaResult = toFlatDeltaResult;
49
+ const aggregateTarget = ['cells', 'bits', 'gasUsed'];
50
+ const statusTarget = ['success', 'exitCode', 'resultCode'];
51
+ const deltaTarget = [
52
+ ...aggregateTarget,
53
+ ...statusTarget,
54
+ 'vmSteps',
55
+ 'totalActions',
56
+ 'skippedActions',
57
+ 'totalActionFees',
58
+ 'data',
59
+ 'code',
60
+ 'state',
61
+ 'message',
62
+ 'in',
63
+ 'out',
64
+ 'execute',
65
+ 'compute',
66
+ 'action',
67
+ ];
68
+ /**
69
+ * Recursively collects the sum of all numeric fields named `needle` within an arbitrary data structure
70
+ */
71
+ function sumDeep(source, needle) {
72
+ if (source === null || typeof source !== 'object') {
73
+ return 0;
74
+ }
75
+ if (Array.isArray(source)) {
76
+ return source.map((item) => sumDeep(item, needle)).reduce((total, current) => total + current, 0);
77
+ }
78
+ let total = 0;
79
+ for (const [key, value] of Object.entries(source)) {
80
+ if (key === needle && typeof value === 'number') {
81
+ total += value;
82
+ continue;
83
+ }
84
+ total += sumDeep(value, needle);
85
+ }
86
+ return total;
87
+ }
88
+ function aggregatedCompareMetric(before, after, basePath = []) {
89
+ const out = {};
90
+ const keys = new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})]);
91
+ keys.forEach((key) => {
92
+ if (!deltaTarget.includes(key))
93
+ return;
94
+ const prev = before[key];
95
+ const next = after[key];
96
+ const path = [...basePath, key];
97
+ if (prev === undefined || next === undefined)
98
+ return;
99
+ if (typeof prev === 'object' && typeof next === 'object') {
100
+ Object.assign(out, aggregatedCompareMetric(prev, next, path));
101
+ aggregateTarget.forEach((aggKey) => {
102
+ const beforeSum = sumDeep(prev, aggKey);
103
+ const afterSum = sumDeep(next, aggKey);
104
+ const aggPath = [...path, aggKey].join('.');
105
+ out[aggPath] = {
106
+ kind: beforeSum > afterSum ? 'decrease' : beforeSum < afterSum ? 'increase' : 'same',
107
+ path: aggPath,
108
+ before: beforeSum,
109
+ after: afterSum,
110
+ };
111
+ });
112
+ return;
113
+ }
114
+ if (typeof prev === 'number' && typeof next === 'number') {
115
+ const fullPath = path.join('.');
116
+ out[fullPath] = {
117
+ kind: prev > next ? 'decrease' : prev < next ? 'increase' : 'same',
118
+ path: fullPath,
119
+ before: prev,
120
+ after: next,
121
+ };
122
+ }
123
+ });
124
+ if (basePath.length === 0) {
125
+ aggregateTarget.forEach((aggKey) => {
126
+ const beforeSum = sumDeep(before, aggKey);
127
+ const afterSum = sumDeep(after, aggKey);
128
+ const rootPath = `root.${aggKey}`;
129
+ out[rootPath] = {
130
+ kind: beforeSum > afterSum ? 'decrease' : beforeSum < afterSum ? 'increase' : 'same',
131
+ path: rootPath,
132
+ before: beforeSum,
133
+ after: afterSum,
134
+ };
135
+ });
136
+ }
137
+ return out;
138
+ }
139
+ exports.aggregatedCompareMetric = aggregatedCompareMetric;
140
+ function prepareItemDelta(item, calcDelta = true) {
141
+ const out = (0, exports.undefinedDeltaMetric)();
142
+ if (!item) {
143
+ return out;
144
+ }
145
+ out.kind = calcDelta ? item.kind : 'init';
146
+ out.value = item.after.toString();
147
+ if (calcDelta) {
148
+ let change = item.kind === 'increase' ? ' +' : ' ';
149
+ if (item.kind === 'same') {
150
+ change += 'same';
151
+ }
152
+ else if (item.before === 0) {
153
+ change += '100.00%';
154
+ }
155
+ else {
156
+ change += (((item.after - item.before) / item.before) * 100).toFixed(2) + '%';
157
+ }
158
+ out.value += change;
159
+ }
160
+ return out;
161
+ }
162
+ function prepareDelta(pair) {
163
+ const result = {};
164
+ const out = {
165
+ label: pair.after.label,
166
+ createdAt: pair.after.createdAt,
167
+ result,
168
+ };
169
+ const beforeMap = new Map();
170
+ if (pair.before) {
171
+ for (const b of pair.before.items) {
172
+ const contractKey = b.contractName || b.address;
173
+ const methodKey = b.methodName || b.opCode;
174
+ if (!beforeMap.has(contractKey)) {
175
+ beforeMap.set(contractKey, new Map());
176
+ }
177
+ beforeMap.get(contractKey).set(methodKey, b);
178
+ }
179
+ }
180
+ for (const a of pair.after.items) {
181
+ const contractKey = a.contractName || a.address;
182
+ const methodKey = a.methodName || a.opCode;
183
+ const b = beforeMap.get(contractKey)?.get(methodKey); // может быть undefined
184
+ const calcDelta = !!b;
185
+ if (!result[contractKey]) {
186
+ result[contractKey] = {};
187
+ }
188
+ const item = {};
189
+ result[contractKey][methodKey] = item;
190
+ const delta = aggregatedCompareMetric(b || a, a);
191
+ item.gasUsed = prepareItemDelta(delta['execute.compute.gasUsed'], calcDelta);
192
+ item.cells = prepareItemDelta(delta['state.cells'], calcDelta);
193
+ item.bits = prepareItemDelta(delta['state.bits'], calcDelta);
194
+ }
195
+ return out;
196
+ }
197
+ exports.prepareDelta = prepareDelta;
198
+ function makeGasReport(list) {
199
+ list = (list || []).sort((0, collectMetric_1.sortByCreatedAt)());
200
+ const out = [];
201
+ if (list.length === 0)
202
+ return out;
203
+ for (let i = 0; i < list.length; i++) {
204
+ const after = list[i];
205
+ const before = i > 0 ? list[i - 1] : undefined;
206
+ out.push(prepareDelta({ after, before }));
207
+ }
208
+ return out;
209
+ }
210
+ exports.makeGasReport = makeGasReport;
@@ -0,0 +1,2 @@
1
+ import { ColorDelta, DeltaResult } from './deltaResult';
2
+ export declare function gasReportTable(list: DeltaResult[], color?: ColorDelta): string;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gasReportTable = void 0;
4
+ const table_1 = require("table");
5
+ const deltaResult_1 = require("./deltaResult");
6
+ const collectMetric_1 = require("./collectMetric");
7
+ const gapCellWidth = 1;
8
+ const maxCellWidth = 25;
9
+ const border = {
10
+ topBody: '─',
11
+ topJoin: '┬',
12
+ topLeft: '┌',
13
+ topRight: '┐',
14
+ bottomBody: '─',
15
+ bottomJoin: '┴',
16
+ bottomLeft: '└',
17
+ bottomRight: '┘',
18
+ bodyLeft: '│',
19
+ bodyRight: '│',
20
+ bodyJoin: '│',
21
+ joinBody: '─',
22
+ joinLeft: '├',
23
+ joinRight: '┤',
24
+ joinJoin: '┼',
25
+ };
26
+ function wrap(value, max) {
27
+ if (value.length > max) {
28
+ return `${value.slice(0, max - 3)}...`;
29
+ }
30
+ return value;
31
+ }
32
+ function prepareResult(list, color) {
33
+ list = list.sort((0, collectMetric_1.sortByCreatedAt)(true));
34
+ const flat = (0, deltaResult_1.toFlatDeltaResult)(list);
35
+ const rows = [];
36
+ const widthCols = [];
37
+ for (const [contract, method, ...values] of flat.rows) {
38
+ const row = [wrap(contract, maxCellWidth), wrap(method, maxCellWidth)];
39
+ widthCols[0] = Math.max(widthCols[0] ?? 0, row[0].length);
40
+ widthCols[1] = Math.max(widthCols[1] ?? 0, row[1].length);
41
+ values.forEach((metric, idx) => {
42
+ const value = color ? color(metric) : metric.value;
43
+ row.push(value);
44
+ const colIdx = idx + 2;
45
+ widthCols[colIdx] = Math.max(widthCols[colIdx] ?? 0, metric.value.length);
46
+ });
47
+ rows.push(row);
48
+ }
49
+ const headers = flat.header;
50
+ for (let i = 0; i < headers.length; i++) {
51
+ widthCols[i] = Math.max(widthCols[i] ?? 0, headers[i].length + gapCellWidth);
52
+ }
53
+ const groupIndex = {};
54
+ let current = 0;
55
+ while (current < rows.length) {
56
+ const contract = rows[current][0];
57
+ let count = 1;
58
+ while (rows[current + count]?.[0] === contract) {
59
+ count++;
60
+ }
61
+ groupIndex[contract] = { index: current, size: count };
62
+ current += count;
63
+ }
64
+ return {
65
+ labels: list.map((s) => s.label),
66
+ headers,
67
+ widthCols,
68
+ rows,
69
+ groupIndex,
70
+ };
71
+ }
72
+ function gasReportTable(list, color) {
73
+ const result = prepareResult(list, color);
74
+ if (result.rows.length < 1) {
75
+ return 'No data available';
76
+ }
77
+ const columns = [];
78
+ for (let i = 0; i < result.headers.length; i++) {
79
+ columns.push({ alignment: 'center', verticalAlignment: 'middle', width: result.widthCols[i] });
80
+ }
81
+ const spanningCells = [
82
+ { col: 0, row: 0, rowSpan: 2, verticalAlignment: 'middle' },
83
+ { col: 1, row: 0, rowSpan: 2, verticalAlignment: 'middle' }, // Method title
84
+ ];
85
+ for (const group of Object.values(result.groupIndex)) {
86
+ // rowSpan for Contract name
87
+ spanningCells.push({ col: 0, row: group.index + 2, rowSpan: group.size, verticalAlignment: 'middle' });
88
+ }
89
+ const data = [[], ['', '']];
90
+ data[0].push(...result.headers.slice(0, 2));
91
+ data[1].push(...result.headers.slice(2));
92
+ const metricCount = (result.headers.length - 2) / list.length;
93
+ let labelTitleOffset = 2;
94
+ for (const label of result.labels) {
95
+ spanningCells.push({
96
+ col: labelTitleOffset,
97
+ row: 0,
98
+ colSpan: metricCount,
99
+ verticalAlignment: 'middle',
100
+ });
101
+ labelTitleOffset += metricCount;
102
+ data[0].push(...[label, ...Array.from({ length: metricCount - 1 }, () => '')]);
103
+ }
104
+ data.push(...result.rows);
105
+ const config = {
106
+ columns,
107
+ spanningCells,
108
+ border,
109
+ };
110
+ return (0, table_1.table)(data, config);
111
+ }
112
+ exports.gasReportTable = gasReportTable;
@@ -0,0 +1,6 @@
1
+ export * from './collectMetric';
2
+ export * from './ContractDatabase';
3
+ export * from './defaultColor';
4
+ export * from './deltaResult';
5
+ export * from './gasReportTable';
6
+ export * from './readSnapshots';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./collectMetric"), exports);
18
+ __exportStar(require("./ContractDatabase"), exports);
19
+ __exportStar(require("./defaultColor"), exports);
20
+ __exportStar(require("./deltaResult"), exports);
21
+ __exportStar(require("./gasReportTable"), exports);
22
+ __exportStar(require("./readSnapshots"), exports);
@@ -0,0 +1,2 @@
1
+ import { SnapshotMetricList } from './collectMetric';
2
+ export declare function readSnapshots(snapshotDir: string): Promise<SnapshotMetricList>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readSnapshots = void 0;
4
+ const path_1 = require("path");
5
+ const fs_1 = require("fs");
6
+ async function readSnapshots(snapshotDir) {
7
+ const list = {};
8
+ if (!(0, fs_1.existsSync)(snapshotDir)) {
9
+ return list;
10
+ }
11
+ const snapshotFiles = (0, fs_1.readdirSync)(snapshotDir).filter((f) => f.endsWith('.json'));
12
+ for (const snapshotFile of snapshotFiles) {
13
+ const data = (0, fs_1.readFileSync)((0, path_1.join)(snapshotDir, snapshotFile), 'utf-8');
14
+ try {
15
+ const raw = JSON.parse(data);
16
+ const snapshot = {
17
+ ...raw,
18
+ createdAt: new Date(raw.createdAt),
19
+ };
20
+ if (!list[snapshot.label]) {
21
+ list[snapshot.label] = {
22
+ name: snapshotFile,
23
+ content: snapshot,
24
+ };
25
+ }
26
+ }
27
+ catch (_) {
28
+ throw new Error(`Can not parse snapshot file: ${snapshotFile}`);
29
+ }
30
+ }
31
+ return list;
32
+ }
33
+ exports.readSnapshots = readSnapshots;
@@ -0,0 +1 @@
1
+ export declare function readJsonl<T>(filePath: string): Promise<T[]>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readJsonl = void 0;
4
+ const fs_1 = require("fs");
5
+ const readline_1 = require("readline");
6
+ async function readJsonl(filePath) {
7
+ const input = (0, fs_1.createReadStream)(filePath, { encoding: 'utf8' });
8
+ const readLine = (0, readline_1.createInterface)({
9
+ input,
10
+ crlfDelay: Infinity,
11
+ });
12
+ const result = [];
13
+ for await (const line of readLine) {
14
+ if (!line.trim())
15
+ continue;
16
+ try {
17
+ result.push(JSON.parse(line));
18
+ }
19
+ catch (_) {
20
+ throw new Error(`Could not parse line: ${line}`);
21
+ }
22
+ }
23
+ return result;
24
+ }
25
+ exports.readJsonl = readJsonl;
@@ -0,0 +1 @@
1
+ module.exports = require("./dist/jest/BenchmarkEnvironment").default;
@@ -0,0 +1 @@
1
+ module.exports = require("./dist/jest/BenchmarkReporter").default;
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@ton/sandbox",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "TON transaction emulator",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
7
7
  "author": "TonTech",
8
8
  "files": [
9
+ "jest-environment.js",
10
+ "jest-reporter.js",
9
11
  "dist/**/*"
10
12
  ],
11
13
  "repository": {
@@ -20,19 +22,31 @@
20
22
  "@types/jest": "^29.5.0",
21
23
  "@types/node": "^18.15.11",
22
24
  "jest": "^29.5.0",
25
+ "jest-config": "^29.7.0",
26
+ "jest-environment-node": "^29.7.0",
23
27
  "ts-jest": "^29.0.5",
24
28
  "ts-node": "^10.9.1",
25
29
  "typescript": "^4.9.5"
26
30
  },
27
31
  "peerDependencies": {
28
- "@ton/crypto": ">=3.3.0"
32
+ "@ton/crypto": ">=3.3.0",
33
+ "jest": "^29.5.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "jest": {
37
+ "optional": true
38
+ }
29
39
  },
30
40
  "scripts": {
41
+ "metric": "BENCH_NEW=\"@ton/sandbox v$(npm view @ton/sandbox version)\" jest",
31
42
  "wasm:pack": "ts-node ./scripts/pack-wasm.ts",
32
43
  "wasm:copy": "cp src/executor/emulator-emscripten.js src/executor/emulator-emscripten.wasm.js ./dist/executor",
33
44
  "test": "yarn wasm:pack && yarn jest src",
34
- "build": "rm -rf dist && yarn wasm:pack && yarn test && tsc && yarn wasm:copy",
45
+ "build": "rm -rf dist && yarn wasm:pack && tsc && yarn test && yarn wasm:copy",
35
46
  "config:pack": "ts-node ./scripts/pack-config.ts"
36
47
  },
37
- "packageManager": "yarn@3.6.1"
48
+ "packageManager": "yarn@3.6.1",
49
+ "dependencies": {
50
+ "table": "^6.9.0"
51
+ }
38
52
  }