Skip to content

Commit

Permalink
Add infrastructure for precompiled smartcontracts (#927)
Browse files Browse the repository at this point in the history
* Utils for writing precompiled contracts

* Precompiled contracts in config, override gas_usage for them

* Add base class for precompiled contracts

* Improve utils for precompiled smc

* Implement GETPRECOMPILEDGAS

* Enable precompiles by flag

* Process null data in PrecompiledSmartContract

* Fix ton_block wasm build

* Fix vm::util::store_(u)long

---------

Co-authored-by: SpyCheese <[email protected]>
  • Loading branch information
EmelyanenkoK and SpyCheese authored Mar 5, 2024
1 parent b09f910 commit 9d05696
Show file tree
Hide file tree
Showing 21 changed files with 991 additions and 95 deletions.
7 changes: 7 additions & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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}>)
Expand Down
4 changes: 4 additions & 0 deletions crypto/block/block.tlb
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,10 @@ _ SizeLimitsConfig = ConfigParam 43;
suspended_address_list#00 addresses:(HashmapE 288 Unit) suspended_until:uint32 = SuspendedAddressList;
_ SuspendedAddressList = ConfigParam 44;

precompiled_smc#b0 gas_usage:uint64 = PrecompiledSmc;
precompiled_contracts_config#c0 list:(HashmapE 256 PrecompiledSmc) = PrecompiledContractsConfig;
_ PrecompiledContractsConfig = ConfigParam 45;

oracle_bridge_params#_ bridge_address:bits256 oracle_mutlisig_address:bits256 oracles:(HashmapE 256 uint256) external_chain_address:bits256 = OracleBridgeParams;
_ OracleBridgeParams = ConfigParam 71; // Ethereum bridge
_ OracleBridgeParams = ConfigParam 72; // Binance Smart Chain bridge
Expand Down
27 changes: 27 additions & 0 deletions crypto/block/mc-config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -2316,4 +2327,20 @@ 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;
}

} // namespace block
10 changes: 10 additions & 0 deletions crypto/block/mc-config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

class Config {
enum {
default_mc_catchain_lifetime = 200,
Expand Down Expand Up @@ -644,6 +653,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,
Expand Down
170 changes: 170 additions & 0 deletions crypto/block/precompiled-smc/PrecompiledSmartContract.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 122 additions & 0 deletions crypto/block/precompiled-smc/PrecompiledSmartContract.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 9d05696

Please sign in to comment.