/*
    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/block.h"
#include "block/block-auto.h"
#include "block/mc-config.h"
#include "vm/cells.h"
#include "vm/boc.h"
#include "vm/cells/CellString.h"
#include "tonlib/utils.h"
#include "tonlib/TonlibClient.h"
#include "tonlib/Client.h"
#include "auto/tl/ton_api_json.h"
#include "auto/tl/tonlib_api_json.h"
#include "td/utils/benchmark.h"
#include "td/utils/filesystem.h"
#include "td/utils/optional.h"
#include "td/utils/overloaded.h"
#include "td/utils/port/path.h"
#include "td/utils/PathView.h"
#include "td/utils/tests.h"
// KeyManager
#include "tonlib/keys/bip39.h"
#include "tonlib/keys/DecryptedKey.h"
#include "tonlib/keys/EncryptedKey.h"
#include "tonlib/keys/Mnemonic.h"
#include "tonlib/keys/SimpleEncryption.h"
TEST(Tonlib, CellString) {
  for (unsigned size :
       {0, 1, 7, 8, 35, 127, 128, 255, 256, (int)vm::CellString::max_bytes - 1, (int)vm::CellString::max_bytes}) {
    auto str = td::rand_string('a', 'z', size);
    for (unsigned head : {0, 1, 7, 8, 127, 35 * 8, 127 * 8, 1023, 1024}) {
      vm::CellBuilder cb;
      vm::CellString::store(cb, str, head).ensure();
      auto cs = vm::load_cell_slice(cb.finalize());
      auto got_str = vm::CellString::load(cs, head).move_as_ok();
      ASSERT_EQ(str, got_str);
    }
  }
};
TEST(Tonlib, Text) {
  for (unsigned size :
       {0, 1, 7, 8, 35, 127, 128, 255, 256, (int)vm::CellText::max_bytes - 1, (int)vm::CellText::max_bytes}) {
    auto str = td::rand_string('a', 'z', size);
    for (unsigned head : {16, 17, 127, 35 * 8, 127 * 8, 1023, 1024}) {
      vm::CellBuilder cb;
      vm::CellText::store(cb, str, head).ensure();
      auto cs = vm::load_cell_slice(cb.finalize());
      auto cs2 = cs;
      cs.print_rec(std::cerr);
      CHECK(block::gen::t_Text.validate_exact_upto(1024, cs2));
      auto got_str = vm::CellText::load(cs).move_as_ok();
      ASSERT_EQ(str, got_str);
    }
  }
};
using namespace tonlib;
TEST(Tonlib, PublicKey) {
  block::PublicKey::parse("pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").ensure_error();
  auto key = block::PublicKey::parse("Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").move_as_ok();
  CHECK(td::buffer_to_hex(key.key) == "3EE9DC0A7A0B6CA01770CE34698792BD8ECB53A6949BFD6C81B6E3CA475B74D7");
  CHECK(key.serialize() == "Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2");
}
TEST(Tonlib, Address) {
  auto a = block::StdAddress::parse("-1:538fa7cc24ff8eaa101d84a5f1ab7e832fe1d84b309cdfef4ee94373aac80f7d").move_as_ok();
  auto b = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
  auto c = block::StdAddress::parse("Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX").move_as_ok();
  CHECK(a == b);
  CHECK(a == c);
  CHECK(block::StdAddress::parse("Ef9Tj6fMJp-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX").is_error());
  CHECK(block::StdAddress::parse("Ef9Tj6fMJp+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").is_error());
  CHECK(block::StdAddress::parse(a.rserialize()).move_as_ok() == a);
}
static auto sync_send = [](auto &client, auto query) {
  using ReturnTypePtr = typename std::decay_t::ReturnType;
  using ReturnType = typename ReturnTypePtr::element_type;
  client.send({1, std::move(query)});
  while (true) {
    auto response = client.receive(100);
    if (response.object) {
      CHECK(response.id == 1);
      if (response.object->get_id() == tonlib_api::error::ID) {
        auto error = tonlib_api::move_object_as(response.object);
        return td::Result(td::Status::Error(error->code_, error->message_));
      }
      return td::Result(tonlib_api::move_object_as(response.object));
    }
  }
};
TEST(Tonlib, InitClose) {
  using tonlib_api::make_object;
  auto cfg = [](auto str) { return make_object(str, "", false, false); };
  auto dir = [](auto str) { return make_object(str); };
  {
    Client client;
    sync_send(client, make_object()).ensure();
    sync_send(client, make_object(make_object(nullptr, dir("."))))
        .ensure_error();
  }
  {
    Client client;
    sync_send(client, make_object(nullptr)).ensure_error();
    sync_send(client, make_object(make_object(cfg("fdajkfldsjkafld"), dir("."))))
        .ensure_error();
    sync_send(client, make_object(make_object(nullptr, dir("fdhskfds"))))
        .ensure_error();
    sync_send(client, make_object(make_object(nullptr, dir(".")))).ensure();
    sync_send(client, make_object(make_object(nullptr, dir("."))))
        .ensure_error();
    td::Slice bad_config = R"abc(
{
  "@type": "config.global",
  "liteclients": [ ]
}
)abc";
    sync_send(client, make_object(cfg(bad_config.str()))).ensure_error();
    auto address = sync_send(client, make_object(
                                         make_object(
                                             "Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2", 123),
                                         0, -1))
                       .move_as_ok();
    sync_send(client, make_object(std::move(address))).ensure_error();
    sync_send(client, make_object()).ensure();
    sync_send(client, make_object()).ensure_error();
    sync_send(client, make_object(make_object(nullptr, dir("."))))
        .ensure_error();
  }
}
td::Slice to_data(const td::SecureString &str) {
  return str.as_slice();
}
td::Slice to_data(const tonlib::SimpleEncryptionV2::Decrypted &str) {
  return str.data.as_slice();
}
template 
void test_encryption() {
  std::string secret = "secret";
  {
    std::string data = "some private data";
    std::string wrong_secret = "wrong secret";
    auto encrypted_data = Encryption::encrypt_data(data, secret);
    LOG(ERROR) << encrypted_data.size();
    auto decrypted_data = Encryption::decrypt_data(encrypted_data, secret).move_as_ok();
    CHECK(data == to_data(decrypted_data));
    Encryption::decrypt_data(encrypted_data, wrong_secret).ensure_error();
    Encryption::decrypt_data("", secret).ensure_error();
    Encryption::decrypt_data(std::string(32, 'a'), secret).ensure_error();
    Encryption::decrypt_data(std::string(33, 'a'), secret).ensure_error();
    Encryption::decrypt_data(std::string(64, 'a'), secret).ensure_error();
    Encryption::decrypt_data(std::string(128, 'a'), secret).ensure_error();
  }
  for (size_t i = 0; i < 255; i++) {
    auto data = td::rand_string('a', 'z', static_cast(i));
    auto encrypted_data = Encryption::encrypt_data(data, secret);
    auto decrypted_data = Encryption::decrypt_data(encrypted_data, secret).move_as_ok();
    CHECK(data == to_data(decrypted_data));
  }
}
TEST(Tonlib, SimpleEncryption) {
  test_encryption();
}
TEST(Tonlib, SimpleEncryptionV2) {
  test_encryption();
}
TEST(Tonlib, SimpleEncryptionAsym) {
  auto private_key = td::Ed25519::generate_private_key().move_as_ok();
  auto public_key = private_key.get_public_key().move_as_ok();
  auto other_private_key = td::Ed25519::generate_private_key().move_as_ok();
  auto other_public_key = private_key.get_public_key().move_as_ok();
  auto wrong_private_key = td::Ed25519::generate_private_key().move_as_ok();
  {
    std::string data = "some private data";
    auto encrypted_data = SimpleEncryptionV2::encrypt_data(data, public_key, other_private_key).move_as_ok();
    LOG(ERROR) << encrypted_data.size();
    auto decrypted_data = SimpleEncryptionV2::decrypt_data(encrypted_data, private_key).move_as_ok();
    CHECK(data == decrypted_data.data);
    auto decrypted_data2 = SimpleEncryptionV2::decrypt_data(encrypted_data, other_private_key).move_as_ok();
    CHECK(data == decrypted_data2.data);
    CHECK(decrypted_data.proof == decrypted_data2.proof);
    auto decrypted_data3 =
        SimpleEncryptionV2::decrypt_data_with_proof(encrypted_data, decrypted_data.proof).move_as_ok();
    CHECK(data == decrypted_data3);
    SimpleEncryptionV2::decrypt_data(encrypted_data, wrong_private_key).ensure_error();
    SimpleEncryptionV2::decrypt_data("", private_key).ensure_error();
    SimpleEncryptionV2::decrypt_data(std::string(32, 'a'), private_key).ensure_error();
    SimpleEncryptionV2::decrypt_data(std::string(33, 'a'), private_key).ensure_error();
    SimpleEncryptionV2::decrypt_data(std::string(64, 'a'), private_key).ensure_error();
    SimpleEncryptionV2::decrypt_data(std::string(128, 'a'), private_key).ensure_error();
    SimpleEncryptionV2::decrypt_data_with_proof(encrypted_data, decrypted_data.proof, "bad salt").ensure_error();
  }
  for (size_t i = 0; i < 255; i++) {
    auto data = td::rand_string('a', 'z', static_cast(i));
    auto encrypted_data = SimpleEncryptionV2::encrypt_data(data, public_key, other_private_key).move_as_ok();
    auto decrypted_data = SimpleEncryptionV2::decrypt_data(encrypted_data, private_key).move_as_ok();
    CHECK(data == decrypted_data.data);
    auto decrypted_data2 = SimpleEncryptionV2::decrypt_data(encrypted_data, other_private_key).move_as_ok();
    CHECK(data == decrypted_data2.data);
  }
}
class MnemonicBench : public td::Benchmark {
 public:
  std::string get_description() const override {
    return "mnemonic is_password_seed";
  }
  void start_up() override {
    Mnemonic::Options options;
    options.password = td::SecureString("qwerty");
    mnemonic_ = Mnemonic::create_new(std::move(options)).move_as_ok();
  }
  void run(int n) override {
    int x = 0;
    for (int i = 0; i < n; i++) {
      x += mnemonic_.value().is_password_seed();
    }
    td::do_not_optimize_away(x);
  }
  td::optional mnemonic_;
};
TEST(Tonlib, Mnemonic) {
  td::bench(MnemonicBench());
  //for (int i = 0; i < 20; i++) {
  //td::PerfWarningTimer perf("Mnemonic::create", 0.01);
  //Mnemonic::Options options;
  //options.password = td::SecureString("qwerty");
  //Mnemonic::create_new(std::move(options)).move_as_ok();
  //}
  // FIXME
  //auto tmp = std::vector{"hello", "world"};
  //CHECK(tmp[0].as_slice() == "hello");
  auto a = Mnemonic::create(td::SecureString(" Hello, . $^\n# World!   "), td::SecureString("cucumber")).move_as_ok();
  auto get_word_list = [] {
    std::vector words;
    words.emplace_back("hello");
    words.emplace_back("world");
    return words;
  };
  auto b = Mnemonic::create(get_word_list(), td::SecureString("cucumber")).move_as_ok();
  CHECK(a.get_words() == b.get_words());
  CHECK(a.get_words() == get_word_list());
  Mnemonic::Options options;
  options.password = td::SecureString("qwerty");
  auto password = options.password.copy();
  auto c = Mnemonic::create_new(std::move(options)).move_as_ok();
  auto d = Mnemonic::create(c.get_words(), std::move(password)).move_as_ok();
  CHECK(c.to_private_key().as_octet_string() == d.to_private_key().as_octet_string());
}
TEST(Tonlib, Keys) {
  auto a = Mnemonic::create(td::SecureString(" Hello, . $^\n# World!   "), td::SecureString("cucumber")).move_as_ok();
  DecryptedKey decrypted_key(std::move(a));
  EncryptedKey encrypted_key = decrypted_key.encrypt("qwerty");
  auto other_decrypted_key = encrypted_key.decrypt("qwerty").move_as_ok();
  encrypted_key.decrypt("abcde").ensure_error();
  CHECK(decrypted_key.mnemonic_words == other_decrypted_key.mnemonic_words);
  CHECK(decrypted_key.private_key.as_octet_string() == other_decrypted_key.private_key.as_octet_string());
}
TEST(Tonlib, ParseAddres) {
  using tonlib_api::make_object;
  Client client;
  // init
  sync_send(client, make_object(
                        make_object(nullptr, make_object("."))))
      .ensure();
  sync_send(client, make_object("hello")).ensure_error();
  auto addr =
      sync_send(client,
                make_object("Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX"))
          .move_as_ok();
  ASSERT_EQ(-1, addr->workchain_id_);
  ASSERT_EQ(true, addr->bounceable_);
  ASSERT_EQ(false, addr->testnet_);
  auto raw = addr->addr_;
  auto addr_str = sync_send(client, make_object(std::move(addr))).move_as_ok();
  ASSERT_EQ("Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX", addr_str->account_address_);
  auto addr_str2 = sync_send(client, make_object(
                                         make_object(-1, false, false, raw)))
                       .move_as_ok();
  ASSERT_EQ("Uf9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfdyS", addr_str2->account_address_);
}
TEST(Tonlib, ConfigParseBug) {
  td::Slice literal =
      "D1000000000000006400000000000186A0DE0000000003E8000000000000000F424000000000000F42400000000000002710000000000098"
      "96800000000005F5E100000000003B9ACA00";
  unsigned char buff[128];
  int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), literal.begin(), literal.end());
  CHECK(bits >= 0);
  auto slice = vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize();
  block::Config::do_get_gas_limits_prices(std::move(slice), 21).ensure();
}
TEST(Tonlib, EncryptionApi) {
  using tonlib_api::make_object;
  Client client;
  // init
  sync_send(client, make_object(
                        make_object(nullptr, make_object("."))))
      .ensure();
  std::string password = "hello world";
  std::string data = "very secret data";
  auto key = std::move(
      sync_send(client, make_object(td::SecureString(password), td::SecureString("salt"), 100000))
          .move_as_ok()
          ->bytes_);
  auto encrypted = std::move(
      sync_send(client, make_object(td::SecureString(data), key.copy())).move_as_ok()->bytes_);
  auto decrypted =
      std::move(sync_send(client, make_object(encrypted.copy(), key.copy())).move_as_ok()->bytes_);
  ASSERT_EQ(data, decrypted);
  auto bad_key = std::move(sync_send(client, make_object(td::SecureString(password + "BAD"),
                                                                          td::SecureString("salt"), 100000))
                               .move_as_ok()
                               ->bytes_);
  sync_send(client, make_object(encrypted.copy(), bad_key.copy())).ensure_error();
}
TEST(Tonlib, KeysApi) {
  using tonlib_api::make_object;
  Client client;
  td::mkdir("testdir").ignore();
  // init
  sync_send(client, make_object(make_object(
                        nullptr, make_object("testdir"))))
      .ensure();
  auto local_password = td::SecureString("local password");
  auto mnemonic_password = td::SecureString("mnemonic password");
  {
    auto key = sync_send(client, make_object(local_password.copy(), td::SecureString{},
                                                                       td::SecureString{}))
                   .move_as_ok();
  }
  //createNewKey local_password:bytes mnemonic_password:bytes = Key;
  auto key = sync_send(client, make_object(local_password.copy(), mnemonic_password.copy(),
                                                                     td::SecureString{}))
                 .move_as_ok();
  sync_send(client, make_object(make_object(
                        make_object(key->public_key_, key->secret_.copy()),
                        td::SecureString("wrong password"))))
      .ensure_error();
  //exportKey input_key:inputKeyRegular = ExportedKey;
  auto exported_key =
      sync_send(client,
                make_object(make_object(
                    make_object(key->public_key_, key->secret_.copy()), local_password.copy())))
          .move_as_ok();
  LOG(ERROR) << to_string(exported_key);
  auto copy_word_list = [&] {
    std::vector word_list_copy;
    for (auto &w : exported_key->word_list_) {
      word_list_copy.push_back(w.copy());
    }
    return word_list_copy;
  };
  //changeLocalPassword input_key:inputKeyRegular new_local_password:bytes = Key;
  auto new_key =
      sync_send(client,
                make_object(
                    make_object(
                        make_object(key->public_key_, key->secret_.copy()), local_password.copy()),
                    td::SecureString("tmp local password")))
          .move_as_ok();
  sync_send(client,
            make_object(make_object(
                make_object(key->public_key_, new_key->secret_.copy()), local_password.copy())))
      .ensure_error();
  auto exported_key2 = sync_send(client, make_object(make_object(
                                             make_object(key->public_key_, new_key->secret_.copy()),
                                             td::SecureString("tmp local password"))))
                           .move_as_ok();
  CHECK(exported_key2->word_list_ == exported_key->word_list_);
  //importKey local_password:bytes mnemonic_password:bytes exported_key:exportedKey = Key;
  auto new_local_password = td::SecureString("new_local_password");
  // import already existed key
  //sync_send(client, make_object(new_local_password.copy(), mnemonic_password.copy(),
  //make_object(copy_word_list())))
  //.ensure_error();
  {
    auto export_password = td::SecureString("export password");
    auto wrong_export_password = td::SecureString("wrong_export password");
    auto exported_encrypted_key =
        sync_send(client, make_object(
                              make_object(
                                  make_object(key->public_key_, new_key->secret_.copy()),
                                  td::SecureString("tmp local password")),
                              export_password.copy()))
            .move_as_ok();
    sync_send(client,
              make_object(make_object(key->public_key_, key->secret_.copy())))
        .move_as_ok();
    sync_send(client, make_object(
                          new_local_password.copy(), wrong_export_password.copy(),
                          make_object(exported_encrypted_key->data_.copy())))
        .ensure_error();
    auto imported_encrypted_key =
        sync_send(client, make_object(
                              new_local_password.copy(), export_password.copy(),
                              make_object(exported_encrypted_key->data_.copy())))
            .move_as_ok();
    CHECK(imported_encrypted_key->public_key_ == key->public_key_);
    key = std::move(imported_encrypted_key);
  }
  //deleteKey public_key:bytes = Ok;
  sync_send(client,
            make_object(make_object(key->public_key_, key->secret_.copy())))
      .move_as_ok();
  auto err1 = sync_send(
      client, make_object(new_local_password.copy(), td::SecureString("wrong password"),
                                                 make_object(copy_word_list())));
  if (err1.is_ok()) {
    if (err1.ok()->public_key_ != key->public_key_) {
      err1 = td::Status::Error("imported key successfully, but the public key is different");
    }
  }
  err1.ensure_error();
  auto err2 =
      sync_send(client, make_object(new_local_password.copy(), td::SecureString(),
                                                           make_object(copy_word_list())));
  err2.ensure_error();
  LOG(INFO) << err1.move_as_error() << " | " << err2.move_as_error();
  auto imported_key =
      sync_send(client, make_object(new_local_password.copy(), mnemonic_password.copy(),
                                                           make_object(copy_word_list())))
          .move_as_ok();
  CHECK(imported_key->public_key_ == key->public_key_);
  CHECK(imported_key->secret_ != key->secret_);
  //exportPemKey input_key:inputKeyRegular key_password:bytes = ExportedPemKey;
  auto pem_password = td::SecureString("pem password");
  auto r_exported_pem_key = sync_send(
      client,
      make_object(
          make_object(
              make_object(key->public_key_, imported_key->secret_.copy()), new_local_password.copy()),
          pem_password.copy()));
  if (r_exported_pem_key.is_error() && r_exported_pem_key.error().message() == "INTERNAL Not supported") {
    return;
  }
  auto exported_pem_key = r_exported_pem_key.move_as_ok();
  LOG(ERROR) << to_string(exported_pem_key);
  //importPemKey exported_key:exportedPemKey key_password:bytes = Key;
  //sync_send(client, make_object(
  //new_local_password.copy(), pem_password.copy(),
  //make_object(exported_pem_key->pem_.copy())))
  //.ensure_error();
  sync_send(client, make_object(
                        make_object(imported_key->public_key_, imported_key->secret_.copy())))
      .move_as_ok();
  sync_send(client, make_object(
                        new_local_password.copy(), td::SecureString("wrong pem password"),
                        make_object(exported_pem_key->pem_.copy())))
      .ensure_error();
  auto new_imported_key = sync_send(client, make_object(
                                                new_local_password.copy(), pem_password.copy(),
                                                make_object(exported_pem_key->pem_.copy())))
                              .move_as_ok();
  CHECK(new_imported_key->public_key_ == key->public_key_);
  CHECK(new_imported_key->secret_ != key->secret_);
  auto exported_raw_key =
      sync_send(client, make_object(make_object(
                            make_object(key->public_key_, new_imported_key->secret_.copy()),
                            new_local_password.copy())))
          .move_as_ok();
  sync_send(client, make_object(
                        make_object(new_imported_key->public_key_, new_imported_key->secret_.copy())))
      .move_as_ok();
  td::Ed25519::PrivateKey pkey(exported_raw_key->data_.copy());
  auto raw_imported_key = sync_send(client, make_object(new_local_password.copy(),
                                                                                          std::move(exported_raw_key)))
                              .move_as_ok();
  CHECK(raw_imported_key->public_key_ == key->public_key_);
  CHECK(raw_imported_key->secret_ != key->secret_);
  auto other_public_key = td::Ed25519::generate_private_key().move_as_ok().get_public_key().move_as_ok();
  std::string text = "hello world";
  std::vector> elements;
  td::Slice addr = "Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX";
  auto encrypted = SimpleEncryptionV2::encrypt_data(text, other_public_key, pkey, addr).move_as_ok().as_slice().str();
  elements.push_back(make_object(
      make_object(addr.str()), make_object(encrypted)));
  auto decrypted =
      sync_send(client, make_object(
                            make_object(
                                make_object(key->public_key_, raw_imported_key->secret_.copy()),
                                new_local_password.copy()),
                            make_object(std::move(elements))))
          .move_as_ok();
  auto proof = decrypted->elements_[0]->proof_;
  downcast_call(*decrypted->elements_[0]->data_,
                td::overloaded([](auto &) { UNREACHABLE(); },
                               [&](tonlib_api::msg_dataDecryptedText &decrypted) { CHECK(decrypted.text_ == text); }));
  auto decrypted2 = sync_send(client, make_object(
                                          proof, make_object(
                                                     make_object(addr.str()),
                                                     make_object(encrypted))))
                        .move_as_ok();
  downcast_call(*decrypted2,
                td::overloaded([](auto &) { UNREACHABLE(); },
                               [&](tonlib_api::msg_dataDecryptedText &decrypted) { CHECK(decrypted.text_ == text); }));
}
TEST(Tonlib, ConfigCache) {
  using tonlib_api::make_object;
  Client client;
  td::rmrf("testdir").ignore();
  td::mkdir("testdir").ignore();
  // init
  sync_send(client, make_object(make_object(
                        nullptr, make_object("testdir"))))
      .ensure();
  auto mainnet = R"abc({
    "liteservers": [
    ],
    "validator": {
      "@type": "validator.config.global",
      "zero_state": {
        "workchain": -1,
        "shard": -9223372036854775808,
        "seqno": 0,
        "root_hash": "F6OpKZKqvqeFp6CQmFomXNMfMj2EnaUSOXN+Mh+wVWk=",
        "file_hash": "XplPz01CXAps5qeSWUtxcyBfdAo5zVb1N979KLSKD24="
      },
      "hardforks": [
        {
          "file_hash": "t/9VBPODF7Zdh4nsnA49dprO69nQNMqYL+zk5bCjV/8=",
          "seqno": 8536841,
          "root_hash": "08Kpc9XxrMKC6BF/FeNHPS3MEL1/Vi/fQU/C9ELUrkc=",
          "workchain": -1,
          "shard": -9223372036854775808
        }
      ]
    }
  })abc";
  auto testnet = R"abc({
    "liteservers": [
    ],
    "validator": {
      "@type": "validator.config.global",
      "zero_state": {
        "workchain": -1,
        "shard": -9223372036854775808,
        "seqno": 0,
        "root_hash": "gj+B8wb/AmlPk1z1AhVI484rhrUpgSr2oSFIh56VoSg=",
        "file_hash": "Z+IKwYS54DmmJmesw/nAD5DzWadnOCMzee+kdgSYDOg="
      },
      "hardforks": [
        {
          "file_hash": "jF3RTD+OyOoP+OI9oIjdV6M8EaOh9E+8+c3m5JkPYdg=",
          "seqno": 5141579,
          "root_hash": "6JSqIYIkW7y8IorxfbQBoXiuY3kXjcoYgQOxTJpjXXA=",
          "workchain": -1,
          "shard": -9223372036854775808
        },
        {
          "file_hash": "WrNoMrn5UIVPDV/ug/VPjYatvde8TPvz5v1VYHCLPh8=",
          "seqno": 5172980,
          "root_hash": "054VCNNtUEwYGoRe1zjH+9b1q21/MeM+3fOo76Vcjes=",
          "workchain": -1,
          "shard": -9223372036854775808
        },
        {
          "file_hash": "xRaxgUwgTXYFb16YnR+Q+VVsczLl6jmYwvzhQ/ncrh4=",
          "seqno": 5176527,
          "root_hash": "SoPLqMe9Dz26YJPOGDOHApTSe5i0kXFtRmRh/zPMGuI=",
          "workchain": -1,
          "shard": -9223372036854775808
        }
      ]
    }
  })abc";
  auto custom = R"abc({
    "liteservers": [
    ],
    "validator": {
      "@type": "validator.config.global",
      "zero_state": {
        "workchain": -1,
        "shard": -9223372036854775808,
        "seqno": 0,
        "root_hash": "ZXSXxDHhTALFxReyTZRd8E4Ya3ySOmpOWAS4rBX9XBY=",
        "file_hash": "eh9yveSz1qMdJ7mOsO+I+H77jkLr9NpAuEkoJuseXBo="
      }
    }
  })abc";
  auto bad = R"abc({
    "liteservers": [
    ],
    "validator": {
      "@type": "validator.config.global",
      "zero_state": {
        "workchain": -1,
        "shard": -9223372036854775808,
        "seqno": 0,
        "file_hash": "eh9yveSz1qMdJ7mOsO+I+H77jkLr9NpAuEkoJuseXBo="
      }
    }
  })abc";
  sync_send(client,
            make_object(make_object(bad, "", true, false)))
      .ensure_error();
  sync_send(client,
            make_object(make_object(mainnet, "", true, false)))
      .ensure();
  sync_send(client,
            make_object(make_object(testnet, "", true, false)))
      .ensure();
  sync_send(client,
            make_object(make_object(custom, "", true, false)))
      .ensure();
  sync_send(client, make_object(
                        make_object(mainnet, "testnet", true, false)))
      .ensure_error();
  sync_send(client, make_object(
                        make_object(testnet, "testnet", true, false)))
      .ensure();
  sync_send(client, make_object(
                        make_object(custom, "testnet", true, false)))
      .ensure_error();
}