@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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.31.0] - 2025-05-20
9
+
10
+ ### Added
11
+
12
+ - Added methods to collect metrics of contracts
13
+ - Added `@ton/sandbox/jest-environment` and `@ton/sandbox/jest-reporter` to write metric snapshots from test run results
14
+ - Added contract method ABI auto-mapping mechanism for detailed benchmark metrics
15
+ - Added methods to generate delta reports from metrics of contracts
16
+
8
17
  ## [0.30.0] - 2025-05-12
9
18
 
10
19
  ### Changed
@@ -383,4 +392,4 @@ type LogsVerbosity = {
383
392
 
384
393
  ### Removed
385
394
 
386
- - Changed `blockchain.pushMessage`, `blockchain.processQueue`, `blockchain.runQueue` to be private
395
+ - Changed `blockchain.pushMessage`, `blockchain.processQueue`, `blockchain.runQueue` to be private
package/README.md CHANGED
@@ -15,6 +15,7 @@ The key difference of this package from [ton-contract-executor](https://github.c
15
15
  * [Cross contract tests](#cross-contract-tests)
16
16
  * [Testing key points](#testing-key-points)
17
17
  * [Test examples](#test-examples)
18
+ * [Benchmark contracts](#benchmark-contracts)
18
19
  * [Sandbox pitfalls](#sandbox-pitfalls)
19
20
  * [Viewing logs](#viewing-logs)
20
21
  * [Setting smart contract state directly](#setting-smart-contract-state-directly)
@@ -290,6 +291,109 @@ Learn more from examples:
290
291
  * [FunC Test Examples](https://docs.ton.org/develop/smart-contracts/examples#examples-of-tests-for-smart-contracts)
291
292
  * [Tact Test Examples](docs/tact-testing-examples.md)
292
293
 
294
+ ## Benchmark contracts
295
+
296
+ The `@ton/sandbox` package provides `@ton/sandbox/jest-environment` and `@ton/sandbox/jest-reporter` built-in support for benchmarking smart contract behavior during tests, including tracking gas usage, cell size, opcode execution, and action phases.
297
+ This is especially useful for performance analysis, gas optimization, and regression checks on contract logic.
298
+
299
+ > ℹ️ See also: [Collect metric API](docs/collect-metric-api.md) for low-level control and manual snapshots.
300
+
301
+ ### Features
302
+
303
+ * Automatic metric collection from all transactions triggered during tests
304
+ * Snapshot reporting with contract-level filters
305
+ * Integration with [blueprint](https://github.com/ton-org/blueprint#benchmark-contracts)
306
+
307
+ ### Setup in `jest.config.ts`
308
+
309
+ ```ts
310
+ import type { Config } from 'jest';
311
+
312
+ const config: Config = {
313
+ preset: 'ts-jest',
314
+ testEnvironment: '@ton/sandbox/jest-environment',
315
+ globalSetup: './jest.setup.ts',
316
+ testPathIgnorePatterns: ['/node_modules/', '/dist/'],
317
+ reporters: [
318
+ 'default',
319
+ ['@ton/sandbox/jest-reporter', {
320
+ // options
321
+ snapshotDir: '.snapshot', // output folder for benchmark reports, default: '.snapshot'
322
+ contractDatabase: 'contract.abi.json', // path or json a map of known contracts, see Collect metric API, default: 'contract.abi.json'
323
+ reportName: 'gas-report', // report name, default: 'gas-report'
324
+ depthCompare: 2, // comparison depth, default: 2
325
+ removeRawResult: true, // remove raw metric file, default: true
326
+ contractExcludes: [ // exclude specific contracts from snapshot, default: []
327
+ 'TreasuryContract',
328
+ ],
329
+ }],
330
+ ],
331
+ };
332
+
333
+ export default config;
334
+ ```
335
+
336
+ ### How to run benchmarks
337
+
338
+ To collect and save snapshot metrics:
339
+
340
+ ```bash
341
+ BENCH_NEW="some" npx jest
342
+ # or
343
+ npx blueprint snapshot --label "some"
344
+ ```
345
+
346
+ This will:
347
+
348
+ * Run your tests
349
+ * Collect contract execution metrics
350
+ * Save a snapshot in `.snapshot/<timestamp>.json`
351
+
352
+ To compare with a previous snapshot:
353
+
354
+ ```bash
355
+ BENCH_DIFF=true npx jest
356
+ # or
357
+ npx blueprint test --gas-report
358
+ ```
359
+
360
+ ### Or setup in `gas-report.config.ts`
361
+
362
+ ```ts
363
+ import config from './jest.config';
364
+
365
+ config.testNamePattern = '^DescribeName .* - test name$'
366
+ config.testEnvironment = '@ton/sandbox/jest-environment'
367
+ config.reporters = [
368
+ ['@ton/sandbox/jest-reporter', {
369
+ contractDatabase: 'abi.json',
370
+ contractExcludes: [
371
+ 'TreasuryContract',
372
+ ],
373
+ }],
374
+ ]
375
+ export default config;
376
+ ```
377
+
378
+ **Collect metric and get report:**
379
+
380
+ ```bash
381
+ npx blueprint snapshot --label "some label" -- --config gas-report.config.ts
382
+ npx blueprint test --gas-report -- --config gas-report.config.ts
383
+ ```
384
+
385
+ ### Output structure
386
+
387
+ By default, the reporter generates:
388
+
389
+ ```
390
+ .project-root/
391
+ ├── .snapshot/
392
+ │ └── 4200000000000.json // timestamped snapshot file
393
+ ├── .sandbox-metric-raw.jsonl // raw metric log (auto-deleted by default)
394
+ ├── contract.abi.json // map of known contracts, see Collect metric API
395
+ └── gas-report.json // aggregate report in json format
396
+ ```
293
397
 
294
398
  ## Sandbox pitfalls
295
399
 
@@ -13,6 +13,7 @@ const AsyncLock_1 = require("../utils/AsyncLock");
13
13
  const message_1 = require("../utils/message");
14
14
  const slimConfig_1 = require("../config/slimConfig");
15
15
  const testTreasurySubwalletId_1 = require("../utils/testTreasurySubwalletId");
16
+ const collectMetric_1 = require("../metric/collectMetric");
16
17
  const CREATE_WALLETS_PREFIX = 'CREATE_WALLETS';
17
18
  function createWalletsSeed(idx) {
18
19
  return `${CREATE_WALLETS_PREFIX}${idx}`;
@@ -452,25 +453,25 @@ class Blockchain {
452
453
  }
453
454
  const value = target[prop];
454
455
  if (typeof prop === 'string' && typeof value === 'function') {
456
+ const ctx = {
457
+ contract,
458
+ methodName: prop,
459
+ };
455
460
  if (prop.startsWith('get')) {
456
461
  return (...args) => value.apply(target, [provider, ...args]);
457
462
  }
458
463
  else if (prop.startsWith('send')) {
459
464
  return async (...args) => {
460
- const ret = value.apply(target, [provider, ...args]);
465
+ let ret = value.apply(target, [provider, ...args]);
461
466
  if (ret instanceof Promise) {
462
- const r = await ret;
463
- return {
464
- ...await blkch.runQueue(),
465
- result: r,
466
- };
467
- }
468
- else {
469
- return {
470
- ...await blkch.runQueue(),
471
- result: ret,
472
- };
467
+ ret = await ret;
473
468
  }
469
+ const out = {
470
+ ...await blkch.runQueue(),
471
+ result: ret,
472
+ };
473
+ await (0, collectMetric_1.collectMetric)(blkch, ctx, out);
474
+ return out;
474
475
  };
475
476
  }
476
477
  }
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export { prettyLogTransaction, prettyLogTransactions, } from './utils/prettyLogT
11
11
  export { printTransactionFees, } from './utils/printTransactionFees';
12
12
  export { internal, } from './utils/message';
13
13
  export { ExtraCurrency, } from './utils/ec';
14
+ export * from './metric';
package/dist/index.js CHANGED
@@ -1,4 +1,18 @@
1
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
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.internal = exports.printTransactionFees = exports.prettyLogTransactions = exports.prettyLogTransaction = exports.TreasuryContract = exports.Executor = exports.EmulationError = exports.TimeError = exports.GetMethodError = exports.createShardAccount = exports.createEmptyShardAccount = exports.SmartContract = exports.wrapTonClient4ForRemote = exports.RemoteBlockchainStorage = exports.LocalBlockchainStorage = exports.BlockchainSender = exports.BlockchainContractProvider = exports.toSandboxContract = exports.Blockchain = exports.defaultConfigSeqno = exports.defaultConfig = void 0;
4
18
  var defaultConfig_1 = require("./config/defaultConfig");
@@ -33,3 +47,4 @@ var printTransactionFees_1 = require("./utils/printTransactionFees");
33
47
  Object.defineProperty(exports, "printTransactionFees", { enumerable: true, get: function () { return printTransactionFees_1.printTransactionFees; } });
34
48
  var message_1 = require("./utils/message");
35
49
  Object.defineProperty(exports, "internal", { enumerable: true, get: function () { return message_1.internal; } });
50
+ __exportStar(require("./metric"), exports);
@@ -0,0 +1,10 @@
1
+ export interface BenchmarkCommandOption {
2
+ label: string;
3
+ doDiff: boolean;
4
+ }
5
+ export declare class BenchmarkCommand {
6
+ readonly label?: string;
7
+ readonly doDiff: boolean;
8
+ constructor(option?: Partial<BenchmarkCommandOption>);
9
+ get doBenchmark(): boolean;
10
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BenchmarkCommand = void 0;
4
+ class BenchmarkCommand {
5
+ constructor(option) {
6
+ option = option || {};
7
+ this.label = option?.label ?? process.env?.BENCH_NEW;
8
+ this.doDiff = option?.doDiff ?? process.env?.BENCH_DIFF === 'true' ?? false;
9
+ }
10
+ get doBenchmark() {
11
+ return this.doDiff || typeof this.label !== 'undefined';
12
+ }
13
+ }
14
+ exports.BenchmarkCommand = BenchmarkCommand;
@@ -0,0 +1,19 @@
1
+ import type { EnvironmentContext } from '@jest/environment';
2
+ import NodeEnvironment from 'jest-environment-node';
3
+ import { Config } from '@jest/types';
4
+ import { BenchmarkCommand, BenchmarkCommandOption } from './BenchmarkCommand';
5
+ export declare const sandboxMetricRawFile = ".sandbox-metric-raw.jsonl";
6
+ export type BenchmarkEnvironmentConfig = {
7
+ projectConfig: Config.ProjectConfig & {
8
+ testEnvironmentCommand?: Partial<BenchmarkCommandOption>;
9
+ };
10
+ globalConfig: Config.GlobalConfig;
11
+ };
12
+ export default class BenchmarkEnvironment extends NodeEnvironment {
13
+ protected command: BenchmarkCommand;
14
+ protected rootDir: string;
15
+ constructor(config: BenchmarkEnvironmentConfig, context: EnvironmentContext);
16
+ setup(): Promise<void>;
17
+ get resultFile(): string;
18
+ teardown(): Promise<void>;
19
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sandboxMetricRawFile = void 0;
7
+ const path_1 = require("path");
8
+ const node_fs_1 = require("node:fs");
9
+ const jest_environment_node_1 = __importDefault(require("jest-environment-node"));
10
+ const BenchmarkCommand_1 = require("./BenchmarkCommand");
11
+ const metric_1 = require("../metric");
12
+ exports.sandboxMetricRawFile = '.sandbox-metric-raw.jsonl';
13
+ class BenchmarkEnvironment extends jest_environment_node_1.default {
14
+ constructor(config, context) {
15
+ super(config, context);
16
+ this.command = new BenchmarkCommand_1.BenchmarkCommand(config.projectConfig.testEnvironmentCommand);
17
+ this.rootDir = config.globalConfig.rootDir;
18
+ }
19
+ async setup() {
20
+ if (this.command.doBenchmark) {
21
+ (0, metric_1.createMetricStore)(this.global);
22
+ }
23
+ }
24
+ get resultFile() {
25
+ return (0, path_1.join)(this.rootDir, exports.sandboxMetricRawFile);
26
+ }
27
+ async teardown() {
28
+ if (this.command.doBenchmark) {
29
+ const store = (0, metric_1.getMetricStore)(this.global) || [];
30
+ const fileName = this.resultFile;
31
+ const folder = (0, path_1.dirname)(fileName);
32
+ if (!(0, node_fs_1.existsSync)(folder)) {
33
+ (0, node_fs_1.mkdirSync)(folder, { recursive: true });
34
+ }
35
+ if (!(0, node_fs_1.existsSync)(fileName)) {
36
+ (0, node_fs_1.writeFileSync)(fileName, '');
37
+ }
38
+ for (const item of store) {
39
+ (0, node_fs_1.appendFileSync)(fileName, JSON.stringify(item) + '\n');
40
+ }
41
+ (0, metric_1.resetMetricStore)(this.global);
42
+ }
43
+ await super.teardown();
44
+ }
45
+ }
46
+ exports.default = BenchmarkEnvironment;
@@ -0,0 +1,45 @@
1
+ import { BaseReporter } from '@jest/reporters';
2
+ import type { Config } from '@jest/types';
3
+ import { ContractABI } from '@ton/core';
4
+ import { BenchmarkCommand } from './BenchmarkCommand';
5
+ import { Metric, CodeHash, SnapshotMetric, ContractDatabase } from '../metric';
6
+ export declare const defaultSnapshotDir = ".snapshot";
7
+ export declare const defaultReportName = "gas-report";
8
+ export declare const defaultContractDatabaseName = "contract.abi.json";
9
+ export declare const defaultDepthCompare = 2;
10
+ export declare const minComparisonDepth = 1;
11
+ type ReportMode = 'gas' | 'average';
12
+ export interface Options {
13
+ reportMode?: ReportMode;
14
+ reportName?: string;
15
+ snapshotDir?: string;
16
+ depthCompare?: number;
17
+ contractExcludes?: string[];
18
+ removeRawResult?: boolean;
19
+ contractDatabase?: Record<CodeHash, ContractABI> | string;
20
+ }
21
+ export default class BenchmarkReporter extends BaseReporter {
22
+ protected rootDirPath: string;
23
+ protected options: Options;
24
+ protected command: BenchmarkCommand;
25
+ contractDatabase: ContractDatabase;
26
+ constructor(globalConfig: Config.GlobalConfig, options?: Options);
27
+ get reportMode(): ReportMode;
28
+ get reportName(): string;
29
+ get snapshotDir(): string;
30
+ get snapshotDirPath(): string;
31
+ get depthCompare(): number;
32
+ get snapshotFiles(): Promise<import("../metric").SnapshotMetricList>;
33
+ get snapshots(): Promise<SnapshotMetric[]>;
34
+ get snapshotCurrent(): Promise<SnapshotMetric>;
35
+ get removeRawResult(): true;
36
+ get contractExcludes(): string[];
37
+ get sandboxMetricRawFile(): string;
38
+ get metricStore(): Promise<Metric[]>;
39
+ readContractDatabase(): ContractDatabase;
40
+ saveContractDatabase(): void;
41
+ onRunComplete(): Promise<void>;
42
+ gasReportReport(data: SnapshotMetric[], benchmarkDepth: number): string[];
43
+ saveSnapshot(label: string): Promise<string>;
44
+ }
45
+ export {};
@@ -0,0 +1,207 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.minComparisonDepth = exports.defaultDepthCompare = exports.defaultContractDatabaseName = exports.defaultReportName = exports.defaultSnapshotDir = void 0;
27
+ const path_1 = require("path");
28
+ const fs_1 = require("fs");
29
+ const chalk_1 = __importStar(require("chalk"));
30
+ const reporters_1 = require("@jest/reporters");
31
+ const BenchmarkCommand_1 = require("./BenchmarkCommand");
32
+ const BenchmarkEnvironment_1 = require("./BenchmarkEnvironment");
33
+ const readJsonl_1 = require("../utils/readJsonl");
34
+ const metric_1 = require("../metric");
35
+ exports.defaultSnapshotDir = '.snapshot';
36
+ exports.defaultReportName = 'gas-report';
37
+ exports.defaultContractDatabaseName = 'contract.abi.json';
38
+ exports.defaultDepthCompare = 2;
39
+ exports.minComparisonDepth = 1;
40
+ const PASS_TEXT = 'PASS';
41
+ const PASS = chalk_1.supportsColor ? chalk_1.default.reset.inverse.bold.green(` ${PASS_TEXT} `) : PASS_TEXT;
42
+ const SKIP_TEXT = 'SKIP';
43
+ const SKIP = chalk_1.supportsColor ? chalk_1.default.reset.inverse.bold.yellow(` ${SKIP_TEXT} `) : SKIP_TEXT;
44
+ class BenchmarkReporter extends reporters_1.BaseReporter {
45
+ constructor(globalConfig, options = {}) {
46
+ super();
47
+ this.rootDirPath = globalConfig.rootDir;
48
+ this.options = options;
49
+ this.command = new BenchmarkCommand_1.BenchmarkCommand();
50
+ if (this.depthCompare < exports.minComparisonDepth) {
51
+ throw new Error(`The minimum depth compare must be greater than or equal to ${exports.minComparisonDepth}`);
52
+ }
53
+ this.contractDatabase = this.readContractDatabase();
54
+ }
55
+ get reportMode() {
56
+ return this.options.reportMode || 'gas';
57
+ }
58
+ get reportName() {
59
+ return this.options.reportName || exports.defaultReportName;
60
+ }
61
+ get snapshotDir() {
62
+ return this.options.snapshotDir || exports.defaultSnapshotDir;
63
+ }
64
+ get snapshotDirPath() {
65
+ const dirPath = (0, path_1.join)(this.rootDirPath, this.snapshotDir);
66
+ try {
67
+ if (!(0, fs_1.existsSync)(dirPath)) {
68
+ (0, fs_1.mkdirSync)(dirPath, { recursive: true });
69
+ }
70
+ }
71
+ catch (_) {
72
+ throw new Error(`Can not create directory: ${dirPath}`);
73
+ }
74
+ return dirPath;
75
+ }
76
+ get depthCompare() {
77
+ return this.options.depthCompare || exports.defaultDepthCompare;
78
+ }
79
+ get snapshotFiles() {
80
+ return (0, metric_1.readSnapshots)(this.snapshotDirPath);
81
+ }
82
+ get snapshots() {
83
+ return this.snapshotFiles.then((list) => Object.values(list).map((item) => item.content));
84
+ }
85
+ get snapshotCurrent() {
86
+ return this.metricStore.then((store) => (0, metric_1.makeSnapshotMetric)(store, {
87
+ contractDatabase: this.contractDatabase,
88
+ contractExcludes: this.contractExcludes,
89
+ }));
90
+ }
91
+ get removeRawResult() {
92
+ return this.options.removeRawResult || true;
93
+ }
94
+ get contractExcludes() {
95
+ return this.options.contractExcludes || [];
96
+ }
97
+ get sandboxMetricRawFile() {
98
+ return (0, path_1.join)(this.rootDirPath, BenchmarkEnvironment_1.sandboxMetricRawFile);
99
+ }
100
+ get metricStore() {
101
+ if ((0, fs_1.existsSync)(this.sandboxMetricRawFile)) {
102
+ return (0, readJsonl_1.readJsonl)(this.sandboxMetricRawFile);
103
+ }
104
+ return new Promise((resolve) => resolve([]));
105
+ }
106
+ readContractDatabase() {
107
+ let data = {};
108
+ const filePath = this.options.contractDatabase || exports.defaultContractDatabaseName;
109
+ if (typeof filePath === 'string') {
110
+ try {
111
+ if ((0, fs_1.existsSync)((0, path_1.join)(this.rootDirPath, filePath))) {
112
+ data = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(this.rootDirPath, filePath), 'utf-8'));
113
+ }
114
+ }
115
+ catch (error) {
116
+ throw new Error(`Could not parse contract database: ${filePath}`);
117
+ }
118
+ }
119
+ return metric_1.ContractDatabase.from(data);
120
+ }
121
+ saveContractDatabase() {
122
+ const contractDatabase = this.options.contractDatabase;
123
+ let filePath = typeof contractDatabase === 'string' ? contractDatabase : exports.defaultContractDatabaseName;
124
+ try {
125
+ const content = JSON.stringify(this.contractDatabase.data, null, 2) + '\n';
126
+ (0, fs_1.writeFileSync)((0, path_1.join)(this.rootDirPath, filePath), content, {
127
+ encoding: 'utf8',
128
+ });
129
+ }
130
+ catch (_) {
131
+ throw new Error(`Can not write: ${filePath}`);
132
+ }
133
+ }
134
+ async onRunComplete() {
135
+ const log = [];
136
+ let status = SKIP;
137
+ if (this.command.doBenchmark) {
138
+ const list = await this.snapshots;
139
+ const snapshots = [await this.snapshotCurrent, ...list];
140
+ let doDiff = this.command.doDiff;
141
+ const depthCompare = Math.min(snapshots.length, this.depthCompare);
142
+ if (doDiff) {
143
+ log.push(`Comparison metric mode: ${this.reportMode} depth: ${depthCompare}`);
144
+ switch (this.reportMode) {
145
+ case 'gas':
146
+ log.push(...this.gasReportReport(snapshots, depthCompare));
147
+ status = PASS;
148
+ break;
149
+ default:
150
+ throw new Error(`Report mode ${this.reportMode} not supported`);
151
+ }
152
+ }
153
+ else if (this.command.label) {
154
+ log.push(`Collect metric mode: "${this.reportMode}"`);
155
+ const file = await this.saveSnapshot(this.command.label);
156
+ log.push(`Report write in '${file}'`);
157
+ status = PASS;
158
+ }
159
+ if (this.removeRawResult) {
160
+ (0, fs_1.unlinkSync)(this.sandboxMetricRawFile);
161
+ }
162
+ this.saveContractDatabase();
163
+ }
164
+ else {
165
+ log.push(`Reporter mode: ${this.reportMode}`);
166
+ }
167
+ this.log(`${status} ${log.join('\n')}`);
168
+ }
169
+ gasReportReport(data, benchmarkDepth) {
170
+ const log = [];
171
+ const reportFile = `${this.reportName}.json`;
172
+ const report = (0, metric_1.makeGasReport)(data);
173
+ try {
174
+ const reportFilePath = (0, path_1.join)(this.rootDirPath, reportFile);
175
+ (0, fs_1.writeFileSync)(reportFilePath, JSON.stringify(report, null, 2) + '\n', {
176
+ encoding: 'utf8',
177
+ });
178
+ log.push(`Gas report write in '${reportFile}'`);
179
+ }
180
+ catch (_) {
181
+ throw new Error(`Can not write: ${reportFile}`);
182
+ }
183
+ const list = report.sort((0, metric_1.sortByCreatedAt)(true)).slice(0, benchmarkDepth);
184
+ log.push((0, metric_1.gasReportTable)(list, metric_1.defaultColor));
185
+ return log;
186
+ }
187
+ async saveSnapshot(label) {
188
+ const snapshot = await this.snapshotCurrent;
189
+ snapshot.label = label;
190
+ const list = await this.snapshotFiles;
191
+ let snapshotFile = `${snapshot.createdAt.getTime()}.json`;
192
+ if (list[snapshot.label]) {
193
+ snapshotFile = list[snapshot.label].name;
194
+ }
195
+ const snapshotFilePath = (0, path_1.join)(this.snapshotDirPath, snapshotFile);
196
+ try {
197
+ (0, fs_1.writeFileSync)(snapshotFilePath, JSON.stringify(snapshot, null, 2) + '\n', {
198
+ encoding: 'utf8',
199
+ });
200
+ }
201
+ catch (_) {
202
+ throw new Error(`Can not write: ${(0, path_1.join)(this.snapshotDir, snapshotFile)}`);
203
+ }
204
+ return (0, path_1.join)(this.snapshotDir, snapshotFile);
205
+ }
206
+ }
207
+ exports.default = BenchmarkReporter;
@@ -0,0 +1,21 @@
1
+ import { ContractABI } from '@ton/core';
2
+ import { CodeHash, Metric, OpCode } from './collectMetric';
3
+ type Condition = {
4
+ codeHash: CodeHash;
5
+ opCode: OpCode;
6
+ receiver: 'internal' | 'external-in' | 'external-out';
7
+ };
8
+ export type ContractDataKey = CodeHash | string;
9
+ export type ContractData = Record<ContractDataKey, ContractABI | ContractDataKey>;
10
+ export declare class ContractDatabase {
11
+ protected list: Map<ContractDataKey, ContractABI>;
12
+ protected match: Map<ContractDataKey, ContractDataKey>;
13
+ constructor(abiList: Map<ContractDataKey, ContractABI>, codeHashMatch: Map<ContractDataKey, ContractDataKey>);
14
+ static from(data: ContractData): ContractDatabase;
15
+ get data(): ContractData;
16
+ origin(needle: ContractDataKey): string;
17
+ get(needle: ContractDataKey): ContractABI | undefined;
18
+ extract(metric: Metric): void;
19
+ by(where: Partial<Condition>): Partial<Metric>;
20
+ }
21
+ export {};