The @ton/sandbox package provides a built-in way to collect detailed metrics during contract execution. This is useful for benchmarking gas usage, VM steps, message forwarding, and storage impact of smart contracts.
ℹ️ See also: Benchmark Contracts documentation
import { beginCell, toNano } from '@ton/core';
import {
Blockchain,
createMetricStore,
makeSnapshotMetric,
ContractDatabase,
defaultColor,
makeGasReport,
gasReportTable,
SnapshotMetric,
resetMetricStore,
} from '@ton/sandbox';
async function main() {
const blockchain = await Blockchain.create();
const [alice, bob] = await blockchain.createWallets(2);
// describe knowledge contracts
const contractDatabase = ContractDatabase.from({
'0xd992502b94ea96e7b34e5d62ffb0c6fc73d78b3e61f11f0848fb3a1eb1afc912': 'TreasuryContract',
TreasuryContract: {
name: 'TreasuryContract',
types: [
{ name: 'ping', header: 0x70696e67, fields: [] },
{ name: 'pong', header: 0x706f6e67, fields: [] },
],
receivers: [
{ receiver: 'internal', message: { kind: 'typed', type: 'ping' } },
{ receiver: 'internal', message: { kind: 'typed', type: 'pong' } },
],
},
});
// initialize metric store
let store = createMetricStore();
const list: SnapshotMetric[] = [];
// first snapshot
await alice.send({
to: bob.address,
value: toNano(1),
body: beginCell().storeUint(0x70696e67, 32).endCell(), // "ping"
});
await bob.send({
to: alice.address,
value: toNano(1),
body: beginCell().storeUint(0x706f6e67, 32).endCell(), // "pong"
});
list.push(makeSnapshotMetric(store, { contractDatabase, label: 'first' }));
// second snapshot
resetMetricStore();
await alice.send({
to: bob.address,
value: toNano(1),
body: beginCell().storeUint(0x70696e67, 32).endCell(), // "ping"
});
await bob.send({
to: alice.address,
value: toNano(1),
body: beginCell().storeUint(0x706f6e67, 32).endCell(), // "pong"
});
list.push(makeSnapshotMetric(store, { contractDatabase }));
// make report
const delta = makeGasReport(list);
console.log(JSON.stringify(contractDatabase.data, null, 2));
console.log(gasReportTable(delta, defaultColor));
}
main().catch((error) => {
console.log(error.message);
});const store = createMetricStore()initializes an in-memory global metric storage (per test context or worker)- The sandbox automatically collects metrics from each transaction triggered via the blockchain during test execution (via
collectMetric()) const snapshot = makeSnapshotMetric('comment', store)produces a de-duplicated and ABI auto-mapping, timestamped snapshot of collected metrics
type SnapshotMetric = {
label: string;
createdAt: Date;
items: Metric[];
}A snapshot consists of:
comment: a user-defined labelcreatedAt: the timestamp when the snapshot was generateditems: an array of uniqueMetricobjects collected during execution
Each Metric includes:
type Metric = {
// the name of the current test (if available in Jest context)
testName?: string
// address of contract in user friendly format
address: string
// hex-formatted hash of contract code
codeHash?: `0x${string}`
// total cells and bits usage of the contract's code and data
state: {
code: {
cells: number
bits: number
}
data: {
cells: number
bits: number
}
}
contractName?: string
methodName?: string
receiver?: 'internal' | 'external-in' | 'external-out'
opCode: `0x${string}`
// information from transaction phases
execute: {
compute: {
type: string
success?: boolean
gasUsed?: number
exitCode?: number
vmSteps?: number
};
action?: {
success: boolean
totalActions: number
skippedActions: number
resultCode: number
totalFwdFees?: number
totalActionFees: number
totalMessageSize: {
cells: number
bits: number
}
}
}
// total cells and bits usage of inbound and outbound messages
message: {
in: {
cells: number
bits: number
}
out: {
cells: number
bits: number
}
}
}You can exclude contracts from the snapshot using:
makeSnapshotMetric('label', store, {
contractExcludes: [
'ContractName1',
'ContractName2',
],
});Use ContractDatabase.from() to define a map of known CodeHash → ContractABI, so method names and contract names are resolved automatically:
makeSnapshotMetric('label', store, {
contractDatabase: ContractDatabase.from({
'0xCodeHashv': {...ContractABI}, // map CodeHash and ABI_1
'ContractName': {...ContractABI}, // map ContractName and ABI_2
'0xCodeHashN1': '0xCodeHash', // aliase for ABI_1
'0xCodeHashN2': 'ContractName', // aliase for ABI_2
}),
});- Read more about benchmarking smart contracts
- Integrate with blueprint
blueprint metricandblueprint benchcommands to compare snapshots over time