@ton/sandbox 0.38.0-dev.20251014114055.f2fd72c → 0.38.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,16 @@ 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.38.0] - 2025-10-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added support for TVM v12
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Failed actions are now partially handled when parsing send modes for transactions
|
|
17
|
+
|
|
8
18
|
## [0.37.2] - 2025-09-23
|
|
9
19
|
|
|
10
20
|
### Fixed
|
|
@@ -4,6 +4,7 @@ exports.blockIdToSerializable = blockIdToSerializable;
|
|
|
4
4
|
exports.snapshotToSerializable = snapshotToSerializable;
|
|
5
5
|
exports.snapshotFromSerializable = snapshotFromSerializable;
|
|
6
6
|
const core_1 = require("@ton/core");
|
|
7
|
+
const SmartContract_1 = require("./SmartContract");
|
|
7
8
|
const Event_1 = require("../event/Event");
|
|
8
9
|
function blockIdToSerializable(blockId) {
|
|
9
10
|
return {
|
|
@@ -48,7 +49,9 @@ function snapshotToSerializable(snapshot) {
|
|
|
48
49
|
debugLogs: transaction.debugLogs,
|
|
49
50
|
oldStorage: transaction.oldStorage?.toBoc().toString('base64'),
|
|
50
51
|
newStorage: transaction.newStorage?.toBoc().toString('base64'),
|
|
51
|
-
outActions: transaction.outActions
|
|
52
|
+
outActions: transaction.outActions
|
|
53
|
+
? writableToBase64((0, SmartContract_1.storeOutListExt)(transaction.outActions))
|
|
54
|
+
: undefined,
|
|
52
55
|
externals: transaction.externals.map((external) => writableToBase64((0, core_1.storeMessageRelaxed)(external))),
|
|
53
56
|
mode: transaction.mode,
|
|
54
57
|
parentHash: transaction.parent?.hash().toString('hex'),
|
|
@@ -76,7 +79,7 @@ function snapshotFromSerializable(serialized) {
|
|
|
76
79
|
debugLogs: t.debugLogs,
|
|
77
80
|
oldStorage: t.oldStorage ? core_1.Cell.fromBase64(t.oldStorage) : undefined,
|
|
78
81
|
newStorage: t.newStorage ? core_1.Cell.fromBase64(t.newStorage) : undefined,
|
|
79
|
-
outActions: t.outActions ? (0,
|
|
82
|
+
outActions: t.outActions ? (0, SmartContract_1.loadOutListExt)(core_1.Cell.fromBase64(t.outActions).beginParse()) : undefined,
|
|
80
83
|
events: (0, Event_1.extractEvents)(transaction),
|
|
81
84
|
mode: t.mode,
|
|
82
85
|
externals: t.externals.map((ext) => (0, core_1.loadMessageRelaxed)(core_1.Cell.fromBase64(ext).beginParse())),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Address, Cell, Message, OutAction, ShardAccount, Transaction, TupleItem, TupleReader } from '@ton/core';
|
|
1
|
+
import { Address, Builder, Cell, Message, OutAction, ShardAccount, Slice, Transaction, TupleItem, TupleReader } from '@ton/core';
|
|
2
2
|
import { Blockchain } from './Blockchain';
|
|
3
3
|
import { ExtraCurrency } from '../utils/ec';
|
|
4
4
|
import { EmulationResult, RunCommonArgs, TickOrTock } from '../executor/Executor';
|
|
@@ -10,6 +10,15 @@ export declare function createShardAccount(args: {
|
|
|
10
10
|
workchain?: number;
|
|
11
11
|
}): ShardAccount;
|
|
12
12
|
export declare function createEmptyShardAccount(address: Address): ShardAccount;
|
|
13
|
+
type ExtendedActionType = OutAction['type'] | 'unknown';
|
|
14
|
+
export type OutActionMalformed = {
|
|
15
|
+
type: 'malformed';
|
|
16
|
+
subtype: ExtendedActionType;
|
|
17
|
+
data: Cell;
|
|
18
|
+
};
|
|
19
|
+
export type OutActionExtended = OutAction | OutActionMalformed;
|
|
20
|
+
export declare function storeOutListExt(actions: OutActionExtended[]): (builder: Builder) => void;
|
|
21
|
+
export declare function loadOutListExt(data: Slice): OutActionExtended[];
|
|
13
22
|
export type Verbosity = 'none' | 'vm_logs' | 'vm_logs_location' | 'vm_logs_gas' | 'vm_logs_full' | 'vm_logs_verbose';
|
|
14
23
|
export type LogsVerbosity = {
|
|
15
24
|
print: boolean;
|
|
@@ -23,7 +32,7 @@ export type SmartContractTransaction = Transaction & {
|
|
|
23
32
|
debugLogs: string;
|
|
24
33
|
oldStorage?: Cell;
|
|
25
34
|
newStorage?: Cell;
|
|
26
|
-
outActions?:
|
|
35
|
+
outActions?: OutActionExtended[];
|
|
27
36
|
};
|
|
28
37
|
export type MessageParams = Partial<{
|
|
29
38
|
now: number;
|
|
@@ -73,9 +82,13 @@ export type SmartContractSnapshot = {
|
|
|
73
82
|
verbosity?: Partial<LogsVerbosity>;
|
|
74
83
|
};
|
|
75
84
|
export declare class SmartContract {
|
|
76
|
-
#private;
|
|
77
85
|
readonly address: Address;
|
|
78
86
|
readonly blockchain: Blockchain;
|
|
87
|
+
private _account;
|
|
88
|
+
private parsedAccount?;
|
|
89
|
+
private lastTxTime;
|
|
90
|
+
private _verbosity?;
|
|
91
|
+
private _debug?;
|
|
79
92
|
constructor(shardAccount: ShardAccount, blockchain: Blockchain);
|
|
80
93
|
snapshot(): SmartContractSnapshot;
|
|
81
94
|
loadFrom(snapshot: SmartContractSnapshot): void;
|
|
@@ -106,3 +119,4 @@ export declare class SmartContract {
|
|
|
106
119
|
get debug(): boolean;
|
|
107
120
|
setDebug(debug: boolean | undefined): void;
|
|
108
121
|
}
|
|
122
|
+
export {};
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SmartContract = exports.EmulationError = exports.TimeError = exports.GetMethodError = void 0;
|
|
4
4
|
exports.createShardAccount = createShardAccount;
|
|
5
5
|
exports.createEmptyShardAccount = createEmptyShardAccount;
|
|
6
|
+
exports.storeOutListExt = storeOutListExt;
|
|
7
|
+
exports.loadOutListExt = loadOutListExt;
|
|
6
8
|
const core_1 = require("@ton/core");
|
|
7
9
|
const ec_1 = require("../utils/ec");
|
|
8
10
|
const selector_1 = require("../utils/selector");
|
|
@@ -62,6 +64,73 @@ function createEmptyShardAccount(address) {
|
|
|
62
64
|
lastTransactionHash: 0n,
|
|
63
65
|
};
|
|
64
66
|
}
|
|
67
|
+
function preloadActionType(data) {
|
|
68
|
+
if (data.remainingBits < 32) {
|
|
69
|
+
return 'unknown';
|
|
70
|
+
}
|
|
71
|
+
const tag = data.preloadUint(32);
|
|
72
|
+
/*
|
|
73
|
+
* action_send_msg#0ec3c86d mode:(## 8)
|
|
74
|
+
* out_msg:^(MessageRelaxed Any) = OutAction;
|
|
75
|
+
*
|
|
76
|
+
* action_set_code#ad4de08e new_code:^Cell = OutAction;
|
|
77
|
+
*
|
|
78
|
+
* action_reserve_currency#36e6b809 mode:(## 8)
|
|
79
|
+
* currency:CurrencyCollection = OutAction;
|
|
80
|
+
*
|
|
81
|
+
* action_change_library#26fa1dd4 mode:(## 7)
|
|
82
|
+
* libref:LibRef = OutAction;
|
|
83
|
+
*
|
|
84
|
+
*/
|
|
85
|
+
switch (tag) {
|
|
86
|
+
case 0x0ec3c86d:
|
|
87
|
+
return 'sendMsg';
|
|
88
|
+
case 0xad4de08e:
|
|
89
|
+
return 'setCode';
|
|
90
|
+
case 0x36e6b809:
|
|
91
|
+
return 'reserve';
|
|
92
|
+
case 0x26fa1dd4:
|
|
93
|
+
return 'changeLibrary';
|
|
94
|
+
default:
|
|
95
|
+
return 'unknown';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function storeActionExt(action) {
|
|
99
|
+
if (action.type === 'malformed') {
|
|
100
|
+
return (builder) => {
|
|
101
|
+
builder.storeSlice(action.data.beginParse());
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return (0, core_1.storeOutAction)(action);
|
|
105
|
+
}
|
|
106
|
+
function storeOutListExt(actions) {
|
|
107
|
+
const cell = actions.reduce((cell, action) => (0, core_1.beginCell)().storeRef(cell).store(storeActionExt(action)).endCell(), (0, core_1.beginCell)().endCell());
|
|
108
|
+
return (builder) => {
|
|
109
|
+
builder.storeSlice(cell.beginParse());
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// loadOutList from @ton/core, but with exception handling
|
|
113
|
+
function loadOutListExt(data) {
|
|
114
|
+
const actions = [];
|
|
115
|
+
while (data.remainingRefs) {
|
|
116
|
+
const nextCell = data.loadRef();
|
|
117
|
+
const dataOrig = data.clone();
|
|
118
|
+
const dataCell = data.asCell();
|
|
119
|
+
try {
|
|
120
|
+
actions.push((0, core_1.loadOutAction)(data));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
const actionType = preloadActionType(dataOrig);
|
|
124
|
+
actions.push({
|
|
125
|
+
type: 'malformed',
|
|
126
|
+
subtype: actionType,
|
|
127
|
+
data: dataCell,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
data = nextCell.beginParse();
|
|
131
|
+
}
|
|
132
|
+
return actions.reverse();
|
|
133
|
+
}
|
|
65
134
|
const verbosityToExecutorVerbosity = {
|
|
66
135
|
none: 'short',
|
|
67
136
|
vm_logs: 'full',
|
|
@@ -130,24 +199,24 @@ exports.EmulationError = EmulationError;
|
|
|
130
199
|
class SmartContract {
|
|
131
200
|
address;
|
|
132
201
|
blockchain;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
202
|
+
_account;
|
|
203
|
+
parsedAccount;
|
|
204
|
+
lastTxTime;
|
|
205
|
+
_verbosity;
|
|
206
|
+
_debug;
|
|
138
207
|
constructor(shardAccount, blockchain) {
|
|
139
208
|
this.address = shardAccount.account.addr;
|
|
140
|
-
this
|
|
141
|
-
this
|
|
142
|
-
this
|
|
209
|
+
this._account = (0, core_1.beginCell)().store((0, core_1.storeShardAccount)(shardAccount)).endCell().toBoc().toString('base64');
|
|
210
|
+
this.parsedAccount = shardAccount;
|
|
211
|
+
this.lastTxTime = shardAccount.account?.storageStats.lastPaid ?? 0;
|
|
143
212
|
this.blockchain = blockchain;
|
|
144
213
|
}
|
|
145
214
|
snapshot() {
|
|
146
215
|
return (0, deepcopy_1.deepcopy)({
|
|
147
216
|
address: this.address,
|
|
148
217
|
account: this.account,
|
|
149
|
-
lastTxTime: this
|
|
150
|
-
verbosity: this
|
|
218
|
+
lastTxTime: this.lastTxTime,
|
|
219
|
+
verbosity: this._verbosity,
|
|
151
220
|
});
|
|
152
221
|
}
|
|
153
222
|
loadFrom(snapshot) {
|
|
@@ -155,8 +224,8 @@ class SmartContract {
|
|
|
155
224
|
throw new Error('Wrong snapshot address');
|
|
156
225
|
}
|
|
157
226
|
this.account = (0, deepcopy_1.deepcopy)(snapshot.account);
|
|
158
|
-
this
|
|
159
|
-
this
|
|
227
|
+
this.lastTxTime = snapshot.lastTxTime;
|
|
228
|
+
this._verbosity = snapshot.verbosity === undefined ? undefined : { ...snapshot.verbosity };
|
|
160
229
|
}
|
|
161
230
|
get ec() {
|
|
162
231
|
return (0, ec_1.extractEc)(this.account.account?.storage.balance.other ??
|
|
@@ -191,15 +260,15 @@ class SmartContract {
|
|
|
191
260
|
return this.account.account?.storage.state;
|
|
192
261
|
}
|
|
193
262
|
get account() {
|
|
194
|
-
if (this
|
|
195
|
-
this
|
|
263
|
+
if (this.parsedAccount === undefined) {
|
|
264
|
+
this.parsedAccount = (0, core_1.loadShardAccount)(core_1.Cell.fromBase64(this._account).beginParse());
|
|
196
265
|
}
|
|
197
|
-
return this
|
|
266
|
+
return this.parsedAccount;
|
|
198
267
|
}
|
|
199
268
|
set account(account) {
|
|
200
|
-
this
|
|
201
|
-
this
|
|
202
|
-
this
|
|
269
|
+
this._account = (0, core_1.beginCell)().store((0, core_1.storeShardAccount)(account)).endCell().toBoc().toString('base64');
|
|
270
|
+
this.parsedAccount = account;
|
|
271
|
+
this.lastTxTime = account.account?.storageStats.lastPaid ?? 0;
|
|
203
272
|
}
|
|
204
273
|
static create(blockchain, args) {
|
|
205
274
|
return new SmartContract(createShardAccount(args), blockchain);
|
|
@@ -209,14 +278,14 @@ class SmartContract {
|
|
|
209
278
|
}
|
|
210
279
|
createCommonArgs(params) {
|
|
211
280
|
const now = params?.now ?? this.blockchain.now ?? Math.floor(Date.now() / 1000);
|
|
212
|
-
if (now < this
|
|
213
|
-
throw new TimeError(this.address, this
|
|
281
|
+
if (now < this.lastTxTime) {
|
|
282
|
+
throw new TimeError(this.address, this.lastTxTime, now);
|
|
214
283
|
}
|
|
215
284
|
return {
|
|
216
285
|
config: this.blockchain.configBase64,
|
|
217
286
|
libs: this.blockchain.libs ?? null,
|
|
218
287
|
verbosity: verbosityToExecutorVerbosity[this.verbosity.vmLogs],
|
|
219
|
-
shardAccount: this
|
|
288
|
+
shardAccount: this._account,
|
|
220
289
|
now,
|
|
221
290
|
lt: this.blockchain.lt,
|
|
222
291
|
randomSeed: params?.randomSeed ?? this.blockchain.random ?? Buffer.alloc(32),
|
|
@@ -272,16 +341,16 @@ class SmartContract {
|
|
|
272
341
|
console.log(res.debugLogs);
|
|
273
342
|
}
|
|
274
343
|
const tx = (0, core_1.loadTransaction)(core_1.Cell.fromBase64(res.result.transaction).beginParse());
|
|
275
|
-
this
|
|
276
|
-
this
|
|
277
|
-
this
|
|
344
|
+
this._account = res.result.shardAccount;
|
|
345
|
+
this.parsedAccount = undefined;
|
|
346
|
+
this.lastTxTime = tx.now;
|
|
278
347
|
let newStorage = undefined;
|
|
279
348
|
if (this.blockchain.recordStorage && this.account.account?.storage.state.type === 'active') {
|
|
280
349
|
newStorage = this.account.account?.storage.state.state.data ?? undefined;
|
|
281
350
|
}
|
|
282
351
|
let outActions = undefined;
|
|
283
352
|
if (res.result.actions) {
|
|
284
|
-
outActions = (
|
|
353
|
+
outActions = loadOutListExt(core_1.Cell.fromBase64(res.result.actions).beginParse());
|
|
285
354
|
}
|
|
286
355
|
return {
|
|
287
356
|
...tx,
|
|
@@ -367,7 +436,7 @@ class SmartContract {
|
|
|
367
436
|
get verbosity() {
|
|
368
437
|
return {
|
|
369
438
|
...this.blockchain.verbosity,
|
|
370
|
-
...this
|
|
439
|
+
...this._verbosity,
|
|
371
440
|
};
|
|
372
441
|
}
|
|
373
442
|
set verbosity(value) {
|
|
@@ -375,21 +444,21 @@ class SmartContract {
|
|
|
375
444
|
}
|
|
376
445
|
setVerbosity(verbosity) {
|
|
377
446
|
if (typeof verbosity === 'string') {
|
|
378
|
-
this
|
|
379
|
-
...this
|
|
447
|
+
this._verbosity = {
|
|
448
|
+
...this._verbosity,
|
|
380
449
|
vmLogs: verbosity,
|
|
381
450
|
blockchainLogs: verbosity !== 'none',
|
|
382
451
|
};
|
|
383
452
|
}
|
|
384
453
|
else {
|
|
385
|
-
this
|
|
454
|
+
this._verbosity = verbosity;
|
|
386
455
|
}
|
|
387
456
|
}
|
|
388
457
|
get debug() {
|
|
389
|
-
return this
|
|
458
|
+
return this._debug ?? this.blockchain.debug;
|
|
390
459
|
}
|
|
391
460
|
setDebug(debug) {
|
|
392
|
-
this
|
|
461
|
+
this._debug = debug;
|
|
393
462
|
}
|
|
394
463
|
}
|
|
395
464
|
exports.SmartContract = SmartContract;
|
package/dist/utils/AsyncLock.js
CHANGED
|
@@ -9,16 +9,16 @@ function createWaiter() {
|
|
|
9
9
|
return { promise, resolve: resolveFn };
|
|
10
10
|
}
|
|
11
11
|
class AsyncLock {
|
|
12
|
-
|
|
12
|
+
waiters = [];
|
|
13
13
|
async acquire() {
|
|
14
|
-
const waiters = this
|
|
15
|
-
this
|
|
14
|
+
const waiters = this.waiters.map((w) => w.promise);
|
|
15
|
+
this.waiters.push(createWaiter());
|
|
16
16
|
if (waiters.length > 0) {
|
|
17
17
|
await Promise.all(waiters);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
async release() {
|
|
21
|
-
const waiter = this
|
|
21
|
+
const waiter = this.waiters.shift();
|
|
22
22
|
if (waiter !== undefined) {
|
|
23
23
|
waiter.resolve();
|
|
24
24
|
}
|