Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 04.04 · 25 мин
Средний
SandboxTestingTVM EmulatorTreasuryGas

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,
        });
    });
});

Что происходит:

  1. Blockchain.create() — создаёт изолированный блокчейн в памяти
  2. blockchain.treasury('deployer') — создаёт кошелёк с балансом (~1,000,000 TON)
  3. blockchain.openContract(...) — регистрирует контракт в локальном блокчейне
  4. 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 сообщения
});

Доступные поля для проверки:

ПолеТипОписание
fromAddressАдрес отправителя
toAddressАдрес получателя
successbooleanТранзакция прошла без ошибок
deploybooleanЭто deploy-транзакция
exitCodenumberКод выхода TVM
opnumberOperation code сообщения
valuebigintКоличество 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);
    });
});
WARNING

Sandbox vs реальная сеть

Sandbox — эмулятор, а не реальный блокчейн. Основные отличия:

  • Нет валидаторов — транзакции выполняются мгновенно
  • Мгновенная финальность — не нужно ждать подтверждений
  • Неограниченный баланс — Treasury создаёт кошельки с огромным балансом
  • Нет сетевых задержек — сообщения доставляются мгновенно

Перед деплоем в mainnet всегда тестируйте в testnet с реальными задержками и комиссиями.


Частые ошибки

  1. Тестируют только happy path (успешный сценарий) и не проверяют обработку ошибок, bounced-сообщений и недостаточного газа.
  2. Не проверяют газовые расходы в тестах: контракт может работать корректно, но тратить в 10 раз больше газа, чем необходимо.
  3. Забывают тестировать порядок сообщений в цепочке: в sandbox сообщения обрабатываются последовательно, но важно проверять все возможные порядки.
  4. Не используют blockchain.now для управления временем в тестах, что критично для контрактов с таймлоками и временными ограничениями.

Проверка знанийKnowledge check
Что делает функция blockchain.treasury('deployer') в тестовом окружении Sandbox?
ОтветAnswer
Функция blockchain.treasury('deployer') создаёт 'казначейство' (Treasury) -- тестовый кошелёк с большим балансом TON (~1,000,000 TON) в локальном эмуляторе. Строковый параметр ('deployer') задаёт seed для детерминистической генерации адреса. Treasury используется для отправки сообщений контрактам в тестах (деплой, вызов функций) без необходимости получать реальный Toncoin.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 10