/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "block/transaction.h"
#include "block/block.h"
#include "block/block-parse.h"
#include "block/block-auto.h"
#include "td/utils/bits.h"
#include "td/utils/uint128.h"
#include "ton/ton-shard.h"
#include "vm/vm.h"
#include "td/utils/Timer.h"
namespace {
class StringLoggerTail : public td::LogInterface {
 public:
  explicit StringLoggerTail(size_t max_size = 256) : buf(max_size, '\0') {}
  void append(td::CSlice slice) override {
    if (slice.size() > buf.size()) {
      slice.remove_prefix(slice.size() - buf.size());
    }
    while (!slice.empty()) {
      size_t s = std::min(buf.size() - pos, slice.size());
      std::copy(slice.begin(), slice.begin() + s, buf.begin() + pos);
      pos += s;
      if (pos == buf.size()) {
        pos = 0;
        truncated = true;
      }
      slice.remove_prefix(s);
    }
  }
  std::string get_log() const {
    if (truncated) {
      std::string res = buf;
      std::rotate(res.begin(), res.begin() + pos, res.end());
      return res;
    } else {
      return buf.substr(0, pos);
    }
  }
 private:
  std::string buf;
  size_t pos = 0;
  bool truncated = false;
};
}
namespace block {
using td::Ref;
Ref ComputePhaseConfig::lookup_library(td::ConstBitPtr key) const {
  return libraries ? vm::lookup_library_in(key, libraries->get_root_cell()) : Ref{};
}
/*
 * 
 *   ACCOUNTS
 * 
 */
bool Account::set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr) {
  workchain = wc;
  addr = new_addr;
  return true;
}
bool Account::set_split_depth(int new_split_depth) {
  if (new_split_depth < 0 || new_split_depth > 30) {
    return false;  // invalid value for split_depth
  }
  if (split_depth_set_) {
    return split_depth_ == new_split_depth;
  } else {
    split_depth_ = (unsigned char)new_split_depth;
    split_depth_set_ = true;
    return true;
  }
}
bool Account::check_split_depth(int split_depth) const {
  return split_depth_set_ ? (split_depth == split_depth_) : (split_depth >= 0 && split_depth <= 30);
}
// initializes split_depth and addr_rewrite
bool Account::parse_maybe_anycast(vm::CellSlice& cs) {
  int t = (int)cs.fetch_ulong(1);
  if (t < 0) {
    return false;
  } else if (!t) {
    return set_split_depth(0);
  }
  int depth;
  return cs.fetch_uint_leq(30, depth)                     // anycast_info$_ depth:(#<= 30)
         && depth                                         // { depth >= 1 }
         && cs.fetch_bits_to(addr_rewrite.bits(), depth)  // rewrite_pfx:(bits depth)
         && set_split_depth(depth);
}
bool Account::store_maybe_anycast(vm::CellBuilder& cb) const {
  if (!split_depth_set_ || !split_depth_) {
    return cb.store_bool_bool(false);
  }
  return cb.store_bool_bool(true)                                    // just$1
         && cb.store_uint_leq(30, split_depth_)                      // depth:(#<= 30)
         && cb.store_bits_bool(addr_rewrite.cbits(), split_depth_);  // rewrite_pfx:(bits depth)
}
bool Account::unpack_address(vm::CellSlice& addr_cs) {
  int addr_tag = block::gen::t_MsgAddressInt.get_tag(addr_cs);
  int new_wc = ton::workchainInvalid;
  switch (addr_tag) {
    case block::gen::MsgAddressInt::addr_std:
      if (!(addr_cs.advance(2) && parse_maybe_anycast(addr_cs) && addr_cs.fetch_int_to(8, new_wc) &&
            addr_cs.fetch_bits_to(addr_orig.bits(), 256) && addr_cs.empty_ext())) {
        return false;
      }
      break;
    case block::gen::MsgAddressInt::addr_var:
      // cannot appear in masterchain / basechain
      return false;
    default:
      return false;
  }
  addr_cs.clear();
  if (new_wc == ton::workchainInvalid) {
    return false;
  }
  if (workchain == ton::workchainInvalid) {
    workchain = new_wc;
    addr = addr_orig;
    addr.bits().copy_from(addr_rewrite.cbits(), split_depth_);
  } else if (split_depth_) {
    ton::StdSmcAddress new_addr = addr_orig;
    new_addr.bits().copy_from(addr_rewrite.cbits(), split_depth_);
    if (new_addr != addr) {
      LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex()
                 << " : account header contains different address " << new_addr.to_hex() << " (with splitting depth "
                 << (int)split_depth_ << ")";
      return false;
    }
  } else if (addr != addr_orig) {
    LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex()
               << " : account header contains different address " << addr_orig.to_hex();
    return false;
  }
  if (workchain != new_wc) {
    LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex()
               << " : account header contains different workchain " << new_wc;
    return false;
  }
  addr_rewrite = addr.bits();  // initialize all 32 bits of addr_rewrite
  if (!split_depth_) {
    my_addr_exact = my_addr;
  }
  return true;
}
bool Account::unpack_storage_info(vm::CellSlice& cs) {
  block::gen::StorageInfo::Record info;
  block::gen::StorageUsed::Record used;
  if (!tlb::unpack_exact(cs, info) || !tlb::csr_unpack(info.used, used)) {
    return false;
  }
  last_paid = info.last_paid;
  if (info.due_payment->prefetch_ulong(1) == 1) {
    vm::CellSlice& cs2 = info.due_payment.write();
    cs2.advance(1);
    due_payment = block::tlb::t_Grams.as_integer_skip(cs2);
    if (due_payment.is_null() || !cs2.empty_ext()) {
      return false;
    }
  } else {
    due_payment = td::zero_refint();
  }
  unsigned long long u = 0;
  u |= storage_stat.cells = block::tlb::t_VarUInteger_7.as_uint(*used.cells);
  u |= storage_stat.bits = block::tlb::t_VarUInteger_7.as_uint(*used.bits);
  u |= storage_stat.public_cells = block::tlb::t_VarUInteger_7.as_uint(*used.public_cells);
  LOG(DEBUG) << "last_paid=" << last_paid << "; cells=" << storage_stat.cells << " bits=" << storage_stat.bits
             << " public_cells=" << storage_stat.public_cells;
  return (u != std::numeric_limits::max());
}
// initializes split_depth (from account state - StateInit)
bool Account::unpack_state(vm::CellSlice& cs) {
  block::gen::StateInit::Record state;
  if (!tlb::unpack_exact(cs, state)) {
    return false;
  }
  int sd = 0;
  if (state.split_depth->size() == 6) {
    sd = (int)state.split_depth->prefetch_ulong(6) - 32;
  }
  if (!set_split_depth(sd)) {
    return false;
  }
  if (state.special->size() > 1) {
    int z = (int)state.special->prefetch_ulong(3);
    if (z < 0) {
      return false;
    }
    tick = z & 2;
    tock = z & 1;
    LOG(DEBUG) << "tick=" << tick << ", tock=" << tock;
  }
  code = state.code->prefetch_ref();
  data = state.data->prefetch_ref();
  library = orig_library = state.library->prefetch_ref();
  return true;
}
bool Account::compute_my_addr(bool force) {
  if (!force && my_addr.not_null() && my_addr_exact.not_null()) {
    return true;
  }
  if (workchain == ton::workchainInvalid) {
    my_addr.clear();
    return false;
  }
  vm::CellBuilder cb;
  Ref cell, cell2;
  if (workchain >= -128 && workchain < 127) {
    if (!(cb.store_long_bool(2, 2)                             // addr_std$10
          && store_maybe_anycast(cb)                           // anycast:(Maybe Anycast)
          && cb.store_long_rchk_bool(workchain, 8)             // workchain_id:int8
          && cb.store_bits_bool(addr_orig)                     // addr:bits256
          && cb.finalize_to(cell) && cb.store_long_bool(4, 3)  // addr_std$10 anycast:(Maybe Anycast)
          && cb.store_long_rchk_bool(workchain, 8)             // workchain_id:int8
          && cb.store_bits_bool(addr)                          // addr:bits256
          && cb.finalize_to(cell2))) {
      return false;
    }
  } else {
    if (!(cb.store_long_bool(3, 2)                             // addr_var$11
          && store_maybe_anycast(cb)                           // anycast:(Maybe Anycast)
          && cb.store_long_bool(256, 9)                        // addr_len:(## 9)
          && cb.store_long_rchk_bool(workchain, 32)            // workchain_id:int32
          && cb.store_bits_bool(addr_orig)                     // addr:(bits addr_len)
          && cb.finalize_to(cell) && cb.store_long_bool(6, 3)  // addr_var$11 anycast:(Maybe Anycast)
          && cb.store_long_bool(256, 9)                        // addr_len:(## 9)
          && cb.store_long_rchk_bool(workchain, 32)            // workchain_id:int32
          && cb.store_bits_bool(addr)                          // addr:(bits addr_len)
          && cb.finalize_to(cell2))) {
      return false;
    }
  }
  my_addr = load_cell_slice_ref(std::move(cell));
  my_addr_exact = load_cell_slice_ref(std::move(cell2));
  return true;
}
bool Account::recompute_tmp_addr(Ref& tmp_addr, int split_depth,
                                 td::ConstBitPtr orig_addr_rewrite) const {
  if (!split_depth && my_addr_exact.not_null()) {
    tmp_addr = my_addr_exact;
    return true;
  }
  if (split_depth == split_depth_ && my_addr.not_null()) {
    tmp_addr = my_addr;
    return true;
  }
  if (split_depth < 0 || split_depth > 30) {
    return false;
  }
  vm::CellBuilder cb;
  bool std = (workchain >= -128 && workchain < 128);
  if (!cb.store_long_bool(std ? 2 : 3, 2)) {  // addr_std$10 or addr_var$11
    return false;
  }
  if (!split_depth) {
    if (!cb.store_bool_bool(false)) {  // anycast:(Maybe Anycast)
      return false;
    }
  } else if (!(cb.store_bool_bool(true)                             // just$1
               && cb.store_long_bool(split_depth, 5)                // depth:(#<= 30)
               && cb.store_bits_bool(addr.bits(), split_depth))) {  // rewrite_pfx:(bits depth)
    return false;
  }
  if (std) {
    if (!cb.store_long_rchk_bool(workchain, 8)) {  // workchain:int8
      return false;
    }
  } else if (!(cb.store_long_bool(256, 9)                // addr_len:(## 9)
               && cb.store_long_bool(workchain, 32))) {  // workchain:int32
    return false;
  }
  Ref cell;
  return cb.store_bits_bool(orig_addr_rewrite, split_depth)  // address:(bits addr_len) or bits256
         && cb.store_bits_bool(addr.bits() + split_depth, 256 - split_depth) && cb.finalize_to(cell) &&
         (tmp_addr = vm::load_cell_slice_ref(std::move(cell))).not_null();
}
bool Account::init_rewrite_addr(int split_depth, td::ConstBitPtr orig_addr_rewrite) {
  if (split_depth_set_ || !set_split_depth(split_depth)) {
    return false;
  }
  addr_orig = addr;
  addr_rewrite = addr.bits();
  addr_orig.bits().copy_from(orig_addr_rewrite, split_depth);
  return compute_my_addr(true);
}
// used to unpack previously existing accounts
bool Account::unpack(Ref shard_account, Ref extra, ton::UnixTime now, bool special) {
  LOG(DEBUG) << "unpacking " << (special ? "special " : "") << "account " << addr.to_hex();
  if (shard_account.is_null()) {
    LOG(ERROR) << "account " << addr.to_hex() << " does not have a valid ShardAccount to unpack";
    return false;
  }
  if (verbosity > 2) {
    shard_account->print_rec(std::cerr, 2);
    block::gen::t_ShardAccount.print(std::cerr, *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))) {
    LOG(ERROR) << "account " << addr.to_hex() << " state is invalid";
    return false;
  }
  last_trans_lt_ = acc_info.last_trans_lt;
  last_trans_hash_ = acc_info.last_trans_hash;
  now_ = now;
  auto account = std::move(acc_info.account);
  total_state = orig_total_state = account;
  auto acc_cs = load_cell_slice(std::move(account));
  if (block::gen::t_Account.get_tag(acc_cs) == block::gen::Account::account_none) {
    is_special = special;
    return acc_cs.size_ext() == 1 && init_new(now);
  }
  block::gen::Account::Record_account acc;
  block::gen::AccountStorage::Record storage;
  if (!(tlb::unpack_exact(acc_cs, acc) && (my_addr = acc.addr).not_null() && unpack_address(acc.addr.write()) &&
        compute_my_addr() && unpack_storage_info(acc.storage_stat.write()) &&
        tlb::csr_unpack(this->storage = std::move(acc.storage), storage) &&
        std::max(storage.last_trans_lt, 1ULL) > acc_info.last_trans_lt && balance.unpack(std::move(storage.balance)))) {
    return false;
  }
  is_special = special;
  last_trans_end_lt_ = storage.last_trans_lt;
  switch (block::gen::t_AccountState.get_tag(*storage.state)) {
    case block::gen::AccountState::account_uninit:
      status = orig_status = acc_uninit;
      state_hash = addr;
      forget_split_depth();
      break;
    case block::gen::AccountState::account_frozen:
      status = orig_status = acc_frozen;
      if (!storage.state->have(2 + 256)) {
        return false;
      }
      state_hash = storage.state->data_bits() + 2;
      break;
    case block::gen::AccountState::account_active:
      status = orig_status = acc_active;
      if (storage.state.write().fetch_ulong(1) != 1) {
        return false;
      }
      inner_state = storage.state;
      if (!unpack_state(storage.state.write())) {
        return false;
      }
      state_hash.clear();
      break;
    default:
      return false;
  }
  LOG(DEBUG) << "end of Account.unpack() for " << workchain << ":" << addr.to_hex()
             << " (balance = " << balance.to_str() << " ; last_trans_lt = " << last_trans_lt_ << ".."
             << last_trans_end_lt_ << ")";
  return true;
}
// used to initialize new accounts
bool Account::init_new(ton::UnixTime now) {
  // only workchain and addr are initialized at this point
  if (workchain == ton::workchainInvalid) {
    return false;
  }
  addr_orig = addr;
  addr_rewrite = addr.cbits();
  last_trans_lt_ = last_trans_end_lt_ = 0;
  last_trans_hash_.set_zero();
  now_ = now;
  last_paid = 0;
  storage_stat.clear();
  due_payment = td::zero_refint();
  balance.set_zero();
  if (my_addr_exact.is_null()) {
    vm::CellBuilder cb;
    if (workchain >= -128 && workchain < 128) {
      CHECK(cb.store_long_bool(4, 3)                  // addr_std$10 anycast:(Maybe Anycast)
            && cb.store_long_rchk_bool(workchain, 8)  // workchain:int8
            && cb.store_bits_bool(addr));             // address:bits256
    } else {
      CHECK(cb.store_long_bool(0xd00, 12)              // addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
            && cb.store_long_rchk_bool(workchain, 32)  // workchain:int32
            && cb.store_bits_bool(addr));              // address:(bits addr_len)
    }
    my_addr_exact = load_cell_slice_ref(cb.finalize());
  }
  if (my_addr.is_null()) {
    my_addr = my_addr_exact;
  }
  if (total_state.is_null()) {
    vm::CellBuilder cb;
    CHECK(cb.store_long_bool(0, 1)  // account_none$0 = Account
          && cb.finalize_to(total_state));
    orig_total_state = total_state;
  }
  state_hash = addr_orig;
  status = orig_status = acc_nonexist;
  split_depth_set_ = false;
  return true;
}
bool Account::forget_split_depth() {
  split_depth_set_ = false;
  split_depth_ = 0;
  addr_orig = addr;
  my_addr = my_addr_exact;
  addr_rewrite = addr.bits();
  return true;
}
bool Account::deactivate() {
  if (status == acc_active) {
    return false;
  }
  // forget special (tick/tock) info
  tick = tock = false;
  if (status == acc_nonexist || status == acc_uninit) {
    // forget split depth and address rewriting info
    forget_split_depth();
    // forget specific state hash for deleted or uninitialized accounts (revert to addr)
    state_hash = addr;
  }
  // forget code and data (only active accounts remember these)
  code.clear();
  data.clear();
  library.clear();
  // if deleted, balance must be zero
  if (status == acc_nonexist && !balance.is_zero()) {
    return false;
  }
  return true;
}
bool Account::belongs_to_shard(ton::ShardIdFull shard) const {
  return workchain == shard.workchain && ton::shard_is_ancestor(shard.shard, addr);
}
void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, const block::StoragePrices& prices,
                                 const vm::CellStorageStat& storage, bool is_mc) {
  td::BigInt256 c{(long long)storage.cells}, b{(long long)storage.bits};
  if (is_mc) {
    // storage.cells * prices.mc_cell_price + storage.bits * prices.mc_bit_price;
    c.mul_short(prices.mc_cell_price);
    b.mul_short(prices.mc_bit_price);
  } else {
    // storage.cells * prices.cell_price + storage.bits * prices.bit_price;
    c.mul_short(prices.cell_price);
    b.mul_short(prices.bit_price);
  }
  b += c;
  b.mul_short(delta);
  CHECK(b.sgn() >= 0);
  payment += b;
}
td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector& pricing,
                                                  const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid,
                                                  bool is_special, bool is_masterchain) {
  if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) {
    return {};
  }
  std::size_t n = pricing.size(), i = n;
  while (i && pricing[i - 1].valid_since > last_paid) {
    --i;
  }
  if (i) {
    --i;
  }
  ton::UnixTime upto = std::max(last_paid, pricing[0].valid_since);
  td::RefInt256 total{true, 0};
  for (; i < n && upto < now; i++) {
    ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now);
    if (upto < valid_until) {
      assert(upto >= pricing[i].valid_since);
      add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain);
    }
    upto = valid_until;
  }
  total.unique_write().rshift(16, 1);  // divide by 2^16 with ceil rounding to obtain nanograms
  return total;
}
td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const {
  return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain());
}
Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now,
                         Ref _inmsg)
    : trans_type(ttype)
    , is_first(_account.transactions.empty())
    , new_tick(_account.tick)
    , new_tock(_account.tock)
    , now(_now)
    , account(_account)
    , my_addr(_account.my_addr)
    , my_addr_exact(_account.my_addr_exact)
    , balance(_account.balance)
    , original_balance(_account.balance)
    , due_payment(_account.due_payment)
    , last_paid(_account.last_paid)
    , new_code(_account.code)
    , new_data(_account.data)
    , new_library(_account.library)
    , in_msg(std::move(_inmsg)) {
  start_lt = std::max(req_start_lt, account.last_trans_end_lt_);
  end_lt = start_lt + 1;
  acc_status = (account.status == Account::acc_nonexist ? Account::acc_uninit : account.status);
  if (acc_status == Account::acc_frozen) {
    frozen_hash = account.state_hash;
  }
}
bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* cfg) {
  if (in_msg.is_null() || in_msg_type) {
    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);
  }
  auto cs = vm::load_cell_slice(in_msg);
  int tag = block::gen::t_CommonMsgInfo.get_tag(cs);
  Ref src_addr, dest_addr;
  switch (tag) {
    case block::gen::CommonMsgInfo::int_msg_info: {
      block::gen::CommonMsgInfo::Record_int_msg_info info;
      if (!(tlb::unpack(cs, info) && msg_balance_remaining.unpack(std::move(info.value)))) {
        return false;
      }
      if (info.ihr_disabled && ihr_delivered) {
        return false;
      }
      bounce_enabled = info.bounce;
      src_addr = std::move(info.src);
      dest_addr = std::move(info.dest);
      in_msg_type = 1;
      td::RefInt256 ihr_fee = block::tlb::t_Grams.as_integer(std::move(info.ihr_fee));
      if (ihr_delivered) {
        in_fwd_fee = std::move(ihr_fee);
      } else {
        in_fwd_fee = td::zero_refint();
        msg_balance_remaining += std::move(ihr_fee);
      }
      if (info.created_lt >= start_lt) {
        start_lt = info.created_lt + 1;
        end_lt = start_lt + 1;
      }
      // ...
      break;
    }
    case block::gen::CommonMsgInfo::ext_in_msg_info: {
      block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
      if (!tlb::unpack(cs, info)) {
        return false;
      }
      src_addr = std::move(info.src);
      dest_addr = std::move(info.dest);
      in_msg_type = 2;
      in_msg_extern = true;
      // compute forwarding fees for this external message
      vm::CellStorageStat sstat;       // for message size
      sstat.compute_used_storage(cs);  // message body
      sstat.bits -= cs.size();         // bits in the root cells are free
      sstat.cells--;                   // the root cell itself is not counted as a cell
      LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits";
      if (sstat.bits > cfg->size_limits.max_msg_bits || sstat.cells > cfg->size_limits.max_msg_cells) {
        LOG(DEBUG) << "inbound external message too large, invalid";
        return false;
      }
      // fetch message pricing info
      CHECK(cfg);
      const MsgPrices& msg_prices = cfg->fetch_msg_prices(account.is_masterchain());
      // compute forwarding fees
      auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, true);
      LOG(DEBUG) << "computed fwd fees = " << fees_c.first << " + " << fees_c.second;
      if (account.is_special) {
        LOG(DEBUG) << "computed fwd fees set to zero for special account";
        fees_c.first = fees_c.second = 0;
      }
      in_fwd_fee = td::make_refint(fees_c.first);
      if (balance.grams < in_fwd_fee) {
        LOG(DEBUG) << "cannot pay for importing this external message";
        return false;
      }
      // (tentatively) debit account for importing this external message
      balance -= in_fwd_fee;
      msg_balance_remaining.set_zero();  // external messages cannot carry value
      // ...
      break;
    }
    default:
      return false;
  }
  // init:(Maybe (Either StateInit ^StateInit))
  switch ((int)cs.prefetch_ulong(2)) {
    case 2: {  // (just$1 (left$0 _:StateInit ))
      Ref state_init;
      vm::CellBuilder cb;
      if (!(cs.advance(2) && block::gen::t_StateInit.fetch_to(cs, state_init) &&
            cb.append_cellslice_bool(std::move(state_init)) && cb.finalize_to(in_msg_state) &&
            block::gen::t_StateInit.validate_ref(in_msg_state))) {
        LOG(DEBUG) << "cannot parse StateInit in inbound message";
        return false;
      }
      break;
    }
    case 3: {  // (just$1 (right$1 _:^StateInit ))
      if (!(cs.advance(2) && cs.fetch_ref_to(in_msg_state) && block::gen::t_StateInit.validate_ref(in_msg_state))) {
        LOG(DEBUG) << "cannot parse ^StateInit in inbound message";
        return false;
      }
      break;
    }
    default:  // nothing$0
      if (!cs.advance(1)) {
        LOG(DEBUG) << "invalid init field in an inbound message";
        return false;
      }
  }
  // body:(Either X ^X)
  switch ((int)cs.fetch_ulong(1)) {
    case 0:  // left$0 _:X
      in_msg_body = Ref{true, cs};
      break;
    case 1:  // right$1 _:^X
      if (cs.size_ext() != 0x10000) {
        LOG(DEBUG) << "body of an inbound message is not represented by exactly one reference";
        return false;
      }
      in_msg_body = load_cell_slice_ref(cs.prefetch_ref());
      break;
    default:
      LOG(DEBUG) << "invalid body field in an inbound message";
      return false;
  }
  total_fees += in_fwd_fee;
  return true;
}
bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect, bool adjust_msg_value) {
  if (now < account.last_paid) {
    return false;
  }
  auto to_pay = account.compute_storage_fees(now, *(cfg.pricing));
  if (to_pay.not_null() && sgn(to_pay) < 0) {
    return false;
  }
  auto res = std::make_unique();
  res->is_special = account.is_special;
  last_paid = res->last_paid_updated = (res->is_special ? 0 : now);
  if (to_pay.is_null() || sgn(to_pay) == 0) {
    res->fees_collected = res->fees_due = td::zero_refint();
  } else if (to_pay <= balance.grams) {
    res->fees_collected = to_pay;
    res->fees_due = td::zero_refint();
    balance -= std::move(to_pay);
  } else if (acc_status == Account::acc_frozen && !force_collect && to_pay + due_payment < cfg.delete_due_limit) {
    // do not collect fee
    res->last_paid_updated = (res->is_special ? 0 : account.last_paid);
    res->fees_collected = res->fees_due = td::zero_refint();
  } else {
    res->fees_collected = balance.grams;
    res->fees_due = std::move(to_pay) - std::move(balance.grams);
    balance.grams = td::zero_refint();
    if (!res->is_special) {
      auto total_due = res->fees_due + due_payment;
      switch (acc_status) {
        case Account::acc_uninit:
        case Account::acc_frozen:
          if (total_due > cfg.delete_due_limit) {
            res->deleted = true;
            acc_status = Account::acc_deleted;
            if (balance.extra.not_null()) {
              // collect extra currencies as a fee
              total_fees += block::CurrencyCollection{0, std::move(balance.extra)};
              balance.extra.clear();
            }
          }
          break;
        case Account::acc_active:
          if (total_due > cfg.freeze_due_limit) {
            res->frozen = true;
            was_frozen = true;
            acc_status = Account::acc_frozen;
          }
          break;
      }
    }
  }
  if (adjust_msg_value && msg_balance_remaining.grams > balance.grams) {
    msg_balance_remaining.grams = balance.grams;
  }
  total_fees += res->fees_collected;
  storage_phase = std::move(res);
  return true;
}
bool Transaction::prepare_credit_phase() {
  credit_phase = std::make_unique();
  auto collected = std::min(msg_balance_remaining.grams, due_payment);
  credit_phase->due_fees_collected = collected;
  due_payment -= collected;
  credit_phase->credit = msg_balance_remaining -= collected;
  if (!msg_balance_remaining.is_valid()) {
    LOG(ERROR) << "cannot compute the amount to be credited in the credit phase of transaction";
    return false;
  }
  // NB: msg_balance_remaining may be deducted from balance later during bounce phase
  balance += msg_balance_remaining;
  if (!balance.is_valid()) {
    LOG(ERROR) << "cannot credit currency collection to account";
    return false;
  }
  total_fees += std::move(collected);
  return true;
}
bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit,
                                               td::RefInt256& delete_due_limit) {
  return cell.not_null() &&
         parse_GasLimitsPrices(vm::load_cell_slice_ref(std::move(cell)), freeze_due_limit, delete_due_limit);
}
bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit,
                                               td::RefInt256& delete_due_limit) {
  if (cs.is_null()) {
    return false;
  }
  block::gen::GasLimitsPrices::Record_gas_flat_pfx flat;
  if (tlb::csr_unpack(cs, flat)) {
    return parse_GasLimitsPrices_internal(std::move(flat.other), freeze_due_limit, delete_due_limit,
                                          flat.flat_gas_limit, flat.flat_gas_price);
  } else {
    return parse_GasLimitsPrices_internal(std::move(cs), freeze_due_limit, delete_due_limit);
  }
}
bool ComputePhaseConfig::parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit,
                                                        td::RefInt256& delete_due_limit, td::uint64 _flat_gas_limit,
                                                        td::uint64 _flat_gas_price) {
  auto f = [&](const auto& r, td::uint64 spec_limit) {
    gas_limit = r.gas_limit;
    special_gas_limit = spec_limit;
    gas_credit = r.gas_credit;
    gas_price = r.gas_price;
    freeze_due_limit = td::make_refint(r.freeze_due_limit);
    delete_due_limit = td::make_refint(r.delete_due_limit);
  };
  block::gen::GasLimitsPrices::Record_gas_prices_ext rec;
  if (tlb::csr_unpack(cs, rec)) {
    f(rec, rec.special_gas_limit);
  } else {
    block::gen::GasLimitsPrices::Record_gas_prices rec0;
    if (tlb::csr_unpack(std::move(cs), rec0)) {
      f(rec0, rec0.gas_limit);
    } else {
      return false;
    }
  }
  flat_gas_limit = _flat_gas_limit;
  flat_gas_price = _flat_gas_price;
  compute_threshold();
  return true;
}
void ComputePhaseConfig::compute_threshold() {
  gas_price256 = td::make_refint(gas_price);
  if (gas_limit > flat_gas_limit) {
    max_gas_threshold =
        td::rshift(gas_price256 * (gas_limit - flat_gas_limit), 16, 1) + td::make_bigint(flat_gas_price);
  } else {
    max_gas_threshold = td::make_refint(flat_gas_price);
  }
}
td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const {
  if (nanograms.is_null() || sgn(nanograms) < 0) {
    return 0;
  }
  if (nanograms >= max_gas_threshold) {
    return gas_limit;
  }
  if (nanograms < flat_gas_price) {
    return 0;
  }
  auto res = td::div((std::move(nanograms) - flat_gas_price) << 16, gas_price256);
  return res->to_long() + flat_gas_limit;
}
td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const {
  return gas_used <= flat_gas_limit ? td::make_refint(flat_gas_price)
                                    : td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price;
}
bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) {
  // Compute gas limits
  if (account.is_special) {
    cp.gas_max = cfg.special_gas_limit;
  } else {
    cp.gas_max = cfg.gas_bought_for(balance.grams);
  }
  cp.gas_credit = 0;
  if (trans_type != tr_ord) {
    // may use all gas that can be bought using remaining balance
    cp.gas_limit = cp.gas_max;
  } else {
    // originally use only gas bought using remaining message balance
    // if the message is "accepted" by the smart contract, the gas limit will be set to gas_max
    cp.gas_limit = std::min(cfg.gas_bought_for(msg_balance_remaining.grams), cp.gas_max);
    if (!block::tlb::t_Message.is_internal(in_msg)) {
      // external messages carry no balance, give them some credit to check whether they are accepted
      cp.gas_credit = std::min(cfg.gas_credit, cp.gas_max);
    }
  }
  LOG(DEBUG) << "gas limits: max=" << cp.gas_max << ", limit=" << cp.gas_limit << ", credit=" << cp.gas_credit;
  return true;
}
Ref Transaction::prepare_vm_stack(ComputePhase& cp) {
  Ref stack_ref{true};
  td::RefInt256 acc_addr{true};
  CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256));
  vm::Stack& stack = stack_ref.write();
  switch (trans_type) {
    case tr_tick:
    case tr_tock:
      stack.push_int(balance.grams);
      stack.push_int(std::move(acc_addr));
      stack.push_bool(trans_type == tr_tock);
      stack.push_smallint(-2);
      return stack_ref;
    case tr_ord:
      stack.push_int(balance.grams);
      stack.push_int(msg_balance_remaining.grams);
      stack.push_cell(in_msg);
      stack.push_cellslice(in_msg_body);
      stack.push_bool(in_msg_extern);
      return stack_ref;
    default:
      LOG(ERROR) << "cannot initialize stack for a transaction of type " << trans_type;
      return {};
  }
}
bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const {
  // we might use SHA256(block_rand_seed . addr . trans_lt)
  // instead, we use SHA256(block_rand_seed . addr)
  // if the smart contract wants to randomize further, it can use RANDOMIZE instruction
  td::BitArray<256 + 256> data;
  data.bits().copy_from(cfg.block_rand_seed.cbits(), 256);
  (data.bits() + 256).copy_from(account.addr_rewrite.cbits(), 256);
  rand_seed.clear();
  data.compute_sha256(rand_seed);
  return true;
}
Ref Transaction::prepare_vm_c7(const ComputePhaseConfig& cfg) const {
  td::BitArray<256> rand_seed;
  td::RefInt256 rand_seed_int{true};
  if (!(prepare_rand_seed(rand_seed, cfg) && rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false))) {
    LOG(ERROR) << "cannot compute rand_seed for transaction";
    throw CollatorError{"cannot generate valid SmartContractInfo"};
    return {};
  }
  auto tuple = vm::make_tuple_ref(
      td::make_refint(0x076ef1ea),                // [ magic:0x076ef1ea
      td::zero_refint(),                          //   actions:Integer
      td::zero_refint(),                          //   msgs_sent:Integer
      td::make_refint(now),                       //   unixtime:Integer
      td::make_refint(account.block_lt),          //   block_lt:Integer
      td::make_refint(start_lt),                  //   trans_lt:Integer
      std::move(rand_seed_int),                   //   rand_seed:Integer
      balance.as_vm_tuple(),                      //   balance_remaining:[Integer (Maybe Cell)]
      my_addr,                                    //  myself:MsgAddressInt
      vm::StackEntry::maybe(cfg.global_config));  //  global_config:(Maybe Cell) ] = SmartContractInfo;
  LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
  return vm::make_tuple_ref(std::move(tuple));
}
int output_actions_count(Ref list) {
  int i = -1;
  do {
    ++i;
    list = load_cell_slice(std::move(list)).prefetch_ref();
  } while (list.not_null());
  return i;
}
bool Transaction::unpack_msg_state(bool lib_only) {
  block::gen::StateInit::Record state;
  if (in_msg_state.is_null() || !tlb::unpack_cell(in_msg_state, state)) {
    LOG(ERROR) << "cannot unpack StateInit from an inbound message";
    return false;
  }
  if (lib_only) {
    in_msg_library = state.library->prefetch_ref();
    return true;
  }
  if (state.split_depth->size() == 6) {
    new_split_depth = (signed char)(state.split_depth->prefetch_ulong(6) - 32);
  } else {
    new_split_depth = 0;
  }
  if (state.special->size() > 1) {
    int z = (int)state.special->prefetch_ulong(3);
    if (z < 0) {
      return false;
    }
    new_tick = z & 2;
    new_tock = z & 1;
    LOG(DEBUG) << "tick=" << new_tick << ", tock=" << new_tock;
  }
  new_code = state.code->prefetch_ref();
  new_data = state.data->prefetch_ref();
  new_library = state.library->prefetch_ref();
  return true;
}
std::vector[> Transaction::compute_vm_libraries(const ComputePhaseConfig& cfg) {
  std::vector][> lib_set;
  if (in_msg_library.not_null()) {
    lib_set.push_back(in_msg_library);
  }
  if (new_library.not_null()) {
    lib_set.push_back(new_library);
  }
  auto global_libs = cfg.get_lib_root();
  if (global_libs.not_null()) {
    lib_set.push_back(std::move(global_libs));
  }
  return lib_set;
}
bool Transaction::check_in_msg_state_hash() {
  CHECK(in_msg_state.not_null());
  CHECK(new_split_depth >= 0 && new_split_depth < 32);
  td::Bits256 in_state_hash = in_msg_state->get_hash().bits();
  int d = new_split_depth;
  if ((in_state_hash.bits() + d).compare(account.addr.bits() + d, 256 - d)) {
    return false;
  }
  orig_addr_rewrite = in_state_hash.bits();
  orig_addr_rewrite_set = true;
  return account.recompute_tmp_addr(my_addr, d, orig_addr_rewrite.bits());
}
bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
  // TODO: add more skip verifications + sometimes use state from in_msg to re-activate
  // ...
  compute_phase = std::make_unique();
  ComputePhase& cp = *(compute_phase.get());
  original_balance -= total_fees;
  if (td::sgn(balance.grams) <= 0) {
    // no gas
    cp.skip_reason = ComputePhase::sk_no_gas;
    return true;
  }
  // Compute gas limits
  if (!compute_gas_limits(cp, cfg)) {
    compute_phase.reset();
    return false;
  }
  if (!cp.gas_limit && !cp.gas_credit) {
    // no gas
    cp.skip_reason = ComputePhase::sk_no_gas;
    return true;
  }
  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";
  }
  if (in_msg_state.not_null() &&
      (acc_status == Account::acc_uninit ||
       (acc_status == Account::acc_frozen && account.state_hash == in_msg_state->get_hash().bits()))) {
    use_msg_state = true;
    if (!(unpack_msg_state() && account.check_split_depth(new_split_depth))) {
      LOG(DEBUG) << "cannot unpack in_msg_state, or it has bad split_depth; cannot init account state";
      cp.skip_reason = ComputePhase::sk_bad_state;
      return true;
    }
    if (acc_status == Account::acc_uninit && !check_in_msg_state_hash()) {
      LOG(DEBUG) << "in_msg_state hash mismatch, cannot init account state";
      cp.skip_reason = ComputePhase::sk_bad_state;
      return true;
    }
  } else if (acc_status != Account::acc_active) {
    // no state, cannot perform transactions
    cp.skip_reason = in_msg_state.not_null() ? ComputePhase::sk_bad_state : ComputePhase::sk_no_state;
    return true;
  } else if (in_msg_state.not_null()) {
    unpack_msg_state(true);  // use only libraries
  }
  // initialize VM
  Ref stack = prepare_vm_stack(cp);
  if (stack.is_null()) {
    compute_phase.reset();
    return false;
  }
  // OstreamLogger ostream_logger(error_stream);
  // auto log = create_vm_log(error_stream ? &ostream_logger : nullptr);
  vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit};
  LOG(DEBUG) << "creating VM";
  std::unique_ptr logger;
  auto vm_log = vm::VmLog();
  if (cfg.with_vm_log) {
    logger = std::make_unique();
    vm_log.log_interface = logger.get();
    vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false);
  }
  vm::VmState vm{new_code, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)};
  vm.set_max_data_depth(cfg.max_vm_data_depth);
  vm.set_c7(prepare_vm_c7(cfg));  // tuple with SmartContractInfo
  // vm.incr_stack_trace(1);    // enable stack dump after each step
  LOG(DEBUG) << "starting VM";
  cp.vm_init_state_hash = vm.get_state_hash();
  td::Timer timer;
  cp.exit_code = ~vm.run();
  double elapsed = timer.elapsed();
  LOG(DEBUG) << "VM terminated with exit code " << cp.exit_code;
  cp.out_of_gas = (cp.exit_code == ~(int)vm::Excno::out_of_gas);
  cp.vm_final_state_hash = vm.get_final_state_hash(cp.exit_code);
  stack = vm.get_stack_ref();
  cp.vm_steps = (int)vm.get_steps_count();
  gas = vm.get_gas_limits();
  cp.gas_used = std::min(gas.gas_consumed(), gas.gas_limit);
  cp.accepted = (gas.gas_credit == 0);
  cp.success = (cp.accepted && vm.committed());
  if (cp.accepted & use_msg_state) {
    was_activated = true;
    acc_status = Account::acc_active;
  }
  LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas.gas_consumed() << ", max=" << gas.gas_max
            << ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit;
  LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success
            << ", time=" << elapsed << "s";
  if (logger != nullptr) {
    cp.vm_log = logger->get_log();
  }
  if (cp.success) {
    cp.new_data = vm.get_committed_state().c4;  // c4 -> persistent data
    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: ";
      load_cell_slice(cp.new_data).print_rec(std::cerr);
      std::cerr << "output actions: ";
      block::gen::OutList{out_act_num}.print_ref(std::cerr, cp.actions);
    }
  }
  cp.mode = 0;
  cp.exit_arg = 0;
  if (!cp.success && stack->depth() > 0) {
    td::RefInt256 tos = stack->tos().as_int();
    if (tos.not_null() && tos->signed_fits_bits(32)) {
      cp.exit_arg = (int)tos->to_long();
    }
  }
  if (cp.accepted) {
    if (account.is_special) {
      cp.gas_fees = td::zero_refint();
    } else {
      cp.gas_fees = cfg.compute_gas_price(cp.gas_used);
      total_fees += cp.gas_fees;
      balance -= cp.gas_fees;
    }
    LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * "
               << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for "
               << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str();
    CHECK(td::sgn(balance.grams) >= 0);
  }
  return true;
}
bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) {
  if (!compute_phase || !compute_phase->success) {
    return false;
  }
  action_phase = std::make_unique();
  ActionPhase& ap = *(action_phase.get());
  ap.result_code = -1;
  ap.result_arg = 0;
  ap.tot_actions = ap.spec_actions = ap.skipped_actions = ap.msgs_created = 0;
  Ref list = compute_phase->actions;
  assert(list.not_null());
  ap.action_list_hash = list->get_hash().bits();
  ap.remaining_balance = balance;
  ap.end_lt = end_lt;
  ap.total_fwd_fees = td::zero_refint();
  ap.total_action_fees = td::zero_refint();
  ap.reserved_balance.set_zero();
  td::Ref old_code = new_code, old_data = new_data, old_library = new_library;
  auto enforce_state_size_limits = [&]() {
    if (account.is_special) {
      return true;
    }
    if (!check_state_size_limit(cfg)) {
      // Rollback changes to state, fail action phase
      LOG(INFO) << "Account state size exceeded limits";
      new_storage_stat.clear();
      new_code = old_code;
      new_data = old_data;
      new_library = old_library;
      ap.result_code = 50;
      ap.state_size_too_big = true;
      return false;
    }
    return true;
  };
  int n = 0;
  while (true) {
    ap.action_list.push_back(list);
    auto cs = load_cell_slice(std::move(list));
    if (!cs.size_ext()) {
      break;
    }
    if (!cs.have_refs()) {
      ap.result_code = 32;  // action list invalid
      ap.result_arg = n;
      ap.action_list_invalid = true;
      LOG(DEBUG) << "action list invalid: entry found with data but no next reference";
      return true;
    }
    list = cs.prefetch_ref();
    n++;
    if (n > cfg.max_actions) {
      ap.result_code = 33;  // too many actions
      ap.result_arg = n;
      ap.action_list_invalid = true;
      LOG(DEBUG) << "action list too long: more than " << cfg.max_actions << " actions";
      return true;
    }
  }
  ap.tot_actions = n;
  ap.spec_actions = ap.skipped_actions = 0;
  for (int i = n - 1; i >= 0; --i) {
    ap.result_arg = n - 1 - i;
    if (!block::gen::t_OutListNode.validate_ref(ap.action_list[i])) {
      ap.result_code = 34;  // action #i invalid or unsupported
      ap.action_list_invalid = true;
      LOG(DEBUG) << "invalid action " << ap.result_arg << " found while preprocessing action list: error code "
                 << ap.result_code;
      return true;
    }
  }
  ap.valid = true;
  for (int i = n - 1; i >= 0; --i) {
    ap.result_arg = n - 1 - i;
    vm::CellSlice cs = load_cell_slice(ap.action_list[i]);
    CHECK(cs.fetch_ref().not_null());
    int tag = block::gen::t_OutAction.get_tag(cs);
    CHECK(tag >= 0);
    int err_code = 34;
    switch (tag) {
      case block::gen::OutAction::action_set_code:
        err_code = try_action_set_code(cs, ap, cfg);
        break;
      case block::gen::OutAction::action_send_msg:
        err_code = try_action_send_msg(cs, ap, cfg);
        if (err_code == -2) {
          err_code = try_action_send_msg(cs, ap, cfg, 1);
          if (err_code == -2) {
            err_code = try_action_send_msg(cs, ap, cfg, 2);
          }
        }
        break;
      case block::gen::OutAction::action_reserve_currency:
        err_code = try_action_reserve_currency(cs, ap, cfg);
        break;
      case block::gen::OutAction::action_change_library:
        err_code = try_action_change_library(cs, ap, cfg);
        break;
    }
    if (err_code) {
      ap.result_code = (err_code == -1 ? 34 : err_code);
      ap.end_lt = end_lt;
      if (err_code == -1 || err_code == 34) {
        ap.action_list_invalid = true;
      }
      if (err_code == 37 || err_code == 38) {
        ap.no_funds = true;
      }
      LOG(DEBUG) << "invalid action " << ap.result_arg << " in action list: error code " << ap.result_code;
      // This is reuqired here because changes to libraries are applied even if actipn phase fails
      enforce_state_size_limits();
      return true;
    }
  }
  end_lt = ap.end_lt;
  if (ap.new_code.not_null()) {
    new_code = ap.new_code;
  }
  new_data = compute_phase->new_data;  // tentative persistent data update applied
  if (!enforce_state_size_limits()) {
    return true;
  }
  ap.result_arg = 0;
  ap.result_code = 0;
  CHECK(ap.remaining_balance.grams->sgn() >= 0);
  CHECK(ap.reserved_balance.grams->sgn() >= 0);
  ap.remaining_balance += ap.reserved_balance;
  CHECK(ap.remaining_balance.is_valid());
  if (ap.acc_delete_req) {
    CHECK(ap.remaining_balance.is_zero());
    ap.acc_status_change = ActionPhase::acst_deleted;
    acc_status = Account::acc_deleted;
    was_deleted = true;
  }
  ap.success = true;
  out_msgs = std::move(ap.out_msgs);
  total_fees +=
      ap.total_action_fees;  // NB: forwarding fees are not accounted here (they are not collected by the validators in this transaction)
  balance = ap.remaining_balance;
  return true;
}
int Transaction::try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) {
  block::gen::OutAction::Record_action_set_code rec;
  if (!tlb::unpack_exact(cs, rec)) {
    return -1;
  }
  ap.new_code = std::move(rec.new_code);
  ap.code_changed = true;
  ap.spec_actions++;
  return 0;
}
int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) {
  block::gen::OutAction::Record_action_change_library rec;
  if (!tlb::unpack_exact(cs, rec)) {
    return -1;
  }
  // mode: +0 = remove library, +1 = add private library, +2 = add public library
  Ref lib_ref = rec.libref->prefetch_ref();
  ton::Bits256 hash;
  if (lib_ref.not_null()) {
    hash = lib_ref->get_hash().bits();
  } else {
    CHECK(rec.libref.write().fetch_ulong(1) == 0 && rec.libref.write().fetch_bits_to(hash));
  }
  try {
    vm::Dictionary dict{new_library, 256};
    if (!rec.mode) {
      // remove library
      dict.lookup_delete(hash);
      LOG(DEBUG) << "removed " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex();
    } else {
      auto val = dict.lookup(hash);
      if (val.not_null()) {
        bool is_public = val->prefetch_ulong(1);
        auto ref = val->prefetch_ref();
        if (hash == ref->get_hash().bits()) {
          lib_ref = ref;
          if (is_public == (rec.mode >> 1)) {
            // library already in required state
            ap.spec_actions++;
            return 0;
          }
        }
      }
      if (lib_ref.is_null()) {
        // library code not found
        return 41;
      }
      vm::CellStorageStat sstat;
      sstat.compute_used_storage(lib_ref);
      if (sstat.cells > cfg.size_limits.max_library_cells) {
        return 43;
      }
      vm::CellBuilder cb;
      CHECK(cb.store_bool_bool(rec.mode >> 1) && cb.store_ref_bool(std::move(lib_ref)));
      CHECK(dict.set_builder(hash, cb));
      LOG(DEBUG) << "added " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex();
    }
    new_library = std::move(dict).extract_root_cell();
  } catch (vm::VmError& vme) {
    return 42;
  }
  ap.spec_actions++;
  return 0;
}
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
td::uint64 MsgPrices::compute_fwd_fees(td::uint64 cells, td::uint64 bits) const {
  return lump_price + td::uint128(bit_price)
                          .mult(bits)
                          .add(td::uint128(cell_price).mult(cells))
                          .add(td::uint128(0xffffu))
                          .shr(16)
                          .lo();
}
std::pair MsgPrices::compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits,
                                                                  bool ihr_disabled) const {
  td::uint64 fwd = compute_fwd_fees(cells, bits);
  if (ihr_disabled) {
    return std::pair(fwd, 0);
  }
  return std::pair(fwd, td::uint128(fwd).mult(ihr_factor).shr(16).lo());
}
td::RefInt256 MsgPrices::get_first_part(td::RefInt256 total) const {
  return (std::move(total) * first_frac) >> 16;
}
td::uint64 MsgPrices::get_first_part(td::uint64 total) const {
  return td::uint128(total).mult(first_frac).shr(16).lo();
}
td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const {
  return (std::move(total) * next_frac) >> 16;
}
bool Transaction::check_replace_src_addr(Ref& src_addr) const {
  int t = (int)src_addr->prefetch_ulong(2);
  if (!t && src_addr->size_ext() == 2) {
    // addr_none$00  --> replace with the address of current smart contract
    src_addr = my_addr;
    return true;
  }
  if (t != 2) {
    // invalid address (addr_extern and addr_var cannot be source addresses)
    return false;
  }
  if (src_addr->contents_equal(*my_addr) || src_addr->contents_equal(*my_addr_exact)) {
    // source address matches that of the current account
    return true;
  }
  // only one valid case remaining: rewritten source address used, replace with the complete one
  // (are we sure we want to allow this?)
  return false;
}
bool Transaction::check_rewrite_dest_addr(Ref& dest_addr, const ActionPhaseConfig& cfg,
                                          bool* is_mc) const {
  if (!dest_addr->prefetch_ulong(1)) {
    // all external addresses allowed
    if (is_mc) {
      *is_mc = false;
    }
    return true;
  }
  bool repack = false;
  int tag = block::gen::t_MsgAddressInt.get_tag(*dest_addr);
  block::gen::MsgAddressInt::Record_addr_var rec;
  if (tag == block::gen::MsgAddressInt::addr_var) {
    if (!tlb::csr_unpack(dest_addr, rec)) {
      // cannot unpack addr_var
      LOG(DEBUG) << "cannot unpack addr_var in a destination address";
      return false;
    }
    if (rec.addr_len == 256 && rec.workchain_id >= -128 && rec.workchain_id < 128) {
      LOG(DEBUG) << "destination address contains an addr_var to be repacked into addr_std";
      repack = true;
    }
  } else if (tag == block::gen::MsgAddressInt::addr_std) {
    block::gen::MsgAddressInt::Record_addr_std recs;
    if (!tlb::csr_unpack(dest_addr, recs)) {
      // cannot unpack addr_std
      LOG(DEBUG) << "cannot unpack addr_std in a destination address";
      return false;
    }
    rec.anycast = std::move(recs.anycast);
    rec.addr_len = 256;
    rec.workchain_id = recs.workchain_id;
    rec.address = td::make_bitstring_ref(recs.address);
  } else {
    // unknown address format (not a MsgAddressInt)
    LOG(DEBUG) << "destination address does not have a MsgAddressInt tag";
    return false;
  }
  if (rec.workchain_id != ton::masterchainId) {
    // recover destination workchain info from configuration
    auto it = cfg.workchains->find(rec.workchain_id);
    if (it == cfg.workchains->end()) {
      // undefined destination workchain
      LOG(DEBUG) << "destination address contains unknown workchain_id " << rec.workchain_id;
      return false;
    }
    if (!it->second->accept_msgs) {
      // workchain does not accept new messages
      LOG(DEBUG) << "destination address belongs to workchain " << rec.workchain_id << " not accepting new messages";
      return false;
    }
    if (!it->second->is_valid_addr_len(rec.addr_len)) {
      // invalid address length for specified workchain
      LOG(DEBUG) << "destination address has length " << rec.addr_len << " invalid for destination workchain "
                 << rec.workchain_id;
      return false;
    }
  }
  if (rec.anycast->size() > 1) {
    // destination address is an anycast
    if (rec.workchain_id == ton::masterchainId) {
      // anycast addresses disabled in masterchain
      LOG(DEBUG) << "masterchain destination address has an anycast field";
      return false;
    }
    vm::CellSlice cs{*rec.anycast};
    int d = (int)cs.fetch_ulong(6) - 32;
    if (d <= 0 || d > 30) {
      // invalid anycast prefix length
      return false;
    }
    unsigned pfx = (unsigned)cs.fetch_ulong(d);
    unsigned my_pfx = (unsigned)account.addr.cbits().get_uint(d);
    if (pfx != my_pfx) {
      // rewrite destination address
      vm::CellBuilder cb;
      CHECK(cb.store_long_bool(32 + d, 6)     // just$1 depth:(#<= 30)
            && cb.store_long_bool(my_pfx, d)  // rewrite_pfx:(bits depth)
            && (rec.anycast = load_cell_slice_ref(cb.finalize())).not_null());
      repack = true;
    }
  }
  if (is_mc) {
    *is_mc = (rec.workchain_id == ton::masterchainId);
  }
  if (!repack) {
    return true;
  }
  if (rec.addr_len == 256 && rec.workchain_id >= -128 && rec.workchain_id < 128) {
    // repack as an addr_std
    vm::CellBuilder cb;
    CHECK(cb.store_long_bool(2, 2)                             // addr_std$10
          && cb.append_cellslice_bool(std::move(rec.anycast))  // anycast:(Maybe Anycast) ...
          && cb.store_long_bool(rec.workchain_id, 8)           // workchain_id:int8
          && cb.append_bitstring(std::move(rec.address))       // address:bits256
          && (dest_addr = load_cell_slice_ref(cb.finalize())).not_null());
  } else {
    // repack as an addr_var
    CHECK(tlb::csr_pack(dest_addr, std::move(rec)));
  }
  CHECK(block::gen::t_MsgAddressInt.validate_csr(dest_addr));
  return true;
}
int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, const ActionPhaseConfig& cfg,
                                     int redoing) {
  block::gen::OutAction::Record_action_send_msg act_rec;
  // mode: +128 = attach all remaining balance, +64 = attach all remaining balance of the inbound message, +32 = delete smart contract if balance becomes zero, +1 = pay message fees, +2 = skip if message cannot be sent
  vm::CellSlice cs{cs0};
  if (!tlb::unpack_exact(cs, act_rec) || (act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) {
    return -1;
  }
  bool skip_invalid = (act_rec.mode & 2);
  // try to parse suggested message in act_rec.out_msg
  td::RefInt256 fwd_fee, ihr_fee;
  block::gen::MessageRelaxed::Record msg;
  if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) {
    return -1;
  }
  if (redoing >= 1) {
    if (msg.init->size_refs() >= 2) {
      LOG(DEBUG) << "moving the StateInit of a suggested outbound message into a separate cell";
      // init:(Maybe (Either StateInit ^StateInit))
      // transform (just (left z:StateInit)) into (just (right z:^StateInit))
      CHECK(msg.init.write().fetch_ulong(2) == 2);
      vm::CellBuilder cb;
      Ref cell;
      CHECK(cb.append_cellslice_bool(std::move(msg.init))  // StateInit
            && cb.finalize_to(cell)                        // -> ^StateInit
            && cb.store_long_bool(3, 2)                    // (just (right ... ))
            && cb.store_ref_bool(std::move(cell))          // z:^StateInit
            && cb.finalize_to(cell));
      msg.init = vm::load_cell_slice_ref(std::move(cell));
    } else {
      redoing = 2;
    }
  }
  if (redoing >= 2 && msg.body->size_ext() > 1 && msg.body->prefetch_ulong(1) == 0) {
    LOG(DEBUG) << "moving the body of a suggested outbound message into a separate cell";
    // body:(Either X ^X)
    // transform (left x:X) into (right x:^X)
    CHECK(msg.body.write().fetch_ulong(1) == 0);
    vm::CellBuilder cb;
    Ref cell;
    CHECK(cb.append_cellslice_bool(std::move(msg.body))  // X
          && cb.finalize_to(cell)                        // -> ^X
          && cb.store_long_bool(1, 1)                    // (right ... )
          && cb.store_ref_bool(std::move(cell))          // x:^X
          && cb.finalize_to(cell));
    msg.body = vm::load_cell_slice_ref(std::move(cell));
  }
  block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info;
  bool ext_msg = msg.info->prefetch_ulong(1);
  if (ext_msg) {
    // ext_out_msg_info$11 constructor of CommonMsgInfoRelaxed
    block::gen::CommonMsgInfoRelaxed::Record_ext_out_msg_info erec;
    if (!tlb::csr_unpack(msg.info, erec)) {
      return -1;
    }
    if (act_rec.mode & ~3) {
      return -1;  // invalid mode for an external message
    }
    info.src = std::move(erec.src);
    info.dest = std::move(erec.dest);
    // created_lt and created_at are ignored
    info.ihr_disabled = true;
    info.bounce = false;
    info.bounced = false;
    fwd_fee = ihr_fee = td::zero_refint();
  } else {
    // int_msg_info$0 constructor
    if (!tlb::csr_unpack(msg.info, info) || !block::tlb::t_CurrencyCollection.validate_csr(info.value)) {
      return -1;
    }
    fwd_fee = block::tlb::t_Grams.as_integer(info.fwd_fee);
    ihr_fee = block::tlb::t_Grams.as_integer(info.ihr_fee);
  }
  // set created_at and created_lt to correct values
  info.created_at = now;
  info.created_lt = ap.end_lt;
  // always clear bounced flag
  info.bounced = false;
  // have to check source address
  // it must be either our source address, or empty
  if (!check_replace_src_addr(info.src)) {
    LOG(DEBUG) << "invalid source address in a proposed outbound message";
    return 35;  // invalid source address
  }
  bool to_mc = false;
  if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc)) {
    LOG(DEBUG) << "invalid destination address in a proposed outbound message";
    return skip_invalid ? 0 : 36;  // invalid destination address
  }
  // fetch message pricing info
  const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain());
  // compute size of message
  vm::CellStorageStat sstat;  // for message size
  // preliminary storage estimation of the resulting message
  sstat.add_used_storage(msg.init, true, 3);  // message init
  sstat.add_used_storage(msg.body, true, 3);  // message body (the root cell itself is not counted)
  if (!ext_msg) {
    sstat.add_used_storage(info.value->prefetch_ref());
  }
  LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits";
  if (sstat.bits > cfg.size_limits.max_msg_bits || sstat.cells > cfg.size_limits.max_msg_cells) {
    LOG(DEBUG) << "message too large, invalid";
    return skip_invalid ? 0 : 40;
  }
  // compute forwarding fees
  auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, info.ihr_disabled);
  LOG(DEBUG) << "computed fwd fees = " << fees_c.first << " + " << fees_c.second;
  if (account.is_special) {
    LOG(DEBUG) << "computed fwd fees set to zero for special account";
    fees_c.first = fees_c.second = 0;
  }
  // set fees to computed values
  if (fwd_fee->unsigned_fits_bits(63) && fwd_fee->to_long() < (long long)fees_c.first) {
    fwd_fee = td::make_refint(fees_c.first);
  }
  if (fees_c.second && ihr_fee->unsigned_fits_bits(63) && ihr_fee->to_long() < (long long)fees_c.second) {
    ihr_fee = td::make_refint(fees_c.second);
  }
  Ref new_msg;
  td::RefInt256 fees_collected, fees_total;
  unsigned new_msg_bits;
  if (!ext_msg) {
    // Process outbound internal message
    // check value, check/compute ihr_fees, fwd_fees
    // ...
    if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) {
      LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message";
      return skip_invalid ? 0 : 37;
    }
    if (info.ihr_disabled) {
      // if IHR is disabled, IHR fees will be always zero
      ihr_fee = td::zero_refint();
    }
    // extract value to be carried by the message
    block::CurrencyCollection req;
    CHECK(req.unpack(info.value));
    CHECK(req.grams.not_null());
    if (act_rec.mode & 0x80) {
      // attach all remaining balance to this message
      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 (!(act_rec.mode & 1) && compute_phase) {
        req -= compute_phase->gas_fees;
        if (!req.is_valid()) {
          LOG(DEBUG)
              << "not enough value to transfer with the message: all of the inbound message value has been consumed";
          return skip_invalid ? 0 : 37;
        }
      }
    }
    // compute req_grams + fees
    td::RefInt256 req_grams_brutto = req.grams;
    fees_total = fwd_fee + ihr_fee;
    if (act_rec.mode & 1) {
      // we are going to pay the fees
      req_grams_brutto += fees_total;
    } else if (req.grams < fees_total) {
      // receiver pays the fees (but cannot)
      LOG(DEBUG) << "not enough value attached to the message to pay forwarding fees : have " << req.grams << ", need "
                 << fees_total;
      return skip_invalid ? 0 : 37;  // not enough grams
    } else {
      // decrease message value
      req.grams -= fees_total;
    }
    // check that we have at least the required value
    if (ap.remaining_balance.grams < req_grams_brutto) {
      LOG(DEBUG) << "not enough grams to transfer with the message : remaining balance is "
                 << ap.remaining_balance.to_str() << ", need " << req_grams_brutto << " (including forwarding fees)";
      return skip_invalid ? 0 : 37;  // not enough grams
    }
    Ref new_extra;
    if (!block::sub_extra_currency(ap.remaining_balance.extra, req.extra, new_extra)) {
      LOG(DEBUG) << "not enough extra currency to send with the message: "
                 << block::CurrencyCollection{0, req.extra}.to_str() << " required, only "
                 << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " available";
      return skip_invalid ? 0 : 38;  // not enough (extra) funds
    }
    if (ap.remaining_balance.extra.not_null() || req.extra.not_null()) {
      LOG(DEBUG) << "subtracting extra currencies: "
                 << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " minus "
                 << block::CurrencyCollection{0, req.extra}.to_str() << " equals "
                 << block::CurrencyCollection{0, new_extra}.to_str();
    }
    auto fwd_fee_mine = msg_prices.get_first_part(fwd_fee);
    auto fwd_fee_remain = fwd_fee - fwd_fee_mine;
    // re-pack message value
    CHECK(req.pack_to(info.value));
    CHECK(block::tlb::t_Grams.pack_integer(info.fwd_fee, fwd_fee_remain));
    CHECK(block::tlb::t_Grams.pack_integer(info.ihr_fee, ihr_fee));
    // serialize message
    CHECK(tlb::csr_pack(msg.info, info));
    vm::CellBuilder cb;
    if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) {
      LOG(DEBUG) << "outbound message does not fit into a cell after rewriting";
      return redoing < 2 ? -2 : (skip_invalid ? 0 : 39);
    }
    new_msg_bits = cb.size();
    new_msg = cb.finalize();
    // clear msg_balance_remaining if it has been used
    if (act_rec.mode & 0xc0) {
      msg_balance_remaining.set_zero();
    }
    // update balance
    ap.remaining_balance -= req_grams_brutto;
    ap.remaining_balance.extra = std::move(new_extra);
    CHECK(ap.remaining_balance.is_valid());
    CHECK(ap.remaining_balance.grams->sgn() >= 0);
    fees_total = fwd_fee + ihr_fee;
    fees_collected = fwd_fee_mine;
  } else {
    // external messages also have forwarding fees
    if (ap.remaining_balance.grams < fwd_fee) {
      LOG(DEBUG) << "not enough funds to pay for an outbound external message";
      return skip_invalid ? 0 : 37;  // not enough grams
    }
    // repack message
    // ext_out_msg_info$11 constructor of CommonMsgInfo
    block::gen::CommonMsgInfo::Record_ext_out_msg_info erec;
    erec.src = info.src;
    erec.dest = info.dest;
    erec.created_at = info.created_at;
    erec.created_lt = info.created_lt;
    CHECK(tlb::csr_pack(msg.info, erec));
    vm::CellBuilder cb;
    if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) {
      LOG(DEBUG) << "outbound message does not fit into a cell after rewriting";
      return redoing < 2 ? -2 : (skip_invalid ? 0 : 39);
    }
    new_msg_bits = cb.size();
    new_msg = cb.finalize();
    // update balance
    ap.remaining_balance -= fwd_fee;
    CHECK(ap.remaining_balance.is_valid());
    CHECK(td::sgn(ap.remaining_balance.grams) >= 0);
    fees_collected = fees_total = fwd_fee;
  }
  if (!block::tlb::t_Message.validate_ref(new_msg)) {
    LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to hand-written check";
    return -1;
  }
  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);
    return -1;
  }
  if (verbosity > 2) {
    std::cerr << "converted outbound message: ";
    block::gen::t_Message_Any.print_ref(std::cerr, new_msg);
  }
  ap.msgs_created++;
  ap.end_lt++;
  ap.out_msgs.push_back(std::move(new_msg));
  ap.total_action_fees += fees_collected;
  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();
  }
  ap.tot_msg_bits += sstat.bits + new_msg_bits;
  ap.tot_msg_cells += sstat.cells + 1;
  return 0;
}
int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) {
  block::gen::OutAction::Record_action_reserve_currency rec;
  if (!tlb::unpack_exact(cs, rec) || (rec.mode & ~15)) {
    return -1;
  }
  int mode = rec.mode;
  LOG(INFO) << "in try_action_reserve_currency(" << mode << ")";
  CurrencyCollection reserve, newc;
  if (!reserve.validate_unpack(std::move(rec.currency))) {
    LOG(DEBUG) << "cannot parse currency field in action_reserve_currency";
    return -1;
  }
  LOG(DEBUG) << "action_reserve_currency: mode=" << mode << ", reserve=" << reserve.to_str()
             << ", balance=" << ap.remaining_balance.to_str() << ", original balance=" << original_balance.to_str();
  if (mode & 4) {
    if (mode & 8) {
      reserve = original_balance - reserve;
    } else {
      reserve += original_balance;
    }
  } else if (mode & 8) {
    LOG(DEBUG) << "invalid reserve mode " << mode;
    return -1;
  }
  if (!reserve.is_valid() || td::sgn(reserve.grams) < 0) {
    LOG(DEBUG) << "cannot reserve a negative amount: " << reserve.to_str();
    return -1;
  }
  if (reserve.grams > ap.remaining_balance.grams) {
    if (mode & 2) {
      reserve.grams = ap.remaining_balance.grams;
    } else {
      LOG(DEBUG) << "cannot reserve " << reserve.grams << " nanograms : only " << ap.remaining_balance.grams
                 << " available";
      return 37;  // not enough grams
    }
  }
  if (!block::sub_extra_currency(ap.remaining_balance.extra, reserve.extra, newc.extra)) {
    LOG(DEBUG) << "not enough extra currency to reserve: " << block::CurrencyCollection{0, reserve.extra}.to_str()
               << " required, only " << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str()
               << " available";
    if (mode & 2) {
      // TODO: process (mode & 2) correctly by setting res_extra := inf (reserve.extra, ap.remaining_balance.extra)
    }
    return 38;  // not enough (extra) funds
  }
  newc.grams = ap.remaining_balance.grams - reserve.grams;
  if (mode & 1) {
    // leave only res_grams, reserve everything else
    std::swap(newc, reserve);
  }
  // set remaining_balance to new_grams and new_extra
  ap.remaining_balance = std::move(newc);
  // increase reserved_balance by res_grams and res_extra
  ap.reserved_balance += std::move(reserve);
  CHECK(ap.reserved_balance.is_valid());
  CHECK(ap.remaining_balance.is_valid());
  LOG(INFO) << "changed remaining balance to " << ap.remaining_balance.to_str() << ", reserved balance to "
            << ap.reserved_balance.to_str();
  ap.spec_actions++;
  return 0;
}
bool Transaction::check_state_size_limit(const ActionPhaseConfig& cfg) {
  auto cell_equal = [](const td::Ref& a, const td::Ref& b) -> bool {
    if (a.is_null()) {
      return b.is_null();
    }
    if (b.is_null()) {
      return false;
    }
    return a->get_hash() == b->get_hash();
  };
  if (cell_equal(account.code, new_code) && cell_equal(account.data, new_data) &&
      cell_equal(account.library, new_library)) {
    return true;
  }
  // new_storage_stat is used here beause these stats will be reused in compute_state()
  new_storage_stat.limit_cells = cfg.size_limits.max_acc_state_cells;
  new_storage_stat.limit_bits = cfg.size_limits.max_acc_state_bits;
  new_storage_stat.add_used_storage(new_code);
  new_storage_stat.add_used_storage(new_data);
  new_storage_stat.add_used_storage(new_library);
  if (acc_status == Account::acc_active) {
    new_storage_stat.clear_limit();
  } else {
    new_storage_stat.clear();
  }
  return new_storage_stat.cells <= cfg.size_limits.max_acc_state_cells &&
         new_storage_stat.bits <= cfg.size_limits.max_acc_state_bits;
}
bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) {
  if (in_msg.is_null() || !bounce_enabled) {
    return false;
  }
  bounce_phase = std::make_unique();
  BouncePhase& bp = *bounce_phase;
  block::gen::Message::Record msg;
  block::gen::CommonMsgInfo::Record_int_msg_info info;
  auto cs = vm::load_cell_slice(in_msg);
  if (!(tlb::unpack(cs, info) && gen::t_Maybe_Either_StateInit_Ref_StateInit.skip(cs) && cs.have(1) &&
        cs.have_refs((int)cs.prefetch_ulong(1)))) {
    bounce_phase.reset();
    return false;
  }
  if (cs.fetch_ulong(1)) {
    cs = vm::load_cell_slice(cs.prefetch_ref());
  }
  info.ihr_disabled = true;
  info.bounce = false;
  info.bounced = true;
  std::swap(info.src, info.dest);
  bool to_mc = false;
  if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc)) {
    LOG(DEBUG) << "invalid destination address in a bounced message";
    bounce_phase.reset();
    return false;
  }
  // fetch message pricing info
  const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain());
  // compute size of message
  vm::CellStorageStat sstat;  // for message size
  // preliminary storage estimation of the resulting message
  sstat.compute_used_storage(info.value->prefetch_ref());
  bp.msg_bits = sstat.bits;
  bp.msg_cells = sstat.cells;
  // compute forwarding fees
  bp.fwd_fees = msg_prices.compute_fwd_fees(sstat.cells, sstat.bits);
  // check whether the message has enough funds
  auto msg_balance = msg_balance_remaining;
  if (compute_phase && compute_phase->gas_fees.not_null()) {
    msg_balance.grams -= compute_phase->gas_fees;
  }
  if ((msg_balance.grams < 0) ||
      (msg_balance.grams->signed_fits_bits(64) && msg_balance.grams->to_long() < (long long)bp.fwd_fees)) {
    // not enough funds
    bp.nofunds = true;
    return true;
  }
  // debit msg_balance_remaining from account's (tentative) balance
  balance -= msg_balance;
  CHECK(balance.is_valid());
  // debit total forwarding fees from the message's balance, then split forwarding fees into our part and remaining part
  msg_balance -= td::make_refint(bp.fwd_fees);
  bp.fwd_fees_collected = msg_prices.get_first_part(bp.fwd_fees);
  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_at = now;
  vm::CellBuilder cb;
  CHECK(cb.store_long_bool(5, 4)                            // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
        && cb.append_cellslice_bool(info.src)               // src:MsgAddressInt
        && cb.append_cellslice_bool(info.dest)              // dest:MsgAddressInt
        && msg_balance.store(cb)                            // value:CurrencyCollection
        && block::tlb::t_Grams.store_long(cb, 0)            // ihr_fee:Grams
        && block::tlb::t_Grams.store_long(cb, bp.fwd_fees)  // fwd_fee:Grams
        && cb.store_long_bool(info.created_lt, 64)          // created_lt:uint64
        && cb.store_long_bool(info.created_at, 32)          // created_at:uint32
        && cb.store_bool_bool(false));                      // init:(Maybe ...)
  if (cfg.bounce_msg_body) {
    int body_bits = std::min((int)cs.size(), cfg.bounce_msg_body);
    if (cb.remaining_bits() >= body_bits + 33u) {
      CHECK(cb.store_bool_bool(false)                             // body:(Either X ^X) -> left X
            && cb.store_long_bool(-1, 32)                         // int = -1 ("message type")
            && cb.append_bitslice(cs.prefetch_bits(body_bits)));  // truncated message body
    } else {
      vm::CellBuilder cb2;
      CHECK(cb.store_bool_bool(true)                             // body:(Either X ^X) -> right ^X
            && cb2.store_long_bool(-1, 32)                       // int = -1 ("message type")
            && cb2.append_bitslice(cs.prefetch_bits(body_bits))  // truncated message body
            && cb.store_builder_ref_bool(std::move(cb2)));       // ^X
    }
  } else {
    CHECK(cb.store_bool_bool(false));  // body:(Either ..)
  }
  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);
  }
  out_msgs.push_back(bp.out_msg);
  bp.ok = true;
  return true;
}
/*
 * 
 *  SERIALIZE PREPARED TRANSACTION
 * 
 */
bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const {
  int v;
  switch (acc_status) {
    case acc_nonexist:
    case acc_deleted:
      v = 3;  // acc_state_nonexist$11
      break;
    case acc_uninit:
      v = 0;  // acc_state_uninit$00
      break;
    case acc_frozen:
      v = 1;  // acc_state_frozen$01
      break;
    case acc_active:
      v = 2;  // acc_state_active$10
      break;
    default:
      return false;
  }
  return cb.store_long_bool(v, 2);
}
static td::optional try_update_storage_stat(const vm::CellStorageStat& old_stat,
                                                                 td::Ref old_cs,
                                                                 td::Ref new_cell) {
  if (old_stat.cells == 0 || old_cs.is_null()) {
    return {};
  }
  vm::CellSlice new_cs = vm::CellSlice(vm::NoVm(), new_cell);
  if (old_cs->size_refs() != new_cs.size_refs()) {
    return {};
  }
  for (unsigned i = 0; i < old_cs->size_refs(); ++i) {
    if (old_cs->prefetch_ref(i)->get_hash() != new_cs.prefetch_ref(i)->get_hash()) {
      return {};
    }
  }
  if (old_stat.bits < old_cs->size()) {
    return {};
  }
  vm::CellStorageStat new_stat;
  new_stat.cells = old_stat.cells;
  new_stat.bits = old_stat.bits - old_cs->size() + new_cs.size();
  new_stat.public_cells = old_stat.public_cells;
  return new_stat;
}
bool Transaction::compute_state() {
  if (new_total_state.not_null()) {
    return true;
  }
  if (acc_status == Account::acc_uninit && !was_activated && balance.is_zero()) {
    LOG(DEBUG) << "account is uninitialized and has zero balance, deleting it back";
    acc_status = Account::acc_nonexist;
    was_created = false;
  }
  if (acc_status == Account::acc_nonexist || acc_status == Account::acc_deleted) {
    CHECK(balance.is_zero());
    vm::CellBuilder cb;
    CHECK(cb.store_long_bool(0, 1)  // account_none$0
          && cb.finalize_to(new_total_state));
    return true;
  }
  vm::CellBuilder cb;
  CHECK(cb.store_long_bool(end_lt, 64)  // account_storage$_ last_trans_lt:uint64
        && balance.store(cb));          // balance:CurrencyCollection
  int ticktock = new_tick * 2 + new_tock;
  unsigned si_pos = 0;
  if (acc_status == Account::acc_uninit) {
    CHECK(cb.store_long_bool(0, 2));  // account_uninit$00 = AccountState
  } else if (acc_status == Account::acc_frozen) {
    if (was_frozen) {
      vm::CellBuilder cb2;
      CHECK(account.split_depth_ ? cb2.store_long_bool(account.split_depth_ + 32, 6)  // _ ... = StateInit
                                 : cb2.store_long_bool(0, 1));                        // ... split_depth:(Maybe (## 5))
      CHECK(ticktock ? cb2.store_long_bool(ticktock | 4, 3) : cb2.store_long_bool(0, 1));  // special:(Maybe TickTock)
      CHECK(cb2.store_maybe_ref(new_code) && cb2.store_maybe_ref(new_data) && cb2.store_maybe_ref(new_library));
      // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib)
      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;
      }
    }
    new_code.clear();
    new_data.clear();
    new_library.clear();
    if (frozen_hash == account.addr_orig) {
      // if frozen_hash equals account's "original" address (before rewriting), do not need storing hash
      CHECK(cb.store_long_bool(0, 2));  // account_uninit$00 = AccountState
    } else {
      CHECK(cb.store_long_bool(1, 2)              // account_frozen$01
            && cb.store_bits_bool(frozen_hash));  // state_hash:bits256
    }
  } else {
    CHECK(acc_status == Account::acc_active && !was_frozen && !was_deleted);
    si_pos = cb.size_ext() + 1;
    CHECK(account.split_depth_ ? cb.store_long_bool(account.split_depth_ + 96, 7)      // account_active$1 _:StateInit
                               : cb.store_long_bool(2, 2));                            // ... split_depth:(Maybe (## 5))
    CHECK(ticktock ? cb.store_long_bool(ticktock | 4, 3) : cb.store_long_bool(0, 1));  // special:(Maybe TickTock)
    CHECK(cb.store_maybe_ref(new_code) && cb.store_maybe_ref(new_data) && cb.store_maybe_ref(new_library));
    // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib)
  }
  auto storage = cb.finalize();
  new_storage = td::Ref(true, vm::NoVm(), storage);
  if (si_pos) {
    auto cs_ref = load_cell_slice_ref(storage);
    CHECK(cs_ref.unique_write().skip_ext(si_pos));
    new_inner_state = std::move(cs_ref);
  } else {
    new_inner_state.clear();
  }
  vm::CellStorageStat& stats = new_storage_stat;
  auto new_stats = try_update_storage_stat(account.storage_stat, account.storage, storage);
  if (new_stats) {
    stats = new_stats.unwrap();
  } else {
    td::Timer timer;
    CHECK(stats.add_used_storage(Ref(storage)));
    if (timer.elapsed() > 0.1) {
      LOG(INFO) << "Compute used storage took " << timer.elapsed() << "s";
    }
  }
  CHECK(cb.store_long_bool(1, 1)                       // account$1
        && cb.append_cellslice_bool(account.my_addr)   // addr:MsgAddressInt
        && block::store_UInt7(cb, stats.cells)         // storage_used$_ cells:(VarUInteger 7)
        && block::store_UInt7(cb, stats.bits)          //   bits:(VarUInteger 7)
        && block::store_UInt7(cb, stats.public_cells)  //   public_cells:(VarUInteger 7)
        && cb.store_long_bool(last_paid, 32));         // last_paid:uint32
  if (due_payment.not_null() && td::sgn(due_payment) != 0) {
    CHECK(cb.store_long_bool(1, 1) && block::tlb::t_Grams.store_integer_ref(cb, due_payment));
    // due_payment:(Maybe Grams)
  } else {
    CHECK(cb.store_long_bool(0, 1));
  }
  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);
  }
  CHECK(block::tlb::t_Account.validate_ref(new_total_state));
  return true;
}
bool Transaction::serialize() {
  if (root.not_null()) {
    return true;
  }
  if (!compute_state()) {
    return false;
  }
  vm::Dictionary dict{15};
  for (unsigned i = 0; i < out_msgs.size(); i++) {
    td::BitArray<15> key{i};
    if (!dict.set_ref(key, out_msgs[i], vm::Dictionary::SetMode::Add)) {
      return false;
    }
  }
  vm::CellBuilder cb, cb2;
  if (!(cb.store_long_bool(7, 4)                                             // transaction$0111
        && cb.store_bits_bool(account.addr)                                  // account_addr:bits256
        && cb.store_long_bool(start_lt)                                      // lt:uint64
        && cb.store_bits_bool(account.last_trans_hash_)                      // prev_trans_hash:bits256
        && cb.store_long_bool(account.last_trans_lt_, 64)                    // prev_trans_lt:uint64
        && cb.store_long_bool(account.now_, 32)                              // now:uint32
        && cb.store_ulong_rchk_bool(out_msgs.size(), 15)                     // outmsg_cnt:uint15
        && account.store_acc_status(cb)                                      // orig_status:AccountStatus
        && account.store_acc_status(cb, acc_status)                          // end_status:AccountStatus
        && cb2.store_maybe_ref(in_msg)                                       // ^[ in_msg:(Maybe ^(Message Any)) ...
        && std::move(dict).append_dict_to_bool(cb2)                          //    out_msgs:(HashmapE 15 ^(Message Any))
        && cb.store_ref_bool(cb2.finalize())                                 // ]
        && total_fees.store(cb)                                              // total_fees:CurrencyCollection
        && cb2.store_long_bool(0x72, 8)                                      //   update_hashes#72
        && cb2.store_bits_bool(account.total_state->get_hash().bits(), 256)  //   old_hash:bits256
        && cb2.store_bits_bool(new_total_state->get_hash().bits(), 256)      //   new_hash:bits256
        && cb.store_ref_bool(cb2.finalize()))) {                             // state_update:^(HASH_UPDATE Account)
    return false;
  }
  switch (trans_type) {
    case tr_tick:  // fallthrough
    case tr_tock: {
      vm::CellBuilder cb3;
      bool act = compute_phase->success;
      bool act_ok = act && action_phase->success;
      CHECK(cb2.store_long_bool(trans_type == tr_tick ? 2 : 3, 4)  // trans_tick_tock$000 is_tock:Bool
            && serialize_storage_phase(cb2)                        // storage:TrStoragePhase
            && serialize_compute_phase(cb2)                        // compute_ph:TrComputePhase
            && cb2.store_bool_bool(act)                            // action:(Maybe
            && (!act || (serialize_action_phase(cb3)               //   ^TrActionPhase)
                         && cb2.store_ref_bool(cb3.finalize()))) &&
            cb2.store_bool_bool(!act_ok)         // aborted:Bool
            && cb2.store_bool_bool(was_deleted)  // destroyed:Bool
            && cb.store_ref_bool(cb2.finalize()) && cb.finalize_to(root));
      break;
    }
    case tr_ord: {
      vm::CellBuilder cb3;
      bool have_storage = (bool)storage_phase;
      bool have_credit = (bool)credit_phase;
      bool have_bounce = (bool)bounce_phase;
      bool act = compute_phase->success;
      bool act_ok = act && action_phase->success;
      CHECK(cb2.store_long_bool(0, 4)                           // trans_ord$0000
            && cb2.store_long_bool(!bounce_enabled, 1)          // credit_first:Bool
            && cb2.store_bool_bool(have_storage)                // storage_ph:(Maybe
            && (!have_storage || serialize_storage_phase(cb2))  //   TrStoragePhase)
            && cb2.store_bool_bool(have_credit)                 // credit_ph:(Maybe
            && (!have_credit || serialize_credit_phase(cb2))    //   TrCreditPhase)
            && serialize_compute_phase(cb2)                     // compute_ph:TrComputePhase
            && cb2.store_bool_bool(act)                         // action:(Maybe
            && (!act || (serialize_action_phase(cb3) && cb2.store_ref_bool(cb3.finalize())))  //   ^TrActionPhase)
            && cb2.store_bool_bool(!act_ok)                                                   // aborted:Bool
            && cb2.store_bool_bool(have_bounce)                                               // bounce:(Maybe
            && (!have_bounce || serialize_bounce_phase(cb2))                                  //   TrBouncePhase
            && cb2.store_bool_bool(was_deleted)                                               // destroyed:Bool
            && cb.store_ref_bool(cb2.finalize()) && cb.finalize_to(root));
      break;
    }
    default:
      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);
  }
  if (!block::gen::t_Transaction.validate_ref(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);
    root.clear();
    return false;
  }
  if (!block::tlb::t_Transaction.validate_ref(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);
    root.clear();
    return false;
  }
  return true;
}
bool Transaction::serialize_storage_phase(vm::CellBuilder& cb) {
  if (!storage_phase) {
    return false;
  }
  StoragePhase& sp = *storage_phase;
  bool ok;
  // tr_phase_storage$_ storage_fees_collected:Grams
  if (sp.fees_collected.not_null()) {
    ok = block::tlb::t_Grams.store_integer_ref(cb, sp.fees_collected);
  } else {
    ok = block::tlb::t_Grams.null_value(cb);
  }
  // storage_fees_due:(Maybe Grams)
  ok &= block::store_Maybe_Grams_nz(cb, sp.fees_due);
  // status_change:AccStatusChange
  if (sp.deleted || sp.frozen) {
    ok &= cb.store_long_bool(sp.deleted ? 3 : 2, 2);  // acst_frozen$10 acst_deleted$11
  } else {
    ok &= cb.store_long_bool(0, 1);  // acst_unchanged$0 = AccStatusChange
  }
  return ok;
}
bool Transaction::serialize_credit_phase(vm::CellBuilder& cb) {
  if (!credit_phase) {
    return false;
  }
  CreditPhase& cp = *credit_phase;
  // tr_phase_credit$_ due_fees_collected:(Maybe Grams) credit:CurrencyCollection
  return block::store_Maybe_Grams_nz(cb, cp.due_fees_collected) && cp.credit.store(cb);
}
bool Transaction::serialize_compute_phase(vm::CellBuilder& cb) {
  if (!compute_phase) {
    return false;
  }
  ComputePhase& cp = *compute_phase;
  switch (cp.skip_reason) {
    // tr_compute_phase_skipped$0 reason:ComputeSkipReason;
    case ComputePhase::sk_no_state:
      return cb.store_long_bool(0, 3);  // cskip_no_state$00 = ComputeSkipReason;
    case ComputePhase::sk_bad_state:
      return cb.store_long_bool(1, 3);  // cskip_bad_state$01 = ComputeSkipReason;
    case ComputePhase::sk_no_gas:
      return cb.store_long_bool(2, 3);  // cskip_no_gas$10 = ComputeSkipReason;
    case ComputePhase::sk_none:
      break;
    default:
      return false;
  }
  vm::CellBuilder cb2;
  bool ok, credit = (cp.gas_credit != 0), exarg = (cp.exit_arg != 0);
  ok = cb.store_long_bool(1, 1)                                   // tr_phase_compute_vm$1
       && cb.store_long_bool(cp.success, 1)                       // success:Bool
       && cb.store_long_bool(cp.msg_state_used, 1)                // msg_state_used:Bool
       && cb.store_long_bool(cp.account_activated, 1)             // account_activated:Bool
       && block::tlb::t_Grams.store_integer_ref(cb, cp.gas_fees)  // gas_fees:Grams
       && block::store_UInt7(cb2, cp.gas_used)                    // ^[ gas_used:(VarUInteger 7)
       && block::store_UInt7(cb2, cp.gas_limit)                   //    gas_limit:(VarUInteger 7)
       && cb2.store_long_bool(credit, 1)                          //    gas_credit:(Maybe (VarUInteger 3))
       && (!credit || block::tlb::t_VarUInteger_3.store_long(cb2, cp.gas_credit)) &&
       cb2.store_long_rchk_bool(cp.mode, 8)      //    mode:int8
       && cb2.store_long_bool(cp.exit_code, 32)  //    exit_code:int32
       && cb2.store_long_bool(exarg, 1)          //    exit_arg:(Maybe int32)
       && (!exarg || cb2.store_long_bool(cp.exit_arg, 32)) &&
       cb2.store_ulong_rchk_bool(cp.vm_steps, 32)      //    vm_steps:uint32
       && cb2.store_bits_bool(cp.vm_init_state_hash)   //    vm_init_state_hash:bits256
       && cb2.store_bits_bool(cp.vm_final_state_hash)  //    vm_final_state_hash:bits256
       && cb.store_ref_bool(cb2.finalize());           // ] = TrComputePhase
  return ok;
}
bool Transaction::serialize_action_phase(vm::CellBuilder& cb) {
  if (!action_phase) {
    return false;
  }
  ActionPhase& ap = *action_phase;
  bool ok, arg = (ap.result_arg != 0);
  ok = cb.store_long_bool(ap.success, 1)                                             // tr_phase_action$_ success:Bool
       && cb.store_long_bool(ap.valid, 1)                                            // valid:Bool
       && cb.store_long_bool(ap.no_funds, 1)                                         // no_funds:Bool
       && cb.store_long_bool(ap.acc_status_change, (ap.acc_status_change >> 1) + 1)  // status_change:AccStatusChange
       && block::store_Maybe_Grams_nz(cb, ap.total_fwd_fees)                         // total_fwd_fees:(Maybe Grams)
       && block::store_Maybe_Grams_nz(cb, ap.total_action_fees)                      // total_action_fees:(Maybe Grams)
       && cb.store_long_bool(ap.result_code, 32)                                     // result_code:int32
       && cb.store_long_bool(arg, 1)                                                 // result_arg:(Maybe
       && (!arg || cb.store_long_bool(ap.result_arg, 32))                            //    uint32)
       && cb.store_ulong_rchk_bool(ap.tot_actions, 16)                               // tot_actions:uint16
       && cb.store_ulong_rchk_bool(ap.spec_actions, 16)                              // spec_actions:uint16
       && cb.store_ulong_rchk_bool(ap.skipped_actions, 16)                           // skipped_actions:uint16
       && cb.store_ulong_rchk_bool(ap.msgs_created, 16)                              // msgs_created:uint16
       && cb.store_bits_bool(ap.action_list_hash)                                    // action_list_hash:bits256
       && block::store_UInt7(cb, ap.tot_msg_cells, ap.tot_msg_bits);                 // tot_msg_size:StorageUsed
  return ok;
}
bool Transaction::serialize_bounce_phase(vm::CellBuilder& cb) {
  if (!bounce_phase) {
    return false;
  }
  BouncePhase& bp = *bounce_phase;
  if (!(bp.ok ^ bp.nofunds)) {
    return false;
  }
  if (bp.nofunds) {
    return cb.store_long_bool(1, 2)                              // tr_phase_bounce_nofunds$01
           && block::store_UInt7(cb, bp.msg_cells, bp.msg_bits)  // msg_size:StorageUsed
           && block::tlb::t_Grams.store_long(cb, bp.fwd_fees);   // req_fwd_fees:Grams
  } else {
    return cb.store_long_bool(1, 1)                                      // tr_phase_bounce_ok$1
           && block::store_UInt7(cb, bp.msg_cells, bp.msg_bits)          // msg_size:StorageUsed
           && block::tlb::t_Grams.store_long(cb, bp.fwd_fees_collected)  // msg_fees:Grams
           && block::tlb::t_Grams.store_long(cb, bp.fwd_fees);           // fwd_fees:Grams
  }
}
td::Result Transaction::estimate_block_storage_profile_incr(
    const vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const {
  if (root.is_null()) {
    return td::Status::Error("Cannot estimate the size profile of a transaction before it is serialized");
  }
  if (new_total_state.is_null()) {
    return td::Status::Error("Cannot estimate the size profile of a transaction before its new state is computed");
  }
  return store_stat.tentative_add_proof(new_total_state, usage_tree) + store_stat.tentative_add_cell(root);
}
bool Transaction::update_block_storage_profile(vm::NewCellStorageStat& store_stat,
                                               const vm::CellUsageTree* usage_tree) const {
  if (root.is_null() || new_total_state.is_null()) {
    return false;
  }
  store_stat.add_proof(new_total_state, usage_tree);
  store_stat.add_cell(root);
  return true;
}
bool Transaction::would_fit(unsigned cls, const block::BlockLimitStatus& blimst) const {
  auto res = estimate_block_storage_profile_incr(blimst.st_stat, blimst.limits.usage_tree);
  if (res.is_error()) {
    LOG(ERROR) << res.move_as_error();
    return false;
  }
  auto extra = res.move_as_ok();
  return blimst.would_fit(cls, end_lt, gas_used(), &extra);
}
bool Transaction::update_limits(block::BlockLimitStatus& blimst, bool with_size) const {
  if (!(blimst.update_lt(end_lt) && blimst.update_gas(gas_used()))) {
    return false;
  }
  if (with_size) {
    return blimst.add_proof(new_total_state) && blimst.add_cell(root) && blimst.add_transaction() &&
           blimst.add_account(is_first);
  }
  return true;
}
/*
 * 
 *  COMMIT TRANSACTION
 * 
 */
Ref Transaction::commit(Account& acc) {
  CHECK(account.last_trans_end_lt_ <= start_lt && start_lt < end_lt);
  CHECK(root.not_null());
  CHECK(new_total_state.not_null());
  CHECK((const void*)&acc == (const void*)&account);
  // export all fields modified by the Transaction into original account
  // NB: this is the only method that modifies account
  if (orig_addr_rewrite_set && new_split_depth >= 0 && acc.status != Account::acc_active &&
      acc_status == Account::acc_active) {
    LOG(DEBUG) << "setting address rewriting info for newly-activated account " << acc.addr.to_hex()
               << " with split_depth=" << new_split_depth
               << ", orig_addr_rewrite=" << orig_addr_rewrite.bits().to_hex(new_split_depth);
    CHECK(acc.init_rewrite_addr(new_split_depth, orig_addr_rewrite.bits()));
  }
  acc.status = (acc_status == Account::acc_deleted ? Account::acc_nonexist : acc_status);
  acc.last_trans_lt_ = start_lt;
  acc.last_trans_end_lt_ = end_lt;
  acc.last_trans_hash_ = root->get_hash().bits();
  acc.last_paid = last_paid;
  acc.storage_stat = new_storage_stat;
  acc.storage = new_storage;
  acc.balance = std::move(balance);
  acc.due_payment = std::move(due_payment);
  acc.total_state = std::move(new_total_state);
  acc.inner_state = std::move(new_inner_state);
  if (was_frozen) {
    acc.state_hash = frozen_hash;
  }
  acc.my_addr = std::move(my_addr);
  // acc.my_addr_exact = std::move(my_addr_exact);
  acc.code = std::move(new_code);
  acc.data = std::move(new_data);
  acc.library = std::move(new_library);
  if (acc.status == Account::acc_active) {
    acc.tick = new_tick;
    acc.tock = new_tock;
  } else {
    CHECK(acc.deactivate());
  }
  end_lt = 0;
  acc.push_transaction(root, start_lt);
  return root;
}
LtCellRef Transaction::extract_out_msg(unsigned i) {
  return {start_lt + i + 1, std::move(out_msgs.at(i))};
}
NewOutMsg Transaction::extract_out_msg_ext(unsigned i) {
  return {start_lt + i + 1, std::move(out_msgs.at(i)), root};
}
void Transaction::extract_out_msgs(std::vector& list) {
  for (unsigned i = 0; i < out_msgs.size(); i++) {
    list.emplace_back(start_lt + i + 1, std::move(out_msgs[i]));
  }
}
void Account::push_transaction(Ref trans_root, ton::LogicalTime trans_lt) {
  transactions.emplace_back(trans_lt, std::move(trans_root));
}
bool Account::create_account_block(vm::CellBuilder& cb) {
  if (transactions.empty()) {
    return false;
  }
  if (!(cb.store_long_bool(5, 4)         // acc_trans#5
        && cb.store_bits_bool(addr))) {  // account_addr:bits256
    return false;
  }
  vm::AugmentedDictionary dict{64, block::tlb::aug_AccountTransactions};
  for (auto& z : transactions) {
    if (!dict.set_ref(td::BitArray<64>{(long long)z.first}, z.second, vm::Dictionary::SetMode::Add)) {
      LOG(ERROR) << "error creating the list of transactions for account " << addr.to_hex()
                 << " : cannot add transaction with lt=" << z.first;
      return false;
    }
  }
  Ref dict_root = std::move(dict).extract_root_cell();
  // transactions:(HashmapAug 64 ^Transaction Grams)
  if (dict_root.is_null() || !cb.append_cellslice_bool(vm::load_cell_slice(std::move(dict_root)))) {
    return false;
  }
  vm::CellBuilder cb2;
  return cb2.store_long_bool(0x72, 8)                                      // update_hashes#72
         && cb2.store_bits_bool(orig_total_state->get_hash().bits(), 256)  // old_hash:bits256
         && cb2.store_bits_bool(total_state->get_hash().bits(), 256)       // new_hash:bits256
         && cb.store_ref_bool(cb2.finalize());                             // state_update:^(HASH_UPDATE Account)
}
bool Account::libraries_changed() const {
  bool s = orig_library.not_null();
  bool t = library.not_null();
  if (s & t) {
    return orig_library->get_hash() != library->get_hash();
  } else {
    return s != t;
  }
}
}  // namespace block
]