1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

Add account state by transaction and emulator (extended) (#592)

* account_state_by_transaction

* Correct time calculation

* Bug fixes

* Refactor

* namespace block::transaction

* smc.forget

* RunEmulator: remove wallet_id

* Refactor & fixes

* AccountStateByTransaction: use shardchain block instead of masterchain block

* transaction emulator core

* refactor

* tx emulator major functionality

* small format changes

* readme

* clean

* return json, add support for init messages

* tx emulator readme

* refactor getConfigParam and getConfigAll

* add shardchain_libs_boc parameter

* option to change verbosity level of transaction emulator

* fix deserializing ShardAccount with account_none

* add mode needSpecialSmc when unpack config

* emulator: block::Transaction -> block::transaction::Transaction

* Refactor

* emulator: Fix bug

* emulator: Support for emulator-extern

* emulator: Refactor

* Return vm log and vm exit code.

* fix build on macos, emulator_static added

* adjust documentation

* ignore_chksig for external messages

* tvm emulator, run get method

* Added more params for transaction emulator

* Added setters for optional transaction emulator params, moved libs to a new setter

* Added actions cell output to transaction emulator

* fix tonlib build

* refactoring, rand seed as hex size 64, tvm emulator send message

* tvm send message, small refactoring

* fix config decoding, rename

* improve documentation

* macos export symbols

* Added run_get_method to transaction emulator emscipten wrapper

* Fixed empty action list serialization

* Changed actions list cell to serialize as json null instead of empty string in transaction emulator

* stack as boc

* log gas remaining

* Fix prev_block_id

* fix build errors

* Refactor fetch_config_params

* fix failing unwrap of optional rand_seed

* lookup correct shard, choose prev_block based on account shard

* fix tonlib android jni build

---------

Co-authored-by: legaii <jgates.ardux@gmail.com>
Co-authored-by: ms <dungeon666master@protonmail.com>
Co-authored-by: krigga <krigga7@gmail.com>
This commit is contained in:
EmelyanenkoK 2023-02-02 10:03:45 +03:00 committed by GitHub
parent adf67aa869
commit 3b3c25b654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2095 additions and 158 deletions

55
emulator/CMakeLists.txt Normal file
View file

@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
set(EMULATOR_STATIC_SOURCE
transaction-emulator.cpp
tvm-emulator.hpp
)
set(EMULATOR_HEADERS
transaction-emulator.h
emulator-extern.h
)
set(EMULATOR_SOURCE
emulator-extern.cpp
)
set(EMULATOR_EMSCRIPTEN_SOURCE
transaction-emscripten.cpp
)
include(GenerateExportHeader)
add_library(emulator_static STATIC ${EMULATOR_STATIC_SOURCE})
target_link_libraries(emulator_static PUBLIC ton_crypto ton_block smc-envelope)
add_library(emulator SHARED ${EMULATOR_SOURCE} ${EMULATOR_HEADERS})
target_link_libraries(emulator PUBLIC emulator_static)
generate_export_header(emulator EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/emulator_export.h)
target_include_directories(emulator PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
if (APPLE)
set_target_properties(emulator PROPERTIES LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/emulator_export_list")
endif()
if (USE_EMSCRIPTEN)
add_executable(emulator-emscripten ${EMULATOR_EMSCRIPTEN_SOURCE})
target_link_libraries(emulator-emscripten PUBLIC emulator)
target_link_options(emulator-emscripten PRIVATE -sEXPORTED_RUNTIME_METHODS=_malloc,free,UTF8ToString,stringToUTF8,allocate,ALLOC_NORMAL,lengthBytesUTF8)
target_link_options(emulator-emscripten PRIVATE -sEXPORTED_FUNCTIONS=_emulate,_free,_run_get_method)
target_link_options(emulator-emscripten PRIVATE -sEXPORT_NAME=EmulatorModule)
target_link_options(emulator-emscripten PRIVATE -sERROR_ON_UNDEFINED_SYMBOLS=0)
target_link_options(emulator-emscripten PRIVATE -Oz)
target_link_options(emulator-emscripten PRIVATE -sIGNORE_MISSING_MAIN=1)
target_link_options(emulator-emscripten PRIVATE -sAUTO_NATIVE_LIBRARIES=0)
target_link_options(emulator-emscripten PRIVATE -sMODULARIZE=1)
target_link_options(emulator-emscripten PRIVATE -sENVIRONMENT=web)
target_link_options(emulator-emscripten PRIVATE -sFILESYSTEM=0)
target_link_options(emulator-emscripten PRIVATE -fexceptions)
target_compile_options(emulator-emscripten PRIVATE -fexceptions)
endif()

32
emulator/README.md Normal file
View file

@ -0,0 +1,32 @@
# Emulator
Emulator is a shared library containing the following functionality:
- Emulating blockchain transactions
- Emulating TVM - get methods and sending external and internal messages.
## Transaction Emulator
To emulate transaction you need the following data:
- Account state of type *ShardAccount*.
- Global config of type *(Hashmap 32 ^Cell)*.
- Inbound message of type *MessageAny*.
Optionally you can set emulation parameters:
- *ignore_chksig* - whether CHKSIG instructions are set to always succeed. Default: *false*
- *lt* - logical time of emulation. Default: next block's lt after the account's last transaction block.
- *unixtime* - unix time of emulation. Default: current system time
- *rand_seed* - random seed. Default: generated randomly
- *libs* - shared libraries. If your smart contract uses shared libraries (located in masterchain), you should set this parameter.
Emulator output contains:
- Transaction object (*Transaction*)
- New account state (*ShardAccount*)
- Actions cell (*OutList n*)
- TVM log
## TVM Emulator
TVM emulator is intended to run get methods or emulate sending message on TVM level. It is initialized with smart contract code and data cells.
- To run get method you pass *initial stack* and *method id* (as integer).
- To emulate sending message you pass *message body* and in case of internal message *amount* in nanograms.

27
emulator/StringLog.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef TON_STRINGLOG_H
#define TON_STRINGLOG_H
#include "td/utils/logging.h"
#include <thread>
class StringLog : public td::LogInterface {
public:
StringLog() {
}
void append(td::CSlice new_slice, int log_level) override {
str.append(new_slice.str());
}
void rotate() override {
}
std::string get_string() const {
return str;
}
private:
std::string str;
};
#endif //TON_STRINGLOG_H

View file

@ -0,0 +1,189 @@
#include "emulator-extern.h"
#include "td/utils/logging.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/misc.h"
#include "td/utils/optional.h"
#include "StringLog.h"
#include <iostream>
#include "crypto/common/bitstring.h"
struct TransactionEmulationParams {
uint32_t utime;
uint64_t lt;
td::optional<std::string> rand_seed_hex;
bool ignore_chksig;
};
td::Result<TransactionEmulationParams> decode_transaction_emulation_params(const char* json) {
TransactionEmulationParams params;
std::string json_str(json);
TRY_RESULT(input_json, td::json_decode(td::MutableSlice(json_str)));
auto &obj = input_json.get_object();
TRY_RESULT(utime_field, td::get_json_object_field(obj, "utime", td::JsonValue::Type::Number, false));
TRY_RESULT(utime, td::to_integer_safe<td::uint32>(utime_field.get_number()));
params.utime = utime;
TRY_RESULT(lt_field, td::get_json_object_field(obj, "lt", td::JsonValue::Type::String, false));
TRY_RESULT(lt, td::to_integer_safe<td::uint64>(lt_field.get_string()));
params.lt = lt;
TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", true));
if (rand_seed_str.size() > 0) {
params.rand_seed_hex = rand_seed_str;
}
TRY_RESULT(ignore_chksig, td::get_json_object_bool_field(obj, "ignore_chksig", false));
params.ignore_chksig = ignore_chksig;
return params;
}
struct GetMethodParams {
std::string code;
std::string data;
int verbosity;
td::optional<std::string> libs;
std::string address;
uint32_t unixtime;
uint64_t balance;
std::string rand_seed_hex;
int64_t gas_limit;
int method_id;
};
td::Result<GetMethodParams> decode_get_method_params(const char* json) {
GetMethodParams params;
std::string json_str(json);
TRY_RESULT(input_json, td::json_decode(td::MutableSlice(json_str)));
auto &obj = input_json.get_object();
TRY_RESULT(code, td::get_json_object_string_field(obj, "code", false));
params.code = code;
TRY_RESULT(data, td::get_json_object_string_field(obj, "data", false));
params.data = data;
TRY_RESULT(verbosity, td::get_json_object_int_field(obj, "verbosity", false));
params.verbosity = verbosity;
TRY_RESULT(libs, td::get_json_object_string_field(obj, "libs", true));
if (libs.size() > 0) {
params.libs = libs;
}
TRY_RESULT(address, td::get_json_object_string_field(obj, "address", false));
params.address = address;
TRY_RESULT(unixtime_field, td::get_json_object_field(obj, "unixtime", td::JsonValue::Type::Number, false));
TRY_RESULT(unixtime, td::to_integer_safe<td::uint32>(unixtime_field.get_number()));
params.unixtime = unixtime;
TRY_RESULT(balance_field, td::get_json_object_field(obj, "balance", td::JsonValue::Type::String, false));
TRY_RESULT(balance, td::to_integer_safe<td::uint64>(balance_field.get_string()));
params.balance = balance;
TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", false));
params.rand_seed_hex = rand_seed_str;
TRY_RESULT(gas_limit_field, td::get_json_object_field(obj, "gas_limit", td::JsonValue::Type::String, false));
TRY_RESULT(gas_limit, td::to_integer_safe<td::uint64>(gas_limit_field.get_string()));
params.gas_limit = gas_limit;
TRY_RESULT(method_id, td::get_json_object_int_field(obj, "method_id", false));
params.method_id = method_id;
return params;
}
extern "C" {
const char *emulate(const char *config, const char* libs, int verbosity, const char* account, const char* message, const char* params) {
StringLog logger;
td::log_interface = &logger;
SET_VERBOSITY_LEVEL(verbosity_DEBUG);
auto decoded_params_res = decode_transaction_emulation_params(params);
if (decoded_params_res.is_error()) {
return strdup(R"({"fail":true,"message":"Can't decode other params"})");
}
auto decoded_params = decoded_params_res.move_as_ok();
auto em = transaction_emulator_create(config, verbosity);
bool rand_seed_set = true;
if (decoded_params.rand_seed_hex) {
rand_seed_set = transaction_emulator_set_rand_seed(em, decoded_params.rand_seed_hex.unwrap().c_str());
}
if (!transaction_emulator_set_libs(em, libs) ||
!transaction_emulator_set_lt(em, decoded_params.lt) ||
!transaction_emulator_set_unixtime(em, decoded_params.utime) ||
!transaction_emulator_set_ignore_chksig(em, decoded_params.ignore_chksig) ||
!rand_seed_set) {
transaction_emulator_destroy(em);
return strdup(R"({"fail":true,"message":"Can't set params"})");
}
auto tx = transaction_emulator_emulate_transaction(em, account, message);
transaction_emulator_destroy(em);
const char* output = nullptr;
{
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("output", td::JsonRaw(td::Slice(tx)));
json_obj("logs", logger.get_string());
json_obj.leave();
output = strdup(jb.string_builder().as_cslice().c_str());
}
free((void*) tx);
return output;
}
const char *run_get_method(const char *params, const char* stack, const char* config) {
StringLog logger;
td::log_interface = &logger;
SET_VERBOSITY_LEVEL(verbosity_DEBUG);
auto decoded_params_res = decode_get_method_params(params);
if (decoded_params_res.is_error()) {
return strdup(R"({"fail":true,"message":"Can't decode params"})");
}
auto decoded_params = decoded_params_res.move_as_ok();
auto tvm = tvm_emulator_create(decoded_params.code.c_str(), decoded_params.data.c_str(), decoded_params.verbosity);
if ((decoded_params.libs && !tvm_emulator_set_libraries(tvm, decoded_params.libs.value().c_str())) ||
!tvm_emulator_set_c7(tvm, decoded_params.address.c_str(), decoded_params.unixtime,
decoded_params.balance, decoded_params.rand_seed_hex.c_str(), config) ||
(decoded_params.gas_limit > 0 && !tvm_emulator_set_gas_limit(tvm, decoded_params.gas_limit))) {
tvm_emulator_destroy(tvm);
return strdup(R"({"fail":true,"message":"Can't set params"})");
}
auto res = tvm_emulator_run_get_method(tvm, decoded_params.method_id, stack);
tvm_emulator_destroy(tvm);
const char* output = nullptr;
{
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("output", td::JsonRaw(td::Slice(res)));
json_obj("logs", logger.get_string());
json_obj.leave();
output = strdup(jb.string_builder().as_cslice().c_str());
}
free((void*) res);
return output;
}
}

View file

@ -0,0 +1,435 @@
#include "emulator-extern.h"
#include "td/utils/base64.h"
#include "td/utils/Status.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
#include "td/utils/Variant.h"
#include "td/utils/overloaded.h"
#include "transaction-emulator.h"
#include "tvm-emulator.hpp"
#include "crypto/vm/stack.hpp"
td::Result<td::Ref<vm::Cell>> boc_b64_to_cell(const char *boc) {
TRY_RESULT_PREFIX(boc_decoded, td::base64_decode(td::Slice(boc)), "Can't decode base64 boc: ");
return vm::std_boc_deserialize(boc_decoded);
}
td::Result<std::string> cell_to_boc_b64(td::Ref<vm::Cell> cell) {
TRY_RESULT_PREFIX(boc, vm::std_boc_serialize(std::move(cell), vm::BagOfCells::Mode::WithCRC32C), "Can't serialize cell: ");
return td::base64_encode(boc.as_slice());
}
const char *success_response(std::string&& transaction, std::string&& new_shard_account, std::string&& vm_log, td::optional<std::string>&& actions) {
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("success", td::JsonTrue());
json_obj("transaction", std::move(transaction));
json_obj("shard_account", std::move(new_shard_account));
json_obj("vm_log", std::move(vm_log));
if (actions) {
json_obj("actions", actions.unwrap());
} else {
json_obj("actions", td::JsonNull());
}
json_obj.leave();
return strdup(jb.string_builder().as_cslice().c_str());
}
const char *error_response(std::string&& error) {
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("success", td::JsonFalse());
json_obj("error", std::move(error));
json_obj.leave();
return strdup(jb.string_builder().as_cslice().c_str());
}
const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_code) {
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("success", td::JsonFalse());
json_obj("error", "External message not accepted by smart contract");
json_obj("vm_log", std::move(vm_log));
json_obj("vm_exit_code", vm_exit_code);
json_obj.leave();
return strdup(jb.string_builder().as_cslice().c_str());
}
#define ERROR_RESPONSE(error) return error_response(error)
td::Result<block::Config> decode_config(const char* config_boc) {
TRY_RESULT_PREFIX(config_params_cell, boc_b64_to_cell(config_boc), "Can't deserialize config params boc: ");
auto global_config = block::Config(config_params_cell, td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc);
TRY_STATUS_PREFIX(global_config.unpack(), "Can't unpack config params: ");
return global_config;
}
void *transaction_emulator_create(const char *config_params_boc, int vm_log_verbosity) {
auto global_config_res = decode_config(config_params_boc);
if (global_config_res.is_error()) {
LOG(ERROR) << global_config_res.move_as_error().message();
return nullptr;
}
return new emulator::TransactionEmulator(global_config_res.move_as_ok(), vm_log_verbosity);
}
const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
auto message_cell_r = boc_b64_to_cell(message_boc);
if (message_cell_r.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't deserialize message boc: " << message_cell_r.move_as_error());
}
auto message_cell = message_cell_r.move_as_ok();
auto message_cs = vm::load_cell_slice(message_cell);
int msg_tag = block::gen::t_CommonMsgInfo.get_tag(message_cs);
auto shard_account_cell = boc_b64_to_cell(shard_account_boc);
if (shard_account_cell.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't deserialize shard account boc: " << shard_account_cell.move_as_error());
}
auto shard_account_slice = vm::load_cell_slice(shard_account_cell.ok_ref());
block::gen::ShardAccount::Record shard_account;
if (!tlb::unpack(shard_account_slice, shard_account)) {
ERROR_RESPONSE(PSTRING() << "Can't unpack shard account cell");
}
td::Ref<vm::CellSlice> addr_slice;
auto account_slice = vm::load_cell_slice(shard_account.account);
if (block::gen::t_Account.get_tag(account_slice) == block::gen::Account::account_none) {
if (msg_tag == block::gen::CommonMsgInfo::ext_in_msg_info) {
block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
if (!tlb::unpack(message_cs, info)) {
ERROR_RESPONSE(PSTRING() << "Can't unpack inbound external message");
}
addr_slice = std::move(info.dest);
}
else if (msg_tag == block::gen::CommonMsgInfo::int_msg_info) {
block::gen::CommonMsgInfo::Record_int_msg_info info;
if (!tlb::unpack(message_cs, info)) {
ERROR_RESPONSE(PSTRING() << "Can't unpack inbound internal message");
}
addr_slice = std::move(info.dest);
} else {
ERROR_RESPONSE(PSTRING() << "Only ext in and int message are supported");
}
} else {
block::gen::Account::Record_account account_record;
if (!tlb::unpack(account_slice, account_record)) {
ERROR_RESPONSE(PSTRING() << "Can't unpack account cell");
}
addr_slice = std::move(account_record.addr);
}
ton::WorkchainId wc;
ton::StdSmcAddress addr;
if (!block::tlb::t_MsgAddressInt.extract_std_address(addr_slice, wc, addr)) {
ERROR_RESPONSE(PSTRING() << "Can't extract account address");
}
auto account = block::Account(wc, addr.bits());
ton::UnixTime now = (unsigned)std::time(nullptr);
bool is_special = wc == ton::masterchainId && emulator->get_config().is_special_smartcontract(addr);
if (!account.unpack(vm::load_cell_slice_ref(shard_account_cell.move_as_ok()), td::Ref<vm::CellSlice>(), now, is_special)) {
ERROR_RESPONSE(PSTRING() << "Can't unpack shard account");
}
auto result = emulator->emulate_transaction(std::move(account), message_cell, 0, 0, block::transaction::Transaction::tr_ord);
if (result.is_error()) {
ERROR_RESPONSE(PSTRING() << "Emulate transaction failed: " << result.move_as_error());
}
auto emulation_result = result.move_as_ok();
auto external_not_accepted = dynamic_cast<emulator::TransactionEmulator::EmulationExternalNotAccepted *>(emulation_result.get());
if (external_not_accepted) {
return external_not_accepted_response(std::move(external_not_accepted->vm_log), external_not_accepted->vm_exit_code);
}
auto emulation_success = dynamic_cast<emulator::TransactionEmulator::EmulationSuccess&>(*emulation_result);
auto trans_boc_b64 = cell_to_boc_b64(std::move(emulation_success.transaction));
if (trans_boc_b64.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't serialize Transaction to boc " << trans_boc_b64.move_as_error());
}
auto new_shard_account_cell = vm::CellBuilder().store_ref(emulation_success.account.total_state)
.store_bits(emulation_success.account.last_trans_hash_.as_bitslice())
.store_long(emulation_success.account.last_trans_lt_).finalize();
auto new_shard_account_boc_b64 = cell_to_boc_b64(std::move(new_shard_account_cell));
if (new_shard_account_boc_b64.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't serialize ShardAccount to boc " << new_shard_account_boc_b64.move_as_error());
}
td::optional<td::string> actions_boc_b64;
if (emulation_success.actions.not_null()) {
auto actions_boc_b64_result = cell_to_boc_b64(std::move(emulation_success.actions));
if (actions_boc_b64_result.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't serialize actions list cell to boc " << actions_boc_b64_result.move_as_error());
}
actions_boc_b64 = actions_boc_b64_result.move_as_ok();
}
return success_response(trans_boc_b64.move_as_ok(), new_shard_account_boc_b64.move_as_ok(), std::move(emulation_success.vm_log), std::move(actions_boc_b64));
}
bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
emulator->set_unixtime(unixtime);
return true;
}
bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
emulator->set_lt(lt);
return true;
}
bool transaction_emulator_set_rand_seed(void *transaction_emulator, const char* rand_seed_hex) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
auto rand_seed_hex_slice = td::Slice(rand_seed_hex);
if (rand_seed_hex_slice.size() != 64) {
LOG(ERROR) << "Rand seed expected as 64 characters hex string";
return false;
}
auto rand_seed_bytes = td::hex_decode(rand_seed_hex_slice);
if (rand_seed_bytes.is_error()) {
LOG(ERROR) << "Can't decode hex rand seed";
return false;
}
td::BitArray<256> rand_seed;
rand_seed.as_slice().copy_from(rand_seed_bytes.move_as_ok());
emulator->set_rand_seed(rand_seed);
return true;
}
bool transaction_emulator_set_ignore_chksig(void *transaction_emulator, bool ignore_chksig) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
emulator->set_ignore_chksig(ignore_chksig);
return true;
}
bool transaction_emulator_set_config(void *transaction_emulator, const char* config_boc) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
auto global_config_res = decode_config(config_boc);
if (global_config_res.is_error()) {
LOG(ERROR) << global_config_res.move_as_error().message();
return false;
}
emulator->set_config(global_config_res.move_as_ok());
return true;
}
bool transaction_emulator_set_libs(void *transaction_emulator, const char* shardchain_libs_boc) {
auto emulator = static_cast<emulator::TransactionEmulator *>(transaction_emulator);
if (shardchain_libs_boc != nullptr) {
auto shardchain_libs_cell = boc_b64_to_cell(shardchain_libs_boc);
if (shardchain_libs_cell.is_error()) {
LOG(ERROR) << "Can't deserialize shardchain libraries boc: " << shardchain_libs_cell.move_as_error();
return false;
}
emulator->set_libs(vm::Dictionary(shardchain_libs_cell.move_as_ok(), 256));
}
return true;
}
void transaction_emulator_destroy(void *transaction_emulator) {
delete static_cast<emulator::TransactionEmulator *>(transaction_emulator);
}
bool emulator_set_verbosity_level(int verbosity_level) {
if (0 <= verbosity_level && verbosity_level <= VERBOSITY_NAME(NEVER)) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity_level);
return true;
}
return false;
}
void *tvm_emulator_create(const char *code, const char *data, int vm_log_verbosity) {
auto code_cell = boc_b64_to_cell(code);
if (code_cell.is_error()) {
LOG(ERROR) << "Can't deserialize code boc: " << code_cell.move_as_error();
return nullptr;
}
auto data_cell = boc_b64_to_cell(data);
if (data_cell.is_error()) {
LOG(ERROR) << "Can't deserialize code boc: " << data_cell.move_as_error();
return nullptr;
}
auto emulator = new emulator::TvmEmulator(code_cell.move_as_ok(), data_cell.move_as_ok());
emulator->set_vm_verbosity_level(vm_log_verbosity);
return emulator;
}
bool tvm_emulator_set_libraries(void *tvm_emulator, const char *libs_boc) {
vm::Dictionary libs{256};
auto libs_cell = boc_b64_to_cell(libs_boc);
if (libs_cell.is_error()) {
LOG(ERROR) << "Can't deserialize libraries boc: " << libs_cell.move_as_error();
return false;
}
libs = vm::Dictionary(libs_cell.move_as_ok(), 256);
auto emulator = static_cast<emulator::TvmEmulator *>(tvm_emulator);
emulator->set_libraries(std::move(libs));
return true;
}
bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixtime, uint64_t balance, const char *rand_seed_hex, const char *config_boc) {
auto emulator = static_cast<emulator::TvmEmulator *>(tvm_emulator);
auto std_address = block::StdAddress::parse(td::Slice(address));
if (std_address.is_error()) {
LOG(ERROR) << "Can't parse address: " << std_address.move_as_error();
return false;
}
auto config_params_cell = boc_b64_to_cell(config_boc);
if (config_params_cell.is_error()) {
LOG(ERROR) << "Can't deserialize config params boc: " << config_params_cell.move_as_error();
return false;
}
auto global_config = std::make_shared<block::Config>(config_params_cell.move_as_ok(), td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc);
auto unpack_res = global_config->unpack();
if (unpack_res.is_error()) {
LOG(ERROR) << "Can't unpack config params";
return false;
}
auto rand_seed_hex_slice = td::Slice(rand_seed_hex);
if (rand_seed_hex_slice.size() != 64) {
LOG(ERROR) << "Rand seed expected as 64 characters hex string";
return false;
}
auto rand_seed_bytes = td::hex_decode(rand_seed_hex_slice);
if (rand_seed_bytes.is_error()) {
LOG(ERROR) << "Can't decode hex rand seed";
return false;
}
td::BitArray<256> rand_seed;
rand_seed.as_slice().copy_from(rand_seed_bytes.move_as_ok());
emulator->set_c7(std_address.move_as_ok(), unixtime, balance, rand_seed, std::const_pointer_cast<const block::Config>(global_config));
return true;
}
bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit) {
auto emulator = static_cast<emulator::TvmEmulator *>(tvm_emulator);
emulator->set_gas_limit(gas_limit);
return true;
}
const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const char *stack_boc) {
auto stack_cell = boc_b64_to_cell(stack_boc);
if (stack_cell.is_error()) {
ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack cell: " << stack_cell.move_as_error().to_string());
}
auto stack_cs = vm::load_cell_slice(stack_cell.move_as_ok());
td::Ref<vm::Stack> stack;
if (!vm::Stack::deserialize_to(stack_cs, stack)) {
ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack");
}
auto emulator = static_cast<emulator::TvmEmulator *>(tvm_emulator);
auto result = emulator->run_get_method(method_id, stack);
vm::CellBuilder stack_cb;
if (!result.stack->serialize(stack_cb)) {
ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack");
}
auto result_stack_boc = cell_to_boc_b64(stack_cb.finalize());
if (result_stack_boc.is_error()) {
ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack cell: " << result_stack_boc.move_as_error().to_string());
}
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("success", td::JsonTrue());
json_obj("stack", result_stack_boc.move_as_ok());
json_obj("gas_used", std::to_string(result.gas_used));
json_obj("vm_exit_code", result.code);
json_obj("vm_log", result.vm_log);
if (result.missing_library.is_null()) {
json_obj("missing_library", td::JsonNull());
} else {
json_obj("missing_library", td::Bits256(result.missing_library).to_hex());
}
json_obj.leave();
return strdup(jb.string_builder().as_cslice().c_str());
}
const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *message_body_boc) {
auto message_body_cell = boc_b64_to_cell(message_body_boc);
if (message_body_cell.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't deserialize message body boc: " << message_body_cell.move_as_error());
}
auto emulator = static_cast<emulator::TvmEmulator *>(tvm_emulator);
auto result = emulator->send_external_message(message_body_cell.move_as_ok());
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("success", td::JsonTrue());
json_obj("gas_used", std::to_string(result.gas_used));
json_obj("vm_exit_code", result.code);
json_obj("accepted", td::JsonBool(result.accepted));
json_obj("vm_log", result.vm_log);
if (result.missing_library.is_null()) {
json_obj("missing_library", td::JsonNull());
} else {
json_obj("missing_library", td::Bits256(result.missing_library).to_hex());
}
json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok());
json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok());
json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok());
json_obj.leave();
return strdup(jb.string_builder().as_cslice().c_str());
}
const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *message_body_boc, uint64_t amount) {
auto message_body_cell = boc_b64_to_cell(message_body_boc);
if (message_body_cell.is_error()) {
ERROR_RESPONSE(PSTRING() << "Can't deserialize message body boc: " << message_body_cell.move_as_error());
}
auto emulator = static_cast<emulator::TvmEmulator *>(tvm_emulator);
auto result = emulator->send_internal_message(message_body_cell.move_as_ok(), amount);
td::JsonBuilder jb;
auto json_obj = jb.enter_object();
json_obj("success", td::JsonTrue());
json_obj("gas_used", std::to_string(result.gas_used));
json_obj("vm_exit_code", result.code);
json_obj("accepted", td::JsonBool(result.accepted));
json_obj("vm_log", result.vm_log);
if (result.missing_library.is_null()) {
json_obj("missing_library", td::JsonNull());
} else {
json_obj("missing_library", td::Bits256(result.missing_library).to_hex());
}
json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok());
json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok());
json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok());
json_obj.leave();
return strdup(jb.string_builder().as_cslice().c_str());
}
void tvm_emulator_destroy(void *tvm_emulator) {
delete static_cast<emulator::TvmEmulator *>(tvm_emulator);
}

216
emulator/emulator-extern.h Normal file
View file

@ -0,0 +1,216 @@
#pragma once
#include <stdint.h>
#include "emulator_export.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Creates TransactionEmulator object
* @param config_params_boc Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell)
* @param vm_log_verbosity Verbosity level of VM log. 0 - log truncated to last 256 characters. 1 - unlimited length log.
* 2 - for each command prints its cell hash and offset. 3 - for each command log prints all stack values.
* @return Pointer to TransactionEmulator or nullptr in case of error
*/
EMULATOR_EXPORT void *transaction_emulator_create(const char *config_params_boc, int vm_log_verbosity);
/**
* @brief Set unixtime for emulation
* @param transaction_emulator Pointer to TransactionEmulator object
* @param unixtime Unix timestamp
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime);
/**
* @brief Set lt for emulation
* @param transaction_emulator Pointer to TransactionEmulator object
* @param lt Logical time
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt);
/**
* @brief Set rand seed for emulation
* @param transaction_emulator Pointer to TransactionEmulator object
* @param rand_seed_hex Hex string of length 64
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool transaction_emulator_set_rand_seed(void *transaction_emulator, const char* rand_seed_hex);
/**
* @brief Set ignore_chksig flag for emulation
* @param transaction_emulator Pointer to TransactionEmulator object
* @param ignore_chksig Whether emulation should always succeed on CHKSIG operation
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool transaction_emulator_set_ignore_chksig(void *transaction_emulator, bool ignore_chksig);
/**
* @brief Set unixtime for emulation
* @param transaction_emulator Pointer to TransactionEmulator object
* @param config_boc Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell)
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool transaction_emulator_set_config(void *transaction_emulator, const char* config_boc);
/**
* @brief Set unixtime for emulation
* @param transaction_emulator Pointer to TransactionEmulator object
* @param libs_boc Base64 encoded BoC serialized shared libraries dictionary (HashmapE 256 ^Cell).
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool transaction_emulator_set_libs(void *transaction_emulator, const char* libs_boc);
/**
* @brief Emulate transaction
* @param transaction_emulator Pointer to TransactionEmulator object
* @param shard_account_boc Base64 encoded BoC serialized ShardAccount
* @param message_boc Base64 encoded BoC serialized inbound Message (internal or external)
* @return Json object with error:
* {
* "success": false,
* "error": "Error description"
* // and optional fields "vm_exit_code" and "vm_log" in case external message was not accepted.
* }
* Or success:
* {
* "success": true,
* "transaction": "Base64 encoded Transaction boc",
* "shard_account": "Base64 encoded new ShardAccount boc",
* "vm_log": "execute DUP...",
* "actions": "Base64 encoded compute phase actions boc (OutList n)"
* }
*/
EMULATOR_EXPORT const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc);
/**
* @brief Destroy TransactionEmulator object
* @param transaction_emulator Pointer to TransactionEmulator object
*/
EMULATOR_EXPORT void transaction_emulator_destroy(void *transaction_emulator);
/**
* @brief Set global verbosity level of the library
* @param verbosity_level New verbosity level (0 - never, 1 - error, 2 - warning, 3 - info, 4 - debug)
*/
EMULATOR_EXPORT bool emulator_set_verbosity_level(int verbosity_level);
/**
* @brief Create TVM emulator
* @param code_boc Base64 encoded BoC serialized smart contract code cell
* @param data_boc Base64 encoded BoC serialized smart contract data cell
* @param vm_log_verbosity Verbosity level of VM log
* @return Pointer to TVM emulator object
*/
EMULATOR_EXPORT void *tvm_emulator_create(const char *code_boc, const char *data_boc, int vm_log_verbosity);
/**
* @brief Set libraries for TVM emulator
* @param libs_boc Base64 encoded BoC serialized libraries dictionary (HashmapE 256 ^Cell).
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool tvm_emulator_set_libraries(void *tvm_emulator, const char *libs_boc);
/**
* @brief Set c7 parameters
* @param tvm_emulator Pointer to TVM emulator
* @param address Adress of smart contract
* @param unixtime Unix timestamp
* @param balance Smart contract balance
* @param rand_seed_hex Random seed as hex string of length 64
* @param config Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell)
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixtime, uint64_t balance, const char *rand_seed_hex, const char *config);
/**
* @brief Set TVM gas limit
* @param tvm_emulator Pointer to TVM emulator
* @param gas_limit Gas limit
* @return true in case of success, false in case of error
*/
EMULATOR_EXPORT bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit);
/**
* @brief Run get method
* @param tvm_emulator Pointer to TVM emulator
* @param method_id Integer method id
* @param stack_boc Base64 encoded BoC serialized stack (VmStack)
* @return Json object with error:
* {
* "success": false,
* "error": "Error description"
* }
* Or success:
* {
* "success": true
* "vm_log": "...",
* "vm_exit_code": 0,
* "stack": "Base64 encoded BoC serialized stack (VmStack)",
* "missing_library": null,
* "gas_used": 1212
* }
*/
EMULATOR_EXPORT const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const char *stack_boc);
/**
* @brief Send external message
* @param tvm_emulator Pointer to TVM emulator
* @param message_body_boc Base64 encoded BoC serialized message body cell.
* @return Json object with error:
* {
* "success": false,
* "error": "Error description"
* }
* Or success:
* {
* "success": true,
* "new_code": "Base64 boc decoded new code cell",
* "new_data": "Base64 boc decoded new data cell",
* "accepted": true,
* "vm_exit_code": 0,
* "vm_log": "...",
* "missing_library": null,
* "gas_used": 1212,
* "actions": "Base64 boc decoded actions cell of type (OutList n)"
* }
*/
EMULATOR_EXPORT const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *message_body_boc);
/**
* @brief Send internal message
* @param tvm_emulator Pointer to TVM emulator
* @param message_body_boc Base64 encoded BoC serialized message body cell.
* @param amount Amount of nanograms attached with internal message.
* @return Json object with error:
* {
* "success": false,
* "error": "Error description"
* }
* Or success:
* {
* "success": true,
* "new_code": "Base64 boc decoded new code cell",
* "new_data": "Base64 boc decoded new data cell",
* "accepted": true,
* "vm_exit_code": 0,
* "vm_log": "...",
* "missing_library": null,
* "gas_used": 1212,
* "actions": "Base64 boc decoded actions cell of type (OutList n)"
* }
*/
EMULATOR_EXPORT const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *message_body_boc, uint64_t amount);
/**
* @brief Destroy TVM emulator object
* @param tvm_emulator Pointer to TVM emulator object
*/
EMULATOR_EXPORT void tvm_emulator_destroy(void *tvm_emulator);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -0,0 +1,17 @@
_transaction_emulator_create
_transaction_emulator_set_lt
_transaction_emulator_set_rand_seed
_transaction_emulator_set_ignore_chksig
_transaction_emulator_set_config
_transaction_emulator_set_libs
_transaction_emulator_emulate_transaction
_transaction_emulator_destroy
_emulator_set_verbosity_level
_tvm_emulator_create
_tvm_emulator_set_libraries
_tvm_emulator_set_c7
_tvm_emulator_set_gas_limit
_tvm_emulator_run_get_method
_tvm_emulator_send_external_message
_tvm_emulator_send_internal_message
_tvm_emulator_destroy

View file

@ -0,0 +1,254 @@
#include <string>
#include "transaction-emulator.h"
#include "crypto/common/refcnt.hpp"
#include "vm/cp0.h"
using td::Ref;
using namespace std::string_literals;
namespace emulator {
td::Result<std::unique_ptr<TransactionEmulator::EmulationResult>> TransactionEmulator::emulate_transaction(
block::Account&& account, td::Ref<vm::Cell> msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type) {
td::Ref<vm::Cell> old_mparams;
std::vector<block::StoragePrices> storage_prices;
block::StoragePhaseConfig storage_phase_cfg{&storage_prices};
block::ComputePhaseConfig compute_phase_cfg;
block::ActionPhaseConfig action_phase_cfg;
td::RefInt256 masterchain_create_fee, basechain_create_fee;
if (!utime) {
utime = unixtime_;
}
if (!utime) {
utime = (unsigned)std::time(nullptr);
}
auto fetch_res = block::FetchConfigParams::fetch_config_params(config_, &old_mparams,
&storage_prices, &storage_phase_cfg,
&rand_seed_, &compute_phase_cfg,
&action_phase_cfg, &masterchain_create_fee,
&basechain_create_fee, account.workchain, utime);
if(fetch_res.is_error()) {
return fetch_res.move_as_error_prefix("cannot fetch config params ");
}
vm::init_op_cp0();
if (!lt) {
lt = lt_;
}
if (!lt) {
lt = (account.last_trans_lt_ / block::ConfigInfo::get_lt_align() + 1) * block::ConfigInfo::get_lt_align(); // next block after account_.last_trans_lt_
}
compute_phase_cfg.libraries = std::make_unique<vm::Dictionary>(libraries_);
compute_phase_cfg.ignore_chksig = ignore_chksig_;
compute_phase_cfg.with_vm_log = true;
compute_phase_cfg.vm_log_verbosity = vm_log_verbosity_;
auto res = create_transaction(msg_root, &account, utime, lt, trans_type,
&storage_phase_cfg, &compute_phase_cfg,
&action_phase_cfg);
if(res.is_error()) {
return res.move_as_error_prefix("cannot run message on account ");
}
std::unique_ptr<block::transaction::Transaction> trans = res.move_as_ok();
if (!trans->compute_phase->accepted && trans->in_msg_extern) {
auto vm_log = trans->compute_phase->vm_log;
auto vm_exit_code = trans->compute_phase->exit_code;
return std::make_unique<TransactionEmulator::EmulationExternalNotAccepted>(std::move(vm_log), vm_exit_code);
}
if (!trans->serialize()) {
return td::Status::Error(-669,"cannot serialize new transaction for smart contract "s + trans->account.addr.to_hex());
}
auto trans_root = trans->commit(account);
if (trans_root.is_null()) {
return td::Status::Error(PSLICE() << "cannot commit new transaction for smart contract");
}
return std::make_unique<TransactionEmulator::EmulationSuccess>(std::move(trans_root), std::move(account), std::move(trans->compute_phase->vm_log), std::move(trans->compute_phase->actions));
}
td::Result<TransactionEmulator::EmulationSuccess> TransactionEmulator::emulate_transaction(block::Account&& account, td::Ref<vm::Cell> original_trans) {
block::gen::Transaction::Record record_trans;
if (!tlb::unpack_cell(original_trans, record_trans)) {
return td::Status::Error("Failed to unpack Transaction");
}
ton::LogicalTime lt = record_trans.lt;
ton::UnixTime utime = record_trans.now;
account.now_ = utime;
td::Ref<vm::Cell> msg_root = record_trans.r1.in_msg->prefetch_ref();
int tag = block::gen::t_TransactionDescr.get_tag(vm::load_cell_slice(record_trans.description));
int trans_type = block::transaction::Transaction::tr_none;
switch (tag) {
case block::gen::TransactionDescr::trans_ord: {
trans_type = block::transaction::Transaction::tr_ord;
break;
}
case block::gen::TransactionDescr::trans_storage: {
trans_type = block::transaction::Transaction::tr_storage;
break;
}
case block::gen::TransactionDescr::trans_tick_tock: {
block::gen::TransactionDescr::Record_trans_tick_tock tick_tock;
if (!tlb::unpack_cell(record_trans.description, tick_tock)) {
return td::Status::Error("Failed to unpack tick tock transaction description");
}
trans_type = tick_tock.is_tock ? block::transaction::Transaction::tr_tock : block::transaction::Transaction::tr_tick;
break;
}
case block::gen::TransactionDescr::trans_split_prepare: {
trans_type = block::transaction::Transaction::tr_split_prepare;
break;
}
case block::gen::TransactionDescr::trans_split_install: {
trans_type = block::transaction::Transaction::tr_split_install;
break;
}
case block::gen::TransactionDescr::trans_merge_prepare: {
trans_type = block::transaction::Transaction::tr_merge_prepare;
break;
}
case block::gen::TransactionDescr::trans_merge_install: {
trans_type = block::transaction::Transaction::tr_merge_install;
break;
}
}
TRY_RESULT(emulation, emulate_transaction(std::move(account), msg_root, utime, lt, trans_type));
auto emulation_result = dynamic_cast<EmulationSuccess&>(*emulation);
if (td::Bits256(emulation_result.transaction->get_hash().bits()) != td::Bits256(original_trans->get_hash().bits())) {
return td::Status::Error("transaction hash mismatch");
}
if (!check_state_update(emulation_result.account, record_trans)) {
return td::Status::Error("account hash mismatch");
}
return emulation_result;
}
td::Result<TransactionEmulator::EmulationChain> TransactionEmulator::emulate_transactions_chain(block::Account&& account, std::vector<td::Ref<vm::Cell>>&& original_transactions) {
std::vector<td::Ref<vm::Cell>> emulated_transactions;
for (const auto& original_trans : original_transactions) {
if (original_trans.is_null()) {
continue;
}
TRY_RESULT(emulation_result, emulate_transaction(std::move(account), original_trans));
emulated_transactions.push_back(std::move(emulation_result.transaction));
account = std::move(emulation_result.account);
}
return TransactionEmulator::EmulationChain{ std::move(emulated_transactions), std::move(account) };
}
bool TransactionEmulator::check_state_update(const block::Account& account, const block::gen::Transaction::Record& trans) {
block::gen::HASH_UPDATE::Record hash_update;
return tlb::type_unpack_cell(trans.state_update, block::gen::t_HASH_UPDATE_Account, hash_update) &&
hash_update.new_hash == account.total_state->get_hash().bits();
}
td::Result<std::unique_ptr<block::transaction::Transaction>> TransactionEmulator::create_transaction(
td::Ref<vm::Cell> msg_root, block::Account* acc,
ton::UnixTime utime, ton::LogicalTime lt, int trans_type,
block::StoragePhaseConfig* storage_phase_cfg,
block::ComputePhaseConfig* compute_phase_cfg,
block::ActionPhaseConfig* action_phase_cfg) {
bool external{false}, ihr_delivered{false}, need_credit_phase{false};
if (msg_root.not_null()) {
auto cs = vm::load_cell_slice(msg_root);
external = block::gen::t_CommonMsgInfo.get_tag(cs);
}
if (trans_type == block::transaction::Transaction::tr_ord) {
need_credit_phase = !external;
} else if (trans_type == block::transaction::Transaction::tr_merge_install) {
need_credit_phase = true;
}
std::unique_ptr<block::transaction::Transaction> trans =
std::make_unique<block::transaction::Transaction>(*acc, trans_type, lt, utime, msg_root);
if (msg_root.not_null() && !trans->unpack_input_msg(ihr_delivered, action_phase_cfg)) {
if (external) {
// inbound external message was not accepted
return td::Status::Error(-701,"inbound external message rejected by account "s + acc->addr.to_hex() +
" before smart-contract execution");
}
return td::Status::Error(-669,"cannot unpack input message for a new transaction");
}
if (trans->bounce_enabled) {
if (!trans->prepare_storage_phase(*storage_phase_cfg, true)) {
return td::Status::Error(-669,"cannot create storage phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
if (need_credit_phase && !trans->prepare_credit_phase()) {
return td::Status::Error(-669,"cannot create credit phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
} else {
if (need_credit_phase && !trans->prepare_credit_phase()) {
return td::Status::Error(-669,"cannot create credit phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
if (!trans->prepare_storage_phase(*storage_phase_cfg, true, need_credit_phase)) {
return td::Status::Error(-669,"cannot create storage phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
}
if (!trans->prepare_compute_phase(*compute_phase_cfg)) {
return td::Status::Error(-669,"cannot create compute phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
if (!trans->compute_phase->accepted) {
if (!external && trans->compute_phase->skip_reason == block::ComputePhase::sk_none) {
return td::Status::Error(-669,"new ordinary transaction for smart contract "s + acc->addr.to_hex() +
" has not been accepted by the smart contract (?)");
}
}
if (trans->compute_phase->success && !trans->prepare_action_phase(*action_phase_cfg)) {
return td::Status::Error(-669,"cannot create action phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
if (trans->bounce_enabled && !trans->compute_phase->success && !trans->prepare_bounce_phase(*action_phase_cfg)) {
return td::Status::Error(-669,"cannot create bounce phase of a new transaction for smart contract "s + acc->addr.to_hex());
}
return trans;
}
void TransactionEmulator::set_unixtime(ton::UnixTime unixtime) {
unixtime_ = unixtime;
}
void TransactionEmulator::set_lt(ton::LogicalTime lt) {
lt_ = lt;
}
void TransactionEmulator::set_rand_seed(td::BitArray<256>& rand_seed) {
rand_seed_ = rand_seed;
}
void TransactionEmulator::set_ignore_chksig(bool ignore_chksig) {
ignore_chksig_ = ignore_chksig;
}
void TransactionEmulator::set_config(block::Config &&config) {
config_ = std::forward<block::Config>(config);
}
void TransactionEmulator::set_libs(vm::Dictionary &&libs) {
libraries_ = std::forward<vm::Dictionary>(libs);
}
} // namespace emulator

View file

@ -0,0 +1,83 @@
#pragma once
#include "crypto/common/refcnt.hpp"
#include "ton/ton-types.h"
#include "crypto/vm/cells.h"
#include "block/transaction.h"
#include "block/block-auto.h"
#include "block/block-parse.h"
#include "block/mc-config.h"
namespace emulator {
class TransactionEmulator {
block::Config config_;
vm::Dictionary libraries_;
int vm_log_verbosity_;
ton::UnixTime unixtime_;
ton::LogicalTime lt_;
td::BitArray<256> rand_seed_;
bool ignore_chksig_;
public:
TransactionEmulator(block::Config&& config, int vm_log_verbosity = 0) :
config_(std::move(config)), libraries_(256), vm_log_verbosity_(vm_log_verbosity),
unixtime_(0), lt_(0), rand_seed_(0), ignore_chksig_(false) {
}
struct EmulationResult {
std::string vm_log;
EmulationResult(std::string vm_log_) : vm_log(vm_log_) {}
virtual ~EmulationResult() = default;
};
struct EmulationSuccess: EmulationResult {
td::Ref<vm::Cell> transaction;
block::Account account;
td::Ref<vm::Cell> actions;
EmulationSuccess(td::Ref<vm::Cell> transaction_, block::Account account_, std::string vm_log_, td::Ref<vm::Cell> actions_) :
EmulationResult(vm_log_), transaction(transaction_), account(account_) , actions(actions_)
{}
};
struct EmulationExternalNotAccepted: EmulationResult {
int vm_exit_code;
EmulationExternalNotAccepted(std::string vm_log_, int vm_exit_code_) :
EmulationResult(vm_log_), vm_exit_code(vm_exit_code_)
{}
};
struct EmulationChain {
std::vector<td::Ref<vm::Cell>> transactions;
block::Account account;
};
const block::Config& get_config() {
return config_;
}
td::Result<std::unique_ptr<EmulationResult>> emulate_transaction(
block::Account&& account, td::Ref<vm::Cell> msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type);
td::Result<EmulationSuccess> emulate_transaction(block::Account&& account, td::Ref<vm::Cell> original_trans);
td::Result<EmulationChain> emulate_transactions_chain(block::Account&& account, std::vector<td::Ref<vm::Cell>>&& original_transactions);
void set_unixtime(ton::UnixTime unixtime);
void set_lt(ton::LogicalTime lt);
void set_rand_seed(td::BitArray<256>& rand_seed);
void set_ignore_chksig(bool ignore_chksig);
void set_config(block::Config &&config);
void set_libs(vm::Dictionary &&libs);
private:
bool check_state_update(const block::Account& account, const block::gen::Transaction::Record& trans);
td::Result<std::unique_ptr<block::transaction::Transaction>> create_transaction(
td::Ref<vm::Cell> msg_root, block::Account* acc,
ton::UnixTime utime, ton::LogicalTime lt, int trans_type,
block::StoragePhaseConfig* storage_phase_cfg,
block::ComputePhaseConfig* compute_phase_cfg,
block::ActionPhaseConfig* action_phase_cfg);
};
} // namespace emulator

46
emulator/tvm-emulator.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "smc-envelope/SmartContract.h"
namespace emulator {
class TvmEmulator {
ton::SmartContract smc_;
ton::SmartContract::Args args_;
public:
using Answer = ton::SmartContract::Answer;
TvmEmulator(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data): smc_({code, data}) {
}
void set_vm_verbosity_level(int vm_log_verbosity) {
args_.set_vm_verbosity_level(vm_log_verbosity);
}
void set_libraries(vm::Dictionary&& libraries) {
args_.set_libraries(libraries);
}
void set_gas_limit(int64_t limit) {
args_.set_limits(vm::GasLimits(limit));
}
void set_c7(block::StdAddress address, uint32_t unixtime, uint64_t balance, td::BitArray<256> rand_seed, std::shared_ptr<const block::Config> config) {
args_.set_address(address);
args_.set_now(unixtime);
args_.set_balance(balance);
args_.set_rand_seed(rand_seed);
args_.set_config(config);
}
Answer run_get_method(int method_id, td::Ref<vm::Stack> stack) {
return smc_.run_get_method(args_.set_stack(stack).set_method_id(method_id));
}
Answer send_external_message(td::Ref<vm::Cell> message_body) {
return smc_.send_external_message(message_body, args_);
}
Answer send_internal_message(td::Ref<vm::Cell> message_body, uint64_t amount) {
return smc_.send_internal_message(message_body, args_.set_amount(amount));
}
};
}