mirror of
https://github.com/danog/ton.git
synced 2024-12-12 09:09:37 +01:00
781 lines
30 KiB
C++
781 lines
30 KiB
C++
|
/*
|
||
|
This file is part of TON Blockchain source code.
|
||
|
|
||
|
TON Blockchain is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU 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 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 General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
In addition, as a special exception, the copyright holders give permission
|
||
|
to link the code of portions of this program with the OpenSSL library.
|
||
|
You must obey the GNU General Public License in all respects for all
|
||
|
of the code used other than OpenSSL. If you modify file(s) with this
|
||
|
exception, you may extend this exception to your version of the file(s),
|
||
|
but you are not obligated to do so. If you do not wish to do so, delete this
|
||
|
exception statement from your version. If you delete this exception statement
|
||
|
from all source files in the program, then also delete it here.
|
||
|
|
||
|
Copyright 2017-2019 Telegram Systems LLP
|
||
|
*/
|
||
|
#include <cassert>
|
||
|
#include <algorithm>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
#include <iostream>
|
||
|
#include <sstream>
|
||
|
#include <fstream>
|
||
|
#include <memory>
|
||
|
#include <cstring>
|
||
|
#include <cstdlib>
|
||
|
#include <cmath>
|
||
|
#include <map>
|
||
|
#include <functional>
|
||
|
#include <limits>
|
||
|
#include <getopt.h>
|
||
|
|
||
|
#include "vm/stack.hpp"
|
||
|
#include "vm/boc.h"
|
||
|
|
||
|
#include "fift/Fift.h"
|
||
|
#include "fift/Dictionary.h"
|
||
|
#include "fift/SourceLookup.h"
|
||
|
#include "fift/words.h"
|
||
|
|
||
|
#include "td/utils/logging.h"
|
||
|
#include "td/utils/misc.h"
|
||
|
#include "td/utils/Parser.h"
|
||
|
#include "td/utils/port/path.h"
|
||
|
#include "td/utils/port/signals.h"
|
||
|
|
||
|
#include "block.h"
|
||
|
#include "block-parse.h"
|
||
|
#include "block-auto.h"
|
||
|
#include "mc-config.h"
|
||
|
|
||
|
#define PDO(__op) \
|
||
|
if (!(__op)) { \
|
||
|
ok = false; \
|
||
|
}
|
||
|
#define THRERR(__msg) \
|
||
|
if (!ok) { \
|
||
|
throw fift::IntError{__msg}; \
|
||
|
}
|
||
|
#define RETERR \
|
||
|
if (!ok) { \
|
||
|
return false; \
|
||
|
}
|
||
|
|
||
|
using td::Ref;
|
||
|
|
||
|
int verbosity;
|
||
|
|
||
|
enum { wc_master = -1, wc_base = 0 };
|
||
|
constexpr int wc_undef = std::numeric_limits<int>::min();
|
||
|
|
||
|
int workchain_id = wc_undef;
|
||
|
int global_id = 0;
|
||
|
|
||
|
typedef td::BitArray<256> hash_t;
|
||
|
|
||
|
struct SmcDescr {
|
||
|
hash_t addr;
|
||
|
int split_depth;
|
||
|
bool preinit_only;
|
||
|
td::RefInt256 gram_balance;
|
||
|
Ref<vm::DataCell> state_init; // StateInit
|
||
|
Ref<vm::DataCell> account; // Account
|
||
|
SmcDescr(const hash_t& _addr) : addr(_addr), split_depth(0), preinit_only(false) {
|
||
|
}
|
||
|
};
|
||
|
|
||
|
std::map<hash_t, SmcDescr> smart_contracts;
|
||
|
td::RefInt256 total_smc_balance{true, 0}, max_total_smc_balance;
|
||
|
|
||
|
struct PublicLibDescr {
|
||
|
Ref<vm::Cell> root;
|
||
|
std::set<hash_t> publishers;
|
||
|
PublicLibDescr(Ref<vm::Cell> _root) : root(std::move(_root)) {
|
||
|
}
|
||
|
};
|
||
|
|
||
|
std::map<hash_t, PublicLibDescr> public_libraries;
|
||
|
|
||
|
hash_t config_addr;
|
||
|
Ref<vm::Cell> config_param_root;
|
||
|
bool config_addr_set;
|
||
|
vm::Dictionary config_dict{32};
|
||
|
|
||
|
ton::UnixTime now;
|
||
|
|
||
|
bool set_config_smc(const SmcDescr& smc) {
|
||
|
if (config_addr_set || smc.preinit_only || workchain_id != wc_master || smc.split_depth) {
|
||
|
return false;
|
||
|
}
|
||
|
vm::CellSlice cs = load_cell_slice(smc.state_init);
|
||
|
bool ok = true;
|
||
|
PDO(block::gen::t_Maybe_natwidth_5.skip(cs) && block::gen::t_Maybe_TickTock.skip(cs) &&
|
||
|
block::gen::t_Maybe_Ref_Cell.skip(cs));
|
||
|
RETERR;
|
||
|
Ref<vm::Cell> data;
|
||
|
PDO(cs.fetch_ulong(1) == 1 && cs.fetch_ref_to(data));
|
||
|
THRERR("config smart contract must have non-empty data");
|
||
|
vm::CellSlice cs2 = load_cell_slice(data);
|
||
|
PDO(cs2.fetch_ref_to(data));
|
||
|
THRERR("first reference in config smart contract data must point to initial configuration");
|
||
|
PDO(block::valid_config_data(data, smc.addr));
|
||
|
THRERR("invalid smart contract configuration data");
|
||
|
config_addr = smc.addr;
|
||
|
config_param_root = std::move(data);
|
||
|
config_addr_set = true;
|
||
|
if (verbosity > 2) {
|
||
|
std::cerr << "set smart contract " << config_addr << " as the configuration smart contract with configuration:\n";
|
||
|
load_cell_slice(config_param_root).print_rec(std::cerr);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void interpret_set_workchain(vm::Stack& stack) {
|
||
|
workchain_id = stack.pop_smallint_range(0x7fffffff, -0x7fffffff);
|
||
|
}
|
||
|
|
||
|
void interpret_get_workchain(vm::Stack& stack) {
|
||
|
stack.push_smallint(workchain_id);
|
||
|
}
|
||
|
|
||
|
void interpret_set_global_id(vm::Stack& stack) {
|
||
|
global_id = stack.pop_smallint_range(0x7fffffff, -0x7fffffff);
|
||
|
}
|
||
|
|
||
|
void interpret_get_global_id(vm::Stack& stack) {
|
||
|
stack.push_smallint(global_id);
|
||
|
}
|
||
|
|
||
|
void interpret_get_verbosity(vm::Stack& stack) {
|
||
|
stack.push_smallint(GET_VERBOSITY_LEVEL());
|
||
|
}
|
||
|
|
||
|
void interpret_set_verbosity(vm::Stack& stack) {
|
||
|
int x = stack.pop_smallint_range(15);
|
||
|
SET_VERBOSITY_LEVEL(x);
|
||
|
}
|
||
|
|
||
|
void interpret_set_config_smartcontract(vm::Stack& stack) {
|
||
|
if (workchain_id != wc_master) {
|
||
|
throw fift::IntError{"configuration smart contract may be selected in masterchain only"};
|
||
|
}
|
||
|
if (config_addr_set) {
|
||
|
throw fift::IntError{"configuration smart contract already selected"};
|
||
|
}
|
||
|
td::RefInt256 int_addr = stack.pop_int_finite();
|
||
|
hash_t addr;
|
||
|
if (!int_addr->export_bits(addr.bits(), 256, false)) {
|
||
|
throw fift::IntError{"not a valid smart-contract address"};
|
||
|
}
|
||
|
auto it = smart_contracts.find(addr);
|
||
|
if (it == smart_contracts.end()) {
|
||
|
throw fift::IntError{"unknown smart contract"};
|
||
|
}
|
||
|
const SmcDescr& smc = it->second;
|
||
|
assert(smc.addr == addr);
|
||
|
if (smc.preinit_only) {
|
||
|
throw fift::IntError{"configuration smart contract must be completely initialized"};
|
||
|
}
|
||
|
if (!set_config_smc(smc)) {
|
||
|
throw fift::IntError{"invalid configuration smart contract"};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool is_empty_cell(Ref<vm::Cell> cell) {
|
||
|
bool is_special;
|
||
|
auto cs = load_cell_slice_special(std::move(cell), is_special);
|
||
|
return !is_special && cs.empty_ext();
|
||
|
}
|
||
|
|
||
|
bool add_public_library(hash_t lib_addr, hash_t smc_addr, Ref<vm::Cell> lib_root) {
|
||
|
if (lib_root.is_null() || lib_root->get_hash().as_array() != lib_addr.as_array()) {
|
||
|
return false;
|
||
|
}
|
||
|
auto ins = public_libraries.emplace(lib_addr, lib_root);
|
||
|
PublicLibDescr& lib = ins.first->second;
|
||
|
lib.publishers.insert(smc_addr);
|
||
|
if (verbosity > 2) {
|
||
|
std::cerr << "added " << (ins.second ? "new " : "") << "public library " << lib_addr << " with publisher "
|
||
|
<< smc_addr << std::endl;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
td::RefInt256 create_smartcontract(td::RefInt256 smc_addr, Ref<vm::Cell> code, Ref<vm::Cell> data,
|
||
|
Ref<vm::Cell> library, td::RefInt256 balance, int special, int split_depth,
|
||
|
int mode) {
|
||
|
if (is_empty_cell(code)) {
|
||
|
code.clear();
|
||
|
}
|
||
|
if (is_empty_cell(data)) {
|
||
|
data.clear();
|
||
|
}
|
||
|
if (is_empty_cell(library)) {
|
||
|
library.clear();
|
||
|
}
|
||
|
bool ok = true;
|
||
|
if (library.not_null()) {
|
||
|
PDO(block::valid_library_collection(library, false));
|
||
|
THRERR("not a valid library collection");
|
||
|
}
|
||
|
vm::CellBuilder cb;
|
||
|
if (!split_depth) {
|
||
|
PDO(cb.store_long_bool(0, 1));
|
||
|
} else {
|
||
|
PDO(cb.store_long_bool(1, 1) && cb.store_ulong_rchk_bool(split_depth, 5));
|
||
|
}
|
||
|
THRERR("invalid split_depth for a smart contract");
|
||
|
if (!special) {
|
||
|
PDO(cb.store_long_bool(0, 1));
|
||
|
} else {
|
||
|
PDO(cb.store_long_bool(1, 1) && cb.store_ulong_rchk_bool(special, 2));
|
||
|
}
|
||
|
THRERR("invalid special TickTock argument for a smart contract");
|
||
|
PDO(cb.store_maybe_ref(std::move(code)) && cb.store_maybe_ref(std::move(data)) && cb.store_maybe_ref(library));
|
||
|
THRERR("cannot store smart-contract code, data or library");
|
||
|
Ref<vm::DataCell> state_init = cb.finalize();
|
||
|
hash_t addr;
|
||
|
if (smc_addr.is_null()) {
|
||
|
addr = state_init->get_hash().as_array();
|
||
|
smc_addr = td::RefInt256{true};
|
||
|
PDO(smc_addr.write().import_bits(addr.data(), 0, 256, false));
|
||
|
} else if (mode == 1) {
|
||
|
throw fift::IntError{"cannot create uninitialized smart contracts with specified addresses"};
|
||
|
} else {
|
||
|
PDO(smc_addr->export_bits(addr.data(), 0, 256, false));
|
||
|
}
|
||
|
THRERR("cannot initialize smart-contract address");
|
||
|
if (verbosity > 2) {
|
||
|
std::cerr << "smart-contract address is ";
|
||
|
std::cerr << addr << " = " << smc_addr << std::endl;
|
||
|
}
|
||
|
PDO(mode || !sgn(balance));
|
||
|
THRERR("cannot set non-zero balance to smart contract unless it is initialized");
|
||
|
PDO(sgn(balance) >= 0);
|
||
|
THRERR("balance cannot be negative");
|
||
|
if (!mode) {
|
||
|
return smc_addr; // compute address only
|
||
|
}
|
||
|
auto it = smart_contracts.find(addr);
|
||
|
if (it != smart_contracts.end()) {
|
||
|
std::cerr << "smart contract " << addr << " already defined\n";
|
||
|
throw fift::IntError{"smart contract already exists"};
|
||
|
}
|
||
|
auto ins = smart_contracts.emplace(addr, addr);
|
||
|
assert(ins.second);
|
||
|
SmcDescr& smc = ins.first->second;
|
||
|
smc.split_depth = split_depth;
|
||
|
smc.preinit_only = (mode == 1);
|
||
|
smc.gram_balance = balance;
|
||
|
total_smc_balance += balance;
|
||
|
if (mode > 1) {
|
||
|
smc.state_init = std::move(state_init);
|
||
|
}
|
||
|
if (max_total_smc_balance.not_null() && total_smc_balance > max_total_smc_balance) {
|
||
|
throw fift::IntError{"total smart-contract balance exceeds limit"};
|
||
|
}
|
||
|
cb.reset();
|
||
|
PDO(cb.store_long_bool(0, 64) // account_storage$_ last_trans_lt:uint64
|
||
|
&& block::tlb::t_Grams.store_integer_value(cb, *balance) // balance.grams:Grams
|
||
|
&& cb.store_long_bool(0, 1)); // balance.other:ExtraCurrencyCollection
|
||
|
if (mode == 1) {
|
||
|
PDO(block::gen::t_AccountState.pack_account_uninit(cb));
|
||
|
} else {
|
||
|
PDO(block::gen::t_AccountState.pack_account_active(cb, vm::load_cell_slice_ref(smc.state_init)));
|
||
|
}
|
||
|
THRERR("cannot create smart-contract AccountStorage");
|
||
|
Ref<vm::DataCell> storage = cb.finalize();
|
||
|
vm::CellStorageStat stats;
|
||
|
PDO(stats.compute_used_storage(Ref<vm::Cell>(storage)));
|
||
|
if (verbosity > 2) {
|
||
|
std::cerr << "storage is:\n";
|
||
|
vm::load_cell_slice(storage).print_rec(std::cerr);
|
||
|
std::cerr << "stats: bits=" << stats.bits << ", cells=" << stats.cells << std::endl;
|
||
|
std::cerr << "block::gen::AccountStorage.validate_ref() = " << block::gen::t_AccountStorage.validate_ref(storage)
|
||
|
<< std::endl;
|
||
|
std::cerr << "block::tlb::AccountStorage.validate_ref() = " << block::tlb::t_AccountStorage.validate_ref(storage)
|
||
|
<< std::endl;
|
||
|
}
|
||
|
PDO(block::gen::t_AccountStorage.validate_ref(storage));
|
||
|
THRERR("AccountStorage of created smart-contract is invalid (?)");
|
||
|
cb.reset(); // build Account
|
||
|
PDO(cb.store_long_bool(1, 1)); // account$1
|
||
|
int ctor = 3; // addr_var$11
|
||
|
if (workchain_id >= -128 && workchain_id <= 127) {
|
||
|
ctor = 2; // addr_std$10
|
||
|
}
|
||
|
PDO(cb.store_long_bool(ctor, 2)); // addr_std$10 or addr_var$11
|
||
|
if (split_depth) {
|
||
|
PDO(cb.store_long_bool(1, 1) // just$1
|
||
|
&& cb.store_ulong_rchk_bool(split_depth, 5) // depth:(## 5)
|
||
|
&& cb.store_bits_bool(addr.cbits(), split_depth)); // rewrite pfx:(depth * Bit)
|
||
|
} else {
|
||
|
PDO(cb.store_long_bool(0, 1)); // nothing$0
|
||
|
}
|
||
|
PDO(cb.store_long_rchk_bool(workchain_id, ctor == 2 ? 8 : 32) && cb.store_bits_bool(addr.cbits(), 256));
|
||
|
THRERR("Cannot serialize addr:MsgAddressInt of the new smart contract");
|
||
|
// storage_stat:StorageInfo -> storage_stat.used:StorageUsed
|
||
|
PDO(block::store_UInt7(cb, stats.cells) // cells:(VarUInteger 7)
|
||
|
&& block::store_UInt7(cb, stats.bits) // bits:(VarUInteger 7)
|
||
|
&& block::store_UInt7(cb, stats.public_cells)); // public_cells:(VarUInteger 7)
|
||
|
THRERR("Cannot serialize used:StorageUsed of the new smart contract");
|
||
|
PDO(cb.store_long_bool(0, 33)); // last_paid:uint32 due_payment:(Maybe Grams)
|
||
|
PDO(cb.append_data_cell_bool(storage)); // storage:AccountStorage
|
||
|
THRERR("Cannot create Account of the new smart contract");
|
||
|
smc.account = cb.finalize();
|
||
|
if (verbosity > 2) {
|
||
|
std::cerr << "account is:\n";
|
||
|
vm::load_cell_slice(smc.account).print_rec(std::cerr);
|
||
|
std::cerr << "block::gen::Account.validate_ref() = " << block::gen::t_Account.validate_ref(smc.account)
|
||
|
<< std::endl;
|
||
|
std::cerr << "block::tlb::Account.validate_ref() = " << block::tlb::t_Account.validate_ref(smc.account)
|
||
|
<< std::endl;
|
||
|
}
|
||
|
PDO(block::gen::t_Account.validate_ref(smc.account));
|
||
|
THRERR("Account of created smart contract is invalid (?)");
|
||
|
if (library.not_null()) {
|
||
|
vm::Dictionary dict{std::move(library), 256};
|
||
|
ok &= dict.check_for_each([addr](Ref<vm::CellSlice> cs, td::ConstBitPtr key, int n) -> bool {
|
||
|
return !cs->prefetch_ulong(1) || add_public_library(key, addr, cs->prefetch_ref());
|
||
|
});
|
||
|
THRERR("Error processing libraries published by new smart contract");
|
||
|
}
|
||
|
return smc_addr;
|
||
|
}
|
||
|
|
||
|
// stores accounts:ShardAccounts
|
||
|
bool store_accounts(vm::CellBuilder& cb) {
|
||
|
vm::AugmentedDictionary dict{256, block::tlb::aug_ShardAccounts};
|
||
|
for (const auto& smc_pair : smart_contracts) {
|
||
|
const SmcDescr& smc = smc_pair.second;
|
||
|
CHECK(smc_pair.first == smc.addr);
|
||
|
vm::CellBuilder cb;
|
||
|
bool ok = cb.store_ref_bool(smc.account) // account_descr$_ acc:^Account
|
||
|
&& cb.store_zeroes_bool(256 + 64) // last_trans_hash:bits256 last_trans_lt:uint64
|
||
|
&& dict.set_builder(smc.addr.cbits(), 256, cb, vm::Dictionary::SetMode::Add);
|
||
|
CHECK(ok);
|
||
|
}
|
||
|
return std::move(dict).append_dict_to_bool(cb);
|
||
|
}
|
||
|
|
||
|
// stores libraries:(HashmapE 256 LibDescr)
|
||
|
bool store_public_libraries(vm::CellBuilder& cb) {
|
||
|
vm::Dictionary dict{256};
|
||
|
bool ok = true;
|
||
|
vm::CellBuilder empty_cb;
|
||
|
for (const auto& lib_pair : public_libraries) {
|
||
|
const PublicLibDescr pl = lib_pair.second;
|
||
|
PDO(pl.root->get_hash().as_array() == lib_pair.first.as_array());
|
||
|
vm::Dictionary publishers{256};
|
||
|
for (const auto& publisher : pl.publishers) {
|
||
|
PDO(publishers.set_builder(publisher.cbits(), 256, empty_cb, vm::Dictionary::SetMode::Add));
|
||
|
}
|
||
|
Ref<vm::Cell> root = std::move(publishers).extract_root_cell();
|
||
|
PDO(root.not_null());
|
||
|
THRERR("public library has an empty or invalid set of publishers");
|
||
|
vm::CellBuilder value_cb; // LibDescr
|
||
|
PDO(value_cb.store_long_bool(0, 2) && value_cb.store_ref_bool(pl.root) &&
|
||
|
value_cb.append_cellslice_bool(vm::load_cell_slice(std::move(root))));
|
||
|
THRERR("cannot create LibDescr for a public library");
|
||
|
PDO(dict.set_builder(lib_pair.first.cbits(), 256, value_cb, vm::Dictionary::SetMode::Add));
|
||
|
THRERR("cannot insert LibDescr of a public library into the public library collection");
|
||
|
}
|
||
|
PDO(std::move(dict).append_dict_to_bool(cb));
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
// stores config:ConfigParams
|
||
|
bool store_config_params(vm::CellBuilder& cb) {
|
||
|
return config_addr_set && config_param_root.not_null() &&
|
||
|
cb.store_bits_bool(config_addr.cbits(), 256) // _ config_addr:bits256
|
||
|
&& cb.store_ref_bool(config_param_root); // config:^(Hashmap 32 ^Cell)
|
||
|
}
|
||
|
|
||
|
// stores hash of initial masterchain validator set computed from configuration parameter 34
|
||
|
bool store_validator_list_hash(vm::CellBuilder& cb) {
|
||
|
Ref<vm::Cell> vset_cell = config_dict.lookup_ref(td::BitArray<32>{34});
|
||
|
auto res = block::Config::unpack_validator_set(std::move(vset_cell));
|
||
|
if (res.is_error()) {
|
||
|
LOG(ERROR) << "cannot unpack current validator set: " << res.move_as_error().to_string();
|
||
|
return false;
|
||
|
}
|
||
|
auto vset = res.move_as_ok();
|
||
|
LOG_CHECK(vset) << "unpacked validator set is empty";
|
||
|
auto ccvc = block::Config::unpack_catchain_validators_config(config_dict.lookup_ref(td::BitArray<32>{28}));
|
||
|
ton::ShardIdFull shard{ton::masterchainId};
|
||
|
auto nodes = block::Config::do_compute_validator_set(ccvc, shard, *vset, now, 0);
|
||
|
LOG_CHECK(!nodes.empty()) << "validator node list in unpacked validator set is empty";
|
||
|
auto vset_hash = block::compute_validator_set_hash(0, shard, std::move(nodes));
|
||
|
LOG(DEBUG) << "initial validator set hash is " << vset_hash;
|
||
|
return cb.store_long_bool(vset_hash, 32);
|
||
|
}
|
||
|
|
||
|
// stores custom:(Maybe ^McStateExtra)
|
||
|
bool store_custom(vm::CellBuilder& cb) {
|
||
|
if (workchain_id != wc_master) {
|
||
|
return cb.store_long_bool(0, 1); // nothing
|
||
|
}
|
||
|
vm::CellBuilder cb2, cb3;
|
||
|
bool ok = true;
|
||
|
PDO(cb2.store_long_bool(0xcc26, 16) // masterchain_state_extra#cc26
|
||
|
&& cb2.store_long_bool(0, 1) // shard_hashes:ShardHashes = (HashmapE 32 ^(BinTree ShardDescr))
|
||
|
&& store_config_params(cb2) // config:ConfigParams
|
||
|
&& cb3.store_long_bool(0, 16) // ^[ flags:(## 16) { flags = 0 }
|
||
|
&& store_validator_list_hash(cb3) // validator_list_hash_short:uint32
|
||
|
&& cb3.store_long_bool(0, 32) // catchain_seqno:uint32
|
||
|
&& cb3.store_bool_bool(true) // nx_cc_updated:Bool
|
||
|
&& cb3.store_zeroes_bool(1 + 65) // prev_blocks:OldMcBlocksInfo
|
||
|
&& cb3.store_long_bool(2, 1 + 1) // after_key_block:Bool last_key_block:(Maybe ...)
|
||
|
&& cb2.store_ref_bool(cb3.finalize()) // ]
|
||
|
&& block::CurrencyCollection{total_smc_balance}.store(cb2) // global_balance:CurrencyCollection
|
||
|
&& cb.store_long_bool(1, 1) // just
|
||
|
&& cb.store_ref_bool(cb2.finalize()));
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
Ref<vm::Cell> create_state() {
|
||
|
vm::CellBuilder cb, cb2;
|
||
|
now = static_cast<ton::UnixTime>(time(0));
|
||
|
bool ok = true;
|
||
|
PDO(workchain_id != wc_undef);
|
||
|
THRERR("workchain_id is unset, cannot generate state");
|
||
|
PDO(workchain_id != wc_master || config_addr_set);
|
||
|
THRERR("configuration smart contract must be selected");
|
||
|
PDO(cb.store_long_bool(0x9023afe2, 32) // shard_state#9023afe2
|
||
|
&& cb.store_long_bool(global_id, 32)); // global_id:int32
|
||
|
PDO(cb.store_long_bool(0, 8) && cb.store_long_bool(workchain_id, 32) &&
|
||
|
cb.store_long_bool(0, 64) // shard_id:ShardIdent
|
||
|
&& cb.store_long_bool(0, 32) // seq_no:#
|
||
|
&& cb.store_zeroes_bool(32) // vert_seq_no:#
|
||
|
&& cb.store_long_bool(now, 32) // gen_utime:uint32
|
||
|
&& cb.store_zeroes_bool(64) // gen_lt:uint64
|
||
|
&& cb.store_ones_bool(32) // min_ref_mc_seqno:uint32
|
||
|
&& cb2.store_zeroes_bool(1 + 64 + 2) // OutMsgQueueInfo
|
||
|
&& cb.store_ref_bool(cb2.finalize()) // out_msg_queue_info:^OutMsgQueueInfo
|
||
|
&& cb.store_long_bool(0, 1) // before_split:Bool
|
||
|
&& store_accounts(cb2) // accounts:^ShardAccounts
|
||
|
&& cb.store_ref_bool(cb2.finalize()) // ...
|
||
|
&& cb2.store_zeroes_bool(128) // ^[ overload_history:uint64 underload_history:uint64
|
||
|
&& block::CurrencyCollection{total_smc_balance}.store(cb2) // total_balance:CurrencyCollection
|
||
|
&& block::tlb::t_CurrencyCollection.null_value(cb2) // total_validator_fees:CurrencyCollection
|
||
|
&& store_public_libraries(cb2) // libraries:(Hashmap 256 LibDescr)
|
||
|
&& cb2.store_long_bool(0, 1) // master_ref:(Maybe BlkMasterInfo)
|
||
|
&& cb.store_ref_bool(cb2.finalize()) // ]
|
||
|
&& store_custom(cb)); // custom:(Maybe ^McStateExtra)
|
||
|
THRERR("cannot create blockchain state");
|
||
|
Ref<vm::Cell> cell = cb.finalize();
|
||
|
if (verbosity > 2) {
|
||
|
std::cerr << "shard_state is:\n";
|
||
|
vm::load_cell_slice(cell).print_rec(std::cerr);
|
||
|
std::cerr << "pretty-printed shard_state is:\n";
|
||
|
block::gen::t_ShardState.print_ref(std::cerr, cell);
|
||
|
std::cerr << "\n";
|
||
|
std::cerr << "block::gen::ShardState.validate_ref() = " << block::gen::t_ShardState.validate_ref(cell) << std::endl;
|
||
|
std::cerr << "block::tlb::ShardState.validate_ref() = " << block::tlb::t_ShardState.validate_ref(cell) << std::endl;
|
||
|
block::gen::ShardStateUnsplit::Record data;
|
||
|
bool ok1 = tlb::unpack_cell(cell, data);
|
||
|
std::cerr << "block::gen::ShardState.unpack_cell() = " << ok1 << std::endl;
|
||
|
if (ok1) {
|
||
|
std::cerr << "shard_id = " << data.shard_id
|
||
|
<< "; out_msg_queue_info = " << load_cell_slice(data.out_msg_queue_info)
|
||
|
<< "; total_balance = " << data.r1.total_balance << std::endl;
|
||
|
}
|
||
|
}
|
||
|
PDO(block::gen::t_ShardState.validate_ref(cell));
|
||
|
PDO(block::tlb::t_ShardState.validate_ref(cell));
|
||
|
THRERR("created an invalid ShardState record");
|
||
|
return cell;
|
||
|
}
|
||
|
|
||
|
// code (cell)
|
||
|
// data (cell)
|
||
|
// library (cell)
|
||
|
// balance (int)
|
||
|
// split_depth (int 0..32)
|
||
|
// special (int 0..3, +2 = tick, +1 = tock)
|
||
|
// [ address (uint256) ]
|
||
|
// mode (0 = compute address only, 1 = create uninit, 2 = create complete; +4 = with specified address)
|
||
|
// --> 256-bit address
|
||
|
void interpret_register_smartcontract(vm::Stack& stack) {
|
||
|
if (workchain_id == wc_undef) {
|
||
|
throw fift::IntError{"cannot register a smartcontract unless the workchain is specified first"};
|
||
|
}
|
||
|
td::RefInt256 spec_addr;
|
||
|
int mode = stack.pop_smallint_range(2 + 4); // allowed modes: 0 1 2 4 5 6
|
||
|
if (mode == 3) {
|
||
|
throw fift::IntError{"invalid mode"};
|
||
|
}
|
||
|
if (mode & 4) {
|
||
|
spec_addr = stack.pop_int_finite();
|
||
|
mode &= ~4;
|
||
|
}
|
||
|
int special = stack.pop_smallint_range(3);
|
||
|
if (special && workchain_id != wc_master) {
|
||
|
throw fift::IntError{"cannot create special smartcontracts outside of the masterchain"};
|
||
|
}
|
||
|
int split_depth = stack.pop_smallint_range(32);
|
||
|
td::RefInt256 balance = stack.pop_int_finite();
|
||
|
if (sgn(balance) < 0) {
|
||
|
throw fift::IntError{"initial balance of a smartcontract cannot be negative"};
|
||
|
}
|
||
|
if (sgn(balance) > 0 && !mode) {
|
||
|
throw fift::IntError{"cannot set non-zero balance if an account is not created"};
|
||
|
}
|
||
|
Ref<vm::Cell> library = stack.pop_cell();
|
||
|
Ref<vm::Cell> data = stack.pop_cell();
|
||
|
Ref<vm::Cell> code = stack.pop_cell();
|
||
|
td::RefInt256 addr = create_smartcontract(std::move(spec_addr), std::move(code), std::move(data), std::move(library),
|
||
|
std::move(balance), special, split_depth, mode);
|
||
|
if (addr.is_null()) {
|
||
|
throw fift::IntError{"internal error while creating smartcontract"};
|
||
|
}
|
||
|
stack.push(std::move(addr));
|
||
|
}
|
||
|
|
||
|
void interpret_create_state(vm::Stack& stack) {
|
||
|
if (!global_id) {
|
||
|
throw fift::IntError{
|
||
|
"(global) blockchain id must be set to a non-zero value: negative for test chains, positive for production"};
|
||
|
}
|
||
|
Ref<vm::Cell> state = create_state();
|
||
|
if (state.is_null()) {
|
||
|
throw fift::IntError{"could not create blockchain state"};
|
||
|
}
|
||
|
stack.push(std::move(state));
|
||
|
}
|
||
|
|
||
|
void interpret_get_config_dict(vm::Stack& stack) {
|
||
|
Ref<vm::Cell> value = config_dict.get_root_cell();
|
||
|
if (value.is_null()) {
|
||
|
stack.push_bool(false);
|
||
|
} else {
|
||
|
stack.push_cell(std::move(value));
|
||
|
stack.push_bool(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void interpret_get_config_param(vm::Stack& stack) {
|
||
|
int x = stack.pop_smallint_range(0x7fffffff, 0x80000000);
|
||
|
Ref<vm::Cell> value = config_dict.lookup_ref(td::BitArray<32>{x});
|
||
|
if (value.is_null()) {
|
||
|
stack.push_bool(false);
|
||
|
} else {
|
||
|
stack.push_cell(std::move(value));
|
||
|
stack.push_bool(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void interpret_set_config_param(vm::Stack& stack) {
|
||
|
int x = stack.pop_smallint_range(0x7fffffff, 0x80000000);
|
||
|
Ref<vm::Cell> value = stack.pop_cell();
|
||
|
if (verbosity > 2 && x >= 0) {
|
||
|
std::cerr << "setting configuration parameter #" << x << " to ";
|
||
|
// vm::load_cell_slice(value).print_rec(std::cerr);
|
||
|
block::gen::ConfigParam{x}.print_ref(std::cerr, value);
|
||
|
std::cerr << std::endl;
|
||
|
}
|
||
|
if (x >= 0 && !block::gen::ConfigParam{x}.validate_ref(value)) {
|
||
|
throw fift::IntError{"invalid value for indicated configuration parameter"};
|
||
|
}
|
||
|
if (!config_dict.set_ref(td::BitArray<32>{x}, std::move(value))) {
|
||
|
throw fift::IntError{"cannot set value of configuration parameter (value too long?)"};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void interpret_is_shard_state(vm::Stack& stack) {
|
||
|
Ref<vm::Cell> cell = stack.pop_cell();
|
||
|
if (verbosity > 4) {
|
||
|
std::cerr << "custom shard state is:\n";
|
||
|
vm::load_cell_slice(cell).print_rec(std::cerr);
|
||
|
std::cerr << "pretty-printed custom shard state is:\n";
|
||
|
block::gen::t_ShardState.print_ref(std::cerr, cell);
|
||
|
}
|
||
|
stack.push_bool(block::gen::t_ShardState.validate_ref(std::move(cell)));
|
||
|
}
|
||
|
|
||
|
void interpret_is_workchain_descr(vm::Stack& stack) {
|
||
|
Ref<vm::Cell> cell = stack.pop_cell();
|
||
|
if (verbosity > 4) {
|
||
|
std::cerr << "WorkchainDescr is:\n";
|
||
|
vm::load_cell_slice(cell).print_rec(std::cerr);
|
||
|
std::cerr << "pretty-printed WorkchainDescr is:\n";
|
||
|
block::gen::t_WorkchainDescr.print_ref(std::cerr, cell);
|
||
|
}
|
||
|
stack.push_bool(block::gen::t_WorkchainDescr.validate_ref(std::move(cell)));
|
||
|
}
|
||
|
|
||
|
void init_words_custom(fift::Dictionary& d) {
|
||
|
d.def_stack_word("verb@ ", interpret_get_verbosity);
|
||
|
d.def_stack_word("verb! ", interpret_set_verbosity);
|
||
|
d.def_stack_word("wcid@ ", interpret_get_workchain);
|
||
|
d.def_stack_word("wcid! ", interpret_set_workchain);
|
||
|
d.def_stack_word("globalid@ ", interpret_get_global_id);
|
||
|
d.def_stack_word("globalid! ", interpret_set_global_id);
|
||
|
d.def_stack_word("config@ ", interpret_get_config_param);
|
||
|
d.def_stack_word("config! ", interpret_set_config_param);
|
||
|
d.def_stack_word("(configdict) ", interpret_get_config_dict);
|
||
|
d.def_stack_word("register_smc ", interpret_register_smartcontract);
|
||
|
d.def_stack_word("set_config_smc ", interpret_set_config_smartcontract);
|
||
|
d.def_stack_word("create_state ", interpret_create_state);
|
||
|
d.def_stack_word("isShardState? ", interpret_is_shard_state);
|
||
|
d.def_stack_word("isWorkchainDescr? ", interpret_is_workchain_descr);
|
||
|
}
|
||
|
|
||
|
void usage(const char* progname) {
|
||
|
std::cerr
|
||
|
<< "Creates initial state for a TON blockchain, using configuration defined by Fift-language source files\n";
|
||
|
std::cerr
|
||
|
<< "usage: " << progname
|
||
|
<< " [-i] [-n] [-I <source-include-path>] {-L <library-fif-file>} <source-file1-fif> <source-file2-fif> ...\n";
|
||
|
std::cerr << "\t-n\tDo not preload preamble files `Fift.fif` and `CreateState.fif`\n"
|
||
|
"\t-i\tForce interactive mode even if explicit source file names are indicated\n"
|
||
|
"\t-I<source-search-path>\tSets colon-separated library source include path. If not indicated, "
|
||
|
"$FIFTPATH is used instead.\n"
|
||
|
"\t-L<library-fif-file>\tPre-loads a library source file\n"
|
||
|
"\t-v<verbosity-level>\tSet verbosity level\n";
|
||
|
std::exit(2);
|
||
|
}
|
||
|
|
||
|
void parse_include_path_set(std::string include_path_set, std::vector<std::string>& res) {
|
||
|
td::Parser parser(include_path_set);
|
||
|
while (!parser.empty()) {
|
||
|
auto path = parser.read_till_nofail(':');
|
||
|
if (!path.empty()) {
|
||
|
res.push_back(path.str());
|
||
|
}
|
||
|
parser.skip_nofail(':');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void preload_preamble(fift::Fift& fift, std::string filename, bool standard = true) {
|
||
|
auto status = fift.interpret_file(filename, "");
|
||
|
if (status.is_error()) {
|
||
|
LOG(ERROR) << "Error interpreting " << (standard ? "standard" : "application-specific") << " preamble file `"
|
||
|
<< filename << "`: " << status.error().message()
|
||
|
<< "\nCheck that correct include path is set by -I or by FIFTPATH environment variable, or disable "
|
||
|
"standard preamble by -n.\n";
|
||
|
std::exit(2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int main(int argc, char* const argv[]) {
|
||
|
td::set_default_failure_signal_handler().ensure();
|
||
|
bool interactive = false;
|
||
|
bool fift_preload = true, no_env = false, script_mode = false;
|
||
|
std::vector<std::string> library_source_files, source_list;
|
||
|
std::vector<std::string> source_include_path;
|
||
|
std::string ton_db_path;
|
||
|
|
||
|
fift::Fift::Config config;
|
||
|
|
||
|
int i;
|
||
|
int new_verbosity_level = VERBOSITY_NAME(INFO);
|
||
|
while (!script_mode && (i = getopt(argc, argv, "hinsI:L:v:")) != -1) {
|
||
|
switch (i) {
|
||
|
case 'i':
|
||
|
interactive = true;
|
||
|
break;
|
||
|
case 'n':
|
||
|
fift_preload = false;
|
||
|
break;
|
||
|
case 'I':
|
||
|
LOG(ERROR) << source_include_path;
|
||
|
parse_include_path_set(optarg, source_include_path);
|
||
|
no_env = true;
|
||
|
break;
|
||
|
case 's':
|
||
|
script_mode = true;
|
||
|
break;
|
||
|
case 'L':
|
||
|
library_source_files.emplace_back(optarg);
|
||
|
break;
|
||
|
case 'v':
|
||
|
new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer<int>(td::Slice(optarg)));
|
||
|
break;
|
||
|
case 'h':
|
||
|
default:
|
||
|
usage(argv[0]);
|
||
|
}
|
||
|
}
|
||
|
SET_VERBOSITY_LEVEL(new_verbosity_level);
|
||
|
|
||
|
while (optind < argc) {
|
||
|
source_list.emplace_back(argv[optind++]);
|
||
|
if (script_mode) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!no_env) {
|
||
|
const char* path = std::getenv("FIFTPATH");
|
||
|
if (path) {
|
||
|
parse_include_path_set(path ? path : "/usr/lib/fift", source_include_path);
|
||
|
}
|
||
|
}
|
||
|
std::string current_dir;
|
||
|
auto r_current_dir = td::realpath(".");
|
||
|
if (r_current_dir.is_ok()) {
|
||
|
current_dir = r_current_dir.move_as_ok();
|
||
|
source_include_path.push_back(current_dir);
|
||
|
}
|
||
|
config.source_lookup = fift::SourceLookup(std::make_unique<fift::OsFileLoader>());
|
||
|
for (auto& path : source_include_path) {
|
||
|
config.source_lookup.add_include_path(path);
|
||
|
}
|
||
|
|
||
|
fift::init_words_common(config.dictionary);
|
||
|
fift::init_words_vm(config.dictionary);
|
||
|
fift::init_words_ton(config.dictionary);
|
||
|
init_words_custom(config.dictionary);
|
||
|
|
||
|
if (script_mode) {
|
||
|
fift::import_cmdline_args(config.dictionary, source_list.empty() ? "" : source_list[0], argc - optind,
|
||
|
argv + optind);
|
||
|
}
|
||
|
|
||
|
fift::Fift fift(std::move(config));
|
||
|
|
||
|
if (fift_preload) {
|
||
|
preload_preamble(fift, "Fift.fif", true);
|
||
|
preload_preamble(fift, "CreateState.fif", false);
|
||
|
}
|
||
|
|
||
|
for (auto source : library_source_files) {
|
||
|
auto status = fift.interpret_file(source, "");
|
||
|
if (status.is_error()) {
|
||
|
std::cerr << "Error interpreting preloaded file `" << source << "`: " << status.error().to_string() << std::endl;
|
||
|
std::exit(2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (source_list.empty() && !interactive) {
|
||
|
std::cerr << "No Fift source files specified" << std::endl;
|
||
|
std::exit(2);
|
||
|
}
|
||
|
|
||
|
for (const auto& source : source_list) {
|
||
|
auto status = fift.interpret_file(source, current_dir);
|
||
|
if (status.is_error()) {
|
||
|
std::cerr << "Error interpreting file `" << source << "`: " << status.error().to_string() << std::endl;
|
||
|
std::exit(2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (interactive) {
|
||
|
fift.interpret_istream(std::cin, current_dir).ensure();
|
||
|
}
|
||
|
// show_total_cells();
|
||
|
}
|