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

updated smartcontracts

- updated smartcontracts
- updated fullnode database layout
- fixed memory leak in blockchain-explorer
- updated tonlib
This commit is contained in:
ton 2019-10-23 17:43:50 +04:00
parent 9c9248a9ae
commit c860ce3d1e
104 changed files with 7309 additions and 1335 deletions

View file

@ -17,23 +17,14 @@
Copyright 2017-2019 Telegram Systems LLP
*/
#include "fift/Fift.h"
#include "fift/words.h"
#include "fift/utils.h"
#include "block/block.h"
#include "block/block-auto.h"
#include "vm/cells.h"
#include "vm/boc.h"
#include "vm/cells/MerkleProof.h"
#include "vm/cells/CellString.h"
#include "tonlib/CellString.h"
#include "tonlib/utils.h"
#include "tonlib/TestGiver.h"
#include "tonlib/TestWallet.h"
#include "tonlib/Wallet.h"
#include "tonlib/GenericAccount.h"
#include "tonlib/TonlibClient.h"
#include "tonlib/Client.h"
@ -70,181 +61,6 @@ TEST(Tonlib, CellString) {
using namespace tonlib;
std::string current_dir() {
return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str();
}
std::string load_source(std::string name) {
return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok();
}
td::Ref<vm::Cell> get_test_wallet_source() {
std::string code = R"ABCD(
SETCP0 DUP IFNOTRET // return if recv_internal
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
}>
INC 32 THROWIF // fail unless recv_external
512 INT LDSLICEX DUP 32 PLDU // sign cs cnt
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk
s1 s2 XCPU // sign cs cnt pubk cnt' cnt
EQUAL 33 THROWIFNOT // ( seqno mismatch? )
s2 PUSH HASHSU // sign cs cnt pubk hash
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
CHKSIGNU // pubk cs cnt ?
34 THROWIFNOT // signature mismatch
ACCEPT
SWAP 32 LDU NIP
DUP SREFS IF:<{
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
8 LDU LDREF // pubk cnt mode msg cs
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
}>
ENDS
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
)ABCD";
return fift::compile_asm(code).move_as_ok();
}
td::Ref<vm::Cell> get_wallet_source() {
std::string code = R"ABCD(
SETCP0 DUP IFNOTRET // return if recv_internal
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
}>
INC 32 THROWIF // fail unless recv_external
9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs
SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs
c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key
s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno
EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno
s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash
s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key
ACCEPT
s0 s2 XCHG // public_key stored_seqno cs
WHILE:<{
DUP SREFS // public_key stored_seqno cs _40
}>DO<{ // public_key stored_seqno cs
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode
SENDRAWMSG // public_key stored_seqno cs
}>
ENDS INC // public_key seqno'
NEWC 32 STU 256 STU ENDC c4 POP
)ABCD";
return fift::compile_asm(code).move_as_ok();
}
TEST(Tonlib, TestWallet) {
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok());
CHECK(get_test_wallet_source()->get_hash() == TestWallet::get_init_code()->get_hash());
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok();
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
auto pub_key = priv_key.get_public_key().move_as_ok();
auto init_state = TestWallet::get_init_state(pub_key);
auto init_message = TestWallet::get_init_message(priv_key);
auto address = GenericAccount::get_address(0, init_state);
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
LOG(ERROR) << "-------";
vm::load_cell_slice(res).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure();
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup),
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123",
"321", "-C", "TEST"})
.move_as_ok();
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto gift_message = GenericAccount::create_ext_message(
address, {}, TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest));
LOG(ERROR) << "-------";
vm::load_cell_slice(gift_message).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
}
td::Ref<vm::Cell> get_wallet_source_fc() {
return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok();
}
TEST(Tonlib, Wallet) {
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok());
CHECK(get_wallet_source()->get_hash() == Wallet::get_init_code()->get_hash());
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok();
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
auto pub_key = priv_key.get_public_key().move_as_ok();
auto init_state = Wallet::get_init_state(pub_key);
auto init_message = Wallet::get_init_message(priv_key);
auto address = GenericAccount::get_address(0, init_state);
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
LOG(ERROR) << "-------";
vm::load_cell_slice(res).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
class ZeroOsTime : public fift::OsTime {
public:
td::uint32 now() override {
return 0;
}
};
fift_output.source_lookup.set_os_time(std::make_unique<ZeroOsTime>());
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
fift_output =
fift::mem_run_fift(std::move(fift_output.source_lookup),
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"})
.move_as_ok();
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto gift_message = GenericAccount::create_ext_message(
address, {}, Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest));
LOG(ERROR) << "-------";
vm::load_cell_slice(gift_message).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
}
TEST(Tonlib, TestGiver) {
auto address =
block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok();
LOG(ERROR) << address.bounceable;
auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"),
{"aba", address.rserialize(), "0", "6.666", "wallet-query"})
.move_as_ok();
LOG(ERROR) << fift_output.output;
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto res = GenericAccount::create_ext_message(
TestGiver::address(), {}, TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address));
vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
}
TEST(Tonlib, PublicKey) {
block::PublicKey::parse("pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").ensure_error();
auto key = block::PublicKey::parse("Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").move_as_ok();
@ -480,15 +296,15 @@ TEST(Tonlib, KeysApi) {
td::SecureString{}))
.move_as_ok();
sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()),
td::SecureString("wrong password"))))
.ensure_error();
//exportKey input_key:inputKey = ExportedKey;
//exportKey input_key:inputKeyRegular = ExportedKey;
auto exported_key =
sync_send(client,
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()), local_password.copy())))
.move_as_ok();
LOG(ERROR) << to_string(exported_key);
@ -500,20 +316,20 @@ TEST(Tonlib, KeysApi) {
return word_list_copy;
};
//changeLocalPassword input_key:inputKey new_local_password:bytes = Key;
//changeLocalPassword input_key:inputKeyRegular new_local_password:bytes = Key;
auto new_key =
sync_send(client,
make_object<tonlib_api::changeLocalPassword>(
make_object<tonlib_api::inputKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()), local_password.copy()),
td::SecureString("tmp local password")))
.move_as_ok();
sync_send(client,
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()), local_password.copy())))
.ensure_error();
auto exported_key2 = sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
auto exported_key2 = sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()),
td::SecureString("tmp local password"))))
.move_as_ok();
@ -531,7 +347,7 @@ TEST(Tonlib, KeysApi) {
auto wrong_export_password = td::SecureString("wrong_export password");
auto exported_encrypted_key =
sync_send(client, make_object<tonlib_api::exportEncryptedKey>(
make_object<tonlib_api::inputKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()),
td::SecureString("tmp local password")),
export_password.copy()))
@ -576,12 +392,12 @@ TEST(Tonlib, KeysApi) {
CHECK(imported_key->public_key_ == key->public_key_);
CHECK(imported_key->secret_ != key->secret_);
//exportPemKey input_key:inputKey key_password:bytes = ExportedPemKey;
//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<tonlib_api::exportPemKey>(
make_object<tonlib_api::inputKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(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") {

View file

@ -34,10 +34,12 @@
#include "ton/ton-tl.hpp"
#include "block/block.h"
#include "block/block-auto.h"
#include "Ed25519.h"
#include "tonlib/GenericAccount.h"
#include "tonlib/TestGiver.h"
#include "tonlib/TestWallet.h"
#include "smc-envelope/GenericAccount.h"
#include "smc-envelope/MultisigWallet.h"
#include "smc-envelope/TestGiver.h"
#include "smc-envelope/TestWallet.h"
#include "tonlib/LastBlock.h"
#include "tonlib/ExtClient.h"
#include "tonlib/utils.h"
@ -57,18 +59,21 @@
#include "td/utils/optional.h"
#include "td/utils/overloaded.h"
#include "td/utils/MpscPollableQueue.h"
#include "td/utils/port/path.h"
#include "td/utils/port/signals.h"
using namespace tonlib;
constexpr td::int64 Gramm = 1000000000;
auto sync_send = [](auto& client, auto query) {
using ReturnTypePtr = typename std::decay_t<decltype(*query)>::ReturnType;
using ReturnType = typename ReturnTypePtr::element_type;
client.send({1, std::move(query)});
while (true) {
auto response = client.receive(100);
if (response.object) {
if (response.object && response.id != 0) {
CHECK(response.id == 1);
if (response.object->get_id() == tonlib_api::error::ID) {
auto error = tonlib_api::move_object_as<tonlib_api::error>(response.object);
@ -78,87 +83,295 @@ auto sync_send = [](auto& client, auto query) {
}
}
};
auto static_send = [](auto query) {
using ReturnTypePtr = typename std::decay_t<decltype(*query)>::ReturnType;
using ReturnType = typename ReturnTypePtr::element_type;
auto response = Client::execute({1, std::move(query)});
if (response.object->get_id() == tonlib_api::error::ID) {
auto error = tonlib_api::move_object_as<tonlib_api::error>(response.object);
return td::Result<ReturnTypePtr>(td::Status::Error(error->code_, error->message_));
}
return td::Result<ReturnTypePtr>(tonlib_api::move_object_as<ReturnType>(response.object));
};
struct Key {
std::string public_key;
td::SecureString secret;
tonlib_api::object_ptr<tonlib_api::inputKey> get_input_key() const {
return tonlib_api::make_object<tonlib_api::inputKey>(
tonlib_api::object_ptr<tonlib_api::InputKey> get_input_key() const {
return tonlib_api::make_object<tonlib_api::inputKeyRegular>(
tonlib_api::make_object<tonlib_api::key>(public_key, secret.copy()), td::SecureString("local"));
}
tonlib_api::object_ptr<tonlib_api::InputKey> get_fake_input_key() const {
return tonlib_api::make_object<tonlib_api::inputKeyFake>();
}
};
struct Wallet {
std::string address;
Key key;
};
struct TransactionId {
td::int64 lt{0};
std::string hash;
};
struct AccountState {
enum Type { Empty, Wallet, Unknown } type{Empty};
td::int64 sync_utime{-1};
td::int64 balance{-1};
TransactionId last_transaction_id;
std::string address;
bool is_inited() const {
return type != Empty;
}
};
using tonlib_api::make_object;
void sync(Client& client) {
sync_send(client, make_object<tonlib_api::sync>()).ensure();
}
std::string wallet_address(Client& client, const Key& key) {
return sync_send(client, make_object<tonlib_api::wallet_getAccountAddress>(
make_object<tonlib_api::wallet_initialAccountState>(key.public_key)))
.move_as_ok()
->account_address_;
}
Wallet import_wallet_from_pkey(Client& client, std::string pkey, std::string password) {
auto key = sync_send(client, make_object<tonlib_api::importPemKey>(
td::SecureString("local"), td::SecureString(password),
make_object<tonlib_api::exportedPemKey>(td::SecureString(pkey))))
.move_as_ok();
Wallet wallet{"", {key->public_key_, std::move(key->secret_)}};
wallet.address = wallet_address(client, wallet.key);
return wallet;
}
std::string test_giver_address(Client& client) {
using tonlib_api::make_object;
return sync_send(client, make_object<tonlib_api::testGiver_getAccountAddress>()).move_as_ok()->account_address_;
}
td::int64 get_balance(Client& client, std::string address) {
AccountState get_account_state(Client& client, std::string address) {
auto generic_state = sync_send(client, tonlib_api::make_object<tonlib_api::generic_getAccountState>(
tonlib_api::make_object<tonlib_api::accountAddress>(address)))
.move_as_ok();
td::int64 res = 0;
tonlib_api::downcast_call(*generic_state, [&](auto& state) { res = state.account_state_->balance_; });
AccountState res;
tonlib_api::downcast_call(*generic_state, [&](auto& state) {
res.balance = state.account_state_->balance_;
res.sync_utime = state.account_state_->sync_utime_;
res.last_transaction_id.lt = state.account_state_->last_transaction_id_->lt_;
res.last_transaction_id.hash = state.account_state_->last_transaction_id_->hash_;
});
res.address = address;
switch (generic_state->get_id()) {
case tonlib_api::generic_accountStateUninited::ID:
res.type = AccountState::Empty;
break;
case tonlib_api::generic_accountStateWallet::ID:
res.type = AccountState::Wallet;
break;
default:
res.type = AccountState::Unknown;
break;
}
return res;
}
bool is_inited(Client& client, std::string address) {
auto generic_state = sync_send(client, tonlib_api::make_object<tonlib_api::generic_getAccountState>(
tonlib_api::make_object<tonlib_api::accountAddress>(address)))
.move_as_ok();
return generic_state->get_id() != tonlib_api::generic_accountStateUninited::ID;
struct QueryId {
td::int64 id;
};
struct Fee {
td::int64 in_fwd_fee{0};
td::int64 storage_fee{0};
td::int64 gas_fee{0};
td::int64 fwd_fee{0};
td::int64 sum() const {
return in_fwd_fee + storage_fee + gas_fee + fwd_fee;
}
};
template <class T>
auto to_fee(const T& fee) {
Fee res;
res.in_fwd_fee = fee->in_fwd_fee_;
res.storage_fee = fee->storage_fee_;
res.gas_fee = fee->gas_fee_;
res.fwd_fee = fee->fwd_fee_;
return res;
}
void transfer_grams(Client& client, std::string from, std::string to, td::int64 amount,
tonlib_api::object_ptr<tonlib_api::inputKey> input_key) {
auto balance = get_balance(client, to);
sync_send(client, tonlib_api::make_object<tonlib_api::generic_sendGrams>(
std::move(input_key), tonlib_api::make_object<tonlib_api::accountAddress>(from),
tonlib_api::make_object<tonlib_api::accountAddress>(to), amount, 0, true, "GIFT"))
.ensure();
while (balance == get_balance(client, to)) {
td::StringBuilder& operator<<(td::StringBuilder& sb, const Fee& fees) {
return sb << td::tag("in_fwd_fee", fees.in_fwd_fee) << td::tag("storage_fee", fees.storage_fee)
<< td::tag("gas_fee", fees.gas_fee) << td::tag("fwd_fee", fees.fwd_fee);
}
struct QueryInfo {
td::int64 valid_until;
std::string body_hash;
};
td::Result<QueryId> create_send_grams_query(Client& client, const Wallet& source, std::string destination,
td::int64 amount, std::string message, bool force = false, int timeout = 0,
bool fake = false) {
auto r_id = sync_send(client, tonlib_api::make_object<tonlib_api::generic_createSendGramsQuery>(
fake ? source.key.get_fake_input_key() : source.key.get_input_key(),
tonlib_api::make_object<tonlib_api::accountAddress>(source.address),
tonlib_api::make_object<tonlib_api::accountAddress>(destination), amount, timeout,
force, std::move(message)));
TRY_RESULT(id, std::move(r_id));
return QueryId{id->id_};
}
td::Result<QueryId> create_raw_query(Client& client, std::string source, std::string init_code, std::string init_data,
std::string body) {
auto r_id =
sync_send(client, tonlib_api::make_object<tonlib_api::raw_createQuery>(
tonlib_api::make_object<tonlib_api::accountAddress>(source), init_code, init_data, body));
TRY_RESULT(id, std::move(r_id));
return QueryId{id->id_};
}
std::pair<Fee, Fee> query_estimate_fees(Client& client, QueryId query_id, bool ignore_chksig = false) {
auto fees = sync_send(client, tonlib_api::make_object<tonlib_api::query_estimateFees>(query_id.id, ignore_chksig))
.move_as_ok();
return std::make_pair(to_fee(fees->source_fees_), to_fee(fees->destination_fees_));
}
void query_send(Client& client, QueryId query_id) {
sync_send(client, tonlib_api::make_object<tonlib_api::query_send>(query_id.id)).ensure();
}
QueryInfo query_get_info(Client& client, QueryId query_id) {
auto info = sync_send(client, tonlib_api::make_object<tonlib_api::query_getInfo>(query_id.id)).move_as_ok();
return QueryInfo{info->valid_until_, info->body_hash_};
}
td::Result<AccountState> wait_state_change(Client& client, const AccountState& old_state, td::int64 valid_until) {
while (true) {
auto new_state = get_account_state(client, old_state.address);
if (new_state.last_transaction_id.lt != old_state.last_transaction_id.lt) {
return new_state;
}
if (valid_until != 0 && new_state.sync_utime >= valid_until) {
return td::Status::Error("valid_until expired");
}
client.receive(1);
}
};
td::Result<tonlib_api::object_ptr<tonlib_api::raw_transactions>> get_transactions(Client& client, std::string address,
const TransactionId& from) {
auto got_transactions = sync_send(client, make_object<tonlib_api::raw_getTransactions>(
make_object<tonlib_api::accountAddress>(address),
make_object<tonlib_api::internal_transactionId>(from.lt, from.hash)))
.move_as_ok();
return std::move(got_transactions);
}
td::Status transfer_grams(Client& client, const Wallet& wallet, std::string address, td::int64 amount) {
auto src_state = get_account_state(client, wallet.address);
auto dst_state = get_account_state(client, address);
auto message = td::rand_string('a', 'z', 500);
LOG(INFO) << "Transfer: create query " << (double)amount / Gramm << " from " << wallet.address << " to " << address;
auto r_query_id = create_send_grams_query(client, wallet, address, amount, message);
if (r_query_id.is_error() && td::begins_with(r_query_id.error().message(), "DANGEROUS_TRANSACTION")) {
ASSERT_TRUE(dst_state.type == AccountState::Empty);
LOG(INFO) << "Transfer: recreate query due to DANGEROUS_TRANSACTION error";
r_query_id = create_send_grams_query(client, wallet, address, amount, message, true);
}
r_query_id.ensure();
QueryId query_id = r_query_id.move_as_ok();
auto query_info = query_get_info(client, query_id);
auto fees = query_estimate_fees(client, query_id);
LOG(INFO) << "Expected src fees: " << fees.first;
LOG(INFO) << "Expected dst fees: " << fees.second;
bool transfer_all = amount == src_state.balance;
if (!transfer_all && amount + fees.first.sum() + 10 > src_state.balance) {
return td::Status::Error("Not enough balance for query");
}
LOG(INFO) << "Transfer: send query";
query_send(client, query_id);
td::Timer timer;
TRY_RESULT(new_src_state, wait_state_change(client, src_state, query_info.valid_until));
LOG(INFO) << "Transfer: reached source in " << timer;
td::int64 lt;
td::int64 first_fee;
{
auto tr = get_transactions(client, src_state.address, new_src_state.last_transaction_id).move_as_ok();
CHECK(tr->transactions_.size() > 0);
const auto& txn = tr->transactions_[0];
CHECK(txn->in_msg_->body_hash_ == query_info.body_hash);
ASSERT_EQ(1u, txn->out_msgs_.size());
ASSERT_EQ(message, txn->out_msgs_[0]->message_);
lt = txn->out_msgs_[0]->created_lt_;
auto fee_difference = fees.first.sum() - txn->fee_;
first_fee = txn->fee_;
auto desc = PSTRING() << fee_difference << " storage:[" << fees.first.storage_fee << " vs " << txn->storage_fee_
<< "] other:[" << fees.first.sum() - fees.first.storage_fee << " vs " << txn->other_fee_
<< "]";
LOG(INFO) << "Source fee difference " << desc;
LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big source fee difference " << desc;
}
TRY_RESULT(new_dst_state, wait_state_change(client, dst_state, new_src_state.sync_utime + 30));
LOG(INFO) << "Transfer: reached destination in " << timer;
{
auto tr = get_transactions(client, dst_state.address, new_dst_state.last_transaction_id).move_as_ok();
CHECK(tr->transactions_.size() > 0);
const auto& txn = tr->transactions_[0];
ASSERT_EQ(lt, txn->in_msg_->created_lt_);
if (transfer_all) {
ASSERT_EQ(amount - first_fee, txn->in_msg_->value_);
} else {
ASSERT_EQ(new_src_state.address, txn->in_msg_->source_);
}
ASSERT_EQ(new_src_state.address, txn->in_msg_->source_);
ASSERT_EQ(message, txn->in_msg_->message_);
auto fee_difference = fees.second.sum() - txn->fee_;
auto desc = PSTRING() << fee_difference << " storage:[" << fees.second.storage_fee << " vs " << txn->storage_fee_
<< "] other:[" << fees.second.sum() - fees.second.storage_fee << " vs " << txn->other_fee_
<< "]";
LOG(INFO) << "Destination fee difference " << desc;
LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big destination fee difference " << desc;
}
return td::Status::OK();
}
Wallet create_empty_wallet(Client& client) {
using tonlib_api::make_object;
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(td::SecureString("local"),
td::SecureString("mnemonic"), td::SecureString()))
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(td::SecureString("local"), td::SecureString(),
td::SecureString()))
.move_as_ok();
Wallet wallet{"", {key->public_key_, std::move(key->secret_)}};
auto account_address =
sync_send(client, make_object<tonlib_api::testWallet_getAccountAddress>(
make_object<tonlib_api::testWallet_initialAccountState>(wallet.key.public_key)))
sync_send(client, make_object<tonlib_api::wallet_getAccountAddress>(
make_object<tonlib_api::wallet_initialAccountState>(wallet.key.public_key)))
.move_as_ok();
wallet.address = account_address->account_address_;
// get state of empty account
auto state = get_account_state(client, wallet.address);
ASSERT_EQ(-1, state.balance);
ASSERT_EQ(AccountState::Empty, state.type);
return wallet;
}
Wallet create_wallet(Client& client) {
using tonlib_api::make_object;
auto wallet = create_empty_wallet(client);
transfer_grams(client, test_giver_address(client), wallet.address, 6000000000, {});
sync_send(client, make_object<tonlib_api::testWallet_init>(wallet.key.get_input_key())).ensure();
while (!is_inited(client, wallet.address)) {
client.receive(1);
}
LOG(ERROR) << get_balance(client, wallet.address);
return wallet;
}
std::string get_test_giver_address(Client& client) {
return sync_send(client, tonlib_api::make_object<tonlib_api::testGiver_getAccountAddress>())
.move_as_ok()
->account_address_;
}
void dump_transaction_history(Client& client, std::string address) {
using tonlib_api::make_object;
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
@ -180,166 +393,156 @@ void dump_transaction_history(Client& client, std::string address) {
LOG(ERROR) << cnt;
}
void test_estimate_fees_without_key(Client& client, const Wallet& wallet_a, const Wallet& wallet_b) {
LOG(ERROR) << " SUBTEST: estimate fees without key";
{
auto query_id = create_send_grams_query(client, wallet_a, wallet_b.address, 0, "???", true, 0, true).move_as_ok();
auto fees1 = query_estimate_fees(client, query_id, false);
auto fees2 = query_estimate_fees(client, query_id, true);
LOG(INFO) << "Fee without ignore_chksig\t" << fees1;
LOG(INFO) << "Fee with ignore_chksig\t" << fees2;
CHECK(fees1.first.gas_fee == 0);
CHECK(fees2.first.gas_fee != 0);
}
}
void test_back_and_forth_transfer(Client& client, const Wallet& giver_wallet, bool flag) {
LOG(ERROR) << "TEST: back and forth transfer";
// just generate private key and address
auto wallet_a = create_empty_wallet(client);
LOG(INFO) << wallet_a.address;
// get state of empty account
auto state = get_account_state(client, wallet_a.address);
ASSERT_EQ(-1, state.balance);
ASSERT_EQ(AccountState::Empty, state.type);
test_estimate_fees_without_key(client, giver_wallet, wallet_a);
// transfer from giver to a
transfer_grams(client, giver_wallet, wallet_a.address, 1 * Gramm).ensure();
state = get_account_state(client, wallet_a.address);
ASSERT_EQ(1 * Gramm, state.balance);
ASSERT_EQ(AccountState::Empty, state.type);
test_estimate_fees_without_key(client, wallet_a, giver_wallet);
if (flag) {
// transfer from a to giver
transfer_grams(client, wallet_a, giver_wallet.address, 5 * Gramm / 10).ensure();
state = get_account_state(client, wallet_a.address);
ASSERT_TRUE(state.balance < 5 * Gramm / 10);
ASSERT_EQ(AccountState::Wallet, state.type);
}
// transfer all remaining balance (test flag 128)
transfer_grams(client, wallet_a, giver_wallet.address, state.balance).ensure();
state = get_account_state(client, wallet_a.address);
ASSERT_TRUE(state.balance == 0);
ASSERT_EQ(AccountState::Wallet, state.type);
}
void test_multisig(Client& client, const Wallet& giver_wallet) {
LOG(ERROR) << "TEST: multisig";
int n = 16;
int k = 10;
std::vector<td::Ed25519::PrivateKey> private_keys;
for (int i = 0; i < n; i++) {
private_keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
}
auto ms = ton::MultisigWallet::create();
auto init_data = ms->create_init_data(
td::transform(private_keys, [](const auto& pk) { return pk.get_public_key().move_as_ok().as_octet_string(); }),
k);
ms = ton::MultisigWallet::create(init_data);
auto raw_address = ms->get_address(ton::basechainId);
auto address = raw_address.rserialize();
transfer_grams(client, giver_wallet, address, 1 * Gramm).ensure();
auto init_state = ms->get_init_state();
// Just transfer all (some) money back in one query
vm::CellBuilder icb;
ton::GenericAccount::store_int_message(icb, block::StdAddress::parse(giver_wallet.address).move_as_ok(),
5 * Gramm / 10);
icb.store_bytes("\0\0\0\0", 4);
vm::CellString::store(icb, "Greatings from multisig", 35 * 8).ensure();
ton::MultisigWallet::QueryBuilder qb(-1, icb.finalize());
for (int i = 0; i < k - 1; i++) {
qb.sign(i, private_keys[i]);
}
auto query_id =
create_raw_query(client, address, vm::std_boc_serialize(ms->get_state().code).move_as_ok().as_slice().str(),
vm::std_boc_serialize(ms->get_state().data).move_as_ok().as_slice().str(),
vm::std_boc_serialize(qb.create(k - 1, private_keys[k - 1])).move_as_ok().as_slice().str())
.move_as_ok();
auto fees = query_estimate_fees(client, query_id);
LOG(INFO) << "Expected src fees: " << fees.first;
LOG(INFO) << "Expected dst fees: " << fees.second;
auto a_state = get_account_state(client, address);
query_send(client, query_id);
auto new_a_state = wait_state_change(client, a_state, a_state.sync_utime + 30).move_as_ok();
}
int main(int argc, char* argv[]) {
td::set_default_failure_signal_handler();
using tonlib_api::make_object;
td::OptionsParser p;
std::string global_config_str;
std::string giver_key_str;
std::string giver_key_pwd = "cucumber";
std::string keystore_dir = "test-keystore";
bool reset_keystore_dir = false;
p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) {
TRY_RESULT(str, td::read_file_str(fname.str()));
global_config_str = std::move(str);
LOG(ERROR) << global_config_str;
return td::Status::OK();
});
p.add_option('G', "giver-key", "file with a wallet key that should be used as a giver", [&](td::Slice fname) {
TRY_RESULT(str, td::read_file_str(fname.str()));
giver_key_str = std::move(str);
return td::Status::OK();
});
p.add_option('f', "force", "reser keystore dir", [&]() {
reset_keystore_dir = true;
return td::Status::OK();
});
p.run(argc, argv).ensure();
if (reset_keystore_dir) {
td::rmrf(keystore_dir).ignore();
td::mkdir(keystore_dir).ensure();
}
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
static_send(make_object<tonlib_api::setLogTagVerbosityLevel>("tonlib_query", 4)).ensure();
auto tags = static_send(make_object<tonlib_api::getLogTags>()).move_as_ok()->tags_;
for (auto& tag : tags) {
static_send(make_object<tonlib_api::setLogTagVerbosityLevel>(tag, 4)).ensure();
}
Client client;
{
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
make_object<tonlib_api::config>(global_config_str, "", false, false),
make_object<tonlib_api::keyStoreTypeDirectory>("."))))
make_object<tonlib_api::keyStoreTypeDirectory>(keystore_dir))))
.ensure();
}
//dump_transaction_history(client, get_test_giver_address(client));
auto wallet_a = create_wallet(client);
auto wallet_b = create_empty_wallet(client);
transfer_grams(client, wallet_a.address, wallet_b.address, 3000000000, wallet_a.key.get_input_key());
auto a = get_balance(client, wallet_a.address);
auto b = get_balance(client, wallet_b.address);
LOG(ERROR) << a << " " << b;
return 0;
{
// init
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
make_object<tonlib_api::config>(global_config_str, "", false, false),
make_object<tonlib_api::keyStoreTypeDirectory>("."))))
.ensure();
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(
td::SecureString("local"), td::SecureString("mnemonic"), td::SecureString()))
.move_as_ok();
// wait till client is synchronized with blockchain.
// not necessary, but synchronized will be trigged anyway later
sync(client);
auto create_input_key = [&] {
return make_object<tonlib_api::inputKey>(make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()),
td::SecureString("local"));
};
// give wallet with some test grams to run test
auto giver_wallet = import_wallet_from_pkey(client, giver_key_str, giver_key_pwd);
auto public_key_raw = key->public_key_;
td::Ed25519::PublicKey public_key_std(td::SecureString{public_key_raw});
sync_send(client, make_object<tonlib_api::options_setConfig>(
make_object<tonlib_api::config>(global_config_str, "", false, false)))
.ensure();
auto wallet_addr = GenericAccount::get_address(0, TestWallet::get_init_state(public_key_std));
{
auto account_address =
sync_send(client, make_object<tonlib_api::testWallet_getAccountAddress>(
make_object<tonlib_api::testWallet_initialAccountState>(public_key_raw)))
.move_as_ok();
ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_);
}
std::string test_giver_address;
{
auto account_address = sync_send(client, make_object<tonlib_api::testGiver_getAccountAddress>()).move_as_ok();
test_giver_address = account_address->account_address_;
ASSERT_EQ(TestGiver::address().rserialize(), test_giver_address);
}
{
auto account_address =
sync_send(
client,
make_object<tonlib_api::raw_getAccountAddress>(make_object<tonlib_api::raw_initialAccountState>(
vm::std_boc_serialize(TestWallet::get_init_code()).move_as_ok().as_slice().str(),
vm::std_boc_serialize(TestWallet::get_init_data(public_key_std)).move_as_ok().as_slice().str())))
.move_as_ok();
ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_);
}
{
auto state = sync_send(client, make_object<tonlib_api::raw_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
.move_as_ok();
LOG(ERROR) << to_string(state);
}
td::int32 seqno = 0;
{
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
LOG(ERROR) << to_string(state);
seqno = state->seqno_;
}
{
sync_send(client, make_object<tonlib_api::testGiver_sendGrams>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize()), seqno,
1000000000ll * 6666 / 1000, "GIFT"))
.ensure();
}
while (true) {
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
if (state->seqno_ > seqno) {
break;
}
client.receive(1);
}
while (true) {
auto state = sync_send(client, make_object<tonlib_api::raw_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
.move_as_ok();
td::int64 grams_count = state->balance_;
if (grams_count > 0) {
LOG(ERROR) << "GOT " << grams_count;
break;
}
client.receive(1);
}
{ sync_send(client, make_object<tonlib_api::testWallet_init>(create_input_key())).ensure(); }
while (true) {
auto r_state = sync_send(client, make_object<tonlib_api::testWallet_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())));
if (r_state.is_ok()) {
LOG(ERROR) << to_string(r_state.ok());
break;
}
client.receive(1);
}
{
sync_send(client, make_object<tonlib_api::generic_sendGrams>(
create_input_key(), make_object<tonlib_api::accountAddress>(wallet_addr.rserialize()),
make_object<tonlib_api::accountAddress>(test_giver_address), 1000000000ll * 3333 / 1000, 0,
true, "GIFT"))
.ensure();
}
while (true) {
auto generic_state = sync_send(client, make_object<tonlib_api::generic_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
.move_as_ok();
if (generic_state->get_id() == tonlib_api::generic_accountStateTestWallet::ID) {
auto state = tonlib_api::move_object_as<tonlib_api::generic_accountStateTestWallet>(generic_state);
if (state->account_state_->balance_ < 5617007000) {
LOG(ERROR) << to_string(state);
break;
}
}
client.receive(1);
}
{
auto generic_state = sync_send(client, make_object<tonlib_api::generic_getAccountState>(
make_object<tonlib_api::accountAddress>(test_giver_address)))
.move_as_ok();
CHECK(generic_state->get_id() == tonlib_api::generic_accountStateTestGiver::ID);
LOG(ERROR) << to_string(generic_state);
}
}
test_back_and_forth_transfer(client, giver_wallet, false);
test_back_and_forth_transfer(client, giver_wallet, true);
test_multisig(client, giver_wallet);
return 0;
}