From 9d0569645231b8f764635883ff344aad27786682 Mon Sep 17 00:00:00 2001 From: EmelyanenkoK Date: Tue, 5 Mar 2024 16:54:49 +0300 Subject: [PATCH] Add infrastructure for precompiled smartcontracts (#927) * Utils for writing precompiled contracts * Precompiled contracts in config, override gas_usage for them * Add base class for precompiled contracts * Improve utils for precompiled smc * Implement GETPRECOMPILEDGAS * Enable precompiles by flag * Process null data in PrecompiledSmartContract * Fix ton_block wasm build * Fix vm::util::store_(u)long --------- Co-authored-by: SpyCheese --- crypto/CMakeLists.txt | 7 + crypto/block/block.tlb | 4 + crypto/block/mc-config.cpp | 27 ++ crypto/block/mc-config.h | 10 + .../PrecompiledSmartContract.cpp | 170 +++++++++++ .../PrecompiledSmartContract.h | 122 ++++++++ crypto/block/precompiled-smc/common.h | 21 ++ crypto/block/transaction.cpp | 115 +++++++- crypto/block/transaction.h | 5 + crypto/smc-envelope/SmartContract.cpp | 1 + crypto/vm/arithops.cpp | 22 ++ crypto/vm/arithops.h | 10 + crypto/vm/cellops.cpp | 189 ++++++++++++ crypto/vm/cellops.h | 40 ++- crypto/vm/memo.cpp | 15 + crypto/vm/memo.h | 20 ++ crypto/vm/tonops.cpp | 269 ++++++++++++------ crypto/vm/tonops.h | 29 ++ doc/GlobalVersions.md | 5 +- validator-engine/validator-engine.cpp | 4 + validator/impl/validate-query.cpp | 1 + 21 files changed, 991 insertions(+), 95 deletions(-) create mode 100644 crypto/block/precompiled-smc/PrecompiledSmartContract.cpp create mode 100644 crypto/block/precompiled-smc/PrecompiledSmartContract.h create mode 100644 crypto/block/precompiled-smc/common.h diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 827c903b6..29b954664 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -213,6 +213,7 @@ set(BLOCK_SOURCE block/mc-config.cpp block/output-queue-merger.cpp block/transaction.cpp + block/precompiled-smc/PrecompiledSmartContract.cpp ${TLB_BLOCK_AUTO} block/block-binlog.h @@ -223,6 +224,8 @@ set(BLOCK_SOURCE block/check-proof.h block/output-queue-merger.h block/transaction.h + block/precompiled-smc/PrecompiledSmartContract.h + block/precompiled-smc/common.h ) set(SMC_ENVELOPE_SOURCE @@ -376,6 +379,10 @@ add_library(ton_block STATIC ${BLOCK_SOURCE}) target_include_directories(ton_block PUBLIC $ $ $) target_link_libraries(ton_block PUBLIC ton_crypto tdutils tdactor tl_api) +if (USE_EMSCRIPTEN) + target_link_options(ton_block PRIVATE -fexceptions) + target_compile_options(ton_block PRIVATE -fexceptions) +endif() add_executable(func func/func-main.cpp ${FUNC_LIB_SOURCE}) target_include_directories(func PUBLIC $) diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index 4b36f13ba..3ae542399 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -788,6 +788,10 @@ _ SizeLimitsConfig = ConfigParam 43; suspended_address_list#00 addresses:(HashmapE 288 Unit) suspended_until:uint32 = SuspendedAddressList; _ SuspendedAddressList = ConfigParam 44; +precompiled_smc#b0 gas_usage:uint64 = PrecompiledSmc; +precompiled_contracts_config#c0 list:(HashmapE 256 PrecompiledSmc) = PrecompiledContractsConfig; +_ PrecompiledContractsConfig = ConfigParam 45; + oracle_bridge_params#_ bridge_address:bits256 oracle_mutlisig_address:bits256 oracles:(HashmapE 256 uint256) external_chain_address:bits256 = OracleBridgeParams; _ OracleBridgeParams = ConfigParam 71; // Ethereum bridge _ OracleBridgeParams = ConfigParam 72; // Binance Smart Chain bridge diff --git a/crypto/block/mc-config.cpp b/crypto/block/mc-config.cpp index 8b3871551..1dbfeaedc 100644 --- a/crypto/block/mc-config.cpp +++ b/crypto/block/mc-config.cpp @@ -2032,6 +2032,17 @@ td::Ref Config::get_unpacked_config_tuple(ton::UnixTime now) const { return td::make_cnt_ref>(std::move(tuple)); } +PrecompiledContractsConfig Config::get_precompiled_contracts_config() const { + PrecompiledContractsConfig c; + td::Ref param = get_config_param(45); + gen::PrecompiledContractsConfig::Record rec; + if (param.is_null() || !tlb::unpack_cell(param, rec)) { + return c; + } + c.list = vm::Dictionary{rec.list->prefetch_ref(), 256}; + return c; +} + td::Result> Config::unpack_validator_set_start_stop(Ref vset_root) { if (vset_root.is_null()) { return td::Status::Error("validator set absent"); @@ -2316,4 +2327,20 @@ td::Result> ConfigInfo::get_prev_blocks_info() const { block_id_to_tuple(last_key_block)); } +td::optional PrecompiledContractsConfig::get_contract( + td::Bits256 code_hash) const { + auto list_copy = list; + auto cs = list_copy.lookup(code_hash); + if (cs.is_null()) { + return {}; + } + gen::PrecompiledSmc::Record rec; + if (!tlb::csr_unpack(cs, rec)) { + return {}; + } + Contract c; + c.gas_usage = rec.gas_usage; + return c; +} + } // namespace block diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index 624e3e03d..2ca893995 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -526,6 +526,15 @@ struct BurningConfig { } }; +struct PrecompiledContractsConfig { + struct Contract { + td::uint64 gas_usage; + }; + vm::Dictionary list{256}; + + td::optional get_contract(td::Bits256 code_hash) const; +}; + class Config { enum { default_mc_catchain_lifetime = 200, @@ -644,6 +653,7 @@ class Config { std::unique_ptr get_suspended_addresses(ton::UnixTime now) const; BurningConfig get_burning_config() const; td::Ref get_unpacked_config_tuple(ton::UnixTime now) const; + PrecompiledContractsConfig get_precompiled_contracts_config() const; static std::vector do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf, ton::ShardIdFull shard, const block::ValidatorSet& vset, ton::UnixTime time, diff --git a/crypto/block/precompiled-smc/PrecompiledSmartContract.cpp b/crypto/block/precompiled-smc/PrecompiledSmartContract.cpp new file mode 100644 index 000000000..6797216df --- /dev/null +++ b/crypto/block/precompiled-smc/PrecompiledSmartContract.cpp @@ -0,0 +1,170 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#include "common.h" +#include +#include "vm/memo.h" + +namespace block::precompiled { + +using namespace vm; + +Result PrecompiledSmartContract::run(td::Ref my_address, ton::UnixTime now, ton::LogicalTime cur_lt, + CurrencyCollection balance, td::Ref c4, vm::CellSlice msg_body, + td::Ref msg, CurrencyCollection msg_balance, bool is_external, + std::vector> libraries, int global_version, + td::uint16 max_data_depth, td::Ref my_code, + td::Ref unpacked_config, td::RefInt256 due_payment, + td::uint64 precompiled_gas_usage) { + my_address_ = std::move(my_address); + now_ = now; + cur_lt_ = cur_lt; + balance_ = std::move(balance); + c4_ = (c4.not_null() ? std::move(c4) : CellBuilder().finalize()); + in_msg_body_ = std::move(msg_body); + in_msg_ = std::move(msg); + in_msg_balance_ = std::move(msg_balance); + is_external_ = is_external; + my_code_ = std::move(my_code); + unpacked_config_ = std::move(unpacked_config); + due_payment_ = std::move(due_payment); + precompiled_gas_usage_ = precompiled_gas_usage; + + vm::DummyVmState vm_state{std::move(libraries), global_version}; + vm::VmStateInterface::Guard guard{&vm_state}; + + Result result; + try { + result = do_run(); + } catch (vm::VmError &e) { + result = Result::error(e.get_errno(), e.get_arg()); + } catch (Result &r) { + result = std::move(r); + } + + if (result.exit_code != 0 && result.exit_code != 1) { + // see VmState::try_commit() + if (c4_.is_null() || c4_->get_depth() > max_data_depth || c4_->get_level() != 0 || c5_.is_null() || + c5_->get_depth() > max_data_depth || c5_->get_level() != 0) { + result = Result::error(Excno::cell_ov, 0); + } + } + return result; +} + +void PrecompiledSmartContract::send_raw_message(const td::Ref &msg, int mode) { + CellBuilder cb; + if (!(cb.store_ref_bool(c5_) // out_list$_ {n:#} prev:^(OutList n) + && cb.store_long_bool(0x0ec3c86d, 32) // action_send_msg#0ec3c86d + && cb.store_long_bool(mode, 8) // mode:(## 8) + && cb.store_ref_bool(msg))) { + throw VmError{Excno::cell_ov, "cannot serialize raw output message into an output action cell"}; + } + c5_ = cb.finalize_novm(); +} + +void PrecompiledSmartContract::raw_reserve(const td::RefInt256 &amount, int mode) { + if (amount->sgn() < 0) { + throw VmError{Excno::range_chk, "amount of nanograms must be non-negative"}; + } + CellBuilder cb; + if (!(cb.store_ref_bool(c5_) // out_list$_ {n:#} prev:^(OutList n) + && cb.store_long_bool(0x36e6b809, 32) // action_reserve_currency#36e6b809 + && cb.store_long_bool(mode, 8) // mode:(## 8) + && util::store_coins(cb, std::move(amount), true) // + && cb.store_maybe_ref({}))) { + throw VmError{Excno::cell_ov, "cannot serialize raw reserved currency amount into an output action cell"}; + } + c5_ = cb.finalize_novm(); +} + +td::RefInt256 PrecompiledSmartContract::get_compute_fee(ton::WorkchainId wc, td::uint64 gas_used) { + if (gas_used >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::GasLimitsPrices prices = util::get_gas_prices(unpacked_config_, wc); + return util::check_finite(prices.compute_gas_price(gas_used)); +} + +td::RefInt256 PrecompiledSmartContract::get_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells) { + if (bits >= (1ULL << 63) || cells >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::MsgPrices prices = util::get_msg_prices(unpacked_config_, wc); + return util::check_finite(prices.compute_fwd_fees256(cells, bits)); +} + +td::RefInt256 PrecompiledSmartContract::get_storage_fee(ton::WorkchainId wc, td::uint64 duration, td::uint64 bits, + td::uint64 cells) { + if (bits >= (1ULL << 63) || cells >= (1ULL << 63) || duration >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + td::optional maybe_prices = util::get_storage_prices(unpacked_config_); + return util::check_finite(util::calculate_storage_fee(maybe_prices, wc, duration, bits, cells)); +} + +td::RefInt256 PrecompiledSmartContract::get_simple_compute_fee(ton::WorkchainId wc, td::uint64 gas_used) { + if (gas_used >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::GasLimitsPrices prices = util::get_gas_prices(unpacked_config_, wc); + return util::check_finite(td::rshift(td::make_refint(prices.gas_price) * gas_used, 16, 1)); +} + +td::RefInt256 PrecompiledSmartContract::get_simple_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells) { + if (bits >= (1ULL << 63) || cells >= (1ULL << 63)) { + throw VmError{Excno::range_chk}; + } + block::MsgPrices prices = util::get_msg_prices(unpacked_config_, wc); + return util::check_finite( + td::rshift(td::make_refint(prices.bit_price) * bits + td::make_refint(prices.cell_price) * cells, 16, 1)); +} + +td::RefInt256 PrecompiledSmartContract::get_original_fwd_fee(ton::WorkchainId wc, const td::RefInt256 &x) { + if (x->sgn() < 0) { + throw VmError{Excno::range_chk, "fwd_fee is negative"}; + } + block::MsgPrices prices = util::get_msg_prices(unpacked_config_, wc); + return util::check_finite(td::muldiv(x, td::make_refint(1 << 16), td::make_refint((1 << 16) - prices.first_frac))); +} + +static std::atomic_bool precompiled_execution_enabled{false}; + +std::unique_ptr get_implementation(td::Bits256 code_hash) { + if (!precompiled_execution_enabled) { + return nullptr; + } + static std::map (*)()> map = []() { + auto from_hex = [](td::Slice s) -> td::Bits256 { + td::Bits256 x; + CHECK(x.from_hex(s) == 256); + return x; + }; + std::map (*)()> map; +#define CONTRACT(hash, cls) \ + map[from_hex(hash)] = []() -> std::unique_ptr { return std::make_unique(); }; + // CONTRACT("CODE_HASH_HEX", ClassName); + return map; + }(); + auto it = map.find(code_hash); + return it == map.end() ? nullptr : it->second(); +} + +void set_precompiled_execution_enabled(bool value) { + precompiled_execution_enabled = value; +} + +} // namespace block::precompiled diff --git a/crypto/block/precompiled-smc/PrecompiledSmartContract.h b/crypto/block/precompiled-smc/PrecompiledSmartContract.h new file mode 100644 index 000000000..509ebf793 --- /dev/null +++ b/crypto/block/precompiled-smc/PrecompiledSmartContract.h @@ -0,0 +1,122 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "common/refcnt.hpp" +#include "common/refint.h" +#include "vm/cells.h" +#include "vm/cellslice.h" +#include "vm/dict.h" +#include "vm/boc.h" +#include +#include "tl/tlblib.hpp" +#include "td/utils/bits.h" +#include "ton/ton-types.h" +#include "block/block.h" +#include "block/mc-config.h" + +namespace block::precompiled { + +struct Result { + int exit_code = 0; + td::optional exit_arg; + bool accepted = true; + bool committed = false; + + static Result error(int code, long long arg = 0) { + Result res; + res.exit_code = code; + res.exit_arg = arg; + return res; + } + + static Result error(vm::Excno code, long long arg = 0) { + Result res; + res.exit_code = (int)code; + res.exit_arg = arg; + return res; + } + + static Result not_accepted(int code = 0) { + Result res; + res.exit_code = code; + res.accepted = false; + return res; + } + + static Result success() { + Result res; + res.committed = true; + return res; + } +}; + +class PrecompiledSmartContract { + public: + virtual ~PrecompiledSmartContract() = default; + + virtual std::string get_name() const = 0; + + virtual int required_version() const { + return 6; + } + + Result run(td::Ref my_address, ton::UnixTime now, ton::LogicalTime cur_lt, CurrencyCollection balance, + td::Ref c4, vm::CellSlice msg_body, td::Ref msg, CurrencyCollection msg_balance, + bool is_external, std::vector> libraries, int global_version, td::uint16 max_data_depth, + td::Ref my_code, td::Ref unpacked_config, td::RefInt256 due_payment, td::uint64 precompiled_gas_usage); + + td::Ref get_c4() const { + return c4_; + } + td::Ref get_c5() const { + return c5_; + } + + protected: + td::Ref my_address_; + ton::UnixTime now_; + ton::LogicalTime cur_lt_; + CurrencyCollection balance_; + vm::CellSlice in_msg_body_; + td::Ref in_msg_; + CurrencyCollection in_msg_balance_; + bool is_external_; + td::Ref my_code_; + td::Ref unpacked_config_; + td::RefInt256 due_payment_; + td::uint64 precompiled_gas_usage_; + + td::Ref c4_; + td::Ref c5_ = vm::CellBuilder().finalize_novm(); + + void send_raw_message(const td::Ref& msg, int mode); + void raw_reserve(const td::RefInt256& amount, int mode); + + td::RefInt256 get_compute_fee(ton::WorkchainId wc, td::uint64 gas_used); + td::RefInt256 get_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells); + td::RefInt256 get_storage_fee(ton::WorkchainId wc, td::uint64 duration, td::uint64 bits, td::uint64 cells); + td::RefInt256 get_simple_compute_fee(ton::WorkchainId wc, td::uint64 gas_used); + td::RefInt256 get_simple_forward_fee(ton::WorkchainId wc, td::uint64 bits, td::uint64 cells); + td::RefInt256 get_original_fwd_fee(ton::WorkchainId wc, const td::RefInt256& x); + + virtual Result do_run() = 0; +}; + +std::unique_ptr get_implementation(td::Bits256 code_hash); +void set_precompiled_execution_enabled(bool value); // disabled by default + +} // namespace block::precompiled \ No newline at end of file diff --git a/crypto/block/precompiled-smc/common.h b/crypto/block/precompiled-smc/common.h new file mode 100644 index 000000000..0406d7dee --- /dev/null +++ b/crypto/block/precompiled-smc/common.h @@ -0,0 +1,21 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +*/ +#pragma once +#include "PrecompiledSmartContract.h" +#include "vm/arithops.h" +#include "vm/cellops.h" +#include "vm/tonops.h" \ No newline at end of file diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index b3aa6c8f4..88966179d 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -1341,6 +1341,9 @@ Ref Transaction::prepare_vm_c7(const ComputePhaseConfig& cfg) const { tuple.push_back(cfg.unpacked_config_tuple.not_null() ? vm::StackEntry(cfg.unpacked_config_tuple) : vm::StackEntry()); // unpacked_config_tuple:[...] tuple.push_back(due_payment.not_null() ? due_payment : td::zero_refint()); // due_payment:Integer + tuple.push_back(compute_phase->precompiled_gas_usage + ? vm::StackEntry(td::make_refint(compute_phase->precompiled_gas_usage.value())) + : vm::StackEntry()); // precompiled_gas_usage:Integer } auto tuple_ref = td::make_cnt_ref>(std::move(tuple)); LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple_ref).to_string(); @@ -1460,6 +1463,80 @@ bool Transaction::check_in_msg_state_hash() { return account.recompute_tmp_addr(my_addr, d, orig_addr_rewrite.bits()); } +/** + * Runs the precompiled smart contract and prepares the compute phase. + * + * @param cfg The configuration for the compute phase. + * @param impl Implementation of the smart contract + * + * @returns True if the contract was successfully executed, false otherwise. + */ +bool Transaction::run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& impl) { + ComputePhase& cp = *compute_phase; + CHECK(cp.precompiled_gas_usage); + td::uint64 gas_usage = cp.precompiled_gas_usage.value(); + td::Timer timer; + auto result = + impl.run(my_addr, now, start_lt, balance, new_data, *in_msg_body, in_msg, msg_balance_remaining, in_msg_extern, + compute_vm_libraries(cfg), cfg.global_version, cfg.max_vm_data_depth, new_code, + cfg.unpacked_config_tuple, due_payment.not_null() ? due_payment : td::zero_refint(), gas_usage); + double elapsed = timer.elapsed(); + cp.vm_init_state_hash = td::Bits256::zero(); + cp.exit_code = result.exit_code; + cp.out_of_gas = false; + cp.vm_final_state_hash = td::Bits256::zero(); + cp.vm_steps = 0; + cp.gas_used = gas_usage; + cp.accepted = result.accepted; + cp.success = (cp.accepted && result.committed); + LOG(INFO) << "Running precompiled smart contract " << impl.get_name() << ": exit_code=" << result.exit_code + << " accepted=" << result.accepted << " success=" << cp.success << " gas_used=" << gas_usage + << " time=" << elapsed << "s"; + if (cp.accepted & use_msg_state) { + was_activated = true; + acc_status = Account::acc_active; + } + if (cfg.with_vm_log) { + cp.vm_log = PSTRING() << "Running precompiled smart contract " << impl.get_name() + << ": exit_code=" << result.exit_code << " accepted=" << result.accepted + << " success=" << cp.success << " gas_used=" << gas_usage << " time=" << elapsed << "s"; + } + if (cp.success) { + cp.new_data = impl.get_c4(); + cp.actions = impl.get_c5(); + int out_act_num = output_actions_count(cp.actions); + if (verbosity > 2) { + std::cerr << "new smart contract data: "; + bool can_be_special = true; + load_cell_slice_special(cp.new_data, can_be_special).print_rec(std::cerr); + std::cerr << "output actions: "; + block::gen::OutList{out_act_num}.print_ref(std::cerr, cp.actions); + } + } + cp.mode = 0; + cp.exit_arg = 0; + if (!cp.success && result.exit_arg) { + auto value = td::narrow_cast_safe(result.exit_arg.value()); + if (value.is_ok()) { + cp.exit_arg = value.ok(); + } + } + if (cp.accepted) { + if (account.is_special) { + cp.gas_fees = td::zero_refint(); + } else { + cp.gas_fees = cfg.compute_gas_price(cp.gas_used); + total_fees += cp.gas_fees; + balance -= cp.gas_fees; + } + LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " + << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " + << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); + CHECK(td::sgn(balance.grams) >= 0); + } + return true; +} + /** * Prepares the compute phase of a transaction, which includes running TVM. * @@ -1528,6 +1605,33 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { cp.skip_reason = ComputePhase::sk_bad_state; return true; } + + td::optional precompiled; + if (new_code.not_null() && trans_type == tr_ord) { + precompiled = cfg.precompiled_contracts.get_contract(new_code->get_hash().bits()); + } + + vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit}; + if (precompiled) { + td::uint64 gas_usage = precompiled.value().gas_usage; + cp.precompiled_gas_usage = gas_usage; + if (gas_usage > cp.gas_limit) { + cp.skip_reason = ComputePhase::sk_no_gas; + return true; + } + auto impl = precompiled::get_implementation(new_code->get_hash().bits()); + if (impl != nullptr && !cfg.dont_run_precompiled_ && impl->required_version() <= cfg.global_version) { + return run_precompiled_contract(cfg, *impl); + } + + // Contract is marked as precompiled in global config, but implementation is not available + // In this case we run TVM and override gas_used + LOG(INFO) << "Unknown precompiled contract (code_hash=" << new_code->get_hash().to_hex() + << ", gas_usage=" << gas_usage << "), running VM"; + long long limit = account.is_special ? cfg.special_gas_limit : cfg.gas_limit; + gas = vm::GasLimits{limit, limit, gas.gas_credit ? limit : 0}; + } + // initialize VM Ref stack = prepare_vm_stack(cp); if (stack.is_null()) { @@ -1536,7 +1640,6 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { } // OstreamLogger ostream_logger(error_stream); // auto log = create_vm_log(error_stream ? &ostream_logger : nullptr); - vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit}; LOG(DEBUG) << "creating VM"; std::unique_ptr logger; @@ -1585,6 +1688,15 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { was_activated = true; acc_status = Account::acc_active; } + if (precompiled) { + cp.gas_used = precompiled.value().gas_usage; + cp.vm_steps = 0; + cp.vm_init_state_hash = cp.vm_final_state_hash = td::Bits256::zero(); + if (cp.out_of_gas) { + LOG(ERROR) << "Precompiled smc got out_of_gas in TVM"; + return false; + } + } LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas.gas_consumed() << ", max=" << gas.gas_max << ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit; LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success @@ -3568,6 +3680,7 @@ td::Status FetchConfigParams::fetch_config_params( } compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now); compute_phase_cfg->size_limits = size_limits; + compute_phase_cfg->precompiled_contracts = config.get_precompiled_contracts_config(); } { // compute action_phase_cfg diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 57defc8c8..42cd84a83 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -29,6 +29,7 @@ #include "ton/ton-types.h" #include "block/block.h" #include "block/mc-config.h" +#include "precompiled-smc/PrecompiledSmartContract.h" namespace block { using td::Ref; @@ -122,6 +123,8 @@ struct ComputePhaseConfig { SizeLimitsConfig size_limits; int vm_log_verbosity = 0; bool stop_on_accept_message = false; + PrecompiledContractsConfig precompiled_contracts; + bool dont_run_precompiled_ = false; ComputePhaseConfig() : gas_price(0), gas_limit(0), special_gas_limit(0), gas_credit(0) { compute_threshold(); @@ -189,6 +192,7 @@ struct ComputePhase { Ref new_data; Ref actions; std::string vm_log; + td::optional precompiled_gas_usage; }; struct ActionPhase { @@ -372,6 +376,7 @@ struct Transaction { bool compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg); Ref prepare_vm_stack(ComputePhase& cp); std::vector> compute_vm_libraries(const ComputePhaseConfig& cfg); + bool run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& precompiled); bool prepare_compute_phase(const ComputePhaseConfig& cfg); bool prepare_action_phase(const ActionPhaseConfig& cfg); td::Status check_state_limits(const SizeLimitsConfig& size_limits, bool update_storage_stat = true); diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 1908b9128..13e5c70b8 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -174,6 +174,7 @@ td::Ref prepare_vm_c7(SmartContract::Args args, td::Ref cod if (args.config && args.config.value()->get_global_version() >= 6) { tuple.push_back(args.config.value()->get_unpacked_config_tuple(now)); // unpacked_config_tuple tuple.push_back(td::zero_refint()); // due_payment + tuple.push_back(vm::StackEntry()); // precompiled_gas_usage:Integer } auto tuple_ref = td::make_cnt_ref>(std::move(tuple)); //LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); diff --git a/crypto/vm/arithops.cpp b/crypto/vm/arithops.cpp index 1d3111b2f..fc482fc4d 100644 --- a/crypto/vm/arithops.cpp +++ b/crypto/vm/arithops.cpp @@ -1069,4 +1069,26 @@ void register_arith_ops(OpcodeTable& cp0) { register_int_cmp_ops(cp0); } +namespace util { + +const td::RefInt256& check_signed_fits(const td::RefInt256& x, int bits) { + if (!x->signed_fits_bits(bits)) { + throw VmError{Excno::int_ov}; + } + return x; +} + +const td::RefInt256& check_unsigned_fits(const td::RefInt256& x, int bits) { + if (!x->unsigned_fits_bits(bits)) { + throw VmError{Excno::int_ov}; + } + return x; +} + +const td::RefInt256& check_finite(const td::RefInt256& x) { + return check_signed_fits(x, 257); +} + +} // namespace util + } // namespace vm diff --git a/crypto/vm/arithops.h b/crypto/vm/arithops.h index 63adcf7c5..c9735798c 100644 --- a/crypto/vm/arithops.h +++ b/crypto/vm/arithops.h @@ -18,10 +18,20 @@ */ #pragma once +#include "common/refint.h" namespace vm { class OpcodeTable; void register_arith_ops(OpcodeTable& cp0); +namespace util { + +// throw on error +const td::RefInt256& check_signed_fits(const td::RefInt256& x, int bits); +const td::RefInt256& check_unsigned_fits(const td::RefInt256& x, int bits); +const td::RefInt256& check_finite(const td::RefInt256& x); + +} // namespace util + } // namespace vm diff --git a/crypto/vm/cellops.cpp b/crypto/vm/cellops.cpp index 94407231a..61ffe5c55 100644 --- a/crypto/vm/cellops.cpp +++ b/crypto/vm/cellops.cpp @@ -1544,4 +1544,193 @@ void register_cell_ops(OpcodeTable& cp0) { register_cell_deserialize_ops(cp0); } +namespace util { + +bool load_int256_q(CellSlice& cs, td::RefInt256& res, int len, bool sgnd, bool quiet) { + if (!cs.fetch_int256_to(len, res, sgnd)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + return true; +} +bool load_long_q(CellSlice& cs, td::int64& res, int len, bool quiet) { + CHECK(0 <= len && len <= 64); + if (!cs.have(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + res = cs.fetch_long(len); + return true; +} +bool load_ulong_q(CellSlice& cs, td::uint64& res, int len, bool quiet) { + CHECK(0 <= len && len <= 64); + if (!cs.have(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + res = cs.fetch_ulong(len); + return true; +} +bool load_ref_q(CellSlice& cs, td::Ref& res, bool quiet) { + if (!cs.have_refs(1)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + res = cs.fetch_ref(); + return true; +} +bool load_maybe_ref_q(CellSlice& cs, td::Ref& res, bool quiet) { + if (!cs.fetch_maybe_ref(res)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + return true; +} +bool skip_bits_q(CellSlice& cs, int bits, bool quiet) { + if (!cs.skip_first(bits)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und}; + } + return true; +} + +td::RefInt256 load_int256(CellSlice& cs, int len, bool sgnd) { + td::RefInt256 x; + load_int256_q(cs, x, len, sgnd, false); + return x; +} +td::int64 load_long(CellSlice& cs, int len) { + td::int64 x; + load_long_q(cs, x, len, false); + return x; +} +td::uint64 load_ulong(CellSlice& cs, int len) { + td::uint64 x; + load_ulong_q(cs, x, len, false); + return x; +} +td::Ref load_ref(CellSlice& cs) { + td::Ref x; + load_ref_q(cs, x, false); + return x; +} +td::Ref load_maybe_ref(CellSlice& cs) { + td::Ref x; + load_maybe_ref_q(cs, x, false); + return x; +} +void check_have_bits(const CellSlice& cs, int bits) { + if (!cs.have(bits)) { + throw VmError{Excno::cell_und}; + } +} +void skip_bits(CellSlice& cs, int bits) { + skip_bits_q(cs, bits, false); +} +void end_parse(CellSlice& cs) { + if (cs.size() || cs.size_refs()) { + throw VmError{Excno::cell_und, "extra data remaining in deserialized cell"}; + } +} + +bool store_int256(CellBuilder& cb, const td::RefInt256& x, int len, bool sgnd, bool quiet) { + if (!cb.can_extend_by(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + if (!x->fits_bits(len, sgnd)) { + if (quiet) { + return false; + } + throw VmError{Excno::range_chk}; + } + cb.store_int256(*x, len, sgnd); + return true; +} +bool store_long(CellBuilder& cb, td::int64 x, int len, bool quiet) { + CHECK(len > 0); + if (!cb.can_extend_by(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + if (len < 64 && (x < td::int64(std::numeric_limits::max() << (len - 1)) || x >= (1LL << (len - 1)))) { + if (quiet) { + return false; + } + throw VmError{Excno::range_chk}; + } + if (len > 64) { + cb.store_bits_same(len - 64, x < 0); + len = 64; + } + cb.store_long(x, len); + return true; +} +bool store_ulong(CellBuilder& cb, td::uint64 x, int len, bool quiet) { + CHECK(len > 0); + if (!cb.can_extend_by(len)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + if (len < 64 && x >= (1ULL << len)) { + if (quiet) { + return false; + } + throw VmError{Excno::range_chk}; + } + if (len > 64) { + cb.store_zeroes(len - 64); + len = 64; + } + cb.store_long(x, len); + return true; +} +bool store_ref(CellBuilder& cb, td::Ref x, bool quiet) { + if (!cb.store_ref_bool(std::move(x))) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + return true; +} +bool store_maybe_ref(CellBuilder& cb, td::Ref x, bool quiet) { + if (!cb.store_maybe_ref(std::move(x))) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + return true; +} +bool store_slice(CellBuilder& cb, const CellSlice& cs, bool quiet) { + if (!cell_builder_add_slice_bool(cb, cs)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov}; + } + return true; +} + +} // namespace util + } // namespace vm diff --git a/crypto/vm/cellops.h b/crypto/vm/cellops.h index 1c08d8e34..0fd62854f 100644 --- a/crypto/vm/cellops.h +++ b/crypto/vm/cellops.h @@ -23,12 +23,42 @@ namespace vm { class OpcodeTable; -void register_cell_ops(OpcodeTable &cp0); +void register_cell_ops(OpcodeTable& cp0); -std::string dump_push_ref(CellSlice &cs, unsigned args, int pfx_bits, std::string name); -int compute_len_push_ref(const CellSlice &cs, unsigned args, int pfx_bits); +std::string dump_push_ref(CellSlice& cs, unsigned args, int pfx_bits, std::string name); +int compute_len_push_ref(const CellSlice& cs, unsigned args, int pfx_bits); -std::string dump_push_ref2(CellSlice &cs, unsigned args, int pfx_bits, std::string name); -int compute_len_push_ref2(const CellSlice &cs, unsigned args, int pfx_bits); +std::string dump_push_ref2(CellSlice& cs, unsigned args, int pfx_bits, std::string name); +int compute_len_push_ref2(const CellSlice& cs, unsigned args, int pfx_bits); + +namespace util { + +// "_q" functions throw on error if not quiet, return false if quiet (leaving cs unchanged) +bool load_int256_q(CellSlice& cs, td::RefInt256& res, int len, bool sgnd, bool quiet); +bool load_long_q(CellSlice& cs, td::int64& res, int len, bool quiet); +bool load_ulong_q(CellSlice& cs, td::uint64& res, int len, bool quiet); +bool load_ref_q(CellSlice& cs, td::Ref& res, bool quiet); +bool load_maybe_ref_q(CellSlice& cs, td::Ref& res, bool quiet); +bool skip_bits_q(CellSlice& cs, int bits, bool quiet); + +// Non-"_q" functions throw on error +td::RefInt256 load_int256(CellSlice& cs, int len, bool sgnd); +td::int64 load_long(CellSlice& cs, int len); +td::uint64 load_ulong(CellSlice& cs, int len); +td::Ref load_ref(CellSlice& cs); +td::Ref load_maybe_ref(CellSlice& cs); +void check_have_bits(const CellSlice& cs, int bits); +void skip_bits(CellSlice& cs, int bits); +void end_parse(CellSlice& cs); + +// store_... functions throw on error if not quiet, return false if quiet (leaving cb unchanged) +bool store_int256(CellBuilder& cb, const td::RefInt256& x, int len, bool sgnd, bool quiet = false); +bool store_long(CellBuilder& cb, td::int64 x, int len, bool quiet = false); +bool store_ulong(CellBuilder& cb, td::uint64 x, int len, bool quiet = false); +bool store_ref(CellBuilder& cb, td::Ref x, bool quiet = false); +bool store_maybe_ref(CellBuilder& cb, td::Ref x, bool quiet = false); +bool store_slice(CellBuilder& cb, const CellSlice& cs, bool quiet = false); + +} // namespace util } // namespace vm diff --git a/crypto/vm/memo.cpp b/crypto/vm/memo.cpp index 576f28f19..015bab6d0 100644 --- a/crypto/vm/memo.cpp +++ b/crypto/vm/memo.cpp @@ -18,6 +18,7 @@ */ #include "vm/memo.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { using td::Ref; @@ -30,4 +31,18 @@ bool FakeVmStateLimits::register_op(int op_units) { return ok; } +Ref DummyVmState::load_library(td::ConstBitPtr hash) { + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup + VmStateInterface::Guard guard{global_version >= 4 ? tmp_ctx.get() : VmStateInterface::get()}; + for (const auto& lib_collection : libraries) { + auto lib = lookup_library_in(hash, lib_collection); + if (lib.not_null()) { + return lib; + } + } + missing_library = td::Bits256{hash}; + return {}; +} + } // namespace vm diff --git a/crypto/vm/memo.h b/crypto/vm/memo.h index 3aee39a6b..db6dc8e20 100644 --- a/crypto/vm/memo.h +++ b/crypto/vm/memo.h @@ -20,6 +20,7 @@ #include "common/refcnt.hpp" #include "vm/cells.h" #include "vm/vmstate.h" +#include "td/utils/optional.h" namespace vm { using td::Ref; @@ -34,4 +35,23 @@ class FakeVmStateLimits : public VmStateInterface { bool register_op(int op_units = 1) override; }; +class DummyVmState : public VmStateInterface { + public: + explicit DummyVmState(std::vector> libraries, int global_version = ton::SUPPORTED_VERSION) + : libraries(std::move(libraries)), global_version(global_version) { + } + Ref load_library(td::ConstBitPtr hash) override; + int get_global_version() const override { + return global_version; + } + td::optional get_missing_library() const { + return missing_library; + } + + private: + std::vector> libraries; + int global_version; + td::optional missing_library; +}; + } // namespace vm diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 9ce7fe9c4..4b2d1734e 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -124,7 +124,7 @@ static const StackEntry& get_param(VmState* st, unsigned idx) { } // ConfigParams: 18 (only one entry), 19, 20, 21, 24, 25, 43 -static td::Ref get_unpacked_config_param(VmState* st, unsigned idx) { +static td::Ref get_unpacked_config_tuple(VmState* st) { auto tuple = st->get_c7(); auto t1 = tuple_index(tuple, 0).as_tuple_range(255); if (t1.is_null()) { @@ -134,7 +134,7 @@ static td::Ref get_unpacked_config_param(VmState* st, unsigned idx) { if (t2.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a tuple"}; } - return tuple_index(t2, idx).as_slice(); + return t2; } int exec_get_param(VmState* st, unsigned idx, const char* name) { @@ -249,7 +249,7 @@ int exec_get_prev_blocks_info(VmState* st, unsigned idx, const char* name) { int exec_get_global_id(VmState* st) { VM_LOG(st) << "execute GLOBALID"; if (st->get_global_version() >= 6) { - Ref cs = get_unpacked_config_param(st, 1); + Ref cs = tuple_index(get_unpacked_config_tuple(st), 1).as_slice(); if (cs.is_null()) { throw VmError{Excno::type_chk, "intermediate value is not a slice"}; } @@ -276,36 +276,12 @@ int exec_get_global_id(VmState* st) { return 0; } -static block::GasLimitsPrices get_gas_prices(VmState* st, bool is_masterchain) { - Ref cs = get_unpacked_config_param(st, is_masterchain ? 2 : 3); - if (cs.is_null()) { - throw VmError{Excno::type_chk, "intermediate value is not a slice"}; - } - auto r_prices = block::Config::do_get_gas_limits_prices(*cs, is_masterchain ? 20 : 21); - if (r_prices.is_error()) { - throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; - } - return r_prices.move_as_ok(); -} - -static block::MsgPrices get_msg_prices(VmState* st, bool is_masterchain) { - Ref cs = get_unpacked_config_param(st, is_masterchain ? 4 : 5); - if (cs.is_null()) { - throw VmError{Excno::type_chk, "intermediate value is not a slice"}; - } - auto r_prices = block::Config::do_get_msg_prices(*cs, is_masterchain ? 24 : 25); - if (r_prices.is_error()) { - throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; - } - return r_prices.move_as_ok(); -} - int exec_get_gas_fee(VmState* st) { VM_LOG(st) << "execute GETGASFEE"; Stack& stack = st->get_stack(); bool is_masterchain = stack.pop_bool(); td::uint64 gas = stack.pop_long_range(std::numeric_limits::max(), 0); - block::GasLimitsPrices prices = get_gas_prices(st, is_masterchain); + block::GasLimitsPrices prices = util::get_gas_prices(get_unpacked_config_tuple(st), is_masterchain); stack.push_int(prices.compute_gas_price(gas)); return 0; } @@ -317,27 +293,9 @@ int exec_get_storage_fee(VmState* st) { td::int64 delta = stack.pop_long_range(std::numeric_limits::max(), 0); td::uint64 bits = stack.pop_long_range(std::numeric_limits::max(), 0); td::uint64 cells = stack.pop_long_range(std::numeric_limits::max(), 0); - Ref cs = get_unpacked_config_param(st, 0); - if (cs.is_null()) { - // null means tat no StoragePrices is active, so the price is 0 - stack.push_smallint(0); - return 0; - } - auto r_prices = block::Config::do_get_one_storage_prices(*cs); - if (r_prices.is_error()) { - throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; - } - block::StoragePrices prices = r_prices.move_as_ok(); - td::RefInt256 total; - if (is_masterchain) { - total = td::make_refint(cells) * prices.mc_cell_price; - total += td::make_refint(bits) * prices.mc_bit_price; - } else { - total = td::make_refint(cells) * prices.cell_price; - total += td::make_refint(bits) * prices.bit_price; - } - total *= delta; - stack.push_int(td::rshift(total, 16, 1)); + td::optional maybe_prices = + util::get_storage_prices(get_unpacked_config_tuple(st)); + stack.push_int(util::calculate_storage_fee(maybe_prices, is_masterchain, delta, bits, cells)); return 0; } @@ -347,7 +305,7 @@ int exec_get_forward_fee(VmState* st) { bool is_masterchain = stack.pop_bool(); td::uint64 bits = stack.pop_long_range(std::numeric_limits::max(), 0); td::uint64 cells = stack.pop_long_range(std::numeric_limits::max(), 0); - block::MsgPrices prices = get_msg_prices(st, is_masterchain); + block::MsgPrices prices = util::get_msg_prices(get_unpacked_config_tuple(st), is_masterchain); stack.push_int(prices.compute_fwd_fees256(cells, bits)); return 0; } @@ -355,7 +313,7 @@ int exec_get_forward_fee(VmState* st) { int exec_get_precompiled_gas(VmState* st) { VM_LOG(st) << "execute GETPRECOMPILEDGAS"; Stack& stack = st->get_stack(); - stack.push_null(); + stack.push(get_param(st, 16)); return 0; } @@ -367,7 +325,7 @@ int exec_get_original_fwd_fee(VmState* st) { if (fwd_fee->sgn() < 0) { throw VmError{Excno::range_chk, "fwd_fee is negative"}; } - block::MsgPrices prices = get_msg_prices(st, is_masterchain); + block::MsgPrices prices = util::get_msg_prices(get_unpacked_config_tuple(st), is_masterchain); stack.push_int(td::muldiv(fwd_fee, td::make_refint(1 << 16), td::make_refint((1 << 16) - prices.first_frac))); return 0; } @@ -377,7 +335,7 @@ int exec_get_gas_fee_simple(VmState* st) { Stack& stack = st->get_stack(); bool is_masterchain = stack.pop_bool(); td::uint64 gas = stack.pop_long_range(std::numeric_limits::max(), 0); - block::GasLimitsPrices prices = get_gas_prices(st, is_masterchain); + block::GasLimitsPrices prices = util::get_gas_prices(get_unpacked_config_tuple(st), is_masterchain); stack.push_int(td::rshift(td::make_refint(prices.gas_price) * gas, 16, 1)); return 0; } @@ -388,7 +346,7 @@ int exec_get_forward_fee_simple(VmState* st) { bool is_masterchain = stack.pop_bool(); td::uint64 bits = stack.pop_long_range(std::numeric_limits::max(), 0); td::uint64 cells = stack.pop_long_range(std::numeric_limits::max(), 0); - block::MsgPrices prices = get_msg_prices(st, is_masterchain); + block::MsgPrices prices = util::get_msg_prices(get_unpacked_config_tuple(st), is_masterchain); stack.push_int(td::rshift(td::make_refint(prices.bit_price) * bits + td::make_refint(prices.cell_price) * cells, 16, 1)); // divide by 2^16 with ceil rounding return 0; @@ -1349,19 +1307,14 @@ int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { Stack& stack = st->get_stack(); auto csr = stack.pop_cellslice(); td::RefInt256 x; - int len; - if (!(csr.write().fetch_uint_to(len_bits, len) && csr.unique_write().fetch_int256_to(len * 8, x, sgnd))) { - if (quiet) { - stack.push_bool(false); - } else { - throw VmError{Excno::cell_und, "cannot deserialize a variable-length integer"}; - } - } else { + if (util::load_var_integer_q(csr.write(), x, len_bits, sgnd, quiet)) { stack.push_int(std::move(x)); stack.push_cellslice(std::move(csr)); if (quiet) { stack.push_bool(true); } + } else { + stack.push_bool(false); } return 0; } @@ -1376,21 +1329,13 @@ int exec_store_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { stack.check_underflow(2); auto x = stack.pop_int(); auto cbr = stack.pop_builder(); - unsigned len = (((unsigned)x->bit_size(sgnd) + 7) >> 3); - if (len >= (1u << len_bits)) { - throw VmError{Excno::range_chk}; - } - if (!(cbr.write().store_long_bool(len, len_bits) && cbr.unique_write().store_int256_bool(*x, len * 8, sgnd))) { - if (quiet) { - stack.push_bool(false); - } else { - throw VmError{Excno::cell_ov, "cannot serialize a variable-length integer"}; - } - } else { + if (util::store_var_integer(cbr.write(), x, len_bits, sgnd, quiet)) { stack.push_builder(std::move(cbr)); if (quiet) { stack.push_bool(true); } + } else { + stack.push_bool(false); } return 0; } @@ -1433,22 +1378,17 @@ bool skip_message_addr(CellSlice& cs) { int exec_load_message_addr(VmState* st, bool quiet) { VM_LOG(st) << "execute LDMSGADDR" << (quiet ? "Q" : ""); Stack& stack = st->get_stack(); - auto csr = stack.pop_cellslice(), csr_copy = csr; - auto& cs = csr.write(); - if (!(skip_message_addr(cs) && csr_copy.write().cut_tail(cs))) { - csr.clear(); - if (quiet) { - stack.push_cellslice(std::move(csr_copy)); - stack.push_bool(false); - } else { - throw VmError{Excno::cell_und, "cannot load a MsgAddress"}; - } - } else { - stack.push_cellslice(std::move(csr_copy)); + auto csr = stack.pop_cellslice(); + td::Ref addr{true}; + if (util::load_msg_addr_q(csr.write(), addr.write(), quiet)) { + stack.push_cellslice(std::move(addr)); stack.push_cellslice(std::move(csr)); if (quiet) { stack.push_bool(true); } + } else { + stack.push_cellslice(std::move(csr)); + stack.push_bool(false); } return 0; } @@ -1747,7 +1687,7 @@ int exec_send_message(VmState* st) { bool is_masterchain = parse_addr_workchain(*my_addr) == -1 || (!ext_msg && parse_addr_workchain(*dest) == -1); td::Ref prices_cs; if (st->get_global_version() >= 6) { - prices_cs = get_unpacked_config_param(st, is_masterchain ? 4 : 5); + prices_cs = tuple_index(get_unpacked_config_tuple(st), is_masterchain ? 4 : 5).as_slice(); } else { Ref config_dict = get_param(st, 9).as_cell(); Dictionary config{config_dict, 32}; @@ -1769,7 +1709,8 @@ int exec_send_message(VmState* st) { // bits in the root cell of a message are not included in msg.bits (lump_price pays for them) td::uint64 max_cells; if (st->get_global_version() >= 6) { - auto r_size_limits_config = block::Config::do_get_size_limits_config(get_unpacked_config_param(st, 6)); + auto r_size_limits_config = + block::Config::do_get_size_limits_config(tuple_index(get_unpacked_config_tuple(st), 6).as_slice()); if (r_size_limits_config.is_error()) { throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_size_limits_config.error().message()}; } @@ -2020,4 +1961,158 @@ void register_ton_ops(OpcodeTable& cp0) { register_ton_message_ops(cp0); } +namespace util { + +bool load_var_integer_q(CellSlice& cs, td::RefInt256& res, int len_bits, bool sgnd, bool quiet) { + CellSlice cs0 = cs; + int len; + if (!(cs.fetch_uint_to(len_bits, len) && cs.fetch_int256_to(len * 8, res, sgnd))) { + cs = std::move(cs0); + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot deserialize a variable-length integer"}; + } + return true; +} +bool load_coins_q(CellSlice& cs, td::RefInt256& res, bool quiet) { + return load_var_integer_q(cs, res, 4, false, quiet); +} +bool load_msg_addr_q(CellSlice& cs, CellSlice& res, bool quiet) { + res = cs; + if (!skip_message_addr(cs)) { + cs = res; + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot load a MsgAddress"}; + } + res.cut_tail(cs); + return true; +} +bool parse_std_addr_q(CellSlice cs, ton::WorkchainId& res_wc, ton::StdSmcAddress& res_addr, bool quiet) { + // Like exec_rewrite_message_addr, but for std address case + std::vector tuple; + if (!(parse_message_addr(cs, tuple) && cs.empty_ext())) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot parse a MsgAddress"}; + } + int t = (int)std::move(tuple[0]).as_int()->to_long(); + if (t != 2 && t != 3) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "cannot parse a MsgAddressInt"}; + } + auto addr = std::move(tuple[3]).as_slice(); + auto prefix = std::move(tuple[1]).as_slice(); + if (addr->size() != 256) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_und, "MsgAddressInt is not a standard 256-bit address"}; + } + res_wc = (int)tuple[2].as_int()->to_long(); + CHECK(addr->prefetch_bits_to(res_addr) && + (prefix.is_null() || prefix->prefetch_bits_to(res_addr.bits(), prefix->size()))); + return true; +} + +td::RefInt256 load_var_integer(CellSlice& cs, int len_bits, bool sgnd) { + td::RefInt256 x; + load_var_integer_q(cs, x, len_bits, sgnd, false); + return x; +} +td::RefInt256 load_coins(CellSlice& cs) { + return load_var_integer(cs, 4, false); +} +CellSlice load_msg_addr(CellSlice& cs) { + CellSlice addr; + load_msg_addr_q(cs, addr, false); + return addr; +} +std::pair parse_std_addr(CellSlice cs) { + std::pair res; + parse_std_addr_q(std::move(cs), res.first, res.second, false); + return res; +} + +bool store_var_integer(CellBuilder& cb, const td::RefInt256& x, int len_bits, bool sgnd, bool quiet) { + unsigned len = (((unsigned)x->bit_size(sgnd) + 7) >> 3); + if (len >= (1u << len_bits)) { + throw VmError{Excno::range_chk}; // throw even if quiet + } + if (!cb.can_extend_by(len_bits + len * 8)) { + if (quiet) { + return false; + } + throw VmError{Excno::cell_ov, "cannot serialize a variable-length integer"}; + } + CHECK(cb.store_long_bool(len, len_bits) && cb.store_int256_bool(*x, len * 8, sgnd)); + return true; +} +bool store_coins(CellBuilder& cb, const td::RefInt256& x, bool quiet) { + return store_var_integer(cb, x, 4, false, quiet); +} + +block::GasLimitsPrices get_gas_prices(const Ref& unpacked_config, bool is_masterchain) { + Ref cs = tuple_index(unpacked_config, is_masterchain ? 2 : 3).as_slice(); + if (cs.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a slice"}; + } + auto r_prices = block::Config::do_get_gas_limits_prices(*cs, is_masterchain ? 20 : 21); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + return r_prices.move_as_ok(); +} + +block::MsgPrices get_msg_prices(const Ref& unpacked_config, bool is_masterchain) { + Ref cs = tuple_index(unpacked_config, is_masterchain ? 4 : 5).as_slice(); + if (cs.is_null()) { + throw VmError{Excno::type_chk, "intermediate value is not a slice"}; + } + auto r_prices = block::Config::do_get_msg_prices(*cs, is_masterchain ? 24 : 25); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + return r_prices.move_as_ok(); +} + +td::optional get_storage_prices(const Ref& unpacked_config) { + Ref cs = tuple_index(unpacked_config, 0).as_slice(); + if (cs.is_null()) { + // null means tat no StoragePrices is active, so the price is 0 + return {}; + } + auto r_prices = block::Config::do_get_one_storage_prices(*cs); + if (r_prices.is_error()) { + throw VmError{Excno::cell_und, PSTRING() << "cannot parse config: " << r_prices.error().message()}; + } + return r_prices.move_as_ok(); +} + +td::RefInt256 calculate_storage_fee(const td::optional& maybe_prices, bool is_masterchain, + td::uint64 delta, td::uint64 bits, td::uint64 cells) { + if (!maybe_prices) { + // no StoragePrices is active, so the price is 0 + return td::zero_refint(); + } + const block::StoragePrices& prices = maybe_prices.value(); + td::RefInt256 total; + if (is_masterchain) { + total = td::make_refint(cells) * prices.mc_cell_price; + total += td::make_refint(bits) * prices.mc_bit_price; + } else { + total = td::make_refint(cells) * prices.cell_price; + total += td::make_refint(bits) * prices.bit_price; + } + total *= delta; + return td::rshift(total, 16, 1); +} + +} // namespace util + } // namespace vm diff --git a/crypto/vm/tonops.h b/crypto/vm/tonops.h index 5b3e14d99..bbac078f2 100644 --- a/crypto/vm/tonops.h +++ b/crypto/vm/tonops.h @@ -17,6 +17,9 @@ Copyright 2017-2020 Telegram Systems LLP */ #pragma once +#include "vm/vm.h" +#include "ton/ton-types.h" +#include "mc-config.h" namespace vm { @@ -24,4 +27,30 @@ class OpcodeTable; void register_ton_ops(OpcodeTable& cp0); +namespace util { + +// "_q" functions throw on error if not quiet, return false if quiet (leaving cs unchanged) +bool load_var_integer_q(CellSlice& cs, td::RefInt256& res, int len_bits, bool sgnd, bool quiet); +bool load_coins_q(CellSlice& cs, td::RefInt256& res, bool quiet); +bool load_msg_addr_q(CellSlice& cs, CellSlice& res, bool quiet); +bool parse_std_addr_q(CellSlice cs, ton::WorkchainId& res_wc, ton::StdSmcAddress& res_addr, bool quiet); + +// Non-"_q" functions throw on error +td::RefInt256 load_var_integer(CellSlice& cs, int len_bits, bool sgnd); +td::RefInt256 load_coins(CellSlice& cs); +CellSlice load_msg_addr(CellSlice& cs); +std::pair parse_std_addr(CellSlice cs); + +// store_... functions throw on error if not quiet, return false if quiet (leaving cb unchanged) +bool store_var_integer(CellBuilder& cb, const td::RefInt256& x, int len_bits, bool sgnd, bool quiet = false); +bool store_coins(CellBuilder& cb, const td::RefInt256& x, bool quiet = false); + +block::GasLimitsPrices get_gas_prices(const td::Ref& unpacked_config, bool is_masterchain); +block::MsgPrices get_msg_prices(const td::Ref& unpacked_config, bool is_masterchain); +td::optional get_storage_prices(const td::Ref& unpacked_config); +td::RefInt256 calculate_storage_fee(const td::optional& maybe_prices, bool is_masterchain, + td::uint64 delta, td::uint64 bits, td::uint64 cells); + +} // namespace util + } // namespace vm diff --git a/doc/GlobalVersions.md b/doc/GlobalVersions.md index 7e3b4ce92..6c176552f 100644 --- a/doc/GlobalVersions.md +++ b/doc/GlobalVersions.md @@ -58,7 +58,7 @@ See [this post](https://t.me/tonstatus/88) for details. ## Version 6 ### c7 tuple -**c7** tuple extended from 14 to 16 elements: +**c7** tuple extended from 14 to 17 elements: * **14**: tuple that contains some config parameters as cell slices. If the parameter is absent from the config, the value is null. Asm opcode: `UNPACKEDCONFIGTUPLE`. * **0**: `StoragePrices` from `ConfigParam 18`. Not the whole dict, but only the one StoragePrices entry (one which corresponds to the current time). * **1**: `ConfigParam 19` (global id). @@ -68,6 +68,7 @@ See [this post](https://t.me/tonstatus/88) for details. * **5**: `ConfigParam 25` (fwd fees). * **6**: `ConfigParam 43` (size limits). * **15**: "[due payment](https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L237)" - current debt for storage fee (nanotons). Asm opcode: `DUEPAYMENT`. +* **16**: "precompiled gas usage" - gas usage for the current contract if it is precompiled (see `ConfigParam 45`), `null` otherwise. Asm opcode: `GETPRECOMPILEDGAS`. ### New TVM instructions @@ -75,7 +76,7 @@ See [this post](https://t.me/tonstatus/88) for details. * `GETGASFEE` (`gas_used is_mc - price`) - calculates gas fee. * `GETSTORAGEFEE` (`cells bits seconds is_mc - price`) - calculates storage fees (only current StoragePrices entry is used). * `GETFORWARDFEE` (`cells bits is_mc - price`) - calculates forward fee. -* `GETPRECOMPILEDGAS` (`- null`) - reserved, currently returns `null`. +* `GETPRECOMPILEDGAS` (`- x`) - returns gas usage for the current contract if it is precompiled, `null` otherwise. * `GETORIGINALFWDFEE` (`fwd_fee is_mc - orig_fwd_fee`) - calculate `fwd_fee * 2^16 / first_frac`. Can be used to get the original `fwd_fee` of the message. * `GETGASFEESIMPLE` (`gas_used is_mc - price`) - same as `GETGASFEE`, but without flat price (just `(gas_used * price) / 2^16`). * `GETFORWARDFEESIMPLE` (`cells bits is_mc - price`) - same as `GETFORWARDFEE`, but without lump price (just `(bits*bit_price + cells*cell_price) / 2^16`). diff --git a/validator-engine/validator-engine.cpp b/validator-engine/validator-engine.cpp index b8d428f3a..96103cd0b 100644 --- a/validator-engine/validator-engine.cpp +++ b/validator-engine/validator-engine.cpp @@ -72,6 +72,7 @@ #include "block-auto.h" #include "block-parse.h" #include "common/delay.h" +#include "block/precompiled-smc/PrecompiledSmartContract.h" Config::Config() { out_port = 3278; @@ -3812,6 +3813,9 @@ int main(int argc, char *argv[]) { acts.push_back([&x, v]() { td::actor::send_closure(x, &ValidatorEngine::set_archive_preload_period, v); }); return td::Status::OK(); }); + p.add_option('\0', "enable-precompiled-smc", + "enable exectuion of precompiled contracts (experimental, disabled by default)", + []() { block::precompiled::set_precompiled_execution_enabled(true); }); auto S = p.run(argc, argv); if (S.is_error()) { LOG(ERROR) << "failed to parse options: " << S.move_as_error(); diff --git a/validator/impl/validate-query.cpp b/validator/impl/validate-query.cpp index 44b49226b..d925fce63 100644 --- a/validator/impl/validate-query.cpp +++ b/validator/impl/validate-query.cpp @@ -965,6 +965,7 @@ bool ValidateQuery::fetch_config_params() { } compute_phase_cfg_.suspended_addresses = config_->get_suspended_addresses(now_); compute_phase_cfg_.size_limits = size_limits; + compute_phase_cfg_.precompiled_contracts = config_->get_precompiled_contracts_config(); } { // compute action_phase_cfg