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

Merge pull request #1543 from ton-blockchain/testnet

Merge developer branch
This commit is contained in:
EmelyanenkoK 2025-03-05 17:21:19 +03:00 committed by GitHub
commit 0439613bff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
184 changed files with 6872 additions and 2243 deletions

View file

@ -20,10 +20,12 @@ jobs:
submodules: 'recursive'
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v3.5.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v3.10.0
with:
driver-opts: image=moby/buildkit:v0.11.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@ -32,6 +34,17 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and export to Docker
uses: docker/build-push-action@v6
with:
load: true
context: ./
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test
- name: Test
run: |
docker run --rm -e "TEST=1" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test
- name: Get tag as branch name
id: tag
run: |

View file

@ -20,10 +20,10 @@ jobs:
submodules: 'recursive'
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v3.5.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v3.10.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v3

View file

@ -1,3 +1,17 @@
## 2025.03 Update
1. New extracurrency behavior introduced, check [GlobalVersions.md](./doc/GlobalVersions.md#version-10)
2. Optmization of validation process, in particular CellStorageStat.
3. Flag for speeding up broadcasts in various overlays.
4. Fixes for static builds for emulator and tonlibjson
5. Improving getstats output: adds
* Liteserver queries count
* Collated/validated blocks count, number of active sessions
* Persistent state sizes
* Initial sync progress
6. Fixes in logging, TON Storage, external message checking, persistent state downloading, UB in tonlib
Besides the work of the core team, this update is based on the efforts of @Sild from StonFi(UB in tonlib).
## 2025.02 Update
1. Series of improvement/fixes for `Config8.version >= 9`, check [GlobalVersions.md](./doc/GlobalVersions.md)
2. Fix for better discovery of updated nodes' (validators') IPs: retry dht queries

View file

@ -1,6 +1,13 @@
FROM ubuntu:22.04 AS builder
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git ninja-build libsodium-dev libmicrohttpd-dev liblz4-dev pkg-config autoconf automake libtool libjemalloc-dev lsb-release software-properties-common gnupg
rm /var/lib/dpkg/info/libc-bin.* && \
apt-get clean && \
apt-get update && \
apt install libc-bin && \
apt-get install -y build-essential cmake clang openssl libssl-dev zlib1g-dev gperf wget git \
ninja-build libsodium-dev libmicrohttpd-dev liblz4-dev pkg-config autoconf automake libtool \
libjemalloc-dev lsb-release software-properties-common gnupg
RUN wget https://apt.llvm.org/llvm.sh && \
chmod +x llvm.sh && \
@ -25,6 +32,7 @@ RUN mkdir build && \
blockchain-explorer emulator tonlibjson http-proxy adnl-proxy
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y wget curl libatomic1 openssl libsodium-dev libmicrohttpd-dev liblz4-dev libjemalloc-dev htop \
net-tools netcat iptraf-ng jq tcpdump pv plzip && \

View file

@ -526,10 +526,12 @@ void CatChainReceiverImpl::start_up() {
for (td::uint32 i = 0; i < get_sources_cnt(); i++) {
root_keys.emplace(get_source(i)->get_hash(), OVERLAY_MAX_ALLOWED_PACKET_SIZE);
}
td::actor::send_closure(overlay_manager_, &overlay::Overlays::create_private_overlay,
overlay::OverlayOptions overlay_options;
overlay_options.broadcast_speed_multiplier_ = opts_.broadcast_speed_multiplier;
td::actor::send_closure(overlay_manager_, &overlay::Overlays::create_private_overlay_ex,
get_source(local_idx_)->get_adnl_id(), overlay_full_id_.clone(), std::move(ids),
make_callback(), overlay::OverlayPrivacyRules{0, 0, std::move(root_keys)},
R"({ "type": "catchain" })");
R"({ "type": "catchain" })", std::move(overlay_options));
CHECK(root_block_);

View file

@ -19,6 +19,6 @@
namespace ton {
// See doc/GlobalVersions.md
const int SUPPORTED_VERSION = 9;
constexpr int SUPPORTED_VERSION = 10;
}

View file

@ -360,7 +360,6 @@ MsgProcessedUptoCollection::MsgProcessedUptoCollection(ton::ShardIdFull _owner,
z.shard = key.get_uint(64);
z.mc_seqno = (unsigned)((key + 64).get_uint(32));
z.last_inmsg_lt = value.write().fetch_ulong(64);
// std::cerr << "ProcessedUpto shard " << std::hex << z.shard << std::dec << std::endl;
return value.write().fetch_bits_to(z.last_inmsg_hash) && z.shard && ton::shard_contains(owner.shard, z.shard);
});
}
@ -862,8 +861,10 @@ td::Status ShardState::unpack_out_msg_queue_info(Ref<vm::Cell> out_msg_queue_inf
out_msg_queue_ =
std::make_unique<vm::AugmentedDictionary>(std::move(qinfo.out_queue), 352, block::tlb::aug_OutMsgQueue);
if (verbosity >= 3 * 1) {
LOG(DEBUG) << "unpacking ProcessedUpto of our previous block " << id_.to_str();
block::gen::t_ProcessedInfo.print(std::cerr, qinfo.proc_info);
FLOG(DEBUG) {
sb << "unpacking ProcessedUpto of our previous block " << id_.to_str();
block::gen::t_ProcessedInfo.print(sb, qinfo.proc_info);
};
}
if (!block::gen::t_ProcessedInfo.validate_csr(1024, qinfo.proc_info)) {
return td::Status::Error(
@ -1349,6 +1350,35 @@ bool CurrencyCollection::clamp(const CurrencyCollection& other) {
return ok || invalidate();
}
bool CurrencyCollection::check_extra_currency_limit(td::uint32 max_currencies) const {
td::uint32 count = 0;
return vm::Dictionary{extra, 32}.check_for_each([&](td::Ref<vm::CellSlice>, td::ConstBitPtr, int) {
++count;
return count <= max_currencies;
});
}
bool CurrencyCollection::remove_zero_extra_currencies(Ref<vm::Cell>& root, td::uint32 max_currencies) {
td::uint32 count = 0;
vm::Dictionary dict{root, 32};
int res = dict.filter([&](const vm::CellSlice& cs, td::ConstBitPtr, int) -> int {
++count;
if (count > max_currencies) {
return -1;
}
td::RefInt256 val = tlb::t_VarUInteger_32.as_integer(cs);
if (val.is_null()) {
return -1;
}
return val->sgn() > 0;
});
if (res < 0) {
return false;
}
root = dict.get_root_cell();
return true;
}
bool CurrencyCollection::operator==(const CurrencyCollection& other) const {
return is_valid() && other.is_valid() && !td::cmp(grams, other.grams) &&
(extra.not_null() == other.extra.not_null()) &&

View file

@ -391,6 +391,8 @@ struct CurrencyCollection {
CurrencyCollection operator-(CurrencyCollection&& other) const;
CurrencyCollection operator-(td::RefInt256 other_grams) const;
bool clamp(const CurrencyCollection& other);
bool check_extra_currency_limit(td::uint32 max_currencies) const;
static bool remove_zero_extra_currencies(Ref<vm::Cell>& root, td::uint32 max_currencies);
bool store(vm::CellBuilder& cb) const;
bool store_or_zero(vm::CellBuilder& cb) const;
bool fetch(vm::CellSlice& cs);

View file

@ -801,7 +801,7 @@ size_limits_config#01 max_msg_bits:uint32 max_msg_cells:uint32 max_library_cells
max_ext_msg_size:uint32 max_ext_msg_depth:uint16 = SizeLimitsConfig;
size_limits_config_v2#02 max_msg_bits:uint32 max_msg_cells:uint32 max_library_cells:uint32 max_vm_data_depth:uint16
max_ext_msg_size:uint32 max_ext_msg_depth:uint16 max_acc_state_cells:uint32 max_acc_state_bits:uint32
max_acc_public_libraries:uint32 defer_out_queue_size_limit:uint32 = SizeLimitsConfig;
max_acc_public_libraries:uint32 defer_out_queue_size_limit:uint32 max_msg_extra_currencies:uint32 = SizeLimitsConfig;
_ SizeLimitsConfig = ConfigParam 43;
// key is [ wc:int32 addr:uint256 ]

View file

@ -163,8 +163,11 @@ td::Status ConfigInfo::unpack() {
}
gen::McStateExtra::Record extra_info;
if (!tlb::unpack_cell(state_extra_root_, extra_info)) {
vm::load_cell_slice(state_extra_root_).print_rec(std::cerr);
block::gen::t_McStateExtra.print_ref(std::cerr, state_extra_root_);
FLOG(WARNING) {
sb << "state extra information is invalid: ";
vm::load_cell_slice(state_extra_root_).print_rec(sb);
block::gen::t_McStateExtra.print_ref(sb, state_extra_root_);
};
return td::Status::Error("state extra information is invalid");
}
gen::ValidatorInfo::Record validator_info;
@ -1067,7 +1070,6 @@ Ref<McShardHash> ShardConfig::get_shard_hash(ton::ShardIdFull id, bool exact) co
ton::ShardIdFull true_id;
vm::CellSlice cs;
if (get_shard_hash_raw(cs, id, true_id, exact)) {
// block::gen::t_ShardDescr.print(std::cerr, vm::CellSlice{cs});
return McShardHash::unpack(cs, true_id);
} else {
return {};
@ -1637,8 +1639,10 @@ bool ShardConfig::set_shard_info(ton::ShardIdFull shard, Ref<vm::Cell> value) {
if (!gen::t_BinTree_ShardDescr.validate_ref(1024, value)) {
LOG(ERROR) << "attempting to store an invalid (BinTree ShardDescr) at shard configuration position "
<< shard.to_str();
gen::t_BinTree_ShardDescr.print_ref(std::cerr, value);
vm::load_cell_slice(value).print_rec(std::cerr);
FLOG(WARNING) {
gen::t_BinTree_ShardDescr.print_ref(sb, value);
vm::load_cell_slice(value).print_rec(sb);
};
return false;
}
auto root = shard_hashes_dict_->lookup_ref(td::BitArray<32>{shard.workchain});
@ -1956,6 +1960,7 @@ td::Result<SizeLimitsConfig> Config::do_get_size_limits_config(td::Ref<vm::CellS
limits.max_acc_state_cells = rec.max_acc_state_cells;
limits.max_acc_public_libraries = rec.max_acc_public_libraries;
limits.defer_out_queue_size_limit = rec.defer_out_queue_size_limit;
limits.max_msg_extra_currencies = rec.max_msg_extra_currencies;
};
gen::SizeLimitsConfig::Record_size_limits_config rec_v1;
gen::SizeLimitsConfig::Record_size_limits_config_v2 rec_v2;

View file

@ -397,6 +397,7 @@ struct SizeLimitsConfig {
td::uint32 max_acc_state_bits = (1 << 16) * 1023;
td::uint32 max_acc_public_libraries = 256;
td::uint32 defer_out_queue_size_limit = 256;
td::uint32 max_msg_extra_currencies = 2;
};
struct CatchainValidatorsConfig {

View file

@ -138,7 +138,6 @@ bool OutputQueueMerger::add_root(int src, Ref<vm::Cell> outmsg_root) {
if (outmsg_root.is_null()) {
return true;
}
//block::gen::HashmapAug{352, block::gen::t_EnqueuedMsg, block::gen::t_uint64}.print_ref(std::cerr, outmsg_root);
auto kv = std::make_unique<MsgKeyValue>(src, std::move(outmsg_root));
if (kv->replace_by_prefix(common_pfx.cbits(), common_pfx_len)) {
heap.push_back(std::move(kv));

View file

@ -446,8 +446,10 @@ bool Account::unpack(Ref<vm::CellSlice> shard_account, ton::UnixTime now, bool s
return false;
}
if (verbosity > 2) {
shard_account->print_rec(std::cerr, 2);
block::gen::t_ShardAccount.print(std::cerr, *shard_account);
FLOG(INFO) {
shard_account->print_rec(sb, 2);
block::gen::t_ShardAccount.print(sb, shard_account);
};
}
block::gen::ShardAccount::Record acc_info;
if (!(block::tlb::t_ShardAccount.validate_csr(shard_account) && tlb::unpack_exact(shard_account.write(), acc_info))) {
@ -737,9 +739,11 @@ bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig*
return false;
}
if (verbosity > 2) {
fprintf(stderr, "unpacking inbound message for a new transaction: ");
block::gen::t_Message_Any.print_ref(std::cerr, in_msg);
load_cell_slice(in_msg).print_rec(std::cerr);
FLOG(INFO) {
sb << "unpacking inbound message for a new transaction: ";
block::gen::t_Message_Any.print_ref(sb, in_msg);
load_cell_slice(in_msg).print_rec(sb);
};
}
auto cs = vm::load_cell_slice(in_msg);
int tag = block::gen::t_CommonMsgInfo.get_tag(cs);
@ -1550,11 +1554,13 @@ bool Transaction::run_precompiled_contract(const ComputePhaseConfig& cfg, precom
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);
FLOG(INFO) {
sb << "new smart contract data: ";
bool can_be_special = true;
load_cell_slice_special(cp.new_data, can_be_special).print_rec(sb);
sb << "output actions: ";
block::gen::OutList{out_act_num}.print_ref(sb, cp.actions);
};
}
}
cp.mode = 0;
@ -1619,7 +1625,6 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
if (in_msg_state.not_null()) {
LOG(DEBUG) << "HASH(in_msg_state) = " << in_msg_state->get_hash().bits().to_hex(256)
<< ", account_state_hash = " << account.state_hash.to_hex();
// vm::load_cell_slice(in_msg_state).print_rec(std::cerr);
} else {
LOG(DEBUG) << "in_msg_state is null";
}
@ -1775,11 +1780,13 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
cp.actions = vm.get_committed_state().c5; // c5 -> action list
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);
FLOG(INFO) {
sb << "new smart contract data: ";
bool can_be_special = true;
load_cell_slice_special(cp.new_data, can_be_special).print_rec(sb);
sb << "output actions: ";
block::gen::OutList{out_act_num}.print_ref(sb, cp.actions);
};
}
}
cp.mode = 0;
@ -1993,9 +2000,9 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) {
ap.remaining_balance += ap.reserved_balance;
CHECK(ap.remaining_balance.is_valid());
if (ap.acc_delete_req) {
CHECK(ap.remaining_balance.is_zero());
CHECK(cfg.extra_currency_v2 ? ap.remaining_balance.grams->sgn() == 0 : ap.remaining_balance.is_zero());
ap.acc_status_change = ActionPhase::acst_deleted;
acc_status = Account::acc_deleted;
acc_status = (ap.remaining_balance.is_zero() ? Account::acc_deleted : Account::acc_uninit);
was_deleted = true;
}
ap.success = true;
@ -2465,6 +2472,20 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
LOG(DEBUG) << "invalid destination address in a proposed outbound message";
return check_skip_invalid(36); // invalid destination address
}
if (cfg.extra_currency_v2) {
CurrencyCollection value;
if (!value.unpack(info.value)) {
LOG(DEBUG) << "invalid value:ExtraCurrencies in a proposed outbound message";
return check_skip_invalid(37); // invalid value:CurrencyCollection
}
if (!CurrencyCollection::remove_zero_extra_currencies(value.extra, cfg.size_limits.max_msg_extra_currencies)) {
LOG(DEBUG) << "invalid value:ExtraCurrencies in a proposed outbound message: too many currencies (max "
<< cfg.size_limits.max_msg_extra_currencies << ")";
// Dict should be valid, since it was checked in t_OutListNode.validate_ref, so error here means limit exceeded
return check_skip_invalid(41); // invalid value:CurrencyCollection : too many extra currencies
}
info.value = value.pack();
}
// fetch message pricing info
const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain());
@ -2517,7 +2538,7 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
};
add_used_storage(msg.init, 3); // message init
add_used_storage(msg.body, 3); // message body (the root cell itself is not counted)
if (!ext_msg) {
if (!ext_msg && !cfg.extra_currency_v2) {
add_used_storage(info.value->prefetch_ref(), 0);
}
auto collect_fine = [&] {
@ -2588,11 +2609,19 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
if (act_rec.mode & 0x80) {
// attach all remaining balance to this message
req = ap.remaining_balance;
if (cfg.extra_currency_v2) {
req.grams = ap.remaining_balance.grams;
} else {
req = ap.remaining_balance;
}
act_rec.mode &= ~1; // pay fees from attached value
} else if (act_rec.mode & 0x40) {
// attach all remaining balance of the inbound message (in addition to the original value)
req += msg_balance_remaining;
if (cfg.extra_currency_v2) {
req.grams += msg_balance_remaining.grams;
} else {
req += msg_balance_remaining;
}
if (!(act_rec.mode & 1)) {
req -= ap.action_fine;
if (compute_phase) {
@ -2632,6 +2661,11 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
return check_skip_invalid(37); // not enough grams
}
if (cfg.extra_currency_v2 && !req.check_extra_currency_limit(cfg.size_limits.max_msg_extra_currencies)) {
LOG(DEBUG) << "too many extra currencies in the message : max " << cfg.size_limits.max_msg_extra_currencies;
return check_skip_invalid(41); // to many extra currencies
}
Ref<vm::Cell> new_extra;
if (!block::sub_extra_currency(ap.remaining_balance.extra, req.extra, new_extra)) {
@ -2673,7 +2707,11 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
// clear msg_balance_remaining if it has been used
if (act_rec.mode & 0xc0) {
msg_balance_remaining.set_zero();
if (cfg.extra_currency_v2) {
msg_balance_remaining.grams = td::zero_refint();
} else {
msg_balance_remaining.set_zero();
}
}
// update balance
@ -2725,14 +2763,18 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
}
if (!block::gen::t_Message_Any.validate_ref(new_msg)) {
LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to automated check";
block::gen::t_Message_Any.print_ref(std::cerr, new_msg);
vm::load_cell_slice(new_msg).print_rec(std::cerr);
FLOG(INFO) {
block::gen::t_Message_Any.print_ref(sb, new_msg);
vm::load_cell_slice(new_msg).print_rec(sb);
};
collect_fine();
return -1;
}
if (verbosity > 2) {
std::cerr << "converted outbound message: ";
block::gen::t_Message_Any.print_ref(std::cerr, new_msg);
FLOG(INFO) {
sb << "converted outbound message: ";
block::gen::t_Message_Any.print_ref(sb, new_msg);
};
}
ap.msgs_created++;
@ -2743,8 +2785,13 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
ap.total_fwd_fees += fees_total;
if ((act_rec.mode & 0xa0) == 0xa0) {
CHECK(ap.remaining_balance.is_zero());
ap.acc_delete_req = ap.reserved_balance.is_zero();
if (cfg.extra_currency_v2) {
CHECK(ap.remaining_balance.grams->sgn() == 0);
ap.acc_delete_req = ap.reserved_balance.grams->sgn() == 0;
} else {
CHECK(ap.remaining_balance.is_zero());
ap.acc_delete_req = ap.reserved_balance.is_zero();
}
}
ap.tot_msg_bits += sstat.bits + new_msg_bits;
@ -3015,7 +3062,8 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) {
bp.fwd_fees -= bp.fwd_fees_collected;
total_fees += td::make_refint(bp.fwd_fees_collected);
// serialize outbound message
info.created_lt = end_lt++;
info.created_lt = start_lt + 1 + out_msgs.size();
end_lt++;
info.created_at = now;
vm::CellBuilder cb;
CHECK(cb.store_long_bool(5, 4) // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
@ -3045,8 +3093,10 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) {
}
CHECK(cb.finalize_to(bp.out_msg));
if (verbosity > 2) {
LOG(INFO) << "generated bounced message: ";
block::gen::t_Message_Any.print_ref(std::cerr, bp.out_msg);
FLOG(INFO) {
sb << "generated bounced message: ";
block::gen::t_Message_Any.print_ref(sb, bp.out_msg);
};
}
out_msgs.push_back(bp.out_msg);
bp.ok = true;
@ -3094,6 +3144,7 @@ bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const {
* Tries to update the storage statistics based on the old storage statistics and old account state without fully recomputing it.
*
* It succeeds if only root cell of AccountStorage is changed.
* old_cs and new_cell are AccountStorage without extra currencies (if global_version >= 10).
*
* @param old_stat The old storage statistics.
* @param old_cs The old AccountStorage.
@ -3127,13 +3178,48 @@ static td::optional<vm::CellStorageStat> try_update_storage_stat(const vm::CellS
return new_stat;
}
/**
* Removes extra currencies dict from AccountStorage.
*
* This is used for computing account storage stats.
*
* @param storage_cs AccountStorage as CellSlice.
*
* @returns AccountStorage without extra currencies as Cell.
*/
static td::Ref<vm::Cell> storage_without_extra_currencies(td::Ref<vm::CellSlice> storage_cs) {
block::gen::AccountStorage::Record rec;
if (!block::gen::csr_unpack(storage_cs, rec)) {
LOG(ERROR) << "failed to unpack AccountStorage";
return {};
}
if (rec.balance->size_refs() > 0) {
block::gen::CurrencyCollection::Record balance;
if (!block::gen::csr_unpack(rec.balance, balance)) {
LOG(ERROR) << "failed to unpack AccountStorage";
return {};
}
balance.other = vm::CellBuilder{}.store_zeroes(1).as_cellslice_ref();
if (!block::gen::csr_pack(rec.balance, balance)) {
LOG(ERROR) << "failed to pack AccountStorage";
return {};
}
}
td::Ref<vm::Cell> cell;
if (!block::gen::pack_cell(cell, rec)) {
LOG(ERROR) << "failed to pack AccountStorage";
return {};
}
return cell;
}
namespace transaction {
/**
* Computes the new state of the account.
*
* @returns True if the state computation is successful, false otherwise.
*/
bool Transaction::compute_state() {
bool Transaction::compute_state(const SerializeConfig& cfg) {
if (new_total_state.not_null()) {
return true;
}
@ -3167,11 +3253,13 @@ bool Transaction::compute_state() {
auto frozen_state = cb2.finalize();
frozen_hash = frozen_state->get_hash().bits();
if (verbosity >= 3 * 1) { // !!!DEBUG!!!
std::cerr << "freezing state of smart contract: ";
block::gen::t_StateInit.print_ref(std::cerr, frozen_state);
CHECK(block::gen::t_StateInit.validate_ref(frozen_state));
CHECK(block::tlb::t_StateInit.validate_ref(frozen_state));
std::cerr << "with hash " << frozen_hash.to_hex() << std::endl;
FLOG(INFO) {
sb << "freezing state of smart contract: ";
block::gen::t_StateInit.print_ref(sb, frozen_state);
CHECK(block::gen::t_StateInit.validate_ref(frozen_state));
CHECK(block::tlb::t_StateInit.validate_ref(frozen_state));
sb << "with hash " << frozen_hash.to_hex();
};
}
}
new_code.clear();
@ -3203,13 +3291,27 @@ bool Transaction::compute_state() {
new_inner_state.clear();
}
vm::CellStorageStat& stats = new_storage_stat;
auto new_stats = try_update_storage_stat(account.storage_stat, account.storage, storage);
td::Ref<vm::CellSlice> old_storage_for_stat = account.storage;
td::Ref<vm::Cell> new_storage_for_stat = storage;
if (cfg.extra_currency_v2) {
new_storage_for_stat = storage_without_extra_currencies(new_storage);
if (new_storage_for_stat.is_null()) {
return false;
}
if (old_storage_for_stat.not_null()) {
old_storage_for_stat = vm::load_cell_slice_ref(storage_without_extra_currencies(old_storage_for_stat));
if (old_storage_for_stat.is_null()) {
return false;
}
}
}
auto new_stats = try_update_storage_stat(account.storage_stat, old_storage_for_stat, storage);
if (new_stats) {
stats = new_stats.unwrap();
} else {
TD_PERF_COUNTER(transaction_storage_stat_b);
td::Timer timer;
stats.add_used_storage(Ref<vm::Cell>(storage)).ensure();
stats.add_used_storage(new_storage_for_stat).ensure();
if (timer.elapsed() > 0.1) {
LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s";
}
@ -3229,8 +3331,10 @@ bool Transaction::compute_state() {
CHECK(cb.append_data_cell_bool(std::move(storage)));
new_total_state = cb.finalize();
if (verbosity > 2) {
std::cerr << "new account state: ";
block::gen::t_Account.print_ref(std::cerr, new_total_state);
FLOG(INFO) {
sb << "new account state: ";
block::gen::t_Account.print_ref(sb, new_total_state);
};
}
CHECK(block::tlb::t_Account.validate_ref(new_total_state));
return true;
@ -3243,11 +3347,11 @@ bool Transaction::compute_state() {
*
* @returns True if the serialization is successful, False otherwise.
*/
bool Transaction::serialize() {
bool Transaction::serialize(const SerializeConfig& cfg) {
if (root.not_null()) {
return true;
}
if (!compute_state()) {
if (!compute_state(cfg)) {
return false;
}
vm::Dictionary dict{15};
@ -3322,22 +3426,28 @@ bool Transaction::serialize() {
return false;
}
if (verbosity >= 3 * 1) {
std::cerr << "new transaction: ";
block::gen::t_Transaction.print_ref(std::cerr, root);
vm::load_cell_slice(root).print_rec(std::cerr);
FLOG(INFO) {
sb << "new transaction: ";
block::gen::t_Transaction.print_ref(sb, root);
vm::load_cell_slice(root).print_rec(sb);
};
}
if (!block::gen::t_Transaction.validate_ref(4096, root)) {
LOG(ERROR) << "newly-generated transaction failed to pass automated validation:";
vm::load_cell_slice(root).print_rec(std::cerr);
block::gen::t_Transaction.print_ref(std::cerr, root);
FLOG(INFO) {
vm::load_cell_slice(root).print_rec(sb);
block::gen::t_Transaction.print_ref(sb, root);
};
root.clear();
return false;
}
if (!block::tlb::t_Transaction.validate_ref(4096, root)) {
LOG(ERROR) << "newly-generated transaction failed to pass hand-written validation:";
vm::load_cell_slice(root).print_rec(std::cerr);
block::gen::t_Transaction.print_ref(std::cerr, root);
FLOG(INFO) {
vm::load_cell_slice(root).print_rec(sb);
block::gen::t_Transaction.print_ref(sb, root);
};
root.clear();
return false;
}
@ -3707,6 +3817,7 @@ bool Account::libraries_changed() const {
* @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`.
* @param compute_phase_cfg Pointer to store the compute phase configuration.
* @param action_phase_cfg Pointer to store the action phase configuration.
* @param serialize_cfg Pointer to store the serialize phase configuration.
* @param masterchain_create_fee Pointer to store the masterchain create fee.
* @param basechain_create_fee Pointer to store the basechain create fee.
* @param wc The workchain ID.
@ -3715,15 +3826,15 @@ bool Account::libraries_changed() const {
td::Status FetchConfigParams::fetch_config_params(
const block::ConfigInfo& config, Ref<vm::Cell>* old_mparams, std::vector<block::StoragePrices>* storage_prices,
StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg,
ActionPhaseConfig* action_phase_cfg, td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee,
ton::WorkchainId wc, ton::UnixTime now) {
ActionPhaseConfig* action_phase_cfg, SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee,
td::RefInt256* basechain_create_fee, ton::WorkchainId wc, ton::UnixTime now) {
auto prev_blocks_info = config.get_prev_blocks_info();
if (prev_blocks_info.is_error()) {
return prev_blocks_info.move_as_error_prefix(
td::Status::Error(-668, "cannot fetch prev blocks info from masterchain configuration: "));
}
return fetch_config_params(config, prev_blocks_info.move_as_ok(), old_mparams, storage_prices, storage_phase_cfg,
rand_seed, compute_phase_cfg, action_phase_cfg, masterchain_create_fee,
rand_seed, compute_phase_cfg, action_phase_cfg, serialize_cfg, masterchain_create_fee,
basechain_create_fee, wc, now);
}
@ -3738,6 +3849,7 @@ td::Status FetchConfigParams::fetch_config_params(
* @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`.
* @param compute_phase_cfg Pointer to store the compute phase configuration.
* @param action_phase_cfg Pointer to store the action phase configuration.
* @param serialize_cfg Pointer to store the serialize phase configuration.
* @param masterchain_create_fee Pointer to store the masterchain create fee.
* @param basechain_create_fee Pointer to store the basechain create fee.
* @param wc The workchain ID.
@ -3747,8 +3859,8 @@ td::Status FetchConfigParams::fetch_config_params(
const block::Config& config, td::Ref<vm::Tuple> prev_blocks_info, Ref<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* storage_prices, StoragePhaseConfig* storage_phase_cfg,
td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg,
td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, ton::WorkchainId wc,
ton::UnixTime now) {
SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee,
ton::WorkchainId wc, ton::UnixTime now) {
*old_mparams = config.get_config_param(9);
{
auto res = config.get_storage_prices();
@ -3820,6 +3932,10 @@ td::Status FetchConfigParams::fetch_config_params(
action_phase_cfg->disable_custom_fess = config.get_global_version() >= 8;
action_phase_cfg->reserve_extra_enabled = config.get_global_version() >= 9;
action_phase_cfg->mc_blackhole_addr = config.get_burning_config().blackhole_addr;
action_phase_cfg->extra_currency_v2 = config.get_global_version() >= 10;
}
{
serialize_cfg->extra_currency_v2 = config.get_global_version() >= 10;
}
{
// fetch block_grams_created

View file

@ -170,12 +170,17 @@ struct ActionPhaseConfig {
bool message_skip_enabled{false};
bool disable_custom_fess{false};
bool reserve_extra_enabled{false};
bool extra_currency_v2{false};
td::optional<td::Bits256> mc_blackhole_addr;
const MsgPrices& fetch_msg_prices(bool is_masterchain) const {
return is_masterchain ? fwd_mc : fwd_std;
}
};
struct SerializeConfig {
bool extra_currency_v2{false};
};
struct CreditPhase {
td::RefInt256 due_fees_collected;
block::CurrencyCollection credit;
@ -389,8 +394,8 @@ struct Transaction {
bool prepare_action_phase(const ActionPhaseConfig& cfg);
td::Status check_state_limits(const SizeLimitsConfig& size_limits, bool update_storage_stat = true);
bool prepare_bounce_phase(const ActionPhaseConfig& cfg);
bool compute_state();
bool serialize();
bool compute_state(const SerializeConfig& cfg);
bool serialize(const SerializeConfig& cfg);
td::uint64 gas_used() const {
return compute_phase ? compute_phase->gas_used : 0;
}
@ -428,14 +433,14 @@ struct FetchConfigParams {
std::vector<block::StoragePrices>* storage_prices,
StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed,
ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg,
td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee,
ton::WorkchainId wc, ton::UnixTime now);
SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee,
td::RefInt256* basechain_create_fee, ton::WorkchainId wc, ton::UnixTime now);
static td::Status fetch_config_params(const block::Config& config, Ref<vm::Tuple> prev_blocks_info,
Ref<vm::Cell>* old_mparams, std::vector<block::StoragePrices>* storage_prices,
StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed,
ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg,
td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee,
ton::WorkchainId wc, ton::UnixTime now);
SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee,
td::RefInt256* basechain_create_fee, ton::WorkchainId wc, ton::UnixTime now);
};
} // namespace block

View file

@ -1,7 +1,7 @@
// Standard library for Tolk (LGPL licence).
// It contains common functions that are available out of the box, the user doesn't have to import anything.
// More specific functions are required to be imported explicitly, like "@stdlib/tvm-dicts".
tolk 0.8
tolk 0.9
/**
Tuple manipulation primitives.
@ -139,7 +139,7 @@ fun getMyOriginalBalance(): int
/// `int` — balance in nanotoncoins;
/// `cell` — a dictionary with 32-bit keys representing the balance of "extra currencies".
@pure
fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell]
fun getMyOriginalBalanceWithExtraCurrencies(): [int, cell?]
asm "BALANCE";
/// Returns the logical time of the current transaction.
@ -154,7 +154,7 @@ fun getCurrentBlockLogicalTime(): int
/// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value.
@pure
fun getBlockchainConfigParam(x: int): cell
fun getBlockchainConfigParam(x: int): cell?
asm "CONFIGOPTPARAM";
/// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later.
@ -291,7 +291,7 @@ fun calculateSliceSizeStrict(s: slice, maxCells: int): (int, int, int)
/// otherwise the returned value is one plus the maximum of depths of cells referred to from [c].
/// If [c] is a `null` instead of a cell, returns zero.
@pure
fun getCellDepth(c: cell): int
fun getCellDepth(c: cell?): int
asm "CDEPTH";
/// Returns the depth of `slice` [s].
@ -417,12 +417,12 @@ fun getLastBits(self: slice, len: int): slice
/// Loads a dictionary (TL HashMapE structure, represented as TVM cell) from a slice.
/// Returns `null` if `nothing` constructor is used.
@pure
fun loadDict(mutate self: slice): cell
fun loadDict(mutate self: slice): cell?
asm( -> 1 0) "LDDICT";
/// Preloads a dictionary (cell) from a slice.
@pure
fun preloadDict(self: slice): cell
fun preloadDict(self: slice): cell?
asm "PLDDICT";
/// Loads a dictionary as [loadDict], but returns only the remainder of the slice.
@ -433,12 +433,12 @@ fun skipDict(mutate self: slice): self
/// Loads (Maybe ^Cell) from a slice.
/// In other words, loads 1 bit: if it's true, loads the first ref, otherwise returns `null`.
@pure
fun loadMaybeRef(mutate self: slice): cell
fun loadMaybeRef(mutate self: slice): cell?
asm( -> 1 0) "LDOPTREF";
/// Preloads (Maybe ^Cell) from a slice.
@pure
fun preloadMaybeRef(self: slice): cell
fun preloadMaybeRef(self: slice): cell?
asm "PLDOPTREF";
/// Loads (Maybe ^Cell), but returns only the remainder of the slice.
@ -497,13 +497,13 @@ fun storeBool(mutate self: builder, x: bool): self
/// Stores dictionary (represented by TVM `cell` or `null`) into a builder.
/// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise.
@pure
fun storeDict(mutate self: builder, c: cell): self
fun storeDict(mutate self: builder, c: cell?): self
asm(c self) "STDICT";
/// Stores (Maybe ^Cell) into a builder.
/// In other words, if cell is `null`, store '0' bit; otherwise, store '1' and a ref to [c].
@pure
fun storeMaybeRef(mutate self: builder, c: cell): self
fun storeMaybeRef(mutate self: builder, c: cell?): self
asm(c self) "STOPTREF";
/// Concatenates two builders.
@ -661,7 +661,7 @@ fun reserveToncoinsOnBalance(nanoTonCoins: int, reserveMode: int): void
/// Similar to [reserveToncoinsOnBalance], but also accepts a dictionary extraAmount (represented by a cell or null)
/// with extra currencies. In this way currencies other than Toncoin can be reserved.
fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell, reserveMode: int): void
fun reserveExtraCurrenciesOnBalance(nanoTonCoins: int, extraAmount: cell?, reserveMode: int): void
asm "RAWRESERVEX";

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/**
Gas and payment related primitives.

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/**
Lisp-style lists are nested 2-elements tuples: `(1, (2, (3, null)))` represents list `[1, 2, 3]`.
@ -14,17 +14,18 @@ fun createEmptyList(): tuple
/// Adds an element to the beginning of lisp-style list.
/// Note, that it does not mutate the list: instead, it returns a new one (it's a lisp pattern).
@pure
fun listPrepend<X>(head: X, tail: tuple): tuple
fun listPrepend<X>(head: X, tail: tuple?): tuple
asm "CONS";
/// Extracts the head and the tail of lisp-style list.
@pure
fun listSplit<X>(list: tuple): (X, tuple)
fun listSplit<X>(list: tuple): (X, tuple?)
asm "UNCONS";
/// Extracts the tail and the head of lisp-style list.
/// After extracting the last element, tuple is assigned to null.
@pure
fun listNext<X>(mutate self: tuple): X
fun listNext<X>(mutate self: tuple?): X
asm( -> 1 0) "UNCONS";
/// Returns the head of lisp-style list.
@ -34,5 +35,5 @@ fun listGetHead<X>(list: tuple): X
/// Returns the tail of lisp-style list.
@pure
fun listGetTail(list: tuple): tuple
fun listGetTail(list: tuple): tuple?
asm "CDR";

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/**
Dictionaries are represented as `cell` data type (cells can store anything, dicts in particular).
@ -9,288 +9,289 @@ tolk 0.8
- uDict* - dicts with unsigned integer keys
- sDict* - dicts with arbitrary slice keys
When accessing a dict element, you should not only provide a key, but provide keyLen,
since for optimization, for optimization, key length is not stored in the dictionary itself.
since for optimization, key length is not stored in the dictionary itself.
Every dictionary object (`self` parameter) can be null. TVM NULL is essentially "empty dictionary".
*/
/// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL
@pure
fun createEmptyDict(): cell
fun createEmptyDict(): cell?
asm "NEWDICT";
/// Checks whether a dictionary is empty.
@pure
fun dictIsEmpty(self: cell): bool
fun dictIsEmpty(self: cell?): bool
asm "DICTEMPTY";
@pure
fun iDictGet(self: cell, keyLen: int, key: int): (slice, bool)
fun iDictGet(self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTIGET" "NULLSWAPIFNOT";
@pure
fun uDictGet(self: cell, keyLen: int, key: int): (slice, bool)
fun uDictGet(self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTUGET" "NULLSWAPIFNOT";
@pure
fun sDictGet(self: cell, keyLen: int, key: slice): (slice, bool)
fun sDictGet(self: cell?, keyLen: int, key: slice): (slice?, bool)
asm(key self keyLen) "DICTGET" "NULLSWAPIFNOT";
@pure
fun iDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void
fun iDictSet(mutate self: cell?, keyLen: int, key: int, value: slice): void
asm(value key self keyLen) "DICTISET";
@pure
fun uDictSet(mutate self: cell, keyLen: int, key: int, value: slice): void
fun uDictSet(mutate self: cell?, keyLen: int, key: int, value: slice): void
asm(value key self keyLen) "DICTUSET";
@pure
fun sDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): void
fun sDictSet(mutate self: cell?, keyLen: int, key: slice, value: slice): void
asm(value key self keyLen) "DICTSET";
@pure
fun iDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void
fun iDictSetRef(mutate self: cell?, keyLen: int, key: int, value: cell): void
asm(value key self keyLen) "DICTISETREF";
@pure
fun uDictSetRef(mutate self: cell, keyLen: int, key: int, value: cell): void
fun uDictSetRef(mutate self: cell?, keyLen: int, key: int, value: cell): void
asm(value key self keyLen) "DICTUSETREF";
@pure
fun sDictSetRef(mutate self: cell, keyLen: int, key: slice, value: cell): void
fun sDictSetRef(mutate self: cell?, keyLen: int, key: slice, value: cell): void
asm(value key self keyLen) "DICTSETREF";
@pure
fun iDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun iDictSetIfNotExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTIADD";
@pure
fun uDictSetIfNotExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun uDictSetIfNotExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTUADD";
@pure
fun iDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun iDictSetIfExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTIREPLACE";
@pure
fun uDictSetIfExists(mutate self: cell, keyLen: int, key: int, value: slice): bool
fun uDictSetIfExists(mutate self: cell?, keyLen: int, key: int, value: slice): bool
asm(value key self keyLen) "DICTUREPLACE";
@pure
fun iDictGetRef(self: cell, keyLen: int, key: int): (cell, bool)
fun iDictGetRef(self: cell?, keyLen: int, key: int): (cell?, bool)
asm(key self keyLen) "DICTIGETREF" "NULLSWAPIFNOT";
@pure
fun uDictGetRef(self: cell, keyLen: int, key: int): (cell, bool)
fun uDictGetRef(self: cell?, keyLen: int, key: int): (cell?, bool)
asm(key self keyLen) "DICTUGETREF" "NULLSWAPIFNOT";
@pure
fun sDictGetRef(self: cell, keyLen: int, key: slice): (cell, bool)
fun sDictGetRef(self: cell?, keyLen: int, key: slice): (cell?, bool)
asm(key self keyLen) "DICTGETREF" "NULLSWAPIFNOT";
@pure
fun iDictGetRefOrNull(self: cell, keyLen: int, key: int): cell
fun iDictGetRefOrNull(self: cell?, keyLen: int, key: int): cell?
asm(key self keyLen) "DICTIGETOPTREF";
@pure
fun uDictGetRefOrNull(self: cell, keyLen: int, key: int): cell
fun uDictGetRefOrNull(self: cell?, keyLen: int, key: int): cell?
asm(key self keyLen) "DICTUGETOPTREF";
@pure
fun sDictGetRefOrNull(self: cell, keyLen: int, key: slice): cell
fun sDictGetRefOrNull(self: cell?, keyLen: int, key: slice): cell?
asm(key self keyLen) "DICTGETOPTREF";
@pure
fun iDictDelete(mutate self: cell, keyLen: int, key: int): bool
fun iDictDelete(mutate self: cell?, keyLen: int, key: int): bool
asm(key self keyLen) "DICTIDEL";
@pure
fun uDictDelete(mutate self: cell, keyLen: int, key: int): bool
fun uDictDelete(mutate self: cell?, keyLen: int, key: int): bool
asm(key self keyLen) "DICTUDEL";
@pure
fun sDictDelete(mutate self: cell, keyLen: int, key: slice): bool
fun sDictDelete(mutate self: cell?, keyLen: int, key: slice): bool
asm(key self keyLen) "DICTDEL";
@pure
fun iDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool)
fun iDictSetAndGet(mutate self: cell?, keyLen: int, key: int, value: slice): (slice?, bool)
asm(value key self keyLen) "DICTISETGET" "NULLSWAPIFNOT";
@pure
fun uDictSetAndGet(mutate self: cell, keyLen: int, key: int, value: slice): (slice, bool)
fun uDictSetAndGet(mutate self: cell?, keyLen: int, key: int, value: slice): (slice?, bool)
asm(value key self keyLen) "DICTUSETGET" "NULLSWAPIFNOT";
@pure
fun sDictSetAndGet(mutate self: cell, keyLen: int, key: slice, value: slice): (slice, bool)
fun sDictSetAndGet(mutate self: cell?, keyLen: int, key: slice, value: slice): (slice?, bool)
asm(value key self keyLen) "DICTSETGET" "NULLSWAPIFNOT";
@pure
fun iDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell
fun iDictSetAndGetRefOrNull(mutate self: cell?, keyLen: int, key: int, value: cell): cell?
asm(value key self keyLen) "DICTISETGETOPTREF";
@pure
fun uDictSetAndGetRefOrNull(mutate self: cell, keyLen: int, key: int, value: cell): cell
fun uDictSetAndGetRefOrNull(mutate self: cell?, keyLen: int, key: int, value: cell): cell?
asm(value key self keyLen) "DICTUSETGETOPTREF";
@pure
fun iDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool)
fun iDictDeleteAndGet(mutate self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTIDELGET" "NULLSWAPIFNOT";
@pure
fun uDictDeleteAndGet(mutate self: cell, keyLen: int, key: int): (slice, bool)
fun uDictDeleteAndGet(mutate self: cell?, keyLen: int, key: int): (slice?, bool)
asm(key self keyLen) "DICTUDELGET" "NULLSWAPIFNOT";
@pure
fun sDictDeleteAndGet(mutate self: cell, keyLen: int, key: slice): (slice, bool)
fun sDictDeleteAndGet(mutate self: cell?, keyLen: int, key: slice): (slice?, bool)
asm(key self keyLen) "DICTDELGET" "NULLSWAPIFNOT";
@pure
fun iDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void
fun iDictSetBuilder(mutate self: cell?, keyLen: int, key: int, value: builder): void
asm(value key self keyLen) "DICTISETB";
@pure
fun uDictSetBuilder(mutate self: cell, keyLen: int, key: int, value: builder): void
fun uDictSetBuilder(mutate self: cell?, keyLen: int, key: int, value: builder): void
asm(value key self keyLen) "DICTUSETB";
@pure
fun sDictSetBuilder(mutate self: cell, keyLen: int, key: slice, value: builder): void
fun sDictSetBuilder(mutate self: cell?, keyLen: int, key: slice, value: builder): void
asm(value key self keyLen) "DICTSETB";
@pure
fun iDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun iDictSetBuilderIfNotExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTIADDB";
@pure
fun uDictSetBuilderIfNotExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun uDictSetBuilderIfNotExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTUADDB";
@pure
fun iDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun iDictSetBuilderIfExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTIREPLACEB";
@pure
fun uDictSetBuilderIfExists(mutate self: cell, keyLen: int, key: int, value: builder): bool
fun uDictSetBuilderIfExists(mutate self: cell?, keyLen: int, key: int, value: builder): bool
asm(value key self keyLen) "DICTUREPLACEB";
@pure
fun iDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun iDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
@pure
fun uDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun uDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
@pure
fun sDictDeleteFirstAndGet(mutate self: cell, keyLen: int): (slice, slice, bool)
fun sDictDeleteFirstAndGet(mutate self: cell?, keyLen: int): (slice?, slice?, bool)
asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
@pure
fun iDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun iDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
@pure
fun uDictDeleteLastAndGet(mutate self: cell, keyLen: int): (int, slice, bool)
fun uDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (int?, slice?, bool)
asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
@pure
fun sDictDeleteLastAndGet(mutate self: cell, keyLen: int): (slice, slice, bool)
fun sDictDeleteLastAndGet(mutate self: cell?, keyLen: int): (slice?, slice?, bool)
asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
@pure
fun iDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
fun iDictGetFirst(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
@pure
fun uDictGetFirst(self: cell, keyLen: int): (int, slice, bool)
fun uDictGetFirst(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
@pure
fun sDictGetFirst(self: cell, keyLen: int): (slice, slice, bool)
fun sDictGetFirst(self: cell?, keyLen: int): (slice?, slice?, bool)
asm (-> 1 0 2) "DICTMIN" "NULLSWAPIFNOT2";
@pure
fun iDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
fun iDictGetFirstAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
@pure
fun uDictGetFirstAsRef(self: cell, keyLen: int): (int, cell, bool)
fun uDictGetFirstAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
@pure
fun sDictGetFirstAsRef(self: cell, keyLen: int): (slice, cell, bool)
fun sDictGetFirstAsRef(self: cell?, keyLen: int): (slice?, cell?, bool)
asm (-> 1 0 2) "DICTMINREF" "NULLSWAPIFNOT2";
@pure
fun iDictGetLast(self: cell, keyLen: int): (int, slice, bool)
fun iDictGetLast(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
@pure
fun uDictGetLast(self: cell, keyLen: int): (int, slice, bool)
fun uDictGetLast(self: cell?, keyLen: int): (int?, slice?, bool)
asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
@pure
fun sDictGetLast(self: cell, keyLen: int): (slice, slice, bool)
fun sDictGetLast(self: cell?, keyLen: int): (slice?, slice?, bool)
asm (-> 1 0 2) "DICTMAX" "NULLSWAPIFNOT2";
@pure
fun iDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
fun iDictGetLastAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
@pure
fun uDictGetLastAsRef(self: cell, keyLen: int): (int, cell, bool)
fun uDictGetLastAsRef(self: cell?, keyLen: int): (int?, cell?, bool)
asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
@pure
fun sDictGetLastAsRef(self: cell, keyLen: int): (slice, cell, bool)
fun sDictGetLastAsRef(self: cell?, keyLen: int): (slice?, cell?, bool)
asm (-> 1 0 2) "DICTMAXREF" "NULLSWAPIFNOT2";
@pure
fun iDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetNext(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2";
@pure
fun uDictGetNext(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetNext(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2";
@pure
fun iDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetNextOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2";
@pure
fun uDictGetNextOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetNextOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2";
@pure
fun iDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetPrev(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2";
@pure
fun uDictGetPrev(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetPrev(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2";
@pure
fun iDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun iDictGetPrevOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2";
@pure
fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
fun uDictGetPrevOrEqual(self: cell?, keyLen: int, pivot: int): (int?, slice?, bool)
asm(pivot self keyLen -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
@ -299,13 +300,13 @@ fun uDictGetPrevOrEqual(self: cell, keyLen: int, pivot: int): (int, slice, bool)
*/
@pure
fun prefixDictGet(self: cell, keyLen: int, key: slice): (slice, slice, slice, bool)
fun prefixDictGet(self: cell?, keyLen: int, key: slice): (slice, slice?, slice?, bool)
asm(key self keyLen) "PFXDICTGETQ" "NULLSWAPIFNOT2";
@pure
fun prefixDictSet(mutate self: cell, keyLen: int, key: slice, value: slice): bool
fun prefixDictSet(mutate self: cell?, keyLen: int, key: slice, value: slice): bool
asm(value key self keyLen) "PFXDICTSET";
@pure
fun prefixDictDelete(mutate self: cell, keyLen: int, key: slice): bool
fun prefixDictDelete(mutate self: cell?, keyLen: int, key: slice): bool
asm(key self keyLen) "PFXDICTDEL";

View file

@ -1,5 +1,5 @@
// A part of standard library for Tolk
tolk 0.8
tolk 0.9
/// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls.
/// The primitive returns the current value of `c3`.

View file

@ -196,6 +196,13 @@ bool TLB::print_ref(std::ostream& os, Ref<vm::Cell> cell_ref, int indent, int re
return pp.fail_unless(print_ref(pp, std::move(cell_ref)));
}
bool TLB::print_ref(td::StringBuilder& sb, Ref<vm::Cell> cell_ref, int indent, int rec_limit) const {
std::ostringstream ss;
auto result = print_ref(ss, std::move(cell_ref), indent, rec_limit);
sb << ss.str();
return result;
}
std::string TLB::as_string_skip(vm::CellSlice& cs, int indent) const {
std::ostringstream os;
print_skip(os, cs, indent);

View file

@ -246,7 +246,14 @@ class TLB {
bool print(std::ostream& os, Ref<vm::CellSlice> cs_ref, int indent = 0, int rec_limit = 0) const {
return print(os, *cs_ref, indent, rec_limit);
}
bool print(td::StringBuilder& sb, Ref<vm::CellSlice> cs_ref, int indent = 0, int rec_limit = 0) const {
std::ostringstream ss;
auto result = print(ss, *cs_ref, indent, rec_limit);
sb << ss.str();
return result;
}
bool print_ref(std::ostream& os, Ref<vm::Cell> cell_ref, int indent = 0, int rec_limit = 0) const;
bool print_ref(td::StringBuilder& sb, Ref<vm::Cell> cell_ref, int indent = 0, int rec_limit = 0) const;
bool print_ref(int rec_limit, std::ostream& os, Ref<vm::Cell> cell_ref, int indent = 0) const {
return print_ref(os, std::move(cell_ref), indent, rec_limit);
}

View file

@ -1153,8 +1153,12 @@ td::Result<CellStorageStat::CellInfo> CellStorageStat::add_used_storage(Ref<vm::
return ins.first->second;
}
}
vm::CellSlice cs{vm::NoVm{}, std::move(cell)};
return add_used_storage(std::move(cs), kill_dup, skip_count_root);
vm::CellSlice cs{vm::NoVm{}, cell};
TRY_RESULT(res, add_used_storage(std::move(cs), kill_dup, skip_count_root));
if (kill_dup) {
seen[cell->get_hash()] = res;
}
return res;
}
void NewCellStorageStat::add_cell(Ref<Cell> cell) {

View file

@ -101,9 +101,9 @@ class NewCellStorageStat {
private:
const CellUsageTree* usage_tree_;
std::set<vm::Cell::Hash> seen_;
td::HashSet<vm::Cell::Hash> seen_;
Stat stat_;
std::set<vm::Cell::Hash> proof_seen_;
td::HashSet<vm::Cell::Hash> proof_seen_;
Stat proof_stat_;
const NewCellStorageStat* parent_{nullptr};
@ -117,7 +117,7 @@ struct CellStorageStat {
struct CellInfo {
td::uint32 max_merkle_depth = 0;
};
std::map<vm::Cell::Hash, CellInfo> seen;
td::HashMap<vm::Cell::Hash, CellInfo> seen;
CellStorageStat() : cells(0), bits(0), public_cells(0) {
}
explicit CellStorageStat(unsigned long long limit_cells)
@ -173,7 +173,7 @@ class ProofStorageStat {
enum CellStatus {
c_none = 0, c_prunned = 1, c_loaded = 2
};
std::map<vm::Cell::Hash, CellStatus> cells_;
td::HashMap<vm::Cell::Hash, CellStatus> cells_;
td::uint64 proof_size_ = 0;
};

View file

@ -1026,6 +1026,13 @@ bool CellSlice::print_rec(std::ostream& os, int indent) const {
return print_rec(os, &limit, indent);
}
bool CellSlice::print_rec(td::StringBuilder& sb, int indent) const {
std::ostringstream ss;
auto result = print_rec(ss, indent);
sb << ss.str();
return result;
}
bool CellSlice::print_rec(int limit, std::ostream& os, int indent) const {
return print_rec(os, &limit, indent);
}

View file

@ -257,6 +257,7 @@ class CellSlice : public td::CntObject {
void dump(std::ostream& os, int level = 0, bool endl = true) const;
void dump_hex(std::ostream& os, int mode = 0, bool endl = false) const;
bool print_rec(std::ostream& os, int indent = 0) const;
bool print_rec(td::StringBuilder& sb, int indent = 0) const;
bool print_rec(std::ostream& os, int* limit, int indent = 0) const;
bool print_rec(int limit, std::ostream& os, int indent = 0) const;
void error() const {

View file

@ -1761,6 +1761,10 @@ int exec_send_message(VmState* st) {
vm::VmStorageStat stat(max_cells);
CellSlice cs = load_cell_slice(msg_cell);
cs.skip_first(cs.size());
if (st->get_global_version() >= 10 && have_extra_currencies) {
// Skip extra currency dict
cs.advance_refs(1);
}
stat.add_storage(cs);
if (!ext_msg) {
@ -1773,7 +1777,9 @@ int exec_send_message(VmState* st) {
if (value.is_null()) {
throw VmError{Excno::type_chk, "invalid param BALANCE"};
}
have_extra_currencies |= !tuple_index(balance, 1).as_cell().is_null();
if (st->get_global_version() < 10) {
have_extra_currencies |= !tuple_index(balance, 1).as_cell().is_null();
}
} else if (mode & 64) { // value += value of incoming message
Ref<Tuple> balance = get_param(st, 11).as_tuple();
if (balance.is_null()) {
@ -1784,7 +1790,9 @@ int exec_send_message(VmState* st) {
throw VmError{Excno::type_chk, "invalid param INCOMINGVALUE"};
}
value += balance_grams;
have_extra_currencies |= !tuple_index(balance, 1).as_cell().is_null();
if (st->get_global_version() < 10) {
have_extra_currencies |= !tuple_index(balance, 1).as_cell().is_null();
}
}
}

View file

@ -134,4 +134,25 @@ Example: if the last masterchain block seqno is `19071` then the list contains b
- `PFXDICTADD`, `PFXDICTSET`, `PFXDICTREPLACE`, `PFXDICTDEL`, `GETGASFEE`, `GETSTORAGEFEE`, `GETFORWARDFEE`, `GETORIGINALFWDFEE`, `GETGASFEESIMPLE`, `GETFORWARDFEESIMPLE`, `HASHEXT`
- Now setting the contract code to a library cell does not consume additional gas on execution of the code.
- Temporary increase gas limit for some accounts (see [this post](https://t.me/tondev_news/129) for details, `override_gas_limit` in `transaction.cpp` for the list of accounts).
- Fix recursive jump to continuations with non-null control data.
- Fix recursive jump to continuations with non-null control data.
## Version 10
### Extra currencies
- Internal messages cannot carry more than 2 different extra currencies. The limit can be changed in size limits config (`ConfigParam 43`).
- Amount of an extra currency in an output action "send message" can be zero.
- In action phase zero values are automatically deleted from the dictionary before sending.
- However, the size of the extra currency dictionary in the "send message" action should not be greater than 2 (or the value in size limits config).
- Extra currency dictionary is not counted in message size and does not affect message fees.
- Message mode `+64` (carry all remaining message balance) is now considered as "carry all remaining TONs from message balance".
- Message mode `+128` (carry all remaining account balance) is now considered as "carry all remaining TONs from account balance".
- Message mode `+32` (delete account if balance is zero) deletes account if it has zero TONs, regardless of extra currencies.
- Deleted accounts with extra currencies become `account_uninit`, extra currencies remain on the account.
- `SENDMSG` in TVM calculates message size and fees without extra currencies, uses new `+64` and `+128` mode behavior.
- `SENDMSG` does not check the number of extra currencies.
- Extra currency dictionary is not counted in the account size and does not affect storage fees.
- Accounts with already existing extra currencies will get their sizes recomputed without EC only after modifying `AccountState`.
### TVM changes
- `SENDMSG` calculates messages size and fees without extra currencies, uses new +64 and +128 mode behavior.
- `SENDMSG` does not check the number of extra currencies.

View file

@ -1,8 +1,6 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
option(EMULATOR_STATIC "Build emulator as static library" OFF)
set(EMULATOR_STATIC_SOURCE
transaction-emulator.cpp
@ -22,7 +20,7 @@ include(GenerateExportHeader)
add_library(emulator_static STATIC ${EMULATOR_STATIC_SOURCE})
target_link_libraries(emulator_static PUBLIC ton_crypto smc-envelope)
if (USE_EMSCRIPTEN)
if (EMULATOR_STATIC OR USE_EMSCRIPTEN)
add_library(emulator STATIC ${EMULATOR_SOURCE})
else()
add_library(emulator SHARED ${EMULATOR_SOURCE})
@ -35,7 +33,7 @@ else()
endif()
generate_export_header(emulator EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/emulator_export.h)
if (USE_EMSCRIPTEN)
if (EMULATOR_STATIC OR USE_EMSCRIPTEN)
target_compile_definitions(emulator PUBLIC EMULATOR_STATIC_DEFINE)
endif()
target_include_directories(emulator PUBLIC

View file

@ -16,6 +16,7 @@ td::Result<std::unique_ptr<TransactionEmulator::EmulationResult>> TransactionEmu
block::StoragePhaseConfig storage_phase_cfg{&storage_prices};
block::ComputePhaseConfig compute_phase_cfg;
block::ActionPhaseConfig action_phase_cfg;
block::SerializeConfig serialize_config;
td::RefInt256 masterchain_create_fee, basechain_create_fee;
if (!utime) {
@ -25,11 +26,9 @@ td::Result<std::unique_ptr<TransactionEmulator::EmulationResult>> TransactionEmu
utime = (unsigned)std::time(nullptr);
}
auto fetch_res = block::FetchConfigParams::fetch_config_params(*config_, prev_blocks_info_, &old_mparams,
&storage_prices, &storage_phase_cfg,
&rand_seed_, &compute_phase_cfg,
&action_phase_cfg, &masterchain_create_fee,
&basechain_create_fee, account.workchain, utime);
auto fetch_res = block::FetchConfigParams::fetch_config_params(
*config_, prev_blocks_info_, &old_mparams, &storage_prices, &storage_phase_cfg, &rand_seed_, &compute_phase_cfg,
&action_phase_cfg, &serialize_config, &masterchain_create_fee, &basechain_create_fee, account.workchain, utime);
if(fetch_res.is_error()) {
return fetch_res.move_as_error_prefix("cannot fetch config params ");
}
@ -66,7 +65,7 @@ td::Result<std::unique_ptr<TransactionEmulator::EmulationResult>> TransactionEmu
return std::make_unique<TransactionEmulator::EmulationExternalNotAccepted>(std::move(vm_log), vm_exit_code, elapsed);
}
if (!trans->serialize()) {
if (!trans->serialize(serialize_config)) {
return td::Status::Error(-669,"cannot serialize new transaction for smart contract "s + trans->account.addr.to_hex());
}

View file

@ -32,7 +32,7 @@ void OverlayOutboundFecBroadcast::alarm() {
fec_type_.size(), flags_, std::move(X.data), X.id, fec_type_, date_);
}
alarm_timestamp() = td::Timestamp::in(0.010);
alarm_timestamp() = td::Timestamp::in(delay_);
if (seqno_ >= to_send_) {
stop();
@ -46,8 +46,9 @@ void OverlayOutboundFecBroadcast::start_up() {
OverlayOutboundFecBroadcast::OverlayOutboundFecBroadcast(td::BufferSlice data, td::uint32 flags,
td::actor::ActorId<OverlayImpl> overlay,
PublicKeyHash local_id)
PublicKeyHash local_id, double speed_multiplier)
: flags_(flags) {
delay_ /= speed_multiplier;
CHECK(data.size() <= (1 << 27));
local_id_ = local_id;
overlay_ = std::move(overlay);
@ -63,9 +64,10 @@ OverlayOutboundFecBroadcast::OverlayOutboundFecBroadcast(td::BufferSlice data, t
}
td::actor::ActorId<OverlayOutboundFecBroadcast> OverlayOutboundFecBroadcast::create(
td::BufferSlice data, td::uint32 flags, td::actor::ActorId<OverlayImpl> overlay, PublicKeyHash local_id) {
return td::actor::create_actor<OverlayOutboundFecBroadcast>(td::actor::ActorOptions().with_name("bcast"),
std::move(data), flags, overlay, local_id)
td::BufferSlice data, td::uint32 flags, td::actor::ActorId<OverlayImpl> overlay, PublicKeyHash local_id,
double speed_multiplier) {
return td::actor::create_actor<OverlayOutboundFecBroadcast>(
td::actor::ActorOptions().with_name("bcast"), std::move(data), flags, overlay, local_id, speed_multiplier)
.release();
}

View file

@ -37,6 +37,7 @@ class OverlayOutboundFecBroadcast : public td::actor::Actor {
PublicKeyHash local_id_;
Overlay::BroadcastDataHash data_hash_;
td::uint32 flags_ = 0;
double delay_ = 0.010;
td::int32 date_;
std::unique_ptr<td::fec::Encoder> encoder_;
td::actor::ActorId<OverlayImpl> overlay_;
@ -45,9 +46,9 @@ class OverlayOutboundFecBroadcast : public td::actor::Actor {
public:
static td::actor::ActorId<OverlayOutboundFecBroadcast> create(td::BufferSlice data, td::uint32 flags,
td::actor::ActorId<OverlayImpl> overlay,
PublicKeyHash local_id);
PublicKeyHash local_id, double speed_multiplier = 1.0);
OverlayOutboundFecBroadcast(td::BufferSlice data, td::uint32 flags, td::actor::ActorId<OverlayImpl> overlay,
PublicKeyHash local_id);
PublicKeyHash local_id, double speed_multiplier = 1.0);
void alarm() override;
void start_up() override;

View file

@ -63,7 +63,7 @@ td::actor::ActorOwn<Overlay> Overlay::create_private(
return td::actor::create_actor<OverlayImpl>(
overlay_actor_name(overlay_id), keyring, adnl, manager, dht_node, local_id, std::move(overlay_id),
OverlayType::FixedMemberList, std::move(nodes), std::vector<PublicKeyHash>(), OverlayMemberCertificate{},
std::move(callback), std::move(rules), std::move(scope));
std::move(callback), std::move(rules), std::move(scope), std::move(opts));
}
td::actor::ActorOwn<Overlay> Overlay::create_semiprivate(
@ -99,6 +99,7 @@ OverlayImpl::OverlayImpl(td::actor::ActorId<keyring::Keyring> keyring, td::actor
overlay_id_ = id_full_.compute_short_id();
frequent_dht_lookup_ = opts_.frequent_dht_lookup_;
peer_list_.local_member_flags_ = opts_.local_overlay_member_flags_;
opts_.broadcast_speed_multiplier_ = std::max(opts_.broadcast_speed_multiplier_, 1e-9);
VLOG(OVERLAY_INFO) << this << ": creating";
@ -490,7 +491,8 @@ void OverlayImpl::send_broadcast_fec(PublicKeyHash send_as, td::uint32 flags, td
VLOG(OVERLAY_WARNING) << "broadcast source certificate is invalid";
return;
}
OverlayOutboundFecBroadcast::create(std::move(data), flags, actor_id(this), send_as);
OverlayOutboundFecBroadcast::create(std::move(data), flags, actor_id(this), send_as,
opts_.broadcast_speed_multiplier_);
}
void OverlayImpl::print(td::StringBuilder &sb) {

View file

@ -269,6 +269,7 @@ struct OverlayOptions {
td::uint32 nodes_to_send_ = 4;
td::uint32 propagate_broadcast_to_ = 5;
td::uint32 default_permanent_members_flags_ = 0;
double broadcast_speed_multiplier_ = 1.0;
};
class Overlays : public td::actor::Actor {

View file

@ -1,12 +1,13 @@
## 2025.02 Update
1. Series of improvement/fixes for `Config8.version >= 9`, check [GlobalVersions.md](./doc/GlobalVersions.md)
2. Fix for better discovery of updated nodes' (validators') IPs: retry dht queries
3. Series of improvements for extra currency adoption: fixed c7 in rungetmethod, reserve modes
4. TVM: Fix processing continuation control data on deep jump
5. A few fixes of tl-b schemes: crc computation, incorrect tag for merkle proofs, advance_ext, NatWidth print
6. Emulator improvements: fix setting libraries, extracurrency support
7. Increase of gas limit for unlocking highload-v2 wallets locked in the beginning of 2024
8. Validator console improvement: dashed names, better shard formats
## 2025.03 Update
1. New extracurrency behavior introduced, check [GlobalVersions.md](./doc/GlobalVersions.md#version-10)
2. Optmization of validation process, in particular CellStorageStat.
3. Flag for speeding up broadcasts in various overlays.
4. Fixes for static builds for emulator and tonlibjson
5. Improving getstats output: add
* Liteserver queries count
* Collated/validated blocks count, number of active sessions
* Persistent state sizes
* Initial sync progress
6. Fixes in logging, TON Storage, external message checking, persistent state downloading, UB in tonlib
Besides the work of the core team, this update is based on the efforts of @dbaranovstonfi from StonFi(libraries in emulator), @Rexagon (ret on deep jumps), @tvorogme from DTon (`advance_ext`), Nan from Zellic (`stk_und` and JNI)
Besides the work of the core team, this update is based on the efforts of @Sild from StonFi(UB in tonlib).

View file

@ -251,7 +251,7 @@ void PeerActor::loop_update_init() {
}
s = s.substr(peer_init_offset_, UPDATE_INIT_BLOCK_SIZE);
auto query = create_update_query(ton::create_tl_object<ton::ton_api::storage_updateInit>(
td::BufferSlice(s), (int)peer_init_offset_, to_ton_api(node_state)));
td::BufferSlice(s), (int)peer_init_offset_ * 8, to_ton_api(node_state)));
// take care about update_state_query initial state
update_state_query_.state = node_state;
@ -502,11 +502,11 @@ void PeerActor::process_update_peer_parts(const tl_object_ptr<ton_api::storage_U
},
[&](const ton::ton_api::storage_updateState &state) {},
[&](const ton::ton_api::storage_updateInit &init) {
LOG(DEBUG) << "Processing updateInit query (offset=" << init.have_pieces_offset_ * 8 << ")";
LOG(DEBUG) << "Processing updateInit query (offset=" << init.have_pieces_offset_ << ")";
td::Bitset new_bitset;
new_bitset.set_raw(init.have_pieces_.as_slice().str());
size_t offset = init.have_pieces_offset_ * 8;
for (auto size = new_bitset.size(), i = size_t(0); i < size; i++) {
size_t offset = init.have_pieces_offset_;
for (auto size = new_bitset.size(), i = (size_t)0; i < size; i++) {
if (new_bitset.get(i)) {
add_piece(static_cast<PartId>(offset + i));
}

View file

@ -128,6 +128,10 @@ inline Timestamp &operator+=(Timestamp &a, double b) {
return a;
}
inline double operator-(const Timestamp &a, const Timestamp &b) {
return a.at() - b.at();
}
template <class StorerT>
void store(const Timestamp &timestamp, StorerT &storer) {
storer.store_binary(timestamp.at() - Time::now() + Clocks::system());

View file

@ -264,8 +264,8 @@ class Logger {
sb_ << other;
return *this;
}
LambdaPrintHelper<td::Logger> operator<<(const LambdaPrint &) {
return LambdaPrintHelper<td::Logger>{*this};
LambdaPrintHelper<td::StringBuilder> operator<<(const LambdaPrint &) {
return LambdaPrintHelper<td::StringBuilder>{sb_};
}
MutableCSlice as_cslice() {

View file

@ -2,7 +2,7 @@ import "@stdlib/tvm-lowlevel"
fun pair_first<X, Y>(p: [X, Y]): X asm "FIRST";
fun one(dummy: tuple) {
fun one(dummy: tuple?) {
return 1;
}
@ -144,15 +144,16 @@ fun test95() {
"""
test95 PROC:<{
...
next GETGLOB // '10
3 PUSHINT // '10 '12=3
4 PUSHINT // '10 '12=3 '13=4
5 PUSHINT // '10 '12=3 '13=4 '14=5
TRIPLE // '15 '16
next SETGLOB
next GETGLOB // g_next
3 PUSHINT // g_next '14=3
4 PUSHINT // g_next '14=3 '15=4
5 PUSHINT // g_next '14=3 '15=4 '16=5
TRIPLE // '10 '11
SWAP
cur SETGLOB
cur GETGLOB // '17
next GETGLOB // '17 '18
next SETGLOB
cur GETGLOB // g_cur
next GETGLOB // g_cur g_next
}>
"""
*/

View file

@ -147,5 +147,5 @@ fun main() {
// x.0 x.1
"""
@code_hash 7627024945492125068389905298530400936797031708759561372406088054030801992712
@code_hash 61280273714870328160131559159866470128402169974050439159015534193532598351244
*/

View file

@ -26,10 +26,189 @@ fun typesAsIdentifiers(builder: builder) {
return int;
}
global callOrder: tuple;
fun getTensor_12() {
callOrder.tuplePush(100);
return (1, 2);
}
fun getTensor_1X(x: int) {
callOrder.tuplePush(101);
return (1, x);
}
fun getTuple_12() {
callOrder.tuplePush(110);
return [1, 2];
}
fun getTuple_1X(x: int) {
callOrder.tuplePush(111);
return [1, x];
}
fun getUntypedTuple_12() {
callOrder.tuplePush(120);
var t = createEmptyTuple(); t.tuplePush(1); t.tuplePush(2);
return t;
}
fun getUntypedTuple_1X(x: int) {
callOrder.tuplePush(121);
var t = createEmptyTuple(); t.tuplePush(1); t.tuplePush(x);
return t;
}
fun getIntValue5() {
callOrder.tuplePush(10);
return 5;
}
fun getIntValueX(x: int) {
callOrder.tuplePush(11);
return x;
}
@method_id(102)
fun test102() {
callOrder = createEmptyTuple();
var x = 0;
getTensor_12().0 = getIntValue5();
getTensor_1X(5).1 = getIntValue5();
getTensor_1X(x = 10).0 = getIntValueX(x);
return (callOrder, x);
}
@method_id(103)
fun test103() {
callOrder = createEmptyTuple();
var x = 0;
getTuple_12().0 = getIntValue5();
getTuple_1X(5).1 = getIntValue5();
getTuple_1X(x = 10).0 = getIntValueX(x);
return (callOrder, x);
}
@method_id(104)
fun test104() {
callOrder = createEmptyTuple();
var x = 0;
getUntypedTuple_12().0 = getIntValue5();
getUntypedTuple_1X(5).1 = getIntValue5();
getUntypedTuple_1X(x = 10).0 = getIntValueX(x);
return (callOrder, x);
}
@method_id(105)
fun test105() {
callOrder = createEmptyTuple();
getTensor_12().0 = getTensor_1X(getIntValue5()).1 = getIntValueX(getTensor_12().1);
return callOrder;
}
@method_id(106)
fun test106() {
callOrder = createEmptyTuple();
getTuple_12().0 = getTuple_1X(getIntValue5()).1 = getIntValueX(getTuple_12().1);
return callOrder;
}
global t107: (int, int);
@method_id(107)
fun test107() {
((t107 = (1, 2)).0, (t107 = (3, 4)).1) = (5, 6);
return t107;
}
global g108: int;
fun assertEq(a: int, b: int) {
assert(a == b, 10);
return b;
}
@method_id(108)
fun test108() {
callOrder = createEmptyTuple();
g108 = 0;
getTensor_1X(g108 = 8).1 = assertEq(g108, 8);
return (callOrder, g108);
}
@method_id(109)
fun test109() {
callOrder = createEmptyTuple();
var x = 0;
[getTuple_12().0, getTuple_1X(x = getIntValue5()).1, getTuple_1X(x += 10).0] = [getIntValue5(), getIntValue5(), getIntValueX(x)];
return (callOrder, x);
}
global g110: int;
global t110: (int, int);
@method_id(110)
fun test110() {
callOrder = createEmptyTuple();
var xy = [0, 0];
[xy.0, getTuple_1X(g110 = 8).0] = [g110 += 5, getIntValueX(g110 += 10)];
[xy.1, getTuple_1X((t110 = (8, 9)).0).1] = [t110.0 += 5, getIntValueX(t110.1 += 10)];
return (xy, callOrder, g110, t110);
}
@method_id(111)
fun test111() {
callOrder = createEmptyTuple();
var z = -1;
var xy = [0, z = 0];
var rhs = [getIntValueX(xy.1 += 10), xy.1, xy.0, z += 50];
[xy.0, getTuple_1X(g110 = 8 + getIntValueX(xy.1)).0, xy.1, z] = rhs;
return (xy, g110, callOrder, z);
}
@method_id(112)
fun test112() {
var xy = [1, 2];
((((xy))).0, ((xy.1))) = ((xy).1, ((xy.0)));
return xy;
}
@method_id(113)
fun test113() {
var (a, t, z) = (1, [2,3], (-1,-1));
(a, t, a, z, t.1, z.1) = (10, [a,12], 13, (a, t.1), 14, t.1);
return (a, t, z);
}
global g114: int;
global t114: [int, int];
global z114: (int, int);
@method_id(114)
fun test114() {
g114 = 1;
t114 = [2, 3];
(g114, t114, g114, z114, t114.1, z114.1) = (10, [g114,12], 13, (g114, t114.1), 14, t114.1);
return (g114, t114, z114);
}
@method_id(115)
fun test115() {
callOrder = createEmptyTuple();
var x = 0;
var y = 0;
[getTensor_1X(x = 5).0, y] = getTuple_1X(x = 9);
return (callOrder, x, y);
}
@method_id(116)
fun test116() {
var (a,b,c,d) = (0,0,0,0);
var rhs = [1, 2, 3, 4];
var rhs2 = ([a,b,c,d] = rhs);
__expect_type(rhs2, "[int, int, int, int]");
return (a, b, c, d, rhs2);
}
fun main(value: int) {
var (x: int, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
var (x: int?, y) = (autoInferIntNull(value), autoInferIntNull(value * 2));
if (x == null && y == null) { return null; }
return x == null || y == null ? -1 : x + y;
return x == null || y == null ? -1 : x! + y!;
}
/**
@ -37,4 +216,35 @@ fun main(value: int) {
@testcase | 0 | 6 | -1
@testcase | 0 | 11 | (null)
@testcase | 101 | 78 | 88
@testcase | 102 | | [ 100 10 101 10 101 11 ] 10
@testcase | 103 | | [ 110 10 111 10 111 11 ] 10
@testcase | 104 | | [ 120 10 121 10 121 11 ] 10
@testcase | 105 | | [ 100 10 101 100 11 ]
@testcase | 106 | | [ 110 10 111 110 11 ]
@testcase | 107 | | 3 4
@testcase | 108 | | [ 101 ] 8
@testcase | 109 | | [ 110 10 111 111 10 10 11 ] 15
@testcase | 110 | | [ 13 13 ] [ 111 11 111 11 ] 23 13 19
@testcase | 111 | | [ 10 0 ] 18 [ 11 11 111 ] 50
@testcase | 112 | | [ 2 1 ]
@testcase | 113 | | 13 [ 1 14 ] 1 3
@testcase | 114 | | 13 [ 1 14 ] 1 3
@testcase | 115 | | [ 101 111 ] 9 9
@testcase | 116 | | 1 2 3 4 [ 1 2 3 4 ]
@fif_codegen
"""
test116 PROC:<{
//
1 PUSHINT // '10=1
2 PUSHINT // '10=1 '11=2
3 PUSHINT // '10=1 '11=2 '12=3
4 PUSHINT // '10=1 '11=2 '12=3 '13=4
4 TUPLE // rhs
DUP // rhs rhs
4 UNTUPLE // rhs2 a b c d
4 ROLL // a b c d rhs2
}>
"""
*/

View file

@ -8,7 +8,7 @@ fun unnamed_args(_: int, _: slice, _: int) {
return true;
}
fun main(x: int, y: int, z: int): bool {
fun main(x: int, y: int, z: int): bool? {
op = `_+_`;
if (0) { return null; }
return check_assoc(x, y, z);

View file

@ -32,7 +32,8 @@ fun test1(): [int,int,int,int,int] {
fun test2(): [int,int,int] {
var b: builder = beginCell().myStoreInt(1, 32);
b = b.myStoreInt(2, 32);
b.myStoreInt(3, 32);
// operator ! here and below is used just for testing purposes, it doesn't affect the result
b!.myStoreInt(3, 32);
var cs: slice = b.endCell().beginParse();
var one: int = cs.myLoadInt(32);
@ -43,14 +44,14 @@ fun test2(): [int,int,int] {
@method_id(103)
fun test3(ret: int): int {
val same: int = beginCell().storeUint(ret,32).endCell().beginParse().loadUint(32);
val same: int = beginCell()!.storeUint(ret,32).endCell().beginParse().loadUint(32);
return same;
}
@method_id(104)
fun test4(): [int,int] {
var b: builder = beginCell().myStoreInt(1, 32);
b = b.storeInt(2, 32).storeInt(3, 32);
var b: builder = (beginCell() as builder).myStoreInt(1, 32);
b = b!.storeInt(2, 32)!.storeInt(3, 32);
var cs: slice = b.endCell().beginParse();
var (one, _, three) = (cs.getFirstBits(32).loadUint(32), cs.skipBits(64), cs.load_u32());
@ -116,7 +117,7 @@ fun test10() {
fun test11() {
var s: slice = beginCell().storeInt(1, 32).storeInt(2, 32).storeInt(3, 32).storeInt(4, 32).storeInt(5, 32).storeInt(6, 32).storeInt(7, 32).endCell().beginParse();
var size1 = getRemainingBitsCount(s);
s.skipBits(32);
s!.skipBits(32);
var s1: slice = s.getFirstBits(64);
var n1 = s1.loadInt(32);
var size2 = getRemainingBitsCount(s);

View file

@ -35,7 +35,7 @@ Below, I just give examples of @fif_codegen tag:
"""
main PROC:<{
// s
17 PUSHINT // s '1=17
17 PUSHINT // s '3=17
OVER // s z=17 t
WHILE:<{
...

View file

@ -1,15 +1,15 @@
import "@stdlib/tvm-dicts"
fun addIntToIDict(mutate self: cell, key: int, number: int): void {
fun addIntToIDict(mutate self: cell?, key: int, number: int): void {
return self.iDictSetBuilder(32, key, beginCell().storeInt(number, 32));
}
fun calculateDictLen(d: cell) {
fun calculateDictLen(d: cell?) {
var len = 0;
var (k, v, f) = d.uDictGetFirst(32);
while (f) {
len += 1;
(k, v, f) = d.uDictGetNext(32, k);
(k, v, f) = d.uDictGetNext(32, k!);
}
return len;
}
@ -25,13 +25,13 @@ fun loadTwoDigitNumberFromSlice(mutate self: slice): int {
fun test101(getK1: int, getK2: int, getK3: int) {
var dict = createEmptyDict();
dict.uDictSetBuilder(32, 1, beginCell().storeUint(1, 32));
var (old1: slice, found1) = dict.uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse());
var (old2: slice, found2) = dict.uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse());
var (cur3: slice, found3) = dict.uDictGet(32, getK3);
var (old1: slice?, found1) = dict.uDictSetAndGet(32, getK1, beginCell().storeUint(2, 32).endCell().beginParse());
var (old2: slice?, found2) = dict.uDictSetAndGet(32, getK2, beginCell().storeUint(3, 32).endCell().beginParse());
var (cur3: slice?, found3) = dict.uDictGet(32, getK3);
return (
found1 ? old1.loadUint(32) : -1,
found2 ? old2.loadUint(32) : -1,
found3 ? cur3.loadUint(32) : -1
found1 ? old1!.loadUint(32) : -1,
found2 ? old2!.loadUint(32) : -1,
found3 ? cur3!.loadUint(32) : -1
);
}
@ -47,7 +47,7 @@ fun test102() {
while (!shouldBreak) {
var (kDel, kVal, wasDel) = dict.iDictDeleteLastAndGet(32);
if (wasDel) {
deleted.tuplePush([kDel, kVal.loadInt(32)]);
deleted.tuplePush([kDel, kVal!.loadInt(32)]);
} else {
shouldBreak = true;
}
@ -82,14 +82,14 @@ fun test104() {
var (old2, _) = dict.sDictDeleteAndGet(32, "key1");
var (restK, restV, _) = dict.sDictGetFirst(32);
var (restK1, restV1, _) = dict.sDictDeleteLastAndGet(32);
assert (restK.isSliceBitsEqual(restK1)) throw 123;
assert (restV.isSliceBitsEqual(restV1)) throw 123;
assert (restK!.isSliceBitsEqual(restK1!)) throw 123;
assert (restV!.isSliceBitsEqual(restV1!)) throw 123;
return (
old1.loadTwoDigitNumberFromSlice(),
old2.loadTwoDigitNumberFromSlice(),
restV.loadTwoDigitNumberFromSlice(),
restK.loadTwoDigitNumberFromSlice(),
restK.loadTwoDigitNumberFromSlice()
old1!.loadTwoDigitNumberFromSlice(),
old2!.loadTwoDigitNumberFromSlice(),
restV!.loadTwoDigitNumberFromSlice(),
restK!.loadTwoDigitNumberFromSlice(),
restK!.loadTwoDigitNumberFromSlice()
);
}

View file

@ -49,17 +49,17 @@ fun manyEq<T1, T2, T3>(a: T1, b: T2, c: T3): [T1, T2, T3] {
fun test104(f: int) {
var result = (
manyEq(1 ? 1 : 1, f ? 0 : null, !f ? getTwo() as int : null),
manyEq(f ? null as int : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool(), eq4(f))
manyEq(f ? null as int? : eq2(2), beginCell().storeBool(true).endCell().beginParse().loadBool(), eq4(f))
);
__expect_type(result, "([int, int, int], [int, bool, int])");
__expect_type(result, "([int, int?, int?], [int?, bool, int])");
return result;
}
fun calcSum<X>(x: X, y: X) { return x + y; }
fun calcSum<X>(x: X, y: X) { return x! + y!; }
@method_id(105)
fun test105() {
if (0) { calcSum(((0)), null); }
if (0) { calcSum(((0 as int?)), null); }
return (calcSum(1, 2));
}

View file

@ -1,21 +1,21 @@
import "@stdlib/tvm-dicts"
fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell {
var dict: cell = createEmptyDict();
fun prepareDict_3_30_4_40_5_x(valueAt5: int): cell? {
var dict: cell? = createEmptyDict();
dict.iDictSetBuilder(32, 3, beginCell().storeInt(30, 32));
dict.iDictSetBuilder(32, 4, beginCell().storeInt(40, 32));
dict.iDictSetBuilder(32, 5, beginCell().storeInt(valueAt5, 32));
return dict;
}
fun lookupIdxByValue(idict32: cell, value: int): int {
var cur_key = -1;
fun lookupIdxByValue(idict32: cell?, value: int): int {
var cur_key: int? = -1;
do {
var (cur_key redef, cs: slice, found: bool) = idict32.iDictGetNext(32, cur_key);
var (cur_key redef, cs: slice?, found: bool) = idict32.iDictGetNext(32, cur_key!);
// one-line condition (via &) doesn't work, since right side is calculated immediately
if (found) {
if (cs.loadInt(32) == value) {
return cur_key;
if (cs!.loadInt(32) == value) {
return cur_key!;
}
}
} while (found);

View file

@ -21,6 +21,26 @@ fun plus(mutate self: int, y: int): int {
fun eq<X>(v: X): X { return v; }
global gTup: [int];
global gTens: (int, int);
@method_id(100)
fun testCodegenSimple() {
var t1 = [1];
t1.0 = 2;
debugPrintString("");
var t2 = [[1]];
t2.0.0 = 2;
debugPrintString("");
gTup = [1];
gTup.0 = 2;
debugPrintString("");
gTens = (1,2);
gTens.1 = 4;
debugPrintString("");
return (t1, t2, gTup, gTens);
}
@method_id(101)
fun test101() {
var t = (1, (2, 3), [4, 5, [6, 7]], 8);
@ -66,8 +86,8 @@ fun test104() {
}
@method_id(105)
fun test105(x: int, y: int): (tuple, int, (int, int), int, int) {
var ab = (createEmptyTuple(), (x, y), tupleSize);
fun test105(x: int, y: int): (tuple, int, (int?, int), int, int) {
var ab = (createEmptyTuple(), (x as int?, y), tupleSize);
ab.0.tuplePush(1);
tuplePush(mutate ab.0, 2);
ab.1.0 = null;
@ -78,7 +98,7 @@ fun test105(x: int, y: int): (tuple, int, (int, int), int, int) {
@method_id(106)
fun test106(x: int, y: int) {
var ab = [createEmptyTuple(), [x, y], tupleSize];
var ab = [createEmptyTuple(), [x as int?, y], tupleSize];
ab.0.tuplePush(1);
tuplePush(mutate ab.0, 2);
ab.1.0 = null;
@ -158,7 +178,7 @@ fun test114(f: int, s: int) {
@method_id(115)
fun test115() {
var y = [[[[true]]]];
return (y, y.0.0.0.0 = !y.0.0.0.0, y.0);
return (y, ((((y).0).0).0).0 = !y.0.0.0.0, y.0);
}
@method_id(116)
@ -213,6 +233,25 @@ fun test121(zero: int) {
return t;
}
fun isFirstComponentGt0<T1,T2>(t: (T1, T2)): bool {
return t.0 > 0;
}
@method_id(122)
fun test122(x: (int, int)) {
return (
isFirstComponentGt0(x), isFirstComponentGt0((2, beginCell())), isFirstComponentGt0<int,slice?>((0, null)),
x.isFirstComponentGt0(), (2, beginCell()).isFirstComponentGt0(), (0, null).isFirstComponentGt0<int,slice?>()
);
}
@method_id(123)
fun test123() {
var t = [[10, 20]] as [[int,int]]?;
((t!).0).0 = ((t!).0).1 = 100;
return t;
}
fun main(){}
@ -238,6 +277,58 @@ fun main(){}
@testcase | 119 | 1 2 3 4 | 4 1 3
@testcase | 120 | | 3 4 [ 5 6 ]
@testcase | 121 | 0 | [ 3 ]
@testcase | 122 | 1 2 | -1 -1 0 -1 -1 0
@testcase | 123 | | [ [ 100 100 ] ]
@fif_codegen
"""
testCodegenSimple PROC:<{
//
1 PUSHINT // '2=1
SINGLE // t1
2 PUSHINT // t1 '3=2
0 SETINDEX // t1
x{} PUSHSLICE // t1 '6
STRDUMP DROP
1 PUSHINT // t1 '10=1
SINGLE // t1 '9
SINGLE // t1 t2
2 PUSHINT // t1 t2 '11=2
OVER // t1 t2 '11=2 t2
0 INDEX // t1 t2 '11=2 '14
SWAP // t1 t2 '14 '11=2
0 SETINDEX // t1 t2 '14
0 SETINDEX // t1 t2
x{} PUSHSLICE // t1 t2 '17
STRDUMP DROP
1 PUSHINT // t1 t2 '20=1
SINGLE // t1 t2 '18
gTup SETGLOB
2 PUSHINT // t1 t2 '21=2
gTup GETGLOB // t1 t2 '21=2 g_gTup
SWAP // t1 t2 g_gTup '21=2
0 SETINDEX // t1 t2 g_gTup
gTup SETGLOB
x{} PUSHSLICE // t1 t2 '25
STRDUMP DROP
1 PUSHINT // t1 t2 '28=1
2 PUSHINT // t1 t2 '26=1 '27=2
PAIR
gTens SETGLOB
4 PUSHINT // t1 t2 g_gTens.1=4
gTens GETGLOB
UNPAIR // t1 t2 g_gTens.1=4 g_gTens.0 g_gTens.1
DROP // t1 t2 g_gTens.1=4 g_gTens.0
SWAP // t1 t2 g_gTens.0 g_gTens.1=4
PAIR
gTens SETGLOB
x{} PUSHSLICE // t1 t2 '36
STRDUMP DROP
gTup GETGLOB // t1 t2 g_gTup
gTens GETGLOB
UNPAIR // t1 t2 g_gTup g_gTens.0 g_gTens.1
}>
"""
@fif_codegen
"""
@ -247,26 +338,6 @@ fun main(){}
}>
"""
@fif_codegen
"""
test104 PROC:<{
//
5 PUSHINT // '2=5
DUP // '2=5 '3=5
PAIR // '1
SINGLE // m
10 PUSHINT // m '5=10
20 PUSHINT // m '5=10 '6=20
s2 PUSH // m '5=10 '6=20 m
0 INDEX // m '10=10 '12=20 '8
SWAP // m '10=10 '8 '12=20
1 SETINDEX // m '10=10 '8
SWAP // m '8 '10=10
0 SETINDEX // m '8
0 SETINDEX // m
...
"""
@fif_codegen
"""
testCodegenIndexPostfix1 PROC:<{

View file

@ -18,10 +18,12 @@ fun test1(x: int, y: int) {
__expect_type(random() ? x : y, "int");
__expect_type(eq(x), "int");
__expect_type(eq<int>(x), "int");
__expect_type(eq<int>(null), "int");
__expect_type(eq<int?>(null), "int?");
__expect_type(x as int, "int");
__expect_type(+x, "int");
__expect_type(~x, "int");
__expect_type(x!, "int");
__expect_type(x!!!, "int");
{
var x: slice = beginCell().endCell().beginParse();
__expect_type(x, "slice");
@ -62,9 +64,9 @@ fun test5(x: int) {
__expect_type([], "[]");
__expect_type([x], "[int]");
__expect_type([x, x >= 1], "[int, bool]");
__expect_type([x, x >= 1, null as slice], "[int, bool, slice]");
__expect_type([x, x >= 1, null as slice?], "[int, bool, slice?]");
__expect_type((x, [x], [[x], x]), "(int, [int], [[int], int])");
__expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell]");
__expect_type(getMyOriginalBalanceWithExtraCurrencies(), "[int, cell?]");
}
fun test6() {
@ -84,6 +86,17 @@ fun test7() {
// __expect_type(eq<(int, slice)>, "(int, slice) -> (int, slice)");
}
fun alwaysThrows(): never { throw 123; }
fun alwaysThrowsNotAnnotated() { throw 123; }
fun alwaysThrowsNotAnnotated2() { alwaysThrows(); }
fun test9() {
__expect_type(alwaysThrows(), "never");
__expect_type(alwaysThrows, "() -> never");
__expect_type(alwaysThrowsNotAnnotated(), "void");
__expect_type(alwaysThrowsNotAnnotated2(), "void");
}
fun main() {
return 0;

View file

@ -1,9 +1,9 @@
fun main() {
var c = 1;
(c, c) = (2, 3);
var t = createEmptyTuple();
t.0 = (1, 2);
}
/**
@compilation_should_fail
@stderr one variable modified twice inside the same expression
@stderr a tuple can not have `(int, int)` inside, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -1,11 +1,8 @@
fun incThree(mutate a: int, mutate b: int, mutate c: int) {}
fun main() {
var c = [[[1, 2]]];
incThree(mutate c.0.0.0, mutate c.0.0.1, mutate c.0.0.0);
fun main(cs: slice) {
var cb = cs.tupleSize;
}
/**
@compilation_should_fail
@stderr one variable modified twice inside the same expression
@stderr referencing a method for `tuple` with object of type `slice`
*/

View file

@ -1,10 +1,9 @@
global gg: (int, int);
fun main() {
[gg.0, gg.1, gg.0] = [0, 1, 0];
var t = createEmptyTuple();
var xy = t.0 as (int, int);
}
/**
@compilation_should_fail
@stderr one variable modified twice inside the same expression
@stderr a tuple can not have `(int, int)` inside, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -1,10 +0,0 @@
global gg: (int, [int, int]);
fun main() {
(gg.1.0, gg.1, gg.1.1) = (0, [1, 2], 3);
}
/**
@compilation_should_fail
@stderr one variable both modified and read inside the same expression
*/

View file

@ -1,9 +0,0 @@
fun main() {
var ab = (1, 2);
(ab, ab.1) = ((2, 3), 4);
}
/**
@compilation_should_fail
@stderr one variable both modified and read inside the same expression
*/

View file

@ -1,9 +0,0 @@
fun main() {
var t = createEmptyTuple();
t.0 = (1, 2);
}
/**
@compilation_should_fail
@stderr can not put `(int, int)` into a tuple, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -1,8 +0,0 @@
fun main(cs: slice) {
var cb = cs.tupleSize;
}
/**
@compilation_should_fail
@stderr referencing a method for `tuple` with object of type `slice`
*/

View file

@ -7,5 +7,5 @@ fun main() {
/**
@compilation_should_fail
@stderr can not put `(int, builder)` into a tuple, because it occupies 2 stack slots in TVM, not 1
@stderr a tuple can not have `(int, builder)` inside, because it occupies 2 stack slots in TVM, not 1
*/

View file

@ -6,5 +6,5 @@ fun failCantDeduceWithoutArgument() {
/**
@compilation_should_fail
@stderr can not deduce X for generic function `f<X>`
@stderr too few arguments in call to `f`, expected 2, have 1
*/

View file

@ -0,0 +1,11 @@
fun calcSum<X>(x: X, y: X) { return x + y; }
fun cantApplyPlusOnNullable() {
return calcSum(((0 as int?)), null);
}
/**
@compilation_should_fail
@stderr in function `calcSum<int?>`
@stderr can not apply operator `+` to `int?` and `int?`
*/

View file

@ -0,0 +1,17 @@
fun eq<X>(v: X) {}
fun cantDeduceWhenNotInferred() {
// at type inferring (before type checking) they are unknown
var (x, y) = 2;
eq(x as int); // ok (since execution doesn't reach type checking)
eq<int>(x); // ok (since execution doesn't reach type checking)
eq(x);
}
/**
@compilation_should_fail
@stderr in function `cantDeduceWhenNotInferred`
@stderr can not deduce X for generic function `eq<X>`
@stderr eq(x);
*/

View file

@ -11,8 +11,7 @@ fun foo<X>(value: X) : X {
/**
@compilation_should_fail
@stderr while instantiating generic function `foo<slice>`
@stderr while instantiating generic function `bar<slice>`
@stderr in function `bar<slice>`
@stderr can not convert type `int` to return type `slice`
@stderr return 1
*/

View file

@ -0,0 +1,10 @@
fun getNullableTuple(): tuple? { return createEmptyTuple(); }
fun cantUseLValueUnwrappedNotNull() {
tuplePush(mutate getNullableTuple()!, 1);
}
/**
@compilation_should_fail
@stderr function call can not be used as lvalue
*/

View file

@ -0,0 +1,10 @@
fun getNullableTuple(): tuple? { return createEmptyTuple(); }
fun cantUseLValueUnwrappedNotNull() {
tuplePush(mutate getNullableTuple()!, 1);
}
/**
@compilation_should_fail
@stderr function call can not be used as lvalue
*/

View file

@ -0,0 +1,13 @@
fun acceptMutateNullableTensor(mutate self: (int, int)?) {
}
fun cantModifyTupleIndexWithTypeTransition() {
var t = [1, null];
t.1.acceptMutateNullableTensor();
}
/**
@compilation_should_fail
@stderr can not call method for mutate `(int, int)?` with object of type `null`
@stderr because mutation is not type compatible
*/

View file

@ -0,0 +1,8 @@
fun invalidNever(): never {
if (random()) { throw 123; }
}
/**
@compilation_should_fail
@stderr a function returning `never` can not have a reachable endpoint
*/

View file

@ -3,6 +3,7 @@ fun failBitwiseNotOnBool() {
if (~eq) {
return 0;
}
return -1;
}
/**

View file

@ -0,0 +1,14 @@
fun autoGetIntOrNull() {
if (random()) { return 1; }
return null;
}
fun testAutoInferredIntOrNull() {
var b: builder = autoGetIntOrNull() as builder;
}
/**
@compilation_should_fail
@stderr type `int?` can not be cast to `builder`
*/

View file

@ -0,0 +1,13 @@
fun getNullable4(): int? {
return 4;
}
fun testCantSumNullable() {
return 1 + getNullable4();
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int` and `int?`
*/

View file

@ -0,0 +1,13 @@
@pure
fun myDictDeleteStrict(mutate self: cell, keyLen: int, key: int): bool
asm(key self keyLen) "DICTIDEL";
fun testCantCallDictMethodsOnNullable(c: cell) {
c.beginParse().loadDict().myDictDeleteStrict(16, 1);
}
/**
@compilation_should_fail
@stderr can not call method for `cell` with object of type `cell?`
*/

View file

@ -0,0 +1,10 @@
fun testCantUseNullableAsCondition(x: int?) {
if (x) { return 1; }
return 0;
}
/**
@compilation_should_fail
@stderr can not use `int?` as a boolean condition
*/

View file

@ -0,0 +1,16 @@
fun incrementOrSetNull(mutate x: int?) {
if (random()) { x! += 1; }
else { x = null; }
}
fun cantCallMutateMethodNotNullable() {
var x = 1;
incrementOrSetNull(mutate x);
return x;
}
/**
@compilation_should_fail
@stderr can not pass `int` to mutate `int?`
@stderr because mutation is not type compatible
*/

View file

@ -0,0 +1,12 @@
fun getNullableInt(): int? { return 5; }
fun testCantApplyNotNullForAlwaysNull() {
var x: int? = getNullableInt();
if (x != null) { return 0; }
return x! + 1;
}
/**
@compilation_should_fail
@stderr operator `!` used for always null expression
*/

View file

@ -0,0 +1,15 @@
fun getNullableInt(): int? { return 5; }
fun testFlowContextAppliedInBinaryOperator() {
var x: int? = getNullableInt();
var y: int? = getNullableInt();
if ((y = null) < y) {
return -100;
}
return 0;
}
/**
@compilation_should_fail
@stderr can not apply operator `<` to `null` and `null`
*/

View file

@ -0,0 +1,14 @@
fun getNullableInt(): int? { return 5; }
fun testNeverTypeOccurs() {
var x: int? = getNullableInt();
if (x == null && x != null) {
return x + 0;
}
return 0;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `never` and `int`
*/

View file

@ -0,0 +1,9 @@
fun testLogicalAndNotConditionDoesntAffect(x: int?) {
var gt1 = x != null && x > 1;
return x + 0;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int`
*/

View file

@ -0,0 +1,15 @@
fun getTensor(): (int?, int?) { return (5, null); }
fun testSmartCastsForFieldsDropAfterAssign() {
var t = getTensor();
if (t.0 != null && t.1 != null) {
t = getTensor();
return t.0 + t.1;
}
return -1;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int?`
*/

View file

@ -0,0 +1,16 @@
fun getNullableInt(): int? { return 5; }
fun getTensor(x: int?): (int?, int) { return (x, 0); }
fun testSmartCastsDropAfterAssign() {
var x: int? = 0;
var y: int? = 0;
(getTensor(x = getNullableInt()).0, getTensor(y = getNullableInt()).0) = (x + y, x - y);
return x+y;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int?`
@stderr x + y, x - y
*/

View file

@ -0,0 +1,14 @@
fun takeNullableTensor(mutate ij: (int, int)?) { }
fun testSmartCastsDropAfterMutate() {
var x: (int, int)? = (1, 2);
return x.0; // ok
takeNullableTensor(mutate x);
return x.1; // error
}
/**
@compilation_should_fail
@stderr type `(int, int)?` is not indexable
@stderr return x.1
*/

View file

@ -0,0 +1,12 @@
fun getNullableInt(): int? { return 5; }
fun testAssertThrowIsConditional() {
var (x, y) = (getNullableInt(), getNullableInt());
assert(x != null) throw(y = 10);
return x + y;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int` and `int?`
*/

View file

@ -0,0 +1,18 @@
fun assignNull2<T1, T2>(mutate x: T1?, mutate y: T2?) {
if (false) {
x = null;
y = null;
}
}
fun testSmartCastsDropAfterNullableGeneric() {
var (x: int?, y: int?) = (1, 2);
x * y; // ok
assignNull2(x, y); // treated like assignments to nullable
x << y; // error
}
/**
@compilation_should_fail
@stderr can not apply operator `<<` to `int?` and `int?`
*/

View file

@ -0,0 +1,15 @@
fun getNullableInt(): int? { return 5; }
fun testReassignInRedef() {
var t1: int? = getNullableInt();
if (t1 != null) {
var (t1 redef, t2) = (getNullableInt(), 5);
return t1 + t2;
}
return -1;
}
/**
@compilation_should_fail
@stderr can not apply operator `+` to `int?` and `int`
*/

View file

@ -0,0 +1,14 @@
fun getNullableInt(): int? { return 5; }
fun testTryBodyDontSmartCast() {
var x = getNullableInt();
try {
x = 5;
} catch {}
return x * 10; // x is not int here; for now, we have no exception edges, assuming it can be anywhere inside try
}
/**
@compilation_should_fail
@stderr can not apply operator `*` to `int?` and `int`
*/

View file

@ -0,0 +1,15 @@
fun getNullableInt(): int? { return 5; }
fun testDoWhileCondition() {
var (x: int?, y: int?) = (10, 20);
do {
x = getNullableInt();
y = getNullableInt();
} while(x == null);
return x * y; // x is 100% int, but y is not
}
/**
@compilation_should_fail
@stderr can not apply operator `*` to `int` and `int?`
*/

View file

@ -0,0 +1,9 @@
fun cantAssignIntToTensor() {
var (x, y) = 2;
x + y;
}
/**
@compilation_should_fail
@stderr can not assign `int` to a tensor
*/

View file

@ -0,0 +1,9 @@
fun cantAssignSizesMismatch() {
var [x, y] = [2, 3, 4];
x + y;
}
/**
@compilation_should_fail
@stderr can not assign `[int, int, int]`, sizes mismatch
*/

View file

@ -53,9 +53,8 @@ fun testDict(last: int) {
}
@method_id(105)
fun testNotNull(x: int) {
// return [x == null, null == x, !(x == null), null == null, +(null != null)];
return [x == null, null == x, !(x == null)];
fun testNotNull(x: int?) {
return [x == null, null == x, !(x == null), null == null, (null != null) as int];
}
@method_id(106)
@ -170,8 +169,8 @@ fun main() {
@testcase | 104 | 50 | 3 5 -1
@testcase | 104 | 100 | 3 5 5
@testcase | 104 | 0 | 3 -1 5
@testcase | 105 | 0 | [ 0 0 -1 ]
@testcase | 105 | null | [ -1 -1 0 ]
@testcase | 105 | 0 | [ 0 0 -1 -1 0 ]
@testcase | 105 | null | [ -1 -1 0 -1 0 ]
@testcase | 106 | | [ 0 0 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ 0 -1 ]
@testcase | 107 | | [ -1 -1 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ -1 0 ]
@testcase | 108 | 1 2 | -1

View file

@ -307,7 +307,7 @@ fun main(){}
...
incrementTwoInPlace CALLDICT // x y sum1
-ROT
10 PUSHINT // sum1 x y '10=10
10 PUSHINT // sum1 x y '11=10
incrementTwoInPlace CALLDICT // sum1 x y sum2
s1 s3 s0 XCHG3 // x y sum1 sum2
}>

View file

@ -0,0 +1,28 @@
fun takeInt(a: int) {}
@method_id(101)
fun test1(x: int?) {
if (x == null && x != null) {
var y = x;
__expect_type(y, "never");
__expect_type(y!, "never");
// `never` type is assignable to anything, flow won't reach this point
var t: (int, int) = x;
t = y;
takeInt(x);
var cb: (int) -> int = x;
x as int?;
x as (int, int)?;
x as never;
return x;
}
return 123;
}
fun main() {
__expect_type(test1, "(int?) -> int");
}
/**
@testcase | 101 | null | 123
*/

View file

@ -2,13 +2,13 @@ import "@stdlib/lisp-lists"
@method_id(101)
fun test1() {
var numbers: tuple = createEmptyList();
var numbers: tuple? = createEmptyList();
numbers = listPrepend(1, numbers);
numbers = listPrepend(2, numbers);
numbers = listPrepend(3, numbers);
numbers = listPrepend(4, numbers);
var (h: int, numbers redef) = listSplit(numbers);
h += listGetHead(numbers);
var (h: int, numbers redef) = listSplit(numbers!);
h += listGetHead(numbers!);
_ = null;
(_, _) = (null, null);
@ -22,22 +22,24 @@ fun test1() {
}
@method_id(102)
fun test2(x: int) {
fun test2(x: int?) {
if (null != x) {
var y: int = null;
var y: int? = null;
if (y != null) { return 10; }
return y;
if (10 < 20) { // always true at runtime (not at compile-time)
return y;
}
}
try {
return x + 10; // will throw, since not a number
return x! + 10; // will throw, since not a number
} catch {
return -1;
}
return 100;
}
fun myIsNull(x: int): int {
return x == null ? -1 : x;
fun myIsNull(x: int?): int {
return x == null ? -1 : x!;
}
@method_id(103)
@ -45,14 +47,6 @@ fun test3(x: int) {
return myIsNull(x > 10 ? null : x);
}
fun getUntypedNull() {
var untyped: null = null;
if (true) {
return untyped;
}
return untyped;
}
@method_id(104)
fun test4(): null {
var (_, (_, untyped: null)) = (3, (createEmptyTuple, null));
@ -62,23 +56,25 @@ fun test4(): null {
return untyped;
}
@method_id(105)
fun test5() {
var n: slice = getUntypedNull();
return !(null == n) ? n.loadInt(32) : 100;
}
@method_id(107)
fun test7() {
var b = beginCell().storeMaybeRef(null);
var s = b.endCell().beginParse();
var b = beginCell().storeMaybeRef(null) as builder?;
var s = b!.endCell().beginParse();
var c = s.loadMaybeRef();
return (null == c) as int * 10 + (b != null) as int;
}
fun test8() {
__expect_type(null, "null");
__expect_type([[null]], "[[null]]");
__expect_type(null as tuple?, "tuple?");
__expect_type(null as [int]?, "[int]?");
__expect_type(((null)) as (int, int)?, "(int, int)?");
}
fun main() {
// now, the compiler doesn't optimize this at compile-time, fif codegen contains ifs
var i: int = null;
// the compiler optimizes this at compile-time
var i: int? = null;
if (i == null) {
return 1;
}
@ -92,7 +88,6 @@ fun main() {
@testcase | 103 | 5 | 5
@testcase | 103 | 15 | -1
@testcase | 104 | | (null)
@testcase | 105 | | 100
@testcase | 107 | | -11
@fif_codegen
"""
@ -120,12 +115,7 @@ fun main() {
"""
main PROC:<{
//
PUSHNULL // i
ISNULL // '2
IFJMP:<{ //
1 PUSHINT // '3=1
}> //
10 PUSHINT // '4=10
1 PUSHINT // '3=1
}>
"""
@ -133,14 +123,14 @@ fun main() {
"""
test7 PROC:<{
...
LDOPTREF // b '8 '7
LDOPTREF // b '9 '8
DROP // b c
ISNULL // b '11
10 MULCONST // b '13
SWAP // '13 b
ISNULL // '13 '14
NOT // '13 '15
ADD // '16
NOT // '13 '14
ADD // '15
}>
"""
*/

View file

@ -0,0 +1,492 @@
fun getNullableInt(): int? { return 5; }
fun sumOfNullableTensorComponents(t: (int, int)?): int {
if (t == null) { return 0; }
return t!.0 + t!.1;
}
fun isTensorNull(t: (int, int)?) {
return t == null;
}
fun incrementNullableTensorComponents(mutate self: (int, int)?): self {
if (self != null) {
self!.0 += 1;
self!.1 += 1;
}
return self;
}
fun incrementTensorComponents(mutate self: (int, int)): self {
self.0 += 1;
self.1 += 1;
return self;
}
fun assignFirstComponent(mutate t: (int, int), first: int) {
t!.0 = first;
}
fun assignFirstComponentNullable(mutate t: (int, int)?, first: int) {
if (t == null) {
t = (first, 0);
} else {
t!.0 = first;
}
}
fun getNullableTensor(firstComponent: int?): (int, int)? {
return firstComponent == null ? null : (firstComponent!, 2);
}
fun sumOfTensor(x: (int, int)) {
return x.0 + x.1;
}
fun assignNullTo<T>(mutate x: T?) {
x = null;
}
fun getTensor12() {
return (1,2);
}
@method_id(101)
fun test101(): (int, int)? {
return (1, 2);
}
@method_id(102)
fun test102(): ((int, int)?, (int, int)?) {
var t = (1, 2);
return (t, null);
}
@method_id(103)
fun test103(t: (int, int)) {
var t2: (int, int)? = t;
return (sumOfNullableTensorComponents(t), sumOfNullableTensorComponents(t2), sumOfNullableTensorComponents(null), t2);
}
@method_id(104)
fun test104() {
var t1_1: (int, int)? = (1, 2);
var t1_2: (int, int)? = t1_1;
var t1_3: (int, int)? = t1_1!;
var t2_1: (int, int)? = getNullableTensor(null);
var t2_2 = t2_1;
return (t1_3, t2_2);
}
@method_id(105)
fun test105() {
return (null as (int, slice, cell)?, (1, 2, 3) as (int, int, int)?);
}
@method_id(106)
fun test106() {
var t: (int?, int?)? = (((((1, 2))) as (int, int)));
return t;
}
@method_id(107)
fun test107() {
var ab = (1, 2);
var ab2: (int, int)? = ab;
return (isTensorNull(ab), isTensorNull(ab2), isTensorNull(null), ab.isTensorNull(), ab2.isTensorNull(), null.isTensorNull());
}
@method_id(108)
fun test108(x1: (int, int)) {
incrementTensorComponents(mutate x1);
x1.incrementTensorComponents();
var x2: (int, int)? = x1;
__expect_type(x2, "(int, int)");
x2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x2);
__expect_type(x2, "(int, int)?");
var x3: (int, int)? = null;
__expect_type(x3, "null");
x3.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate x3);
return (x1, x2, x3);
}
fun isTensorNullGen<T1, T2>(t: (T1, T2)?) {
return t == null;
}
@method_id(109)
fun test109() {
var x1 = (1, 2);
var x2: (int, int)? = x1;
var x3: (int, int)? = x1.1 > 10 ? (1, 2) : null;
return (
isTensorNullGen(x1), isTensorNullGen(x2), isTensorNullGen<int,int>(null),
isTensorNullGen<int,int>(x1), isTensorNullGen<int,int>(x3),
x1.isTensorNullGen(), x2.isTensorNullGen(), x3.isTensorNullGen(), null.isTensorNullGen<int,int>()
);
}
global g110_1: (int, int);
global g110_2: (int, int)?;
@method_id(110)
fun test110() {
g110_1 = getNullableTensor(1)!;
incrementTensorComponents(mutate g110_1);
g110_1.incrementTensorComponents();
g110_2 = g110_1;
g110_2.incrementNullableTensorComponents().incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate g110_2);
var tmp = g110_2;
g110_2 = null;
g110_2.incrementNullableTensorComponents();
incrementNullableTensorComponents(mutate g110_2);
return (g110_1, g110_2, tmp);
}
@method_id(111)
fun test111() {
var x = (1, 2);
assignFirstComponent(mutate x, 50);
var x2: (int, int)? = null;
var x3 = x2 as (int, int)?;
assignFirstComponentNullable(mutate x2, 30);
assignFirstComponentNullable(mutate x3, 70);
g110_1 = (1, 2);
g110_2 = null;
assignFirstComponent(mutate g110_1, 90);
assignFirstComponentNullable(mutate g110_2, 100);
return (x.0, x2!.0, x3!.0, g110_1.0, g110_2!.0);
}
@method_id(112)
fun test112() {
var x: (int, int)? = (10, 20);
incrementTensorComponents(mutate x!);
x!.incrementTensorComponents();
return x;
}
@method_id(113)
fun test113() {
var t = [1, null]; // t.1 is always null
return isTensorNull(t.1);
}
@method_id(114)
fun test114(): ((slice, (cell, [int, slice, tuple]))?, slice?, (int?, bool?)?) {
var t = [[null]];
return (t.0.0, t.0.0, t.0.0);
}
@method_id(115)
fun test115() {
var tt = getNullableTensor(null);
assignFirstComponentNullable(mutate tt, 5);
return (
getNullableTensor(1)!.incrementTensorComponents(),
sumOfNullableTensorComponents(getNullableTensor(1).incrementNullableTensorComponents().incrementNullableTensorComponents()),
getNullableTensor(null).incrementNullableTensorComponents(),
tt,
sumOfNullableTensorComponents(getNullableTensor(null))
);
}
@method_id(116)
fun test116(returnNull: bool) {
var t1: (int, int)? = returnNull ? null : getTensor12();
var t2 = returnNull ? null as (int, int)? : getTensor12() as (int, int)?;
returnNull ? null : (1, 2);
return (t1, t2);
}
@method_id(117)
fun test117() {
var (a, b: (int, int)?, c) = (1, null, 3);
return (b, a, c);
}
fun autoInferNullableTensor(a: int?, b: int) {
if (a != null) {
return (a!, b);
}
return null;
}
@method_id(118)
fun test118(a: int?) {
return autoInferNullableTensor(a, 10);
}
@method_id(119)
fun test119() {
var x: (int, int)? = (1, 2);
x = null;
var tt: (int, (int, int)?) = (0, (1, 2));
tt.1 = null;
var third: (int, (int, int)?, int) = (0, (1, 2), 3);
third.2 = 100;
return (x, tt.1, third.1, third.2);
}
@method_id(120)
fun test120(setNull: bool) {
var x: (int, int)? = (1, 2);
if (setNull) {
assignNullTo(mutate x);
}
return x;
}
@method_id(121)
fun test121() {
var t: [int?, [int?, int?]?] = [1, [2, 3]];
t.1 = [3, 4];
return t;
}
@method_id(122)
fun test122(setNull: bool) {
var t: [int?, [int?, int?]?, int?, [int?, int?]?]? = [1, [2, 3], 4, null];
if (setNull) {
assignNullTo(mutate t!.1);
} else {
var rhs = [3, 4];
t!!.1 = rhs;
}
return t;
}
@method_id(123)
fun test123() {
var t: (int?, (int?, int?)?) = (1, (2, 3));
t.1 = (3, 4);
return t;
}
@method_id(124)
fun test124(setNull: bool) {
var t: (int?, (int?, int?)?, int?, (int?, int?)?)? = (1, (2, 3), 4, null);
if (setNull) {
assignNullTo(mutate t!.1);
} else {
var rhs = (3, 4);
t!!.1 = rhs;
}
return t;
}
global g125: int;
fun getT125(): (int, (int, int)?, (int?, int)?) { return (g125 += 1, null, null); }
@method_id(125)
fun test125() {
g125 = 0;
getT125().1 = null;
getT125().2 = (1, 2);
(getT125()!! as (int, (int, int)?, (int?, int)?)).2 = null;
// test that nothing left on a stack
return g125;
}
@method_id(126)
fun test126() {
var tt1: (int, null, int) = (1, null, 2);
var (a: int, b: (int, int)?, c: int) = tt1;
return (a, b, c);
}
@method_id(127)
fun test127(choice: int) {
var tt1: (int, null, int) = (1, null, 2);
var tt2: (int, (int, int), int) = (1, (2, 3), 4);
var tt3: (int, (int, int)?, int) = (1, null, 5);
var abc: (int, (int, int)?, int) = choice == 1 ? tt1 : choice == 2 ? tt2 : tt3;
return abc;
}
fun get128_1() { return (1, null, 2); }
fun get128_2() { return null; }
fun get128_3() { return (1, (2, 3), 4); }
fun takeT128(abc: (int, (int, int)?, int)?) { return abc; }
@method_id(128)
fun test128(choice: int) {
if (choice == 1) {
return takeT128(get128_1())!;
}
if (choice == 2) {
return takeT128(get128_2());
}
return takeT128(get128_3());
}
@method_id(129)
fun test129(setNull: bool) {
var t: (int?, int?) = (getNullableInt(), getNullableInt());
var r1 = (t, t == null, t != null);
t = (setNull ? null : 1, setNull ? null : 2);
var r2 = (t, t == null, t != null);
return (r1, r2);
}
@method_id(130)
fun test130(setNull: bool) {
var os: (int, (int, int)?) = (1, setNull ? null : (2, 3));
return os;
}
fun getEmptyNullableTensor(getNull: bool): ()? {
return getNull ? null : ();
}
@method_id(131)
fun test131() {
var nonNullEmptyT = getEmptyNullableTensor(false);
var nullEmptyT = getEmptyNullableTensor(true);
var emptyT = nonNullEmptyT!;
__expect_type(emptyT, "()");
var doubleNulls1 = (null, null) as (()?, ()?);
var doubleNulls2 = ((), ()) as (()?, ()?);
var doubleNulls3 = ((), ()) as (()?, ()?)?;
var stillEmpty = ((), ());
return (nonNullEmptyT, 777, nullEmptyT, 777, emptyT, 777, nullEmptyT!, 777, doubleNulls1, doubleNulls2, 777, doubleNulls3, 777, stillEmpty);
}
@method_id(132)
fun test132() {
var doubleNulls: (()?, ()?) = (getEmptyNullableTensor(true), getEmptyNullableTensor(false));
var result = ((null as ()?) == null, (() as ()?) == null, doubleNulls.0 == null, doubleNulls.1 == null);
var aln1: int? = (doubleNulls.1 = null);
var aln2: null = (doubleNulls.1 = null);
return (result, 777, aln1, aln2, doubleNulls.1 == null, doubleNulls);
}
@method_id(133)
fun test133() {
var x: (int, int)? = (10, 20);
return sumOfTensor(x) + x.0 + x.1; // smart casted
}
@method_id(134)
fun test134(): (int, int)? {
var x: (int, int)? = (10, 20);
incrementTensorComponents(mutate x); // smart casted
return x;
}
fun getNormalNullableTensorWidth1(vLess100: int?): ([int?], ())? {
if (vLess100 != null && vLess100 >= 100) {
return null;
}
return ([vLess100], ()); // such a nullable tensor can store NULL in the same slot
}
fun getTrickyNullableTensorWidth1(vLess100: int?): (int?, ())? {
if (vLess100 != null && vLess100 >= 100) {
return null;
}
return (vLess100, ()); // such a nullable tensor requires an extra stack slot for null presence
}
fun getEvenTrickierNullableWidth1(vLess100: int?): ((), (int?, ()), ())? {
if (vLess100 != null && vLess100 >= 100) {
return null;
}
return ((), (vLess100, ()), ());
}
@method_id(135)
fun test135() {
var n1 = getNormalNullableTensorWidth1(10); // ([10], ())
var n2 = getNormalNullableTensorWidth1(null); // ([null], ())
var n3 = getNormalNullableTensorWidth1(100); // null
var t1 = getTrickyNullableTensorWidth1(10); // (10, ())
var t2 = getTrickyNullableTensorWidth1(null); // (null, ())
var t3 = getTrickyNullableTensorWidth1(100); // null
var e1 = getEvenTrickierNullableWidth1(10); // ((), (10, ()), ())
var e2 = getEvenTrickierNullableWidth1(null); // ((), (null, (), ())
var e3 = getEvenTrickierNullableWidth1(100); // null
return (n1, n2, n3, 777, t1, t2, t3, 777, e1, e2, e3, 777,
n1 == null, n2 == null, n3 == null, t1 == null, t2 == null, t3 == null, e1 == null, e2 == null, e3 == null, 777,
t1!.0 == null, t2!.0 == null, e1!.1.0 == null, e1!.1.1 == null, e2!.1.0 == null, e2!.1.1 == null);
}
fun main(){}
/**
@testcase | 101 | | 1 2 -1
@testcase | 102 | | 1 2 -1 (null) (null) 0
@testcase | 103 | 1 2 | 3 3 0 1 2
@testcase | 104 | | 1 2 (null) (null) 0
@testcase | 105 | | (null) (null) (null) 0 1 2 3 -1
@testcase | 106 | | 1 2
@testcase | 107 | | 0 0 -1 0 0 -1
@testcase | 108 | 5 6 | 7 8 10 11 -1 (null) (null) 0
@testcase | 109 | | 0 0 -1 0 -1 0 0 -1 -1
@testcase | 110 | | 3 4 (null) (null) 0 6 7 -1
@testcase | 111 | | 50 30 70 90 100
@testcase | 112 | | 12 22
@testcase | 113 | | -1
@testcase | 114 | | (null) (null) (null) 0 (null) (null) (null) 0
@testcase | 115 | | 2 3 7 (null) (null) 0 5 0 -1 0
@testcase | 116 | -1 | (null) (null) 0 (null) (null) 0
@testcase | 116 | 0 | 1 2 -1 1 2 -1
@testcase | 117 | | (null) 1 3
@testcase | 118 | 5 | 5 10 -1
@testcase | 118 | null | (null) (null) 0
@testcase | 119 | | (null) (null) 1 2 -1 100
@testcase | 120 | -1 | (null) (null) 0
@testcase | 120 | 0 | 1 2 -1
@testcase | 121 | | [ 1 [ 3 4 ] ]
@testcase | 122 | 0 | [ 1 [ 3 4 ] 4 (null) ]
@testcase | 122 | -1 | [ 1 (null) 4 (null) ]
@testcase | 123 | | 1 3 4 -1
@testcase | 124 | 0 | 1 3 4 -1 4 (null) (null) 0
@testcase | 124 | -1 | 1 (null) (null) 0 4 (null) (null) 0
@testcase | 125 | | 3
@testcase | 126 | | 1 (null) 2
@testcase | 127 | 1 | 1 (null) (null) 0 2
@testcase | 127 | 2 | 1 2 3 -1 4
@testcase | 127 | 3 | 1 (null) (null) 0 5
@testcase | 128 | 1 | 1 (null) (null) 0 2 -1
@testcase | 128 | 2 | (null) (null) (null) (null) (null) 0
@testcase | 128 | 3 | 1 2 3 -1 4 -1
@testcase | 129 | 0 | 5 5 0 -1 1 2 0 -1
@testcase | 129 | -1 | 5 5 0 -1 (null) (null) 0 -1
@testcase | 130 | 0 | 1 2 3 -1
@testcase | 130 | -1 | 1 (null) (null) 0
@testcase | 131 | | -1 777 0 777 777 777 0 0 -1 -1 777 -1 -1 -1 777
@testcase | 132 | | -1 0 -1 0 777 (null) (null) -1 0 0
@testcase | 133 | | 60
@testcase | 134 | | 11 21 -1
@testcase | 135 | | [ 10 ] [ (null) ] (null) 777 10 -1 (null) -1 (null) 0 777 10 -1 (null) -1 (null) 0 777 0 0 -1 0 0 -1 0 0 -1 777 0 -1 0 0 -1 0
@fif_codegen
"""
isTensorNull PROC:<{
// t.0 t.1 t.NNFlag
2 1 BLKDROP2 // t.NNFlag
0 EQINT // '3
}>
"""
@fif_codegen
"""
test113 PROC:<{
//
1 PUSHINT // '2=1
PUSHNULL // '2=1 '3
PAIR // t
1 INDEX // '5
PUSHNULL // '5 '6
0 PUSHINT // '5 '6 '7=0
isTensorNull CALLDICT // '8
}>
"""
*/

View file

@ -0,0 +1,109 @@
fun getNullable4(): int? { return 4; }
fun getNullableIntNull(): int? asm "PUSHNULL";
fun eqInt(x: int) { return x; }
fun eq<T>(x: T) { return x; }
fun unwrap<T>(x: T?): T { return x!; }
fun intOr0(x: int?): int { return null == x ? 0 : x!; }
@method_id(101)
fun test101(x: int) {
var re = x == 0 ? null : 100;
return re == null ? re : 200 + getNullable4()!;
}
@method_id(102)
fun test102(a: int) {
try {
throw (123, a > 10 ? null : a);
return 0;
} catch (excno, arg) {
var i = arg as int?;
return excno + (i != null ? i!!!!! : -100);
}
}
@method_id(103)
fun test103(x: int?): (bool, bool, int) {
var x_gt_0 = x != null && eqInt(x!) > 0;
var x_lt_0 = x != null && eq(x)! < 0;
if (x == null) {
return (x_gt_0, x_lt_0, 0);
}
return (x_gt_0, x_lt_0, x!);
}
@method_id(104)
fun test104(x: int?) {
var x2 = eq(x = 10);
var ab = (x2, getNullableIntNull());
return (unwrap(ab.0) + (ab.1 == null ? -100 : ab.1!), ab.1);
}
@method_id(105)
fun test105() {
var xy: (int?, int?) = (5, null);
var ab = [1 ? [xy.0, xy.1] : null];
ab.0!.0 = intOr0(ab.0!.0);
ab.0!.1 = intOr0(ab.0!.1);
return ab.0!.0! + ab.0!.1!;
}
global gTup106: tuple?;
global gInt106: int?;
@method_id(106)
fun test106() {
gInt106 = 0;
gInt106! += 5;
var int106: int? = 0;
var gTup106 = createEmptyTuple();
gTup106!.tuplePush(createEmptyTuple());
(gTup106!.0 as tuple?)!.tuplePush(0 as int?);
tuplePush(mutate gTup106!, gInt106);
tuplePush(mutate gTup106!.0, int106! += 1);
return (gTup106 == null, null != gTup106, gTup106, gTup106!.0 as tuple?);
}
@method_id(107)
fun test107() {
var b: builder? = beginCell();
b!.storeInt(1, 32).storeInt(2, 32);
b = b!.storeInt(3, 32);
storeInt(mutate b!, 4, 32);
(b! as builder).storeInt(5, 32);
return b!.getBuilderBitsCount();
}
@method_id(108)
fun test108() {
var (a, b: cell?, c) = (1, beginCell().endCell(), 3);
if (10>3) { b = null; }
return a + (b == null ? 0 : b!.beginParse().loadInt(32)) + c;
}
@method_id(109)
fun test109() {
var a = getNullable4();
var b = getNullable4();
return ([a, b] = [3, 4], a, b);
}
fun main(x: int?, y: int?) {
}
/**
@testcase | 101 | 0 | (null)
@testcase | 101 | -1 | 204
@testcase | 102 | 5 | 128
@testcase | 102 | 15 | 23
@testcase | 103 | 10 | -1 0 10
@testcase | 104 | 8 | -90 (null)
@testcase | 105 | | 5
@testcase | 106 | | 0 -1 [ [ 0 1 ] 5 ] [ 0 1 ]
@testcase | 107 | | 160
@testcase | 108 | | 4
@testcase | 109 | | [ 3 4 ] 3 4
*/

View file

@ -0,0 +1,678 @@
// the goal of this file is not only to @testcase results —
// but to check that this file compiles
fun getNullableInt(): int? { return 5; }
fun getNullableSlice(): slice? { return null; }
fun takeNullableInt(a: int?) {}
fun takeNullableSlice(a: slice?) {}
fun increment(mutate self: int) { self += 1; }
fun assignToInt(mutate self: int, value: int) { self = value; }
fun assignToNullableInt(mutate self: int?, value: int) { self = value; }
fun sameTensor(t: (int, int)) { return t; }
fun sameTensor2(t: (int?, (slice, slice, slice, builder)?)) { return t; }
fun eq<T>(v: T) { return v; }
fun getTwo<X>(): X { return 2 as X; }
fun test1(): int {
var x = getNullableInt();
var y = getNullableInt();
if (x != null && y != null) {
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
return -1;
}
fun test2() {
var (x, y) = (getNullableInt(), getNullableInt());
if (x == null || y == null) {
return null;
}
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test3(): int {
var ([x, y]) = [getNullableInt(), getNullableInt()];
if (x != null) {
if (((y)) != null) {
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
return x;
}
if (random() > -1) {
if (y == null) { return -1; }
else { return y; }
}
return 0;
}
fun test4() {
var x = getNullableInt();
if (x != null && x > 0) {
var x = getNullableInt();
if ((x) != null && x + 10 < 0) {
var x = getNullableInt();
return 10 > 3 && 10 < 10 && x != null && x + 8 > 10;
}
}
if (x != null && x < 1) {
return false;
}
if (x == null && x == null) {
__expect_type(x, "null");
return true;
}
return x < x + 3;
}
fun test5() {
var (a, (b, c)) = (getNullableInt(), (getNullableInt(), getNullableInt()));
if (a == null) { return -1; }
if (!(b != null)) { return -2; }
if (random() ? c == null && c == null : c == null) { return -3; }
return a + b + c;
}
fun test6() {
var a: int? = 5;
__expect_type(a, "int");
__expect_type(a != null ? a : null, "int");
__expect_type(a == null ? "" : a, "int");
takeNullableInt(a);
__expect_type(a, "int");
if (random()) {
a = null;
} else {
if (random()) { a = null; }
else { a = null; }
}
__expect_type(a, "null");
takeNullableSlice(a); // ok, `slice?` is `slice | null`, here a definitely null
var b: int? = true ? null : "sl";
__expect_type(b, "null");
takeNullableInt(b);
takeNullableSlice(b); // same reason
var c: int? = 10;
__expect_type(c, "int");
takeNullableSlice(c = null);
}
fun test7() {
var (a, b, c, d) = (getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt());
if (a == null && true) { return -1; }
if (true && true && 1 && !0 && b == null) { return -2; }
if (true ? c == null && (((c))) == null && true : false) { return -3; }
if (!true ? random() > 0 : a != null && (d == null && b != null)) { return -4; }
return a + b + c + d;
}
fun test8(x: int?, y: int?) {
var allGt1 = x != null && x > 1 && y != null && y > 1;
var xGtY = x != null && y != null && x > y;
var xLtEq0 = x == null || x < 0;
(x = 0) < random() || x > 10;
return x + 0;
}
fun test9() {
var x = getNullableInt();
var y = getNullableInt();
if (x == null || y == null) {
return -1;
}
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test10(): int {
var (x, y) = (getNullableInt(), getNullableInt());
if (x == null) {
if (y == null) { return -1; }
__expect_type(x, "null");
__expect_type(y, "int");
return y;
}
if (y == null) {
return x;
}
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test11() {
var [x, y] = [getNullableInt(), getNullableInt()];
if (random()) { return x == null || y == null ? -1 : x + y; }
if (true && (x == null || y == null) && !!true) { return 0; }
return x + y;
}
fun test12() {
var (x, y) = (getNullableInt(), getNullableInt());
if (random() ? x == null || y == null : x == null || y == null) { return -1; }
__expect_type(x, "int");
__expect_type(y, "int");
return x + y;
}
fun test13() {
var x: int? = getNullableInt();
var y: int? = 10;
var z = getNullableInt();
var w = getNullableInt();
beginCell().storeInt(x!, 32).storeInt(x = getNullableInt()!, 32).storeInt(x, 32)
.storeInt(y, 32).storeInt(z = 10, 32).storeInt(x + y + z, 32)
.storeInt(w == null ? -1 : w, 32).storeInt(!(null == w) ? w : -1, 32);
}
fun test14() {
var (x, y) = (getNullableInt(), getNullableInt());
if (x == null) {
x = 0;
}
if (y == null) {
if (random()) { return 0; }
else { y = 0; }
}
return x + y;
}
fun test20() {
var t = (getNullableInt(), getNullableInt());
if (t.0 != null && t.1 != null) {
__expect_type(t.0, "int");
__expect_type(t.1, "int");
return t.0 + t.1;
}
t.0 = 10;
if (t.1 == null) {
t.1 = 20;
}
__expect_type(t.0, "int");
__expect_type(t.1, "int");
return t.0 + t.1;
}
fun test21() {
var t = (getNullableInt(), (getNullableInt(), getNullableInt()));
if (t.0 != null && t.1.0 != null) {
if (t.1.1 != null) { return t.0 + t.1.0 + t.1.1; }
return t.0 + t.1.0;
}
if (t.0 != null) {
return t.0 + 0;
}
__expect_type(t.0, "null");
__expect_type(t.1.0, "int?");
return t.1.0 == null ? -1 : t.1.0 + 0;
}
fun test22() {
var t = (getNullableInt(), (getNullableInt(), getNullableInt()));
if (t.0 == null || t.1.0 == null || t.1.1 == null) {
return -1;
}
return t.0 + t.1.0 + t.1.1;
}
@method_id(123)
fun test23() {
var (x: int?, y: int?, z: int?) = (getNullableInt(), getNullableInt(), getNullableInt());
((x = 1, 0).0, (y = 2, 1).0) = (3, z = 4);
return x + y + z;
}
@method_id(124)
fun test24(x: int?) {
if (x == null) {
__expect_type(x, "null");
assignToNullableInt(mutate x, 10);
__expect_type(x, "int?");
x.assignToNullableInt(x! + 5);
} else {
__expect_type(x, "int");
increment(mutate x);
x.increment();
__expect_type(x, "int");
}
__expect_type(x, "int?");
return x;
}
fun test25() {
var x = (getNullableInt(), getNullableInt(), getNullableInt());
x.0 = x.2 = random();
return (x.0) + ((x.2));
}
fun test26() {
var x = [getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(),
getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt()];
if (~(x.0 = random())) { return; }
if ((x.1 = random()) < (x.2 = random())) { return; }
else if (!(x.2 <=> (x.3 = random()))) { return; }
x.5 = (x.4 = random()) ? (x.6 = random()) : (x.6 = random());
if ((x.7 = random()) as int) { return; }
if (((((x.8 = random()) != null)))) { return; }
if ([x.1, (x.9 = random())!].1) { return; }
val result = x.0+x.1+x.2+x.3+x.4+x.5+x.6+x.7+x.8+x.9;
}
fun test27() {
var (x, _) = ([getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(),
getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt()], []);
+(x.0 = random());
x.0 += [((x.1 = random()) < (x.2 = random() + x.1)) as int].0;
!(x.2 <=> (x.3 = random() + x.2));
x.5 = (x.4 = random()) ? (x.6 = random()) : (x.6 = random());
(x.7 = random()) as int;
(((((x.8 = random()) != null))));
[x.1, (x.9 = random())!].1;
return x.0+x.1+x.2+x.3+x.4+x.5+x.6+x.7+x.8+x.9;
}
fun test28() {
var x = (getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt());
__expect_type((x.0 = random(), x.0 += (x.1 = random()) as int, !(x.1 <=> (x.2 = random() + x.0)) == null, (x.3 = random()) ? x.3 : (!x.3) as int),
"(int, int, bool, int)");
}
fun test29() {
var x = (getNullableInt(), getNullableInt(), getNullableInt(), getNullableInt());
__expect_type([x.0 = random(), ((x.0 += (x.1 = random()) as int)), !(x.1 <=> (x.2 = random() + x.0)) == null, (x.3 = random()) ? x.3 : (!x.3) as int],
"[int, int, bool, int]");
}
@method_id(130)
fun test30(initial5: bool) {
var t: (int?, (int?, (int?, int?))) = initial5
? (getNullableInt(), (getNullableInt(), (getNullableInt(), getNullableInt())))
: (null, (null, (null, null)));
if (t.0 == null || t.1.0 == null || t.1.1.0 == null || t.1.1.1 == null) {
if (t.1.0 == null || t.1.1.0 == null) {
if (t.1.1.0 == null) {
t.1.1.0 = 4;
}
__expect_type(t.1.1.0, "int");
__expect_type(t.1.1.1, "int?");
__expect_type(t.1.0, "int?");
t.1.1.1 = 3;
t.1.0 = 2;
__expect_type(t.1.1.1, "int");
__expect_type(t.1.0, "int");
}
if (((((t.1.1.1)))) != null) {}
else { t.1.1.1 = 3; }
t.0 = 1;
}
return t.0 + t.1.0 + t.1.1.0 + t.1.1.1;
}
fun test31() {
var t = (getNullableInt(), getNullableInt());
t.0 == null ? (t.0, t.1) = (1, 2) : (t.1, t.0) = (4, 3);
return t.0 + t.1;
}
@method_id(132)
fun test32() {
var t: (int?, (int?, int?)?, (int?, int?)) = (getNullableInt(), (getNullableInt(), getNullableInt()), (getNullableInt(), getNullableInt()));
if (t.0 == null) { return -1; }
t.1 != null && t.1.0 == null ? t.1 = (1, 2) : t.1 = (3, 4);
if (t.2.1 != null) { t.2.0 = 1; t.2.1 = 2; }
else { [t.2.0, t.2.1] = [3, 4]; }
return t.0 + t.1.0! + t.1.1! + t.2.0 + t.2.1;
}
@method_id(133)
fun test33(): int {
var x = getNullableInt();
repeat (eq(x = 5)) {
__expect_type(x, "int");
increment(mutate x);
}
return x;
}
fun test34() {
var (x, y) = (getNullableInt(), getNullableInt());
if (random()) { throw (x = 1, y = 2); }
else { throw (x = 3, y = (1, getNullableInt()!).1); }
return x + y;
}
fun test35() {
var (x, y, z, t) = (getNullableInt(), getNullableInt(), getNullableInt(), (getNullableInt(), getNullableInt()));
assert (x != null, 404);
assert (t.0 != null && true && !(t.1 == null) && !(z = 4)) throw (y = 404);
__expect_type(y, "int?");
return x + t.0 + t.1 + z;
}
fun test36() {
var x = getNullableInt();
assert (x == null, x + 0); // check that x is int there
__expect_type(x, "null");
}
fun test37() {
var (x, code) = (getNullableInt()!, getNullableInt());
try {
} catch(code) {
x = 20;
return x + code; // code is scoped
}
return code == null ? x : x + code;
}
fun assignNull2<T1, T2>(mutate x: T1?, mutate y: T2?) {
x = null;
y = null;
}
fun test38() {
var (x: int?, y: int?) = (1, 2);
__expect_type(x, "int");
__expect_type(y, "int");
assignNull2<int, int>(mutate x, mutate y);
__expect_type(x, "int?");
__expect_type(y, "int?");
if (x != null) {
if (y == null) { return -1; }
return x + y;
}
var t: (int?, slice?) = (null, null);
if (!false) { t.0 = 1; }
if (true) { t.1 = beginCell().endCell().beginParse(); }
__expect_type(t.0, "int");
__expect_type(t.1, "slice");
t.0 + t.1.loadInt(32);
assignNull2(mutate t.0, mutate t.1);
__expect_type(t.0, "int?");
__expect_type(t.1, "slice?");
t.0 != null && t.1 != null ? t.0 + loadInt(mutate t.1, 32) : -1;
return t.0 != null && t.1 != null ? t.0 + loadInt(mutate t.1, 32) : -1;
}
@method_id(139)
fun test39() {
var x: (int?, int?)? = (4, null);
x.1 = 10;
x.1 += 1;
x!.1 += 1;
return (x!.0! + x.1);
}
@method_id(140)
fun test40(second: int?) {
var x: (int?, int?)? = (4, second);
if (x.1 != null) {
val result = x.1 + x!.1 + x!!.1 + x.1! + x!!.1!!;
}
if (x!.1 != null) {
val result = x.1 + x!.1 + x!!.1 + x.1! + x!!.1!!;
}
if (!(x!!.1 != null)) {
return -1;
}
return x.1 + x!.1 + x!!.1 + x.1! + x!!.1!!;
}
@method_id(141)
fun test41() {
var t: (int, int)? = null;
return sameTensor(t = (1, 2));
}
@method_id(142)
fun test42() {
var t: (int?, (int?, (int, int)?)?) = (getNullableInt(), (1, (2, 3)));
t.1 = (3,null);
__expect_type(t.1, "(int?, (int, int)?)");
__expect_type(t, "(int?, (int?, (int, int)?)?)");
return (t, t.1);
}
@method_id(143)
fun test43() {
var t1: ((int, int), int?) = ((1, 2), 3);
var t2: ((int?, int?), (int?,int?)?) = ((null, null), (null, 5));
t2.0 = t1.0 = (10, 11);
t2.1 = t1.1 = null;
return (t1, t2);
}
@method_id(144)
fun test44() {
var t1: ((int, int), int?) = ((1, 2), 3);
var t2: ((int?, int?), (int?,int?)?) = ((null, null), (null, 5));
t1.0 = t2.0 = (10, 11);
t1.1 = t2.1 = null;
__expect_type(t1, "((int, int), int?)");
__expect_type(t2, "((int?, int?), (int?, int?)?)");
return (t1, t2);
}
@method_id(145)
fun test45() {
var t: (int?, (int?, (int, int)?)?) = (getNullableInt(), (1, (2, 3)));
var t2 = sameTensor2(t.1 = (3,null));
return (t, t2, t.1);
}
fun autoInfer46() {
var t1: int? = 3;
var t2: (int, int)? = (4, 5);
__expect_type(t1, "int");
__expect_type(t2, "(int, int)");
return (t1, t2); // proven to be not null, inferred (int, (int,int))
}
@method_id(146)
fun test46() {
var r46_1: (int, (int,int)) = autoInfer46();
var r46_2: (int, (int,int)?) = autoInfer46();
return (r46_1, r46_2);
}
@method_id(147)
fun test47() {
var t1: int? = 3;
var t2: (int, int)? = (4, 5);
t1 = t2 = null;
__expect_type(t1, "null");
__expect_type(t2, "null");
var result = (t1, t2); // proven to be always null, inferred (null, null), 2 slots on a stack
return (result, 100, result.1, 100, t2 as (int, int)?);
}
fun test48() {
var t1: int? = getNullableInt();
if (t1 != null) {
var (t1 redef, t2) = (10, 5);
return t1 + t2;
var t2 redef = getNullableInt()!;
return t1 + t2;
}
return -1;
}
fun test49(x: int?) {
while (x == null) {
x = getNullableInt();
}
__expect_type(x, "int");
return x + 1;
}
fun test50() {
var (x: int?, y: int?) = (1, 2);
do {
x = getNullableInt();
y = getNullableInt();
} while (x == null || y == null);
return x + y;
}
fun test51() {
while (true) { return; }
// test that no error "control reaches end of function"
}
fun test52() {
do { } while (true);
}
fun test53() {
var x1: int? = getNullableInt();
var x2: int? = 5;
var x3: int? = 5;
var x10: int? = null;
var x11: int? = 5;
var x12: int? = 5;
while (x1 != null) {
__expect_type(x1, "int"); // because condition
__expect_type(x2, "int?"); // because re-assigned
__expect_type(x3, "int?"); // because re-assigned
__expect_type(x10, "null");
__expect_type(x11, "int");
x1 = getNullableInt();
__expect_type(x1, "int?");
assignToNullableInt(mutate x2, 5);
x3.assignToNullableInt(5);
x11 = 10;
assignToInt(mutate x12, 5);
}
__expect_type(x1, "null");
__expect_type(x2, "int?");
__expect_type(x3, "int?");
}
fun test54() {
var x1: int? = null;
var x2: int? = 5;
var x3: int? = 5;
var x10: int? = null;
var x11: int? = 5;
var x12: int? = 5;
do {
__expect_type(x1, "int?"); // because re-assigned
__expect_type(x2, "int?"); // because re-assigned
__expect_type(x3, "int?"); // because re-assigned
__expect_type(x10, "null");
__expect_type(x11, "int");
x1 = getNullableInt();
__expect_type(x1, "int?");
assignToNullableInt(mutate x2, 5);
if (random()) { x3.assignToNullableInt(5); }
x11 = 10;
assignToInt(mutate x12, 5);
} while (x1 != null);
__expect_type(x1, "null");
__expect_type(x2, "int?");
__expect_type(x3, "int?");
}
fun eq55<T>(v: T) { return v; }
fun test55() {
var x: int? = 4;
while (true) {
// currently, generic functions are instantiated at the type inferring step
// in case of loops, type inferring is re-enterable
// first iteration: x is int, eq<int> instantiated
// second (final) iteration: x is int?, eq<int?> instantiated
// (checked via codegen)
eq55(x);
__expect_type(x, "int?"); // types are checked (unlike generics instantiated) after inferring
x = random() ? 1 : null;
}
__expect_type(x, "int?");
}
fun test56() {
var i: int? = null;
var (j: int?, k: int?) = (null, null);
__expect_type(i, "null");
__expect_type(k, "null");
i = getTwo();
[j, ((k))] = [getTwo(), ((getTwo()))];
__expect_type(i, "int?");
__expect_type(j, "int?");
__expect_type(k, "int?");
}
fun test57(mutate x: int?): int {
if (x == null) { x = 5; }
else {
if (x < 10) { x = 10; }
else { x = 20; }
}
if (x != null) {
return 123;
}
__expect_type(x, "int");
// no "return" needed, because end of function is unreachable
}
@method_id(158)
fun test58() {
var (x1, x2: int?) = (getNullableInt(), null);
return (test57(mutate x1), x1, test57(mutate x2), x2);
}
fun test59() {
var (x1: int?, x2, x3) = (getNullableInt()!, getNullableInt(), 5);
if ((x2 = x3) != null) {
__expect_type(x2, "int");
}
__expect_type(x2, "int");
if ((x2 = getNullableInt()) != null) {
__expect_type(x2, "int");
}
__expect_type(x2, "int?");
if (((x1) = x2) == null) {
return;
}
__expect_type(x1, "int");
}
fun main(x: int?): int {
return x == null ? -1 : x;
}
/**
@testcase | 0 | 1 | 1
@testcase | 123 | | 7
@testcase | 124 | 4 | 6
@testcase | 124 | null | 15
@testcase | 130 | -1 | 20
@testcase | 130 | 0 | 10
@testcase | 132 | | 15
@testcase | 133 | | 10
@testcase | 139 | | 16
@testcase | 140 | 5 | 25
@testcase | 141 | | 1 2
@testcase | 142 | | 5 3 (null) (null) 0 -1 3 (null) (null) 0
@testcase | 143 | | 10 11 (null) 10 11 (null) (null) 0
@testcase | 144 | | 10 11 (null) 10 11 (null) (null) 0
@testcase | 145 | | 5 3 (null) (null) 0 -1 3 (null) (null) (null) (null) 0 3 (null) (null) 0
@testcase | 146 | | 3 4 5 3 4 5 -1
@testcase | 147 | | (null) (null) 100 (null) 100 (null) (null) 0
@testcase | 158 | | 123 10 123 5
@stderr warning: expression of type `int` is always not null, this condition is always true
@stderr warning: unreachable code
@stderr var t2 redef = getNullableInt()!;
@fif_codegen eq55<int?> PROC:<{
@fif_codegen eq55<int> PROC:<{
*/

View file

@ -164,6 +164,78 @@ fun test109(): (int, int) {
return (g_reg, l_reg);
}
fun alwaysThrow123(): never {
throw 123;
}
fun alwaysThrowX(x: int): never {
if (x > 10) { throw (x, beginCell()); }
else { throw (x, null); }
}
fun anotherNever(throw123: bool): never {
if (throw123) { alwaysThrow123(); }
alwaysThrowX(456);
}
fun testCodegen1(x: int) {
if (x > 10) {
throw 123;
anotherNever(true); // unreachable, will be dropped
}
else if (x < 10) {
throw x;
return -123; // unreachable, will be dropped
}
return 0;
}
fun testCodegen2(x: int) {
if (x > 10) {
alwaysThrow123();
anotherNever(true); // unreachable, will be dropped
}
else if (x < 10) {
anotherNever(false);
return -123; // unreachable, will be dropped
}
return 0;
}
@method_id(110)
fun test110(b: bool) {
try {
if (b == true) { testCodegen1(100); }
testCodegen1(5);
return -1;
} catch (ex) {
return ex;
}
}
@method_id(111)
fun test111(b: bool) {
try {
if (b == true) { testCodegen2(100); }
testCodegen2(5);
return -1;
} catch (ex) {
return ex;
}
}
fun mySetCode(newCode: slice): void
asm "SETCODE";
fun testCodegen3(numberId: int, paramVal: cell) {
if (numberId == -1000) {
var cs = paramVal.beginParse();
mySetCode(cs);
throw 0;
}
paramVal.beginParse();
}
fun main() {
}
@ -187,6 +259,65 @@ fun main() {
@testcase | 107 | 5 | 5
@testcase | 107 | 20 | 20
@testcase | 108 | | 0
@testcase | 109 | | 10 10
@testcase | 110 | -1 | 123
@testcase | 110 | 0 | 5
@testcase | 111 | -1 | 123
@testcase | 111 | 0 | 456
@code_hash 39307974281105539319288356721945232226028429128341177951717392648324358675585
@code_hash 57361460846265694653029920796509802052573595128418810728101968091567195330515
@fif_codegen
"""
testCodegen1 PROC:<{
// x
DUP // x x
10 GTINT // x '2
IFJMP:<{ // x
123 THROW
}> // x
DUP // x x
10 LESSINT // x '6
IFJMP:<{ // x
THROWANY
}> // x
DROP //
0 PUSHINT // '8=0
}>
"""
@fif_codegen
"""
testCodegen2 PROC:<{
// x
DUP // x x
10 GTINT // x '2
IFJMP:<{ // x
DROP //
alwaysThrow123 CALLDICT
}> // x
10 LESSINT // '5
IFJMP:<{ //
FALSE // '6
anotherNever CALLDICT
}> //
0 PUSHINT // '8=0
}>
"""
@fif_codegen
"""
testCodegen3 PROC:<{
// numberId paramVal
SWAP
-1000 PUSHINT // paramVal numberId '2=-1000
EQUAL // paramVal '3
IFJMP:<{ // paramVal
CTOS // cs
SETCODE
0 THROW
}> // paramVal
DROP //
}>
"""
*/

View file

@ -0,0 +1,22 @@
fun main(x: int?) {
if (x != null && x == null) {
return 1 + 2;
}
if (x == null) {
return -1;
}
if (x != null) {
return -2;
}
return 3 + 4;
}
/**
@testcase | 0 | 5 | -2
@testcase | 0 | null | -1
@stderr warning: variable `x` of type `int` is always not null
@stderr if (x != null)
@stderr warning: unreachable code
@stderr return 3 + 4
*/

View file

@ -0,0 +1,24 @@
fun alwaysThrows(): never {
throw 456;
}
fun testUnreachable(x: int) {
if (x) { throw 123; }
else { alwaysThrows(); }
return 1;
}
fun main() {
try {
testUnreachable(100);
throw 80;
} catch (excNo) {
return excNo;
}
}
/**
@testcase | 0 | | 123
@stderr warning: unreachable code
@stderr return 1;
*/

View file

@ -27,8 +27,8 @@ fun test1(): int {
var demo_var: int = demo_10;
var demo_slice: int = demo_20;
if (demo_var > 0) {
var demo_var: tuple = null;
var demo_slice: tuple = null;
var demo_var: tuple? = null;
var demo_slice: tuple? = null;
}
return demo_var + demo_slice;
}

View file

@ -138,6 +138,43 @@ fun testIndexedAccessApply() {
return functions2.0(functions1.1(b)).loadInt(32);
}
fun getNullable4(): int? { return 4; }
fun myBeginCell(): builder? asm "NEWC";
@method_id(108)
fun testCallingNotNull() {
var n4: () -> int? = getNullable4;
var creator: (() -> builder?)? = myBeginCell;
var end2: [int, (builder -> cell)?] = [0, endCell];
var c: cell = end2.1!((creator!()!)!.storeInt(getNullable4()!, 32));
return c.beginParse().loadInt(32);
}
fun sumOfTensorIfNotNull(t: (int, int)?) {
if (t == null) { return 0; }
return t!.0 + t!.1;
}
@method_id(109)
fun testTypeTransitionOfVarCall() {
var summer = sumOfTensorIfNotNull;
var hh1 = [1, null];
var tt1 = (3, 4);
return (summer(null), summer((1,2)), summer(hh1.1), summer(tt1));
}
fun makeTensor(x1: int, x2: int, x3: int, x4: int, x5: int) {
return (x1, x2, x3, x4, x5);
}
fun eq<T>(x: T): T { return x; }
@method_id(110)
fun testVarsModificationInsideVarCall(x: int) {
var cb = makeTensor;
return x > 3 ? cb(x, x += 5, eq(x *= x), x, eq(x)) : null;
}
fun main() {}
/**
@ -148,4 +185,8 @@ fun main() {}
@testcase | 105 | | 1
@testcase | 106 | | 1 1 [ 2 ] [ 2 ]
@testcase | 107 | | 65537
@testcase | 108 | | 4
@testcase | 109 | | 0 3 0 7
@testcase | 110 | 5 | 5 10 100 100 100 -1
@testcase | 110 | 0 | (null) (null) (null) (null) (null) 0
*/

View file

@ -0,0 +1,28 @@
fun getNullableInt(): int? { return null; }
fun main() {
var c: int? = 6;
__expect_type(c, "int");
if (c == null) {}
var d: int? = c;
if (((d)) != null && tupleSize(createEmptyTuple())) {}
var e: int? = getNullableInt();
if (e != null) {
return true;
}
__expect_type(e, "null");
null == e;
return null != null;
}
/**
@testcase | 0 | | 0
@stderr warning: variable `c` of type `int` is always not null, this condition is always false
@stderr warning: variable `d` of type `int` is always not null, this condition is always true
@stderr warning: variable `e` is always null, this condition is always true
@stderr warning: expression is always null, this condition is always false
*/

View file

@ -0,0 +1,26 @@
fun main() {
var (a, b, c, d, e) = (1, beginCell(), beginCell().endCell().beginParse(), [1], true as bool?);
var alwaysInt = a != null ? 1 : null;
__expect_type(alwaysInt, "int");
if (!(c == null)) {
if (10 < 3) { assert(b == null, 100); }
}
while (d == null || false) {}
return e! != null;
}
/**
@testcase | 0 | | -1
@stderr warning: variable `a` of type `int` is always not null, this condition is always true
@stderr warning: condition of ternary operator is always true
@stderr warning: variable `c` of type `slice` is always not null, this condition is always false
@stderr warning: condition of `if` is always true
@stderr warning: variable `b` of type `builder` is always not null, this condition is always false
@stderr warning: condition of `assert` is always false
@stderr warning: condition of `while` is always false
@stderr warning: expression of type `bool` is always not null, this condition is always true
*/

View file

@ -12,8 +12,8 @@ set(TOLK_SOURCE
pipe-register-symbols.cpp
pipe-resolve-identifiers.cpp
pipe-calc-rvalue-lvalue.cpp
pipe-detect-unreachable.cpp
pipe-infer-types-and-calls.cpp
pipe-check-inferred-types.cpp
pipe-refine-lvalue-for-mutate.cpp
pipe-check-rvalue-lvalue.cpp
pipe-check-pure-impure.cpp
@ -23,6 +23,7 @@ set(TOLK_SOURCE
pipe-find-unused-symbols.cpp
pipe-generate-fif-output.cpp
type-system.cpp
smart-casts-cfg.cpp
generics-helpers.cpp
abscode.cpp
analyzer.cpp

View file

@ -402,7 +402,7 @@ void CodeBlob::print(std::ostream& os, int flags) const {
std::vector<var_idx_t> CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name) {
std::vector<var_idx_t> ir_idx;
int stack_w = var_type->calc_width_on_stack();
int stack_w = var_type->get_width_on_stack();
ir_idx.reserve(stack_w);
if (const TypeDataTensor* t_tensor = var_type->try_as<TypeDataTensor>()) {
for (int i = 0; i < t_tensor->size(); ++i) {
@ -410,7 +410,11 @@ std::vector<var_idx_t> CodeBlob::create_var(TypePtr var_type, SrcLocation loc, s
std::vector<var_idx_t> nested = create_var(t_tensor->items[i], loc, std::move(sub_name));
ir_idx.insert(ir_idx.end(), nested.begin(), nested.end());
}
} else if (var_type != TypeDataVoid::create()) {
} else if (const TypeDataNullable* t_nullable = var_type->try_as<TypeDataNullable>(); t_nullable && stack_w != 1) {
std::string null_flag_name = name.empty() ? name : name + ".NNFlag";
ir_idx = create_var(t_nullable->inner, loc, std::move(name));
ir_idx.emplace_back(create_var(TypeDataBool::create(), loc, std::move(null_flag_name))[0]);
} else if (var_type != TypeDataVoid::create() && var_type != TypeDataNever::create()) {
#ifdef TOLK_DEBUG
tolk_assert(stack_w == 1);
#endif

Some files were not shown because too many files have changed in this diff Show more