@ton/sandbox 0.37.0-tvmbeta.1 → 0.37.1

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,22 @@ 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.37.1] - 2025-09-03
9
+
10
+ ### Fixed
11
+
12
+ - Made `@ton/test-utils` import optional, preventing errors in environments where it's not installed.
13
+
14
+ ## [0.37.0] - 2025-08-18
15
+
16
+ ### Added
17
+
18
+ - Added `snapshotToSerializable` and `snapshotFromSerializable` utilities to enable snapshot serialization (e.g., to JSON), allowing snapshots to be saved, transferred, and restored easily.
19
+
20
+ ### Changed
21
+
22
+ - Switched to Jest 30
23
+
8
24
  ## [0.36.0] - 2025-08-04
9
25
 
10
26
  ### Added
package/README.md CHANGED
@@ -23,6 +23,7 @@ The key difference of this package from [ton-contract-executor](https://github.c
23
23
  * [Viewing logs](#viewing-logs)
24
24
  * [Setting smart contract state directly](#setting-smart-contract-state-directly)
25
25
  * [Using snapshots](#using-snapshots)
26
+ * [Serializing snapshots](#serializing-snapshots)
26
27
  * [Performing testing on contracts from a real network](#performing-testing-on-contracts-from-a-real-network)
27
28
  * [Step-by-step execution](#step-by-step-execution)
28
29
  * [Network/Block configuration](#networkblock-configuration)
@@ -666,11 +667,13 @@ Note that this is a low-level function and does not check any invariants, such a
666
667
  It is possible to store the whole `Blockchain` state in an object and restore this state later. This can be useful to compare the outcomes of different actions after a certain point, or to store the state of the contract system after a long series of configuration actions in order to quickly restore it for all required tests instead of setting it up each time.
667
668
 
668
669
  To store the state, do the following:
670
+
669
671
  ```typescript
670
672
  const snapshot = blockchain.snapshot()
671
673
  ```
672
674
 
673
675
  To restore the state, do the following:
676
+
674
677
  ```typescript
675
678
  await blockchain.loadFrom(snapshot)
676
679
  ```
@@ -686,6 +689,32 @@ Note: snapshots store **the entire state** of a `Blockchain` instance, that incl
686
689
 
687
690
  Basically, the state of a `Blockchain` instance after it is restored using a snapshot is the same as if the same actions were performed on that instance as on the instance from which the snapshot originates.
688
691
 
692
+ ### Serializing snapshots
693
+
694
+ Snapshots contain `Cell`s and `Buffer`s, which are **not JSON-safe**. Use the provided helpers to convert snapshots to/from plain JSON objects:
695
+
696
+ ```ts
697
+ import { snapshotToSerializable, snapshotFromSerializable } from './BlockchainSnapshot';
698
+
699
+ const serializable = snapshotToSerializable(blockchain.snapshot());
700
+ const restored = snapshotFromSerializable(serializable);
701
+ await blockchain.loadFrom(restored);
702
+ ```
703
+
704
+ This is useful for saving snapshots to disk or sending them over the network.
705
+
706
+ ```ts
707
+ import fs from 'fs';
708
+
709
+ fs.writeFileSync(
710
+ 'snapshot.json',
711
+ JSON.stringify(snapshotToSerializable(blockchain.snapshot()))
712
+ );
713
+
714
+ const snapshotJson = JSON.parse(fs.readFileSync('snapshot.json', 'utf8'));
715
+ await blockchain.loadFrom(snapshotFromSerializable(snapshotJson));
716
+ ```
717
+
689
718
  ## Performing testing on contracts from a real network
690
719
 
691
720
  It is possible to use Sandbox to perform tests on contracts that are deployed to a real network. To do that, create your `Blockchain` instance using a `RemoteBlockchainStorage`, like so:
@@ -4,11 +4,12 @@ import { BlockchainStorage } from './BlockchainStorage';
4
4
  import { Event } from '../event/Event';
5
5
  import { SandboxContractProvider } from './BlockchainContractProvider';
6
6
  import { TreasuryContract } from '../treasury/Treasury';
7
- import { GetMethodParams, GetMethodResult, LogsVerbosity, MessageParams, SmartContract, SmartContractSnapshot, SmartContractTransaction, Verbosity } from './SmartContract';
7
+ import { GetMethodParams, GetMethodResult, LogsVerbosity, MessageParams, SmartContract, SmartContractTransaction, Verbosity } from './SmartContract';
8
8
  import { ContractsMeta } from '../meta/ContractsMeta';
9
9
  import { Coverage } from '../coverage';
10
10
  import { MessageQueueManager } from './MessageQueueManager';
11
11
  import { AsyncLock } from '../utils/AsyncLock';
12
+ import { BlockchainSnapshot } from './BlockchainSnapshot';
12
13
  export type ExternalOutInfo = {
13
14
  type: 'external-out';
14
15
  src: Address;
@@ -80,19 +81,6 @@ export type TreasuryParams = Partial<{
80
81
  resetBalanceIfZero: boolean;
81
82
  }>;
82
83
  export type BlockchainConfig = Cell | 'default' | 'slim';
83
- export type BlockchainSnapshot = {
84
- contracts: SmartContractSnapshot[];
85
- networkConfig: string;
86
- lt: bigint;
87
- time?: number;
88
- verbosity: LogsVerbosity;
89
- libs?: Cell;
90
- nextCreateWalletIndex: number;
91
- prevBlocksInfo?: PrevBlocksInfo;
92
- randomSeed?: Buffer;
93
- autoDeployLibs: boolean;
94
- transactions: BlockchainTransaction[];
95
- };
96
84
  export type SendMessageIterParams = MessageParams & {
97
85
  allowParallel?: boolean;
98
86
  };
@@ -18,6 +18,7 @@ const deepcopy_1 = require("../utils/deepcopy");
18
18
  const coverage_1 = require("../coverage");
19
19
  const MessageQueueManager_1 = require("./MessageQueueManager");
20
20
  const AsyncLock_1 = require("../utils/AsyncLock");
21
+ const require_1 = require("../utils/require");
21
22
  const CREATE_WALLETS_PREFIX = 'CREATE_WALLETS';
22
23
  function createWalletsSeed(idx) {
23
24
  return `${CREATE_WALLETS_PREFIX}${idx}`;
@@ -693,8 +694,7 @@ class Blockchain {
693
694
  return new Blockchain({
694
695
  executor: opts?.executor ?? (await Executor_1.Executor.create()),
695
696
  storage: opts?.storage ?? new BlockchainStorage_1.LocalBlockchainStorage(),
696
- // eslint-disable-next-line @typescript-eslint/no-require-imports
697
- meta: opts?.meta ?? require('@ton/test-utils')?.contractsMeta,
697
+ meta: opts?.meta ?? (0, require_1.requireOptional)('@ton/test-utils')?.contractsMeta,
698
698
  ...opts,
699
699
  });
700
700
  }
@@ -0,0 +1,61 @@
1
+ import { Cell } from '@ton/core';
2
+ import { LogsVerbosity, SmartContractSnapshot } from './SmartContract';
3
+ import { BlockId, PrevBlocksInfo } from '../executor/Executor';
4
+ import { BlockchainTransaction } from './Blockchain';
5
+ export type BlockchainSnapshot = {
6
+ contracts: SmartContractSnapshot[];
7
+ networkConfig: string;
8
+ lt: bigint;
9
+ time?: number;
10
+ verbosity: LogsVerbosity;
11
+ libs?: Cell;
12
+ nextCreateWalletIndex: number;
13
+ prevBlocksInfo?: PrevBlocksInfo;
14
+ randomSeed?: Buffer;
15
+ autoDeployLibs: boolean;
16
+ transactions: BlockchainTransaction[];
17
+ };
18
+ export type SerializableBlockId = {
19
+ workchain: number;
20
+ shard: string;
21
+ seqno: number;
22
+ rootHash: string;
23
+ fileHash: string;
24
+ };
25
+ export declare function blockIdToSerializable(blockId: BlockId): SerializableBlockId;
26
+ export type SerializableSnapshot = {
27
+ contracts: {
28
+ address: string;
29
+ account: string;
30
+ lastTxTime: number;
31
+ verbosity?: Partial<LogsVerbosity>;
32
+ }[];
33
+ networkConfig: string;
34
+ lt: string;
35
+ time?: number;
36
+ verbosity: LogsVerbosity;
37
+ libs?: string;
38
+ nextCreateWalletIndex: number;
39
+ prevBlocksInfo?: {
40
+ lastMcBlocks: SerializableBlockId[];
41
+ prevKeyBlock: SerializableBlockId;
42
+ lastMcBlocks100?: SerializableBlockId[];
43
+ };
44
+ randomSeed?: string;
45
+ autoDeployLibs: boolean;
46
+ transactions: {
47
+ transaction: string;
48
+ blockchainLogs: string;
49
+ vmLogs: string;
50
+ debugLogs: string;
51
+ oldStorage?: string;
52
+ newStorage?: string;
53
+ outActions?: string;
54
+ externals: string[];
55
+ mode?: number;
56
+ parentHash?: string;
57
+ childrenHashes: string[];
58
+ }[];
59
+ };
60
+ export declare function snapshotToSerializable(snapshot: BlockchainSnapshot): SerializableSnapshot;
61
+ export declare function snapshotFromSerializable(serialized: SerializableSnapshot): BlockchainSnapshot;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blockIdToSerializable = blockIdToSerializable;
4
+ exports.snapshotToSerializable = snapshotToSerializable;
5
+ exports.snapshotFromSerializable = snapshotFromSerializable;
6
+ const core_1 = require("@ton/core");
7
+ const Event_1 = require("../event/Event");
8
+ function blockIdToSerializable(blockId) {
9
+ return {
10
+ workchain: blockId.workchain,
11
+ shard: blockId.shard.toString(),
12
+ seqno: blockId.seqno,
13
+ rootHash: blockId.rootHash.toString('hex'),
14
+ fileHash: blockId.fileHash.toString('hex'),
15
+ };
16
+ }
17
+ function writableToBase64(writer) {
18
+ return (0, core_1.beginCell)().store(writer).endCell().toBoc().toString('base64');
19
+ }
20
+ function snapshotToSerializable(snapshot) {
21
+ return {
22
+ contracts: snapshot.contracts.map((contract) => ({
23
+ account: writableToBase64((0, core_1.storeShardAccount)(contract.account)),
24
+ address: contract.address.toString(),
25
+ lastTxTime: contract.lastTxTime,
26
+ verbosity: contract.verbosity,
27
+ })),
28
+ networkConfig: snapshot.networkConfig,
29
+ lt: snapshot.lt.toString(),
30
+ time: snapshot.time,
31
+ verbosity: snapshot.verbosity,
32
+ libs: snapshot.libs?.toBoc().toString('base64'),
33
+ nextCreateWalletIndex: snapshot.nextCreateWalletIndex,
34
+ prevBlocksInfo: snapshot.prevBlocksInfo
35
+ ? {
36
+ lastMcBlocks: snapshot.prevBlocksInfo.lastMcBlocks.map(blockIdToSerializable),
37
+ prevKeyBlock: blockIdToSerializable(snapshot.prevBlocksInfo.prevKeyBlock),
38
+ lastMcBlocks100: snapshot.prevBlocksInfo.lastMcBlocks100?.map(blockIdToSerializable),
39
+ }
40
+ : undefined,
41
+ randomSeed: snapshot.randomSeed?.toString('hex'),
42
+ autoDeployLibs: snapshot.autoDeployLibs,
43
+ transactions: snapshot.transactions.map((transaction) => {
44
+ return {
45
+ transaction: writableToBase64((0, core_1.storeTransaction)(transaction)),
46
+ blockchainLogs: transaction.blockchainLogs,
47
+ vmLogs: transaction.vmLogs,
48
+ debugLogs: transaction.debugLogs,
49
+ oldStorage: transaction.oldStorage?.toBoc().toString('base64'),
50
+ newStorage: transaction.newStorage?.toBoc().toString('base64'),
51
+ outActions: transaction.outActions ? writableToBase64((0, core_1.storeOutList)(transaction.outActions)) : undefined,
52
+ externals: transaction.externals.map((external) => writableToBase64((0, core_1.storeMessageRelaxed)(external))),
53
+ mode: transaction.mode,
54
+ parentHash: transaction.parent?.hash().toString('hex'),
55
+ childrenHashes: transaction.children.map((tx) => tx.hash().toString('hex')),
56
+ };
57
+ }),
58
+ };
59
+ }
60
+ function blockIdFromSerializable(s) {
61
+ return {
62
+ workchain: s.workchain,
63
+ shard: BigInt(s.shard),
64
+ seqno: s.seqno,
65
+ rootHash: Buffer.from(s.rootHash, 'hex'),
66
+ fileHash: Buffer.from(s.fileHash, 'hex'),
67
+ };
68
+ }
69
+ function snapshotFromSerializable(serialized) {
70
+ const transactions = serialized.transactions.map((t) => {
71
+ const transaction = (0, core_1.loadTransaction)(core_1.Cell.fromBase64(t.transaction).beginParse());
72
+ return {
73
+ ...transaction,
74
+ blockchainLogs: t.blockchainLogs,
75
+ vmLogs: t.vmLogs,
76
+ debugLogs: t.debugLogs,
77
+ oldStorage: t.oldStorage ? core_1.Cell.fromBase64(t.oldStorage) : undefined,
78
+ newStorage: t.newStorage ? core_1.Cell.fromBase64(t.newStorage) : undefined,
79
+ outActions: t.outActions ? (0, core_1.loadOutList)(core_1.Cell.fromBase64(t.outActions).beginParse()) : undefined,
80
+ events: (0, Event_1.extractEvents)(transaction),
81
+ mode: t.mode,
82
+ externals: t.externals.map((ext) => (0, core_1.loadMessageRelaxed)(core_1.Cell.fromBase64(ext).beginParse())),
83
+ children: [],
84
+ };
85
+ });
86
+ const transactionsMap = new Map(transactions.map((tx) => [tx.hash().toString('hex'), tx]));
87
+ for (let i = 0; i < transactions.length; i++) {
88
+ const { parentHash, childrenHashes } = serialized.transactions[i];
89
+ transactions[i].parent = parentHash ? transactionsMap.get(parentHash) : undefined;
90
+ transactions[i].children = childrenHashes.map((hash) => transactionsMap.get(hash));
91
+ }
92
+ return {
93
+ contracts: serialized.contracts.map((contract) => ({
94
+ address: core_1.Address.parse(contract.address),
95
+ account: (0, core_1.loadShardAccount)(core_1.Cell.fromBase64(contract.account).beginParse()),
96
+ lastTxTime: contract.lastTxTime,
97
+ verbosity: contract.verbosity,
98
+ })),
99
+ networkConfig: serialized.networkConfig,
100
+ lt: BigInt(serialized.lt),
101
+ time: serialized.time,
102
+ verbosity: serialized.verbosity,
103
+ libs: serialized.libs ? core_1.Cell.fromBase64(serialized.libs) : undefined,
104
+ nextCreateWalletIndex: serialized.nextCreateWalletIndex,
105
+ prevBlocksInfo: serialized.prevBlocksInfo
106
+ ? {
107
+ lastMcBlocks: serialized.prevBlocksInfo.lastMcBlocks.map(blockIdFromSerializable),
108
+ prevKeyBlock: blockIdFromSerializable(serialized.prevBlocksInfo.prevKeyBlock),
109
+ lastMcBlocks100: serialized.prevBlocksInfo.lastMcBlocks100?.map(blockIdFromSerializable),
110
+ }
111
+ : undefined,
112
+ randomSeed: serialized.randomSeed ? Buffer.from(serialized.randomSeed, 'hex') : undefined,
113
+ autoDeployLibs: serialized.autoDeployLibs,
114
+ transactions,
115
+ };
116
+ }