mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-13 11:42:18 +00:00
Merge branch 'testnet' into block-generation
# Conflicts: # crypto/block/mc-config.cpp # crypto/block/mc-config.h
This commit is contained in:
commit
e216651112
41 changed files with 1233 additions and 192 deletions
21
Changelog.md
21
Changelog.md
|
@ -1,3 +1,24 @@
|
|||
## 2024.03 Update
|
||||
|
||||
1. Preparatory (not enabled yet) code for pre-compiled smart-contract.
|
||||
2. Minor fixes for fee-related opcodes.
|
||||
|
||||
## 2024.02 Update
|
||||
|
||||
1. Improvement of validator synchronisation:
|
||||
* Better handling of block broadcasts -> faster sync
|
||||
* Additional separate overlay among validators as second option for synchronisation
|
||||
2. Improvements in LS:
|
||||
* c7 and library context is fully filled up for server-side rungetmethod
|
||||
* Cache for runmethods and successfull external messages
|
||||
* Logging of LS requests statistic
|
||||
3. Precise control of open files:
|
||||
* almost instantaneous validator start
|
||||
* `--max-archive-fd` option
|
||||
* autoremoval of not used temp archive files
|
||||
* `--archive-preload-period` option
|
||||
4. Preparatory (not enabled yet) code for addition on new TVM instructions for cheaper fee calculation onchain.
|
||||
|
||||
## 2024.01 Update
|
||||
|
||||
1. Fixes in how gas in transactions on special accounts is accounted in block limit. Previously, gas was counted as usual, so to conduct elections that costs >30m gas block limit in masterchain was set to 37m gas. To lower the limit for safety reasons it is proposed to caunt gas on special accounts separately. Besides `gas_max` is set to `special_gas_limit` for all types of transactions on special accounts. New behavior is activated through setting `version >= 5` in `ConfigParam 8;`.
|
||||
|
|
46
README.md
46
README.md
|
@ -10,25 +10,33 @@
|
|||
</div>
|
||||
|
||||
##
|
||||
[![TON Overflow Group][ton-overflow-badge]][ton-overflow-url]
|
||||
[![Stack Overflow Group][stack-overflow-badge]][stack-overflow-url]
|
||||
[![Telegram Community Chat][telegram-tondev-badge]][telegram-tondev-url]
|
||||
[![Telegram Community Group][telegram-community-badge]][telegram-community-url]
|
||||
[![Telegram Foundation Group][telegram-foundation-badge]][telegram-foundation-url]
|
||||
[![Twitter Group][twitter-badge]][twitter-url]
|
||||
|
||||
[telegram-foundation-badge]: https://img.shields.io/badge/TON%20Foundation-2CA5E0?logo=telegram&logoColor=white&style=flat
|
||||
[telegram-community-badge]: https://img.shields.io/badge/TON%20Community-2CA5E0?logo=telegram&logoColor=white&style=flat
|
||||
[telegram-tondev-badge]: https://img.shields.io/badge/chat-TONDev-2CA5E0?logo=telegram&logoColor=white&style=flat
|
||||
[telegram-foundation-url]: https://t.me/tonblockchain
|
||||
[telegram-community-url]: https://t.me/toncoin
|
||||
[telegram-tondev-url]: https://t.me/tondev_eng
|
||||
[twitter-badge]: https://img.shields.io/twitter/follow/ton_blockchain
|
||||
[twitter-url]: https://twitter.com/ton_blockchain
|
||||
[stack-overflow-badge]: https://img.shields.io/badge/-Stack%20Overflow-FE7A16?style=flat&logo=stack-overflow&logoColor=white
|
||||
[stack-overflow-url]: https://stackoverflow.com/questions/tagged/ton
|
||||
[ton-overflow-badge]: https://img.shields.io/badge/-TON%20Overflow-FE7A16?style=flat&logo=stack-overflow&logoColor=white
|
||||
[ton-overflow-url]: https://answers.ton.org
|
||||
<p align="center">
|
||||
<a href="https://tonresear.ch">
|
||||
<img src="https://img.shields.io/badge/TON%20Research-0098EA?style=flat&logo=discourse&label=Forum&labelColor=gray" alt="Ton Research">
|
||||
</a>
|
||||
<a href="https://t.me/toncoin">
|
||||
<img src="https://img.shields.io/badge/TON%20Community-0098EA?logo=telegram&logoColor=white&style=flat" alt="Telegram Community Group">
|
||||
</a>
|
||||
<a href="https://t.me/tonblockchain">
|
||||
<img src="https://img.shields.io/badge/TON%20Foundation-0098EA?logo=telegram&logoColor=white&style=flat" alt="Telegram Foundation Group">
|
||||
</a>
|
||||
<a href="https://t.me/tondev_eng">
|
||||
<img src="https://img.shields.io/badge/chat-TONDev-0098EA?logo=telegram&logoColor=white&style=flat" alt="Telegram Community Chat">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://twitter.com/ton_blockchain">
|
||||
<img src="https://img.shields.io/twitter/follow/ton_blockchain" alt="Twitter Group">
|
||||
</a>
|
||||
<a href="https://answers.ton.org">
|
||||
<img src="https://img.shields.io/badge/-TON%20Overflow-FE7A16?style=flat&logo=stack-overflow&logoColor=white" alt="TON Overflow Group">
|
||||
</a>
|
||||
<a href="https://stackoverflow.com/questions/tagged/ton">
|
||||
<img src="https://img.shields.io/badge/-Stack%20Overflow-FE7A16?style=flat&logo=stack-overflow&logoColor=white" alt="Stack Overflow Group">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
|
@ -148,4 +156,4 @@ More examples for other platforms can be found under `assembly/nix`.
|
|||
|
||||
## Running tests
|
||||
|
||||
Tests are executed by running `ctest` in the build directory. See `doc/Tests.md` for more information.
|
||||
Tests are executed by running `ctest` in the build directory. See `doc/Tests.md` for more information.
|
||||
|
|
|
@ -24,12 +24,14 @@ if [ "$with_tests" = true ]; then
|
|||
else
|
||||
nix-build linux-arm64-static.nix
|
||||
fi
|
||||
mkdir artifacts
|
||||
|
||||
mkdir -p artifacts/lib
|
||||
cp ./result/bin/* artifacts/
|
||||
test $? -eq 0 || { echo "No artifacts have been built..."; exit 1; }
|
||||
chmod +x artifacts/*
|
||||
rm -rf result
|
||||
nix-build linux-arm64-tonlib.nix
|
||||
cp ./result/lib/libtonlibjson.so.0.5 artifacts/libtonlibjson.so
|
||||
cp ./result/lib/libemulator.so artifacts/
|
||||
cp -r crypto/fift/lib artifacts/
|
||||
cp -r crypto/smartcont artifacts/
|
||||
cp ./result/lib/fift/* artifacts/lib/
|
||||
cp -r ./result/share/ton/smartcont artifacts/
|
||||
|
|
|
@ -25,12 +25,13 @@ else
|
|||
nix-build linux-x86-64-static.nix
|
||||
fi
|
||||
|
||||
mkdir artifacts
|
||||
mkdir -p artifacts/lib
|
||||
cp ./result/bin/* artifacts/
|
||||
test $? -eq 0 || { echo "No artifacts have been built..."; exit 1; }
|
||||
chmod +x artifacts/*
|
||||
rm -rf result
|
||||
nix-build linux-x86-64-tonlib.nix
|
||||
cp ./result/lib/libtonlibjson.so.0.5 artifacts/libtonlibjson.so
|
||||
cp ./result/lib/libemulator.so artifacts/
|
||||
cp -r crypto/fift/lib artifacts/
|
||||
cp -r crypto/smartcont artifacts/
|
||||
cp ./result/lib/fift/* artifacts/lib/
|
||||
cp -r ./result/share/ton/smartcont artifacts/
|
||||
|
|
|
@ -22,12 +22,14 @@ if [ "$with_tests" = true ]; then
|
|||
else
|
||||
nix-build macos-static.nix
|
||||
fi
|
||||
mkdir artifacts
|
||||
|
||||
mkdir -p artifacts/lib
|
||||
cp ./result-bin/bin/* artifacts/
|
||||
test $? -eq 0 || { echo "No artifacts have been built..."; exit 1; }
|
||||
chmod +x artifacts/*
|
||||
rm -rf result-bin
|
||||
nix-build macos-tonlib.nix
|
||||
cp ./result/lib/libtonlibjson.dylib artifacts/
|
||||
cp ./result/lib/libemulator.dylib artifacts/
|
||||
cp -r crypto/fift/lib artifacts/
|
||||
cp -r crypto/smartcont artifacts/
|
||||
cp ./result/lib/fift/* artifacts/lib/
|
||||
cp -r ./result/share/ton/smartcont artifacts/
|
||||
|
|
|
@ -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 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/block> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
|
||||
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 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
|
|
|
@ -797,6 +797,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
|
||||
|
|
|
@ -2032,6 +2032,17 @@ td::Ref<vm::Tuple> Config::get_unpacked_config_tuple(ton::UnixTime now) const {
|
|||
return td::make_cnt_ref<std::vector<vm::StackEntry>>(std::move(tuple));
|
||||
}
|
||||
|
||||
PrecompiledContractsConfig Config::get_precompiled_contracts_config() const {
|
||||
PrecompiledContractsConfig c;
|
||||
td::Ref<vm::Cell> 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<std::pair<ton::UnixTime, ton::UnixTime>> Config::unpack_validator_set_start_stop(Ref<vm::Cell> vset_root) {
|
||||
if (vset_root.is_null()) {
|
||||
return td::Status::Error("validator set absent");
|
||||
|
@ -2316,6 +2327,22 @@ td::Result<Ref<vm::Tuple>> ConfigInfo::get_prev_blocks_info() const {
|
|||
block_id_to_tuple(last_key_block));
|
||||
}
|
||||
|
||||
td::optional<PrecompiledContractsConfig::Contract> 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;
|
||||
}
|
||||
|
||||
CollatorConfig Config::get_collator_config(bool need_collator_nodes) const {
|
||||
CollatorConfig collator_config;
|
||||
gen::CollatorConfig::Record rec;
|
||||
|
|
|
@ -526,6 +526,15 @@ struct BurningConfig {
|
|||
}
|
||||
};
|
||||
|
||||
struct PrecompiledContractsConfig {
|
||||
struct Contract {
|
||||
td::uint64 gas_usage;
|
||||
};
|
||||
vm::Dictionary list{256};
|
||||
|
||||
td::optional<Contract> get_contract(td::Bits256 code_hash) const;
|
||||
};
|
||||
|
||||
struct CollatorNodeDescr {
|
||||
ton::ShardIdFull shard;
|
||||
ton::NodeIdShort adnl_id;
|
||||
|
@ -655,6 +664,7 @@ class Config {
|
|||
std::unique_ptr<vm::Dictionary> get_suspended_addresses(ton::UnixTime now) const;
|
||||
BurningConfig get_burning_config() const;
|
||||
td::Ref<vm::Tuple> get_unpacked_config_tuple(ton::UnixTime now) const;
|
||||
PrecompiledContractsConfig get_precompiled_contracts_config() const;
|
||||
static std::vector<ton::ValidatorDescr> do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf,
|
||||
ton::ShardIdFull shard,
|
||||
const block::ValidatorSet& vset, ton::UnixTime time,
|
||||
|
|
170
crypto/block/precompiled-smc/PrecompiledSmartContract.cpp
Normal file
170
crypto/block/precompiled-smc/PrecompiledSmartContract.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "common.h"
|
||||
#include <memory>
|
||||
#include "vm/memo.h"
|
||||
|
||||
namespace block::precompiled {
|
||||
|
||||
using namespace vm;
|
||||
|
||||
Result PrecompiledSmartContract::run(td::Ref<vm::CellSlice> my_address, ton::UnixTime now, ton::LogicalTime cur_lt,
|
||||
CurrencyCollection balance, td::Ref<vm::Cell> c4, vm::CellSlice msg_body,
|
||||
td::Ref<vm::Cell> msg, CurrencyCollection msg_balance, bool is_external,
|
||||
std::vector<td::Ref<vm::Cell>> libraries, int global_version,
|
||||
td::uint16 max_data_depth, td::Ref<vm::Cell> my_code,
|
||||
td::Ref<vm::Tuple> 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<Cell> &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<block::StoragePrices> 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<PrecompiledSmartContract> get_implementation(td::Bits256 code_hash) {
|
||||
if (!precompiled_execution_enabled) {
|
||||
return nullptr;
|
||||
}
|
||||
static std::map<td::Bits256, std::unique_ptr<PrecompiledSmartContract> (*)()> map = []() {
|
||||
auto from_hex = [](td::Slice s) -> td::Bits256 {
|
||||
td::Bits256 x;
|
||||
CHECK(x.from_hex(s) == 256);
|
||||
return x;
|
||||
};
|
||||
std::map<td::Bits256, std::unique_ptr<PrecompiledSmartContract> (*)()> map;
|
||||
#define CONTRACT(hash, cls) \
|
||||
map[from_hex(hash)] = []() -> std::unique_ptr<PrecompiledSmartContract> { return std::make_unique<cls>(); };
|
||||
// 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
|
122
crypto/block/precompiled-smc/PrecompiledSmartContract.h
Normal file
122
crypto/block/precompiled-smc/PrecompiledSmartContract.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 <ostream>
|
||||
#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<long long> 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<vm::CellSlice> my_address, ton::UnixTime now, ton::LogicalTime cur_lt, CurrencyCollection balance,
|
||||
td::Ref<vm::Cell> c4, vm::CellSlice msg_body, td::Ref<vm::Cell> msg, CurrencyCollection msg_balance,
|
||||
bool is_external, std::vector<td::Ref<vm::Cell>> libraries, int global_version, td::uint16 max_data_depth,
|
||||
td::Ref<vm::Cell> my_code, td::Ref<vm::Tuple> unpacked_config, td::RefInt256 due_payment, td::uint64 precompiled_gas_usage);
|
||||
|
||||
td::Ref<vm::Cell> get_c4() const {
|
||||
return c4_;
|
||||
}
|
||||
td::Ref<vm::Cell> get_c5() const {
|
||||
return c5_;
|
||||
}
|
||||
|
||||
protected:
|
||||
td::Ref<vm::CellSlice> my_address_;
|
||||
ton::UnixTime now_;
|
||||
ton::LogicalTime cur_lt_;
|
||||
CurrencyCollection balance_;
|
||||
vm::CellSlice in_msg_body_;
|
||||
td::Ref<vm::Cell> in_msg_;
|
||||
CurrencyCollection in_msg_balance_;
|
||||
bool is_external_;
|
||||
td::Ref<vm::Cell> my_code_;
|
||||
td::Ref<vm::Tuple> unpacked_config_;
|
||||
td::RefInt256 due_payment_;
|
||||
td::uint64 precompiled_gas_usage_;
|
||||
|
||||
td::Ref<vm::Cell> c4_;
|
||||
td::Ref<vm::Cell> c5_ = vm::CellBuilder().finalize_novm();
|
||||
|
||||
void send_raw_message(const td::Ref<vm::Cell>& 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<PrecompiledSmartContract> get_implementation(td::Bits256 code_hash);
|
||||
void set_precompiled_execution_enabled(bool value); // disabled by default
|
||||
|
||||
} // namespace block::precompiled
|
21
crypto/block/precompiled-smc/common.h
Normal file
21
crypto/block/precompiled-smc/common.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "PrecompiledSmartContract.h"
|
||||
#include "vm/arithops.h"
|
||||
#include "vm/cellops.h"
|
||||
#include "vm/tonops.h"
|
|
@ -1341,6 +1341,9 @@ Ref<vm::Tuple> 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::vector<vm::StackEntry>>(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<td::int32>(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<PrecompiledContractsConfig::Contract> 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<vm::Stack> 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<StringLoggerTail> 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
|
||||
|
|
|
@ -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<vm::Cell> new_data;
|
||||
Ref<vm::Cell> actions;
|
||||
std::string vm_log;
|
||||
td::optional<td::uint64> precompiled_gas_usage;
|
||||
};
|
||||
|
||||
struct ActionPhase {
|
||||
|
@ -372,6 +376,7 @@ struct Transaction {
|
|||
bool compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg);
|
||||
Ref<vm::Stack> prepare_vm_stack(ComputePhase& cp);
|
||||
std::vector<Ref<vm::Cell>> 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);
|
||||
|
|
|
@ -169,8 +169,6 @@ class PropagateConstSpan {
|
|||
size_t size_{0};
|
||||
};
|
||||
|
||||
struct Normalize {};
|
||||
|
||||
template <class Tr = BigIntInfo>
|
||||
class AnyIntView {
|
||||
public:
|
||||
|
@ -290,6 +288,7 @@ class BigIntG {
|
|||
public:
|
||||
enum { word_bits = Tr::word_bits, word_shift = Tr::word_shift, max_bits = len, word_cnt = len / word_shift + 1 };
|
||||
typedef typename Tr::word_t word_t;
|
||||
typedef typename Tr::uword_t uword_t;
|
||||
typedef Tr Traits;
|
||||
typedef BigIntG<len * 2, Tr> DoubleInt;
|
||||
|
||||
|
@ -312,9 +311,6 @@ class BigIntG {
|
|||
BigIntG() : n(0) {
|
||||
}
|
||||
explicit BigIntG(word_t x) : n(1) {
|
||||
digits[0] = x;
|
||||
}
|
||||
BigIntG(Normalize, word_t x) : n(1) {
|
||||
if (x >= -Tr::Half && x < Tr::Half) {
|
||||
digits[0] = x;
|
||||
} else if (len <= 1) {
|
||||
|
@ -325,6 +321,25 @@ class BigIntG {
|
|||
digits[n++] = (x >> Tr::word_shift) + (digits[0] < 0);
|
||||
}
|
||||
}
|
||||
explicit BigIntG(uword_t x) : n(1) {
|
||||
if (x < (uword_t)Tr::Half) {
|
||||
digits[0] = x;
|
||||
} else if (len <= 1) {
|
||||
digits[0] = x;
|
||||
normalize_bool();
|
||||
} else {
|
||||
digits[0] = ((x ^ Tr::Half) & (Tr::Base - 1)) - Tr::Half;
|
||||
digits[n++] = (x >> Tr::word_shift) + (digits[0] < 0);
|
||||
}
|
||||
}
|
||||
explicit BigIntG(unsigned x) : BigIntG(uword_t(x)) {
|
||||
}
|
||||
explicit BigIntG(int x) : BigIntG(word_t(x)) {
|
||||
}
|
||||
explicit BigIntG(unsigned long x) : BigIntG(uword_t(x)) {
|
||||
}
|
||||
explicit BigIntG(long x) : BigIntG(word_t(x)) {
|
||||
}
|
||||
BigIntG(const BigIntG& x) : n(x.n) {
|
||||
std::memcpy(digits, x.digits, n * sizeof(word_t));
|
||||
///std::cout << "(BiCC " << (const void*)&x << "->" << (void*)this << ")";
|
||||
|
@ -2556,7 +2571,7 @@ typedef BigIntG<257, BigIntInfo> BigInt256;
|
|||
|
||||
template <int n = 257>
|
||||
BigIntG<n, BigIntInfo> make_bigint(long long x) {
|
||||
return BigIntG<n, BigIntInfo>{Normalize(), x};
|
||||
return BigIntG<n, BigIntInfo>{x};
|
||||
}
|
||||
|
||||
namespace literals {
|
||||
|
|
|
@ -261,10 +261,6 @@ int sgn(RefInt256 x) {
|
|||
return x->sgn();
|
||||
}
|
||||
|
||||
RefInt256 make_refint(long long x) {
|
||||
return td::RefInt256{true, td::Normalize(), x};
|
||||
}
|
||||
|
||||
RefInt256 zero_refint() {
|
||||
// static RefInt256 Zero = td::RefInt256{true, 0};
|
||||
// return Zero;
|
||||
|
|
|
@ -113,8 +113,6 @@ RefInt256 make_refint(Args&&... args) {
|
|||
return td::RefInt256{true, std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
extern RefInt256 make_refint(long long x);
|
||||
|
||||
extern RefInt256 zero_refint();
|
||||
extern RefInt256 bits_to_refint(td::ConstBitPtr bits, int n, bool sgnd = false);
|
||||
|
||||
|
|
|
@ -174,6 +174,7 @@ td::Ref<vm::Tuple> prepare_vm_c7(SmartContract::Args args, td::Ref<vm::Cell> 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::vector<vm::StackEntry>>(std::move(tuple));
|
||||
//LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Cell>& 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<Cell>& 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<Cell> load_ref(CellSlice& cs) {
|
||||
td::Ref<Cell> x;
|
||||
load_ref_q(cs, x, false);
|
||||
return x;
|
||||
}
|
||||
td::Ref<Cell> load_maybe_ref(CellSlice& cs) {
|
||||
td::Ref<Cell> 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<td::uint64>::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<Cell> 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<Cell> 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
|
||||
|
|
|
@ -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<Cell>& res, bool quiet);
|
||||
bool load_maybe_ref_q(CellSlice& cs, td::Ref<Cell>& 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<Cell> load_ref(CellSlice& cs);
|
||||
td::Ref<Cell> 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<Cell> x, bool quiet = false);
|
||||
bool store_maybe_ref(CellBuilder& cb, td::Ref<Cell> x, bool quiet = false);
|
||||
bool store_slice(CellBuilder& cb, const CellSlice& cs, bool quiet = false);
|
||||
|
||||
} // namespace util
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -595,7 +595,7 @@ td::RefInt256 CellSlice::fetch_int256(unsigned bits, bool sgnd) {
|
|||
if (!have(bits)) {
|
||||
return {};
|
||||
} else if (bits < td::BigInt256::word_shift) {
|
||||
return td::make_refint(sgnd ? fetch_long(bits) : fetch_ulong(bits));
|
||||
return td::make_refint(td::int64(sgnd ? fetch_long(bits) : fetch_ulong(bits)));
|
||||
} else {
|
||||
td::RefInt256 res{true};
|
||||
res.unique_write().import_bits(data_bits(), bits, sgnd);
|
||||
|
@ -608,7 +608,7 @@ td::RefInt256 CellSlice::prefetch_int256(unsigned bits, bool sgnd) const {
|
|||
if (!have(bits)) {
|
||||
return {};
|
||||
} else if (bits < td::BigInt256::word_shift) {
|
||||
return td::make_refint(sgnd ? prefetch_long(bits) : prefetch_ulong(bits));
|
||||
return td::make_refint(td::int64(sgnd ? prefetch_long(bits) : prefetch_ulong(bits)));
|
||||
} else {
|
||||
td::RefInt256 res{true};
|
||||
res.unique_write().import_bits(data_bits(), bits, sgnd);
|
||||
|
|
|
@ -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<Cell> DummyVmState::load_library(td::ConstBitPtr hash) {
|
||||
std::unique_ptr<VmStateInterface> 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
|
||||
|
|
|
@ -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<Ref<Cell>> libraries, int global_version = ton::SUPPORTED_VERSION)
|
||||
: libraries(std::move(libraries)), global_version(global_version) {
|
||||
}
|
||||
Ref<Cell> load_library(td::ConstBitPtr hash) override;
|
||||
int get_global_version() const override {
|
||||
return global_version;
|
||||
}
|
||||
td::optional<td::Bits256> get_missing_library() const {
|
||||
return missing_library;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Ref<Cell>> libraries;
|
||||
int global_version;
|
||||
td::optional<td::Bits256> missing_library;
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -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<CellSlice> get_unpacked_config_param(VmState* st, unsigned idx) {
|
||||
static td::Ref<Tuple> 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<CellSlice> 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<CellSlice> cs = get_unpacked_config_param(st, 1);
|
||||
Ref<CellSlice> 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<CellSlice> 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<CellSlice> 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<td::int64>::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<td::int64>::max(), 0);
|
||||
td::uint64 bits = stack.pop_long_range(std::numeric_limits<td::int64>::max(), 0);
|
||||
td::uint64 cells = stack.pop_long_range(std::numeric_limits<td::int64>::max(), 0);
|
||||
Ref<CellSlice> 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<block::StoragePrices> 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<td::int64>::max(), 0);
|
||||
td::uint64 cells = stack.pop_long_range(std::numeric_limits<td::int64>::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<td::int64>::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<td::int64>::max(), 0);
|
||||
td::uint64 cells = stack.pop_long_range(std::numeric_limits<td::int64>::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<CellSlice> 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<CellSlice> 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<Cell> 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<StackEntry> 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<ton::WorkchainId, ton::StdSmcAddress> parse_std_addr(CellSlice cs) {
|
||||
std::pair<ton::WorkchainId, ton::StdSmcAddress> 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<Tuple>& unpacked_config, bool is_masterchain) {
|
||||
Ref<CellSlice> 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<Tuple>& unpacked_config, bool is_masterchain) {
|
||||
Ref<CellSlice> 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<block::StoragePrices> get_storage_prices(const Ref<Tuple>& unpacked_config) {
|
||||
Ref<CellSlice> 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<block::StoragePrices>& 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
|
||||
|
|
|
@ -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<ton::WorkchainId, ton::StdSmcAddress> 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<Tuple>& unpacked_config, bool is_masterchain);
|
||||
block::MsgPrices get_msg_prices(const td::Ref<Tuple>& unpacked_config, bool is_masterchain);
|
||||
td::optional<block::StoragePrices> get_storage_prices(const td::Ref<Tuple>& unpacked_config);
|
||||
td::RefInt256 calculate_storage_fee(const td::optional<block::StoragePrices>& maybe_prices, bool is_masterchain,
|
||||
td::uint64 delta, td::uint64 bits, td::uint64 cells);
|
||||
|
||||
} // namespace util
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -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`).
|
||||
|
|
|
@ -1408,7 +1408,7 @@ bool TestNode::after_parse_run_method(ton::WorkchainId workchain, ton::StdSmcAdd
|
|||
}
|
||||
}
|
||||
});
|
||||
return start_run_method(workchain, addr, ref_blkid, method_name, std::move(params), ext_mode ? 0x1f : 0,
|
||||
return start_run_method(workchain, addr, ref_blkid, method_name, std::move(params), ext_mode ? 0x17 : 0,
|
||||
std::move(P));
|
||||
}
|
||||
|
||||
|
@ -1582,7 +1582,7 @@ bool TestNode::send_past_vset_query(ton::StdSmcAddress elector_addr) {
|
|||
}
|
||||
register_past_vset_info(std::move(S.back()));
|
||||
});
|
||||
return start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "past_elections_list", std::move(params), 0x1f,
|
||||
return start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "past_elections_list", std::move(params), 0x17,
|
||||
std::move(P));
|
||||
}
|
||||
|
||||
|
@ -1643,7 +1643,7 @@ void TestNode::send_get_complaints_query(unsigned elect_id, ton::StdSmcAddress e
|
|||
LOG(ERROR) << "vm virtualization error: " << err.get_msg();
|
||||
}
|
||||
});
|
||||
start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "get_past_complaints", std::move(params), 0x1f,
|
||||
start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "get_past_complaints", std::move(params), 0x17,
|
||||
std::move(P));
|
||||
}
|
||||
|
||||
|
@ -1740,7 +1740,7 @@ void TestNode::send_compute_complaint_price_query(ton::StdSmcAddress elector_add
|
|||
LOG(ERROR) << "vm virtualization error: " << err.get_msg();
|
||||
}
|
||||
});
|
||||
start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "complaint_storage_price", std::move(params), 0x1f,
|
||||
start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "complaint_storage_price", std::move(params), 0x17,
|
||||
std::move(P));
|
||||
}
|
||||
|
||||
|
@ -1837,7 +1837,7 @@ bool TestNode::dns_resolve_send(ton::WorkchainId workchain, ton::StdSmcAddress a
|
|||
}
|
||||
return dns_resolve_finish(workchain, addr, blkid, domain, qdomain, cat, mode, (int)x->to_long(), std::move(cell));
|
||||
});
|
||||
return start_run_method(workchain, addr, blkid, "dnsresolve", std::move(params), 0x1f, std::move(P));
|
||||
return start_run_method(workchain, addr, blkid, "dnsresolve", std::move(params), 0x17, std::move(P));
|
||||
}
|
||||
|
||||
bool TestNode::show_dns_record(std::ostream& os, td::Bits256 cat, Ref<vm::CellSlice> value, bool raw_dump) {
|
||||
|
@ -2269,21 +2269,29 @@ void TestNode::run_smc_method(int mode, ton::BlockIdExt ref_blk, ton::BlockIdExt
|
|||
}
|
||||
}
|
||||
if (exit_code != 0) {
|
||||
LOG(ERROR) << "VM terminated with error code " << exit_code;
|
||||
out << "result: error " << exit_code << std::endl;
|
||||
promise.set_error(td::Status::Error(PSLICE() << "VM terminated with non-zero exit code " << exit_code));
|
||||
return;
|
||||
}
|
||||
stack = vm.get_stack_ref();
|
||||
{
|
||||
} else {
|
||||
stack = vm.get_stack_ref();
|
||||
std::ostringstream os;
|
||||
os << "result: ";
|
||||
stack->dump(os, 3);
|
||||
out << os.str();
|
||||
}
|
||||
if (mode & 4) {
|
||||
if (remote_result.empty()) {
|
||||
out << "remote result: <none>, exit code " << remote_exit_code;
|
||||
if (!(mode & 4)) {
|
||||
if (exit_code != 0) {
|
||||
LOG(ERROR) << "VM terminated with error code " << exit_code;
|
||||
promise.set_error(td::Status::Error(PSLICE() << "VM terminated with non-zero exit code " << exit_code));
|
||||
} else {
|
||||
promise.set_result(stack->extract_contents());
|
||||
}
|
||||
} else {
|
||||
if (remote_exit_code != 0) {
|
||||
out << "remote result: error " << remote_exit_code << std::endl;
|
||||
LOG(ERROR) << "VM terminated with error code " << exit_code;
|
||||
promise.set_error(td::Status::Error(PSLICE() << "VM terminated with non-zero exit code " << exit_code));
|
||||
} else if (remote_result.empty()) {
|
||||
out << "remote result: <none>" << std::endl;
|
||||
promise.set_value({});
|
||||
} else {
|
||||
auto res = vm::std_boc_deserialize(std::move(remote_result));
|
||||
if (res.is_error()) {
|
||||
|
@ -2304,10 +2312,10 @@ void TestNode::run_smc_method(int mode, ton::BlockIdExt ref_blk, ton::BlockIdExt
|
|||
os << "remote result (not to be trusted): ";
|
||||
remote_stack->dump(os, 3);
|
||||
out << os.str();
|
||||
promise.set_value(remote_stack->extract_contents());
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
promise.set_result(stack->extract_contents());
|
||||
} catch (vm::VmVirtError& err) {
|
||||
out << "virtualization error while parsing runSmcMethod result: " << err.get_msg();
|
||||
promise.set_error(
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
## 2024.01 Update
|
||||
## 2024.03 Update
|
||||
|
||||
1. Preparatory (not enabled yet) code for pre-compiled smart-contract.
|
||||
2. Minor fixes for fee-related opcodes.
|
||||
|
||||
1. Fixes in how gas in transactions on special accounts is accounted in block limit. Previously, gas was counted as usual, so to conduct elections that costs >30m gas block limit in masterchain was set to 37m gas. To lower the limit for safety reasons it is proposed to caunt gas on special accounts separately. Besides `gas_max` is set to `special_gas_limit` for all types of transactions on special accounts. New behavior is activated through setting `version >= 5` in `ConfigParam 8;`.
|
||||
* Besides update of config temporally increases gas limit on `EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu` to `special_gas_limit`, see [details](https://t.me/tonstatus/88).
|
||||
2. Improvements in LS behavior
|
||||
* Improved detection of the state with all shards applied to decrease rate of `Block is not applied` error
|
||||
* Better error logs: `block not in db` and `block is not applied` separation
|
||||
* Fix error in proof generation for blocks after merge
|
||||
* Fix most of `block is not applied` issues related to sending too recent block in Proofs
|
||||
* LS now check external messages till `accept_message` (`set_gas`).
|
||||
3. Improvements in DHT work and storage, CellDb, config.json ammendment, peer misbehavior detection, validator session stats collection, emulator.
|
||||
4. Change in CTOS and XLOAD behavior activated through setting `version >= 5` in `ConfigParam 8;`:
|
||||
* Loading "nested libraries" (i.e. a library cell that points to another library cell) throws an exception.
|
||||
* Loading a library consumes gas for cell load only once (for the library cell), not twice (both for the library cell and the cell in the library).
|
||||
* `XLOAD` now works differently. When it takes a library cell, it returns the cell that it points to. This allows loading "nested libraries", if needed.
|
||||
|
||||
Besides the work of the Core team, this update is based on the efforts of @XaBbl4 (peer misbehavior detection) and @akifoq (CTOS behavior and gas limit scheme for special accounts).
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
abce
|
||||
Test_Bigint_main_default 0327a04f1252c37f77b6706b902ab2c3235c47738bca3f183c837a2c5d22bb6f
|
||||
Test_Bigint_main_default 76f38492ec19464a1d0eac51d389023a31ce10396b3894061361d159567ce8cd
|
||||
Test_Bitstrings_main_default a8b08af3116923c4c2a14e138d168375abd0c059f2f780d3267b294929a1110e
|
||||
Test_Cells_simple_default 832502642fe4fe5db70de82681aedb7d54d7f3530e0069861fff405fe6f6cf23
|
||||
Test_Fift_bug_div_default 1ac42861ce96b2896001c587f65e9afe1617db48859f19c2f4e3063a20ea60b0
|
||||
|
|
|
@ -1216,7 +1216,7 @@ class RemoteRunSmcMethod : public td::actor::Actor {
|
|||
client_.send_query(
|
||||
//liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:long params:bytes = liteServer.RunMethodResult;
|
||||
ton::lite_api::liteServer_runSmcMethod(
|
||||
0x1f, ton::create_tl_lite_block_id(query_.block_id.value()),
|
||||
0x17, ton::create_tl_lite_block_id(query_.block_id.value()),
|
||||
ton::create_tl_object<ton::lite_api::liteServer_accountId>(query_.address.workchain, query_.address.addr),
|
||||
method_id, std::move(serialized_stack)),
|
||||
[self = this](auto r_state) { self->with_run_method_result(std::move(r_state)); },
|
||||
|
|
|
@ -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;
|
||||
|
@ -4123,6 +4124,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();
|
||||
|
|
|
@ -136,7 +136,8 @@ void FullNodeShardImpl::check_broadcast(PublicKeyHash src, td::BufferSlice broad
|
|||
}
|
||||
|
||||
auto q = B.move_as_ok();
|
||||
if (!processed_ext_msg_broadcasts_.insert(td::sha256_bits256(q->message_->data_)).second) {
|
||||
auto hash = td::sha256_bits256(q->message_->data_);
|
||||
if (!processed_ext_msg_broadcasts_.insert(hash).second) {
|
||||
return promise.set_error(td::Status::Error("duplicate external message broadcast"));
|
||||
}
|
||||
if (config_.ext_messages_broadcast_disabled_) {
|
||||
|
@ -147,6 +148,11 @@ void FullNodeShardImpl::check_broadcast(PublicKeyHash src, td::BufferSlice broad
|
|||
}
|
||||
};
|
||||
}
|
||||
if (my_ext_msg_broadcasts_.count(hash)) {
|
||||
// Don't re-check messages that were sent by us
|
||||
promise.set_result(td::Unit());
|
||||
return;
|
||||
}
|
||||
td::actor::send_closure(validator_manager_, &ValidatorManagerInterface::check_external_message,
|
||||
std::move(q->message_->data_),
|
||||
promise.wrap([](td::Ref<ExtMessage>) { return td::Unit(); }));
|
||||
|
@ -821,9 +827,11 @@ void FullNodeShardImpl::send_external_message(td::BufferSlice data) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (!processed_ext_msg_broadcasts_.insert(td::sha256_bits256(data)).second) {
|
||||
td::Bits256 hash = td::sha256_bits256(data);
|
||||
if (processed_ext_msg_broadcasts_.count(hash)) {
|
||||
return;
|
||||
}
|
||||
my_ext_msg_broadcasts_.insert(hash);
|
||||
auto B = create_serialize_tl_object<ton_api::tonNode_externalMessageBroadcast>(
|
||||
create_tl_object<ton_api::tonNode_externalMessage>(std::move(data)));
|
||||
if (B.size() <= overlay::Overlays::max_simple_broadcast_size()) {
|
||||
|
@ -1016,6 +1024,7 @@ void FullNodeShardImpl::alarm() {
|
|||
}
|
||||
if (cleanup_processed_ext_msg_at_ && cleanup_processed_ext_msg_at_.is_in_past()) {
|
||||
processed_ext_msg_broadcasts_.clear();
|
||||
my_ext_msg_broadcasts_.clear();
|
||||
cleanup_processed_ext_msg_at_ = td::Timestamp::in(60.0);
|
||||
}
|
||||
alarm_timestamp().relax(sync_completed_at_);
|
||||
|
|
|
@ -280,6 +280,7 @@ class FullNodeShardImpl : public FullNodeShard {
|
|||
|
||||
FullNodeConfig config_;
|
||||
|
||||
std::set<td::Bits256> my_ext_msg_broadcasts_;
|
||||
std::set<td::Bits256> processed_ext_msg_broadcasts_;
|
||||
td::Timestamp cleanup_processed_ext_msg_at_;
|
||||
};
|
||||
|
|
|
@ -157,7 +157,7 @@ void LiteQuery::start_up() {
|
|||
});
|
||||
return;
|
||||
}
|
||||
use_cache_ = !cache_.empty() && query_obj_->get_id() == lite_api::liteServer_runSmcMethod::ID;
|
||||
use_cache_ = use_cache();
|
||||
if (use_cache_) {
|
||||
cache_key_ = td::sha256_bits256(query_);
|
||||
td::actor::send_closure(
|
||||
|
@ -173,6 +173,22 @@ void LiteQuery::start_up() {
|
|||
}
|
||||
}
|
||||
|
||||
bool LiteQuery::use_cache() {
|
||||
if (cache_.empty()) {
|
||||
return false;
|
||||
}
|
||||
bool use = false;
|
||||
lite_api::downcast_call(
|
||||
*query_obj_,
|
||||
td::overloaded(
|
||||
[&](lite_api::liteServer_runSmcMethod& q) {
|
||||
// wc=-1, seqno=-1 means "use latest mc block"
|
||||
use = q.id_->workchain_ != masterchainId || q.id_->seqno_ != -1;
|
||||
},
|
||||
[&](auto& obj) { use = false; }));
|
||||
return use;
|
||||
}
|
||||
|
||||
void LiteQuery::perform() {
|
||||
td::actor::send_closure(manager_, &ValidatorManager::add_lite_query_stats, query_obj_->get_id());
|
||||
lite_api::downcast_call(
|
||||
|
@ -848,7 +864,7 @@ void LiteQuery::perform_runSmcMethod(BlockIdExt blkid, WorkchainId workchain, St
|
|||
fatal_error("more than 64k parameter bytes passed");
|
||||
return;
|
||||
}
|
||||
if (mode & ~0x1f) {
|
||||
if (mode & ~0x3f) {
|
||||
fatal_error("unsupported mode in runSmcMethod");
|
||||
return;
|
||||
}
|
||||
|
@ -1245,8 +1261,24 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) {
|
|||
return;
|
||||
}
|
||||
if (mode_ & 0x10000) {
|
||||
finish_runSmcMethod(std::move(shard_proof), proof.move_as_ok(), std::move(acc_root), sstate.gen_utime,
|
||||
sstate.gen_lt);
|
||||
if (!mc_state_.is_null()) {
|
||||
finish_runSmcMethod(std::move(shard_proof), proof.move_as_ok(), std::move(acc_root), sstate.gen_utime,
|
||||
sstate.gen_lt);
|
||||
return;
|
||||
}
|
||||
shard_proof_ = std::move(shard_proof);
|
||||
proof_ = proof.move_as_ok();
|
||||
set_continuation(
|
||||
[&, base_blk_id = base_blk_id_, acc_root, utime = sstate.gen_utime, lt = sstate.gen_lt]() mutable -> void {
|
||||
base_blk_id_ = base_blk_id; // It gets overridden by request_mc_block_data_state
|
||||
finish_runSmcMethod(std::move(shard_proof_), std::move(proof_), std::move(acc_root), utime, lt);
|
||||
});
|
||||
td::optional<BlockIdExt> master_ref = state_->get_master_ref();
|
||||
if (!master_ref) {
|
||||
fatal_error("masterchain ref block is not available");
|
||||
return;
|
||||
}
|
||||
request_mc_block_data_state(master_ref.value());
|
||||
return;
|
||||
}
|
||||
td::BufferSlice data;
|
||||
|
@ -1283,25 +1315,45 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) {
|
|||
|
||||
// same as in lite-client/lite-client-common.cpp
|
||||
static td::Ref<vm::Tuple> prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref<vm::CellSlice> my_addr,
|
||||
const block::CurrencyCollection& balance) {
|
||||
const block::CurrencyCollection& balance,
|
||||
const block::ConfigInfo* config = nullptr, td::Ref<vm::Cell> my_code = {},
|
||||
td::RefInt256 due_payment = td::zero_refint()) {
|
||||
td::BitArray<256> rand_seed;
|
||||
td::RefInt256 rand_seed_int{true};
|
||||
td::Random::secure_bytes(rand_seed.as_slice());
|
||||
if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) {
|
||||
return {};
|
||||
}
|
||||
auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
|
||||
td::make_refint(0), // actions:Integer
|
||||
td::make_refint(0), // msgs_sent:Integer
|
||||
td::make_refint(now), // unixtime:Integer
|
||||
td::make_refint(lt), // block_lt:Integer
|
||||
td::make_refint(lt), // trans_lt:Integer
|
||||
std::move(rand_seed_int), // rand_seed:Integer
|
||||
balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
|
||||
my_addr, // myself:MsgAddressInt
|
||||
vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo;
|
||||
LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
|
||||
return vm::make_tuple_ref(std::move(tuple));
|
||||
std::vector<vm::StackEntry> tuple = {
|
||||
td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
|
||||
td::make_refint(0), // actions:Integer
|
||||
td::make_refint(0), // msgs_sent:Integer
|
||||
td::make_refint(now), // unixtime:Integer
|
||||
td::make_refint(lt), // block_lt:Integer
|
||||
td::make_refint(lt), // trans_lt:Integer
|
||||
std::move(rand_seed_int), // rand_seed:Integer
|
||||
balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
|
||||
my_addr, // myself:MsgAddressInt
|
||||
config ? config->get_root_cell() : vm::StackEntry() // global_config:(Maybe Cell) ] = SmartContractInfo;
|
||||
};
|
||||
if (config && config->get_global_version() >= 4) {
|
||||
tuple.push_back(my_code); // code:Cell
|
||||
tuple.push_back(block::CurrencyCollection::zero().as_vm_tuple()); // in_msg_value:[Integer (Maybe Cell)]
|
||||
tuple.push_back(td::zero_refint()); // storage_fees:Integer
|
||||
|
||||
// [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId;
|
||||
// [ last_mc_blocks:[BlockId...]
|
||||
// prev_key_block:BlockId ] : PrevBlocksInfo
|
||||
auto info = config->get_prev_blocks_info();
|
||||
tuple.push_back(info.is_ok() ? info.move_as_ok() : vm::StackEntry());
|
||||
}
|
||||
if (config && config->get_global_version() >= 6) {
|
||||
tuple.push_back(config->get_unpacked_config_tuple(now)); // unpacked_config_tuple:[...]
|
||||
tuple.push_back(due_payment); // due_payment:Integer
|
||||
}
|
||||
auto tuple_ref = td::make_cnt_ref<std::vector<vm::StackEntry>>(std::move(tuple));
|
||||
LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple_ref).to_string();
|
||||
return vm::make_tuple_ref(std::move(tuple_ref));
|
||||
}
|
||||
|
||||
void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice state_proof, Ref<vm::Cell> acc_root,
|
||||
|
@ -1320,12 +1372,14 @@ void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice
|
|||
}
|
||||
vm::MerkleProofBuilder pb{std::move(acc_root)};
|
||||
block::gen::Account::Record_account acc;
|
||||
block::gen::StorageInfo::Record storage_info;
|
||||
block::gen::AccountStorage::Record store;
|
||||
block::CurrencyCollection balance;
|
||||
block::gen::StateInit::Record state_init;
|
||||
if (!(tlb::unpack_cell(pb.root(), acc) && tlb::csr_unpack(std::move(acc.storage), store) &&
|
||||
balance.validate_unpack(store.balance) && store.state->prefetch_ulong(1) == 1 &&
|
||||
store.state.write().advance(1) && tlb::csr_unpack(std::move(store.state), state_init))) {
|
||||
store.state.write().advance(1) && tlb::csr_unpack(std::move(store.state), state_init) &&
|
||||
tlb::csr_unpack(std::move(acc.storage_stat), storage_info))) {
|
||||
LOG(INFO) << "error unpacking account state, or account is frozen or uninitialized";
|
||||
td::Result<td::BufferSlice> proof_boc;
|
||||
if (mode & 2) {
|
||||
|
@ -1346,12 +1400,35 @@ void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice
|
|||
}
|
||||
auto code = state_init.code->prefetch_ref();
|
||||
auto data = state_init.data->prefetch_ref();
|
||||
auto acc_libs = state_init.library->prefetch_ref();
|
||||
long long gas_limit = client_method_gas_limit;
|
||||
td::RefInt256 due_payment;
|
||||
if (storage_info.due_payment.write().fetch_long(1)) {
|
||||
due_payment = block::tlb::t_Grams.as_integer(storage_info.due_payment);
|
||||
} else {
|
||||
due_payment = td::zero_refint();
|
||||
}
|
||||
LOG(DEBUG) << "creating VM with gas limit " << gas_limit;
|
||||
// **** INIT VM ****
|
||||
auto r_config = block::ConfigInfo::extract_config(
|
||||
mc_state_->root_cell(),
|
||||
block::ConfigInfo::needLibraries | block::ConfigInfo::needCapabilities | block::ConfigInfo::needPrevBlocks);
|
||||
if (r_config.is_error()) {
|
||||
fatal_error(r_config.move_as_error());
|
||||
return;
|
||||
}
|
||||
auto config = r_config.move_as_ok();
|
||||
std::vector<td::Ref<vm::Cell>> libraries;
|
||||
if (config->get_libraries_root().not_null()) {
|
||||
libraries.push_back(config->get_libraries_root());
|
||||
}
|
||||
if (acc_libs.not_null()) {
|
||||
libraries.push_back(acc_libs);
|
||||
}
|
||||
vm::GasLimits gas{gas_limit, gas_limit};
|
||||
vm::VmState vm{std::move(code), std::move(stack_), gas, 1, std::move(data), vm::VmLog::Null()};
|
||||
auto c7 = prepare_vm_c7(gen_utime, gen_lt, td::make_ref<vm::CellSlice>(acc.addr->clone()), balance);
|
||||
vm::VmState vm{code, std::move(stack_), gas, 1, std::move(data), vm::VmLog::Null(), std::move(libraries)};
|
||||
auto c7 = prepare_vm_c7(gen_utime, gen_lt, td::make_ref<vm::CellSlice>(acc.addr->clone()), balance, config.get(),
|
||||
std::move(code), due_payment);
|
||||
vm.set_c7(c7); // tuple with SmartContractInfo
|
||||
// vm.incr_stack_trace(1); // enable stack dump after each step
|
||||
LOG(INFO) << "starting VM to run GET-method of smart contract " << acc_workchain_ << ":" << acc_addr_.to_hex();
|
||||
|
@ -1367,6 +1444,9 @@ void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice
|
|||
td::BufferSlice c7_info, result;
|
||||
if (mode & 8) {
|
||||
// serialize c7
|
||||
if (!(mode & 32)) {
|
||||
c7 = prepare_vm_c7(gen_utime, gen_lt, td::make_ref<vm::CellSlice>(acc.addr->clone()), balance);
|
||||
}
|
||||
vm::CellBuilder cb;
|
||||
if (!(vm::StackEntry{std::move(c7)}.serialize(cb) && cb.finalize_to(cell))) {
|
||||
fatal_error("cannot serialize c7");
|
||||
|
|
|
@ -62,7 +62,7 @@ class LiteQuery : public td::actor::Actor {
|
|||
td::BufferSlice buffer_;
|
||||
std::function<void()> continuation_;
|
||||
bool cont_set_{false};
|
||||
td::BufferSlice shard_proof_;
|
||||
td::BufferSlice shard_proof_, proof_;
|
||||
std::vector<Ref<vm::Cell>> roots_;
|
||||
std::vector<Ref<td::CntObject>> aux_objs_;
|
||||
std::vector<ton::BlockIdExt> blk_ids_;
|
||||
|
@ -98,6 +98,7 @@ class LiteQuery : public td::actor::Actor {
|
|||
bool finish_query(td::BufferSlice result, bool skip_cache_update = false);
|
||||
void alarm() override;
|
||||
void start_up() override;
|
||||
bool use_cache();
|
||||
void perform();
|
||||
void perform_getTime();
|
||||
void perform_getVersion();
|
||||
|
|
|
@ -129,6 +129,15 @@ td::Status ShardStateQ::init() {
|
|||
" contains BlockId " + hdr_id.to_str() +
|
||||
" different from the one originally required");
|
||||
}
|
||||
if (info.r1.master_ref.write().fetch_long(1)) {
|
||||
BlockIdExt mc_id;
|
||||
if (!block::tlb::t_ExtBlkRef.unpack(info.r1.master_ref, mc_id, nullptr)) {
|
||||
return td::Status::Error(-668, "cannot unpack master_ref in shardchain state of "s + blkid.to_str());
|
||||
}
|
||||
master_ref = mc_id;
|
||||
} else {
|
||||
master_ref = {};
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ class ShardStateQ : virtual public ShardState {
|
|||
bool before_split_{false};
|
||||
bool fake_split_{false};
|
||||
bool fake_merge_{false};
|
||||
td::optional<BlockIdExt> master_ref;
|
||||
|
||||
protected:
|
||||
friend class Ref<ShardStateQ>;
|
||||
|
@ -80,6 +81,9 @@ class ShardStateQ : virtual public ShardState {
|
|||
LogicalTime get_logical_time() const override {
|
||||
return lt;
|
||||
}
|
||||
td::optional<BlockIdExt> get_master_ref() const override {
|
||||
return master_ref;
|
||||
}
|
||||
td::Status validate_deep() const override;
|
||||
ShardStateQ* make_copy() const override;
|
||||
td::Result<Ref<MessageQueue>> message_queue() const override;
|
||||
|
|
|
@ -972,6 +972,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
|
||||
|
|
|
@ -44,6 +44,7 @@ class ShardState : public td::CntObject {
|
|||
virtual BlockIdExt get_block_id() const = 0;
|
||||
virtual RootHash root_hash() const = 0;
|
||||
virtual td::Ref<vm::Cell> root_cell() const = 0;
|
||||
virtual td::optional<BlockIdExt> get_master_ref() const = 0;
|
||||
|
||||
virtual td::Status validate_deep() const = 0;
|
||||
|
||||
|
|
Loading…
Reference in a new issue