From e7e57f8e6dac998039d1cfbd910320f3afa15959 Mon Sep 17 00:00:00 2001 From: Marat <98183742+dungeon-master-666@users.noreply.github.com> Date: Sun, 26 Jan 2025 14:39:05 +0100 Subject: [PATCH] add extra currencies support to emulator (#1494) --- emulator/emulator-emscripten.cpp | 31 ++++++++++++++++-- emulator/emulator-extern.cpp | 53 ++++++++++++++++++++++++++++++ emulator/emulator-extern.h | 8 +++++ emulator/test/emulator-tests.cpp | 55 ++++++++++++++++++++++++++++++++ emulator/tvm-emulator.hpp | 4 +++ 5 files changed, 149 insertions(+), 2 deletions(-) diff --git a/emulator/emulator-emscripten.cpp b/emulator/emulator-emscripten.cpp index 17639d280..e5d4e42d7 100644 --- a/emulator/emulator-emscripten.cpp +++ b/emulator/emulator-emscripten.cpp @@ -65,6 +65,7 @@ struct GetMethodParams { std::string address; uint32_t unixtime; uint64_t balance; + std::string extra_currencies; std::string rand_seed_hex; int64_t gas_limit; int method_id; @@ -108,6 +109,32 @@ td::Result decode_get_method_params(const char* json) { TRY_RESULT(balance, td::to_integer_safe(balance_field.get_string())); params.balance = balance; + TRY_RESULT(ec_field, td::get_json_object_field(obj, "extra_currencies", td::JsonValue::Type::Object, true)); + if (ec_field.type() != td::JsonValue::Type::Null) { + if (ec_field.type() != td::JsonValue::Type::Object) { + return td::Status::Error("EC must be of type Object"); + } + td::StringBuilder ec_builder; + auto ec_obj = ec_field.get_object(); + bool is_first = true; + for (auto &field_value : ec_obj) { + auto currency_id = field_value.first; + if (field_value.second.type() != td::JsonValue::Type::String) { + return td::Status::Error(PSLICE() << "EC amount must be of type String"); + } + auto amount = field_value.second.get_string(); + if (!is_first) { + ec_builder << " "; + is_first = false; + } + ec_builder << currency_id << "=" << amount; + } + if (ec_builder.is_error()) { + return td::Status::Error(PSLICE() << "Error building extra currencies string"); + } + params.extra_currencies = ec_builder.as_cslice().str(); + } + TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", false)); params.rand_seed_hex = rand_seed_str; @@ -228,8 +255,8 @@ const char *run_get_method(const char *params, const char* stack, const char* co 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.prev_blocks_info && - !tvm_emulator_set_prev_blocks_info(tvm, decoded_params.prev_blocks_info.value().c_str())) || + (decoded_params.extra_currencies.size() > 0 && !tvm_emulator_set_extra_currencies(tvm, decoded_params.extra_currencies.c_str())) || + (decoded_params.prev_blocks_info && !tvm_emulator_set_prev_blocks_info(tvm, decoded_params.prev_blocks_info.value().c_str())) || (decoded_params.gas_limit > 0 && !tvm_emulator_set_gas_limit(tvm, decoded_params.gas_limit)) || !tvm_emulator_set_debug_enabled(tvm, decoded_params.debug_enabled)) { tvm_emulator_destroy(tvm); diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp index 4e5f17bf7..eb5ff9f9e 100644 --- a/emulator/emulator-extern.cpp +++ b/emulator/emulator-extern.cpp @@ -496,6 +496,59 @@ bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixt return true; } +bool tvm_emulator_set_extra_currencies(void *tvm_emulator, const char *extra_currencies) { + auto emulator = static_cast(tvm_emulator); + vm::Dictionary dict{32}; + td::Slice extra_currencies_str{extra_currencies}; + while (true) { + auto next_space_pos = extra_currencies_str.find(' '); + auto currency_id_amount = next_space_pos == td::Slice::npos ? + extra_currencies_str.substr(0) : extra_currencies_str.substr(0, next_space_pos); + + if (!currency_id_amount.empty()) { + auto delim_pos = currency_id_amount.find('='); + if (delim_pos == td::Slice::npos) { + LOG(ERROR) << "Invalid extra currency format, missing '='"; + return false; + } + + auto currency_id_str = currency_id_amount.substr(0, delim_pos); + auto amount_str = currency_id_amount.substr(delim_pos + 1); + + auto currency_id = td::to_integer_safe(currency_id_str); + if (currency_id.is_error()) { + LOG(ERROR) << "Invalid extra currency id: " << currency_id_str; + return false; + } + auto amount = td::dec_string_to_int256(amount_str); + if (amount.is_null()) { + LOG(ERROR) << "Invalid extra currency amount: " << amount_str; + return false; + } + if (amount == 0) { + continue; + } + if (amount < 0) { + LOG(ERROR) << "Negative extra currency amount: " << amount_str; + return false; + } + + vm::CellBuilder cb; + block::tlb::t_VarUInteger_32.store_integer_value(cb, *amount); + if (!dict.set_builder(td::BitArray<32>(currency_id.ok()), cb, vm::DictionaryBase::SetMode::Add)) { + LOG(ERROR) << "Duplicate extra currency id"; + return false; + } + } + if (next_space_pos == td::Slice::npos) { + break; + } + extra_currencies_str.remove_prefix(next_space_pos + 1); + } + emulator->set_extra_currencies(std::move(dict).extract_root_cell()); + return true; +} + bool tvm_emulator_set_config_object(void* tvm_emulator, void* config) { auto emulator = static_cast(tvm_emulator); auto global_config = std::shared_ptr(static_cast(config), config_deleter); diff --git a/emulator/emulator-extern.h b/emulator/emulator-extern.h index e69a9cb0b..14879e1ec 100644 --- a/emulator/emulator-extern.h +++ b/emulator/emulator-extern.h @@ -182,6 +182,14 @@ EMULATOR_EXPORT bool tvm_emulator_set_libraries(void *tvm_emulator, const char * */ 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 extra currencies balance + * @param tvm_emulator Pointer to TVM emulator + * @param extra_currencies String with extra currencies balance in format "currency_id1=balance1 currency_id2=balance2 ..." + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_extra_currencies(void *tvm_emulator, const char *extra_currencies); + /** * @brief Set config for TVM emulator * @param tvm_emulator Pointer to TVM emulator diff --git a/emulator/test/emulator-tests.cpp b/emulator/test/emulator-tests.cpp index 24394b498..a0be447f5 100644 --- a/emulator/test/emulator-tests.cpp +++ b/emulator/test/emulator-tests.cpp @@ -400,3 +400,58 @@ TEST(Emulator, tvm_emulator) { CHECK(stack_res->depth() == 1); CHECK(stack_res.write().pop_int()->to_long() == init_data.seqno); } + +TEST(Emulator, tvm_emulator_extra_currencies) { + void *tvm_emulator = tvm_emulator_create("te6cckEBBAEAHgABFP8A9KQT9LzyyAsBAgFiAgMABtBfBAAJofpP8E8XmGlj", "te6cckEBAQEAAgAAAEysuc0=", 1); + std::string addr = "0:" + std::string(64, 'F'); + tvm_emulator_set_c7(tvm_emulator, addr.c_str(), 1337, 1000, std::string(64, 'F').c_str(), nullptr); + CHECK(tvm_emulator_set_extra_currencies(tvm_emulator, "100=20000 200=1")); + unsigned method_crc = td::crc16("get_balance"); + unsigned method_id = (method_crc & 0xffff) | 0x10000; + + auto stack = td::make_ref(); + vm::CellBuilder stack_cb; + CHECK(stack->serialize(stack_cb)); + auto stack_cell = stack_cb.finalize(); + auto stack_boc = td::base64_encode(std_boc_serialize(stack_cell).move_as_ok()); + + std::string tvm_res = tvm_emulator_run_get_method(tvm_emulator, method_id, stack_boc.c_str()); + + auto result_json = td::json_decode(td::MutableSlice(tvm_res)); + auto result = result_json.move_as_ok(); + auto& result_obj = result.get_object(); + + auto success_field = td::get_json_object_field(result_obj, "success", td::JsonValue::Type::Boolean, false); + auto success = success_field.move_as_ok().get_boolean(); + CHECK(success); + + auto stack_field = td::get_json_object_field(result_obj, "stack", td::JsonValue::Type::String, false); + auto stack_val = stack_field.move_as_ok(); + auto& stack_obj = stack_val.get_string(); + auto stack_res_boc = td::base64_decode(stack_obj); + auto stack_res_cell = vm::std_boc_deserialize(stack_res_boc.move_as_ok()); + td::Ref stack_res; + auto stack_res_cs = vm::load_cell_slice(stack_res_cell.move_as_ok()); + CHECK(vm::Stack::deserialize_to(stack_res_cs, stack_res)); + CHECK(stack_res->depth() == 1); + auto tuple = stack_res.write().pop_tuple(); + CHECK(tuple->size() == 2); + + auto ton_balance = tuple->at(0).as_int(); + CHECK(ton_balance == 1000); + + auto cell = tuple->at(1).as_cell(); + auto dict = vm::Dictionary{cell, 32}; + auto it = dict.begin(); + std::map ec_balance; + while (!it.eof()) { + auto id = td::BitArray<32>(it.cur_pos()).to_ulong(); + auto value_cs = it.cur_value(); + auto value = block::tlb::t_VarUInteger_32.as_integer(value_cs); + ec_balance[id] = value; + ++it; + } + CHECK(ec_balance.size() == 2); + CHECK(ec_balance[100] == 20000); + CHECK(ec_balance[200] == 1); +} diff --git a/emulator/tvm-emulator.hpp b/emulator/tvm-emulator.hpp index 413298c99..acc13627c 100644 --- a/emulator/tvm-emulator.hpp +++ b/emulator/tvm-emulator.hpp @@ -33,6 +33,10 @@ class TvmEmulator { } } + void set_extra_currencies(td::Ref extra_currencies) { + args_.set_extra_currencies(std::move(extra_currencies)); + } + void set_c7_raw(td::Ref c7) { args_.set_c7(std::move(c7)); }