Sandbox Testing
Тестирование в sandbox — это ваша страховая сетка перед деплоем на блокчейн. В отличие от обычного программирования, где баг можно быстро исправить патчем, ошибка в деплоенном контракте может привести к невозвратимой потере средств. Sandbox эмулирует полную среду TON локально, позволяя проверить все сценарии — включая edge cases, bounce-обработку и газовые расчёты — за секунды и бесплатно.
@ton/sandbox — локальный эмулятор блокчейна TON для тестирования смарт-контрактов. Он выполняет TVM bytecode без подключения к реальной сети, обеспечивая быструю и бесплатную проверку контрактов.
Зачем нужен Sandbox?
- Скорость — тесты выполняются за миллисекунды (нет ожидания блоков)
- Бесплатно — не нужен реальный Toncoin
- Детерминизм — результаты воспроизводимы
- Полная эмуляция — TVM, газ, fees, bouncing — всё работает как в реальной сети
Blockchain.create() и Treasury
Основа тестирования — создание локального блокчейна и “казначейства” (Treasury) для отправки сообщений:
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
describe('Counter', () => {
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let counter: SandboxContract<Counter>;
beforeEach(async () => {
// Создаём локальный блокчейн
blockchain = await Blockchain.create();
// Создаём "казначейство" -- кошелёк с балансом для тестов
deployer = await blockchain.treasury('deployer');
// Деплоим контракт
counter = blockchain.openContract(
await Counter.fromInit()
);
const deployResult = await counter.send(
deployer.getSender(),
{ value: toNano('0.05') },
{ $$type: 'Deploy', queryId: 0n }
);
expect(deployResult.transactions).toHaveTransaction({
from: deployer.address,
to: counter.address,
deploy: true,
success: true,
});
});
});
Что происходит:
Blockchain.create()— создаёт изолированный блокчейн в памятиblockchain.treasury('deployer')— создаёт кошелёк с балансом (~1,000,000 TON)blockchain.openContract(...)— регистрирует контракт в локальном блокчейнеcounter.send(...)— отправляет deploy-сообщение
Отправка сообщений
После деплоя можно отправлять сообщения контракту:
it('should increment counter', async () => {
// Отправляем текстовое сообщение "increment"
const result = await counter.send(
deployer.getSender(),
{ value: toNano('0.02') },
'increment'
);
// Проверяем, что транзакция успешна
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: counter.address,
success: true,
});
});
Для типизированных сообщений:
it('should add value', async () => {
const result = await counter.send(
deployer.getSender(),
{ value: toNano('0.02') },
{
$$type: 'Add',
amount: 42n,
}
);
expect(result.transactions).toHaveTransaction({
from: deployer.address,
to: counter.address,
success: true,
});
});
Проверка состояния через get-методы
Get-методы позволяют проверить состояние контракта после транзакций:
it('should track counter value', async () => {
// Начальное значение
const before = await counter.getCounter();
expect(before).toEqual(0n);
// Инкремент
await counter.send(
deployer.getSender(),
{ value: toNano('0.02') },
'increment'
);
// Проверяем обновлённое значение
const after = await counter.getCounter();
expect(after).toEqual(1n);
});
Проверка транзакций
Sandbox предоставляет matcher toHaveTransaction для детальной проверки:
expect(result.transactions).toHaveTransaction({
from: deployer.address, // Отправитель
to: counter.address, // Получатель
success: true, // Транзакция успешна
deploy: true, // Это deploy-транзакция
exitCode: 0, // Код выхода TVM (0 = OK)
op: 0x12345678, // Operation code сообщения
});
Доступные поля для проверки:
| Поле | Тип | Описание |
|---|---|---|
from | Address | Адрес отправителя |
to | Address | Адрес получателя |
success | boolean | Транзакция прошла без ошибок |
deploy | boolean | Это deploy-транзакция |
exitCode | number | Код выхода TVM |
op | number | Operation code сообщения |
value | bigint | Количество TON в сообщении |
Газ и комиссии
Sandbox эмулирует газ и комиссии как в реальной сети. Можно проверять расход газа:
it('should consume reasonable gas', async () => {
const result = await counter.send(
deployer.getSender(),
{ value: toNano('0.02') },
'increment'
);
// Проверяем, что транзакция не слишком дорогая
const tx = result.transactions[1]; // [0] = external, [1] = internal
if (tx.description.type === 'generic') {
const gasUsed = tx.description.computePhase;
if (gasUsed.type === 'vm') {
console.log('Gas used:', gasUsed.gasUsed);
}
}
});
Полный пример: тест Counter
Объединим всё в полный тестовый файл:
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
describe('Counter', () => {
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let counter: SandboxContract<Counter>;
beforeEach(async () => {
blockchain = await Blockchain.create();
deployer = await blockchain.treasury('deployer');
counter = blockchain.openContract(await Counter.fromInit());
await counter.send(
deployer.getSender(),
{ value: toNano('0.05') },
{ $$type: 'Deploy', queryId: 0n }
);
});
it('should deploy with zero counter', async () => {
const value = await counter.getCounter();
expect(value).toEqual(0n);
});
it('should increment', async () => {
await counter.send(
deployer.getSender(),
{ value: toNano('0.02') },
'increment'
);
const value = await counter.getCounter();
expect(value).toEqual(1n);
});
it('should increment multiple times', async () => {
for (let i = 0; i < 5; i++) {
await counter.send(
deployer.getSender(),
{ value: toNano('0.02') },
'increment'
);
}
const value = await counter.getCounter();
expect(value).toEqual(5n);
});
it('should handle different senders', async () => {
const user = await blockchain.treasury('user');
await counter.send(
user.getSender(),
{ value: toNano('0.02') },
'increment'
);
const value = await counter.getCounter();
expect(value).toEqual(1n);
});
});
Sandbox vs реальная сеть
Sandbox — эмулятор, а не реальный блокчейн. Основные отличия:
- Нет валидаторов — транзакции выполняются мгновенно
- Мгновенная финальность — не нужно ждать подтверждений
- Неограниченный баланс — Treasury создаёт кошельки с огромным балансом
- Нет сетевых задержек — сообщения доставляются мгновенно
Перед деплоем в mainnet всегда тестируйте в testnet с реальными задержками и комиссиями.
Частые ошибки
- Тестируют только happy path (успешный сценарий) и не проверяют обработку ошибок, bounced-сообщений и недостаточного газа.
- Не проверяют газовые расходы в тестах: контракт может работать корректно, но тратить в 10 раз больше газа, чем необходимо.
- Забывают тестировать порядок сообщений в цепочке: в sandbox сообщения обрабатываются последовательно, но важно проверять все возможные порядки.
- Не используют blockchain.now для управления временем в тестах, что критично для контрактов с таймлоками и временными ограничениями.
Проверка знанийЧто делает функция blockchain.treasury('deployer') в тестовом окружении Sandbox?
Check Your Understanding
Finished the lesson?
Mark it as complete to track your progress
Войдите чтобы оценить урок