1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

Add infrastructure for precompiled smartcontracts (#927)

* Utils for writing precompiled contracts

* Precompiled contracts in config, override gas_usage for them

* Add base class for precompiled contracts

* Improve utils for precompiled smc

* Implement GETPRECOMPILEDGAS

* Enable precompiles by flag

* Process null data in PrecompiledSmartContract

* Fix ton_block wasm build

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

---------

Co-authored-by: SpyCheese <mikle98@yandex.ru>
This commit is contained in:
EmelyanenkoK 2024-03-05 16:54:49 +03:00 committed by GitHub
parent b09f910bf2
commit 9d05696452
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 991 additions and 95 deletions

View file

@ -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