Skip to content

@ton/sandbox: State changes not persisted after self-call in a transaction chain #135

@Leepey

Description

@Leepey

Description

Здравствуйте!

Я столкнулся с критической проблемой при тестировании смарт-контрактов в среде @ton/sandbox, которая мешает корректной работе стандартного паттерна фиксации состояния.

Краткое описание проблемы:
Изменения состояния (глобальные переменные, словари), сделанные в одной транзакции, не сохраняются, если для их фиксации используется самовызов (отправка сообщения самому себе). В реальной сети этот паттерн работает корректно.

Окружение:
"@ton/sandbox": ">=0.37.0",
"@ton/test-utils": ">=0.11.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.17.2",
"jest": "^30.0.5",
"prettier": "^3.6.2",
"@ton/ton": ">=15.3.1 <16.0.0",
"@ton/crypto": "^3.3.0",
"ts-jest": "^29.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2",
"@ton/tolk-js": ">=1.0.0",
"@tact-lang/compiler": ">=1.6.13 <2.0.0",
"@ton-community/func-js": ">=0.10.0"

  • ОС: Windows

Steps to Reproduce

Шаги для воспроизведения:

  1. Создайте смарт-контракт со следующей логикой в recv_internal:

    • При получении сообщения с пустым телом (in_msg_body.slice_empty?()).
    • Контракт изменяет несколько глобальных переменных (например, total_deposits += 1).
    • Вызывает save_data() для сохранения изменений.
    • Отправляет сообщение самому себе, чтобы создать action и заставить TVM сохранить состояние.
  2. Напишите тест, который:

    • Отправляет транзакцию на контракт.
    • Проверяет, что состояние изменилось (например, через get-метод get_basic_stats).

Ожидаемое поведение:

  • Транзакция от пользователя (Tx 0) создает action.
  • Транзакция от контракта к себе (Tx 1) успешно обрабатывает этот action.
  • Изменения состояния, сделанные в Tx 0, сохраняются в хранилище контракта.
  • Get-метод после этого возвращает обновленные данные.

Фактическое поведение:

  • Логи транзакций показывают, что Tx 0 успешно создала action (Total Actions: 1).
  • Tx 1 успешно обрабатывается (Success: true, Exit Code: 0).
  • Однако get-методы возвращают старые данные, как будто save_data() никогда не вызывался. Состояние не сохраняется.

Environment

No response

Logs or Screenshots

Логи транзакций:

Вот логи из @ton/sandbox, которые демонстрируют проблему:

--- Transaction 0 ---
From: N/A (e.g., deploy)
To: 0x... (user address)
Success: true
Exit Code: 0
Total Actions: 1  <-- Важно: Действие создано

--- Transaction 1 ---
From: 0x... (user address)
To: 0x... (contract address)
Success: true
Exit Code: 0
Total Actions: 0  <-- Важно: Самовызов обработан, но состояние не сохранилось

Код контракта (FunC):

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    ;; ... проверки ...
    load_data();

    if (in_msg_body.slice_empty?()) {
        return handle_simple_deposit(msg_value, sender);
    }
    ;; ... остальная логика ...
}

() handle_simple_deposit(int msg_value, slice sender) impure {
    ;; ... проверки ...

    // Изменяем состояние
    total_deposits += 1;
    save_data();

    // Отправляем сообщение самому себе, чтобы зафиксировать изменения
    send_raw_message(begin_cell()
        .store_uint(0x18, 6)
        .store_slice(my_address())
        .store_coins(0)
        .store_uint(op_self_call, 32) // op_self_call = 0x73656c66
        .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
    .end_cell(), 64);
}

() recv_internal(...) {
    // ...
    int op = in_msg_body~load_uint(32);
    if (op == op_self_call) {
        return (); // Получили самовызов, ничего не делаем.
    }
    // ...
}

Код теста (TypeScript/Jest):

it('should accept deposits with subsidy', async () => {
    const initialStats = await mixton.getBasicStats();
    await mixton.sendDeposit(user.getSender(), toNano('10.0'));

    const newStats = await mixton.getBasicStats();

    // Этот expect падает
    expect(newStats.totalDeposits).toBe(initialStats.totalDeposits + 1);
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions