@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,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContractDatabase = void 0;
4
+ const collectMetric_1 = require("./collectMetric");
5
+ class ContractDatabase {
6
+ constructor(abiList, codeHashMatch) {
7
+ this.list = abiList;
8
+ this.match = codeHashMatch;
9
+ }
10
+ static from(data) {
11
+ const list = new Map();
12
+ const match = new Map();
13
+ Object.entries(data).forEach(([key, value]) => {
14
+ if (((0, collectMetric_1.isCodeHash)(key) && typeof value === 'string') || ((0, collectMetric_1.isCodeHash)(value) && typeof key === 'string')) {
15
+ match.set(key, value);
16
+ }
17
+ else if (!(0, collectMetric_1.isCodeHash)(value) && typeof value !== 'string') {
18
+ list.set(key, value);
19
+ }
20
+ });
21
+ return new ContractDatabase(list, match);
22
+ }
23
+ get data() {
24
+ const out = {};
25
+ for (const [key, value] of this.match) {
26
+ out[key] = value;
27
+ }
28
+ for (const [key, value] of this.list) {
29
+ out[key] = value;
30
+ }
31
+ return out;
32
+ }
33
+ origin(needle) {
34
+ return this.match.get(needle) || needle;
35
+ }
36
+ get(needle) {
37
+ return this.list.get(this.origin(needle));
38
+ }
39
+ extract(metric) {
40
+ const abiKeyNeedle = metric.contractName || metric.codeHash;
41
+ if (!abiKeyNeedle) {
42
+ return;
43
+ }
44
+ const codeHash = metric.codeHash;
45
+ if ((0, collectMetric_1.isCodeHash)(codeHash) && abiKeyNeedle !== codeHash) {
46
+ this.match.set(codeHash, abiKeyNeedle);
47
+ }
48
+ const abiKey = this.origin(abiKeyNeedle);
49
+ const abi = this.list.get(abiKey) || {};
50
+ if (!abi.receivers) {
51
+ abi.receivers = [];
52
+ }
53
+ if (!abi.types) {
54
+ abi.types = [];
55
+ }
56
+ const find = this.by(metric);
57
+ if (!find.methodName) {
58
+ if (!abi.name) {
59
+ abi.name = metric.contractName || metric.codeHash;
60
+ }
61
+ if (metric.opCode !== '0x0') {
62
+ abi.types.push({
63
+ name: metric.methodName || metric.opCode,
64
+ header: Number(metric.opCode),
65
+ });
66
+ abi.receivers.push({
67
+ receiver: metric.receiver == 'internal' ? 'internal' : 'external',
68
+ message: {
69
+ kind: 'typed',
70
+ type: metric.methodName || metric.opCode,
71
+ },
72
+ });
73
+ }
74
+ }
75
+ this.list.set(abiKey, abi);
76
+ }
77
+ by(where) {
78
+ if (!where.codeHash) {
79
+ return {};
80
+ }
81
+ const abi = this.get(where.codeHash);
82
+ if (!abi) {
83
+ return {};
84
+ }
85
+ const out = {};
86
+ out.contractName = abi.name ? abi.name : undefined;
87
+ let abiType;
88
+ if (where.opCode) {
89
+ for (const item of abi.types || []) {
90
+ if (item.header && item.header === Number(where.opCode)) {
91
+ abiType = item;
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ if (abiType) {
97
+ const receiver = where.receiver ? (where.receiver == 'internal' ? 'internal' : 'external') : undefined;
98
+ for (const item of abi.receivers || []) {
99
+ if (receiver && receiver !== item.receiver) {
100
+ continue;
101
+ }
102
+ if (item.message.kind === 'typed' && item.message.type == abiType.name) {
103
+ out.methodName = item.message.type;
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+ }
111
+ exports.ContractDatabase = ContractDatabase;
@@ -0,0 +1,106 @@
1
+ import { Cell, Contract, Message } from '@ton/core';
2
+ import { Dictionary, DictionaryKeyTypes, TransactionComputePhase } from '@ton/core';
3
+ import { Maybe } from '@ton/core/src/utils/maybe';
4
+ import { Blockchain, SendMessageResult } from '../blockchain/Blockchain';
5
+ import { ContractDatabase } from './ContractDatabase';
6
+ export type MetricContext<T extends Contract> = {
7
+ contract: T;
8
+ methodName: string;
9
+ };
10
+ type StateShort = {
11
+ code: Cell;
12
+ data: Cell;
13
+ };
14
+ export type CellMetric = {
15
+ cells: number;
16
+ bits: number;
17
+ };
18
+ export type ComputePhaseMetric = {
19
+ type: string;
20
+ success?: boolean;
21
+ gasUsed?: number;
22
+ exitCode?: number;
23
+ vmSteps?: number;
24
+ };
25
+ export type ActionPhaseMetric = {
26
+ success: boolean;
27
+ totalActions: number;
28
+ skippedActions: number;
29
+ resultCode: number;
30
+ totalFwdFees?: number;
31
+ totalActionFees: number;
32
+ totalMessageSize: CellMetric;
33
+ };
34
+ export type AddressFriendly = string;
35
+ export declare function isAddressFriendly(value: unknown): value is AddressFriendly;
36
+ export type ContractName = string;
37
+ export type ContractMethodName = string;
38
+ export type CodeHash = `0x${string}`;
39
+ export type OpCode = `0x${string}`;
40
+ export declare function isCodeHash(value: unknown): value is CodeHash;
41
+ export type StateMetric = {
42
+ code: CellMetric;
43
+ data: CellMetric;
44
+ };
45
+ export type Metric = {
46
+ testName?: string;
47
+ address: AddressFriendly;
48
+ codeHash?: CodeHash;
49
+ state: StateMetric;
50
+ contractName?: ContractName;
51
+ methodName?: ContractMethodName;
52
+ receiver?: 'internal' | 'external-in' | 'external-out';
53
+ opCode: OpCode;
54
+ execute: {
55
+ compute: ComputePhaseMetric;
56
+ action?: ActionPhaseMetric;
57
+ };
58
+ message: {
59
+ in: CellMetric;
60
+ out: CellMetric;
61
+ };
62
+ };
63
+ export type SnapshotMetric = {
64
+ label: string;
65
+ createdAt: Date;
66
+ items: Metric[];
67
+ };
68
+ interface HasCreatedAt {
69
+ createdAt: Date;
70
+ }
71
+ export declare function sortByCreatedAt(reverse?: boolean): (a: HasCreatedAt, b: HasCreatedAt) => number;
72
+ export type SnapshotMetricFile = {
73
+ name: string;
74
+ content: SnapshotMetric;
75
+ };
76
+ export type SnapshotMetricList = Record<string, SnapshotMetricFile>;
77
+ export type SnapshotMetricConfig = {
78
+ label: string;
79
+ contractExcludes: ContractName[];
80
+ contractDatabase: ContractDatabase;
81
+ };
82
+ export declare function makeSnapshotMetric(store: Metric[], config?: Partial<SnapshotMetricConfig>): SnapshotMetric;
83
+ export declare function getMetricStore(context?: any): Array<Metric> | undefined;
84
+ export declare function createMetricStore(context?: any): Array<Metric>;
85
+ export declare function resetMetricStore(context?: any): Array<Metric>;
86
+ export declare function calcMessageSize(msg: Maybe<Message>): {
87
+ cells: number;
88
+ bits: number;
89
+ };
90
+ export declare function calcDictSize<K extends DictionaryKeyTypes, V>(dict: Dictionary<K, V>): {
91
+ cells: number;
92
+ bits: number;
93
+ };
94
+ export declare function calcCellSize(root: Cell, visited?: Set<string>): {
95
+ cells: number;
96
+ bits: number;
97
+ };
98
+ export declare function calcStateSize(state: StateShort): StateMetric;
99
+ export declare function calcComputePhase(phase: TransactionComputePhase): ComputePhaseMetric;
100
+ export declare enum OpCodeReserved {
101
+ send = 0,
102
+ notSupported = 4294967295,
103
+ notAllowed = 4294967294
104
+ }
105
+ export declare function collectMetric<T extends Contract>(blockchain: Blockchain, ctx: MetricContext<T>, result: SendMessageResult): Promise<void>;
106
+ export {};
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectMetric = exports.OpCodeReserved = exports.calcComputePhase = exports.calcStateSize = exports.calcCellSize = exports.calcDictSize = exports.calcMessageSize = exports.resetMetricStore = exports.createMetricStore = exports.getMetricStore = exports.makeSnapshotMetric = exports.sortByCreatedAt = exports.isCodeHash = exports.isAddressFriendly = void 0;
4
+ const core_1 = require("@ton/core");
5
+ const ContractDatabase_1 = require("./ContractDatabase");
6
+ function isAddressFriendly(value) {
7
+ return typeof value === 'string' && core_1.Address.isFriendly(value);
8
+ }
9
+ exports.isAddressFriendly = isAddressFriendly;
10
+ function isCodeHash(value) {
11
+ return typeof value === 'string' && value.length === 66 && /^0x[0-9a-fA-F]+$/.test(value);
12
+ }
13
+ exports.isCodeHash = isCodeHash;
14
+ function sortByCreatedAt(reverse = false) {
15
+ return (a, b) => (a.createdAt.getTime() - b.createdAt.getTime()) * (reverse ? -1 : 1);
16
+ }
17
+ exports.sortByCreatedAt = sortByCreatedAt;
18
+ const STORE_METRIC = Symbol.for('ton-sandbox-metric-store');
19
+ function makeSnapshotMetric(store, config = {}) {
20
+ const label = config.label || 'current';
21
+ const contractExcludes = config.contractExcludes || new Array();
22
+ const contractDatabase = config.contractDatabase || ContractDatabase_1.ContractDatabase.from({});
23
+ const snapshot = {
24
+ label,
25
+ createdAt: new Date(),
26
+ items: new Array(),
27
+ };
28
+ // remove duplicates and extract ABI
29
+ const seen = new Set();
30
+ for (const metric of store) {
31
+ const key = JSON.stringify(metric);
32
+ if (seen.has(key))
33
+ continue;
34
+ snapshot.items.push(metric);
35
+ seen.add(key);
36
+ if (metric.codeHash) {
37
+ contractDatabase.extract(metric);
38
+ }
39
+ }
40
+ // ABI auto-mapping
41
+ for (const item of snapshot.items) {
42
+ const find = contractDatabase.by(item);
43
+ if (!item.contractName && find.contractName) {
44
+ item.contractName = find.contractName;
45
+ }
46
+ if (!item.methodName && find.methodName) {
47
+ item.methodName = find.methodName;
48
+ }
49
+ }
50
+ if (contractExcludes.length > 0) {
51
+ snapshot.items = snapshot.items.filter((it) => typeof it.contractName === 'undefined' || !contractExcludes.includes(it.contractName));
52
+ }
53
+ return snapshot;
54
+ }
55
+ exports.makeSnapshotMetric = makeSnapshotMetric;
56
+ function getMetricStore(context = globalThis) {
57
+ return context[STORE_METRIC];
58
+ }
59
+ exports.getMetricStore = getMetricStore;
60
+ function createMetricStore(context = globalThis) {
61
+ if (!Array.isArray(context[STORE_METRIC])) {
62
+ context[STORE_METRIC] = new Array();
63
+ }
64
+ return context[STORE_METRIC];
65
+ }
66
+ exports.createMetricStore = createMetricStore;
67
+ function resetMetricStore(context = globalThis) {
68
+ const store = getMetricStore(context);
69
+ if (store)
70
+ store.length = 0;
71
+ return createMetricStore(context);
72
+ }
73
+ exports.resetMetricStore = resetMetricStore;
74
+ function calcMessageSize(msg) {
75
+ if (msg) {
76
+ return calcCellSize((0, core_1.beginCell)().store((0, core_1.storeMessage)(msg)).endCell());
77
+ }
78
+ return { cells: 0, bits: 0 };
79
+ }
80
+ exports.calcMessageSize = calcMessageSize;
81
+ function calcDictSize(dict) {
82
+ if (dict.size > 0) {
83
+ return calcCellSize((0, core_1.beginCell)().storeDict(dict).endCell().asSlice().loadRef());
84
+ }
85
+ return { cells: 0, bits: 0 };
86
+ }
87
+ exports.calcDictSize = calcDictSize;
88
+ function calcCellSize(root, visited = new Set()) {
89
+ const hash = root.hash().toString('hex');
90
+ if (visited.has(hash)) {
91
+ return { cells: 0, bits: 0 };
92
+ }
93
+ visited.add(hash);
94
+ const out = {
95
+ cells: 1,
96
+ bits: root.bits.length,
97
+ };
98
+ for (const ref of root.refs) {
99
+ const childRes = calcCellSize(ref, visited);
100
+ out.cells += childRes.cells;
101
+ out.bits += childRes.bits;
102
+ }
103
+ return out;
104
+ }
105
+ exports.calcCellSize = calcCellSize;
106
+ function calcStateSize(state) {
107
+ const codeSize = calcCellSize(state.code);
108
+ const dataSize = calcCellSize(state.data);
109
+ return {
110
+ code: codeSize,
111
+ data: dataSize,
112
+ };
113
+ }
114
+ exports.calcStateSize = calcStateSize;
115
+ function calcComputePhase(phase) {
116
+ if (phase.type === 'vm') {
117
+ return {
118
+ type: phase.type,
119
+ success: phase.success,
120
+ gasUsed: Number(phase.gasUsed),
121
+ exitCode: phase.exitCode,
122
+ vmSteps: phase.vmSteps,
123
+ };
124
+ }
125
+ return {
126
+ type: phase.type,
127
+ };
128
+ }
129
+ exports.calcComputePhase = calcComputePhase;
130
+ var OpCodeReserved;
131
+ (function (OpCodeReserved) {
132
+ OpCodeReserved[OpCodeReserved["send"] = 0] = "send";
133
+ OpCodeReserved[OpCodeReserved["notSupported"] = 4294967295] = "notSupported";
134
+ OpCodeReserved[OpCodeReserved["notAllowed"] = 4294967294] = "notAllowed";
135
+ })(OpCodeReserved = exports.OpCodeReserved || (exports.OpCodeReserved = {}));
136
+ async function collectMetric(blockchain, ctx, result) {
137
+ const store = getMetricStore();
138
+ if (!Array.isArray(store)) {
139
+ return;
140
+ }
141
+ let state = { data: { cells: 0, bits: 0 }, code: { cells: 0, bits: 0 } };
142
+ let codeHash;
143
+ if (ctx.contract.init && ctx.contract.init.code && ctx.contract.init.data) {
144
+ codeHash = `0x${ctx.contract.init.code.hash().toString('hex')}`;
145
+ state = calcStateSize({ code: ctx.contract.init.code, data: ctx.contract.init.data });
146
+ }
147
+ else {
148
+ const account = (await blockchain.getContract(ctx.contract.address)).accountState;
149
+ if (account && account.type === 'active' && account.state.code && account.state.data) {
150
+ codeHash = `0x${account.state.code.hash().toString('hex')}`;
151
+ state = calcStateSize({ code: account.state.code, data: account.state.data });
152
+ }
153
+ }
154
+ let testName;
155
+ if (globalThis['expect']) {
156
+ testName = expect.getState().currentTestName;
157
+ }
158
+ let contractName = ctx.contract.constructor.name;
159
+ let methodName = ctx.methodName;
160
+ for (const tx of result.transactions) {
161
+ if (tx.description.type !== 'generic')
162
+ continue;
163
+ const receiver = tx.inMessage?.info.type;
164
+ const body = tx.inMessage?.body ? tx.inMessage.body.beginParse() : undefined;
165
+ let opCode = '0x0';
166
+ if (receiver === 'internal') {
167
+ opCode = `0x${(body && body.remainingBits >= 32 ? body.preloadUint(32) : 0).toString(16)}`;
168
+ }
169
+ if (!methodName && Object.values(OpCodeReserved).includes(Number(opCode))) {
170
+ methodName = OpCodeReserved[Number(opCode)];
171
+ }
172
+ const address = core_1.Address.parseRaw(`0:${tx.address.toString(16).padStart(64, '0')}`);
173
+ const { computePhase, actionPhase, bouncePhase, storagePhase } = tx.description;
174
+ const action = actionPhase
175
+ ? {
176
+ success: actionPhase.success,
177
+ totalActions: actionPhase.totalActions,
178
+ skippedActions: actionPhase.skippedActions,
179
+ resultCode: actionPhase.resultCode,
180
+ totalActionFees: actionPhase.totalActions,
181
+ totalFwdFees: actionPhase.totalFwdFees ? Number(actionPhase.totalFwdFees) : undefined,
182
+ totalMessageSize: {
183
+ cells: Number(actionPhase.totalMessageSize.cells),
184
+ bits: Number(actionPhase.totalMessageSize.bits),
185
+ },
186
+ }
187
+ : undefined;
188
+ const compute = calcComputePhase(computePhase);
189
+ const metric = {
190
+ testName,
191
+ address: address.toString(),
192
+ codeHash,
193
+ contractName,
194
+ methodName,
195
+ receiver,
196
+ opCode,
197
+ execute: {
198
+ compute,
199
+ action,
200
+ },
201
+ message: {
202
+ in: calcMessageSize(tx.inMessage),
203
+ out: calcDictSize(tx.outMessages),
204
+ },
205
+ state,
206
+ };
207
+ store.push(metric);
208
+ methodName = undefined;
209
+ if (!address.equals(ctx.contract.address)) {
210
+ contractName = ctx.contract.constructor.name;
211
+ }
212
+ else {
213
+ contractName = undefined;
214
+ }
215
+ }
216
+ }
217
+ exports.collectMetric = collectMetric;
@@ -0,0 +1,2 @@
1
+ import { DeltaMetric } from './deltaResult';
2
+ export declare function defaultColor(metric: DeltaMetric): string;
@@ -0,0 +1,45 @@
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.defaultColor = void 0;
27
+ const chalk_1 = __importStar(require("chalk"));
28
+ function defaultColor(metric) {
29
+ if (!chalk_1.supportsColor) {
30
+ return metric.value;
31
+ }
32
+ const { green, redBright, gray, yellowBright } = chalk_1.default;
33
+ let color = gray;
34
+ if (metric.kind === 'decrease') {
35
+ color = green;
36
+ }
37
+ else if (metric.kind === 'increase') {
38
+ color = redBright;
39
+ }
40
+ else if (metric.kind === 'undefined') {
41
+ color = yellowBright;
42
+ }
43
+ return color(metric.value);
44
+ }
45
+ exports.defaultColor = defaultColor;
@@ -0,0 +1,39 @@
1
+ import { AddressFriendly, ContractMethodName, ContractName, Metric, OpCode, SnapshotMetric } from './collectMetric';
2
+ export type KindDelta = 'undefined' | 'init' | 'same' | 'increase' | 'decrease';
3
+ export type PathDelta = string;
4
+ export type ItemDelta = {
5
+ kind: KindDelta;
6
+ path: PathDelta;
7
+ before: number;
8
+ after: number;
9
+ };
10
+ export type ListDelta = Record<PathDelta, ItemDelta>;
11
+ export type DeltaMetric = {
12
+ kind: KindDelta;
13
+ value: string;
14
+ };
15
+ export declare const undefinedDeltaMetric: () => DeltaMetric;
16
+ export type ColorDelta = (metric: DeltaMetric) => string;
17
+ export type DeltaMetrics = Record<string, DeltaMetric>;
18
+ export type MethodDelta = Record<ContractMethodName | OpCode, DeltaMetrics>;
19
+ export type ContractDelta = Record<ContractName | AddressFriendly, MethodDelta>;
20
+ export type DeltaResult = {
21
+ label: string;
22
+ createdAt: Date;
23
+ result: ContractDelta;
24
+ };
25
+ export type DeltaRow = [contract: string, method: string, ...values: DeltaMetric[]];
26
+ export type FlatDeltaResult = {
27
+ header: string[];
28
+ rows: DeltaRow[];
29
+ };
30
+ export declare function toFlatDeltaResult(deltas: DeltaResult[]): {
31
+ header: string[];
32
+ rows: DeltaRow[];
33
+ };
34
+ export declare function aggregatedCompareMetric(before: Metric, after: Metric, basePath?: string[]): ListDelta;
35
+ export declare function prepareDelta(pair: {
36
+ after: SnapshotMetric;
37
+ before?: SnapshotMetric;
38
+ }): DeltaResult;
39
+ export declare function makeGasReport(list: SnapshotMetric[]): Array<DeltaResult>;