@ton/sandbox 0.11.0 → 0.12.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,25 @@ 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.12.0] - 2023-10-03
9
+
10
+ ### Added
11
+
12
+ - Step by step execution (`blockchain.sendMessageIter`)
13
+ - Better docs
14
+
15
+ ### Fixed
16
+
17
+ - `now` from `Blockchain` is now honored in `SmartContract.receiveMessage`
18
+ - Exit code 1 is now counted as success in get methods
19
+
20
+ ## [0.11.1] - 2023-07-26
21
+
22
+ ### Changed
23
+
24
+ - Migrated dependencies to @ton organization packages
25
+ - Bumped @ton/test-utils version to 0.3.1
26
+
8
27
  ## [0.11.0] - 2023-05-11
9
28
 
10
29
  ### Added
package/README.md CHANGED
@@ -4,16 +4,32 @@ This package allows you to emulate arbitrary TON smart contracts, send messages
4
4
 
5
5
  The key difference of this package from [ton-contract-executor](https://github.com/ton-community/ton-contract-executor) is the fact that the latter only emulates the compute phase of the contract - it does not know about any other phases and thus does not know anything about fees and balances (in a sense that it does not know whether a contract's balance will be enough to process all the out messages that it produces). On the other hand, this package emulates all the phases of a contract, and as a result, the emulation is much closer to what would happen in a real network.
6
6
 
7
+ ## Content
8
+
9
+ * [Instalation](#installation)
10
+ * [Usage](#usage)
11
+ * [Writing Tests](#writing-tests)
12
+ * [Basic test template](#basic-test-template)
13
+ * [Test a transaction with matcher](#test-a-transaction-with-matcher)
14
+ * [Testing transaction fees](#testing-transaction-fees)
15
+ * [Cross contract tests](#cross-contract-tests)
16
+ * [Test examples](#test-examples)
17
+ * [Viewing logs](#viewing-logs)
18
+ * [Network/Block configuration](#networkblock-configuration)
19
+ * [Contributors](#contributors)
20
+ * [License](#license)
21
+ * [Donations](#donations)
22
+
7
23
  ## Installation
8
24
 
9
25
  Requires node 16 or higher.
10
26
 
11
27
  ```
12
- yarn add @ton/sandbox ton @ton/core ton-crypto
28
+ yarn add @ton/sandbox @ton/ton @ton/core @ton/crypto
13
29
  ```
14
30
  or
15
31
  ```
16
- npm i @ton/sandbox ton @ton/core ton-crypto
32
+ npm i @ton/sandbox @ton/ton @ton/core @ton/crypto
17
33
  ```
18
34
 
19
35
  ## Usage
@@ -25,14 +41,14 @@ import { Blockchain } from "@ton/sandbox";
25
41
  const blockchain = await Blockchain.create()
26
42
  ```
27
43
 
28
- After that, you can use the low level methods on Blockchain (such as sendMessage) to emulate any messages that you want, but the recommended way to use it is to write wrappers for your contract using the `Contract` interface from `ton-core`. Then you can use `blockchain.openContract` on instances of such contracts, and they will be wrapped in a Proxy that will supply a `ContractProvider` as a first argument to all its methods starting with either `get` or `send`. Also all `send` methods will get Promisified and will return results of running the blockchain message queue along with the original method's result in the `result` field.
44
+ After that, you can use the low level methods on Blockchain (such as sendMessage) to emulate any messages that you want, but the recommended way to use it is to write wrappers for your contract using the `Contract` interface from `@ton/core`. Then you can use `blockchain.openContract` on instances of such contracts, and they will be wrapped in a Proxy that will supply a `ContractProvider` as a first argument to all its methods starting with either `get` or `send`. Also all `send` methods will get Promisified and will return results of running the blockchain message queue along with the original method's result in the `result` field.
29
45
 
30
46
  A good example of this is the [treasury contract](/src/treasury/Treasury.ts) that is basically a built-in highload wallet meant to help you write tests for your systems of smart contracts. When `blockchain.treasury` is called, an instance of `TreasuryContract` is created and `blockchain.openContract` is called to "open" it. After that, when you call `treasury.send`, `Blockchain` automatically supplies the first `provider` argument.
31
47
 
32
48
  For your own contracts, you can draw inspiration from the contracts in the [examples](/examples/) - all of them use the `provider.internal` method to send internal messages using the treasuries passed in from the unit test file.
33
49
  Here is an excerpt of that from [NftItem.ts](/examples/contracts/NftItem.ts):
34
50
  ```typescript
35
- import { Address, beginCell, Cell, Contract, ContractProvider, Sender, toNano, Builder } from "ton-core";
51
+ import { Address, beginCell, Cell, Contract, ContractProvider, Sender, toNano, Builder } from "@ton/core";
36
52
 
37
53
  class NftItem implements Contract {
38
54
  async sendTransfer(provider: ContractProvider, via: Sender, params: {
@@ -62,7 +78,7 @@ When you call `nftItem.sendTransfer(treasury.getSender(), { to: recipient })` (w
62
78
 
63
79
  Here is another excerpt that shows the way to interact with get methods from wrappers:
64
80
  ```typescript
65
- import { Contract, ContractProvider } from "ton-core";
81
+ import { Contract, ContractProvider } from "@ton/core";
66
82
 
67
83
  export type NftItemData = {
68
84
  inited: boolean
@@ -94,10 +110,70 @@ Notes:
94
110
  - Ideally, at most one call to **either** `provider.internal` or `provider.external` should be made within a `send` method. Otherwise, you may get hard to interpret (but generally speaking correct) results.
95
111
  - No calls to `provider.external` or `provider.internal` should be made within `get` methods. Otherwise, you will get weird and wrong results in the following `send` methods of any contract.
96
112
 
113
+ ---
114
+
115
+
97
116
  ## Writing tests
98
117
 
118
+ ### Basic test template
119
+
99
120
  You can install additional `@ton/test-utils` package by running `yarn add @ton/test-utils -D` or `npm i --save-dev @ton/test-utils` (with `.toHaveTransaction` for jest or `.transaction` or `.to.have.transaction` for chai matcher) to add additional helpers for ease of testing. Don't forget to import them in your unit test files though!
100
121
 
122
+ Writing tests in Sandbox works through defining arbitary actions with the contract and comparing their results with the expected result, for example:
123
+
124
+ ```typescript
125
+ it('should execute with success', async () => { // description of the test case
126
+ const res = await main.sendMessage(sender.getSender(), toNano('0.05')); // performing an action with contract main and saving result in res
127
+
128
+ expect(res.transactions).toHaveTransaction({ // configure the expected result with expect() function
129
+ from: main.address, // set expected sender for transaction we want to test matcher properties from
130
+ success: true // set the desirable result using matcher property success
131
+ });
132
+
133
+ printTransactionFees(res.transactions); // print table with details on spent fees
134
+ });
135
+ ```
136
+
137
+
138
+ ### Test a transaction with matcher
139
+
140
+ The basic workflow of creating a test is:
141
+ 1. Create a specific wrapped `Contract` entity using `blockchain.openContract()`.
142
+ 2. Describe the actions your `Contract` should perform and save the execution result in `res` variable.
143
+ 3. Verify the properties using the `expect()` function and the matcher `toHaveTransaction()`.
144
+
145
+ The `toHaveTransaction` matcher expects an object with any combination of fields from the `FlatTransaction` type defined with the following properties
146
+
147
+ | Name | Type | Description |
148
+ |----------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
149
+ | from | Address? | Contract address of the message sender |
150
+ | to | Address | Contract address of the message destination |
151
+ | on | Address | Contract address of the message destination (Alternative name of the property `to`). |
152
+ | value | bigint? | Amount of Toncoins in the message in nanotons |
153
+ | body | Cell | Message body defined as a Cell |
154
+ | inMessageBounced | boolean? | Boolean flag Bounced. True - message is bounced, False - message is not bounced. |
155
+ | inMessageBounceable | boolean? | Boolean flag Bounceable. True - message can be bounced, False - message can not be bounced. |
156
+ | op | number? | Op code is the operation identifier number (crc32 from TL-B usually). Expected in the first 32 bits of a message body. |
157
+ | initData | Cell? | InitData Cell. Used for contract deployment processes. |
158
+ | initCode | Cell? | initCode Cell. Used for contract deployment processes. |
159
+ | deploy | boolean | Custom Sandbox flag that indicates whether the contract was deployed during this transaction. True if contract before this transaction was not initialized and after this transaction became initialized. Otherwise - False. |
160
+ | lt | bigint | Logical time (set by validators in a normal network, monotonically increases by a set interval in Sandbox). Used for defining order of transactions related to a certain contract |
161
+ | now | bigint | Unix timestamp of the transaction |
162
+ | outMessagesCount | number | Quantity of outbound messages in a certain transaction |
163
+ | oldStatus | AccountStatus | AccountStatus before transaction execution |
164
+ | endStatus | AccountStatus | AccountStatus after transaction execution |
165
+ | totalFees | bigint? | Number of spent fees in nanotons |
166
+ |aborted| boolean? | True - execution of certain transaction aborted and rollbacked because of errors or insufficient gas. Otherwise - False. |
167
+ |destroyed| boolean? | True - if the existing contract was destroyed due to executing a certain transaction. Otherwise - False. |
168
+ |exitCode| number? | TVM exit code (from compute phase) |
169
+ |actionResultCode| number? | Action phase result code |
170
+ |success| boolean? | Custom Sandbox flag that defines the resulting status of a certain transaction. True - if both the compute and the action phase succeeded. Otherwise - False. |
171
+
172
+
173
+ You can omit those that you're not interested in, and you can also pass in functions accepting those types returning booleans (`true` meaning good) to check for example number ranges, message opcodes, etc. Note however that if a field is optional (like `from?: Address`), then the function needs to accept the optional type, too.
174
+
175
+
176
+
101
177
  Here is an excerpt of how it's used in the NFT collection example mentioned above:
102
178
  ```typescript
103
179
  const buyResult = await buyer.send({
@@ -119,32 +195,80 @@ expect(buyResult.transactions).toHaveTransaction({
119
195
  ```
120
196
  (in that example `jest` is used)
121
197
 
122
- The matcher supports the following fields:
198
+
199
+ ### Testing transaction fees
200
+ It is possible to configure and update the current time of the `Blockchain`, which allows one to inspect how much a contract would spend on storage fees.
201
+
202
+ Suppose we have a `main` instance defined as a wrapped `Contract` instance `main = blockchain.openContract(/* non-wrapped Main instance */)`, and we wish to determine the amount of storage fees that will be accrued between two actions within a specified period.
203
+
123
204
  ```typescript
124
- export type FlatTransaction = {
125
- from?: Address
126
- to: Address
127
- value?: bigint
128
- body: Cell
129
- initData?: Cell
130
- initCode?: Cell
131
- deploy: boolean
132
- lt: bigint
133
- now: number
134
- outMessagesCount: number
135
- oldStatus: AccountStatus
136
- endStatus: AccountStatus
137
- totalFees?: bigint
138
- aborted?: boolean
139
- destroyed?: boolean
140
- exitCode?: number
141
- success?: boolean
142
- }
205
+ it('should storage fees cost less than 1 TON', async () => {
206
+ const time1 = Math.floor(Date.now() / 1000); // current local unix time
207
+ const time2 = time1 + 365 * 24 * 60 * 60; // offset for a year
208
+
209
+ blockchain.now = time1; // set current time
210
+ const res1 = await main.sendMessage(sender.getSender(), toNano('0.05')); // preview of fees
211
+ printTransactionFees(res1.transactions);
212
+
213
+ blockchain.now = time2; // set current time
214
+ const res2 = await main.sendMessage(sender.getSender(), toNano('0.05')); // preview of fees
215
+ printTransactionFees(res2.transactions);
216
+
217
+ const tx2 = res2.transactions[1]; // extract the transaction that executed in a year
218
+ if (tx2.description.type !== 'generic') {
219
+ throw new Error('Generic transaction expected');
220
+ }
221
+
222
+ // Check that the storagePhase fees are less than 1 TON over the course of a year
223
+ expect(tx2.description.storagePhase?.storageFeesCollected).toBeLessThanOrEqual(toNano('1'));
224
+ });
143
225
  ```
144
226
 
145
- But you can omit those you're not interested in, and you can also pass in functions accepting those types returning booleans (`true` meaning good) to check for example number ranges, message opcodes, etc. Note however that if a field is optional (like `from?: Address`), then the function needs to accept the optional type, too.
227
+ ### Cross contract tests
228
+
229
+ The Sandbox emulates the entire process of executing cross-contract interactions as if they occurred on a real blockchain.
230
+ The result of sending a message (transfer) contains basic information about all transactions and actions.
231
+ You can verify all of these by creating specific requirements via `expect()` for each action and transaction.
232
+
233
+ ```typescript
234
+ res = await main.sendMessage(...);
235
+
236
+ expect(res).toHaveTransaction(...) // test case
237
+ <...>
238
+ expect(res).toHaveTransaction(...) // test case
239
+ ```
240
+
241
+ For instance, with [Modern Jetton](https://github.com/EmelyanenkoK/modern_jetton) it's possible to test whether a `mint` message results in minting to a new jetton wallet contract and returns the excess to the minter contract.
242
+ ```typescript
243
+ it('minter admin should be able to mint jettons', async () => {
244
+ // can mint from deployer
245
+ let initialTotalSupply = await jettonMinter.getTotalSupply();
246
+ const deployerJettonWallet = await userWallet(deployer.address);
247
+ let initialJettonBalance = toNano('1000.23');
248
+ const mintResult = await jettonMinter.sendMint(deployer.getSender(), deployer.address, initialJettonBalance, toNano('0.05'), toNano('1'));
249
+
250
+ expect(mintResult.transactions).toHaveTransaction({ // test transaction of deployment of a jetton wallet
251
+ from: jettonMinter.address,
252
+ to: deployerJettonWallet.address,
253
+ deploy: true,
254
+ });
255
+
256
+ expect(mintResult.transactions).toHaveTransaction({ // test transaction of excesses returned to minter
257
+ from: deployerJettonWallet.address,
258
+ to: jettonMinter.address
259
+ });
260
+
261
+ });
262
+ ```
263
+
264
+ ### Test Examples
265
+ You can typically find various tests for Sandbox-based project contracts in the `./tests` directory.
266
+ Learn more from examples:
267
+
268
+ * [FunC Test Examples](https://docs.ton.org/develop/smart-contracts/examples#examples-of-tests-for-smart-contracts)
269
+ * [Tact Test Examples](docs/tact-testing-examples.md)
146
270
 
147
- ### Viewing logs
271
+ ## Viewing logs
148
272
 
149
273
  `Blockchain` and `SmartContract` use `LogsVerbosity` to determine what kinds of logs to print. Here is the definition:
150
274
  ```typescript
@@ -185,7 +309,7 @@ blockchain.verbosity = {
185
309
  ```
186
310
  Note that unlike with `setVerbosityForAddress`, with this setter you have to specify all the values from `LogsVerbosity`.
187
311
 
188
- ### Setting smart contract state directly
312
+ ## Setting smart contract state directly
189
313
 
190
314
  If you want to test some behavior on a contract if it had specific code, data, and other state fields, but do not want to execute all the required transactions for that, you can directly set the full state of the contract as it is stored in sandbox by using this method on the `Blockchain` instance:
191
315
  ```
@@ -195,7 +319,7 @@ There are 2 helpers exported from sandbox that can help you create the `ShardAcc
195
319
 
196
320
  Note that this is a low-level function and does not check any invariants, such as that the address passed as the argument matches the one that is present in the `ShardAccount`, meaning it is possible to break stuff if you're not careful when using it.
197
321
 
198
- ### Network/Block configuration
322
+ ## Network/Block configuration
199
323
 
200
324
  By default, this package will use its [stored network configuration](src/config/defaultConfig.ts) to emulate messages. However, you can set any configuration you want when creating the `Blockchain` instance by passing the configuration cell in the optional `params` argument in the `config` field.
201
325
 
@@ -1,4 +1,4 @@
1
- import { Address, Cell, Message, Transaction, ContractProvider, Contract, Sender, ShardAccount, TupleItem, ExternalAddress, StateInit } from "ton-core";
1
+ import { Address, Cell, Message, Transaction, ContractProvider, Contract, Sender, ShardAccount, TupleItem, ExternalAddress, StateInit } from "@ton/core";
2
2
  import { Executor, TickOrTock } from "../executor/Executor";
3
3
  import { BlockchainStorage } from "./BlockchainStorage";
4
4
  import { Event } from "../event/Event";
@@ -88,11 +88,15 @@ export declare class Blockchain {
88
88
  get config(): Cell;
89
89
  get configBase64(): string;
90
90
  sendMessage(message: Message | Cell, params?: MessageParams): Promise<SendMessageResult>;
91
+ sendMessageIter(message: Message | Cell, params?: MessageParams): Promise<AsyncIterator<BlockchainTransaction> & AsyncIterable<BlockchainTransaction>>;
91
92
  runTickTock(on: Address | Address[], which: TickOrTock, params?: MessageParams): Promise<SendMessageResult>;
92
93
  runGetMethod(address: Address, method: number | string, stack?: TupleItem[], params?: GetMethodParams): Promise<import("./SmartContract").GetMethodResult>;
93
94
  protected pushMessage(message: Message | Cell): Promise<void>;
94
95
  protected pushTickTock(on: Address, which: TickOrTock): Promise<void>;
95
96
  protected runQueue(params?: MessageParams): Promise<SendMessageResult>;
97
+ protected txIter(needsLocking: boolean, params?: MessageParams): AsyncIterator<BlockchainTransaction> & AsyncIterable<BlockchainTransaction>;
98
+ protected processInternal(params?: MessageParams): Promise<IteratorResult<BlockchainTransaction>>;
99
+ protected processTx(needsLocking: boolean, params?: MessageParams): Promise<IteratorResult<BlockchainTransaction>>;
96
100
  protected processQueue(params?: MessageParams): Promise<BlockchainTransaction[]>;
97
101
  provider(address: Address, init?: {
98
102
  code: Cell;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Blockchain = void 0;
4
4
  const defaultConfig_1 = require("../config/defaultConfig");
5
- const ton_core_1 = require("ton-core");
5
+ const core_1 = require("@ton/core");
6
6
  const Executor_1 = require("../executor/Executor");
7
7
  const BlockchainStorage_1 = require("./BlockchainStorage");
8
8
  const Event_1 = require("../event/Event");
@@ -80,7 +80,7 @@ class Blockchain {
80
80
  this.storage = opts.storage;
81
81
  }
82
82
  get config() {
83
- return ton_core_1.Cell.fromBase64(this.networkConfig);
83
+ return core_1.Cell.fromBase64(this.networkConfig);
84
84
  }
85
85
  get configBase64() {
86
86
  return this.networkConfig;
@@ -89,6 +89,15 @@ class Blockchain {
89
89
  await this.pushMessage(message);
90
90
  return await this.runQueue(params);
91
91
  }
92
+ async sendMessageIter(message, params) {
93
+ params = {
94
+ now: this.now,
95
+ ...params,
96
+ };
97
+ await this.pushMessage(message);
98
+ // Iterable will lock on per tx basis
99
+ return await this.txIter(true, params);
100
+ }
92
101
  async runTickTock(on, which, params) {
93
102
  for (const addr of (Array.isArray(on) ? on : [on])) {
94
103
  await this.pushTickTock(addr, which);
@@ -102,7 +111,7 @@ class Blockchain {
102
111
  });
103
112
  }
104
113
  async pushMessage(message) {
105
- const msg = message instanceof ton_core_1.Cell ? (0, ton_core_1.loadMessage)(message.beginParse()) : message;
114
+ const msg = message instanceof core_1.Cell ? (0, core_1.loadMessage)(message.beginParse()) : message;
106
115
  if (msg.info.type === 'external-out') {
107
116
  throw new Error('Cannot send external out message');
108
117
  }
@@ -130,60 +139,80 @@ class Blockchain {
130
139
  externals: txes.map(tx => tx.externals).flat(),
131
140
  };
132
141
  }
142
+ txIter(needsLocking, params) {
143
+ const it = { next: () => this.processTx(needsLocking, params), [Symbol.asyncIterator]() { return it; } };
144
+ return it;
145
+ }
146
+ async processInternal(params) {
147
+ let result = undefined;
148
+ let done = this.messageQueue.length == 0;
149
+ while (!done) {
150
+ const message = this.messageQueue.shift();
151
+ let tx;
152
+ if (message.type === 'message') {
153
+ if (message.info.type === 'external-out') {
154
+ done = this.messageQueue.length == 0;
155
+ continue;
156
+ }
157
+ this.currentLt += LT_ALIGN;
158
+ tx = (await this.getContract(message.info.dest)).receiveMessage(message, params);
159
+ }
160
+ else {
161
+ this.currentLt += LT_ALIGN;
162
+ tx = (await this.getContract(message.on)).runTickTock(message.which, params);
163
+ }
164
+ const transaction = {
165
+ ...tx,
166
+ events: (0, Event_1.extractEvents)(tx),
167
+ parent: message.parentTransaction,
168
+ children: [],
169
+ externals: [],
170
+ };
171
+ transaction.parent?.children.push(transaction);
172
+ result = transaction;
173
+ done = true;
174
+ for (const message of transaction.outMessages.values()) {
175
+ if (message.info.type === 'external-out') {
176
+ transaction.externals.push({
177
+ info: {
178
+ type: 'external-out',
179
+ src: message.info.src,
180
+ dest: message.info.dest ?? undefined,
181
+ createdAt: message.info.createdAt,
182
+ createdLt: message.info.createdLt,
183
+ },
184
+ init: message.init ?? undefined,
185
+ body: message.body,
186
+ });
187
+ continue;
188
+ }
189
+ this.messageQueue.push({
190
+ type: 'message',
191
+ parentTransaction: transaction,
192
+ ...message,
193
+ });
194
+ if (message.info.type === 'internal') {
195
+ this.startFetchingContract(message.info.dest);
196
+ }
197
+ }
198
+ }
199
+ return result === undefined ? { value: result, done: true } : { value: result, done: false };
200
+ }
201
+ async processTx(needsLocking, params) {
202
+ // Lock only if not locked already
203
+ return needsLocking ? await this.lock.with(async () => this.processInternal(params)) : await this.processInternal(params);
204
+ }
133
205
  async processQueue(params) {
134
206
  params = {
135
207
  now: this.now,
136
208
  ...params,
137
209
  };
138
210
  return await this.lock.with(async () => {
211
+ // Locked already
212
+ const txs = this.txIter(false, params);
139
213
  const result = [];
140
- while (this.messageQueue.length > 0) {
141
- const message = this.messageQueue.shift();
142
- let tx;
143
- if (message.type === 'message') {
144
- if (message.info.type === 'external-out') {
145
- continue;
146
- }
147
- this.currentLt += LT_ALIGN;
148
- tx = (await this.getContract(message.info.dest)).receiveMessage(message, params);
149
- }
150
- else {
151
- this.currentLt += LT_ALIGN;
152
- tx = (await this.getContract(message.on)).runTickTock(message.which, params);
153
- }
154
- const transaction = {
155
- ...tx,
156
- events: (0, Event_1.extractEvents)(tx),
157
- parent: message.parentTransaction,
158
- children: [],
159
- externals: [],
160
- };
161
- transaction.parent?.children.push(transaction);
162
- result.push(transaction);
163
- for (const message of transaction.outMessages.values()) {
164
- if (message.info.type === 'external-out') {
165
- transaction.externals.push({
166
- info: {
167
- type: 'external-out',
168
- src: message.info.src,
169
- dest: message.info.dest ?? undefined,
170
- createdAt: message.info.createdAt,
171
- createdLt: message.info.createdLt,
172
- },
173
- init: message.init ?? undefined,
174
- body: message.body,
175
- });
176
- continue;
177
- }
178
- this.messageQueue.push({
179
- type: 'message',
180
- parentTransaction: transaction,
181
- ...message,
182
- });
183
- if (message.info.type === 'internal') {
184
- this.startFetchingContract(message.info.dest);
185
- }
186
- }
214
+ for await (const tx of txs) {
215
+ result.push(tx);
187
216
  }
188
217
  return result;
189
218
  });
@@ -210,15 +239,15 @@ class Blockchain {
210
239
  const contract = await this.getContract(wallet.address);
211
240
  if ((params?.predeploy ?? true) && (contract.accountState === undefined || contract.accountState.type === 'uninit')) {
212
241
  await this.sendMessage((0, message_1.internal)({
213
- from: new ton_core_1.Address(0, Buffer.alloc(32)),
242
+ from: new core_1.Address(0, Buffer.alloc(32)),
214
243
  to: wallet.address,
215
- value: (0, ton_core_1.toNano)(1),
244
+ value: (0, core_1.toNano)(1),
216
245
  stateInit: wallet.init,
217
246
  }));
218
- contract.balance = params?.balance ?? (0, ton_core_1.toNano)(TREASURY_INIT_BALANCE_TONS);
247
+ contract.balance = params?.balance ?? (0, core_1.toNano)(TREASURY_INIT_BALANCE_TONS);
219
248
  }
220
249
  else if ((params?.resetBalanceIfZero ?? true) && contract.balance === 0n) {
221
- contract.balance = params?.balance ?? (0, ton_core_1.toNano)(TREASURY_INIT_BALANCE_TONS);
250
+ contract.balance = params?.balance ?? (0, core_1.toNano)(TREASURY_INIT_BALANCE_TONS);
222
251
  }
223
252
  return wallet;
224
253
  }
@@ -233,15 +262,15 @@ class Blockchain {
233
262
  openContract(contract) {
234
263
  let address;
235
264
  let init = undefined;
236
- if (!ton_core_1.Address.isAddress(contract.address)) {
265
+ if (!core_1.Address.isAddress(contract.address)) {
237
266
  throw Error('Invalid address');
238
267
  }
239
268
  address = contract.address;
240
269
  if (contract.init) {
241
- if (!(contract.init.code instanceof ton_core_1.Cell)) {
270
+ if (!(contract.init.code instanceof core_1.Cell)) {
242
271
  throw Error('Invalid init.code');
243
272
  }
244
- if (!(contract.init.data instanceof ton_core_1.Cell)) {
273
+ if (!(contract.init.data instanceof core_1.Cell)) {
245
274
  throw Error('Invalid init.data');
246
275
  }
247
276
  init = contract.init;
@@ -1,4 +1,4 @@
1
- import { Address, Cell, ContractGetMethodResult, ContractProvider, ContractState, Message, Sender, SendMode, TupleItem } from "ton-core";
1
+ import { Address, Cell, ContractGetMethodResult, ContractProvider, ContractState, Message, Sender, SendMode, TupleItem } from "@ton/core";
2
2
  import { TickOrTock } from "../executor/Executor";
3
3
  import { GetMethodResult, SmartContract } from "./SmartContract";
4
4
  export interface SandboxContractProvider extends ContractProvider {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BlockchainContractProvider = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  function bigintToBuffer(x, n = 32) {
6
6
  const b = Buffer.alloc(n);
7
7
  for (let i = 0; i < n; i++) {
@@ -72,8 +72,8 @@ class BlockchainContractProvider {
72
72
  async internal(via, args) {
73
73
  const init = ((await this.getState()).state.type !== 'active' && this.init) ? this.init : undefined;
74
74
  const bounce = (args.bounce !== null && args.bounce !== undefined) ? args.bounce : true;
75
- const value = typeof args.value === 'string' ? (0, ton_core_1.toNano)(args.value) : args.value;
76
- const body = typeof args.body === 'string' ? (0, ton_core_1.comment)(args.body) : args.body;
75
+ const value = typeof args.value === 'string' ? (0, core_1.toNano)(args.value) : args.value;
76
+ const body = typeof args.body === 'string' ? (0, core_1.comment)(args.body) : args.body;
77
77
  await via.send({
78
78
  to: this.address,
79
79
  value,
@@ -1,4 +1,4 @@
1
- import { Address, Message, Sender, SenderArguments } from "ton-core";
1
+ import { Address, Message, Sender, SenderArguments } from "@ton/core";
2
2
  export declare class BlockchainSender implements Sender {
3
3
  private readonly blockchain;
4
4
  readonly address: Address;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BlockchainSender = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  class BlockchainSender {
6
6
  constructor(blockchain, address) {
7
7
  this.blockchain = blockchain;
@@ -22,7 +22,7 @@ class BlockchainSender {
22
22
  createdAt: 0,
23
23
  createdLt: 0n,
24
24
  },
25
- body: args.body ?? new ton_core_1.Cell()
25
+ body: args.body ?? new core_1.Cell()
26
26
  });
27
27
  }
28
28
  }
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { AccountState, Address } from "ton-core";
2
+ import { AccountState, Address } from "@ton/core";
3
3
  import { SmartContract } from "./SmartContract";
4
4
  import { Blockchain } from "./Blockchain";
5
5
  export interface BlockchainStorage {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RemoteBlockchainStorage = exports.wrapTonClient4ForRemote = exports.LocalBlockchainStorage = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  const SmartContract_1 = require("./SmartContract");
6
6
  class LocalBlockchainStorage {
7
7
  constructor() {
@@ -28,7 +28,7 @@ function convertTonClient4State(state) {
28
28
  case 'uninit':
29
29
  return { type: 'uninit' };
30
30
  case 'active':
31
- return { type: 'active', state: { code: state.code === null ? undefined : ton_core_1.Cell.fromBase64(state.code), data: state.data === null ? undefined : ton_core_1.Cell.fromBase64(state.data) } };
31
+ return { type: 'active', state: { code: state.code === null ? undefined : core_1.Cell.fromBase64(state.code), data: state.data === null ? undefined : core_1.Cell.fromBase64(state.data) } };
32
32
  case 'frozen':
33
33
  return { type: 'frozen', stateHash: BigInt('0x' + Buffer.from(state.stateHash, 'base64').toString('hex')) };
34
34
  default:
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { Blockchain } from "./Blockchain";
3
- import { Address, Cell, Message, ShardAccount, Transaction, TupleItem, TupleReader } from "ton-core";
3
+ import { Address, Cell, Message, ShardAccount, Transaction, TupleItem, TupleReader } from "@ton/core";
4
4
  import { EmulationResult, RunCommonArgs, TickOrTock } from "../executor/Executor";
5
5
  export declare function createShardAccount(args: {
6
6
  address?: Address;
@@ -72,7 +72,7 @@ export declare class SmartContract {
72
72
  set balance(v: bigint);
73
73
  get lastTransactionHash(): bigint;
74
74
  get lastTransactionLt(): bigint;
75
- get accountState(): import("ton-core").AccountState | undefined;
75
+ get accountState(): import("@ton/core").AccountState | undefined;
76
76
  get account(): ShardAccount;
77
77
  set account(account: ShardAccount);
78
78
  static create(blockchain: Blockchain, args: {
@@ -13,11 +13,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var _SmartContract_account, _SmartContract_parsedAccount, _SmartContract_lastTxTime, _SmartContract_verbosity;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.SmartContract = exports.TimeError = exports.GetMethodError = exports.createEmptyShardAccount = exports.createShardAccount = void 0;
16
- const ton_core_1 = require("ton-core");
16
+ const core_1 = require("@ton/core");
17
17
  const selector_1 = require("../utils/selector");
18
18
  function createShardAccount(args) {
19
19
  let wc = args.workchain ?? 0;
20
- let address = args.address ?? (0, ton_core_1.contractAddress)(wc, { code: args.code, data: args.data });
20
+ let address = args.address ?? (0, core_1.contractAddress)(wc, { code: args.code, data: args.data });
21
21
  let balance = args.balance ?? 0n;
22
22
  return {
23
23
  account: {
@@ -105,7 +105,7 @@ class SmartContract {
105
105
  _SmartContract_lastTxTime.set(this, void 0);
106
106
  _SmartContract_verbosity.set(this, void 0);
107
107
  this.address = shardAccount.account.addr;
108
- __classPrivateFieldSet(this, _SmartContract_account, (0, ton_core_1.beginCell)().store((0, ton_core_1.storeShardAccount)(shardAccount)).endCell().toBoc().toString('base64'), "f");
108
+ __classPrivateFieldSet(this, _SmartContract_account, (0, core_1.beginCell)().store((0, core_1.storeShardAccount)(shardAccount)).endCell().toBoc().toString('base64'), "f");
109
109
  __classPrivateFieldSet(this, _SmartContract_parsedAccount, shardAccount, "f");
110
110
  __classPrivateFieldSet(this, _SmartContract_lastTxTime, shardAccount.account?.storageStats.lastPaid ?? 0, "f");
111
111
  this.blockchain = blockchain;
@@ -148,12 +148,12 @@ class SmartContract {
148
148
  }
149
149
  get account() {
150
150
  if (__classPrivateFieldGet(this, _SmartContract_parsedAccount, "f") === undefined) {
151
- __classPrivateFieldSet(this, _SmartContract_parsedAccount, (0, ton_core_1.loadShardAccount)(ton_core_1.Cell.fromBase64(__classPrivateFieldGet(this, _SmartContract_account, "f")).beginParse()), "f");
151
+ __classPrivateFieldSet(this, _SmartContract_parsedAccount, (0, core_1.loadShardAccount)(core_1.Cell.fromBase64(__classPrivateFieldGet(this, _SmartContract_account, "f")).beginParse()), "f");
152
152
  }
153
153
  return __classPrivateFieldGet(this, _SmartContract_parsedAccount, "f");
154
154
  }
155
155
  set account(account) {
156
- __classPrivateFieldSet(this, _SmartContract_account, (0, ton_core_1.beginCell)().store((0, ton_core_1.storeShardAccount)(account)).endCell().toBoc().toString('base64'), "f");
156
+ __classPrivateFieldSet(this, _SmartContract_account, (0, core_1.beginCell)().store((0, core_1.storeShardAccount)(account)).endCell().toBoc().toString('base64'), "f");
157
157
  __classPrivateFieldSet(this, _SmartContract_parsedAccount, account, "f");
158
158
  __classPrivateFieldSet(this, _SmartContract_lastTxTime, account.account?.storageStats.lastPaid ?? 0, "f");
159
159
  }
@@ -181,9 +181,14 @@ class SmartContract {
181
181
  };
182
182
  }
183
183
  receiveMessage(message, params) {
184
+ // Sync now with blockchain instance if not specified in parameters
185
+ params = {
186
+ now: this.blockchain.now,
187
+ ...params,
188
+ };
184
189
  return this.runCommon(() => this.blockchain.executor.runTransaction({
185
190
  ...this.createCommonArgs(params),
186
- message: (0, ton_core_1.beginCell)().store((0, ton_core_1.storeMessage)(message)).endCell(),
191
+ message: (0, core_1.beginCell)().store((0, core_1.storeMessage)(message)).endCell(),
187
192
  }));
188
193
  }
189
194
  runTickTock(which, params) {
@@ -207,7 +212,7 @@ class SmartContract {
207
212
  if (this.verbosity.print && this.verbosity.debugLogs && res.debugLogs.length > 0) {
208
213
  console.log(res.debugLogs);
209
214
  }
210
- const tx = (0, ton_core_1.loadTransaction)(ton_core_1.Cell.fromBase64(res.result.transaction).beginParse());
215
+ const tx = (0, core_1.loadTransaction)(core_1.Cell.fromBase64(res.result.transaction).beginParse());
211
216
  __classPrivateFieldSet(this, _SmartContract_account, res.result.shardAccount, "f");
212
217
  __classPrivateFieldSet(this, _SmartContract_parsedAccount, undefined, "f");
213
218
  __classPrivateFieldSet(this, _SmartContract_lastTxTime, tx.now, "f");
@@ -249,13 +254,13 @@ class SmartContract {
249
254
  if (this.verbosity.print && this.verbosity.debugLogs && res.debugLogs.length > 0) {
250
255
  console.log(res.debugLogs);
251
256
  }
252
- if (res.output.vm_exit_code !== 0) {
257
+ if (res.output.vm_exit_code !== 0 && res.output.vm_exit_code !== 1) {
253
258
  throw new GetMethodError(res.output.vm_exit_code, BigInt(res.output.gas_used), res.logs, res.output.vm_log, res.debugLogs);
254
259
  }
255
- const resStack = (0, ton_core_1.parseTuple)(ton_core_1.Cell.fromBase64(res.output.stack));
260
+ const resStack = (0, core_1.parseTuple)(core_1.Cell.fromBase64(res.output.stack));
256
261
  return {
257
262
  stack: resStack,
258
- stackReader: new ton_core_1.TupleReader(resStack),
263
+ stackReader: new core_1.TupleReader(resStack),
259
264
  exitCode: res.output.vm_exit_code,
260
265
  gasUsed: BigInt(res.output.gas_used),
261
266
  blockchainLogs: res.logs,
@@ -1,4 +1,4 @@
1
- import { Address, Cell, Transaction } from "ton-core";
1
+ import { Address, Cell, Transaction } from "@ton/core";
2
2
  export type EventAccountCreated = {
3
3
  type: 'account_created';
4
4
  account: Address;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { Address, Cell, TupleItem } from "ton-core";
2
+ import { Address, Cell, TupleItem } from "@ton/core";
3
3
  export type GetMethodArgs = {
4
4
  code: Cell;
5
5
  data: Cell;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Executor = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  const base64_1 = require("../utils/base64");
6
6
  const EmulatorModule = require('./emulator-emscripten.js');
7
7
  const verbosityToNum = {
@@ -93,7 +93,7 @@ class Executor {
93
93
  method_id: args.methodId,
94
94
  debug_enabled: args.debugEnabled,
95
95
  };
96
- let stack = (0, ton_core_1.serializeTuple)(args.stack);
96
+ let stack = (0, core_1.serializeTuple)(args.stack);
97
97
  this.debugLogs = [];
98
98
  const resp = JSON.parse(this.extractString(this.invoke('_run_get_method', [
99
99
  JSON.stringify(params),
@@ -1,4 +1,4 @@
1
- import { Address, Cell, Contract, ContractProvider, MessageRelaxed, Sender, SenderArguments, SendMode } from "ton-core";
1
+ import { Address, Cell, Contract, ContractProvider, MessageRelaxed, Sender, SenderArguments, SendMode } from "@ton/core";
2
2
  export type Treasury = Sender & {
3
3
  address: Address;
4
4
  };
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TreasuryContract = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  const DictionaryMessageValue = {
6
6
  serialize(src, builder) {
7
7
  builder.storeUint(src.sendMode, 8);
8
- builder.storeRef((0, ton_core_1.beginCell)().store((0, ton_core_1.storeMessageRelaxed)(src.message)));
8
+ builder.storeRef((0, core_1.beginCell)().store((0, core_1.storeMessageRelaxed)(src.message)));
9
9
  },
10
10
  parse(src) {
11
11
  let sendMode = src.loadUint(8);
12
- let message = (0, ton_core_1.loadMessageRelaxed)(src.loadRef().beginParse());
12
+ let message = (0, core_1.loadMessageRelaxed)(src.loadRef().beginParse());
13
13
  return { sendMode, message };
14
14
  },
15
15
  };
16
16
  function senderArgsToMessageRelaxed(args) {
17
- return (0, ton_core_1.internal)({
17
+ return (0, core_1.internal)({
18
18
  to: args.to,
19
19
  value: args.value,
20
20
  init: args.init,
@@ -27,11 +27,11 @@ class TreasuryContract {
27
27
  return new TreasuryContract(workchain, subwalletId);
28
28
  }
29
29
  constructor(workchain, subwalletId) {
30
- const data = (0, ton_core_1.beginCell)()
30
+ const data = (0, core_1.beginCell)()
31
31
  .storeUint(subwalletId, 256)
32
32
  .endCell();
33
33
  this.init = { code: TreasuryContract.code, data };
34
- this.address = (0, ton_core_1.contractAddress)(workchain, this.init);
34
+ this.address = (0, core_1.contractAddress)(workchain, this.init);
35
35
  this.subwalletId = subwalletId;
36
36
  }
37
37
  async sendMessages(provider, messages, sendMode) {
@@ -60,23 +60,23 @@ class TreasuryContract {
60
60
  return (await provider.getState()).balance;
61
61
  }
62
62
  createTransfer(args) {
63
- let sendMode = ton_core_1.SendMode.PAY_GAS_SEPARATELY;
63
+ let sendMode = core_1.SendMode.PAY_GAS_SEPARATELY;
64
64
  if (args.sendMode !== null && args.sendMode !== undefined) {
65
65
  sendMode = args.sendMode;
66
66
  }
67
67
  if (args.messages.length > 255) {
68
68
  throw new Error('Maximum number of messages is 255');
69
69
  }
70
- let messages = ton_core_1.Dictionary.empty(ton_core_1.Dictionary.Keys.Int(16), DictionaryMessageValue);
70
+ let messages = core_1.Dictionary.empty(core_1.Dictionary.Keys.Int(16), DictionaryMessageValue);
71
71
  let index = 0;
72
72
  for (let m of args.messages) {
73
73
  messages.set(index++, { sendMode, message: m });
74
74
  }
75
- return (0, ton_core_1.beginCell)()
75
+ return (0, core_1.beginCell)()
76
76
  .storeUint(this.subwalletId, 256)
77
77
  .storeDict(messages)
78
78
  .endCell();
79
79
  }
80
80
  }
81
81
  exports.TreasuryContract = TreasuryContract;
82
- TreasuryContract.code = ton_core_1.Cell.fromBase64('te6cckEBBAEARQABFP8A9KQT9LzyyAsBAgEgAwIAWvLT/+1E0NP/0RK68qL0BNH4AH+OFiGAEPR4b6UgmALTB9QwAfsAkTLiAbPmWwAE0jD+omUe');
82
+ TreasuryContract.code = core_1.Cell.fromBase64('te6cckEBBAEARQABFP8A9KQT9LzyyAsBAgEgAwIAWvLT/+1E0NP/0RK68qL0BNH4AH+OFiGAEPR4b6UgmALTB9QwAfsAkTLiAbPmWwAE0jD+omUe');
@@ -1,4 +1,4 @@
1
- import { Address, Cell, Message, StateInit } from "ton-core";
1
+ import { Address, Cell, Message, StateInit } from "@ton/core";
2
2
  export declare function internal(params: {
3
3
  from: Address;
4
4
  to: Address;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.internal = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  function internal(params) {
6
6
  return {
7
7
  info: {
@@ -17,7 +17,7 @@ function internal(params) {
17
17
  createdAt: params.createdAt ?? 0,
18
18
  createdLt: params.createdLt ?? 0n,
19
19
  },
20
- body: params.body ?? new ton_core_1.Cell(),
20
+ body: params.body ?? new core_1.Cell(),
21
21
  init: params.stateInit,
22
22
  };
23
23
  }
@@ -1,3 +1,3 @@
1
- import { Transaction } from "ton-core";
1
+ import { Transaction } from "@ton/core";
2
2
  export declare function prettyLogTransaction(tx: Transaction): string;
3
3
  export declare function prettyLogTransactions(txs: Transaction[]): void;
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prettyLogTransactions = exports.prettyLogTransaction = void 0;
4
- const ton_core_1 = require("ton-core");
4
+ const core_1 = require("@ton/core");
5
5
  function prettyLogTransaction(tx) {
6
6
  let res = `${tx.inMessage?.info.src} ➡️ ${tx.inMessage?.info.dest}\n`;
7
7
  for (let message of tx.outMessages.values()) {
8
8
  if (message.info.type === 'internal') {
9
- res += ` ➡️ ${(0, ton_core_1.fromNano)(message.info.value.coins)} 💎 ${message.info.dest}\n`;
9
+ res += ` ➡️ ${(0, core_1.fromNano)(message.info.value.coins)} 💎 ${message.info.dest}\n`;
10
10
  }
11
11
  else {
12
12
  res += ` ➡️ ${message.info.dest}\n`;
@@ -1,3 +1,3 @@
1
- import { Transaction } from "ton-core";
1
+ import { Transaction } from "@ton/core";
2
2
  export declare function formatCoinsPure(value: bigint, precision?: number): string;
3
3
  export declare function printTransactionFees(transactions: Transaction[]): void;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.testSubwalletId = void 0;
4
- const ton_crypto_1 = require("ton-crypto");
4
+ const crypto_1 = require("@ton/crypto");
5
5
  const prefix = 'TESTSEED';
6
6
  function testSubwalletId(seed) {
7
- return BigInt('0x' + (0, ton_crypto_1.sha256_sync)(prefix + seed).toString('hex'));
7
+ return BigInt('0x' + (0, crypto_1.sha256_sync)(prefix + seed).toString('hex'));
8
8
  }
9
9
  exports.testSubwalletId = testSubwalletId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton/sandbox",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "TON transaction emulator",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
@@ -13,20 +13,20 @@
13
13
  "url": "git+https://github.com/ton-org/sandbox"
14
14
  },
15
15
  "devDependencies": {
16
- "@ton-community/test-utils": "^0.2.0",
16
+ "@ton/core": "^0.49.2",
17
+ "@ton/crypto": "3.2.0",
18
+ "@ton/test-utils": "^0.3.1",
19
+ "@ton/ton": "^13.4.2",
17
20
  "@types/jest": "^29.5.0",
18
21
  "@types/node": "^18.15.11",
19
22
  "jest": "^29.5.0",
20
- "ton": "^13.4.1",
21
- "ton-core": "^0.49.0",
22
- "ton-crypto": "3.2.0",
23
23
  "ts-jest": "^29.0.5",
24
24
  "ts-node": "^10.9.1",
25
25
  "typescript": "^4.9.5"
26
26
  },
27
27
  "peerDependencies": {
28
- "ton-core": ">=0.48.0",
29
- "ton-crypto": ">=3.2.0"
28
+ "@ton/core": ">=0.49.2",
29
+ "@ton/crypto": ">=3.2.0"
30
30
  },
31
31
  "scripts": {
32
32
  "wasm:pack": "ts-node ./scripts/pack-wasm.ts",
@@ -34,5 +34,6 @@
34
34
  "test": "yarn wasm:pack && yarn jest src",
35
35
  "build": "rm -rf dist && yarn wasm:pack && yarn test && tsc && yarn wasm:copy",
36
36
  "config:pack": "ts-node ./scripts/pack-config.ts"
37
- }
38
- }
37
+ },
38
+ "packageManager": "yarn@3.6.1"
39
+ }