diff --git a/emulator/emulator-emscripten.cpp b/emulator/emulator-emscripten.cpp index 881664bd..66e97f35 100644 --- a/emulator/emulator-emscripten.cpp +++ b/emulator/emulator-emscripten.cpp @@ -12,6 +12,8 @@ struct TransactionEmulationParams { uint64_t lt; td::optional rand_seed_hex; bool ignore_chksig; + bool is_tick_tock; + bool is_tock; bool debug_enabled; }; @@ -41,6 +43,16 @@ td::Result decode_transaction_emulation_params(const TRY_RESULT(debug_enabled, td::get_json_object_bool_field(obj, "debug_enabled", false)); params.debug_enabled = debug_enabled; + TRY_RESULT(is_tick_tock, td::get_json_object_bool_field(obj, "is_tick_tock", true, false)); + params.is_tick_tock = is_tick_tock; + + TRY_RESULT(is_tock, td::get_json_object_bool_field(obj, "is_tock", true, false)); + params.is_tock = is_tock; + + if (is_tock && !is_tick_tock) { + return td::Status::Error("Inconsistent parameters is_tick_tock=false, is_tock=true"); + } + return params; } @@ -137,7 +149,12 @@ const char *emulate(const char *config, const char* libs, int verbosity, const c return strdup(R"({"fail":true,"message":"Can't set params"})"); } - auto tx = transaction_emulator_emulate_transaction(em, account, message); + const char *result; + if (decoded_params.is_tick_tock) { + result = transaction_emulator_emulate_tick_tock_transaction(em, account, decoded_params.is_tock); + } else { + result = transaction_emulator_emulate_transaction(em, account, message); + } transaction_emulator_destroy(em); @@ -145,12 +162,12 @@ const char *emulate(const char *config, const char* libs, int verbosity, const c { td::JsonBuilder jb; auto json_obj = jb.enter_object(); - json_obj("output", td::JsonRaw(td::Slice(tx))); + json_obj("output", td::JsonRaw(td::Slice(result))); json_obj("logs", logger.get_string()); json_obj.leave(); output = strdup(jb.string_builder().as_cslice().c_str()); } - free((void*) tx); + free((void*) result); return output; } diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp index 31a7b649..c5717dcd 100644 --- a/emulator/emulator-extern.cpp +++ b/emulator/emulator-extern.cpp @@ -8,6 +8,7 @@ #include "transaction-emulator.h" #include "tvm-emulator.hpp" #include "crypto/vm/stack.hpp" +#include "crypto/vm/memo.h" td::Result> boc_b64_to_cell(const char *boc) { TRY_RESULT_PREFIX(boc_decoded, td::base64_decode(td::Slice(boc)), "Can't decode base64 boc: "); @@ -42,6 +43,7 @@ const char *error_response(std::string&& error) { auto json_obj = jb.enter_object(); json_obj("success", td::JsonFalse()); json_obj("error", std::move(error)); + json_obj("external_not_accepted", td::JsonFalse()); json_obj.leave(); return strdup(jb.string_builder().as_cslice().c_str()); } @@ -51,6 +53,7 @@ const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_cod auto json_obj = jb.enter_object(); json_obj("success", td::JsonFalse()); json_obj("error", "External message not accepted by smart contract"); + json_obj("external_not_accepted", td::JsonTrue()); json_obj("vm_log", std::move(vm_log)); json_obj("vm_exit_code", vm_exit_code); json_obj("elapsed_time", elapsed_time); @@ -131,13 +134,16 @@ const char *transaction_emulator_emulate_transaction(void *transaction_emulator, } auto account = block::Account(wc, addr.bits()); - ton::UnixTime now = (unsigned)std::time(nullptr); + ton::UnixTime now = emulator->get_unixtime(); + if (!now) { + 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(), 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); + auto result = emulator->emulate_transaction(std::move(account), message_cell, now, 0, block::transaction::Transaction::tr_ord); if (result.is_error()) { ERROR_RESPONSE(PSTRING() << "Emulate transaction failed: " << result.move_as_error()); } @@ -176,6 +182,79 @@ const char *transaction_emulator_emulate_transaction(void *transaction_emulator, std::move(actions_boc_b64), emulation_success.elapsed_time); } +const char *transaction_emulator_emulate_tick_tock_transaction(void *transaction_emulator, const char *shard_account_boc, bool is_tock) { + auto emulator = static_cast(transaction_emulator); + + 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 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) { + ERROR_RESPONSE(PSTRING() << "Can't run tick/tock transaction on account_none"); + } + 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 = emulator->get_unixtime(); + if (!now) { + 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(), now, is_special)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack shard account"); + } + + auto trans_type = is_tock ? block::transaction::Transaction::tr_tock : block::transaction::Transaction::tr_tick; + auto result = emulator->emulate_transaction(std::move(account), {}, now, 0, trans_type); + if (result.is_error()) { + ERROR_RESPONSE(PSTRING() << "Emulate transaction failed: " << result.move_as_error()); + } + auto emulation_result = result.move_as_ok(); + + auto emulation_success = dynamic_cast(*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 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), emulation_success.elapsed_time); +} + bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime) { auto emulator = static_cast(transaction_emulator); @@ -309,16 +388,19 @@ bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixt 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(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; + std::shared_ptr global_config; + if (config_boc != nullptr) { + 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; + } + global_config = std::make_shared(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); @@ -365,6 +447,9 @@ const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const auto emulator = static_cast(tvm_emulator); auto result = emulator->run_get_method(method_id, stack); + vm::FakeVmStateLimits fstate(1000); // limit recursive (de)serialization calls + vm::VmStateInterface::Guard guard(&fstate); + vm::CellBuilder stack_cb; if (!result.stack->serialize(stack_cb)) { ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack"); diff --git a/emulator/emulator-extern.h b/emulator/emulator-extern.h index db59a822..c2b0bf9a 100644 --- a/emulator/emulator-extern.h +++ b/emulator/emulator-extern.h @@ -80,8 +80,9 @@ EMULATOR_EXPORT bool transaction_emulator_set_debug_enabled(void *transaction_em * @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. + * "error": "Error description", + * "external_not_accepted": false, + * // and optional fields "vm_exit_code", "vm_log", "elapsed_time" in case external message was not accepted. * } * Or success: * { @@ -89,11 +90,35 @@ EMULATOR_EXPORT bool transaction_emulator_set_debug_enabled(void *transaction_em * "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)" + * "actions": "Base64 encoded compute phase actions boc (OutList n)", + * "elapsed_time": 0.02 * } */ EMULATOR_EXPORT const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc); +/** + * @brief Emulate tick tock transaction + * @param transaction_emulator Pointer to TransactionEmulator object + * @param shard_account_boc Base64 encoded BoC serialized ShardAccount of special account + * @param is_tock True for tock transactions, false for tick + * @return Json object with error: + * { + * "success": false, + * "error": "Error description", + * "external_not_accepted": false + * } + * 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)", + * "elapsed_time": 0.02 + * } + */ +EMULATOR_EXPORT const char *transaction_emulator_emulate_tick_tock_transaction(void *transaction_emulator, const char *shard_account_boc, bool is_tock); + /** * @brief Destroy TransactionEmulator object * @param transaction_emulator Pointer to TransactionEmulator object @@ -129,7 +154,7 @@ EMULATOR_EXPORT bool tvm_emulator_set_libraries(void *tvm_emulator, const char * * @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) + * @param config Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell). Optional. * @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); diff --git a/emulator/emulator_export_list b/emulator/emulator_export_list index 64d4ff59..950c57a0 100644 --- a/emulator/emulator_export_list +++ b/emulator/emulator_export_list @@ -7,6 +7,7 @@ _transaction_emulator_set_config _transaction_emulator_set_libs _transaction_emulator_set_debug_enabled _transaction_emulator_emulate_transaction +_transaction_emulator_emulate_tick_tock_transaction _transaction_emulator_destroy _emulator_set_verbosity_level _tvm_emulator_create diff --git a/emulator/transaction-emulator.h b/emulator/transaction-emulator.h index 08343cb8..b26f4e8a 100644 --- a/emulator/transaction-emulator.h +++ b/emulator/transaction-emulator.h @@ -59,6 +59,10 @@ public: return config_; } + ton::UnixTime get_unixtime() { + return unixtime_; + } + td::Result> emulate_transaction( block::Account&& account, td::Ref msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type); diff --git a/emulator/tvm-emulator.hpp b/emulator/tvm-emulator.hpp index 0236f0ca..46e2538d 100644 --- a/emulator/tvm-emulator.hpp +++ b/emulator/tvm-emulator.hpp @@ -28,7 +28,9 @@ public: args_.set_now(unixtime); args_.set_balance(balance); args_.set_rand_seed(rand_seed); - args_.set_config(config); + if (config) { + args_.set_config(config); + } } void set_debug_enabled(bool debug_enabled) {