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:
parent
adf67aa869
commit
3b3c25b654
32 changed files with 2095 additions and 158 deletions
55
emulator/CMakeLists.txt
Normal file
55
emulator/CMakeLists.txt
Normal 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
32
emulator/README.md
Normal 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
27
emulator/StringLog.h
Normal 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
|
189
emulator/emulator-emscripten.cpp
Normal file
189
emulator/emulator-emscripten.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
435
emulator/emulator-extern.cpp
Normal file
435
emulator/emulator-extern.cpp
Normal 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
216
emulator/emulator-extern.h
Normal 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
|
17
emulator/emulator_export_list
Normal file
17
emulator/emulator_export_list
Normal 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
|
254
emulator/transaction-emulator.cpp
Normal file
254
emulator/transaction-emulator.cpp
Normal 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
|
83
emulator/transaction-emulator.h
Normal file
83
emulator/transaction-emulator.h
Normal 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
46
emulator/tvm-emulator.hpp
Normal 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));
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue